@strapi/strapi 4.6.0-beta.1 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -10
- package/bin/strapi.js +101 -5
- package/ee/ee-store.js +18 -0
- package/ee/index.js +173 -0
- package/ee/license.js +102 -0
- package/{lib/utils → ee}/resources/key.pub +0 -0
- package/lib/Strapi.js +15 -5
- package/lib/commands/builders/admin.js +1 -1
- package/lib/commands/transfer/export.js +37 -36
- package/lib/commands/transfer/import.js +42 -50
- package/lib/commands/transfer/transfer.js +129 -0
- package/lib/commands/transfer/utils.js +93 -6
- package/lib/commands/utils/commander.js +76 -13
- package/lib/commands/utils/helpers.js +108 -0
- package/lib/core/loaders/plugins/index.js +9 -1
- package/lib/core-api/service/index.d.ts +1 -1
- package/lib/core-api/service/single-type.js +14 -1
- package/lib/services/entity-service/components.js +39 -36
- package/lib/services/entity-service/index.js +13 -2
- package/lib/services/errors.js +5 -1
- package/lib/services/event-hub.js +70 -8
- package/lib/services/metrics/index.js +3 -54
- package/lib/services/metrics/sender.js +1 -3
- package/lib/types/core/strapi/index.d.ts +5 -0
- package/lib/types/factories.d.ts +1 -1
- package/lib/utils/cron.js +56 -0
- package/lib/utils/ee.js +1 -121
- package/lib/utils/startup-logger.js +2 -4
- package/package.json +18 -17
- package/lib/commands/utils/index.js +0 -20
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
createLocalFileDestinationProvider,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} = require('@strapi/data-transfer');
|
|
4
|
+
providers: { createLocalFileDestinationProvider },
|
|
5
|
+
} = require('@strapi/data-transfer/lib/file');
|
|
6
|
+
const {
|
|
7
|
+
providers: { createLocalStrapiSourceProvider },
|
|
8
|
+
} = require('@strapi/data-transfer/lib/strapi');
|
|
9
|
+
const { createTransferEngine } = require('@strapi/data-transfer/lib/engine');
|
|
10
10
|
const { isObject, isString, isFinite, toNumber } = require('lodash/fp');
|
|
11
11
|
const fs = require('fs-extra');
|
|
12
12
|
const chalk = require('chalk');
|
|
13
13
|
|
|
14
|
+
const { TransferEngineTransferError } = require('@strapi/data-transfer/lib/engine/errors');
|
|
14
15
|
const {
|
|
15
16
|
getDefaultExportName,
|
|
16
17
|
buildTransferTable,
|
|
17
18
|
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
18
19
|
createStrapiInstance,
|
|
20
|
+
formatDiagnostic,
|
|
19
21
|
} = require('./utils');
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
|
-
* @typedef
|
|
24
|
+
* @typedef ExportCommandOptions Options given to the CLI import command
|
|
23
25
|
*
|
|
24
26
|
* @property {string} [file] The file path to import
|
|
25
27
|
* @property {boolean} [encrypt] Used to encrypt the final archive
|
|
@@ -32,11 +34,11 @@ const logger = console;
|
|
|
32
34
|
const BYTES_IN_MB = 1024 * 1024;
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
|
-
*
|
|
37
|
+
* Export command.
|
|
36
38
|
*
|
|
37
|
-
* It transfers data from a local
|
|
39
|
+
* It transfers data from a local Strapi instance to a file
|
|
38
40
|
*
|
|
39
|
-
* @param {
|
|
41
|
+
* @param {ExportCommandOptions} opts
|
|
40
42
|
*/
|
|
41
43
|
module.exports = async (opts) => {
|
|
42
44
|
// Validate inputs from Commander
|
|
@@ -53,6 +55,8 @@ module.exports = async (opts) => {
|
|
|
53
55
|
const engine = createTransferEngine(source, destination, {
|
|
54
56
|
versionStrategy: 'ignore', // for an export to file, versionStrategy will always be skipped
|
|
55
57
|
schemaStrategy: 'ignore', // for an export to file, schemaStrategy will always be skipped
|
|
58
|
+
exclude: opts.exclude,
|
|
59
|
+
only: opts.only,
|
|
56
60
|
transforms: {
|
|
57
61
|
links: [
|
|
58
62
|
{
|
|
@@ -74,32 +78,25 @@ module.exports = async (opts) => {
|
|
|
74
78
|
},
|
|
75
79
|
});
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
logger.log(`Starting export...`);
|
|
81
|
+
engine.diagnostics.onDiagnostic(formatDiagnostic('export'));
|
|
79
82
|
|
|
80
|
-
|
|
83
|
+
const progress = engine.progress.stream;
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
};
|
|
85
|
+
const getTelemetryPayload = (/* payload */) => {
|
|
86
|
+
return {
|
|
87
|
+
eventProperties: {
|
|
88
|
+
source: engine.sourceProvider.name,
|
|
89
|
+
destination: engine.destinationProvider.name,
|
|
90
|
+
},
|
|
89
91
|
};
|
|
92
|
+
};
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
progress.on('transfer::finish', (payload) => {
|
|
96
|
-
strapi.telemetry.send('didDEITSProcessFinish', telemetryPayload(payload));
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
progress.on('transfer::error', (payload) => {
|
|
100
|
-
strapi.telemetry.send('didDEITSProcessFail', telemetryPayload(payload));
|
|
101
|
-
});
|
|
94
|
+
progress.on('transfer::start', async () => {
|
|
95
|
+
logger.log(`Starting export...`);
|
|
96
|
+
await strapi.telemetry.send('didDEITSProcessStart', getTelemetryPayload());
|
|
97
|
+
});
|
|
102
98
|
|
|
99
|
+
try {
|
|
103
100
|
const results = await engine.transfer();
|
|
104
101
|
const outFile = results.destination.file.path;
|
|
105
102
|
|
|
@@ -108,16 +105,20 @@ module.exports = async (opts) => {
|
|
|
108
105
|
|
|
109
106
|
const outFileExists = await fs.pathExists(outFile);
|
|
110
107
|
if (!outFileExists) {
|
|
111
|
-
throw new
|
|
108
|
+
throw new TransferEngineTransferError(`Export file not created "${outFile}"`);
|
|
112
109
|
}
|
|
113
110
|
|
|
114
111
|
logger.log(`${chalk.bold('Export process has been completed successfully!')}`);
|
|
115
112
|
logger.log(`Export archive is in ${chalk.green(outFile)}`);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
logger.error('Export process failed
|
|
113
|
+
} catch {
|
|
114
|
+
await strapi.telemetry.send('didDEITSProcessFail', getTelemetryPayload());
|
|
115
|
+
logger.error('Export process failed.');
|
|
119
116
|
process.exit(1);
|
|
120
117
|
}
|
|
118
|
+
|
|
119
|
+
// Note: Telemetry can't be sent in a finish event, because it runs async after this block but we can't await it, so if process.exit is used it won't send
|
|
120
|
+
await strapi.telemetry.send('didDEITSProcessFinish', getTelemetryPayload());
|
|
121
|
+
process.exit(0);
|
|
121
122
|
};
|
|
122
123
|
|
|
123
124
|
/**
|
|
@@ -134,7 +135,7 @@ const createSourceProvider = (strapi) => {
|
|
|
134
135
|
/**
|
|
135
136
|
* It creates a local file destination provider based on the given options
|
|
136
137
|
*
|
|
137
|
-
* @param {
|
|
138
|
+
* @param {ExportCommandOptions} opts
|
|
138
139
|
*/
|
|
139
140
|
const createDestinationProvider = (opts) => {
|
|
140
141
|
const { file, compress, encrypt, key, maxSizeJsonl } = opts;
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
createLocalFileSourceProvider,
|
|
5
|
-
|
|
4
|
+
providers: { createLocalFileSourceProvider },
|
|
5
|
+
} = require('@strapi/data-transfer/lib/file');
|
|
6
|
+
const {
|
|
7
|
+
providers: { createLocalStrapiDestinationProvider, DEFAULT_CONFLICT_STRATEGY },
|
|
8
|
+
} = require('@strapi/data-transfer/lib/strapi');
|
|
9
|
+
const {
|
|
6
10
|
createTransferEngine,
|
|
7
11
|
DEFAULT_VERSION_STRATEGY,
|
|
8
12
|
DEFAULT_SCHEMA_STRATEGY,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// eslint-disable-next-line import/no-unresolved, node/no-missing-require
|
|
12
|
-
} = require('@strapi/data-transfer');
|
|
13
|
+
} = require('@strapi/data-transfer/lib/engine');
|
|
14
|
+
|
|
13
15
|
const { isObject } = require('lodash/fp');
|
|
14
|
-
const path = require('path');
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
-
|
|
17
|
+
const {
|
|
18
|
+
buildTransferTable,
|
|
19
|
+
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
20
|
+
createStrapiInstance,
|
|
21
|
+
formatDiagnostic,
|
|
22
|
+
} = require('./utils');
|
|
18
23
|
|
|
19
24
|
/**
|
|
20
25
|
* @typedef {import('@strapi/data-transfer').ILocalFileSourceProviderOptions} ILocalFileSourceProviderOptions
|
|
@@ -39,12 +44,13 @@ module.exports = async (opts) => {
|
|
|
39
44
|
/**
|
|
40
45
|
* To local Strapi instance
|
|
41
46
|
*/
|
|
42
|
-
const strapiInstance = await
|
|
47
|
+
const strapiInstance = await createStrapiInstance();
|
|
43
48
|
|
|
44
49
|
const destinationOptions = {
|
|
45
50
|
async getStrapi() {
|
|
46
51
|
return strapiInstance;
|
|
47
52
|
},
|
|
53
|
+
autoDestroy: false,
|
|
48
54
|
strategy: opts.conflictStrategy || DEFAULT_CONFLICT_STRATEGY,
|
|
49
55
|
restore: {
|
|
50
56
|
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
|
@@ -59,6 +65,7 @@ module.exports = async (opts) => {
|
|
|
59
65
|
versionStrategy: opts.versionStrategy || DEFAULT_VERSION_STRATEGY,
|
|
60
66
|
schemaStrategy: opts.schemaStrategy || DEFAULT_SCHEMA_STRATEGY,
|
|
61
67
|
exclude: opts.exclude,
|
|
68
|
+
only: opts.only,
|
|
62
69
|
rules: {
|
|
63
70
|
links: [
|
|
64
71
|
{
|
|
@@ -77,44 +84,43 @@ module.exports = async (opts) => {
|
|
|
77
84
|
],
|
|
78
85
|
},
|
|
79
86
|
};
|
|
87
|
+
|
|
80
88
|
const engine = createTransferEngine(source, destination, engineOptions);
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
logger.info('Starting import...');
|
|
90
|
+
engine.diagnostics.onDiagnostic(formatDiagnostic('import'));
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
};
|
|
92
|
+
const progress = engine.progress.stream;
|
|
93
|
+
const getTelemetryPayload = () => {
|
|
94
|
+
return {
|
|
95
|
+
eventProperties: {
|
|
96
|
+
source: engine.sourceProvider.name,
|
|
97
|
+
destination: engine.destinationProvider.name,
|
|
98
|
+
},
|
|
93
99
|
};
|
|
100
|
+
};
|
|
94
101
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
progress.on('transfer::finish', (payload) => {
|
|
100
|
-
strapiInstance.telemetry.send('didDEITSProcessFinish', telemetryPayload(payload));
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
progress.on('transfer::error', (payload) => {
|
|
104
|
-
strapiInstance.telemetry.send('didDEITSProcessFail', telemetryPayload(payload));
|
|
105
|
-
});
|
|
102
|
+
progress.on('transfer::start', async () => {
|
|
103
|
+
logger.info('Starting import...');
|
|
104
|
+
await strapiInstance.telemetry.send('didDEITSProcessStart', getTelemetryPayload());
|
|
105
|
+
});
|
|
106
106
|
|
|
107
|
+
try {
|
|
107
108
|
const results = await engine.transfer();
|
|
108
109
|
const table = buildTransferTable(results.engine);
|
|
109
110
|
logger.info(table.toString());
|
|
110
111
|
|
|
111
112
|
logger.info('Import process has been completed successfully!');
|
|
112
|
-
process.exit(0);
|
|
113
113
|
} catch (e) {
|
|
114
|
-
|
|
115
|
-
logger.error(
|
|
114
|
+
await strapiInstance.telemetry.send('didDEITSProcessFail', getTelemetryPayload());
|
|
115
|
+
logger.error('Import process failed.');
|
|
116
116
|
process.exit(1);
|
|
117
117
|
}
|
|
118
|
+
|
|
119
|
+
// Note: Telemetry can't be sent in a finish event, because it runs async after this block but we can't await it, so if process.exit is used it won't send
|
|
120
|
+
await strapiInstance.telemetry.send('didDEITSProcessFinish', getTelemetryPayload());
|
|
121
|
+
await strapiInstance.destroy();
|
|
122
|
+
|
|
123
|
+
process.exit(0);
|
|
118
124
|
};
|
|
119
125
|
|
|
120
126
|
/**
|
|
@@ -130,23 +136,9 @@ const getLocalFileSourceOptions = (opts) => {
|
|
|
130
136
|
*/
|
|
131
137
|
const options = {
|
|
132
138
|
file: { path: opts.file },
|
|
133
|
-
compression: { enabled:
|
|
134
|
-
encryption: { enabled:
|
|
139
|
+
compression: { enabled: !!opts.decompress },
|
|
140
|
+
encryption: { enabled: !!opts.decrypt, key: opts.key },
|
|
135
141
|
};
|
|
136
142
|
|
|
137
|
-
const { extname, parse } = path;
|
|
138
|
-
|
|
139
|
-
let file = options.file.path;
|
|
140
|
-
|
|
141
|
-
if (extname(file) === '.enc') {
|
|
142
|
-
file = parse(file).name;
|
|
143
|
-
options.encryption = { enabled: true, key: opts.key };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (extname(file) === '.gz') {
|
|
147
|
-
file = parse(file).name;
|
|
148
|
-
options.compression = { enabled: true };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
143
|
return options;
|
|
152
144
|
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createTransferEngine } = require('@strapi/data-transfer/lib/engine');
|
|
4
|
+
const {
|
|
5
|
+
providers: {
|
|
6
|
+
createRemoteStrapiDestinationProvider,
|
|
7
|
+
createLocalStrapiSourceProvider,
|
|
8
|
+
createLocalStrapiDestinationProvider,
|
|
9
|
+
},
|
|
10
|
+
} = require('@strapi/data-transfer/lib/strapi');
|
|
11
|
+
const { isObject } = require('lodash/fp');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
buildTransferTable,
|
|
16
|
+
createStrapiInstance,
|
|
17
|
+
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
18
|
+
formatDiagnostic,
|
|
19
|
+
} = require('./utils');
|
|
20
|
+
|
|
21
|
+
const logger = console;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef TransferCommandOptions Options given to the CLI transfer command
|
|
25
|
+
*
|
|
26
|
+
* @property {URL|undefined} [to] The url of a remote Strapi to use as remote destination
|
|
27
|
+
* @property {URL|undefined} [from] The url of a remote Strapi to use as remote source
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Transfer command.
|
|
32
|
+
*
|
|
33
|
+
* It transfers data from a local file to a local strapi instance
|
|
34
|
+
*
|
|
35
|
+
* @param {TransferCommandOptions} opts
|
|
36
|
+
*/
|
|
37
|
+
module.exports = async (opts) => {
|
|
38
|
+
// Validate inputs from Commander
|
|
39
|
+
if (!isObject(opts)) {
|
|
40
|
+
logger.error('Could not parse command arguments');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const strapi = await createStrapiInstance();
|
|
45
|
+
|
|
46
|
+
let source;
|
|
47
|
+
let destination;
|
|
48
|
+
|
|
49
|
+
if (!opts.from && !opts.to) {
|
|
50
|
+
logger.error('At least one source (from) or destination (to) option must be provided');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// if no URL provided, use local Strapi
|
|
55
|
+
if (!opts.from) {
|
|
56
|
+
source = createLocalStrapiSourceProvider({
|
|
57
|
+
getStrapi: () => strapi,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// if URL provided, set up a remote source provider
|
|
61
|
+
else {
|
|
62
|
+
logger.error(`Remote Strapi source provider not yet implemented`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// if no URL provided, use local Strapi
|
|
67
|
+
if (!opts.to) {
|
|
68
|
+
destination = createLocalStrapiDestinationProvider({
|
|
69
|
+
getStrapi: () => strapi,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// if URL provided, set up a remote destination provider
|
|
73
|
+
else {
|
|
74
|
+
destination = createRemoteStrapiDestinationProvider({
|
|
75
|
+
url: opts.to,
|
|
76
|
+
auth: false,
|
|
77
|
+
strategy: 'restore',
|
|
78
|
+
restore: {
|
|
79
|
+
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!source || !destination) {
|
|
85
|
+
logger.error('Could not create providers');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const engine = createTransferEngine(source, destination, {
|
|
90
|
+
versionStrategy: 'strict',
|
|
91
|
+
schemaStrategy: 'strict',
|
|
92
|
+
transforms: {
|
|
93
|
+
links: [
|
|
94
|
+
{
|
|
95
|
+
filter(link) {
|
|
96
|
+
return (
|
|
97
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.left.type) &&
|
|
98
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.right.type)
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
entities: [
|
|
104
|
+
{
|
|
105
|
+
filter(entity) {
|
|
106
|
+
return !DEFAULT_IGNORED_CONTENT_TYPES.includes(entity.type);
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
engine.diagnostics.onDiagnostic(formatDiagnostic('transfer'));
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
logger.log(`Starting transfer...`);
|
|
117
|
+
|
|
118
|
+
const results = await engine.transfer();
|
|
119
|
+
|
|
120
|
+
const table = buildTransferTable(results.engine);
|
|
121
|
+
logger.log(table.toString());
|
|
122
|
+
|
|
123
|
+
logger.log(`${chalk.bold('Transfer process has been completed successfully!')}`);
|
|
124
|
+
process.exit(0);
|
|
125
|
+
} catch (e) {
|
|
126
|
+
logger.error('Transfer process failed.');
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const Table = require('cli-table3');
|
|
5
|
-
const {
|
|
5
|
+
const { Option } = require('commander');
|
|
6
|
+
const { TransferGroupPresets } = require('@strapi/data-transfer/lib/engine');
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
configs: { createOutputFileConfiguration },
|
|
10
|
+
createLogger,
|
|
11
|
+
} = require('@strapi/logger');
|
|
12
|
+
const { readableBytes, exitWith } = require('../utils/helpers');
|
|
6
13
|
const strapi = require('../../index');
|
|
14
|
+
const { getParseListWithChoices } = require('../utils/commander');
|
|
7
15
|
|
|
8
16
|
const pad = (n) => {
|
|
9
17
|
return (n < 10 ? '0' : '') + String(n);
|
|
@@ -74,21 +82,100 @@ const DEFAULT_IGNORED_CONTENT_TYPES = [
|
|
|
74
82
|
'admin::role',
|
|
75
83
|
'admin::api-token',
|
|
76
84
|
'admin::api-token-permission',
|
|
85
|
+
'admin::audit-log',
|
|
77
86
|
];
|
|
78
87
|
|
|
79
88
|
const createStrapiInstance = async (logLevel = 'error') => {
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
try {
|
|
90
|
+
const appContext = await strapi.compile();
|
|
91
|
+
const app = strapi(appContext);
|
|
92
|
+
|
|
93
|
+
app.log.level = logLevel;
|
|
94
|
+
return await app.load();
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (err.code === 'ECONNREFUSED') {
|
|
97
|
+
throw new Error('Process failed. Check the database connection with your Strapi project.');
|
|
98
|
+
}
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const transferDataTypes = Object.keys(TransferGroupPresets);
|
|
104
|
+
|
|
105
|
+
const excludeOption = new Option(
|
|
106
|
+
'--exclude <comma-separated data types>',
|
|
107
|
+
`Exclude data using comma-separated types. Available types: ${transferDataTypes.join(',')}`
|
|
108
|
+
).argParser(getParseListWithChoices(transferDataTypes, 'Invalid options for "exclude"'));
|
|
109
|
+
|
|
110
|
+
const onlyOption = new Option(
|
|
111
|
+
'--only <command-separated data types>',
|
|
112
|
+
`Include only these types of data (plus schemas). Available types: ${transferDataTypes.join(',')}`
|
|
113
|
+
).argParser(getParseListWithChoices(transferDataTypes, 'Invalid options for "only"'));
|
|
82
114
|
|
|
83
|
-
|
|
115
|
+
const validateExcludeOnly = (command) => {
|
|
116
|
+
const { exclude, only } = command.opts();
|
|
117
|
+
if (!only || !exclude) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
84
120
|
|
|
85
|
-
|
|
121
|
+
const choicesInBoth = only.filter((n) => {
|
|
122
|
+
return exclude.indexOf(n) !== -1;
|
|
123
|
+
});
|
|
124
|
+
if (choicesInBoth.length > 0) {
|
|
125
|
+
exitWith(
|
|
126
|
+
1,
|
|
127
|
+
`Data types may not be used in both "exclude" and "only" in the same command. Found in both: ${choicesInBoth.join(
|
|
128
|
+
','
|
|
129
|
+
)}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const errorColors = {
|
|
135
|
+
fatal: chalk.red,
|
|
136
|
+
error: chalk.red,
|
|
137
|
+
silly: chalk.yellow,
|
|
86
138
|
};
|
|
87
139
|
|
|
140
|
+
const formatDiagnostic =
|
|
141
|
+
(operation) =>
|
|
142
|
+
({ details, kind }) => {
|
|
143
|
+
const logger = createLogger(
|
|
144
|
+
createOutputFileConfiguration(`${operation}_error_log_${Date.now()}.log`)
|
|
145
|
+
);
|
|
146
|
+
try {
|
|
147
|
+
if (kind === 'error') {
|
|
148
|
+
const { message, severity = 'fatal' } = details;
|
|
149
|
+
|
|
150
|
+
const colorizeError = errorColors[severity];
|
|
151
|
+
const errorMessage = colorizeError(`[${severity.toUpperCase()}] ${message}`);
|
|
152
|
+
|
|
153
|
+
logger.error(errorMessage);
|
|
154
|
+
}
|
|
155
|
+
if (kind === 'info') {
|
|
156
|
+
const { message, params } = details;
|
|
157
|
+
|
|
158
|
+
const msg = `${message}\n${params ? JSON.stringify(params, null, 2) : ''}`;
|
|
159
|
+
|
|
160
|
+
logger.info(msg);
|
|
161
|
+
}
|
|
162
|
+
if (kind === 'warning') {
|
|
163
|
+
const { origin, message } = details;
|
|
164
|
+
|
|
165
|
+
logger.warn(`(${origin ?? 'transfer'}) ${message}`);
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
logger.error(err);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
88
172
|
module.exports = {
|
|
89
173
|
buildTransferTable,
|
|
90
174
|
getDefaultExportName,
|
|
91
|
-
yyyymmddHHMMSS,
|
|
92
175
|
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
93
176
|
createStrapiInstance,
|
|
177
|
+
excludeOption,
|
|
178
|
+
onlyOption,
|
|
179
|
+
validateExcludeOnly,
|
|
180
|
+
formatDiagnostic,
|
|
94
181
|
};
|
|
@@ -1,12 +1,59 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* This file includes hooks to use for commander.hook and argParsers for commander.argParser
|
|
5
|
+
*/
|
|
6
|
+
|
|
3
7
|
const inquirer = require('inquirer');
|
|
8
|
+
const { InvalidOptionArgumentError, Option } = require('commander');
|
|
9
|
+
const { bold, green, cyan } = require('chalk');
|
|
10
|
+
const { exitWith } = require('./helpers');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* argParser: Parse a comma-delimited string as an array
|
|
14
|
+
*/
|
|
15
|
+
const parseList = (value) => {
|
|
16
|
+
let list;
|
|
17
|
+
try {
|
|
18
|
+
list = value.split(',').map((item) => item.trim()); // trim shouldn't be necessary but might help catch unexpected whitespace characters
|
|
19
|
+
} catch (e) {
|
|
20
|
+
exitWith(1, `Unrecognized input: ${value}`);
|
|
21
|
+
}
|
|
22
|
+
return list;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns an argParser that returns a list
|
|
27
|
+
*/
|
|
28
|
+
const getParseListWithChoices = (choices, errorMessage = 'Invalid options:') => {
|
|
29
|
+
return (value) => {
|
|
30
|
+
const list = parseList(value);
|
|
31
|
+
const invalid = list.filter((item) => {
|
|
32
|
+
return !choices.includes(item);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (invalid.length > 0) {
|
|
36
|
+
exitWith(1, `${errorMessage}: ${invalid.join(',')}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return list;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
4
42
|
|
|
5
43
|
/**
|
|
6
|
-
*
|
|
44
|
+
* argParser: Parse a string as a URL object
|
|
7
45
|
*/
|
|
8
|
-
const
|
|
9
|
-
|
|
46
|
+
const parseURL = (value) => {
|
|
47
|
+
try {
|
|
48
|
+
const url = new URL(value);
|
|
49
|
+
if (!url.host) {
|
|
50
|
+
throw new InvalidOptionArgumentError(`Could not parse url ${value}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return url;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
throw new InvalidOptionArgumentError(`Could not parse url ${value}`);
|
|
56
|
+
}
|
|
10
57
|
};
|
|
11
58
|
|
|
12
59
|
/**
|
|
@@ -16,8 +63,7 @@ const promptEncryptionKey = async (thisCommand) => {
|
|
|
16
63
|
const opts = thisCommand.opts();
|
|
17
64
|
|
|
18
65
|
if (!opts.encrypt && opts.key) {
|
|
19
|
-
|
|
20
|
-
process.exit(1);
|
|
66
|
+
return exitWith(1, 'Key may not be present unless encryption is used');
|
|
21
67
|
}
|
|
22
68
|
|
|
23
69
|
// if encrypt==true but we have no key, prompt for it
|
|
@@ -37,21 +83,30 @@ const promptEncryptionKey = async (thisCommand) => {
|
|
|
37
83
|
]);
|
|
38
84
|
opts.key = answers.key;
|
|
39
85
|
} catch (e) {
|
|
40
|
-
|
|
41
|
-
process.exit(1);
|
|
86
|
+
return exitWith(1, 'Failed to get encryption key');
|
|
42
87
|
}
|
|
43
88
|
if (!opts.key) {
|
|
44
|
-
|
|
45
|
-
process.exit(1);
|
|
89
|
+
return exitWith(1, 'Failed to get encryption key');
|
|
46
90
|
}
|
|
47
91
|
}
|
|
48
92
|
};
|
|
49
93
|
|
|
50
94
|
/**
|
|
51
|
-
* hook: require a confirmation message to be accepted
|
|
95
|
+
* hook: require a confirmation message to be accepted unless forceOption (-f,--force) is used
|
|
96
|
+
*
|
|
97
|
+
* @param {string} message The message to confirm with user
|
|
98
|
+
* @param {object} options Additional options
|
|
52
99
|
*/
|
|
53
100
|
const confirmMessage = (message) => {
|
|
54
|
-
return async () => {
|
|
101
|
+
return async (command) => {
|
|
102
|
+
// if we have a force option, assume yes
|
|
103
|
+
const opts = command.opts();
|
|
104
|
+
if (opts?.force === true) {
|
|
105
|
+
// attempt to mimic the inquirer prompt exactly
|
|
106
|
+
console.log(`${green('?')} ${bold(message)} ${cyan('Yes')}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
55
110
|
const answers = await inquirer.prompt([
|
|
56
111
|
{
|
|
57
112
|
type: 'confirm',
|
|
@@ -61,13 +116,21 @@ const confirmMessage = (message) => {
|
|
|
61
116
|
},
|
|
62
117
|
]);
|
|
63
118
|
if (!answers.confirm) {
|
|
64
|
-
|
|
119
|
+
exitWith(0);
|
|
65
120
|
}
|
|
66
121
|
};
|
|
67
122
|
};
|
|
68
123
|
|
|
124
|
+
const forceOption = new Option(
|
|
125
|
+
'--force',
|
|
126
|
+
`Automatically answer "yes" to all prompts, including potentially destructive requests, and run non-interactively.`
|
|
127
|
+
);
|
|
128
|
+
|
|
69
129
|
module.exports = {
|
|
70
|
-
|
|
130
|
+
getParseListWithChoices,
|
|
131
|
+
parseList,
|
|
132
|
+
parseURL,
|
|
71
133
|
promptEncryptionKey,
|
|
72
134
|
confirmMessage,
|
|
135
|
+
forceOption,
|
|
73
136
|
};
|