mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 03:43:34 +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;
 | 
			
		||||
 | 
			
		||||
  const defaultOptions = {
 | 
			
		||||
    strategy: 'restore',
 | 
			
		||||
    versionMatching: 'exact',
 | 
			
		||||
    schemasMatching: 'exact',
 | 
			
		||||
    versionStrategy: 'exact',
 | 
			
		||||
    schemaStrategy: 'exact',
 | 
			
		||||
    exclude: [],
 | 
			
		||||
  } as ITransferEngineOptions;
 | 
			
		||||
 | 
			
		||||
@ -416,9 +415,8 @@ describe('Transfer engine', () => {
 | 
			
		||||
    describe('schema matching', () => {
 | 
			
		||||
      describe('exact', () => {
 | 
			
		||||
        const engineOptions = {
 | 
			
		||||
          strategy: 'restore',
 | 
			
		||||
          versionMatching: 'exact',
 | 
			
		||||
          schemasMatching: 'exact',
 | 
			
		||||
          versionStrategy: 'exact',
 | 
			
		||||
          schemaStrategy: 'exact',
 | 
			
		||||
          exclude: [],
 | 
			
		||||
        } as ITransferEngineOptions;
 | 
			
		||||
        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 options: ITransferEngineOptions = {
 | 
			
		||||
          ...defaultOptions,
 | 
			
		||||
          versionMatching: 'exact',
 | 
			
		||||
          versionStrategy: 'exact',
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        versionsThatFail.forEach((version) => {
 | 
			
		||||
@ -487,7 +485,7 @@ describe('Transfer engine', () => {
 | 
			
		||||
        const versionsThatSucceed = ['1.2.3'];
 | 
			
		||||
        const options: ITransferEngineOptions = {
 | 
			
		||||
          ...defaultOptions,
 | 
			
		||||
          versionMatching: 'exact',
 | 
			
		||||
          versionStrategy: 'exact',
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        versionsThatFail.forEach((version) => {
 | 
			
		||||
@ -522,7 +520,7 @@ describe('Transfer engine', () => {
 | 
			
		||||
        const versionsThatSucceed = ['1.2.3', '1.3.4', '1.4.4-alpha'];
 | 
			
		||||
        const options: ITransferEngineOptions = {
 | 
			
		||||
          ...defaultOptions,
 | 
			
		||||
          versionMatching: 'major',
 | 
			
		||||
          versionStrategy: 'major',
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await Promise.all(
 | 
			
		||||
@ -561,7 +559,7 @@ describe('Transfer engine', () => {
 | 
			
		||||
        const versionsThatSucceed = ['1.2.3', '1.2.40', '1.2.4-alpha'];
 | 
			
		||||
        const options: ITransferEngineOptions = {
 | 
			
		||||
          ...defaultOptions,
 | 
			
		||||
          versionMatching: 'minor',
 | 
			
		||||
          versionStrategy: 'minor',
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await Promise.all(
 | 
			
		||||
@ -600,7 +598,7 @@ describe('Transfer engine', () => {
 | 
			
		||||
        const versionsThatSucceed = ['1.2.3'];
 | 
			
		||||
        const options: ITransferEngineOptions = {
 | 
			
		||||
          ...defaultOptions,
 | 
			
		||||
          versionMatching: 'patch',
 | 
			
		||||
          versionStrategy: 'patch',
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await Promise.all(
 | 
			
		||||
@ -638,7 +636,7 @@ describe('Transfer engine', () => {
 | 
			
		||||
        const versionsThatSucceed = ['1.2.3', '1.3.4', '5.24.44-alpha'];
 | 
			
		||||
        const options: ITransferEngineOptions = {
 | 
			
		||||
          ...defaultOptions,
 | 
			
		||||
          versionMatching: 'ignore',
 | 
			
		||||
          versionStrategy: 'ignore',
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await Promise.all(
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
import { PassThrough, Transform, Readable, Writable } from 'stream';
 | 
			
		||||
import * as path from 'path';
 | 
			
		||||
import { extname } from 'path';
 | 
			
		||||
import { isEmpty, uniq } from 'lodash/fp';
 | 
			
		||||
import { diff as semverDiff } from 'semver';
 | 
			
		||||
@ -24,6 +23,9 @@ import type { Diff } from '../utils/json';
 | 
			
		||||
import compareSchemas from '../strategies';
 | 
			
		||||
import { filter, map } from '../utils/stream';
 | 
			
		||||
 | 
			
		||||
export const DEFAULT_VERSION_STRATEGY = 'ignore';
 | 
			
		||||
export const DEFAULT_SCHEMA_STRATEGY = 'strict';
 | 
			
		||||
 | 
			
		||||
type SchemaMap = Record<string, Schema>;
 | 
			
		||||
 | 
			
		||||
class TransferEngine<
 | 
			
		||||
@ -158,7 +160,7 @@ class TransferEngine<
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #assertStrapiVersionIntegrity(sourceVersion?: string, destinationVersion?: string) {
 | 
			
		||||
    const strategy = this.options.versionMatching;
 | 
			
		||||
    const strategy = this.options.versionStrategy || DEFAULT_VERSION_STRATEGY;
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
      !sourceVersion ||
 | 
			
		||||
@ -200,7 +202,11 @@ class TransferEngine<
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #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 diffs: { [key: string]: Diff[] } = {};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,8 @@ import type { IAsset, IDestinationProvider, IMetadata, ProviderType } from '../.
 | 
			
		||||
import { restore } from './strategies';
 | 
			
		||||
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 {
 | 
			
		||||
  getStrapi(): Strapi.Strapi | Promise<Strapi.Strapi>;
 | 
			
		||||
@ -40,7 +41,7 @@ class LocalStrapiDestinationProvider implements IDestinationProvider {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #validateOptions() {
 | 
			
		||||
    if (!VALID_STRATEGIES.includes(this.options.strategy)) {
 | 
			
		||||
    if (!VALID_CONFLICT_STRATEGIES.includes(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
 | 
			
		||||
   * "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
 | 
			
		||||
   */
 | 
			
		||||
  schemasMatching: 'exact' | 'strict';
 | 
			
		||||
  schemaStrategy: 'exact' | 'strict' | 'ignore';
 | 
			
		||||
 | 
			
		||||
  // List of rules to integrate into the final pipelines
 | 
			
		||||
  transforms?: {
 | 
			
		||||
 | 
			
		||||
@ -14,11 +14,7 @@ const inquirer = require('inquirer');
 | 
			
		||||
const program = new Command();
 | 
			
		||||
 | 
			
		||||
const packageJSON = require('../package.json');
 | 
			
		||||
const {
 | 
			
		||||
  parseInputList,
 | 
			
		||||
  promptEncryptionKey,
 | 
			
		||||
  confirmKeyValue,
 | 
			
		||||
} = require('../lib/commands/utils/commander');
 | 
			
		||||
const { promptEncryptionKey, confirmMessage } = require('../lib/commands/utils/commander');
 | 
			
		||||
 | 
			
		||||
const checkCwdIsStrapiApp = (name) => {
 | 
			
		||||
  const logErrorAndExit = () => {
 | 
			
		||||
@ -273,45 +269,15 @@ program
 | 
			
		||||
  .addOption(
 | 
			
		||||
    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)'))
 | 
			
		||||
  .allowExcessArguments(false)
 | 
			
		||||
  .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'));
 | 
			
		||||
 | 
			
		||||
// `$ strapi import`
 | 
			
		||||
program
 | 
			
		||||
  .command('import')
 | 
			
		||||
  .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(
 | 
			
		||||
    '-f, --file <file>',
 | 
			
		||||
    'path and filename to the Strapi export file you want to import'
 | 
			
		||||
@ -344,10 +310,8 @@ program
 | 
			
		||||
  })
 | 
			
		||||
  .hook(
 | 
			
		||||
    'preAction',
 | 
			
		||||
    confirmKeyValue(
 | 
			
		||||
      'conflictStrategy',
 | 
			
		||||
      'restore',
 | 
			
		||||
      "Using strategy 'restore' will delete all data in your database. Are you sure you want to proceed?"
 | 
			
		||||
    confirmMessage(
 | 
			
		||||
      'The import will delete all data in your database. Are you sure you want to proceed?'
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  .action(getLocalScript('transfer/import'));
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,6 @@ const {
 | 
			
		||||
 * @typedef ImportCommandOptions Options given to the CLI import command
 | 
			
		||||
 *
 | 
			
		||||
 * @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 {string} [key] Encryption key, only useful when encryption is enabled
 | 
			
		||||
 * @property {boolean} [compress] Used to compress the final archive
 | 
			
		||||
@ -52,8 +51,8 @@ module.exports = async (opts) => {
 | 
			
		||||
  const destination = createDestinationProvider(opts);
 | 
			
		||||
 | 
			
		||||
  const engine = createTransferEngine(source, destination, {
 | 
			
		||||
    strategy: 'restore', // for an export to file, strategy will always be 'restore'
 | 
			
		||||
    versionMatching: 'ignore', // for an export to file, versionMatching will always be skipped
 | 
			
		||||
    versionStrategy: 'ignore', // for an export to file, versionStrategy will always be skipped
 | 
			
		||||
    schemaStrategy: 'ignore', // for an export to file, schemaStrategy will always be skipped
 | 
			
		||||
    transforms: {
 | 
			
		||||
      links: [
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,9 @@ const {
 | 
			
		||||
  createLocalFileSourceProvider,
 | 
			
		||||
  createLocalStrapiDestinationProvider,
 | 
			
		||||
  createTransferEngine,
 | 
			
		||||
  DEFAULT_VERSION_STRATEGY,
 | 
			
		||||
  DEFAULT_SCHEMA_STRATEGY,
 | 
			
		||||
  DEFAULT_CONFLICT_STRATEGY,
 | 
			
		||||
  // TODO: we need to solve this issue with typescript modules
 | 
			
		||||
  // eslint-disable-next-line import/no-unresolved, node/no-missing-require
 | 
			
		||||
} = require('@strapi/data-transfer');
 | 
			
		||||
@ -42,7 +45,7 @@ module.exports = async (opts) => {
 | 
			
		||||
    async getStrapi() {
 | 
			
		||||
      return strapiInstance;
 | 
			
		||||
    },
 | 
			
		||||
    strategy: opts.conflictStrategy,
 | 
			
		||||
    strategy: opts.conflictStrategy || DEFAULT_CONFLICT_STRATEGY,
 | 
			
		||||
    restore: {
 | 
			
		||||
      entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
 | 
			
		||||
    },
 | 
			
		||||
@ -53,8 +56,8 @@ module.exports = async (opts) => {
 | 
			
		||||
   * Configure and run the transfer engine
 | 
			
		||||
   */
 | 
			
		||||
  const engineOptions = {
 | 
			
		||||
    strategy: opts.conflictStrategy,
 | 
			
		||||
    versionMatching: opts.schemaComparison,
 | 
			
		||||
    versionStrategy: opts.versionStrategy || DEFAULT_VERSION_STRATEGY,
 | 
			
		||||
    schemaStrategy: opts.schemaStrategy || DEFAULT_SCHEMA_STRATEGY,
 | 
			
		||||
    exclude: opts.exclude,
 | 
			
		||||
    rules: {
 | 
			
		||||
      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) => {
 | 
			
		||||
  return async (thisCommand) => {
 | 
			
		||||
    const opts = thisCommand.opts();
 | 
			
		||||
 | 
			
		||||
    if (!opts[key] || opts[key] !== value) {
 | 
			
		||||
      console.error(`Could not confirm key ${key}, halting operation.`);
 | 
			
		||||
      process.exit(1);
 | 
			
		||||
    }
 | 
			
		||||
const confirmMessage = (message) => {
 | 
			
		||||
  return async () => {
 | 
			
		||||
    const answers = await inquirer.prompt([
 | 
			
		||||
      {
 | 
			
		||||
        type: 'confirm',
 | 
			
		||||
        message,
 | 
			
		||||
        name: `confirm_${key}`,
 | 
			
		||||
        name: `confirm`,
 | 
			
		||||
        default: false,
 | 
			
		||||
      },
 | 
			
		||||
    ]);
 | 
			
		||||
    if (!answers[`confirm_${key}`]) {
 | 
			
		||||
    if (!answers.confirm) {
 | 
			
		||||
      process.exit(0);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
@ -75,5 +69,5 @@ const confirmKeyValue = (key, value, message) => {
 | 
			
		||||
module.exports = {
 | 
			
		||||
  parseInputList,
 | 
			
		||||
  promptEncryptionKey,
 | 
			
		||||
  confirmKeyValue,
 | 
			
		||||
  confirmMessage,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user