@strapi/strapi 4.9.0-alpha.0 → 4.9.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/bin/strapi.js CHANGED
@@ -18,18 +18,16 @@ const {
18
18
  promptEncryptionKey,
19
19
  confirmMessage,
20
20
  forceOption,
21
+ parseURL,
21
22
  } = require('../lib/commands/utils/commander');
22
- const { exitWith } = require('../lib/commands/utils/helpers');
23
+ const { exitWith, ifOptions, assertUrlHasProtocol } = require('../lib/commands/utils/helpers');
23
24
  const {
24
25
  excludeOption,
25
26
  onlyOption,
27
+ throttleOption,
26
28
  validateExcludeOnly,
27
29
  } = require('../lib/commands/transfer/utils');
28
30
 
29
- process.on('SIGINT', () => {
30
- process.exit();
31
- });
32
-
33
31
  const checkCwdIsStrapiApp = (name) => {
34
32
  const logErrorAndExit = () => {
35
33
  console.log(
@@ -272,59 +270,90 @@ program
272
270
  .option('-s, --silent', `Run the generation silently, without any output`, false)
273
271
  .action(getLocalScript('ts/generate-types'));
274
272
 
275
- // if (process.env.STRAPI_EXPERIMENTAL === 'true') {
276
- // // `$ strapi transfer`
277
- // program
278
- // .command('transfer')
279
- // .description('Transfer data from one source to another')
280
- // .allowExcessArguments(false)
281
- // .addOption(
282
- // new Option(
283
- // '--from <sourceURL>',
284
- // `URL of the remote Strapi instance to get data from`
285
- // ).argParser(parseURL)
286
- // )
287
- // .addOption(
288
- // new Option(
289
- // '--to <destinationURL>',
290
- // `URL of the remote Strapi instance to send data to`
291
- // ).argParser(parseURL)
292
- // )
293
- // .addOption(forceOption)
294
- // // Validate URLs
295
- // .hook(
296
- // 'preAction',
297
- // ifOptions(
298
- // (opts) => opts.from,
299
- // (thisCommand) => assertUrlHasProtocol(thisCommand.opts().from, ['https:', 'http:'])
300
- // )
301
- // )
302
- // .hook(
303
- // 'preAction',
304
- // ifOptions(
305
- // (opts) => opts.to,
306
- // (thisCommand) => assertUrlHasProtocol(thisCommand.opts().to, ['https:', 'http:'])
307
- // )
308
- // )
309
- // .hook(
310
- // 'preAction',
311
- // ifOptions(
312
- // (opts) => !opts.from && !opts.to,
313
- // () => exitWith(1, 'At least one source (from) or destination (to) option must be provided')
314
- // )
315
- // )
316
- // .addOption(forceOption)
317
- // .addOption(excludeOption)
318
- // .addOption(onlyOption)
319
- // .hook('preAction', validateExcludeOnly)
320
- // .hook(
321
- // 'preAction',
322
- // confirmMessage(
323
- // 'The import will delete all data in the remote database. Are you sure you want to proceed?'
324
- // )
325
- // )
326
- // .action(getLocalScript('transfer/transfer'));
327
- // }
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(new Option('--from-token <token>', `Transfer token for the remote Strapi source`))
285
+ .addOption(
286
+ new Option(
287
+ '--to <destinationURL>',
288
+ `URL of the remote Strapi instance to send data to`
289
+ ).argParser(parseURL)
290
+ )
291
+ .addOption(new Option('--to-token <token>', `Transfer token for the remote Strapi destination`))
292
+ .addOption(forceOption)
293
+ .addOption(excludeOption)
294
+ .addOption(onlyOption)
295
+ .addOption(throttleOption)
296
+ .hook('preAction', validateExcludeOnly)
297
+ .hook(
298
+ 'preAction',
299
+ ifOptions(
300
+ (opts) => !(opts.from || opts.to) || (opts.from && opts.to),
301
+ () =>
302
+ exitWith(1, 'Exactly one remote source (from) or destination (to) option must be provided')
303
+ )
304
+ )
305
+ // If --from is used, validate the URL and token
306
+ .hook(
307
+ 'preAction',
308
+ ifOptions(
309
+ (opts) => opts.from,
310
+ async (thisCommand) => {
311
+ assertUrlHasProtocol(thisCommand.opts().from, ['https:', 'http:']);
312
+ if (!thisCommand.opts().fromToken) {
313
+ const answers = await inquirer.prompt([
314
+ {
315
+ type: 'password',
316
+ message: 'Please enter your transfer token for the remote Strapi source',
317
+ name: 'fromToken',
318
+ },
319
+ ]);
320
+ if (!answers.fromToken?.length) {
321
+ exitWith(1, 'No token provided for remote source, aborting transfer.');
322
+ }
323
+ thisCommand.opts().fromToken = answers.fromToken;
324
+ }
325
+ }
326
+ )
327
+ )
328
+ // If --to is used, validate the URL, token, and confirm restore
329
+ .hook(
330
+ 'preAction',
331
+ ifOptions(
332
+ (opts) => opts.to,
333
+ async (thisCommand) => {
334
+ assertUrlHasProtocol(thisCommand.opts().to, ['https:', 'http:']);
335
+ if (!thisCommand.opts().toToken) {
336
+ const answers = await inquirer.prompt([
337
+ {
338
+ type: 'password',
339
+ message: 'Please enter your transfer token for the remote Strapi destination',
340
+ name: 'toToken',
341
+ },
342
+ ]);
343
+ if (!answers.toToken?.length) {
344
+ exitWith(1, 'No token provided for remote destination, aborting transfer.');
345
+ }
346
+ thisCommand.opts().toToken = answers.toToken;
347
+ }
348
+
349
+ await confirmMessage(
350
+ 'The transfer will delete all data in the remote database and media files. Are you sure you want to proceed?',
351
+ { failMessage: 'Transfer process aborted' }
352
+ )(thisCommand);
353
+ }
354
+ )
355
+ )
356
+ .action(getLocalScript('transfer/transfer'));
328
357
 
329
358
  // `$ strapi export`
330
359
  program
@@ -344,6 +373,7 @@ program
344
373
  .addOption(new Option('-f, --file <file>', 'name to use for exported file (without extensions)'))
345
374
  .addOption(excludeOption)
346
375
  .addOption(onlyOption)
376
+ .addOption(throttleOption)
347
377
  .hook('preAction', validateExcludeOnly)
348
378
  .hook('preAction', promptEncryptionKey)
349
379
  .action(getLocalScript('transfer/export'));
@@ -366,6 +396,7 @@ program
366
396
  .addOption(forceOption)
367
397
  .addOption(excludeOption)
368
398
  .addOption(onlyOption)
399
+ .addOption(throttleOption)
369
400
  .hook('preAction', validateExcludeOnly)
370
401
  .hook('preAction', async (thisCommand) => {
371
402
  const opts = thisCommand.opts();
@@ -382,7 +413,7 @@ program
382
413
  },
383
414
  ]);
384
415
  if (!answers.key?.length) {
385
- exitWith(0, 'No key entered, aborting import.');
416
+ exitWith(1, 'No key entered, aborting import.');
386
417
  }
387
418
  opts.key = answers.key;
388
419
  }
@@ -420,7 +451,8 @@ program
420
451
  .hook(
421
452
  'preAction',
422
453
  confirmMessage(
423
- 'The import will delete all data in your database. Are you sure you want to proceed?'
454
+ 'The import will delete all data in your database and media files. Are you sure you want to proceed?',
455
+ { failMessage: 'Import process aborted' }
424
456
  )
425
457
  )
426
458
  .action(getLocalScript('transfer/import'));
package/ee/index.js CHANGED
@@ -3,7 +3,6 @@
3
3
  const { pick } = require('lodash/fp');
4
4
 
5
5
  const { readLicense, verifyLicense, fetchLicense, LicenseCheckError } = require('./license');
6
- const { eeStoreModel } = require('./ee-store');
7
6
  const { shiftCronExpression } = require('../lib/utils/cron');
8
7
 
9
8
  const ONE_MINUTE = 1000 * 60;
@@ -60,7 +59,7 @@ const onlineUpdate = async ({ strapi }) => {
60
59
 
61
60
  try {
62
61
  const storedInfo = await strapi.db
63
- .queryBuilder(eeStoreModel.uid)
62
+ .queryBuilder('strapi::core-store')
64
63
  .where({ key: 'ee_information' })
65
64
  .select('value')
66
65
  .first()
@@ -101,7 +100,7 @@ const onlineUpdate = async ({ strapi }) => {
101
100
 
102
101
  if (shouldContactRegistry) {
103
102
  result.license = license ?? null;
104
- const query = strapi.db.queryBuilder(eeStoreModel.uid).transacting(transaction);
103
+ const query = strapi.db.queryBuilder('strapi::core-store').transacting(transaction);
105
104
 
106
105
  if (!storedInfo) {
107
106
  query.insert({ key: 'ee_information', value: JSON.stringify(result) });
@@ -165,6 +164,10 @@ module.exports = Object.freeze({
165
164
  return ee.enabled;
166
165
  },
167
166
 
167
+ get seats() {
168
+ return ee.licenseInfo.seats;
169
+ },
170
+
168
171
  features: Object.freeze({
169
172
  list,
170
173
  get,
package/ee/license.js CHANGED
@@ -10,7 +10,7 @@ const machineId = require('../lib/utils/machine-id');
10
10
  const DEFAULT_FEATURES = {
11
11
  bronze: [],
12
12
  silver: [],
13
- gold: ['sso', { name: 'audit-logs', options: { retentionDays: 90 } }, 'review-workflows'],
13
+ gold: ['sso', { name: 'audit-logs', options: { retentionDays: 90 } }],
14
14
  };
15
15
 
16
16
  const publicKey = fs.readFileSync(join(__dirname, 'resources/key.pub'));
package/lib/Strapi.js CHANGED
@@ -17,7 +17,6 @@ const { createServer } = require('./services/server');
17
17
  const createWebhookRunner = require('./services/webhook-runner');
18
18
  const { webhookModel, createWebhookStore } = require('./services/webhook-store');
19
19
  const { createCoreStore, coreStoreModel } = require('./services/core-store');
20
- const { eeStoreModel } = require('../ee/ee-store');
21
20
  const createEntityService = require('./services/entity-service');
22
21
  const createCronService = require('./services/cron');
23
22
  const entityValidator = require('./services/entity-validator');
@@ -117,7 +116,6 @@ class Strapi {
117
116
  this.cron = createCronService();
118
117
  this.telemetry = createTelemetry(this);
119
118
  this.requestContext = requestContext;
120
-
121
119
  this.customFields = createCustomFields(this);
122
120
 
123
121
  createUpdateNotifier(this).notify();
@@ -227,7 +225,6 @@ class Strapi {
227
225
 
228
226
  async destroy() {
229
227
  await this.server.destroy();
230
-
231
228
  await this.runLifecyclesFunctions(LIFECYCLES.DESTROY);
232
229
 
233
230
  this.eventHub.destroy();
@@ -409,7 +406,6 @@ class Strapi {
409
406
  const contentTypes = [
410
407
  coreStoreModel,
411
408
  webhookModel,
412
- eeStoreModel,
413
409
  ...Object.values(strapi.contentTypes),
414
410
  ...Object.values(strapi.components),
415
411
  ];
@@ -1,36 +1,43 @@
1
1
  'use strict';
2
2
 
3
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');
4
+ file: {
5
+ providers: { createLocalFileDestinationProvider },
6
+ },
7
+ strapi: {
8
+ providers: { createLocalStrapiSourceProvider },
9
+ },
10
+ engine: { createTransferEngine },
11
+ } = require('@strapi/data-transfer');
12
+
10
13
  const { isObject, isString, isFinite, toNumber } = require('lodash/fp');
11
14
  const fs = require('fs-extra');
12
15
  const chalk = require('chalk');
13
16
 
14
- const { TransferEngineTransferError } = require('@strapi/data-transfer/lib/engine/errors');
17
+ const { TransferEngineTransferError } = require('@strapi/data-transfer').engine.errors;
15
18
  const {
16
19
  getDefaultExportName,
17
20
  buildTransferTable,
18
21
  DEFAULT_IGNORED_CONTENT_TYPES,
19
22
  createStrapiInstance,
20
23
  formatDiagnostic,
24
+ loadersFactory,
25
+ exitMessageText,
26
+ abortTransfer,
21
27
  } = require('./utils');
22
-
28
+ const { exitWith } = require('../utils/helpers');
23
29
  /**
24
30
  * @typedef ExportCommandOptions Options given to the CLI import command
25
31
  *
26
- * @property {string} [file] The file path to import
32
+ * @property {string} [file] The file path to export to
27
33
  * @property {boolean} [encrypt] Used to encrypt the final archive
28
- * @property {string} [key] Encryption key, only useful when encryption is enabled
34
+ * @property {string} [key] Encryption key, used only when encryption is enabled
29
35
  * @property {boolean} [compress] Used to compress the final archive
36
+ * @property {(keyof import('@strapi/data-transfer/src/engine').TransferGroupFilter)[]} [only] If present, only include these filtered groups of data
37
+ * @property {(keyof import('@strapi/data-transfer/src/engine').TransferGroupFilter)[]} [exclude] If present, exclude these filtered groups of data
38
+ * @property {number|undefined} [throttle] Delay in ms after each record
30
39
  */
31
40
 
32
- const logger = console;
33
-
34
41
  const BYTES_IN_MB = 1024 * 1024;
35
42
 
36
43
  /**
@@ -43,8 +50,7 @@ const BYTES_IN_MB = 1024 * 1024;
43
50
  module.exports = async (opts) => {
44
51
  // Validate inputs from Commander
45
52
  if (!isObject(opts)) {
46
- logger.error('Could not parse command arguments');
47
- process.exit(1);
53
+ exitWith(1, 'Could not parse command arguments');
48
54
  }
49
55
 
50
56
  const strapi = await createStrapiInstance();
@@ -57,6 +63,7 @@ module.exports = async (opts) => {
57
63
  schemaStrategy: 'ignore', // for an export to file, schemaStrategy will always be skipped
58
64
  exclude: opts.exclude,
59
65
  only: opts.only,
66
+ throttle: opts.throttle,
60
67
  transforms: {
61
68
  links: [
62
69
  {
@@ -82,6 +89,20 @@ module.exports = async (opts) => {
82
89
 
83
90
  const progress = engine.progress.stream;
84
91
 
92
+ const { updateLoader } = loadersFactory();
93
+
94
+ progress.on(`stage::start`, ({ stage, data }) => {
95
+ updateLoader(stage, data).start();
96
+ });
97
+
98
+ progress.on('stage::finish', ({ stage, data }) => {
99
+ updateLoader(stage, data).succeed();
100
+ });
101
+
102
+ progress.on('stage::progress', ({ stage, data }) => {
103
+ updateLoader(stage, data);
104
+ });
105
+
85
106
  const getTelemetryPayload = (/* payload */) => {
86
107
  return {
87
108
  eventProperties: {
@@ -92,33 +113,41 @@ module.exports = async (opts) => {
92
113
  };
93
114
 
94
115
  progress.on('transfer::start', async () => {
95
- logger.log(`Starting export...`);
116
+ console.log(`Starting export...`);
117
+
96
118
  await strapi.telemetry.send('didDEITSProcessStart', getTelemetryPayload());
97
119
  });
98
120
 
121
+ let results;
122
+ let outFile;
99
123
  try {
100
- const results = await engine.transfer();
101
- const outFile = results.destination.file.path;
102
-
103
- const table = buildTransferTable(results.engine);
104
- logger.log(table.toString());
105
-
124
+ // Abort transfer if user interrupts process
125
+ ['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => {
126
+ process.removeAllListeners(signal);
127
+ process.on(signal, () => abortTransfer({ engine, strapi }));
128
+ });
129
+
130
+ results = await engine.transfer();
131
+ outFile = results.destination.file.path;
106
132
  const outFileExists = await fs.pathExists(outFile);
107
133
  if (!outFileExists) {
108
134
  throw new TransferEngineTransferError(`Export file not created "${outFile}"`);
109
135
  }
110
-
111
- logger.log(`${chalk.bold('Export process has been completed successfully!')}`);
112
- logger.log(`Export archive is in ${chalk.green(outFile)}`);
113
136
  } catch {
114
137
  await strapi.telemetry.send('didDEITSProcessFail', getTelemetryPayload());
115
- logger.error('Export process failed.');
116
- process.exit(1);
138
+ exitWith(1, exitMessageText('export', true));
117
139
  }
118
140
 
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
141
  await strapi.telemetry.send('didDEITSProcessFinish', getTelemetryPayload());
121
- process.exit(0);
142
+ try {
143
+ const table = buildTransferTable(results.engine);
144
+ console.log(table.toString());
145
+ } catch (e) {
146
+ console.error('There was an error displaying the results of the transfer.');
147
+ }
148
+
149
+ console.log(`Export archive is in ${chalk.green(outFile)}`);
150
+ exitWith(0, exitMessageText('export'));
122
151
  };
123
152
 
124
153
  /**
@@ -1,16 +1,14 @@
1
1
  'use strict';
2
2
 
3
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');
4
+ file: {
5
+ providers: { createLocalFileSourceProvider },
6
+ },
7
+ strapi: {
8
+ providers: { createLocalStrapiDestinationProvider, DEFAULT_CONFLICT_STRATEGY },
9
+ },
10
+ engine: { createTransferEngine, DEFAULT_VERSION_STRATEGY, DEFAULT_SCHEMA_STRATEGY },
11
+ } = require('@strapi/data-transfer');
14
12
 
15
13
  const { isObject } = require('lodash/fp');
16
14
 
@@ -19,19 +17,37 @@ const {
19
17
  DEFAULT_IGNORED_CONTENT_TYPES,
20
18
  createStrapiInstance,
21
19
  formatDiagnostic,
20
+ loadersFactory,
21
+ exitMessageText,
22
+ abortTransfer,
22
23
  } = require('./utils');
24
+ const { exitWith } = require('../utils/helpers');
23
25
 
24
26
  /**
25
- * @typedef {import('@strapi/data-transfer').ILocalFileSourceProviderOptions} ILocalFileSourceProviderOptions
27
+ * @typedef {import('@strapi/data-transfer/src/file/providers').ILocalFileSourceProviderOptions} ILocalFileSourceProviderOptions
26
28
  */
27
29
 
28
- const logger = console;
30
+ /**
31
+ * @typedef ImportCommandOptions Options given to the CLI import command
32
+ *
33
+ * @property {string} [file] The file path to import
34
+ * @property {string} [key] Encryption key, used when encryption is enabled
35
+ * @property {(keyof import('@strapi/data-transfer/src/engine').TransferGroupFilter)[]} [only] If present, only include these filtered groups of data
36
+ * @property {(keyof import('@strapi/data-transfer/src/engine').TransferGroupFilter)[]} [exclude] If present, exclude these filtered groups of data
37
+ * @property {number|undefined} [throttle] Delay in ms after each record
38
+ */
29
39
 
40
+ /**
41
+ * Import command.
42
+ *
43
+ * It transfers data from a file to a local Strapi instance
44
+ *
45
+ * @param {ImportCommandOptions} opts
46
+ */
30
47
  module.exports = async (opts) => {
31
48
  // validate inputs from Commander
32
49
  if (!isObject(opts)) {
33
- logger.error('Could not parse arguments');
34
- process.exit(1);
50
+ exitWith(1, 'Could not parse arguments');
35
51
  }
36
52
 
37
53
  /**
@@ -66,6 +82,7 @@ module.exports = async (opts) => {
66
82
  schemaStrategy: opts.schemaStrategy || DEFAULT_SCHEMA_STRATEGY,
67
83
  exclude: opts.exclude,
68
84
  only: opts.only,
85
+ throttle: opts.throttle,
69
86
  rules: {
70
87
  links: [
71
88
  {
@@ -90,6 +107,21 @@ module.exports = async (opts) => {
90
107
  engine.diagnostics.onDiagnostic(formatDiagnostic('import'));
91
108
 
92
109
  const progress = engine.progress.stream;
110
+
111
+ const { updateLoader } = loadersFactory();
112
+
113
+ progress.on(`stage::start`, ({ stage, data }) => {
114
+ updateLoader(stage, data).start();
115
+ });
116
+
117
+ progress.on('stage::finish', ({ stage, data }) => {
118
+ updateLoader(stage, data).succeed();
119
+ });
120
+
121
+ progress.on('stage::progress', ({ stage, data }) => {
122
+ updateLoader(stage, data);
123
+ });
124
+
93
125
  const getTelemetryPayload = () => {
94
126
  return {
95
127
  eventProperties: {
@@ -100,27 +132,35 @@ module.exports = async (opts) => {
100
132
  };
101
133
 
102
134
  progress.on('transfer::start', async () => {
103
- logger.info('Starting import...');
135
+ console.log('Starting import...');
104
136
  await strapiInstance.telemetry.send('didDEITSProcessStart', getTelemetryPayload());
105
137
  });
106
138
 
139
+ let results;
107
140
  try {
108
- const results = await engine.transfer();
109
- const table = buildTransferTable(results.engine);
110
- logger.info(table.toString());
141
+ // Abort transfer if user interrupts process
142
+ ['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => {
143
+ process.removeAllListeners(signal);
144
+ process.on(signal, () => abortTransfer({ engine, strapi }));
145
+ });
111
146
 
112
- logger.info('Import process has been completed successfully!');
147
+ results = await engine.transfer();
113
148
  } catch (e) {
114
149
  await strapiInstance.telemetry.send('didDEITSProcessFail', getTelemetryPayload());
115
- logger.error('Import process failed.');
116
- process.exit(1);
150
+ exitWith(1, exitMessageText('import', true));
151
+ }
152
+
153
+ try {
154
+ const table = buildTransferTable(results.engine);
155
+ console.log(table.toString());
156
+ } catch (e) {
157
+ console.error('There was an error displaying the results of the transfer.');
117
158
  }
118
159
 
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
160
  await strapiInstance.telemetry.send('didDEITSProcessFinish', getTelemetryPayload());
121
161
  await strapiInstance.destroy();
122
162
 
123
- process.exit(0);
163
+ exitWith(0, exitMessageText('import'));
124
164
  };
125
165
 
126
166
  /**