@strapi/strapi 4.6.2 → 4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d

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,8 +18,9 @@ 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,
@@ -272,59 +273,88 @@ program
272
273
  .option('-s, --silent', `Run the generation silently, without any output`, false)
273
274
  .action(getLocalScript('ts/generate-types'));
274
275
 
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
- // }
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('--from <sourceURL>', `URL of the remote Strapi instance to get data from`)
283
+ .argParser(parseURL)
284
+ .hideHelp() // Hidden until pull feature is released
285
+ )
286
+ .addOption(
287
+ new Option('--from-token <token>', `Transfer token for the remote Strapi source`).hideHelp() // Hidden until pull feature is released
288
+ )
289
+ .addOption(
290
+ new Option(
291
+ '--to <destinationURL>',
292
+ `URL of the remote Strapi instance to send data to`
293
+ ).argParser(parseURL)
294
+ )
295
+ .addOption(new Option('--to-token <token>', `Transfer token for the remote Strapi destination`))
296
+ .addOption(forceOption)
297
+ .addOption(excludeOption)
298
+ .addOption(onlyOption)
299
+ .hook('preAction', validateExcludeOnly)
300
+ // If --from is used, validate the URL and token
301
+ .hook(
302
+ 'preAction',
303
+ ifOptions(
304
+ (opts) => opts.from,
305
+ async (thisCommand) => {
306
+ assertUrlHasProtocol(thisCommand.opts().from, ['https:', 'http:']);
307
+ if (!thisCommand.opts().fromToken) {
308
+ const answers = await inquirer.prompt([
309
+ {
310
+ type: 'password',
311
+ message: 'Please enter your transfer token for the remote Strapi source',
312
+ name: 'fromToken',
313
+ },
314
+ ]);
315
+ if (!answers.fromToken?.length) {
316
+ exitWith(0, 'No token entered, aborting transfer.');
317
+ }
318
+ thisCommand.opts().fromToken = answers.fromToken;
319
+ }
320
+ }
321
+ )
322
+ )
323
+ // If --to is used, validate the URL, token, and confirm restore
324
+ .hook(
325
+ 'preAction',
326
+ ifOptions(
327
+ (opts) => opts.to,
328
+ async (thisCommand) => {
329
+ assertUrlHasProtocol(thisCommand.opts().to, ['https:', 'http:']);
330
+ if (!thisCommand.opts().toToken) {
331
+ const answers = await inquirer.prompt([
332
+ {
333
+ type: 'password',
334
+ message: 'Please enter your transfer token for the remote Strapi destination',
335
+ name: 'toToken',
336
+ },
337
+ ]);
338
+ if (!answers.toToken?.length) {
339
+ exitWith(0, 'No token entered, aborting transfer.');
340
+ }
341
+ thisCommand.opts().toToken = answers.toToken;
342
+ }
343
+
344
+ await confirmMessage(
345
+ 'The transfer will delete all data in the remote database and media files. Are you sure you want to proceed?'
346
+ )(thisCommand);
347
+ }
348
+ )
349
+ )
350
+ // .hook(
351
+ // 'preAction',
352
+ // ifOptions(
353
+ // (opts) => !opts.from && !opts.to,
354
+ // () => exitWith(1, 'At least one source (from) or destination (to) option must be provided')
355
+ // )
356
+ // )
357
+ .action(getLocalScript('transfer/transfer'));
328
358
 
329
359
  // `$ strapi export`
330
360
  program
@@ -420,7 +450,7 @@ program
420
450
  .hook(
421
451
  'preAction',
422
452
  confirmMessage(
423
- 'The import will delete all data in your database. Are you sure you want to proceed?'
453
+ 'The import will delete all data in your database and media files. Are you sure you want to proceed?'
424
454
  )
425
455
  )
426
456
  .action(getLocalScript('transfer/import'));
@@ -18,8 +18,9 @@ const {
18
18
  DEFAULT_IGNORED_CONTENT_TYPES,
19
19
  createStrapiInstance,
20
20
  formatDiagnostic,
21
+ loadersFactory,
21
22
  } = require('./utils');
22
-
23
+ const { exitWith } = require('../utils/helpers');
23
24
  /**
24
25
  * @typedef ExportCommandOptions Options given to the CLI import command
25
26
  *
@@ -29,8 +30,6 @@ const {
29
30
  * @property {boolean} [compress] Used to compress the final archive
30
31
  */
31
32
 
32
- const logger = console;
33
-
34
33
  const BYTES_IN_MB = 1024 * 1024;
35
34
 
36
35
  /**
@@ -43,8 +42,7 @@ const BYTES_IN_MB = 1024 * 1024;
43
42
  module.exports = async (opts) => {
44
43
  // Validate inputs from Commander
45
44
  if (!isObject(opts)) {
46
- logger.error('Could not parse command arguments');
47
- process.exit(1);
45
+ exitWith(1, 'Could not parse command arguments');
48
46
  }
49
47
 
50
48
  const strapi = await createStrapiInstance();
@@ -82,6 +80,20 @@ module.exports = async (opts) => {
82
80
 
83
81
  const progress = engine.progress.stream;
84
82
 
83
+ const { updateLoader } = loadersFactory();
84
+
85
+ progress.on(`stage::start`, ({ stage, data }) => {
86
+ updateLoader(stage, data).start();
87
+ });
88
+
89
+ progress.on('stage::finish', ({ stage, data }) => {
90
+ updateLoader(stage, data).succeed();
91
+ });
92
+
93
+ progress.on('stage::progress', ({ stage, data }) => {
94
+ updateLoader(stage, data);
95
+ });
96
+
85
97
  const getTelemetryPayload = (/* payload */) => {
86
98
  return {
87
99
  eventProperties: {
@@ -92,33 +104,34 @@ module.exports = async (opts) => {
92
104
  };
93
105
 
94
106
  progress.on('transfer::start', async () => {
95
- logger.log(`Starting export...`);
107
+ console.log(`Starting export...`);
96
108
  await strapi.telemetry.send('didDEITSProcessStart', getTelemetryPayload());
97
109
  });
98
110
 
111
+ let results;
112
+ let outFile;
99
113
  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
-
114
+ results = await engine.transfer();
115
+ outFile = results.destination.file.path;
106
116
  const outFileExists = await fs.pathExists(outFile);
107
117
  if (!outFileExists) {
108
118
  throw new TransferEngineTransferError(`Export file not created "${outFile}"`);
109
119
  }
110
-
111
- logger.log(`${chalk.bold('Export process has been completed successfully!')}`);
112
- logger.log(`Export archive is in ${chalk.green(outFile)}`);
113
120
  } catch {
114
121
  await strapi.telemetry.send('didDEITSProcessFail', getTelemetryPayload());
115
- logger.error('Export process failed.');
116
- process.exit(1);
122
+ exitWith(1, 'Export process failed.');
117
123
  }
118
124
 
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
125
  await strapi.telemetry.send('didDEITSProcessFinish', getTelemetryPayload());
121
- process.exit(0);
126
+ try {
127
+ const table = buildTransferTable(results.engine);
128
+ console.log(table.toString());
129
+ } catch (e) {
130
+ console.error('There was an error displaying the results of the transfer.');
131
+ }
132
+
133
+ console.log(`${chalk.bold('Export process has been completed successfully!')}`);
134
+ exitWith(0, `Export archive is in ${chalk.green(outFile)}`);
122
135
  };
123
136
 
124
137
  /**
@@ -19,19 +19,18 @@ const {
19
19
  DEFAULT_IGNORED_CONTENT_TYPES,
20
20
  createStrapiInstance,
21
21
  formatDiagnostic,
22
+ loadersFactory,
22
23
  } = require('./utils');
24
+ const { exitWith } = require('../utils/helpers');
23
25
 
24
26
  /**
25
27
  * @typedef {import('@strapi/data-transfer').ILocalFileSourceProviderOptions} ILocalFileSourceProviderOptions
26
28
  */
27
29
 
28
- const logger = console;
29
-
30
30
  module.exports = async (opts) => {
31
31
  // validate inputs from Commander
32
32
  if (!isObject(opts)) {
33
- logger.error('Could not parse arguments');
34
- process.exit(1);
33
+ exitWith(1, 'Could not parse arguments');
35
34
  }
36
35
 
37
36
  /**
@@ -90,6 +89,21 @@ module.exports = async (opts) => {
90
89
  engine.diagnostics.onDiagnostic(formatDiagnostic('import'));
91
90
 
92
91
  const progress = engine.progress.stream;
92
+
93
+ const { updateLoader } = loadersFactory();
94
+
95
+ progress.on(`stage::start`, ({ stage, data }) => {
96
+ updateLoader(stage, data).start();
97
+ });
98
+
99
+ progress.on('stage::finish', ({ stage, data }) => {
100
+ updateLoader(stage, data).succeed();
101
+ });
102
+
103
+ progress.on('stage::progress', ({ stage, data }) => {
104
+ updateLoader(stage, data);
105
+ });
106
+
93
107
  const getTelemetryPayload = () => {
94
108
  return {
95
109
  eventProperties: {
@@ -100,27 +114,30 @@ module.exports = async (opts) => {
100
114
  };
101
115
 
102
116
  progress.on('transfer::start', async () => {
103
- logger.info('Starting import...');
117
+ console.log('Starting import...');
104
118
  await strapiInstance.telemetry.send('didDEITSProcessStart', getTelemetryPayload());
105
119
  });
106
120
 
121
+ let results;
107
122
  try {
108
- const results = await engine.transfer();
109
- const table = buildTransferTable(results.engine);
110
- logger.info(table.toString());
111
-
112
- logger.info('Import process has been completed successfully!');
123
+ results = await engine.transfer();
113
124
  } catch (e) {
114
125
  await strapiInstance.telemetry.send('didDEITSProcessFail', getTelemetryPayload());
115
- logger.error('Import process failed.');
126
+ console.error('Import process failed.');
116
127
  process.exit(1);
117
128
  }
118
129
 
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
130
+ try {
131
+ const table = buildTransferTable(results.engine);
132
+ console.log(table.toString());
133
+ } catch (e) {
134
+ console.error('There was an error displaying the results of the transfer.');
135
+ }
136
+
120
137
  await strapiInstance.telemetry.send('didDEITSProcessFinish', getTelemetryPayload());
121
138
  await strapiInstance.destroy();
122
139
 
123
- process.exit(0);
140
+ exitWith(0, 'Import process has been completed successfully!');
124
141
  };
125
142
 
126
143
  /**
@@ -16,29 +16,30 @@ const {
16
16
  createStrapiInstance,
17
17
  DEFAULT_IGNORED_CONTENT_TYPES,
18
18
  formatDiagnostic,
19
+ loadersFactory,
19
20
  } = require('./utils');
20
-
21
- const logger = console;
21
+ const { exitWith } = require('../utils/helpers');
22
22
 
23
23
  /**
24
24
  * @typedef TransferCommandOptions Options given to the CLI transfer command
25
25
  *
26
26
  * @property {URL|undefined} [to] The url of a remote Strapi to use as remote destination
27
27
  * @property {URL|undefined} [from] The url of a remote Strapi to use as remote source
28
+ * @property {string|undefined} [toToken] The transfer token for the remote Strapi destination
29
+ * @property {string|undefined} [fromToken] The transfer token for the remote Strapi source
28
30
  */
29
31
 
30
32
  /**
31
33
  * Transfer command.
32
34
  *
33
- * It transfers data from a local file to a local strapi instance
35
+ * Transfers data between local Strapi and remote Strapi instances
34
36
  *
35
37
  * @param {TransferCommandOptions} opts
36
38
  */
37
39
  module.exports = async (opts) => {
38
40
  // Validate inputs from Commander
39
41
  if (!isObject(opts)) {
40
- logger.error('Could not parse command arguments');
41
- process.exit(1);
42
+ exitWith(1, 'Could not parse command arguments');
42
43
  }
43
44
 
44
45
  const strapi = await createStrapiInstance();
@@ -47,8 +48,7 @@ module.exports = async (opts) => {
47
48
  let destination;
48
49
 
49
50
  if (!opts.from && !opts.to) {
50
- logger.error('At least one source (from) or destination (to) option must be provided');
51
- process.exit(1);
51
+ exitWith(1, 'At least one source (from) or destination (to) option must be provided');
52
52
  }
53
53
 
54
54
  // if no URL provided, use local Strapi
@@ -59,8 +59,7 @@ module.exports = async (opts) => {
59
59
  }
60
60
  // if URL provided, set up a remote source provider
61
61
  else {
62
- logger.error(`Remote Strapi source provider not yet implemented`);
63
- process.exit(1);
62
+ exitWith(1, `Remote Strapi source provider not yet implemented`);
64
63
  }
65
64
 
66
65
  // if no URL provided, use local Strapi
@@ -71,9 +70,16 @@ module.exports = async (opts) => {
71
70
  }
72
71
  // if URL provided, set up a remote destination provider
73
72
  else {
73
+ if (!opts.toToken) {
74
+ exitWith(1, 'Missing token for remote destination');
75
+ }
76
+
74
77
  destination = createRemoteStrapiDestinationProvider({
75
78
  url: opts.to,
76
- auth: false,
79
+ auth: {
80
+ type: 'token',
81
+ token: opts.toToken,
82
+ },
77
83
  strategy: 'restore',
78
84
  restore: {
79
85
  entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
@@ -82,12 +88,11 @@ module.exports = async (opts) => {
82
88
  }
83
89
 
84
90
  if (!source || !destination) {
85
- logger.error('Could not create providers');
86
- process.exit(1);
91
+ exitWith(1, 'Could not create providers');
87
92
  }
88
93
 
89
94
  const engine = createTransferEngine(source, destination, {
90
- versionStrategy: 'strict',
95
+ versionStrategy: 'exact',
91
96
  schemaStrategy: 'strict',
92
97
  transforms: {
93
98
  links: [
@@ -112,18 +117,31 @@ module.exports = async (opts) => {
112
117
 
113
118
  engine.diagnostics.onDiagnostic(formatDiagnostic('transfer'));
114
119
 
115
- try {
116
- logger.log(`Starting transfer...`);
120
+ const progress = engine.progress.stream;
117
121
 
118
- const results = await engine.transfer();
122
+ const { updateLoader } = loadersFactory();
119
123
 
120
- const table = buildTransferTable(results.engine);
121
- logger.log(table.toString());
124
+ progress.on(`stage::start`, ({ stage, data }) => {
125
+ updateLoader(stage, data).start();
126
+ });
122
127
 
123
- logger.log(`${chalk.bold('Transfer process has been completed successfully!')}`);
124
- process.exit(0);
128
+ progress.on('stage::finish', ({ stage, data }) => {
129
+ updateLoader(stage, data).succeed();
130
+ });
131
+
132
+ progress.on('stage::progress', ({ stage, data }) => {
133
+ updateLoader(stage, data);
134
+ });
135
+
136
+ let results;
137
+ try {
138
+ console.log(`Starting transfer...`);
139
+ results = await engine.transfer();
125
140
  } catch (e) {
126
- logger.error('Transfer process failed.');
127
- process.exit(1);
141
+ exitWith(1, 'Transfer process failed.');
128
142
  }
143
+
144
+ const table = buildTransferTable(results.engine);
145
+ console.log(table.toString());
146
+ exitWith(0, `${chalk.bold('Transfer process has been completed successfully!')}`);
129
147
  };
@@ -9,6 +9,7 @@ const {
9
9
  configs: { createOutputFileConfiguration },
10
10
  createLogger,
11
11
  } = require('@strapi/logger');
12
+ const ora = require('ora');
12
13
  const { readableBytes, exitWith } = require('../utils/helpers');
13
14
  const strapi = require('../../index');
14
15
  const { getParseListWithChoices } = require('../utils/commander');
@@ -82,6 +83,8 @@ const DEFAULT_IGNORED_CONTENT_TYPES = [
82
83
  'admin::role',
83
84
  'admin::api-token',
84
85
  'admin::api-token-permission',
86
+ 'admin::transfer-token',
87
+ 'admin::transfer-token-permission',
85
88
  'admin::audit-log',
86
89
  ];
87
90
 
@@ -169,7 +172,46 @@ const formatDiagnostic =
169
172
  }
170
173
  };
171
174
 
175
+ const loadersFactory = (defaultLoaders = {}) => {
176
+ const loaders = defaultLoaders;
177
+ const updateLoader = (stage, data) => {
178
+ if (!(stage in loaders)) {
179
+ createLoader(stage);
180
+ }
181
+ const stageData = data[stage];
182
+ const elapsedTime = stageData?.startTime
183
+ ? (stageData?.endTime || Date.now()) - stageData.startTime
184
+ : 0;
185
+ const size = `size: ${readableBytes(stageData?.bytes ?? 0)}`;
186
+ const elapsed = `elapsed: ${elapsedTime} ms`;
187
+ const speed =
188
+ elapsedTime > 0 ? `(${readableBytes(((stageData?.bytes ?? 0) * 1000) / elapsedTime)}/s)` : '';
189
+
190
+ loaders[stage].text = `${stage}: ${stageData?.count ?? 0} transfered (${size}) (${elapsed}) ${
191
+ !stageData?.endTime ? speed : ''
192
+ }`;
193
+
194
+ return loaders[stage];
195
+ };
196
+
197
+ const createLoader = (stage) => {
198
+ Object.assign(loaders, { [stage]: ora() });
199
+ return loaders[stage];
200
+ };
201
+
202
+ const getLoader = (stage) => {
203
+ return loaders[stage];
204
+ };
205
+
206
+ return {
207
+ updateLoader,
208
+ createLoader,
209
+ getLoader,
210
+ };
211
+ };
212
+
172
213
  module.exports = {
214
+ loadersFactory,
173
215
  buildTransferTable,
174
216
  getDefaultExportName,
175
217
  DEFAULT_IGNORED_CONTENT_TYPES,
@@ -36,22 +36,29 @@ const readableBytes = (bytes, decimals = 1, padStart = 0) => {
36
36
  *
37
37
  * @param {number} code Code to exit process with
38
38
  * @param {string | Array} message Message(s) to display before exiting
39
+ * @param {Object} options
40
+ * @param {console} options.logger - logger object, defaults to console
41
+ * @param {process} options.prc - process object, defaults to process
42
+ *
39
43
  */
40
- const exitWith = (code, message = undefined) => {
41
- const logger = (message) => {
44
+ const exitWith = (code, message = undefined, options = {}) => {
45
+ const { logger = console, prc = process } = options;
46
+
47
+ const log = (message) => {
42
48
  if (code === 0) {
43
- console.log(chalk.green(message));
49
+ logger.log(chalk.green(message));
44
50
  } else {
45
- console.error(chalk.red(message));
51
+ logger.error(chalk.red(message));
46
52
  }
47
53
  };
48
54
 
49
55
  if (isString(message)) {
50
- logger(message);
56
+ log(message);
51
57
  } else if (isArray(message)) {
52
- message.forEach((msg) => logger(msg));
58
+ message.forEach((msg) => log(msg));
53
59
  }
54
- process.exit(code);
60
+
61
+ prc.exit(code);
55
62
  };
56
63
 
57
64
  /**
@@ -43,7 +43,28 @@ const createAuthentication = () => {
43
43
  return next();
44
44
  }
45
45
 
46
- const strategiesToUse = strategies[route.info.type];
46
+ const routeStrategies = strategies[route.info.type];
47
+ const configStrategies = config?.strategies ?? routeStrategies ?? [];
48
+
49
+ const strategiesToUse = configStrategies.reduce((acc, strategy) => {
50
+ // Resolve by strategy name
51
+ if (typeof strategy === 'string') {
52
+ const routeStrategy = routeStrategies.find((rs) => rs.name === strategy);
53
+
54
+ if (routeStrategy) {
55
+ acc.push(routeStrategy);
56
+ }
57
+ }
58
+
59
+ // Use the given strategy as is
60
+ else if (typeof strategy === 'object') {
61
+ validStrategy(strategy);
62
+
63
+ acc.push(strategy);
64
+ }
65
+
66
+ return acc;
67
+ }, []);
47
68
 
48
69
  for (const strategy of strategiesToUse) {
49
70
  const result = await strategy.authenticate(ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/strapi",
3
- "version": "4.6.2",
3
+ "version": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
4
4
  "description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite",
5
5
  "keywords": [
6
6
  "strapi",
@@ -81,19 +81,19 @@
81
81
  "dependencies": {
82
82
  "@koa/cors": "3.4.3",
83
83
  "@koa/router": "10.1.1",
84
- "@strapi/admin": "4.6.2",
85
- "@strapi/data-transfer": "4.6.2",
86
- "@strapi/database": "4.6.2",
87
- "@strapi/generate-new": "4.6.2",
88
- "@strapi/generators": "4.6.2",
89
- "@strapi/logger": "4.6.2",
90
- "@strapi/permissions": "4.6.2",
91
- "@strapi/plugin-content-manager": "4.6.2",
92
- "@strapi/plugin-content-type-builder": "4.6.2",
93
- "@strapi/plugin-email": "4.6.2",
94
- "@strapi/plugin-upload": "4.6.2",
95
- "@strapi/typescript-utils": "4.6.2",
96
- "@strapi/utils": "4.6.2",
84
+ "@strapi/admin": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
85
+ "@strapi/data-transfer": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
86
+ "@strapi/database": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
87
+ "@strapi/generate-new": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
88
+ "@strapi/generators": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
89
+ "@strapi/logger": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
90
+ "@strapi/permissions": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
91
+ "@strapi/plugin-content-manager": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
92
+ "@strapi/plugin-content-type-builder": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
93
+ "@strapi/plugin-email": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
94
+ "@strapi/plugin-upload": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
95
+ "@strapi/typescript-utils": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
96
+ "@strapi/utils": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
97
97
  "bcryptjs": "2.4.3",
98
98
  "boxen": "5.1.2",
99
99
  "chalk": "4.1.2",
@@ -142,5 +142,5 @@
142
142
  "node": ">=14.19.1 <=18.x.x",
143
143
  "npm": ">=6.0.0"
144
144
  },
145
- "gitHead": "bae505f44c3a779905f6b8dbc0c497e24d9eabfb"
145
+ "gitHead": "117579f4c13806c2cd518e7d7d2f9d0c8a20107d"
146
146
  }