mirror of
https://github.com/strapi/strapi.git
synced 2025-09-22 23:09:47 +00:00
Merge pull request #15169 from strapi/deits/strategies-cleanup
This commit is contained in:
commit
0444bb9929
@ -299,9 +299,8 @@ describe('Transfer engine', () => {
|
|||||||
} as IDestinationProvider;
|
} as IDestinationProvider;
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
strategy: 'restore',
|
versionStrategy: 'exact',
|
||||||
versionMatching: 'exact',
|
schemaStrategy: 'exact',
|
||||||
schemasMatching: 'exact',
|
|
||||||
exclude: [],
|
exclude: [],
|
||||||
} as ITransferEngineOptions;
|
} as ITransferEngineOptions;
|
||||||
|
|
||||||
@ -416,9 +415,8 @@ describe('Transfer engine', () => {
|
|||||||
describe('schema matching', () => {
|
describe('schema matching', () => {
|
||||||
describe('exact', () => {
|
describe('exact', () => {
|
||||||
const engineOptions = {
|
const engineOptions = {
|
||||||
strategy: 'restore',
|
versionStrategy: 'exact',
|
||||||
versionMatching: 'exact',
|
schemaStrategy: 'exact',
|
||||||
schemasMatching: 'exact',
|
|
||||||
exclude: [],
|
exclude: [],
|
||||||
} as ITransferEngineOptions;
|
} as ITransferEngineOptions;
|
||||||
test('source with source schema missing in destination fails', async () => {
|
test('source with source schema missing in destination fails', async () => {
|
||||||
@ -465,7 +463,7 @@ describe('Transfer engine', () => {
|
|||||||
const versionsThatFail = ['foo', 'z1.2.3', '1.2.3z'];
|
const versionsThatFail = ['foo', 'z1.2.3', '1.2.3z'];
|
||||||
const options: ITransferEngineOptions = {
|
const options: ITransferEngineOptions = {
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
versionMatching: 'exact',
|
versionStrategy: 'exact',
|
||||||
};
|
};
|
||||||
|
|
||||||
versionsThatFail.forEach((version) => {
|
versionsThatFail.forEach((version) => {
|
||||||
@ -487,7 +485,7 @@ describe('Transfer engine', () => {
|
|||||||
const versionsThatSucceed = ['1.2.3'];
|
const versionsThatSucceed = ['1.2.3'];
|
||||||
const options: ITransferEngineOptions = {
|
const options: ITransferEngineOptions = {
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
versionMatching: 'exact',
|
versionStrategy: 'exact',
|
||||||
};
|
};
|
||||||
|
|
||||||
versionsThatFail.forEach((version) => {
|
versionsThatFail.forEach((version) => {
|
||||||
@ -522,7 +520,7 @@ describe('Transfer engine', () => {
|
|||||||
const versionsThatSucceed = ['1.2.3', '1.3.4', '1.4.4-alpha'];
|
const versionsThatSucceed = ['1.2.3', '1.3.4', '1.4.4-alpha'];
|
||||||
const options: ITransferEngineOptions = {
|
const options: ITransferEngineOptions = {
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
versionMatching: 'major',
|
versionStrategy: 'major',
|
||||||
};
|
};
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@ -561,7 +559,7 @@ describe('Transfer engine', () => {
|
|||||||
const versionsThatSucceed = ['1.2.3', '1.2.40', '1.2.4-alpha'];
|
const versionsThatSucceed = ['1.2.3', '1.2.40', '1.2.4-alpha'];
|
||||||
const options: ITransferEngineOptions = {
|
const options: ITransferEngineOptions = {
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
versionMatching: 'minor',
|
versionStrategy: 'minor',
|
||||||
};
|
};
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@ -600,7 +598,7 @@ describe('Transfer engine', () => {
|
|||||||
const versionsThatSucceed = ['1.2.3'];
|
const versionsThatSucceed = ['1.2.3'];
|
||||||
const options: ITransferEngineOptions = {
|
const options: ITransferEngineOptions = {
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
versionMatching: 'patch',
|
versionStrategy: 'patch',
|
||||||
};
|
};
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@ -638,7 +636,7 @@ describe('Transfer engine', () => {
|
|||||||
const versionsThatSucceed = ['1.2.3', '1.3.4', '5.24.44-alpha'];
|
const versionsThatSucceed = ['1.2.3', '1.3.4', '5.24.44-alpha'];
|
||||||
const options: ITransferEngineOptions = {
|
const options: ITransferEngineOptions = {
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
versionMatching: 'ignore',
|
versionStrategy: 'ignore',
|
||||||
};
|
};
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { PassThrough, Transform, Readable, Writable } from 'stream';
|
import { PassThrough, Transform, Readable, Writable } from 'stream';
|
||||||
import * as path from 'path';
|
|
||||||
import { extname } from 'path';
|
import { extname } from 'path';
|
||||||
import { isEmpty, uniq } from 'lodash/fp';
|
import { isEmpty, uniq } from 'lodash/fp';
|
||||||
import { diff as semverDiff } from 'semver';
|
import { diff as semverDiff } from 'semver';
|
||||||
@ -24,6 +23,9 @@ import type { Diff } from '../utils/json';
|
|||||||
import compareSchemas from '../strategies';
|
import compareSchemas from '../strategies';
|
||||||
import { filter, map } from '../utils/stream';
|
import { filter, map } from '../utils/stream';
|
||||||
|
|
||||||
|
export const DEFAULT_VERSION_STRATEGY = 'ignore';
|
||||||
|
export const DEFAULT_SCHEMA_STRATEGY = 'strict';
|
||||||
|
|
||||||
type SchemaMap = Record<string, Schema>;
|
type SchemaMap = Record<string, Schema>;
|
||||||
|
|
||||||
class TransferEngine<
|
class TransferEngine<
|
||||||
@ -158,7 +160,7 @@ class TransferEngine<
|
|||||||
}
|
}
|
||||||
|
|
||||||
#assertStrapiVersionIntegrity(sourceVersion?: string, destinationVersion?: string) {
|
#assertStrapiVersionIntegrity(sourceVersion?: string, destinationVersion?: string) {
|
||||||
const strategy = this.options.versionMatching;
|
const strategy = this.options.versionStrategy || DEFAULT_VERSION_STRATEGY;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!sourceVersion ||
|
!sourceVersion ||
|
||||||
@ -200,7 +202,11 @@ class TransferEngine<
|
|||||||
}
|
}
|
||||||
|
|
||||||
#assertSchemasMatching(sourceSchemas: SchemaMap, destinationSchemas: SchemaMap) {
|
#assertSchemasMatching(sourceSchemas: SchemaMap, destinationSchemas: SchemaMap) {
|
||||||
const strategy = this.options.schemasMatching || 'strict';
|
const strategy = this.options.schemaStrategy || DEFAULT_SCHEMA_STRATEGY;
|
||||||
|
if (strategy === 'ignore') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const keys = uniq(Object.keys(sourceSchemas).concat(Object.keys(destinationSchemas)));
|
const keys = uniq(Object.keys(sourceSchemas).concat(Object.keys(destinationSchemas)));
|
||||||
const diffs: { [key: string]: Diff[] } = {};
|
const diffs: { [key: string]: Diff[] } = {};
|
||||||
|
|
||||||
|
@ -6,7 +6,8 @@ import type { IAsset, IDestinationProvider, IMetadata, ProviderType } from '../.
|
|||||||
import { restore } from './strategies';
|
import { restore } from './strategies';
|
||||||
import * as utils from '../../utils';
|
import * as utils from '../../utils';
|
||||||
|
|
||||||
export const VALID_STRATEGIES = ['restore', 'merge'];
|
export const VALID_CONFLICT_STRATEGIES = ['restore', 'merge'];
|
||||||
|
export const DEFAULT_CONFLICT_STRATEGY = 'restore';
|
||||||
|
|
||||||
interface ILocalStrapiDestinationProviderOptions {
|
interface ILocalStrapiDestinationProviderOptions {
|
||||||
getStrapi(): Strapi.Strapi | Promise<Strapi.Strapi>;
|
getStrapi(): Strapi.Strapi | Promise<Strapi.Strapi>;
|
||||||
@ -40,7 +41,7 @@ class LocalStrapiDestinationProvider implements IDestinationProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#validateOptions() {
|
#validateOptions() {
|
||||||
if (!VALID_STRATEGIES.includes(this.options.strategy)) {
|
if (!VALID_CONFLICT_STRATEGIES.includes(this.options.strategy)) {
|
||||||
throw new Error(`Invalid stategy ${this.options.strategy}`);
|
throw new Error(`Invalid stategy ${this.options.strategy}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,12 +112,12 @@ export interface ITransferEngineOptions {
|
|||||||
* "minor" // both the major and minor version should match. 4.3.9 and 4.3.11 will work, while 4.3.9 and 4.4.1 won't
|
* "minor" // both the major and minor version should match. 4.3.9 and 4.3.11 will work, while 4.3.9 and 4.4.1 won't
|
||||||
* "patch" // every part of the version should match. Similar to "exact" but only work on semver.
|
* "patch" // every part of the version should match. Similar to "exact" but only work on semver.
|
||||||
*/
|
*/
|
||||||
versionMatching: 'exact' | 'ignore' | 'major' | 'minor' | 'patch';
|
versionStrategy: 'exact' | 'ignore' | 'major' | 'minor' | 'patch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strategy used to do the schema matching in the integrity checks
|
* Strategy used to do the schema matching in the integrity checks
|
||||||
*/
|
*/
|
||||||
schemasMatching: 'exact' | 'strict';
|
schemaStrategy: 'exact' | 'strict' | 'ignore';
|
||||||
|
|
||||||
// List of rules to integrate into the final pipelines
|
// List of rules to integrate into the final pipelines
|
||||||
transforms?: {
|
transforms?: {
|
||||||
|
@ -14,11 +14,7 @@ const inquirer = require('inquirer');
|
|||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
const packageJSON = require('../package.json');
|
const packageJSON = require('../package.json');
|
||||||
const {
|
const { promptEncryptionKey, confirmMessage } = require('../lib/commands/utils/commander');
|
||||||
parseInputList,
|
|
||||||
promptEncryptionKey,
|
|
||||||
confirmKeyValue,
|
|
||||||
} = require('../lib/commands/utils/commander');
|
|
||||||
|
|
||||||
const checkCwdIsStrapiApp = (name) => {
|
const checkCwdIsStrapiApp = (name) => {
|
||||||
const logErrorAndExit = () => {
|
const logErrorAndExit = () => {
|
||||||
@ -273,45 +269,15 @@ program
|
|||||||
.addOption(
|
.addOption(
|
||||||
new Option('--key <string>', 'Provide encryption key in command instead of using a prompt')
|
new Option('--key <string>', 'Provide encryption key in command instead of using a prompt')
|
||||||
)
|
)
|
||||||
.addOption(
|
|
||||||
new Option(
|
|
||||||
'--max-size-jsonl <max MB per internal backup file>',
|
|
||||||
'split internal jsonl files when exceeding max size in MB'
|
|
||||||
)
|
|
||||||
.argParser(parseFloat)
|
|
||||||
.default(256)
|
|
||||||
)
|
|
||||||
.addOption(new Option('-f, --file <file>', 'name to use for exported file (without extensions)'))
|
.addOption(new Option('-f, --file <file>', 'name to use for exported file (without extensions)'))
|
||||||
.allowExcessArguments(false)
|
.allowExcessArguments(false)
|
||||||
.hook('preAction', promptEncryptionKey)
|
.hook('preAction', promptEncryptionKey)
|
||||||
// validate inputs
|
|
||||||
.hook('preAction', (thisCommand) => {
|
|
||||||
const opts = thisCommand.opts();
|
|
||||||
if (!opts.maxSizeJsonl) {
|
|
||||||
console.error('Invalid max-size-jsonl provided. Must be a number value.');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.action(getLocalScript('transfer/export'));
|
.action(getLocalScript('transfer/export'));
|
||||||
|
|
||||||
// `$ strapi import`
|
// `$ strapi import`
|
||||||
program
|
program
|
||||||
.command('import')
|
.command('import')
|
||||||
.description('Import data from file to Strapi')
|
.description('Import data from file to Strapi')
|
||||||
.addOption(
|
|
||||||
new Option('--conflictStrategy <conflictStrategy>', 'Which strategy to use for ID conflicts')
|
|
||||||
.choices(['restore', 'abort', 'keep', 'replace'])
|
|
||||||
.default('restore')
|
|
||||||
)
|
|
||||||
.addOption(
|
|
||||||
new Option(
|
|
||||||
'--schemaComparison <schemaComparison>',
|
|
||||||
'exact requires every field to match, strict requires Strapi version and content type schema fields do not break, subset requires source schema to exist in destination, bypass skips checks',
|
|
||||||
parseInputList
|
|
||||||
)
|
|
||||||
.choices(['exact', 'strict', 'subset', 'bypass'])
|
|
||||||
.default('exact')
|
|
||||||
)
|
|
||||||
.requiredOption(
|
.requiredOption(
|
||||||
'-f, --file <file>',
|
'-f, --file <file>',
|
||||||
'path and filename to the Strapi export file you want to import'
|
'path and filename to the Strapi export file you want to import'
|
||||||
@ -344,10 +310,8 @@ program
|
|||||||
})
|
})
|
||||||
.hook(
|
.hook(
|
||||||
'preAction',
|
'preAction',
|
||||||
confirmKeyValue(
|
confirmMessage(
|
||||||
'conflictStrategy',
|
'The import will delete all data in your database. Are you sure you want to proceed?'
|
||||||
'restore',
|
|
||||||
"Using strategy 'restore' will delete all data in your database. Are you sure you want to proceed?"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.action(getLocalScript('transfer/import'));
|
.action(getLocalScript('transfer/import'));
|
||||||
|
@ -22,7 +22,6 @@ const {
|
|||||||
* @typedef ImportCommandOptions Options given to the CLI import command
|
* @typedef ImportCommandOptions Options given to the CLI import command
|
||||||
*
|
*
|
||||||
* @property {string} [file] The file path to import
|
* @property {string} [file] The file path to import
|
||||||
* @property {number} [maxSizeJsonl] Maximum size for each .jsonl file
|
|
||||||
* @property {boolean} [encrypt] Used to encrypt the final archive
|
* @property {boolean} [encrypt] Used to encrypt the final archive
|
||||||
* @property {string} [key] Encryption key, only useful when encryption is enabled
|
* @property {string} [key] Encryption key, only useful when encryption is enabled
|
||||||
* @property {boolean} [compress] Used to compress the final archive
|
* @property {boolean} [compress] Used to compress the final archive
|
||||||
@ -52,8 +51,8 @@ module.exports = async (opts) => {
|
|||||||
const destination = createDestinationProvider(opts);
|
const destination = createDestinationProvider(opts);
|
||||||
|
|
||||||
const engine = createTransferEngine(source, destination, {
|
const engine = createTransferEngine(source, destination, {
|
||||||
strategy: 'restore', // for an export to file, strategy will always be 'restore'
|
versionStrategy: 'ignore', // for an export to file, versionStrategy will always be skipped
|
||||||
versionMatching: 'ignore', // for an export to file, versionMatching will always be skipped
|
schemaStrategy: 'ignore', // for an export to file, schemaStrategy will always be skipped
|
||||||
transforms: {
|
transforms: {
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
|
@ -4,6 +4,9 @@ const {
|
|||||||
createLocalFileSourceProvider,
|
createLocalFileSourceProvider,
|
||||||
createLocalStrapiDestinationProvider,
|
createLocalStrapiDestinationProvider,
|
||||||
createTransferEngine,
|
createTransferEngine,
|
||||||
|
DEFAULT_VERSION_STRATEGY,
|
||||||
|
DEFAULT_SCHEMA_STRATEGY,
|
||||||
|
DEFAULT_CONFLICT_STRATEGY,
|
||||||
// TODO: we need to solve this issue with typescript modules
|
// TODO: we need to solve this issue with typescript modules
|
||||||
// eslint-disable-next-line import/no-unresolved, node/no-missing-require
|
// eslint-disable-next-line import/no-unresolved, node/no-missing-require
|
||||||
} = require('@strapi/data-transfer');
|
} = require('@strapi/data-transfer');
|
||||||
@ -42,7 +45,7 @@ module.exports = async (opts) => {
|
|||||||
async getStrapi() {
|
async getStrapi() {
|
||||||
return strapiInstance;
|
return strapiInstance;
|
||||||
},
|
},
|
||||||
strategy: opts.conflictStrategy,
|
strategy: opts.conflictStrategy || DEFAULT_CONFLICT_STRATEGY,
|
||||||
restore: {
|
restore: {
|
||||||
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
||||||
},
|
},
|
||||||
@ -53,8 +56,8 @@ module.exports = async (opts) => {
|
|||||||
* Configure and run the transfer engine
|
* Configure and run the transfer engine
|
||||||
*/
|
*/
|
||||||
const engineOptions = {
|
const engineOptions = {
|
||||||
strategy: opts.conflictStrategy,
|
versionStrategy: opts.versionStrategy || DEFAULT_VERSION_STRATEGY,
|
||||||
versionMatching: opts.schemaComparison,
|
schemaStrategy: opts.schemaStrategy || DEFAULT_SCHEMA_STRATEGY,
|
||||||
exclude: opts.exclude,
|
exclude: opts.exclude,
|
||||||
rules: {
|
rules: {
|
||||||
links: [
|
links: [
|
||||||
|
@ -48,25 +48,19 @@ const promptEncryptionKey = async (thisCommand) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hook: confirm that key has a value with a provided message
|
* hook: require a confirmation message to be accepted
|
||||||
*/
|
*/
|
||||||
const confirmKeyValue = (key, value, message) => {
|
const confirmMessage = (message) => {
|
||||||
return async (thisCommand) => {
|
return async () => {
|
||||||
const opts = thisCommand.opts();
|
|
||||||
|
|
||||||
if (!opts[key] || opts[key] !== value) {
|
|
||||||
console.error(`Could not confirm key ${key}, halting operation.`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
const answers = await inquirer.prompt([
|
const answers = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
message,
|
message,
|
||||||
name: `confirm_${key}`,
|
name: `confirm`,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
if (!answers[`confirm_${key}`]) {
|
if (!answers.confirm) {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -75,5 +69,5 @@ const confirmKeyValue = (key, value, message) => {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
parseInputList,
|
parseInputList,
|
||||||
promptEncryptionKey,
|
promptEncryptionKey,
|
||||||
confirmKeyValue,
|
confirmMessage,
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user