mirror of
https://github.com/strapi/strapi.git
synced 2025-11-09 22:59:14 +00:00
Merge branch 'features/deits' into deits/import-configs
This commit is contained in:
commit
db8dac601f
@ -18,18 +18,15 @@ import { chain, Writable } from 'stream-chain';
|
|||||||
import { createEncryptionCipher } from '../../encryption/encrypt';
|
import { createEncryptionCipher } from '../../encryption/encrypt';
|
||||||
import { createFilePathFactory, createTarEntryStream } from './utils';
|
import { createFilePathFactory, createTarEntryStream } from './utils';
|
||||||
export interface ILocalFileDestinationProviderOptions {
|
export interface ILocalFileDestinationProviderOptions {
|
||||||
// Encryption
|
|
||||||
encryption: {
|
encryption: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
key?: string;
|
key?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compression
|
|
||||||
compression: {
|
compression: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// File
|
|
||||||
file: {
|
file: {
|
||||||
path: string;
|
path: string;
|
||||||
maxSize?: number;
|
maxSize?: number;
|
||||||
|
|||||||
@ -24,25 +24,18 @@ const METADATA_FILE_PATH = 'metadata.json';
|
|||||||
* Provider options
|
* Provider options
|
||||||
*/
|
*/
|
||||||
export interface ILocalFileSourceProviderOptions {
|
export interface ILocalFileSourceProviderOptions {
|
||||||
/**
|
file: {
|
||||||
* Path to the backup archive
|
path: string;
|
||||||
*/
|
};
|
||||||
backupFilePath: string;
|
|
||||||
|
|
||||||
/**
|
encryption: {
|
||||||
* Whether the backup data is encrypted or not
|
enabled: boolean;
|
||||||
*/
|
key?: string;
|
||||||
encrypted?: boolean;
|
};
|
||||||
|
|
||||||
/**
|
compression: {
|
||||||
* Encryption key used to decrypt the encrypted data (if necessary)
|
enabled: boolean;
|
||||||
*/
|
};
|
||||||
encryptionKey?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the backup data is compressed or not
|
|
||||||
*/
|
|
||||||
compressed?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createLocalFileSourceProvider = (options: ILocalFileSourceProviderOptions) => {
|
export const createLocalFileSourceProvider = (options: ILocalFileSourceProviderOptions) => {
|
||||||
@ -58,7 +51,9 @@ class LocalFileSourceProvider implements ISourceProvider {
|
|||||||
constructor(options: ILocalFileSourceProviderOptions) {
|
constructor(options: ILocalFileSourceProviderOptions) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
|
||||||
if (this.options.encrypted && this.options.encryptionKey === undefined) {
|
const { encryption } = this.options;
|
||||||
|
|
||||||
|
if (encryption.enabled && encryption.key === undefined) {
|
||||||
throw new Error('Missing encryption key');
|
throw new Error('Missing encryption key');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,7 +62,7 @@ class LocalFileSourceProvider implements ISourceProvider {
|
|||||||
* Pre flight checks regarding the provided options (making sure that the provided path is correct, etc...)
|
* Pre flight checks regarding the provided options (making sure that the provided path is correct, etc...)
|
||||||
*/
|
*/
|
||||||
bootstrap() {
|
bootstrap() {
|
||||||
const path = this.options.backupFilePath;
|
const { path } = this.options.file;
|
||||||
const isValidBackupPath = fs.existsSync(path);
|
const isValidBackupPath = fs.existsSync(path);
|
||||||
|
|
||||||
// Check if the provided path exists
|
// Check if the provided path exists
|
||||||
@ -108,13 +103,17 @@ class LocalFileSourceProvider implements ISourceProvider {
|
|||||||
return this.#streamJsonlDirectory('configuration');
|
return this.#streamJsonlDirectory('configuration');
|
||||||
}
|
}
|
||||||
|
|
||||||
#getBackupStream(decompress: boolean = true) {
|
#getBackupStream() {
|
||||||
const path = this.options.backupFilePath;
|
const { file, encryption, compression } = this.options;
|
||||||
const readStream = fs.createReadStream(path);
|
|
||||||
const streams: StreamItemArray = [readStream];
|
|
||||||
|
|
||||||
// Handle decompression
|
const fileStream = fs.createReadStream(file.path);
|
||||||
if (decompress) {
|
const streams: StreamItemArray = [fileStream];
|
||||||
|
|
||||||
|
if (encryption.enabled && encryption.key) {
|
||||||
|
streams.push(createDecryptionCipher(encryption.key));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compression.enabled) {
|
||||||
streams.push(zip.createGunzip());
|
streams.push(zip.createGunzip());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,22 +145,12 @@ class LocalFileSourceProvider implements ISourceProvider {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onentry(entry) {
|
onentry(entry) {
|
||||||
const transforms = [];
|
const transforms = [
|
||||||
|
|
||||||
if (options.encrypted) {
|
|
||||||
transforms.push(createDecryptionCipher(options.encryptionKey!));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.compressed) {
|
|
||||||
transforms.push(zip.createGunzip());
|
|
||||||
}
|
|
||||||
|
|
||||||
transforms.push(
|
|
||||||
// JSONL parser to read the data chunks one by one (line by line)
|
// JSONL parser to read the data chunks one by one (line by line)
|
||||||
parser(),
|
parser(),
|
||||||
// The JSONL parser returns each line as key/value
|
// The JSONL parser returns each line as key/value
|
||||||
(line: { key: string; value: any }) => line.value
|
(line: { key: string; value: any }) => line.value,
|
||||||
);
|
];
|
||||||
|
|
||||||
entry
|
entry
|
||||||
// Pipe transforms
|
// Pipe transforms
|
||||||
@ -220,7 +209,7 @@ class LocalFileSourceProvider implements ISourceProvider {
|
|||||||
() => {
|
() => {
|
||||||
// If the promise hasn't been resolved and we've parsed all
|
// If the promise hasn't been resolved and we've parsed all
|
||||||
// the archive entries, then the file doesn't exist
|
// the archive entries, then the file doesn't exist
|
||||||
reject(`${filePath} not found in the archive stream`);
|
reject(new Error(`File "${filePath}" not found`));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -15,10 +15,6 @@ interface ILocalStrapiDestinationProviderOptions {
|
|||||||
strategy: 'restore' | 'merge';
|
strategy: 'restore' | 'merge';
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: getting some type errors with @strapi/logger that need to be resolved first
|
|
||||||
// const log = createLogger();
|
|
||||||
const log = console;
|
|
||||||
|
|
||||||
export const createLocalStrapiDestinationProvider = (
|
export const createLocalStrapiDestinationProvider = (
|
||||||
options: ILocalStrapiDestinationProviderOptions
|
options: ILocalStrapiDestinationProviderOptions
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
// FIXME
|
// FIXME
|
||||||
/* eslint-disable import/extensions */
|
/* eslint-disable import/extensions */
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const path = require('path');
|
||||||
const resolveCwd = require('resolve-cwd');
|
const resolveCwd = require('resolve-cwd');
|
||||||
const { yellow } = require('chalk');
|
const { yellow } = require('chalk');
|
||||||
const { Command, Option } = require('commander');
|
const { Command, Option } = require('commander');
|
||||||
@ -316,14 +317,15 @@ program
|
|||||||
'path and filename to the Strapi export file you want to import'
|
'path and filename to the Strapi export file you want to import'
|
||||||
)
|
)
|
||||||
.addOption(
|
.addOption(
|
||||||
new Option('--key <string>', 'Provide encryption key in command instead of using a prompt')
|
new Option('-k, --key <string>', 'Provide encryption key in command instead of using a prompt')
|
||||||
)
|
)
|
||||||
.allowExcessArguments(false)
|
.allowExcessArguments(false)
|
||||||
.hook('preAction', async (thisCommand) => {
|
.hook('preAction', async (thisCommand) => {
|
||||||
const opts = thisCommand.opts();
|
const opts = thisCommand.opts();
|
||||||
|
const ext = path.extname(String(opts.file));
|
||||||
|
|
||||||
// check extension to guess if we should prompt for key
|
// check extension to guess if we should prompt for key
|
||||||
if (String(opts.file).endsWith('.enc')) {
|
if (ext === '.enc') {
|
||||||
if (!opts.key) {
|
if (!opts.key) {
|
||||||
const answers = await inquirer.prompt([
|
const answers = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
|
|||||||
@ -8,10 +8,15 @@ const {
|
|||||||
// 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');
|
||||||
const { isObject } = require('lodash/fp');
|
const { isObject } = require('lodash/fp');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const strapi = require('../../index');
|
const strapi = require('../../index');
|
||||||
const { buildTransferTable } = require('./utils');
|
const { buildTransferTable } = require('./utils');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('@strapi/data-transfer').ILocalFileSourceProviderOptions} ILocalFileSourceProviderOptions
|
||||||
|
*/
|
||||||
|
|
||||||
const logger = console;
|
const logger = console;
|
||||||
|
|
||||||
module.exports = async (opts) => {
|
module.exports = async (opts) => {
|
||||||
@ -20,14 +25,12 @@ module.exports = async (opts) => {
|
|||||||
logger.error('Could not parse arguments');
|
logger.error('Could not parse arguments');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const filename = opts.file;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* From strapi backup file
|
* From strapi backup file
|
||||||
*/
|
*/
|
||||||
const sourceOptions = {
|
const sourceOptions = getLocalFileSourceOptions(opts);
|
||||||
backupFilePath: filename,
|
|
||||||
};
|
|
||||||
const source = createLocalFileSourceProvider(sourceOptions);
|
const source = createLocalFileSourceProvider(sourceOptions);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,16 +72,50 @@ module.exports = async (opts) => {
|
|||||||
const engine = createTransferEngine(source, destination, engineOptions);
|
const engine = createTransferEngine(source, destination, engineOptions);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log('Starting import...');
|
logger.info('Starting import...');
|
||||||
|
|
||||||
const results = await engine.transfer();
|
const results = await engine.transfer();
|
||||||
const table = buildTransferTable(results.engine);
|
const table = buildTransferTable(results.engine);
|
||||||
logger.log(table.toString());
|
logger.info(table.toString());
|
||||||
|
|
||||||
logger.log('Import process has been completed successfully!');
|
logger.info('Import process has been completed successfully!');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.log(`Import process failed unexpectedly: ${e.message}`);
|
logger.error(`Import process failed unexpectedly: ${e.message}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infer local file source provider options based on a given filename
|
||||||
|
*
|
||||||
|
* @param {{ file: string; key?: string }} opts
|
||||||
|
*
|
||||||
|
* @return {ILocalFileSourceProviderOptions}
|
||||||
|
*/
|
||||||
|
const getLocalFileSourceOptions = (opts) => {
|
||||||
|
/**
|
||||||
|
* @type {ILocalFileSourceProviderOptions}
|
||||||
|
*/
|
||||||
|
const options = {
|
||||||
|
file: { path: opts.file },
|
||||||
|
compression: { enabled: false },
|
||||||
|
encryption: { enabled: false },
|
||||||
|
};
|
||||||
|
|
||||||
|
const { extname, parse } = path;
|
||||||
|
|
||||||
|
let file = options.file.path;
|
||||||
|
|
||||||
|
if (extname(file) === '.enc') {
|
||||||
|
file = parse(file).name;
|
||||||
|
options.encryption = { enabled: true, key: opts.key };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extname(file) === '.gz') {
|
||||||
|
file = parse(file).name;
|
||||||
|
options.compression = { enabled: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user