@strapi/strapi 4.6.0 → 4.7.0-beta.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/bin/strapi.js +92 -55
- package/lib/commands/transfer/export.js +17 -18
- package/lib/commands/transfer/import.js +14 -13
- package/lib/commands/transfer/transfer.js +25 -24
- package/lib/commands/transfer/utils.js +2 -0
- package/lib/commands/utils/helpers.js +14 -7
- package/lib/services/auth/index.js +22 -1
- package/lib/services/entity-service/components.js +50 -33
- package/lib/services/metrics/sender.js +12 -0
- package/lib/types/core/attributes/common.d.ts +4 -1
- package/package.json +16 -16
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
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
|
@@ -409,11 +439,18 @@ program
|
|
|
409
439
|
} else {
|
|
410
440
|
thisCommand.opts().decompress = false;
|
|
411
441
|
}
|
|
442
|
+
|
|
443
|
+
if (extname(file) !== '.tar') {
|
|
444
|
+
exitWith(
|
|
445
|
+
1,
|
|
446
|
+
`The file '${opts.file}' does not appear to be a valid Strapi data file. It must have an extension ending in .tar[.gz][.enc]`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
412
449
|
})
|
|
413
450
|
.hook(
|
|
414
451
|
'preAction',
|
|
415
452
|
confirmMessage(
|
|
416
|
-
'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?'
|
|
417
454
|
)
|
|
418
455
|
)
|
|
419
456
|
.action(getLocalScript('transfer/import'));
|
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
createStrapiInstance,
|
|
20
20
|
formatDiagnostic,
|
|
21
21
|
} = require('./utils');
|
|
22
|
+
const { exitWith } = require('../utils/helpers');
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* @typedef ExportCommandOptions Options given to the CLI import command
|
|
@@ -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
|
-
|
|
47
|
-
process.exit(1);
|
|
45
|
+
exitWith(1, 'Could not parse command arguments');
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
const strapi = await createStrapiInstance();
|
|
@@ -92,33 +90,34 @@ module.exports = async (opts) => {
|
|
|
92
90
|
};
|
|
93
91
|
|
|
94
92
|
progress.on('transfer::start', async () => {
|
|
95
|
-
|
|
93
|
+
console.log(`Starting export...`);
|
|
96
94
|
await strapi.telemetry.send('didDEITSProcessStart', getTelemetryPayload());
|
|
97
95
|
});
|
|
98
96
|
|
|
97
|
+
let results;
|
|
98
|
+
let outFile;
|
|
99
99
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const table = buildTransferTable(results.engine);
|
|
104
|
-
logger.log(table.toString());
|
|
105
|
-
|
|
100
|
+
results = await engine.transfer();
|
|
101
|
+
outFile = results.destination.file.path;
|
|
106
102
|
const outFileExists = await fs.pathExists(outFile);
|
|
107
103
|
if (!outFileExists) {
|
|
108
104
|
throw new TransferEngineTransferError(`Export file not created "${outFile}"`);
|
|
109
105
|
}
|
|
110
|
-
|
|
111
|
-
logger.log(`${chalk.bold('Export process has been completed successfully!')}`);
|
|
112
|
-
logger.log(`Export archive is in ${chalk.green(outFile)}`);
|
|
113
106
|
} catch {
|
|
114
107
|
await strapi.telemetry.send('didDEITSProcessFail', getTelemetryPayload());
|
|
115
|
-
|
|
116
|
-
process.exit(1);
|
|
108
|
+
exitWith(1, 'Export process failed.');
|
|
117
109
|
}
|
|
118
110
|
|
|
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
111
|
await strapi.telemetry.send('didDEITSProcessFinish', getTelemetryPayload());
|
|
121
|
-
|
|
112
|
+
try {
|
|
113
|
+
const table = buildTransferTable(results.engine);
|
|
114
|
+
console.log(table.toString());
|
|
115
|
+
} catch (e) {
|
|
116
|
+
console.error('There was an error displaying the results of the transfer.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log(`${chalk.bold('Export process has been completed successfully!')}`);
|
|
120
|
+
exitWith(0, `Export archive is in ${chalk.green(outFile)}`);
|
|
122
121
|
};
|
|
123
122
|
|
|
124
123
|
/**
|
|
@@ -20,18 +20,16 @@ const {
|
|
|
20
20
|
createStrapiInstance,
|
|
21
21
|
formatDiagnostic,
|
|
22
22
|
} = require('./utils');
|
|
23
|
+
const { exitWith } = require('../utils/helpers');
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* @typedef {import('@strapi/data-transfer').ILocalFileSourceProviderOptions} ILocalFileSourceProviderOptions
|
|
26
27
|
*/
|
|
27
28
|
|
|
28
|
-
const logger = console;
|
|
29
|
-
|
|
30
29
|
module.exports = async (opts) => {
|
|
31
30
|
// validate inputs from Commander
|
|
32
31
|
if (!isObject(opts)) {
|
|
33
|
-
|
|
34
|
-
process.exit(1);
|
|
32
|
+
exitWith(1, 'Could not parse arguments');
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
/**
|
|
@@ -100,27 +98,30 @@ module.exports = async (opts) => {
|
|
|
100
98
|
};
|
|
101
99
|
|
|
102
100
|
progress.on('transfer::start', async () => {
|
|
103
|
-
|
|
101
|
+
console.log('Starting import...');
|
|
104
102
|
await strapiInstance.telemetry.send('didDEITSProcessStart', getTelemetryPayload());
|
|
105
103
|
});
|
|
106
104
|
|
|
105
|
+
let results;
|
|
107
106
|
try {
|
|
108
|
-
|
|
109
|
-
const table = buildTransferTable(results.engine);
|
|
110
|
-
logger.info(table.toString());
|
|
111
|
-
|
|
112
|
-
logger.info('Import process has been completed successfully!');
|
|
107
|
+
results = await engine.transfer();
|
|
113
108
|
} catch (e) {
|
|
114
109
|
await strapiInstance.telemetry.send('didDEITSProcessFail', getTelemetryPayload());
|
|
115
|
-
|
|
110
|
+
console.error('Import process failed.');
|
|
116
111
|
process.exit(1);
|
|
117
112
|
}
|
|
118
113
|
|
|
119
|
-
|
|
114
|
+
try {
|
|
115
|
+
const table = buildTransferTable(results.engine);
|
|
116
|
+
console.log(table.toString());
|
|
117
|
+
} catch (e) {
|
|
118
|
+
console.error('There was an error displaying the results of the transfer.');
|
|
119
|
+
}
|
|
120
|
+
|
|
120
121
|
await strapiInstance.telemetry.send('didDEITSProcessFinish', getTelemetryPayload());
|
|
121
122
|
await strapiInstance.destroy();
|
|
122
123
|
|
|
123
|
-
|
|
124
|
+
exitWith(0, 'Import process has been completed successfully!');
|
|
124
125
|
};
|
|
125
126
|
|
|
126
127
|
/**
|
|
@@ -17,28 +17,28 @@ const {
|
|
|
17
17
|
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
18
18
|
formatDiagnostic,
|
|
19
19
|
} = require('./utils');
|
|
20
|
-
|
|
21
|
-
const logger = console;
|
|
20
|
+
const { exitWith } = require('../utils/helpers');
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
23
|
* @typedef TransferCommandOptions Options given to the CLI transfer command
|
|
25
24
|
*
|
|
26
25
|
* @property {URL|undefined} [to] The url of a remote Strapi to use as remote destination
|
|
27
26
|
* @property {URL|undefined} [from] The url of a remote Strapi to use as remote source
|
|
27
|
+
* @property {string|undefined} [toToken] The transfer token for the remote Strapi destination
|
|
28
|
+
* @property {string|undefined} [fromToken] The transfer token for the remote Strapi source
|
|
28
29
|
*/
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Transfer command.
|
|
32
33
|
*
|
|
33
|
-
*
|
|
34
|
+
* Transfers data between local Strapi and remote Strapi instances
|
|
34
35
|
*
|
|
35
36
|
* @param {TransferCommandOptions} opts
|
|
36
37
|
*/
|
|
37
38
|
module.exports = async (opts) => {
|
|
38
39
|
// Validate inputs from Commander
|
|
39
40
|
if (!isObject(opts)) {
|
|
40
|
-
|
|
41
|
-
process.exit(1);
|
|
41
|
+
exitWith(1, 'Could not parse command arguments');
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const strapi = await createStrapiInstance();
|
|
@@ -47,8 +47,7 @@ module.exports = async (opts) => {
|
|
|
47
47
|
let destination;
|
|
48
48
|
|
|
49
49
|
if (!opts.from && !opts.to) {
|
|
50
|
-
|
|
51
|
-
process.exit(1);
|
|
50
|
+
exitWith(1, 'At least one source (from) or destination (to) option must be provided');
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
// if no URL provided, use local Strapi
|
|
@@ -59,8 +58,7 @@ module.exports = async (opts) => {
|
|
|
59
58
|
}
|
|
60
59
|
// if URL provided, set up a remote source provider
|
|
61
60
|
else {
|
|
62
|
-
|
|
63
|
-
process.exit(1);
|
|
61
|
+
exitWith(1, `Remote Strapi source provider not yet implemented`);
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
// if no URL provided, use local Strapi
|
|
@@ -71,9 +69,16 @@ module.exports = async (opts) => {
|
|
|
71
69
|
}
|
|
72
70
|
// if URL provided, set up a remote destination provider
|
|
73
71
|
else {
|
|
72
|
+
if (!opts.toToken) {
|
|
73
|
+
exitWith(1, 'Missing token for remote destination');
|
|
74
|
+
}
|
|
75
|
+
|
|
74
76
|
destination = createRemoteStrapiDestinationProvider({
|
|
75
77
|
url: opts.to,
|
|
76
|
-
auth:
|
|
78
|
+
auth: {
|
|
79
|
+
type: 'token',
|
|
80
|
+
token: opts.toToken,
|
|
81
|
+
},
|
|
77
82
|
strategy: 'restore',
|
|
78
83
|
restore: {
|
|
79
84
|
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
|
@@ -82,12 +87,11 @@ module.exports = async (opts) => {
|
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
if (!source || !destination) {
|
|
85
|
-
|
|
86
|
-
process.exit(1);
|
|
90
|
+
exitWith(1, 'Could not create providers');
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
const engine = createTransferEngine(source, destination, {
|
|
90
|
-
versionStrategy: '
|
|
94
|
+
versionStrategy: 'exact',
|
|
91
95
|
schemaStrategy: 'strict',
|
|
92
96
|
transforms: {
|
|
93
97
|
links: [
|
|
@@ -112,18 +116,15 @@ module.exports = async (opts) => {
|
|
|
112
116
|
|
|
113
117
|
engine.diagnostics.onDiagnostic(formatDiagnostic('transfer'));
|
|
114
118
|
|
|
119
|
+
let results;
|
|
115
120
|
try {
|
|
116
|
-
|
|
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);
|
|
121
|
+
console.log(`Starting transfer...`);
|
|
122
|
+
results = await engine.transfer();
|
|
125
123
|
} catch (e) {
|
|
126
|
-
|
|
127
|
-
process.exit(1);
|
|
124
|
+
exitWith(1, 'Transfer process failed.');
|
|
128
125
|
}
|
|
126
|
+
|
|
127
|
+
const table = buildTransferTable(results.engine);
|
|
128
|
+
console.log(table.toString());
|
|
129
|
+
exitWith(0, `${chalk.bold('Transfer process has been completed successfully!')}`);
|
|
129
130
|
};
|
|
@@ -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 =
|
|
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
|
-
|
|
49
|
+
logger.log(chalk.green(message));
|
|
44
50
|
} else {
|
|
45
|
-
|
|
51
|
+
logger.error(chalk.red(message));
|
|
46
52
|
}
|
|
47
53
|
};
|
|
48
54
|
|
|
49
55
|
if (isString(message)) {
|
|
50
|
-
|
|
56
|
+
log(message);
|
|
51
57
|
} else if (isArray(message)) {
|
|
52
|
-
message.forEach((msg) =>
|
|
58
|
+
message.forEach((msg) => log(msg));
|
|
53
59
|
}
|
|
54
|
-
|
|
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
|
|
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);
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const { has, prop, omit, toString, pipe, assign } = require('lodash/fp');
|
|
5
5
|
|
|
6
|
-
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
|
|
6
|
+
const { contentTypes: contentTypesUtils, mapAsync } = require('@strapi/utils');
|
|
7
7
|
const { ApplicationError } = require('@strapi/utils').errors;
|
|
8
8
|
const { getComponentAttributes } = require('@strapi/utils').contentTypes;
|
|
9
9
|
|
|
10
|
+
const isDialectMySQL = () => strapi.db.dialect.client === 'mysql';
|
|
11
|
+
|
|
10
12
|
const omitComponentData = (contentType, data) => {
|
|
11
13
|
const { attributes } = contentType;
|
|
12
14
|
const componentAttributes = Object.keys(attributes).filter((attributeName) =>
|
|
@@ -43,10 +45,12 @@ const createComponents = async (uid, data) => {
|
|
|
43
45
|
throw new Error('Expected an array to create repeatable component');
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
49
|
+
const components = await mapAsync(
|
|
50
|
+
componentValue,
|
|
51
|
+
(value) => createComponent(componentUID, value),
|
|
52
|
+
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
|
53
|
+
);
|
|
50
54
|
|
|
51
55
|
componentBody[attributeName] = components.map(({ id }) => {
|
|
52
56
|
return {
|
|
@@ -78,19 +82,23 @@ const createComponents = async (uid, data) => {
|
|
|
78
82
|
throw new Error('Expected an array to create repeatable component');
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
const
|
|
82
|
-
for (const value of dynamiczoneValues) {
|
|
85
|
+
const createDynamicZoneComponents = async (value) => {
|
|
83
86
|
const { id } = await createComponent(value.__component, value);
|
|
84
|
-
|
|
87
|
+
return {
|
|
85
88
|
id,
|
|
86
89
|
__component: value.__component,
|
|
87
90
|
__pivot: {
|
|
88
91
|
field: attributeName,
|
|
89
92
|
},
|
|
90
|
-
}
|
|
91
|
-
}
|
|
93
|
+
};
|
|
94
|
+
};
|
|
92
95
|
|
|
93
|
-
|
|
96
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
97
|
+
componentBody[attributeName] = await mapAsync(
|
|
98
|
+
dynamiczoneValues,
|
|
99
|
+
createDynamicZoneComponents,
|
|
100
|
+
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
|
101
|
+
);
|
|
94
102
|
|
|
95
103
|
continue;
|
|
96
104
|
}
|
|
@@ -139,10 +147,12 @@ const updateComponents = async (uid, entityToUpdate, data) => {
|
|
|
139
147
|
throw new Error('Expected an array to create repeatable component');
|
|
140
148
|
}
|
|
141
149
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
150
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
151
|
+
const components = await mapAsync(
|
|
152
|
+
componentValue,
|
|
153
|
+
(value) => updateOrCreateComponent(componentUID, value),
|
|
154
|
+
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
|
155
|
+
);
|
|
146
156
|
|
|
147
157
|
componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }) => {
|
|
148
158
|
return {
|
|
@@ -176,19 +186,22 @@ const updateComponents = async (uid, entityToUpdate, data) => {
|
|
|
176
186
|
throw new Error('Expected an array to create repeatable component');
|
|
177
187
|
}
|
|
178
188
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
id,
|
|
184
|
-
__component: value.__component,
|
|
185
|
-
__pivot: {
|
|
186
|
-
field: attributeName,
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
}
|
|
189
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
190
|
+
componentBody[attributeName] = await mapAsync(
|
|
191
|
+
dynamiczoneValues,
|
|
192
|
+
async (value) => {
|
|
193
|
+
const { id } = await updateOrCreateComponent(value.__component, value);
|
|
190
194
|
|
|
191
|
-
|
|
195
|
+
return {
|
|
196
|
+
id,
|
|
197
|
+
__component: value.__component,
|
|
198
|
+
__pivot: {
|
|
199
|
+
field: attributeName,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
|
204
|
+
);
|
|
192
205
|
|
|
193
206
|
continue;
|
|
194
207
|
}
|
|
@@ -290,14 +303,18 @@ const deleteComponents = async (uid, entityToDelete, { loadComponents = true } =
|
|
|
290
303
|
|
|
291
304
|
if (attribute.type === 'component') {
|
|
292
305
|
const { component: componentUID } = attribute;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
306
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
307
|
+
await mapAsync(_.castArray(value), (subValue) => deleteComponent(componentUID, subValue), {
|
|
308
|
+
concurrency: isDialectMySQL() ? 1 : Infinity,
|
|
309
|
+
});
|
|
296
310
|
} else {
|
|
297
311
|
// delete dynamic zone components
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
312
|
+
// MySQL/MariaDB can cause deadlocks here if concurrency higher than 1
|
|
313
|
+
await mapAsync(
|
|
314
|
+
_.castArray(value),
|
|
315
|
+
(subValue) => deleteComponent(subValue.__component, subValue),
|
|
316
|
+
{ concurrency: isDialectMySQL() ? 1 : Infinity }
|
|
317
|
+
);
|
|
301
318
|
}
|
|
302
319
|
|
|
303
320
|
continue;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const _ = require('lodash');
|
|
6
|
+
const { map, values, sumBy, pipe, flatMap, propEq } = require('lodash/fp');
|
|
6
7
|
const isDocker = require('is-docker');
|
|
7
8
|
const fetch = require('node-fetch');
|
|
8
9
|
const ciEnv = require('ci-info');
|
|
@@ -40,6 +41,14 @@ module.exports = (strapi) => {
|
|
|
40
41
|
const serverRootPath = strapi.dirs.app.root;
|
|
41
42
|
const adminRootPath = path.join(strapi.dirs.app.root, 'src', 'admin');
|
|
42
43
|
|
|
44
|
+
const getNumberOfDynamicZones = () => {
|
|
45
|
+
return pipe(
|
|
46
|
+
map('attributes'),
|
|
47
|
+
flatMap(values),
|
|
48
|
+
sumBy(propEq('type', 'dynamiczone'))
|
|
49
|
+
)(strapi.contentTypes);
|
|
50
|
+
};
|
|
51
|
+
|
|
43
52
|
const anonymousUserProperties = {
|
|
44
53
|
environment: strapi.config.environment,
|
|
45
54
|
os: os.type(),
|
|
@@ -57,6 +66,9 @@ module.exports = (strapi) => {
|
|
|
57
66
|
useTypescriptOnAdmin: isUsingTypeScriptSync(adminRootPath),
|
|
58
67
|
projectId: uuid,
|
|
59
68
|
isHostedOnStrapiCloud: env('STRAPI_HOSTING', null) === 'strapi.cloud',
|
|
69
|
+
numberOfAllContentTypes: _.size(strapi.contentTypes), // TODO: V5: This event should be renamed numberOfContentTypes in V5 as the name is already taken to describe the number of content types using i18n.
|
|
70
|
+
numberOfComponents: _.size(strapi.components),
|
|
71
|
+
numberOfDynamicZones: getNumberOfDynamicZones(),
|
|
60
72
|
};
|
|
61
73
|
|
|
62
74
|
addPackageJsonStrapiMetadata(anonymousGroupProperties, strapi);
|
|
@@ -30,7 +30,10 @@ export type ConfigurableAttribute = { configurable: true };
|
|
|
30
30
|
export type NonConfigurableAttribute = { configurable: false };
|
|
31
31
|
|
|
32
32
|
// custom field
|
|
33
|
-
export type CustomField<T extends string, P extends object = undefined> = {
|
|
33
|
+
export type CustomField<T extends string, P extends object = undefined> = {
|
|
34
|
+
customField: T;
|
|
35
|
+
options?: P;
|
|
36
|
+
};
|
|
34
37
|
|
|
35
38
|
// min/max
|
|
36
39
|
export type SetMinMax<T extends MinMaxOption<U>, U = number> = T;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/strapi",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.0-beta.0",
|
|
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.
|
|
85
|
-
"@strapi/data-transfer": "4.
|
|
86
|
-
"@strapi/database": "4.
|
|
87
|
-
"@strapi/generate-new": "4.
|
|
88
|
-
"@strapi/generators": "4.
|
|
89
|
-
"@strapi/logger": "4.
|
|
90
|
-
"@strapi/permissions": "4.
|
|
91
|
-
"@strapi/plugin-content-manager": "4.
|
|
92
|
-
"@strapi/plugin-content-type-builder": "4.
|
|
93
|
-
"@strapi/plugin-email": "4.
|
|
94
|
-
"@strapi/plugin-upload": "4.
|
|
95
|
-
"@strapi/typescript-utils": "4.
|
|
96
|
-
"@strapi/utils": "4.
|
|
84
|
+
"@strapi/admin": "4.7.0-beta.0",
|
|
85
|
+
"@strapi/data-transfer": "4.7.0-beta.0",
|
|
86
|
+
"@strapi/database": "4.7.0-beta.0",
|
|
87
|
+
"@strapi/generate-new": "4.7.0-beta.0",
|
|
88
|
+
"@strapi/generators": "4.7.0-beta.0",
|
|
89
|
+
"@strapi/logger": "4.7.0-beta.0",
|
|
90
|
+
"@strapi/permissions": "4.7.0-beta.0",
|
|
91
|
+
"@strapi/plugin-content-manager": "4.7.0-beta.0",
|
|
92
|
+
"@strapi/plugin-content-type-builder": "4.7.0-beta.0",
|
|
93
|
+
"@strapi/plugin-email": "4.7.0-beta.0",
|
|
94
|
+
"@strapi/plugin-upload": "4.7.0-beta.0",
|
|
95
|
+
"@strapi/typescript-utils": "4.7.0-beta.0",
|
|
96
|
+
"@strapi/utils": "4.7.0-beta.0",
|
|
97
97
|
"bcryptjs": "2.4.3",
|
|
98
98
|
"boxen": "5.1.2",
|
|
99
99
|
"chalk": "4.1.2",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"fs-extra": "10.0.0",
|
|
110
110
|
"glob": "7.2.0",
|
|
111
111
|
"http-errors": "1.8.1",
|
|
112
|
-
"inquirer": "8.2.
|
|
112
|
+
"inquirer": "8.2.5",
|
|
113
113
|
"is-docker": "2.2.1",
|
|
114
114
|
"koa": "2.13.4",
|
|
115
115
|
"koa-body": "4.2.0",
|
|
@@ -142,5 +142,5 @@
|
|
|
142
142
|
"node": ">=14.19.1 <=18.x.x",
|
|
143
143
|
"npm": ">=6.0.0"
|
|
144
144
|
},
|
|
145
|
-
"gitHead": "
|
|
145
|
+
"gitHead": "880ba7af867ad43c4cc45b47467b76a9b5606b6e"
|
|
146
146
|
}
|