Code Monkey home page Code Monkey logo

strapi-plugin-config-sync's Introduction

Strapi config-sync plugin

This plugin is a multi-purpose tool to manage your Strapi database records through JSON files. Mostly used to version control config data for automated deployment, automated tests and data sharing for collaboration purposes.

NPM Version Monthly download on NPM CI build status codecov.io

Table of Contents

✨ Features

  • CLI - config-sync CLI for syncing the config from the command line
  • GUI - Settings page for syncing the config in Strapi admin
  • Partial sync - Import or export only specific portions of config
  • Custom types - Include your custom collection types in the sync process
  • Import on bootstrap - Easy automated deployment with importOnBootstrap
  • Exclusion - Exclude single config entries or all entries of a given type
  • Diff viewer - A git-style diff viewer to inspect the config changes

⏳ Installation

Install the plugin in your Strapi project.

# using yarn
yarn add strapi-plugin-config-sync

# using npm
npm install strapi-plugin-config-sync --save

Add the export path to the watchIgnoreFiles list in the config/admin.js file. This way your app won't reload when you export the config in development.

config/admin.js:
module.exports = ({ env }) => ({
  // ...
  watchIgnoreFiles: [
    '**/config/sync/**',
  ],
});

After successful installation you have to rebuild the admin UI so it'll include this plugin. To rebuild and restart Strapi run:

# using yarn
yarn build
yarn develop

# using npm
npm run build
npm run develop

The Config Sync plugin should now appear in the Settings section of your Strapi app.

To start tracking your config changes you have to make the first export. This will dump all your configuration data to the /config/sync directory. You can export either through the CLI or Strapi admin panel

Enjoy 🎉

🖐 Requirements

Complete installation requirements are the exact same as for Strapi itself and can be found in the Strapi documentation.

Supported Strapi versions:

  • Strapi 4.14.4 (recently tested)
  • Strapi ^4.x (use strapi-plugin-config-sync@^1.0.0)
  • Strapi ^3.4.x (use [email protected])

💡 Motivation

In Strapi we come across what I would call config types. These are models of which the records are stored in our database, just like content types. Though the big difference here is that your code ofter relies on the database records of these types.

Having said that, it makes sense that these records can be exported, added to git, and be migrated across environments. This way we can make sure we have all the data our code relies on, on each environment.

Examples of these types are:

  • Admin roles (admin::role)
  • User roles (plugin::users-permissions.role)
  • Admin settings (strapi::core-store)
  • I18n locale (plugin::i18n.locale)

This plugin gives you the tools to sync this data. You can export the data as JSON files on one env, and import them on every other env. By writing this data as JSON files you can easily track them in your version control system (git).

With great power comes great responsibility - Spider-Man

🔌 Command line interface (CLI)

Add the config-sync command as a script to the package.json of your Strapi project:

"scripts": {
  // ...
  "cs": "config-sync"
},

You can now run all the config-sync commands like this:

# using yarn
yarn cs --help

# using npm
npm run cs --help

⬆️ Import ⬇️ Export

Command: import Alias: i

Command: export Alias: e

These commands are used to sync the config in your Strapi project.

Example:

# using yarn
yarn cs import
yarn cs export

# using npm
npm run cs import
npm run cs export
Flag: -y, --yes

Use this flag to skip the confirm prompt and go straight to syncing the config.

[command] --yes
Flag: -t, --type

Use this flag to specify the type of config you want to sync.

[command] --type user-role
Flag: -p, --partial

Use this flag to sync a specific set of configs by giving the CLI a comma-separated string of config names.

[command] --partial user-role.public,i18n-locale.en
Flag: -f, --force

If you're using the soft setting to gracefully import config, you can use this flag to ignore the setting for the current command and forcefully import all changes anyway.

[command] --force

↔️ Diff

Command: diff | Alias: d

This command is used to see the difference between the config as found in the sync directory, and the config as found in the database.

Example:

# using yarn
yarn cs diff

# using npm
npm run cs diff
Argument: <single>

Add a single config name as the argument of the diff command to see the difference of that single file in a git-style diff viewer.

Example:

# using yarn
yarn cs diff user-role.public

# using npm
npm run cs diff user-role.public

🖥️ Admin panel (GUI)

This plugin ships with a React app which can be accessed from the settings page in Strapi admin panel. On this page you can pretty much do the same as you can from the CLI. You can import, export and see the difference between the config as found in the sync directory, and the config as found in the database.

Pro tip: By clicking on one of the items in the diff table you can see the exact difference between sync dir and database in a git-style diff viewer.

Config diff in admin

⌨️ Usage / Workflow

This plugin works best when you use git for the version control of your Strapi project.

The following workflows are assuming you're using git.

Intro

All database records tracked with this plugin will be exported to JSON files. Once exported each change to the file or the record will be tracked. Meaning you can now do one of two things:

  • Change the file(s), and run an import. You have now imported from filesystem -> database.
  • Change the record(s), and run an export. You have now exported from database -> filesystem.

Local development

When building a new feature locally for your Strapi project you'd use the following workflow:

  • Build the feature.
  • Export the config.
  • Commit and push the files to git.

Deployment

When deploying the newly created feature - to either a server, or a co-worker's machine - you'd use the following workflow:

  • Pull the latest file changes to the environment.
  • (Re)start your Strapi instance.
  • Import the config.

Production deployment

The production deployment will be the same as a regular deployment. You just have to be careful before running the import. Ideally making sure the are no open changes before you pull the new code to the environment.

🚀 Config types

By default the plugin will track 4 (official) types.

To track your own custom types you can register them by setting some plugin config.

Default types

These 4 types are by default registered in the sync process.

Admin role

Config name: admin-role | UID: code | Query string: admin::role

User role

Config name: user-role | UID: type | Query string: plugin::users-permissions.role

Core store

Config name: core-store | UID: key | Query string: strapi::core-store

I18n locale

Config name: i18n-locale | UID: code | Query string: plugin::i18n.locale

Custom types

Your custom types can be registered through the customTypes plugin config. This is a setting that can be set in the config/plugins.js file in your project.

Read more about the config/plugins.js file here.

You can register a type by giving the customTypes array an object which contains at least the following 3 properties:

customTypes: [{
  configName: 'webhook',
  queryString: 'webhook',
  uid: 'name',
}],

The example above will register the Strapi webhook type.

Config name

The name of the config type. This value will be used as the first part of the filename for all config of this type. It should be unique from the other types and is preferably written in kebab-case.

Key: configName

required: YES | type: string

Query string

This is the query string of the type. Each type in Strapi has its own query string you can use to programatically preform CRUD actions on the entries of the type. Often for custom types in Strapi the format is something like api::custom-api.custom-type.

Key: queryString

required: YES | type: string

UID

The UID represents a field on the registered type. The value of this field will act as a unique identifier to identify the entries across environments. Therefore it should be unique and preferably un-editable after initial creation.

Mind that you can not use an auto-incremental value like the id as auto-increment does not play nice when you try to match entries across different databases.

If you do not have a single unique value, you can also pass in an array of keys for a combined uid key. This is for example the case for all content types which use i18n features (An example config would be uid: ['productId', 'locale']).

Key: uid

required: YES | type: string | string[]

Relations

The relations array specifies the relations you want to include in the sync process. This feature is used to sync the relations between roles and permissions. See https://github.com/boazpoolman/strapi-plugin-config-sync/blob/master/server/config/types.js#L16.

Example:

{
  configName: 'admin-role',
  queryString: 'admin::role',
  uid: 'code',
  relations: [{
    queryString: 'admin::permission',
    relationName: 'permissions',
    parentName: 'role',
    relationSortFields: ['action', 'subject'],
  }],
},
Key: relations

required: NO | type: array

Components

This property can accept an array of component names from the type. Strapi Components can be included in the export/import process. With "." nested components can also be included in the process.

customTypes: [{
  configName: 'webhook',
  queryString: 'webhook',
  uid: 'name',
  components: ['ParentComponentA', 'ParentComponentA.ChildComponent', 'ParentComponentB']
}],
Key: components

required: NO | type: array

JSON fields

This property can accept an array of field names from the type. It is meant to specify the JSON fields on the type so the plugin can better format the field values when calculating the config difference.

Key: jsonFields

required: NO | type: array

🔍 Naming convention

All the config files written in the sync directory have the same naming convention. It goes as follows:

[config-type].[identifier].json
  • config-type - Corresponds to the configName of the config type.
  • identifier - Corresponds to the value of the uid field of the config type.

🔧 Settings

The settings of the plugin can be overridden in the config/plugins.js file. In the example below you can see how, and also what the default settings are.

config/plugins.js:
module.exports = ({ env }) => ({
  // ...
  'config-sync': {
    enabled: true,
    config: {
      syncDir: "config/sync/",
      minify: false,
      soft: false,
      importOnBootstrap: false,
      customTypes: [],
      excludedTypes: [],
      excludedConfig: [
        "core-store.plugin_users-permissions_grant",
      	"core-store.plugin_upload_metrics",
      	"core-store.strapi_content_types_schema",
	"core-store.ee_information",
      ],
    },
  },
});

Sync dir

The path for reading and writing the sync files.

Key: syncDir

required: YES | type: string | default: config/sync/

Minify

When enabled all the exported JSON files will be minified.

Key: minify

required: NO | type: bool | default: false

Soft

When enabled the import action will be limited to only create new entries. Entries to be deleted, or updated will be skipped from the import process and will remain in it's original state.

Key: soft

required: NO | type: bool | default: false

Import on bootstrap

Allows you to let the config be imported automaticly when strapi is bootstrapping (on strapi start). This setting can't be used locally and should be handled very carefully as it can unintendedly overwrite the changes in your database. PLEASE USE WITH CARE.

Key: importOnBootstrap

required: NO | type: bool | default: false

Custom types

With this setting you can register your own custom config types. This is an array which expects objects with at least the configName, queryString and uid properties. Read more about registering custom types in the Custom config types documentation.

Key: customTypes

required: NO | type: array | default: []

Excluded types

This setting will exclude all the config from a given type from the syncing process. The config types are specified by the configName of the type.

For example:

excludedTypes: ['admin-role']
Key: excludedTypes

required: NO | type: array | default: []

Excluded config

Specify the names of configs you want to exclude from the syncing process. By default the API tokens for users-permissions, which are stored in core_store, are excluded. This setting expects the config names to comply with the naming convention.

Key: excludedConfig

required: NO | type: array | default: ['core-store.plugin_users-permissions_grant', 'core-store.plugin_upload_metrics', 'core-store.strapi_content_types_schema', 'core-store.ee_information',]

🤝 Contributing

Feel free to fork and make a pull request of this plugin. All the input is welcome!

⭐️ Show your support

Give a star if this project helped you.

🔗 Links

🌎 Community support

📝 Resources

strapi-plugin-config-sync's People

Contributors

alexzrp avatar boazpoolman avatar dependabot[bot] avatar goodhoko avatar karlkeefer avatar liarco avatar lucanerlich avatar msacc avatar nihey avatar philipppaoli avatar semiaddict avatar techwolf12 avatar tilman avatar timraasveld avatar yonghunj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

strapi-plugin-config-sync's Issues

Add ability to track webhooks

Feature request

Add ability to track webhooks

Summary

Track webhooks as config as well, so that when deploying/migrating from env to env we have this tracked, and not create manually for each env.

Why is it needed?

devops style in mind.

Suggested solution(s)

Should be pretty easy as we track almost all other changes.

Usage with jest unstable

I tried to used this plugin together with Jest.

I thought I could setup the user permissions using strapi admin, export the settings using this plugin and load those settings during the test runs with jest. So I do not have to manually setup the permissions during each test run.

This first worked for me, but I then noticed errors:
Sometimes, the settings do not get imported.

Example:
When running npm test 5 times, it will likely work 4 times but 1 time it will fail, without anything changed in between.
In my example I have some routes wich should only be accessible for authorized users. Most of the time the requests work but sometimes I get forbidden errors.

error.mov

I suspect that the import runs asynchronously in the background and sometimes the import is not completed before the tests run. But I am not sure about that.

Is there a reference implementation for use with Jest?

I setup Jest as Described in the strapi docs:
https://strapi.io/documentation/developer-docs/latest/guides/unit-testing.html#testing-basic-endpoint-controller

this is my config-sync config:

module.exports = ({ env }) => ({
  'config-sync': {
    destination: "extensions/config-sync/files/",
    minify: false,
    importOnBootstrap: true,
    include: [
      "core-store",
      "role-permissions"
    ],
    exclude: [
      "core-store.plugin_users-permissions_grant"
    ]
  },
});

This is the test that's running in my example video:

  it('should create ticket', async () => {
    await request(strapi.server) // app server is an instance of Class: http.Server
      .post('/tickets')
      .set('accept', 'application/json')
      .set('Content-Type', 'application/json')
      .set('Authorization', 'Bearer ' + jwt)
      .send({
        ...ticketData
      })
      .expect('Content-Type', /json/)
      .expect(200)
      .then(data => {
        expect(data.body).toBeDefined();
        expect(data.body.title).toBe(ticketData.title);
      })
  });

Config split (env specific config)

Feature request

Summary

Have subdirectories inside config folder like dev or prod. In there you could place config files to overwrite the config for specific environments.

Why is it needed?

To have different config in different environments.

Suggested solution(s)

Subdirectories inside the sync folder in which you can override the config. Could be used for env specific config, but maby also for logic based config split.

Sync admin roles & permissions

Similar to how we sync the roles & permissions from the users-permissions plugin, I would like for this plugin to be able to sync the admin roles & permissions as well.

Replace "react-diff-viewer" dependency with "react-diff-viewer-continued"

Bug report

Describe the bug

I get the following warnings when installing strapi-plugin-config-sync:

warning "strapi-plugin-config-sync > [email protected]" has incorrect peer dependency "react@^15.3.0 || ^16.0.0".
warning "strapi-plugin-config-sync > [email protected]" has incorrect peer dependency "react-dom@^15.3.0 || ^16.0.0".

react-diff-viewer seems to be abandoned (last update was in May 2020) and there is a successor fork called react-diff-viewer-continued (npm package).

Steps to reproduce the behavior

  1. Use yarn
  2. Have a React version higher than v16.x installed (or no React installed, which would cause a has unmet peer dependency warning)
  3. Run yarn add strapi-plugin-config-sync
  4. See error

Expected behavior

Use currently maintained dependencies which don't cause warnings

Screenshots

image

System

  • Node.js version: 16.18.0 LTS
  • NPM version: 8.19.2
  • Strapi version: 4.4.7
  • Plugin version: 1.0.4
  • Database: SQLite
  • Operating system: macOS

Dowload config

Feature request

Be able to download config files.

Summary

I use strapi in Google Run, and as many dockerized containers used in production it is sometimes hard to have cli or file access. Therefore it would be great to have a button to download the exported config files from production environments.

Why is it needed?

Can't access exported config in dockerized containers without access to cli or files.

Suggested solution(s)

Have a button to download exported files.

Add 'soft' setting to importOnBootstrap to prevent overwriting of existing changes in DB

Originally posted by @BabyDino in #68 (comment)

When we roll out an API we would love to use the importOnBootstrap setting, but we want to prevent existing changed data of being reimported.

One example:

  • We have a table with e-mail templates. In our dev, we export staging/production ready email templates.
  • We'd like to use importOnBootstrap on staging/production, which would be ideal for a first run.
  • Now we change an imported template in staging or production
  • On the next run, importOnBootstrap overrides the customized template with the dev template.

So basically, if the state is Different for a template, we would like to have that entry skipped by importOnBootstrap. We only like to import the state Only in sync dir with importOnBootstrap.

Roll-back to before the import

Doing a config import can be very drastic, and potentially damaging when you don't have a database backup.
I would like to suggest saving a copy of the config as found in the database just before you do an import.

This way you should be able to roll-back to the config as it was found in your database right before you made the import.

Return non-zero status when diff is not empty.

Feature request

Summary

Make config-sync diff return non-zero status in CLI when the diff is not empty.

Why is it needed?

I'd like to use config-sync diff in my pre-commit git hook to check I didn't forget to export the config before committing (and pushing). Currently, I have to parse the command's output. Returning non-zero status when the diff is not empty would make this easier and also would bring the command's API in line with the POSIX diff and diff commands on most systems.

Suggested solution(s)

Just make the return value conditional on the diff contents. Alternatively, provide a --check option to enable this behaviour.

Related issue(s)/PR(s)

Didn't find any.


I'll be happy to submit a PR if you agree with this proposal. Oh and thanks for all you work on this plugin! 🖤

Is it possible to ignore some files?

Is it possible to ignore some specific files?

I have a plugin that is saving a token in the database. This token differs between production and development. I wouldn't want to track its changes.

Screen Shot 2022-06-22 at 12 14 49 PM

[BUG] Database error when trying to delete the `strapi-super-admin` role & an i18n locale at the same time

Bug report

We are testing this plugin in order to avoid additional steps to duplicate our development admin to production.

Importing all the updated files from a previous environment, we found this bug.

[2022-08-13 18:48:57.435] error: error: insert into "admin_permissions" ("action", "conditions", "created_at", "id", "properties", "subject", "updated_at") values ($1, $2, $3, $4, $5, $6, $7) returning "id" - duplicate key value violates unique constraint "admin_permissions_pkey"
Error: error: insert into "admin_permissions" ("action", "conditions", "created_at", "id", "properties", "subject", "updated_at") values ($1, $2, $3, $4, $5, $6, $7) returning "id" - duplicate key value violates unique constraint "admin_permissions_pkey"
    at Object.importSingleConfig (/srv/app/node_modules/strapi-plugin-config-sync/server/services/main.js:234:13)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async /srv/app/node_modules/strapi-plugin-config-sync/server/controllers/config.js:57:7
    at async Promise.all (index 9)
    at async Object.importAll (/srv/app/node_modules/strapi-plugin-config-sync/server/controllers/config.js:56:5)
    at async returnBodyMiddleware (/srv/app/node_modules/@strapi/strapi/lib/services/server/compose-endpoint.js:52:18)
    at async policiesMiddleware (/srv/app/node_modules/@strapi/strapi/lib/services/server/policy.js:24:5)
    at async transform (/srv/app/node_modules/strapi-plugin-transformer/server/middleware/transform.js:9:2)
    at async /srv/app/node_modules/@strapi/strapi/lib/middlewares/body.js:51:9
    at async /srv/app/node_modules/@strapi/strapi/lib/middlewares/logger.js:22:5
    at async /srv/app/node_modules/@strapi/strapi/lib/middlewares/powered-by.js:16:5
    at async cors (/srv/app/node_modules/@koa/cors/index.js:95:16)
    at async /srv/app/node_modules/@strapi/strapi/lib/middlewares/errors.js:13:7
    at async session (/srv/app/node_modules/koa-session/index.js:41:7)
    at async /srv/app/node_modules/@strapi/strapi/lib/services/metrics/middleware.js:29:5

Steps to reproduce the behavior

  1. Go to Config > Config Sync > Tool.
  2. Click on Import.
  3. Look at the toast with the warning.
  4. Go to container logs and find the bug.
  5. Go to Content Management.
  6. Look at some of the models that are missing.
  7. Come back to Config > Config Sync > Tool.
  8. Click on Import again.
  9. Look at a toast with everything working well.
  10. Go back to Content Management.
  11. Find the missing models on the admin.

Expected behavior

It would not show any problem at importing configuration.

Screenshots

If applicable, add screenshots to help explain your problem.

Code snippets

If applicable, add code samples to help explain your problem.

System

  • Node.js version: 4.3.2 (node v14.20.0)
  • Strapi version: 4.3.2
  • Plugin version: 2.0.8
  • Database: PostgreSQL 13.7
  • Operating system: Alpine Linux v3.16

Additional context

Add any other context about the problem here.

Documentation uses `config-sync` command before adding it

Bug report

Describe the bug

When following the README, npm run config-sync export is used before instructions appear to add it to package.json, and I had this error:

PS D:\GIT\hub-cms> npm run config-sync export
npm ERR! Missing script: "config-sync"
npm ERR!
npm ERR! To see a list of scripts, run:
npm ERR!   npm run

It was not available through npm run:

PS D:\GIT\hub-cms> npm run
Lifecycle scripts included in XXX:
  start
    strapi start

available via `npm run-script`:
  develop
    strapi develop
  build
    strapi build
  strapi
    strapi

Expected behavior

Be able to follow the README step by step. Instruct the user to add the command (either as cs or config-sync which should then always be used as such in the rest of the docs)

Can I export in v3 and import in v4?

Question

Summary

Can I export the models from v3 and import them in v4? I know that this will be no backup database and this is not what I want, since my project is in dev phase, but I want to upgrade Strapi and keep the same model.

Differences tool is giving the changes in wrong order.

Continuing with test, we are looking a mismatch on the differences tool.

For instance, we've done the next changes on the setting file (manually on the editor, VSCode):
image
These are the changes we want to replicate on settings.

Now, we go to the Admin Panel, Config Sync plugin and we want to import the above changes, we do click on the items to see the changes and we see the next:
image
You can see what are suppossed to be the changes.

Can you see the mismatch? The actual changes appear as the last version, and the current version appears as the changes.

We choose just ignore this view and we import the changes and, indeed, the changes we want are made.

This means the panel is showing the current settings and its changes in the wrong side of the comparison tool.

It happened with files we edited manually (VSCode) after exporting and before re-importing them.

What's the problem here?

Windows Support

error: unable to create file extensions/config-sync/files/core-store.plugin_content_manager_configuration_content_types::plugins::users-permissions.user.json: Invalid argument

you could remove the : from the File Name.
This does not work on NTFS. So it does not work on Windows.

Admin permissions fail to import

Firstly, thank you for building this fantastic plugin!

Bug report

Describe the bug

I'm finding that in my production environment admin permissions always fail to import. Other config types are fine. I've tried importing on bootstrap, via CLI and via the GUI. All fail.

Running the import command multiple times doesn't do anything.

Screen Shot 2022-01-21 at 7 43 37 am

The diff is visible in the GUI.

Screen Shot 2022-01-21 at 7 48 21 am

My config

'config-sync': {
    enabled: true,
    config: {
      importOnBootstrap: true,
      excludedConfig: [
        "core-store.plugin_users-permissions_advanced"
      ],
    },
  },

System

  • Node.js version: 12.22.1
  • NPM version: 6.14.12
  • Strapi version: 4.0.5
  • Plugin version: 1.0.0-beta.5
  • Database: Mysql
  • Operating system: Ubuntu 20.4

Plugin is failing when running the CLI command yarn cs export

Bug report

Describe the bug

The plugin is failing when running the CLI command yarn cs export. The initial sync through the UI was successful.

Steps to reproduce the behavior

  1. Open a terminal in the root of the Strapi project
  2. Run yarn cs export

Expected behavior

Expected to have a message that the export was succesful.

Code snippets

❯ yarn cs export
yarn run v1.22.17
$ config-sync export
/home/<username>/Play/strapi-postgresql/cms/node_modules/strapi-plugin-config-sync/server/cli.js:163
program.storeOptionsAsProperties(false).allowUnknownOption(true);
        ^

TypeError: program.storeOptionsAsProperties is not a function
    at Object.<anonymous> (/home/<username>/Play/strapi-postgresql/cms/node_modules/strapi-plugin-config-sync/server/cli.js:163:9)
    at Module._compile (internal/modules/cjs/loader.js:1085:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Module.load (internal/modules/cjs/loader.js:950:32)
    at Function.Module._load (internal/modules/cjs/loader.js:790:12)
    at Module.require (internal/modules/cjs/loader.js:974:19)
    at require (internal/modules/cjs/helpers.js:93:18)
    at Object.<anonymous> (/home/<username>/Play/strapi-postgresql/cms/node_modules/strapi-plugin-config-sync/bin/config-sync:5:1)
    at Module._compile (internal/modules/cjs/loader.js:1085:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Module.load (internal/modules/cjs/loader.js:950:32)
    at Function.Module._load (internal/modules/cjs/loader.js:790:12)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
    at internal/main/run_main_module.js:17:47
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

System

  • Node.js version: v14 LTS
  • NPM version: 6.14.15
  • Strapi version: 4.0.3
  • Plugin version: 1.0.0-beta.2
  • Database: Postgres
  • Operating system: Windows 10 in WSL 2 (Ubuntu)
    Screenshot 2022-01-07 120056
    Screenshot 2022-01-07 120210

Exclude options does not exclude some settings on "core-store"

Hi,

We are testing this plugin in order to synchronize our different environment settings.

We just want to persist the content type admin panel's configuration, so we want to exclude the others:

"config-sync": {
    destination: "extensions/config-sync/files/",
    minify: false,
    importOnBootstrap: false,
    include: ["core-store", "role-permissions"],
    exclude: [
      "core-store.model_def_application",
      "core-store.model_def_items",
      "core-store.model_def_plugins",
      "core-store.model_def_strapi",
      "core-store.plugin_users-permissions_grant",
    ],
  },

Unfortunately, it does not work:
image

What's the problem here?

Is there any way to use regex patterns here? lets say: core-store.model_def_* in order to exclude all model definition settings.

Thanks.

Forbid synced configs to be edited in Admin panel

Feature request

Summary

Add an option that makes it impossible to edit configs eligible for exporting from the Admin panel.

Why is it needed?

I'm using the config-sync plugin as outlined in the Workflow section of its documentation. I.e. I alter the configs locally, check the changes into git, and then import them on remote (staging/prod) environments.

Since my deploys to remote envs are fully automated I'm taking advantage of the importOnBootstrap option to import the changes automatically with every deploy. This causes any changes in the DB to be overwritten. This is ultimately correct, as the developer's (and hence the imported) config version should be the source of truth. However it can still cause confusion and/or trouble as users using the Admin panel may not know/understand their changes will disappear with the next deploy.

Suggested solution(s)

The optimal solution is to have an option in the plugin's config that, when set to true, prevents any writes to configs eligible for exporting. This option can then be set depending on the environment.

Indicating that a config is locked in the Admin Panel GUI would be very nice, but simply returning an error after submit would work too, IMO.

Undefined attribute level operator <col> when trying to export relations

Bug report

Describe the bug

I have this in customTypes

{
  configName: 'foos',
  queryString: 'api::foo.foo',
  uid: 'type',
  relations: [
    {
      queryString: 'api::bar.bar',
      relationName: 'bars',
      parentName: 'foo',
      relationSortFields: ['name'],
    }
  ],
},

and when I visit the tool page in Settings, I get an error.

I tried to debug it a lot but haven't gotten anywhere with the root cause. I'd be happy to PR a fix (if it's a bug) when pointed in the right direction.

Screenshot 2022-09-06 at 13 00 18

Steps to reproduce the behavior

  1. Set the customTypes as shown above (including relations)
  2. Click on Settings page
  3. Click on Tool page
  4. See error

Expected behavior

Similar to how exporting relations work for users-permissions plugin, it should work for any model.

Undefined setting creating errors on import/export

Describe the bug

This video describes the bug: https://drive.google.com/file/d/1ALFiP7iSfEzvBaSbsPBdk0TsLnZ2EkXz/view

And here is the error logged to my console:

Error: TypeError: Cannot read property 'code' of undefined
    at Object.exportSingleConfig (/Users/macuser/coding/acc-v4/node_modules/strapi-plugin-config-sync/server/services/main.js:252:13)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async /Users/macuser/coding/acc-v4/node_modules/strapi-plugin-config-sync/server/controllers/config.js:22:9
    at async Promise.all (index 37)
    at async Object.exportAll (/Users/macuser/coding/acc-v4/node_modules/strapi-plugin-config-sync/server/controllers/config.js:21:7)
    at async returnBodyMiddleware (/Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/services/server/compose-endpoint.js:52:18)
    at async policiesMiddleware (/Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/services/server/policy.js:24:5)
    at async /Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/middlewares/body.js:25:7
    at async /Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/middlewares/logger.js:22:5
    at async /Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/middlewares/powered-by.js:16:5
    at async cors (/Users/macuser/coding/acc-v4/node_modules/@koa/cors/index.js:95:16)
    at async /Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/middlewares/errors.js:13:7
    at async session (/Users/macuser/coding/acc-v4/node_modules/koa-session/index.js:41:7)
    at async /Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/services/metrics/middleware.js:29:5

Potentially useful is a different error when I run "import"

[2022-04-23 12:43:28.971] error: TypeError: Cannot read property 'permissions' of null
Error: TypeError: Cannot read property 'permissions' of null
    at Object.importSingleConfig (/Users/macuser/coding/acc-v4/node_modules/strapi-plugin-config-sync/server/services/main.js:227:13)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async /Users/macuser/coding/acc-v4/node_modules/strapi-plugin-config-sync/server/controllers/config.js:57:7
    at async Promise.all (index 0)
    at async Object.importAll (/Users/macuser/coding/acc-v4/node_modules/strapi-plugin-config-sync/server/controllers/config.js:56:5)
    at async returnBodyMiddleware (/Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/services/server/compose-endpoint.js:52:18)
    at async policiesMiddleware (/Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/services/server/policy.js:24:5)
    at async /Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/middlewares/body.js:25:7
    at async /Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/middlewares/logger.js:22:5
    at async /Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/middlewares/powered-by.js:16:5
    at async cors (/Users/macuser/coding/acc-v4/node_modules/@koa/cors/index.js:95:16)
    at async /Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/middlewares/errors.js:13:7
    at async session (/Users/macuser/coding/acc-v4/node_modules/koa-session/index.js:41:7)
    at async /Users/macuser/coding/acc-v4/node_modules/@strapi/strapi/lib/services/metrics/middleware.js:29:5
``

### System

- Node.js version: v14.18.1
- NPM version: 6.14.15
- Strapi version: ^4.1.7
- Plugin version:	Interestingly it no longer shows a version number, it is:
 `"strapi-plugin-config-sync": "boazpoolman/strapi-plugin-config-sync"`
- Database: postgres
- Operating system: MacOS

cs command not working, 'config-sync' is not recognized as an internal or external command

'config-sync' is not recognized as an internal or external command

plugin works from admin panel but not working from cli, here is my versions

  • "strapi": "3.6.8",
  • "strapi-plugin-config-sync": "0.1.6"

When i try to export config using npm run cs export i get this error

'config-sync' is not recognized as an internal or external command, operable program or batch file. npm ERR! code ELIFECYCLE npm ERR! errno 1

Tested on windows and ubuntu

CustomTypes - Does not export when the uid value contains a forward slash "/"

Bug report

Describe the bug

This happened when using customType in the plugin config. Not all data was exported to json.

Steps to reproduce the behavior

When the value of the "uid" field value contains a forward slash e.g "Boy / Girl", it does not export this database row to json file.

Expected behavior

Should export "uid" field values with a forward slash.

System

  • Node.js version: ">=12.x.x <=16.x.x"
  • NPM version: ">=6.0.0"
  • Strapi version: "4.1.6"
  • Plugin version: "^1.0.1"
  • Database: Postgres
  • Operating system: Mac

Partial import/export

Right now you can only import/export the config in bulk. All config will be synced.
I would like for this plugin to be able to import/export the config partially, either syncing a single config, or sync all config of a given type.

"importOnBootstrap: false" is being ignored

Bug report

When deploying to our staging environment the config seems to automatically update our database rather than a manual sync through the admin panel GUI.

After looking at the docs we saw importOnBootstrap and that is was set to false by default, we ran a test with the config override in config/plugins.ts also set to false and created some dummy content to see if it auto populated the database which it did.

We are using version 1.0.4

Expected behaviour

That importOnBootstrap being set to false stops any auto population.

Code snippets

'config-sync': {
    enabled: true,
    config: {
      syncDir: "config/sync/",
      minify: false,
      importOnBootstrap: false,
      customTypes: [],
      excludedTypes: [],
      excludedConfig: [
        "core-store.plugin_users-permissions_grant"
      ],
    },
  },

System

  • Node.js version: 16
  • NPM version: 8.15.0
  • Strapi version: 4.3.6
  • Plugin version: 1.0.4
  • Database: postgres

Admins gives errors at first export

We are testing this plugin in order to import our development settings.

At the first use, we exported the configuration but it gave us an error message and it was not refreshed, the problem here is, ever document was exported properly:
image

After refreshing it we got the changed files, whitout any problem.

What's the bug here?

(minor) Config diff label in interface not visible in dark mode

Bug report

Describe the bug

Only a minor bug but I thought I'd report it anyway, the "X config change" label in the UI is not visible when Strapi UI is in dark mode.

Steps to reproduce the behavior

Enable dark mode in Strapi UI and go to /admin/settings/config-sync

Expected behavior

Text to be visible.

Screenshots

image

System

Latest Strapi 4 and latest module with dark mode.

Thanks again for all your work on this much needed plugin!

Export Single Type, components will be ignored

I have a custom single type with a component:
image
image

When I try to export the single type, the component data is missing in the export:
image

I also tried to change the api service so that I get data with the populated component without parameter.

The config of the customType is:
image

System

Node.js version: 16.13.0
NPM version: 8.3.1
Strapi version: 4.3.0
Plugin version: 1.0.2
Database: Postgres Docker image
Operating system: WSL2 in Windows

Import data to config/env/test database

Hello, i'm working on jest test for strapi.

I have a database in config folder but when i'm running some test i have a new database in config/env/test.
And my objective its when i'm running my test before starting my sqlite server it's to get the sync folder and his data.
But at this moment when i'm doing "yarn cs import" that only impact my "real" database.

Maybe i'm doing something wrong ;)

Use generated `uuid` as the unique identifier

We have a custom type, that only has a uniq headline (in german) and a text field

image

This gets exported as

      customTypes: [
        {
          configName: "article",
          queryString: "api::article.article",
          uid: ["headline"],
        },
    ]

On the filesystem you then have files like

strapi-app/config/sync$ ls -lh article.*
-rw-r--r-- 1 jnachtigall jnachtigall 517 Aug 30 16:44 'article.Benötige ich eine wasserrechtliche Erlaubnis?.json'
-rw-r--r-- 1 jnachtigall jnachtigall 575 Aug 30 16:44 'article.Gibt es Fälle, in denen ich keine Anzeige stellen kann?.json'
-rw-r--r-- 1 jnachtigall jnachtigall 436 Aug 30 16:44 'article.Wie kann ich mich authentifizieren $ anmelden?.json'

This works fine on Linux or in WSL2. However, on Windows this results in these git errors

$ git -c credential.helper= -c core.quotepath=false -c log.showSignature=false rebase origin/develop
error: invalid path 'strapi-app/config/sync/article.Benötige ich eine wasserrechtliche Erlaubnis?.json'
error: invalid path 'strapi-app/config/sync/article.Gibt es Fälle, in denen ich keine Anzeige stellen kann?.json'
error: invalid path 'strapi-app/config/sync/article.Wie kann ich mich authentifizieren $ anmelden?.json'
error: could not detach HEAD

This seems like a pretty well known error, see https://stackoverflow.com/questions/63727594/github-git-checkout-returns-error-invalid-path-on-windows

FWIW, doing the suggested git config core.protectNTFS false did not work for us (resulted in read/write errors and a bluescreen).

We worked around the issue by adding a field articleId with integer to the above custom type and used this as uid instead.

Not sure you want to fix this in your plugin, but maybe a encoding/decoding of non-ascii characters would be good in the filename?

System

  • Node.js version: 16.13.0
  • NPM version: 8.3.1
  • Strapi version: 4.3.0
  • Plugin version: 1.0.2
  • Database: Postgres Docker image
  • Operating system: WSL2 in Windows

CLI - import, export, diff

Having a CLI to interact with for syncing your config is key when setting up a CI flow.
I would like for this plugin to have a CLI that handles the following things:

  • Config import
  • Config partial import (import a single config, or import all configs of a given type)
  • Config export
  • Config partial export (export a single config, or export all configs of a given type)
  • Config diff (see the difference between the config found in the database, and found in the filesystem)

config-sync command not found in CLI

Bug report

Describe the bug

spawn ENOENT

CLI does not seem to be installed when installed using Strapi3 (plugin version 0.1.6).

After installation, there is no config-sync in node_modules/.bin or anywhere in node_modules/strapi-plugin-config-sync

Steps to reproduce the behavior

  1. npm install --save [email protected]
  2. Make edits to config/admin.js according to README
  3. npm run build
  4. npm run develop
  5. Stop strapi
  6. Make edit to package.json (add 'cs' to scripts) according to README
  7. npm run cs

Expected behavior

I would expect that 'config-sync' would work from the CLI; I presume a config-sync command in the NPM bin search path location (./node_modules/.bin/) would be found

Screenshots

If applicable, add screenshots to help explain your problem.

Code snippets

If applicable, add code samples to help explain your problem.

System

  • Node.js version: 14.17.6 (LTS)
  • NPM version: 6.14.15
  • Strapi version: 3.6.8
  • Plugin version: 0.1.6
  • Database: SQLite3
  • Operating system: Linux 5.11.0-43-generic #47~20.04.2-Ubuntu SMP Mon Dec 13 11:06:56 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

Additional context

I can attach full verbose log file (2022-01-05T19_35_02_110Z-debug.log) if it would help

Managed permissions for access to plugin

Thanks a lot for your work on this plugin, definitely something Strapi needs as a core feature of the development lifecycle!

I think that there should be managed permissions to access and manage the plugin so that only admins can import (or export) config.

Similar to the Users & Permissions plugin.
image

Plugin GUI thinks I'm in production environment

Bug report

Describe the bug

Using the 1.0.0-alpha.1 release is showing an warning (that can't be closed) about being in production while I'm in development

Steps to reproduce the behavior

  1. Install plugin in fresh v4 project
  2. Rebuild Admin
  3. Run project with yarn develop
  4. Navigate to AdminUI for plugin
  5. See error

Expected behavior

Haven't looked at project code to see where it's pulling the env variable from, but might be pulling from wrong location

Screenshots

image

image

Code snippets

No specific modifications made, just installed and added the cs script to the package.json

System

  • Node.js version: v14.18.1
  • NPM version: v6.14.15
  • Strapi version: v4.0.0
  • Plugin version: v1.0.0-alpha.1
  • Database: SQLite
  • Operating system: Linux Mint 20.2

Additional context

Error message doesn't appear it can be closed :P

See diff of a single config in the CLI

It would be nice if we could see the diff of a single config in a git-style diff viewer from the CLI.
We can try using the https://www.npmjs.com/package/cli-diff package for this.

As we allready have the formatted diff in the diff command in the CLI this should be pretty easy.
https://github.com/boazpoolman/strapi-plugin-config-sync/blob/master/server/cli.js#L201

We could alter the diff command to specify the name of the config as a parameter.
Something like this: config-sync diff i18n-locale.en to see the diff of the single config.

Custom types

When we are talking about config in this plugin, we are talking about specific tables in the database. Or to speak in Strapi terms; we are talking about types. Though these are not content types, which are different for each env. No these are config types, which should be the same on all envs.

Right now there are 4 config types enabled in the syncing process; 18n-locale, core-store, admin-role and user-role.

I would like for this plugin to be able to define which types in Strapi should be considered a config type in an abstract manner. Also when having these types abstractly defined, I would want other plugins to be able to define their own config type as well. These will be defined outside this plugin and will be included in the syncing process.

Authenticated and Public settings always appears at changes on settings

Every time we make changes on setting files and we go to see them on the Config Sync panel we see both Authenticated and Public:
image
image
As you can see, we imported the changes at first time (image 1); later, we made changes and we go to import them again (image 2); and we see them again.

Why? Is it a normal behaviour? It confuses us too much.

Support for v3?

Feature request

Summary

Supporting Strapi v3 until it is deprecated.

Why is it needed?

Considering how young Strapi v4 is and how the migration guide is still in progress, I think it's a good idea to support v3 for now. I'm sure me and other developers are willing to help. It would be great if you can guide us where to start.

Suggested solution(s)

Related issue(s)/PR(s)

Plugin is failing when clean and build after strapi upgrade

Bug report

Getting ModuleNotFoundError when building the plugin with strapi 4.0.3

Describe the bug

ModuleNotFoundError: Module not found: Error: Can't resolve 'immutable' in '/node_modules/strapi-plugin-config-sync/admin/src/helpers'

I am getting this when trying to build after the upgrade for strapi 4.0.3
A clear and concise description of what the bug is.

Steps to reproduce the behavior

  1. Upgrade strapi to 4.0.3
  2. Run npm run build --clean
  3. The error shows up

Expected behavior

Run run build without error

System

  • Node.js version: Tested with version 14 (npm v8.3.0) and 16 (npm v8.1.0)
  • Strapi version: 4.0.3
  • Plugin version: 1.0.0-beta.1
  • Database: Postgressql
  • Operating system: locally

Error trying to export

Bug report

Describe the bug

When Trying to export from the GUI a change made to the user roles, the GUI displayed both an error message and a success message, and in the logs there is an error

Steps to reproduce the behavior

  1. Go to Users & Permission Plugin, make a change on a role for a content-type
  2. Go to config-sync plugin and clich "Export" (see screenshot)
  3. Click "Yes, export" in the confirmation popup
  4. See error (see screenshot)

Expected behavior

Only the success message should appear

Screenshots

2022-01-17_11-53

Schermata da 2022-01-17 11-54-42

Code snippets

This is the error in the logs:

[2022-01-17 11:42:40.494] error: TypeError: Cannot read properties of undefined (reading 'type')
Error: TypeError: Cannot read properties of undefined (reading 'type')
    at Object.exportSingleConfig (/srv/app/node_modules/strapi-plugin-config-sync/server/services/main.js:252:13)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async /srv/app/node_modules/strapi-plugin-config-sync/server/controllers/config.js:22:9
    at async Promise.all (index 0)
    at async Object.exportAll (/srv/app/node_modules/strapi-plugin-config-sync/server/controllers/config.js:21:7)
    at async returnBodyMiddleware (/srv/app/node_modules/@strapi/strapi/lib/services/server/compose-endpoint.js:52:18)
    at async policiesMiddleware (/srv/app/node_modules/@strapi/strapi/lib/services/server/policy.js:24:5)
    at async /srv/app/node_modules/@strapi/strapi/lib/middlewares/body.js:24:7
    at async /srv/app/node_modules/@strapi/strapi/lib/middlewares/logger.js:22:5
    at async /srv/app/node_modules/@strapi/strapi/lib/middlewares/powered-by.js:16:5
    at async cors (/srv/app/node_modules/@koa/cors/index.js:95:16)
    at async /srv/app/node_modules/@strapi/strapi/lib/middlewares/errors.js:13:7
    at async session (/srv/app/node_modules/koa-session/index.js:41:7)
    at async /srv/app/node_modules/@strapi/strapi/lib/services/metrics/middleware.js:29:5

System

  • Node.js version: v16.13.0
  • Strapi version: 4.0.4
  • Plugin version: 1.0.0-beta.4
  • Operating system: bullseye

Additional context

The "importOnBootstrap" option is used

Strapi 3.6.5 breaks after importing configuration files.

I had to change my working computer to a new one: yikes, the perfect time to test this plugin; well, every time i import even just one file, it breaks the setup a lot.

it can import some configurations, but it breaks many models, their panels, their content manager panels, etc., and i can't figure out why.

After importing settings, we found bugs like the next:

TypeError: Cannot read property 'reduce' of undefined
    at formatLayoutWithMetas (main.790d5f16.chunk.js:177692)
    at formatLayouts (main.790d5f16.chunk.js:177692)
    at _callee$ (main.790d5f16.chunk.js:177684)
    at tryCatch (main.790d5f16.chunk.js:81411)
    at Generator.invoke [as _invoke] (main.790d5f16.chunk.js:81641)
    at Generator.next (main.790d5f16.chunk.js:81466)
    at asyncGeneratorStep (main.790d5f16.chunk.js:18010)
    at _next (main.790d5f16.chunk.js:18032)

I tested it again, and i got the same problem again and again.

Strapi: v20.10.7
OS: macOS 11.5.1 20G80 x86_64 

About the stack:

version: '3'
services:
  strapi:
    container_name: cancer_backend
    image: strapi/strapi
    environment:
      DATABASE_CLIENT: postgres
      DATABASE_NAME: strapi
      DATABASE_HOST: postgres
      DATABASE_PORT: 5432
      DATABASE_USERNAME: strapi
      DATABASE_PASSWORD: strapi
    volumes:
      - ./backend:/srv/app
    ports:
      - '1337:1337'
    depends_on:
      - postgres

  postgres:
    container_name: cancer_db
    image: postgres
    environment:
      POSTGRES_DB: strapi
      POSTGRES_USER: strapi
      POSTGRES_PASSWORD: strapi
    volumes:
      - ./data:/var/lib/postgresql/data
      
  access_db:
    container_name: access_cancer_db
    restart: always
    image: sosedoff/pgweb
    ports:
      - "8087:8081"
    environment:
      - DATABASE_URL=postgres://strapi:strapi@cancer_db:5432/postgres?sslmode=disable
      - VIRTUAL_HOST=strapi.db.localhost
      - VIRTUAL_PORT=8087

And packages used on the project:

  "dependencies": {
    "knex": "0.21.18",
    "pg": "^8.6.0",
    "pg-connection-string": "^2.5.0",
    "strapi": "3.6.5",
    "strapi-admin": "3.6.5",
    "strapi-connector-bookshelf": "3.6.5",
    "strapi-plugin-config-sync": "^0.1.6",
    "strapi-plugin-content-manager": "3.6.5",
    "strapi-plugin-content-type-builder": "3.6.5",
    "strapi-plugin-email": "3.6.5",
    "strapi-plugin-graphql": "3.6.5",
    "strapi-plugin-i18n": "3.6.5",
    "strapi-plugin-upload": "3.6.5",
    "strapi-plugin-users-permissions": "3.6.5",
    "strapi-provider-email-amazon-ses": "^3.6.5",
    "strapi-provider-upload-aws-s3-plus-cdn": "^1.0.6",
    "strapi-utils": "3.6.5"
  },

Any help on this? Thanks.

Improve database accesses

I am setting up a VPS where multiple instances of strapi will be run. These instances connects to a DigitalOcean postgre managed database.

I started about the the lowest tier for the managed database (1GB of RAM) which gives me small amount of concurrent connections (22) something I just learnt. While this still in the PoC phase and the servers will be bigger I found it odd that with a handful of no traffic strapi instances I was quickly getting errors out of postgre when running config-sync import.

I wondered by and while looking at database activity I saw that one run of config-sync import was creating several databases connections at the same time.

I'd assume this is not needed and whatever this is doing it can be done using 1 (or as fewer as possible) connections or at least to have the option to limit it if this is because things are done in parallel.

Any advice/comment is appreciated.

Discriminate sections by status aka show only different files(from db) in import and vis.

Feature request

Summary

It would be nice if the plugin shows only files with differences with the database in the import view and files with differences with existing files in the export files.

Why is it needed?

I saw a big problem when coming to Media Library settings many times after importing all my configurations; later I realized that I was importing even the settings from the database, duplicating and corrupting, I think, the database.

It was not clear to me that importing settings from the database itself could corrupt the database, and I think it could be a general problem for many users.

Suggested solution(s)

Like a tab with import showing only the files that have differences with the database, and an export tab showing only the files that either is not in the configuration folder or have differences with the files in the folder.

Related issue(s)/PR(s)

I think I made this suggestion before, but I'm not pretty sure.

Config sync no longer shows up in admin panel for Strapi 4.1.5, strapi-plugin-config-sync 1.0.0

Bug report

Describe the bug

After upgrading to strapi 4.1.5 and strapi-plugin-config-sync 1.0.0, Config sync no longer shows up in the admin panel.

Steps to reproduce the behavior

  • update from strapi 4.1.3 and strapi-plugin-config-sync 1.0.0-beta.8 to the versions mentioned above
  • build the admin panel: strapi build
  • start strapi
  • config sync no longer shows up in the admin panel 😞

The command line interface still works, though.

Expected behavior

Config sync still shows up in the Strapi admin panel.

System

  • Node.js version: 16.13.1
  • NPM version: 8.3.0
  • Strapi version: 4.1.5
  • Plugin version: 1.0.0
  • Database: sqlite
  • Operating system: Windows 11

combined uid fields for customTypes to support i18n

Feature request

Summary

Allow an array of keys for customTypes.uid config field and create a combined key with it.

Why is it needed?

If i18n is enabled, Strapi will store two entries for each type instance. Right now the customTypes.uid field only accepts one key. It would be great to be able to set an array of keys to be able to also store multiple documents for customTypes which have i18n enabled.

Suggested solution(s)

customTypes: [
                {
                    configName: 'product',
                    queryString: 'api::product.product',
                    uid: ['productId', 'locale'],
                }]

Synching i18n locale changes can break content edit forms

Bug description

This is a follow up on the discussion that started in PR #7
When using the i8n plugin, an installation can become unusable if the default locale is deleted and replaced by another before performing a sync.

Steps to reproduce the behavior

  1. Setup two strapi apps (see docker-compose example below)
  2. Update both installations to 3.6.1
  3. Add i18n and config-sync plugin in both installations
  4. Create test content type in both with same options and fields
  5. Export configs in first and import in second installation
  6. Add some content in second installation
  7. add FR locale under admin/settings/internationalization in first installation
  8. set FR locale as default in first installation
  9. delete EN locale in first installation
  10. Export configs in first installation and import in second installation
  11. Got to edit a content in second installation
  12. "Something went wrong." shows up instead of the edit form

Expected behavior

The edit form allows editing the content as usual.

Screenshots

Error page:
image

Code snippets

Sample docker-compose file used to reproduce the bug:

version: '3'
services:
  strapi:
    image: strapi/strapi
    environment:
      DATABASE_CLIENT: mongo
      DATABASE_NAME: strapi
      DATABASE_HOST: mongo
      DATABASE_PORT: 27017
      DATABASE_USERNAME: strapi
      DATABASE_PASSWORD: strapi
    volumes:
      - ./app:/srv/app
    ports:
      - 1337:1337
    depends_on:
      - mongo

  mongo:
    image: mongo
    environment:
      MONGO_INITDB_DATABASE: strapi
      MONGO_INITDB_ROOT_USERNAME: strapi
      MONGO_INITDB_ROOT_PASSWORD: strapi
    volumes:
      - ./.data:/data/db

  strapi2:
    image: strapi/strapi
    environment:
      DATABASE_CLIENT: mongo
      DATABASE_NAME: strapi
      DATABASE_HOST: mongo2
      DATABASE_PORT: 27017
      DATABASE_USERNAME: strapi
      DATABASE_PASSWORD: strapi
    volumes:
      - ./app2:/srv/app
    ports:
      - 1338:1337
    depends_on:
      - mongo2

  mongo2:
    image: mongo
    environment:
      MONGO_INITDB_DATABASE: strapi
      MONGO_INITDB_ROOT_USERNAME: strapi
      MONGO_INITDB_ROOT_PASSWORD: strapi
    volumes:
      - ./.data2:/data/db

System

Node.js version: 14.16.0
NPM version: 7.7.6
Strapi version: 3.6.1
Database: MongoDB
Operating system: Docker

Additional context

The issua can be manually fixed by making the same locale changes in the second installation (see steps 7-9).

[BUG] Importing configuration files gives always error in Strapi 4.3.4.

Bug report

Describe the bug

Every time we try to import the configuration for our models we get an error toast, looking on the console we get this error:

XHRPOSThttp://localhost:1337/config-sync/import
[HTTP/1.1 500 Internal Server Error 2477ms]

	
POST
	http://localhost:1337/config-sync/import
Status
500
Internal Server Error
VersionHTTP/1.1
Transferred1.11 kB (99 B size)
Referrer Policyno-referrer
Request PriorityHighest

    	
    Access-Control-Allow-Credentials
    	true
    Access-Control-Allow-Origin
    	http://localhost:1337
    Connection
    	keep-alive
    Content-Length
    	99
    Content-Security-Policy
    	connect-src 'self' https:;script-src 'self' editor.unlayer.com;frame-src 'self' editor.unlayer.com;img-src 'self' data: blob: cdn.jsdelivr.net strapi.io dl.airtable.com res.cloudinary.com;media-src 'self' data: blob: dl.airtable.com res.cloudinary.com;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'
    Content-Type
    	application/json; charset=utf-8
    Date
    	Mon, 15 Aug 2022 15:55:44 GMT
    Expect-CT
    	max-age=0
    Keep-Alive
    	timeout=5
    Referrer-Policy
    	no-referrer
    Strict-Transport-Security
    	max-age=31536000; includeSubDomains
    Vary
    	Origin
    X-Content-Type-Options
    	nosniff
    X-DNS-Prefetch-Control
    	off
    X-Download-Options
    	noopen
    X-Frame-Options
    	SAMEORIGIN
    X-Permitted-Cross-Domain-Policies
    	none
    	
    Accept
    	*/*
    Accept-Encoding
    	gzip, deflate, br
    Accept-Language
    	en-GB,tr;q=0.8,es;q=0.5,en;q=0.3
    Authorization
    	Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjYwNTc4ODgyLCJleHAiOjE2NjMxNzA4ODJ9.W6jgkZ3Ow0b3D-LynqtxQe08YpCB2-Sj9Om0mrav1IU
    Connection
    	keep-alive
    Content-Length
    	2790
    Content-Type
    	application/json
    Host
    	localhost:1337
    Origin
    	http://localhost:1337
    Sec-Fetch-Dest
    	empty
    Sec-Fetch-Mode
    	cors
    Sec-Fetch-Site
    	same-origin
    User-Agent
    	Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:103.0) Gecko/20100101 Firefox/103.0

And going to the network, we can see this.
Screen Shot 2022-08-15 at 19 00 13

Moving to the content manager, we get two behaviours:

  1. The configuration settings were properly loaded.
  2. The admin user lost all the permission and cannot see anything.

Steps to reproduce the behavior

  1. Go to Content Manager.
  2. Click on Config Syng > Tools.
  3. Select all the files you want to import.
  4. Import them.
  5. See the toast error.
  6. Go to Console.
  7. Check the error.
  8. Go to Network.
  9. Check the error.

Expected behavior

It shouldn't give an error at importing.

Screenshots

If applicable, add screenshots to help explain your problem.

Code snippets

If applicable, add code samples to help explain your problem.

System

  • Node.js version: v14.20.0
  • Strapi version: 4.3.4
  • Plugin version: 2.0.8
  • Database: PostgreSQL 13.7
  • Operating system: Alpine Linux v3.16

Additional context

It is always a clean installation with a empty database.

Import of custom content type entry has other `id` than on exported system

Bug report

Describe the bug

We use this plugin (thank you) to have a consistent DB (data, settings, custom content types and entries) for our whole team.

We use Postgres as DB.

Steps to reproduce the behavior

  1. I did an export of everything and pushed this in git. There was just one entry type for one custom content type. On my system this entry type had the id 3.
  2. Colleague B did an import. On his system the id for this entry was now 1.

I guess that I had 3 because I did create 2 other entries before which I deleted before I did an export.

Now when I did an API GET request like http://localhost:1337/api/our-custom-content-type/3 it worked on my system, but on colleague B she got a 404. She needed to change it to http://localhost:1337/api/our-custom-content-type/1

Expected behavior

To have a consistent data sharing the IDs should be in sync. Especially since those are used for API access.

System

  • Node.js version: 16.13.0
  • NPM version: 8.3.1
  • Strapi version: 4.3.0
  • Plugin version: 1.0.2
  • Database: Postgres Docker image
  • Operating system: WSL2 in Windows

Additional context

Not sure this is a bug or a feature 🙃 But having ever-changing IDs makes it hard to consume the strapi backend API from an external frontend in a consistent way.

Can not use the plugin with a new version of the Strapi CMS (4.1.5)

Bug report

Describe the bug

This plugin is not compatible with the new version (4.1.5) of Strapi CMS.

The error during execute yarn build command

$ strapi build
Building your admin UI with development configuration ...

● Webpack █████████████████████████ building (24%) 0/1 entries 6200/6260 dependencies 1363/1754 modules 272 active 
 babel-loader › node_modules/@strapi/plugin-content-type-builder/admin/src/components/AllowedTypesSelect/index.js

ModuleNotFoundError: Module not found: Error: Can't resolve '@strapi/design-system/Text' in '/Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/admin/src/components/ConfigList'
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/Compilation.js:2011:28
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:795:13
    at eval (eval at create (/Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:10:1)
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:275:22
    at eval (eval at create (/Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:9:1)
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:431:22
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:124:11
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:667:25
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:852:8
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:972:5
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/neo-async/async.js:6883:13
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:955:45
    at finishWithoutResolve (/Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/enhanced-resolve/lib/Resolver.js:307:11)
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/enhanced-resolve/lib/Resolver.js:381:15
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/enhanced-resolve/lib/Resolver.js:430:5
    at eval (eval at create (/Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:15:1)
resolve '@strapi/design-system/Text' in '/Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/admin/src/components/ConfigList'
  Parsed request is a module
  using description file: /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/package.json (relative path: ./admin/src/components/ConfigList)
    Field 'browser' doesn't contain a valid alias configuration
    resolve as module
      /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/admin/src/components/ConfigList/node_modules doesn't exist or is not a directory
      /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/admin/src/components/node_modules doesn't exist or is not a directory
      /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/admin/src/node_modules doesn't exist or is not a directory
      /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/admin/node_modules doesn't exist or is not a directory
      looking for modules in /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/node_modules
        /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/node_modules/@strapi/design-system doesn't exist
      /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/node_modules doesn't exist or is not a directory
      looking for modules in /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules
        existing directory /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system
          using description file: /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/package.json (relative path: .)
            using description file: /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/package.json (relative path: ./Text)
              no extension
                Field 'browser' doesn't contain a valid alias configuration
                /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/Text doesn't exist
              .js
                Field 'browser' doesn't contain a valid alias configuration
                /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/Text.js doesn't exist
              .jsx
                Field 'browser' doesn't contain a valid alias configuration
                /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/Text.jsx doesn't exist
              .react.js
                Field 'browser' doesn't contain a valid alias configuration
                /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/Text.react.js doesn't exist
              as directory
                /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/Text doesn't exist
      /Users/alex.dede/projects/cdp/node_modules doesn't exist or is not a directory
      /Users/alex.dede/projects/node_modules doesn't exist or is not a directory
      /Users/alex.dede/node_modules doesn't exist or is not a directory
      /Users/node_modules doesn't exist or is not a directory
      /node_modules doesn't exist or is not a directory
      looking for modules in /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/admin/node_modules
        /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/admin/node_modules/@strapi/design-system doesn't exist
ModuleNotFoundError: Module not found: Error: Can't resolve '@strapi/design-system/Text' in '/Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/admin/src/components/ConfigList'
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/Compilation.js:2011:28
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:795:13
    at eval (eval at create (/Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:10:1)
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:275:22
    at eval (eval at create (/Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:9:1)
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:431:22
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:124:11
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:667:25
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:852:8
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:972:5
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/neo-async/async.js:6883:13
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/webpack/lib/NormalModuleFactory.js:955:45
    at finishWithoutResolve (/Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/enhanced-resolve/lib/Resolver.js:307:11)
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/enhanced-resolve/lib/Resolver.js:381:15
    at /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/enhanced-resolve/lib/Resolver.js:430:5
    at eval (eval at create (/Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:15:1)
resolve '@strapi/design-system/Text' in '/Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/admin/src/components/ConfigList'
  Parsed request is a module
  using description file: /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/package.json (relative path: ./admin/src/components/ConfigList)
    Field 'browser' doesn't contain a valid alias configuration
    resolve as module
      /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/admin/src/components/ConfigList/node_modules doesn't exist or is not a directory
      /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/admin/src/components/node_modules doesn't exist or is not a directory
      /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/admin/src/node_modules doesn't exist or is not a directory
      /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/admin/node_modules doesn't exist or is not a directory
      looking for modules in /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/node_modules
        /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/strapi-plugin-config-sync/node_modules/@strapi/design-system doesn't exist
      /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/node_modules doesn't exist or is not a directory
      looking for modules in /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules
        existing directory /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system
          using description file: /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/package.json (relative path: .)
            using description file: /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/package.json (relative path: ./Text)
              no extension
                Field 'browser' doesn't contain a valid alias configuration
                /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/Text doesn't exist
              .js
                Field 'browser' doesn't contain a valid alias configuration
                /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/Text.js doesn't exist
              .jsx
                Field 'browser' doesn't contain a valid alias configuration
                /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/Text.jsx doesn't exist
              .react.js
                Field 'browser' doesn't contain a valid alias configuration
                /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/Text.react.js doesn't exist
              as directory
                /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/design-system/Text doesn't exist
      /Users/alex.dede/projects/cdp/node_modules doesn't exist or is not a directory
      /Users/alex.dede/projects/node_modules doesn't exist or is not a directory
      /Users/alex.dede/node_modules doesn't exist or is not a directory
      /Users/node_modules doesn't exist or is not a directory
      /node_modules doesn't exist or is not a directory
      looking for modules in /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/admin/node_modules
        /Users/alex.dede/projects/cdp/cdp-scpc-cms/node_modules/@strapi/admin/node_modules/@strapi/design-system doesn't exist
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

### Steps to reproduce the behavior

1. Create a new Strapi (4.1.5) project
2. Add `strapi-plugin-config-sync` plugin to the created project
3. Execute `yarn build` command
4. See error in the console

### Expected behavior

The project should be built without any errors

### System

- Node.js version: 16.14.0
- NPM version:  8.3.1
- Strapi version: 4.1.5
- Plugin version: 1.0.0-beta.8
- Database: MariaDB
- Operating system: Mac OS

One time imports (db seeding)

Feature request

Summary

It would be neat if the plugin would allow for one-time-imports. This process would be completely seperated from the config syncing and could be used to seed data on remote env's.

Why is it needed?

Currently this plugin works for syncing config data. Though if you were to use the plugin to sync content you would run into trouble if you edit the content on production. Because on the next import you would lose the changes that are made.

Suggested solution(s)

I would like to have this be a process that is seperated from the config syncing. So it won't affect the functionality of the plugin as it is.

We would have an extra folder (or folders) that holds the JSON files for the data that is to be used for seeding. When we deploy these files we can import them by using a new command (yarn cs seed) which will write the data from the JSON files into the db.
As soon as the file is imported the job is done. The next time the seed command is ran the plugin will not try to overwrite the data in the db.

This feature will allow you to migrate content data through one-time imports as apposed to full on data sync as currently is the only option with this plugin.

Related issue(s)/PR(s)

N.A.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.