@strapi/strapi 4.6.0-beta.0 → 4.6.0-beta.2
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 +8 -4
- package/bin/strapi.js +139 -1
- package/lib/Strapi.js +8 -5
- package/lib/commands/opt-in-telemetry.js +2 -2
- package/lib/commands/opt-out-telemetry.js +2 -2
- package/lib/commands/transfer/export.js +158 -0
- package/lib/commands/transfer/import.js +154 -0
- package/lib/commands/transfer/transfer.js +127 -0
- package/lib/commands/transfer/utils.js +132 -0
- package/lib/commands/utils/commander.js +136 -0
- package/lib/commands/utils/helpers.js +108 -0
- 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 +10 -4
- package/lib/services/entity-service/index.js +12 -2
- package/lib/services/errors.js +5 -1
- package/lib/services/event-hub.js +70 -8
- package/lib/services/metrics/admin-user-hash.js +21 -0
- package/lib/services/metrics/index.js +6 -4
- package/lib/services/metrics/middleware.js +1 -1
- package/lib/services/metrics/sender.js +17 -9
- package/lib/types/core/attributes/relation.d.ts +9 -12
- package/lib/types/core/schemas/index.d.ts +6 -1
- package/lib/types/core/strapi/index.d.ts +15 -4
- package/lib/types/factories.d.ts +3 -3
- package/lib/utils/ee.js +1 -1
- package/lib/utils/success.js +1 -1
- package/package.json +16 -15
package/README.md
CHANGED
|
@@ -80,14 +80,18 @@ Complete installation requirements can be found in the documentation under <a hr
|
|
|
80
80
|
- CentOS/RHEL 8
|
|
81
81
|
- macOS Mojave
|
|
82
82
|
- Windows 10
|
|
83
|
-
- Docker
|
|
83
|
+
- Docker
|
|
84
84
|
|
|
85
85
|
(Please note that Strapi may work on other operating systems, but these are not tested nor officially supported at this time.)
|
|
86
86
|
|
|
87
87
|
**Node:**
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
Strapi only supports maintenance and LTS versions of Node.js. Please refer to the <a href="https://nodejs.org/en/about/releases/">Node.js release schedule</a> for more information. NPM versions installed by default with Node.js are supported. Generally it's recommended to use yarn over npm where possible.
|
|
90
|
+
|
|
91
|
+
| Strapi Version | Recommended | Minimum |
|
|
92
|
+
| -------------- | ----------- | ------- |
|
|
93
|
+
| 4.3.9 and up | 18.x | 14.x |
|
|
94
|
+
| 4.0.x to 4.3.8 | 16.x | 14.x |
|
|
91
95
|
|
|
92
96
|
**Database:**
|
|
93
97
|
|
|
@@ -98,7 +102,7 @@ Complete installation requirements can be found in the documentation under <a hr
|
|
|
98
102
|
| PostgreSQL | 11.0 | 14.0 |
|
|
99
103
|
| SQLite | 3 | 3 |
|
|
100
104
|
|
|
101
|
-
**We recommend always using the latest version of Strapi to start your new projects**.
|
|
105
|
+
**We recommend always using the latest version of Strapi stable to start your new projects**.
|
|
102
106
|
|
|
103
107
|
## Features
|
|
104
108
|
|
package/bin/strapi.js
CHANGED
|
@@ -5,13 +5,27 @@
|
|
|
5
5
|
// FIXME
|
|
6
6
|
/* eslint-disable import/extensions */
|
|
7
7
|
const _ = require('lodash');
|
|
8
|
+
const path = require('path');
|
|
8
9
|
const resolveCwd = require('resolve-cwd');
|
|
9
10
|
const { yellow } = require('chalk');
|
|
10
|
-
const { Command } = require('commander');
|
|
11
|
+
const { Command, Option } = require('commander');
|
|
12
|
+
const inquirer = require('inquirer');
|
|
11
13
|
|
|
12
14
|
const program = new Command();
|
|
13
15
|
|
|
14
16
|
const packageJSON = require('../package.json');
|
|
17
|
+
const {
|
|
18
|
+
promptEncryptionKey,
|
|
19
|
+
confirmMessage,
|
|
20
|
+
parseURL,
|
|
21
|
+
forceOption,
|
|
22
|
+
} = require('../lib/commands/utils/commander');
|
|
23
|
+
const { ifOptions, assertUrlHasProtocol, exitWith } = require('../lib/commands/utils/helpers');
|
|
24
|
+
const {
|
|
25
|
+
excludeOption,
|
|
26
|
+
onlyOption,
|
|
27
|
+
validateExcludeOnly,
|
|
28
|
+
} = require('../lib/commands/transfer/utils');
|
|
15
29
|
|
|
16
30
|
const checkCwdIsStrapiApp = (name) => {
|
|
17
31
|
const logErrorAndExit = () => {
|
|
@@ -255,4 +269,128 @@ program
|
|
|
255
269
|
.option('-s, --silent', `Run the generation silently, without any output`, false)
|
|
256
270
|
.action(getLocalScript('ts/generate-types'));
|
|
257
271
|
|
|
272
|
+
if (process.env.STRAPI_EXPERIMENTAL === 'true') {
|
|
273
|
+
// `$ strapi transfer`
|
|
274
|
+
program
|
|
275
|
+
.command('transfer')
|
|
276
|
+
.description('Transfer data from one source to another')
|
|
277
|
+
.allowExcessArguments(false)
|
|
278
|
+
.addOption(
|
|
279
|
+
new Option(
|
|
280
|
+
'--from <sourceURL>',
|
|
281
|
+
`URL of the remote Strapi instance to get data from`
|
|
282
|
+
).argParser(parseURL)
|
|
283
|
+
)
|
|
284
|
+
.addOption(
|
|
285
|
+
new Option(
|
|
286
|
+
'--to <destinationURL>',
|
|
287
|
+
`URL of the remote Strapi instance to send data to`
|
|
288
|
+
).argParser(parseURL)
|
|
289
|
+
)
|
|
290
|
+
.addOption(forceOption)
|
|
291
|
+
// Validate URLs
|
|
292
|
+
.hook(
|
|
293
|
+
'preAction',
|
|
294
|
+
ifOptions(
|
|
295
|
+
(opts) => opts.from,
|
|
296
|
+
(thisCommand) => assertUrlHasProtocol(thisCommand.opts().from, ['https:', 'http:'])
|
|
297
|
+
)
|
|
298
|
+
)
|
|
299
|
+
.hook(
|
|
300
|
+
'preAction',
|
|
301
|
+
ifOptions(
|
|
302
|
+
(opts) => opts.to,
|
|
303
|
+
(thisCommand) => assertUrlHasProtocol(thisCommand.opts().to, ['https:', 'http:'])
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
.hook(
|
|
307
|
+
'preAction',
|
|
308
|
+
ifOptions(
|
|
309
|
+
(opts) => !opts.from && !opts.to,
|
|
310
|
+
() => exitWith(1, 'At least one source (from) or destination (to) option must be provided')
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
.addOption(forceOption)
|
|
314
|
+
.addOption(excludeOption)
|
|
315
|
+
.addOption(onlyOption)
|
|
316
|
+
.hook('preAction', validateExcludeOnly)
|
|
317
|
+
.hook(
|
|
318
|
+
'preAction',
|
|
319
|
+
confirmMessage(
|
|
320
|
+
'The import will delete all data in the remote database. Are you sure you want to proceed?'
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
.action(getLocalScript('transfer/transfer'));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// `$ strapi export`
|
|
327
|
+
program
|
|
328
|
+
.command('export')
|
|
329
|
+
.description('Export data from Strapi to file')
|
|
330
|
+
.allowExcessArguments(false)
|
|
331
|
+
.addOption(
|
|
332
|
+
new Option('--no-encrypt', `Disables 'aes-128-ecb' encryption of the output file`).default(true)
|
|
333
|
+
)
|
|
334
|
+
.addOption(new Option('--no-compress', 'Disables gzip compression of output file').default(true))
|
|
335
|
+
.addOption(
|
|
336
|
+
new Option(
|
|
337
|
+
'-k, --key <string>',
|
|
338
|
+
'Provide encryption key in command instead of using the prompt'
|
|
339
|
+
)
|
|
340
|
+
)
|
|
341
|
+
.addOption(new Option('-f, --file <file>', 'name to use for exported file (without extensions)'))
|
|
342
|
+
.addOption(excludeOption)
|
|
343
|
+
.addOption(onlyOption)
|
|
344
|
+
.hook('preAction', validateExcludeOnly)
|
|
345
|
+
.hook('preAction', promptEncryptionKey)
|
|
346
|
+
.action(getLocalScript('transfer/export'));
|
|
347
|
+
|
|
348
|
+
// `$ strapi import`
|
|
349
|
+
program
|
|
350
|
+
.command('import')
|
|
351
|
+
.description('Import data from file to Strapi')
|
|
352
|
+
.allowExcessArguments(false)
|
|
353
|
+
.requiredOption(
|
|
354
|
+
'-f, --file <file>',
|
|
355
|
+
'path and filename for the Strapi export file you want to import'
|
|
356
|
+
)
|
|
357
|
+
.addOption(
|
|
358
|
+
new Option(
|
|
359
|
+
'-k, --key <string>',
|
|
360
|
+
'Provide encryption key in command instead of using the prompt'
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
.addOption(forceOption)
|
|
364
|
+
.addOption(excludeOption)
|
|
365
|
+
.addOption(onlyOption)
|
|
366
|
+
.hook('preAction', validateExcludeOnly)
|
|
367
|
+
.hook('preAction', async (thisCommand) => {
|
|
368
|
+
const opts = thisCommand.opts();
|
|
369
|
+
const ext = path.extname(String(opts.file));
|
|
370
|
+
|
|
371
|
+
// check extension to guess if we should prompt for key
|
|
372
|
+
if (ext === '.enc') {
|
|
373
|
+
if (!opts.key) {
|
|
374
|
+
const answers = await inquirer.prompt([
|
|
375
|
+
{
|
|
376
|
+
type: 'password',
|
|
377
|
+
message: 'Please enter your decryption key',
|
|
378
|
+
name: 'key',
|
|
379
|
+
},
|
|
380
|
+
]);
|
|
381
|
+
if (!answers.key?.length) {
|
|
382
|
+
exitWith(0, 'No key entered, aborting import.');
|
|
383
|
+
}
|
|
384
|
+
opts.key = answers.key;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
.hook(
|
|
389
|
+
'preAction',
|
|
390
|
+
confirmMessage(
|
|
391
|
+
'The import will delete all data in your database. Are you sure you want to proceed?'
|
|
392
|
+
)
|
|
393
|
+
)
|
|
394
|
+
.action(getLocalScript('transfer/import'));
|
|
395
|
+
|
|
258
396
|
program.parseAsync(process.argv);
|
package/lib/Strapi.js
CHANGED
|
@@ -225,7 +225,7 @@ class Strapi {
|
|
|
225
225
|
|
|
226
226
|
await this.runLifecyclesFunctions(LIFECYCLES.DESTROY);
|
|
227
227
|
|
|
228
|
-
this.eventHub.
|
|
228
|
+
this.eventHub.destroy();
|
|
229
229
|
|
|
230
230
|
if (_.has(this, 'db')) {
|
|
231
231
|
await this.db.destroy();
|
|
@@ -242,11 +242,14 @@ class Strapi {
|
|
|
242
242
|
sendStartupTelemetry() {
|
|
243
243
|
// Emit started event.
|
|
244
244
|
// do not await to avoid slower startup
|
|
245
|
+
// This event is anonymous
|
|
245
246
|
this.telemetry.send('didStartServer', {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
groupProperties: {
|
|
248
|
+
database: strapi.config.get('database.connection.client'),
|
|
249
|
+
plugins: Object.keys(strapi.plugins),
|
|
250
|
+
// TODO: to add back
|
|
251
|
+
// providers: this.config.installedProviders,
|
|
252
|
+
},
|
|
250
253
|
});
|
|
251
254
|
}
|
|
252
255
|
|
|
@@ -53,12 +53,12 @@ const generateNewPackageJSON = (packageObj) => {
|
|
|
53
53
|
|
|
54
54
|
const sendEvent = async (uuid) => {
|
|
55
55
|
try {
|
|
56
|
-
await fetch('https://analytics.strapi.io/track', {
|
|
56
|
+
await fetch('https://analytics.strapi.io/api/v2/track', {
|
|
57
57
|
method: 'POST',
|
|
58
58
|
body: JSON.stringify({
|
|
59
59
|
event: 'didOptInTelemetry',
|
|
60
|
-
uuid,
|
|
61
60
|
deviceId: machineID(),
|
|
61
|
+
groupProperties: { projectId: uuid },
|
|
62
62
|
}),
|
|
63
63
|
headers: { 'Content-Type': 'application/json' },
|
|
64
64
|
});
|
|
@@ -28,12 +28,12 @@ const writePackageJSON = async (path, file, spacing) => {
|
|
|
28
28
|
|
|
29
29
|
const sendEvent = async (uuid) => {
|
|
30
30
|
try {
|
|
31
|
-
await fetch('https://analytics.strapi.io/track', {
|
|
31
|
+
await fetch('https://analytics.strapi.io/api/v2/track', {
|
|
32
32
|
method: 'POST',
|
|
33
33
|
body: JSON.stringify({
|
|
34
34
|
event: 'didOptOutTelemetry',
|
|
35
|
-
uuid,
|
|
36
35
|
deviceId: machineID(),
|
|
36
|
+
groupProperties: { projectId: uuid },
|
|
37
37
|
}),
|
|
38
38
|
headers: { 'Content-Type': 'application/json' },
|
|
39
39
|
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
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
|
+
const { isObject, isString, isFinite, toNumber } = require('lodash/fp');
|
|
11
|
+
const fs = require('fs-extra');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
getDefaultExportName,
|
|
16
|
+
buildTransferTable,
|
|
17
|
+
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
18
|
+
createStrapiInstance,
|
|
19
|
+
} = require('./utils');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef ImportCommandOptions Options given to the CLI import command
|
|
23
|
+
*
|
|
24
|
+
* @property {string} [file] The file path to import
|
|
25
|
+
* @property {boolean} [encrypt] Used to encrypt the final archive
|
|
26
|
+
* @property {string} [key] Encryption key, only useful when encryption is enabled
|
|
27
|
+
* @property {boolean} [compress] Used to compress the final archive
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const logger = console;
|
|
31
|
+
|
|
32
|
+
const BYTES_IN_MB = 1024 * 1024;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Import command.
|
|
36
|
+
*
|
|
37
|
+
* It transfers data from a local file to a local strapi instance
|
|
38
|
+
*
|
|
39
|
+
* @param {ImportCommandOptions} opts
|
|
40
|
+
*/
|
|
41
|
+
module.exports = async (opts) => {
|
|
42
|
+
// Validate inputs from Commander
|
|
43
|
+
if (!isObject(opts)) {
|
|
44
|
+
logger.error('Could not parse command arguments');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const strapi = await createStrapiInstance();
|
|
49
|
+
|
|
50
|
+
const source = createSourceProvider(strapi);
|
|
51
|
+
const destination = createDestinationProvider(opts);
|
|
52
|
+
|
|
53
|
+
const engine = createTransferEngine(source, destination, {
|
|
54
|
+
versionStrategy: 'ignore', // for an export to file, versionStrategy will always be skipped
|
|
55
|
+
schemaStrategy: 'ignore', // for an export to file, schemaStrategy will always be skipped
|
|
56
|
+
exclude: opts.exclude,
|
|
57
|
+
only: opts.only,
|
|
58
|
+
transforms: {
|
|
59
|
+
links: [
|
|
60
|
+
{
|
|
61
|
+
filter(link) {
|
|
62
|
+
return (
|
|
63
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.left.type) &&
|
|
64
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.right.type)
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
entities: [
|
|
70
|
+
{
|
|
71
|
+
filter(entity) {
|
|
72
|
+
return !DEFAULT_IGNORED_CONTENT_TYPES.includes(entity.type);
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const progress = engine.progress.stream;
|
|
80
|
+
|
|
81
|
+
const getTelemetryPayload = (/* payload */) => {
|
|
82
|
+
return {
|
|
83
|
+
eventProperties: {
|
|
84
|
+
source: engine.sourceProvider.name,
|
|
85
|
+
destination: engine.destinationProvider.name,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
progress.on('transfer::start', async () => {
|
|
91
|
+
logger.log(`Starting export...`);
|
|
92
|
+
await strapi.telemetry.send('didDEITSProcessStart', getTelemetryPayload());
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const results = await engine.transfer();
|
|
97
|
+
const outFile = results.destination.file.path;
|
|
98
|
+
|
|
99
|
+
const table = buildTransferTable(results.engine);
|
|
100
|
+
logger.log(table.toString());
|
|
101
|
+
|
|
102
|
+
const outFileExists = await fs.pathExists(outFile);
|
|
103
|
+
if (!outFileExists) {
|
|
104
|
+
throw new Error(`Export file not created "${outFile}"`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
logger.log(`${chalk.bold('Export process has been completed successfully!')}`);
|
|
108
|
+
logger.log(`Export archive is in ${chalk.green(outFile)}`);
|
|
109
|
+
} catch (e) {
|
|
110
|
+
await strapi.telemetry.send('didDEITSProcessFail', getTelemetryPayload());
|
|
111
|
+
logger.error('Export process failed unexpectedly:', e.toString());
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 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
|
|
116
|
+
await strapi.telemetry.send('didDEITSProcessFinish', getTelemetryPayload());
|
|
117
|
+
process.exit(0);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* It creates a local strapi destination provider
|
|
122
|
+
*/
|
|
123
|
+
const createSourceProvider = (strapi) => {
|
|
124
|
+
return createLocalStrapiSourceProvider({
|
|
125
|
+
async getStrapi() {
|
|
126
|
+
return strapi;
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* It creates a local file destination provider based on the given options
|
|
133
|
+
*
|
|
134
|
+
* @param {ImportCommandOptions} opts
|
|
135
|
+
*/
|
|
136
|
+
const createDestinationProvider = (opts) => {
|
|
137
|
+
const { file, compress, encrypt, key, maxSizeJsonl } = opts;
|
|
138
|
+
|
|
139
|
+
const filepath = isString(file) && file.length > 0 ? file : getDefaultExportName();
|
|
140
|
+
|
|
141
|
+
const maxSizeJsonlInMb = isFinite(toNumber(maxSizeJsonl))
|
|
142
|
+
? toNumber(maxSizeJsonl) * BYTES_IN_MB
|
|
143
|
+
: undefined;
|
|
144
|
+
|
|
145
|
+
return createLocalFileDestinationProvider({
|
|
146
|
+
file: {
|
|
147
|
+
path: filepath,
|
|
148
|
+
maxSizeJsonl: maxSizeJsonlInMb,
|
|
149
|
+
},
|
|
150
|
+
encryption: {
|
|
151
|
+
enabled: encrypt,
|
|
152
|
+
key: encrypt ? key : undefined,
|
|
153
|
+
},
|
|
154
|
+
compression: {
|
|
155
|
+
enabled: compress,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
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 {
|
|
10
|
+
createTransferEngine,
|
|
11
|
+
DEFAULT_VERSION_STRATEGY,
|
|
12
|
+
DEFAULT_SCHEMA_STRATEGY,
|
|
13
|
+
} = require('@strapi/data-transfer/lib/engine');
|
|
14
|
+
|
|
15
|
+
const { isObject } = require('lodash/fp');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const strapi = require('../../index');
|
|
19
|
+
const { buildTransferTable, DEFAULT_IGNORED_CONTENT_TYPES } = require('./utils');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {import('@strapi/data-transfer').ILocalFileSourceProviderOptions} ILocalFileSourceProviderOptions
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const logger = console;
|
|
26
|
+
|
|
27
|
+
module.exports = async (opts) => {
|
|
28
|
+
// validate inputs from Commander
|
|
29
|
+
if (!isObject(opts)) {
|
|
30
|
+
logger.error('Could not parse arguments');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* From strapi backup file
|
|
36
|
+
*/
|
|
37
|
+
const sourceOptions = getLocalFileSourceOptions(opts);
|
|
38
|
+
|
|
39
|
+
const source = createLocalFileSourceProvider(sourceOptions);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* To local Strapi instance
|
|
43
|
+
*/
|
|
44
|
+
const strapiInstance = await strapi(await strapi.compile()).load();
|
|
45
|
+
|
|
46
|
+
const destinationOptions = {
|
|
47
|
+
async getStrapi() {
|
|
48
|
+
return strapiInstance;
|
|
49
|
+
},
|
|
50
|
+
autoDestroy: false,
|
|
51
|
+
strategy: opts.conflictStrategy || DEFAULT_CONFLICT_STRATEGY,
|
|
52
|
+
restore: {
|
|
53
|
+
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const destination = createLocalStrapiDestinationProvider(destinationOptions);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Configure and run the transfer engine
|
|
60
|
+
*/
|
|
61
|
+
const engineOptions = {
|
|
62
|
+
versionStrategy: opts.versionStrategy || DEFAULT_VERSION_STRATEGY,
|
|
63
|
+
schemaStrategy: opts.schemaStrategy || DEFAULT_SCHEMA_STRATEGY,
|
|
64
|
+
exclude: opts.exclude,
|
|
65
|
+
only: opts.only,
|
|
66
|
+
rules: {
|
|
67
|
+
links: [
|
|
68
|
+
{
|
|
69
|
+
filter(link) {
|
|
70
|
+
return (
|
|
71
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.left.type) &&
|
|
72
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.right.type)
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
entities: [
|
|
78
|
+
{
|
|
79
|
+
filter: (entity) => !DEFAULT_IGNORED_CONTENT_TYPES.includes(entity.type),
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const engine = createTransferEngine(source, destination, engineOptions);
|
|
86
|
+
|
|
87
|
+
const progress = engine.progress.stream;
|
|
88
|
+
const getTelemetryPayload = () => {
|
|
89
|
+
return {
|
|
90
|
+
eventProperties: {
|
|
91
|
+
source: engine.sourceProvider.name,
|
|
92
|
+
destination: engine.destinationProvider.name,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
progress.on('transfer::start', async () => {
|
|
98
|
+
logger.info('Starting import...');
|
|
99
|
+
await strapiInstance.telemetry.send('didDEITSProcessStart', getTelemetryPayload());
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const results = await engine.transfer();
|
|
104
|
+
const table = buildTransferTable(results.engine);
|
|
105
|
+
logger.info(table.toString());
|
|
106
|
+
|
|
107
|
+
logger.info('Import process has been completed successfully!');
|
|
108
|
+
} catch (e) {
|
|
109
|
+
await strapiInstance.telemetry.send('didDEITSProcessFail', getTelemetryPayload());
|
|
110
|
+
logger.error('Import process failed unexpectedly:');
|
|
111
|
+
logger.error(e);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 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
|
|
116
|
+
await strapiInstance.telemetry.send('didDEITSProcessFinish', getTelemetryPayload());
|
|
117
|
+
await strapiInstance.destroy();
|
|
118
|
+
|
|
119
|
+
process.exit(0);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Infer local file source provider options based on a given filename
|
|
124
|
+
*
|
|
125
|
+
* @param {{ file: string; key?: string }} opts
|
|
126
|
+
*
|
|
127
|
+
* @return {ILocalFileSourceProviderOptions}
|
|
128
|
+
*/
|
|
129
|
+
const getLocalFileSourceOptions = (opts) => {
|
|
130
|
+
/**
|
|
131
|
+
* @type {ILocalFileSourceProviderOptions}
|
|
132
|
+
*/
|
|
133
|
+
const options = {
|
|
134
|
+
file: { path: opts.file },
|
|
135
|
+
compression: { enabled: false },
|
|
136
|
+
encryption: { enabled: false },
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const { extname, parse } = path;
|
|
140
|
+
|
|
141
|
+
let file = options.file.path;
|
|
142
|
+
|
|
143
|
+
if (extname(file) === '.enc') {
|
|
144
|
+
file = parse(file).name;
|
|
145
|
+
options.encryption = { enabled: true, key: opts.key };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (extname(file) === '.gz') {
|
|
149
|
+
file = parse(file).name;
|
|
150
|
+
options.compression = { enabled: true };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return options;
|
|
154
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
} = require('./utils');
|
|
19
|
+
|
|
20
|
+
const logger = console;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef TransferCommandOptions Options given to the CLI transfer command
|
|
24
|
+
*
|
|
25
|
+
* @property {URL|undefined} [to] The url of a remote Strapi to use as remote destination
|
|
26
|
+
* @property {URL|undefined} [from] The url of a remote Strapi to use as remote source
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Transfer command.
|
|
31
|
+
*
|
|
32
|
+
* It transfers data from a local file to a local strapi instance
|
|
33
|
+
*
|
|
34
|
+
* @param {TransferCommandOptions} opts
|
|
35
|
+
*/
|
|
36
|
+
module.exports = async (opts) => {
|
|
37
|
+
// Validate inputs from Commander
|
|
38
|
+
if (!isObject(opts)) {
|
|
39
|
+
logger.error('Could not parse command arguments');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const strapi = await createStrapiInstance();
|
|
44
|
+
|
|
45
|
+
let source;
|
|
46
|
+
let destination;
|
|
47
|
+
|
|
48
|
+
if (!opts.from && !opts.to) {
|
|
49
|
+
logger.error('At least one source (from) or destination (to) option must be provided');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// if no URL provided, use local Strapi
|
|
54
|
+
if (!opts.from) {
|
|
55
|
+
source = createLocalStrapiSourceProvider({
|
|
56
|
+
getStrapi: () => strapi,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// if URL provided, set up a remote source provider
|
|
60
|
+
else {
|
|
61
|
+
logger.error(`Remote Strapi source provider not yet implemented`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// if no URL provided, use local Strapi
|
|
66
|
+
if (!opts.to) {
|
|
67
|
+
destination = createLocalStrapiDestinationProvider({
|
|
68
|
+
getStrapi: () => strapi,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// if URL provided, set up a remote destination provider
|
|
72
|
+
else {
|
|
73
|
+
destination = createRemoteStrapiDestinationProvider({
|
|
74
|
+
url: opts.to,
|
|
75
|
+
auth: false,
|
|
76
|
+
strategy: 'restore',
|
|
77
|
+
restore: {
|
|
78
|
+
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!source || !destination) {
|
|
84
|
+
logger.error('Could not create providers');
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const engine = createTransferEngine(source, destination, {
|
|
89
|
+
versionStrategy: 'strict',
|
|
90
|
+
schemaStrategy: 'strict',
|
|
91
|
+
transforms: {
|
|
92
|
+
links: [
|
|
93
|
+
{
|
|
94
|
+
filter(link) {
|
|
95
|
+
return (
|
|
96
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.left.type) &&
|
|
97
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.right.type)
|
|
98
|
+
);
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
entities: [
|
|
103
|
+
{
|
|
104
|
+
filter(entity) {
|
|
105
|
+
return !DEFAULT_IGNORED_CONTENT_TYPES.includes(entity.type);
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
logger.log(`Starting transfer...`);
|
|
114
|
+
|
|
115
|
+
const results = await engine.transfer();
|
|
116
|
+
|
|
117
|
+
const table = buildTransferTable(results.engine);
|
|
118
|
+
logger.log(table.toString());
|
|
119
|
+
|
|
120
|
+
logger.log(`${chalk.bold('Transfer process has been completed successfully!')}`);
|
|
121
|
+
process.exit(0);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
logger.error('Transfer process failed unexpectedly');
|
|
124
|
+
logger.error(e);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
};
|