chore: Setup playground for testing

pull/32/head
Boaz Poolman 2021-12-29 21:56:49 +01:00
parent e31d1ebca0
commit cb5f889aaa
30 changed files with 12560 additions and 6 deletions

View File

@ -3,4 +3,4 @@
**/build **/build
**/config **/config
**/scripts **/scripts
**/xsl **/playground

View File

@ -24,6 +24,25 @@ jobs:
run: yarn --ignore-scripts --frozen-lockfile run: yarn --ignore-scripts --frozen-lockfile
- name: Run eslint - name: Run eslint
run: yarn run eslint run: yarn run eslint
integration:
name: 'integration'
needs: [lint]
runs-on: ubuntu-latest
strategy:
matrix:
node: [12, 14, 16]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
cache: 'yarn'
- name: Install dependencies
run: yarn playground:install --ignore-scripts --frozen-lockfile
- name: Install plugin
run: yarn add strapi-plugin-config-sync@boazpoolman/strapi-plugin-config-sync#${GITHUB_REF##*/}
- name: Run test
run: yarn run -s test:integration
# unit: # unit:
# name: 'unit' # name: 'unit'
# needs: [lint] # needs: [lint]

View File

@ -23,7 +23,7 @@ git clone git@github.com:YOUR_USERNAME/strapi-plugin-config-sync.git config-sync
Go to the plugin and install it's dependencies. Go to the plugin and install it's dependencies.
```bash ```bash
cd YOUR_STRAPI_PROJECT/src/plugins/config-sync/ && yarn install-local cd YOUR_STRAPI_PROJECT/src/plugins/config-sync/ && yarn plugin:install
``` ```
#### 4. Enable the plugin #### 4. Enable the plugin

View File

@ -17,7 +17,10 @@
"eslint": "eslint --max-warnings=0 './**/*.{js,jsx}'", "eslint": "eslint --max-warnings=0 './**/*.{js,jsx}'",
"eslint:fix": "eslint --fix './**/*.{js,jsx}'", "eslint:fix": "eslint --fix './**/*.{js,jsx}'",
"test:unit": "jest --verbose", "test:unit": "jest --verbose",
"install-local": "yarn install && rm -rf node_modules/@strapi/helper-plugin" "test:integration": "cd playground && jest --verbose",
"plugin:install": "yarn install && rm -rf node_modules/@strapi/helper-plugin",
"playground:install": "cd playground && yarn install",
"playground:develop": "cd playground && yarn develop"
}, },
"dependencies": { "dependencies": {
"chalk": "^4.1.2", "chalk": "^4.1.2",

16
playground/.editorconfig Normal file
View File

@ -0,0 +1,16 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[{package.json,*.yml}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false

2
playground/.env.example Normal file
View File

@ -0,0 +1,2 @@
HOST=0.0.0.0
PORT=1337

3
playground/.eslintignore Normal file
View File

@ -0,0 +1,3 @@
.cache
build
**/node_modules/**

27
playground/.eslintrc Normal file
View File

@ -0,0 +1,27 @@
{
"parser": "babel-eslint",
"extends": "eslint:recommended",
"env": {
"commonjs": true,
"es6": true,
"node": true,
"browser": false
},
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": false
},
"sourceType": "module"
},
"globals": {
"strapi": true
},
"rules": {
"indent": ["error", 2, { "SwitchCase": 1 }],
"linebreak-style": ["error", "unix"],
"no-console": 0,
"quotes": ["error", "single"],
"semi": ["error", "always"]
}
}

114
playground/.gitignore vendored Normal file
View File

@ -0,0 +1,114 @@
############################
# OS X
############################
.DS_Store
.AppleDouble
.LSOverride
Icon
.Spotlight-V100
.Trashes
._*
############################
# Linux
############################
*~
############################
# Windows
############################
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
*.cab
*.msi
*.msm
*.msp
############################
# Packages
############################
*.7z
*.csv
*.dat
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
*.com
*.class
*.dll
*.exe
*.o
*.seed
*.so
*.swo
*.swp
*.swn
*.swm
*.out
*.pid
############################
# Logs and databases
############################
.tmp
*.log
*.sql
*.sqlite
*.sqlite3
############################
# Misc.
############################
*#
ssl
.idea
nbproject
public/uploads/*
!public/uploads/.gitkeep
############################
# Node.js
############################
lib-cov
lcov.info
pids
logs
results
node_modules
.node_history
############################
# Tests
############################
testApp
coverage
############################
# Strapi
############################
.env
license.txt
exports
*.cache
build
.strapi-updater.json

3
playground/README.md Normal file
View File

@ -0,0 +1,3 @@
# Strapi application
A quick description of your strapi application

View File

@ -0,0 +1,22 @@
'use strict';
const util = require('util');
const exec = util.promisify(require('child_process').exec);
jest.setTimeout(10000);
describe('Test the config-sync CLI', () => {
test('Export', async () => {
const { stdout } = await exec('yarn cs export -y');
expect(stdout).toContain('Finished export');
});
test('Import', async () => {
await exec('rm -rf config/sync/admin-role.strapi-editor.json');
const { stdout } = await exec('yarn cs import -y');
expect(stdout).toContain('Finished import');
});
test('Diff', async () => {
const { stdout } = await exec('yarn cs diff');
expect(stdout).toContain('No differences between DB and sync directory');
});
});

232
playground/cli.js Normal file
View File

@ -0,0 +1,232 @@
#!/usr/bin/env node
const { Command } = require('commander');
const Table = require('cli-table');
const chalk = require('chalk');
const inquirer = require('inquirer');
const { isEmpty } = require('lodash');
const strapi = require('@strapi/strapi');
const warnings = require('../server/warnings');
const packageJSON = require('../package.json');
const program = new Command();
const initTable = (head) => {
return new Table({
head: [chalk.green('Name'), chalk.green(head || 'State')],
colWidths: [65, 20],
chars: { top: '═',
'top-mid': '╤',
'top-left': '╔',
'top-right': '╗',
bottom: '═',
'bottom-mid': '╧',
'bottom-left': '╚',
'bottom-right': '╝',
left: '║',
'left-mid': '╟',
mid: '─',
'mid-mid': '┼',
right: '║',
'right-mid': '╢',
middle: '│',
},
});
};
const getConfigState = (diff, configName, syncType) => {
if (
diff.fileConfig[configName]
&& diff.databaseConfig[configName]
) {
return chalk.yellow(syncType ? 'Update' : 'Different');
} else if (
diff.fileConfig[configName]
&& !diff.databaseConfig[configName]
) {
if (syncType === 'import') {
return chalk.green('Create');
} else if (syncType === 'export') {
return chalk.red('Delete');
} else {
return chalk.red('Only in sync dir');
}
} else if (
!diff.fileConfig[configName]
&& diff.databaseConfig[configName]
) {
if (syncType === 'import') {
return chalk.red('Delete');
} else if (syncType === 'export') {
return chalk.green('Create');
} else {
return chalk.green('Only in DB');
}
}
};
const handleAction = async (syncType, skipConfirm, configType, partials) => {
const app = await strapi().load();
const diff = await app.plugin('config-sync').service('main').getFormattedDiff();
// No changes.
if (isEmpty(diff.diff)) {
console.log(`${chalk.cyan.bold('[notice]')} There are no changes to ${syncType}.`);
process.exit(0);
}
// Init table.
const table = initTable('Action');
const configNames = partials && partials.split(',');
const partialDiff = {};
// Fill partialDiff with arguments.
if (configNames) {
configNames.map((name) => {
if (diff.diff[name]) partialDiff[name] = diff.diff[name];
});
}
if (configType) {
Object.keys(diff.diff).map((name) => {
if (configType === name.split('.')[0]) {
partialDiff[name] = diff.diff[name];
}
});
}
// No changes for partial diff.
if ((partials || configType) && isEmpty(partialDiff)) {
console.log(`${chalk.cyan.bold('[notice]')} There are no changes for the specified config.`);
process.exit(0);
}
const finalDiff = (partials || configType) && partialDiff ? partialDiff : diff.diff;
// Add diff to table.
Object.keys(finalDiff).map((configName) => {
table.push([configName, getConfigState(diff, configName, syncType)]);
});
// Print table.
console.log(table.toString(), '\n');
// Prompt to confirm.
let answer = {};
if (!skipConfirm) {
answer = await inquirer.prompt([{
type: 'confirm',
name: 'confirm',
message: `Are you sure you want to ${syncType} the config changes?`,
}]);
console.log('');
}
// Preform the action.
if (skipConfirm || answer.confirm) {
if (syncType === 'import') {
const onSuccess = (name) => console.log(`${chalk.cyan.bold('[notice]')} Imported ${name}`);
try {
await Promise.all(Object.keys(finalDiff).map(async (name) => {
let warning;
if (
getConfigState(diff, name, syncType) === chalk.red('Delete')
&& warnings.delete[name]
) warning = warnings.delete[name];
await app.plugin('config-sync').service('main').importSingleConfig(name, onSuccess);
if (warning) console.log(`${chalk.yellow.bold('[warning]')} ${warning}`);
}));
console.log(`${chalk.green.bold('[success]')} Finished import`);
} catch (e) {
console.log(`${chalk.red.bold('[error]')} Something went wrong during the import. ${e}`);
}
}
if (syncType === 'export') {
const onSuccess = (name) => console.log(`${chalk.cyan.bold('[notice]')} Exported ${name}`);
try {
await Promise.all(Object.keys(finalDiff).map(async (name) => {
await app.plugin('config-sync').service('main').exportSingleConfig(name, onSuccess);
}));
console.log(`${chalk.green.bold('[success]')} Finished export`);
} catch (e) {
console.log(`${chalk.red.bold('[error]')} Something went wrong during the export. ${e}`);
}
}
}
process.exit(0);
};
// Initial program setup
program.storeOptionsAsProperties(false).allowUnknownOption(true);
program.helpOption('-h, --help', 'Display help for command');
program.addHelpCommand('help [command]', 'Display help for command');
// `$ config-sync version` (--version synonym)
program.version(packageJSON.version, '-v, --version', 'Output the version number');
program
.command('version')
.description('Output your version of the config-sync plugin')
.action(() => {
process.stdout.write(`${packageJSON.version}\n`);
process.exit(0);
});
// `$ config-sync import`
program
.command('import')
.alias('i')
.option('-t, --type <type>', 'The type of config')
.option('-p, --partial <partials>', 'A comma separated string of configs')
.option('-y, --yes', 'Skip the confirm prompt')
.description('Import the config')
.action(async ({ yes, type, partial }) => {
return handleAction('import', yes, type, partial);
});
// `$ config-sync export`
program
.command('export')
.alias('e')
.option('-t, --type <type>', 'The type of config')
.option('-p, --partial <partials>', 'A comma separated string of configs')
.option('-y, --yes', 'Skip the confirm prompt')
.description('Export the config')
.action(async ({ yes, type, partial }) => {
return handleAction('export', yes, type, partial);
});
// `$ config-sync diff`
program
.command('diff')
.alias('d')
// .option('-t, --type <type>', 'The type of config') // TODO: partial diff
.description('The config diff')
.action(async ({ type }) => {
const app = await strapi().load();
const diff = await app.plugin('config-sync').service('main').getFormattedDiff();
// No changes.
if (isEmpty(diff.diff)) {
console.log(`${chalk.cyan.bold('[notice]')} No differences between DB and sync directory.`);
process.exit(0);
}
// Init table.
const table = initTable();
// Add diff to table.
Object.keys(diff.diff).map((configName) => {
table.push([configName, getConfigState(diff, configName)]);
});
// Print table.
console.log(table.toString());
process.exit(0);
});
program.parseAsync(process.argv);

View File

@ -0,0 +1,8 @@
module.exports = ({ env }) => ({
auth: {
secret: env('ADMIN_JWT_SECRET', 'c27c3833823a12b0761e32b22dc0113a'),
},
watchIgnoreFiles: [
'**/config/sync/**',
],
});

7
playground/config/api.js Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
rest: {
defaultLimit: 25,
maxLimit: 100,
withCount: true,
},
};

View File

@ -0,0 +1,11 @@
const path = require('path');
module.exports = ({ env }) => ({
connection: {
client: 'sqlite',
connection: {
filename: path.join(__dirname, '..', env('DATABASE_FILENAME', '.tmp/data.db')),
},
useNullAsDefault: true,
},
});

View File

@ -0,0 +1,11 @@
module.exports = [
'strapi::errors',
'strapi::security',
'strapi::cors',
'strapi::poweredBy',
'strapi::logger',
'strapi::query',
'strapi::body',
'strapi::favicon',
'strapi::public',
];

View File

@ -0,0 +1,7 @@
module.exports = ({ env }) => ({
'config-sync': {
config: {
customTypes: [],
},
},
});

View File

@ -0,0 +1,4 @@
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
});

BIN
playground/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,7 @@
module.exports = {
name: 'Integration test',
testMatch: ['**/__tests__/?(*.)+(spec|test).js'],
transform: {},
coverageDirectory: '../coverage/',
collectCoverage: true,
};

34
playground/package.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "playground",
"private": true,
"version": "0.1.0",
"description": "A Strapi application",
"scripts": {
"develop": "strapi develop",
"start": "strapi start",
"build": "strapi build",
"strapi": "strapi",
"cs": "config-sync"
},
"devDependencies": {
"jest": "^26.0.1",
"jest-cli": "^26.0.1"
},
"dependencies": {
"@strapi/plugin-i18n": "4.0.2",
"@strapi/plugin-users-permissions": "4.0.2",
"@strapi/strapi": "4.0.2",
"sqlite3": "5.0.2"
},
"author": {
"name": "A Strapi developer"
},
"strapi": {
"uuid": "2e84e366-1e09-43c2-a99f-a0d0acbc2ca5"
},
"engines": {
"node": ">=12.x.x <=16.x.x",
"npm": ">=6.0.0"
},
"license": "MIT"
}

View File

@ -0,0 +1,3 @@
# To prevent search engines from seeing the site altogether, uncomment the next two lines:
# User-Agent: *
# Disallow: /

View File

View File

@ -0,0 +1,35 @@
export default {
config: {
locales: [
// 'ar',
// 'fr',
// 'cs',
// 'de',
// 'dk',
// 'es',
// 'he',
// 'id',
// 'it',
// 'ja',
// 'ko',
// 'ms',
// 'nl',
// 'no',
// 'pl',
// 'pt-BR',
// 'pt',
// 'ru',
// 'sk',
// 'sv',
// 'th',
// 'tr',
// 'uk',
// 'vi',
// 'zh-Hans',
// 'zh',
],
},
bootstrap(app) {
console.log(app);
},
};

View File

@ -0,0 +1,9 @@
'use strict';
/* eslint-disable no-unused-vars */
module.exports = (config, webpack) => {
// Note: we provide webpack above so you should not `require` it
// Perform customizations to webpack config
// Important: return the modified config
return config;
};

View File

View File

20
playground/src/index.js Normal file
View File

@ -0,0 +1,20 @@
'use strict';
module.exports = {
/**
* An asynchronous register function that runs before
* your application is initialized.
*
* This gives you an opportunity to extend code.
*/
register(/*{ strapi }*/) {},
/**
* An asynchronous bootstrap function that runs before
* your application gets started.
*
* This gives you an opportunity to set up your data model,
* run jobs, or perform some special logic.
*/
bootstrap(/*{ strapi }*/) {},
};

11957
playground/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,15 +6,15 @@ const ConfigType = class ConfigType {
constructor({ queryString, configName, uid, jsonFields, relations }) { constructor({ queryString, configName, uid, jsonFields, relations }) {
if (!configName) { if (!configName) {
strapi.log.error(logMessage('A config type was registered without a config name.')); strapi.log.error(logMessage('A config type was registered without a config name.'));
return; process.exit(0);
} }
if (!queryString) { if (!queryString) {
strapi.log.error(logMessage(`No query string found for the '${configName}' config type.`)); strapi.log.error(logMessage(`No query string found for the '${configName}' config type.`));
return; process.exit(0);
} }
if (!uid) { if (!uid) {
strapi.log.error(logMessage(`No uid found for the '${configName}' config type.`)); strapi.log.error(logMessage(`No uid found for the '${configName}' config type.`));
return; process.exit(0);
} }
this.queryString = queryString; this.queryString = queryString;
this.configPrefix = configName; this.configPrefix = configName;