Merge branch 'features/deits' into deits/import-configs

This commit is contained in:
Bassel 2022-12-07 11:36:18 +02:00
commit db8dac601f
5 changed files with 77 additions and 56 deletions

View File

@ -18,18 +18,15 @@ import { chain, Writable } from 'stream-chain';
import { createEncryptionCipher } from '../../encryption/encrypt';
import { createFilePathFactory, createTarEntryStream } from './utils';
export interface ILocalFileDestinationProviderOptions {
// Encryption
encryption: {
enabled: boolean;
key?: string;
};
// Compression
compression: {
enabled: boolean;
};
// File
file: {
path: string;
maxSize?: number;

View File

@ -24,25 +24,18 @@ const METADATA_FILE_PATH = 'metadata.json';
* Provider options
*/
export interface ILocalFileSourceProviderOptions {
/**
* Path to the backup archive
*/
backupFilePath: string;
file: {
path: string;
};
/**
* Whether the backup data is encrypted or not
*/
encrypted?: boolean;
encryption: {
enabled: boolean;
key?: string;
};
/**
* Encryption key used to decrypt the encrypted data (if necessary)
*/
encryptionKey?: string;
/**
* Whether the backup data is compressed or not
*/
compressed?: boolean;
compression: {
enabled: boolean;
};
}
export const createLocalFileSourceProvider = (options: ILocalFileSourceProviderOptions) => {
@ -58,7 +51,9 @@ class LocalFileSourceProvider implements ISourceProvider {
constructor(options: ILocalFileSourceProviderOptions) {
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');
}
}
@ -67,7 +62,7 @@ class LocalFileSourceProvider implements ISourceProvider {
* Pre flight checks regarding the provided options (making sure that the provided path is correct, etc...)
*/
bootstrap() {
const path = this.options.backupFilePath;
const { path } = this.options.file;
const isValidBackupPath = fs.existsSync(path);
// Check if the provided path exists
@ -108,13 +103,17 @@ class LocalFileSourceProvider implements ISourceProvider {
return this.#streamJsonlDirectory('configuration');
}
#getBackupStream(decompress: boolean = true) {
const path = this.options.backupFilePath;
const readStream = fs.createReadStream(path);
const streams: StreamItemArray = [readStream];
#getBackupStream() {
const { file, encryption, compression } = this.options;
// Handle decompression
if (decompress) {
const fileStream = fs.createReadStream(file.path);
const streams: StreamItemArray = [fileStream];
if (encryption.enabled && encryption.key) {
streams.push(createDecryptionCipher(encryption.key));
}
if (compression.enabled) {
streams.push(zip.createGunzip());
}
@ -146,22 +145,12 @@ class LocalFileSourceProvider implements ISourceProvider {
},
onentry(entry) {
const transforms = [];
if (options.encrypted) {
transforms.push(createDecryptionCipher(options.encryptionKey!));
}
if (options.compressed) {
transforms.push(zip.createGunzip());
}
transforms.push(
const transforms = [
// JSONL parser to read the data chunks one by one (line by line)
parser(),
// The JSONL parser returns each line as key/value
(line: { key: string; value: any }) => line.value
);
(line: { key: string; value: any }) => line.value,
];
entry
// Pipe transforms
@ -220,7 +209,7 @@ class LocalFileSourceProvider implements ISourceProvider {
() => {
// If the promise hasn't been resolved and we've parsed all
// the archive entries, then the file doesn't exist
reject(`${filePath} not found in the archive stream`);
reject(new Error(`File "${filePath}" not found`));
}
);
});

View File

@ -15,10 +15,6 @@ interface ILocalStrapiDestinationProviderOptions {
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 = (
options: ILocalStrapiDestinationProviderOptions
) => {

View File

@ -5,6 +5,7 @@
// FIXME
/* eslint-disable import/extensions */
const _ = require('lodash');
const path = require('path');
const resolveCwd = require('resolve-cwd');
const { yellow } = require('chalk');
const { Command, Option } = require('commander');
@ -316,14 +317,15 @@ program
'path and filename to the Strapi export file you want to import'
)
.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)
.hook('preAction', async (thisCommand) => {
const opts = thisCommand.opts();
const ext = path.extname(String(opts.file));
// check extension to guess if we should prompt for key
if (String(opts.file).endsWith('.enc')) {
if (ext === '.enc') {
if (!opts.key) {
const answers = await inquirer.prompt([
{

View File

@ -8,10 +8,15 @@ const {
// eslint-disable-next-line import/no-unresolved, node/no-missing-require
} = require('@strapi/data-transfer');
const { isObject } = require('lodash/fp');
const path = require('path');
const strapi = require('../../index');
const { buildTransferTable } = require('./utils');
/**
* @typedef {import('@strapi/data-transfer').ILocalFileSourceProviderOptions} ILocalFileSourceProviderOptions
*/
const logger = console;
module.exports = async (opts) => {
@ -20,14 +25,12 @@ module.exports = async (opts) => {
logger.error('Could not parse arguments');
process.exit(1);
}
const filename = opts.file;
/**
* From strapi backup file
*/
const sourceOptions = {
backupFilePath: filename,
};
const sourceOptions = getLocalFileSourceOptions(opts);
const source = createLocalFileSourceProvider(sourceOptions);
/**
@ -69,16 +72,50 @@ module.exports = async (opts) => {
const engine = createTransferEngine(source, destination, engineOptions);
try {
logger.log('Starting import...');
logger.info('Starting import...');
const results = await engine.transfer();
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);
} catch (e) {
logger.log(`Import process failed unexpectedly: ${e.message}`);
logger.error(`Import process failed unexpectedly: ${e.message}`);
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;
};