@strapi/strapi 4.10.0-beta.0 → 4.10.0-beta.1
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/.eslintignore +2 -0
- package/.eslintrc.js +4 -0
- package/bin/strapi.js +34 -21
- package/ee/index.js +39 -4
- package/ee/license.js +3 -1
- package/lib/Strapi.js +11 -7
- package/lib/commands/report.js +35 -0
- package/lib/commands/transfer/export.js +24 -17
- package/lib/commands/transfer/import.js +37 -16
- package/lib/commands/transfer/transfer.js +59 -12
- package/lib/commands/transfer/utils.js +53 -4
- package/lib/commands/utils/commander.js +14 -0
- package/lib/core/domain/content-type/validator.js +1 -1
- package/lib/core/loaders/plugins/index.js +7 -2
- package/lib/core/registries/policies.d.ts +3 -3
- package/lib/core-api/controller/collection-type.js +0 -1
- package/lib/core-api/controller/index.js +1 -1
- package/lib/factories.js +3 -4
- package/lib/middlewares/logger.js +1 -17
- package/lib/middlewares/security.js +1 -1
- package/lib/services/event-hub.js +6 -1
- package/lib/services/metrics/index.js +1 -0
- package/lib/services/metrics/sender.js +0 -12
- package/lib/services/server/index.js +2 -2
- package/lib/services/server/register-routes.js +1 -1
- package/lib/services/utils/dynamic-zones.js +13 -0
- package/package.json +20 -20
package/.eslintignore
ADDED
package/.eslintrc.js
ADDED
package/bin/strapi.js
CHANGED
|
@@ -24,13 +24,10 @@ const { exitWith, ifOptions, assertUrlHasProtocol } = require('../lib/commands/u
|
|
|
24
24
|
const {
|
|
25
25
|
excludeOption,
|
|
26
26
|
onlyOption,
|
|
27
|
+
throttleOption,
|
|
27
28
|
validateExcludeOnly,
|
|
28
29
|
} = require('../lib/commands/transfer/utils');
|
|
29
30
|
|
|
30
|
-
process.on('SIGINT', () => {
|
|
31
|
-
process.exit();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
31
|
const checkCwdIsStrapiApp = (name) => {
|
|
35
32
|
const logErrorAndExit = () => {
|
|
36
33
|
console.log(
|
|
@@ -261,6 +258,14 @@ program
|
|
|
261
258
|
.description('Enable anonymous telemetry and metadata sending to Strapi analytics')
|
|
262
259
|
.action(getLocalScript('opt-in-telemetry'));
|
|
263
260
|
|
|
261
|
+
program
|
|
262
|
+
.command('report')
|
|
263
|
+
.description('Get system stats for debugging and submitting issues')
|
|
264
|
+
.option('-u, --uuid', 'Include Project UUID')
|
|
265
|
+
.option('-d, --dependencies', 'Include Project Dependencies')
|
|
266
|
+
.option('--all', 'Include All Information')
|
|
267
|
+
.action(getLocalScript('report'));
|
|
268
|
+
|
|
264
269
|
program
|
|
265
270
|
.command('ts:generate-types')
|
|
266
271
|
.description(`Generate TypeScript typings for your schemas`)
|
|
@@ -279,13 +284,12 @@ program
|
|
|
279
284
|
.description('Transfer data from one source to another')
|
|
280
285
|
.allowExcessArguments(false)
|
|
281
286
|
.addOption(
|
|
282
|
-
new Option(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
.addOption(
|
|
287
|
-
new Option('--from-token <token>', `Transfer token for the remote Strapi source`).hideHelp() // Hidden until pull feature is released
|
|
287
|
+
new Option(
|
|
288
|
+
'--from <sourceURL>',
|
|
289
|
+
`URL of the remote Strapi instance to get data from`
|
|
290
|
+
).argParser(parseURL)
|
|
288
291
|
)
|
|
292
|
+
.addOption(new Option('--from-token <token>', `Transfer token for the remote Strapi source`))
|
|
289
293
|
.addOption(
|
|
290
294
|
new Option(
|
|
291
295
|
'--to <destinationURL>',
|
|
@@ -296,7 +300,16 @@ program
|
|
|
296
300
|
.addOption(forceOption)
|
|
297
301
|
.addOption(excludeOption)
|
|
298
302
|
.addOption(onlyOption)
|
|
303
|
+
.addOption(throttleOption)
|
|
299
304
|
.hook('preAction', validateExcludeOnly)
|
|
305
|
+
.hook(
|
|
306
|
+
'preAction',
|
|
307
|
+
ifOptions(
|
|
308
|
+
(opts) => !(opts.from || opts.to) || (opts.from && opts.to),
|
|
309
|
+
() =>
|
|
310
|
+
exitWith(1, 'Exactly one remote source (from) or destination (to) option must be provided')
|
|
311
|
+
)
|
|
312
|
+
)
|
|
300
313
|
// If --from is used, validate the URL and token
|
|
301
314
|
.hook(
|
|
302
315
|
'preAction',
|
|
@@ -313,10 +326,15 @@ program
|
|
|
313
326
|
},
|
|
314
327
|
]);
|
|
315
328
|
if (!answers.fromToken?.length) {
|
|
316
|
-
exitWith(1, 'No token
|
|
329
|
+
exitWith(1, 'No token provided for remote source, aborting transfer.');
|
|
317
330
|
}
|
|
318
331
|
thisCommand.opts().fromToken = answers.fromToken;
|
|
319
332
|
}
|
|
333
|
+
|
|
334
|
+
await confirmMessage(
|
|
335
|
+
'The transfer will delete all the local Strapi assets and its database. Are you sure you want to proceed?',
|
|
336
|
+
{ failMessage: 'Transfer process aborted' }
|
|
337
|
+
)(thisCommand);
|
|
320
338
|
}
|
|
321
339
|
)
|
|
322
340
|
)
|
|
@@ -336,25 +354,18 @@ program
|
|
|
336
354
|
},
|
|
337
355
|
]);
|
|
338
356
|
if (!answers.toToken?.length) {
|
|
339
|
-
exitWith(1, 'No token
|
|
357
|
+
exitWith(1, 'No token provided for remote destination, aborting transfer.');
|
|
340
358
|
}
|
|
341
359
|
thisCommand.opts().toToken = answers.toToken;
|
|
342
360
|
}
|
|
343
361
|
|
|
344
362
|
await confirmMessage(
|
|
345
|
-
'The transfer will delete all
|
|
363
|
+
'The transfer will delete all the remote Strapi assets and its database. Are you sure you want to proceed?',
|
|
346
364
|
{ failMessage: 'Transfer process aborted' }
|
|
347
365
|
)(thisCommand);
|
|
348
366
|
}
|
|
349
367
|
)
|
|
350
368
|
)
|
|
351
|
-
// .hook(
|
|
352
|
-
// 'preAction',
|
|
353
|
-
// ifOptions(
|
|
354
|
-
// (opts) => !opts.from && !opts.to,
|
|
355
|
-
// () => exitWith(1, 'At least one source (from) or destination (to) option must be provided')
|
|
356
|
-
// )
|
|
357
|
-
// )
|
|
358
369
|
.action(getLocalScript('transfer/transfer'));
|
|
359
370
|
|
|
360
371
|
// `$ strapi export`
|
|
@@ -375,6 +386,7 @@ program
|
|
|
375
386
|
.addOption(new Option('-f, --file <file>', 'name to use for exported file (without extensions)'))
|
|
376
387
|
.addOption(excludeOption)
|
|
377
388
|
.addOption(onlyOption)
|
|
389
|
+
.addOption(throttleOption)
|
|
378
390
|
.hook('preAction', validateExcludeOnly)
|
|
379
391
|
.hook('preAction', promptEncryptionKey)
|
|
380
392
|
.action(getLocalScript('transfer/export'));
|
|
@@ -397,6 +409,7 @@ program
|
|
|
397
409
|
.addOption(forceOption)
|
|
398
410
|
.addOption(excludeOption)
|
|
399
411
|
.addOption(onlyOption)
|
|
412
|
+
.addOption(throttleOption)
|
|
400
413
|
.hook('preAction', validateExcludeOnly)
|
|
401
414
|
.hook('preAction', async (thisCommand) => {
|
|
402
415
|
const opts = thisCommand.opts();
|
|
@@ -451,7 +464,7 @@ program
|
|
|
451
464
|
.hook(
|
|
452
465
|
'preAction',
|
|
453
466
|
confirmMessage(
|
|
454
|
-
'The import will delete all data in your database
|
|
467
|
+
'The import will delete all assets and data in your database. Are you sure you want to proceed?',
|
|
455
468
|
{ failMessage: 'Import process aborted' }
|
|
456
469
|
)
|
|
457
470
|
)
|
package/ee/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { pick } = require('lodash/fp');
|
|
3
|
+
const { pick, isEqual } = require('lodash/fp');
|
|
4
4
|
|
|
5
5
|
const { readLicense, verifyLicense, fetchLicense, LicenseCheckError } = require('./license');
|
|
6
6
|
const { shiftCronExpression } = require('../lib/utils/cron');
|
|
@@ -13,10 +13,31 @@ const ee = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const disable = (message) => {
|
|
16
|
+
// Prevent emitting ee.disable if it was already disabled
|
|
17
|
+
const shouldEmitEvent = ee.enabled !== false;
|
|
18
|
+
|
|
16
19
|
ee.logger?.warn(`${message} Switching to CE.`);
|
|
17
20
|
// Only keep the license key for potential re-enabling during a later check
|
|
18
21
|
ee.licenseInfo = pick('licenseKey', ee.licenseInfo);
|
|
22
|
+
|
|
19
23
|
ee.enabled = false;
|
|
24
|
+
|
|
25
|
+
if (shouldEmitEvent) {
|
|
26
|
+
// Notify EE features that they should be disabled
|
|
27
|
+
strapi.eventHub.emit('ee.disable');
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const enable = () => {
|
|
32
|
+
// Prevent emitting ee.enable if it was already enabled
|
|
33
|
+
const shouldEmitEvent = ee.enabled !== true;
|
|
34
|
+
|
|
35
|
+
ee.enabled = true;
|
|
36
|
+
|
|
37
|
+
if (shouldEmitEvent) {
|
|
38
|
+
// Notify EE features that they should be disabled
|
|
39
|
+
strapi.eventHub.emit('ee.enable');
|
|
40
|
+
}
|
|
20
41
|
};
|
|
21
42
|
|
|
22
43
|
let initialized = false;
|
|
@@ -41,7 +62,7 @@ const init = (licenseDir, logger) => {
|
|
|
41
62
|
|
|
42
63
|
if (license) {
|
|
43
64
|
ee.licenseInfo = verifyLicense(license);
|
|
44
|
-
|
|
65
|
+
enable();
|
|
45
66
|
}
|
|
46
67
|
} catch (error) {
|
|
47
68
|
disable(error.message);
|
|
@@ -89,8 +110,22 @@ const onlineUpdate = async ({ strapi }) => {
|
|
|
89
110
|
|
|
90
111
|
if (license) {
|
|
91
112
|
try {
|
|
92
|
-
|
|
113
|
+
// Verify license and check if its info changed
|
|
114
|
+
const newLicenseInfo = verifyLicense(license);
|
|
115
|
+
const licenseInfoChanged =
|
|
116
|
+
!isEqual(newLicenseInfo.features, ee.licenseInfo.features) ||
|
|
117
|
+
newLicenseInfo.seats !== ee.licenseInfo.seats ||
|
|
118
|
+
newLicenseInfo.type !== ee.licenseInfo.type;
|
|
119
|
+
|
|
120
|
+
// Store the new license info
|
|
121
|
+
ee.licenseInfo = newLicenseInfo;
|
|
122
|
+
const wasEnabled = ee.enabled;
|
|
93
123
|
validateInfo();
|
|
124
|
+
|
|
125
|
+
// Notify EE features
|
|
126
|
+
if (licenseInfoChanged && wasEnabled) {
|
|
127
|
+
strapi.eventHub.emit('ee.update');
|
|
128
|
+
}
|
|
94
129
|
} catch (error) {
|
|
95
130
|
disable(error.message);
|
|
96
131
|
}
|
|
@@ -125,7 +160,7 @@ const validateInfo = () => {
|
|
|
125
160
|
return disable('License expired.');
|
|
126
161
|
}
|
|
127
162
|
|
|
128
|
-
|
|
163
|
+
enable();
|
|
129
164
|
};
|
|
130
165
|
|
|
131
166
|
const checkLicense = async ({ strapi }) => {
|
package/ee/license.js
CHANGED
|
@@ -10,7 +10,9 @@ const machineId = require('../lib/utils/machine-id');
|
|
|
10
10
|
const DEFAULT_FEATURES = {
|
|
11
11
|
bronze: [],
|
|
12
12
|
silver: [],
|
|
13
|
-
|
|
13
|
+
// Set a null retention duration to allow the user to override it
|
|
14
|
+
// The default of 90 days is set in the audit logs service
|
|
15
|
+
gold: ['sso', { name: 'audit-logs', options: { retentionDays: null } }, 'review-workflows'],
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
const publicKey = fs.readFileSync(join(__dirname, 'resources/key.pub'));
|
package/lib/Strapi.js
CHANGED
|
@@ -43,6 +43,7 @@ const apisRegistry = require('./core/registries/apis');
|
|
|
43
43
|
const bootstrap = require('./core/bootstrap');
|
|
44
44
|
const loaders = require('./core/loaders');
|
|
45
45
|
const { destroyOnSignal } = require('./utils/signals');
|
|
46
|
+
const getNumberOfDynamicZones = require('./services/utils/dynamic-zones');
|
|
46
47
|
const sanitizersRegistry = require('./core/registries/sanitizers');
|
|
47
48
|
const convertCustomFieldType = require('./utils/convert-custom-field-type');
|
|
48
49
|
|
|
@@ -249,6 +250,9 @@ class Strapi {
|
|
|
249
250
|
groupProperties: {
|
|
250
251
|
database: strapi.config.get('database.connection.client'),
|
|
251
252
|
plugins: Object.keys(strapi.plugins),
|
|
253
|
+
numberOfAllContentTypes: _.size(this.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.
|
|
254
|
+
numberOfComponents: _.size(this.components),
|
|
255
|
+
numberOfDynamicZones: getNumberOfDynamicZones(),
|
|
252
256
|
// TODO: to add back
|
|
253
257
|
// providers: this.config.installedProviders,
|
|
254
258
|
},
|
|
@@ -468,7 +472,7 @@ class Strapi {
|
|
|
468
472
|
await this.startWebhooks();
|
|
469
473
|
|
|
470
474
|
await this.server.initMiddlewares();
|
|
471
|
-
|
|
475
|
+
this.server.initRouting();
|
|
472
476
|
|
|
473
477
|
await this.contentAPI.permissions.registerActions();
|
|
474
478
|
|
|
@@ -536,17 +540,17 @@ class Strapi {
|
|
|
536
540
|
// plugins
|
|
537
541
|
await this.container.get('modules')[lifecycleName]();
|
|
538
542
|
|
|
539
|
-
// user
|
|
540
|
-
const userLifecycleFunction = this.app && this.app[lifecycleName];
|
|
541
|
-
if (isFunction(userLifecycleFunction)) {
|
|
542
|
-
await userLifecycleFunction({ strapi: this });
|
|
543
|
-
}
|
|
544
|
-
|
|
545
543
|
// admin
|
|
546
544
|
const adminLifecycleFunction = this.admin && this.admin[lifecycleName];
|
|
547
545
|
if (isFunction(adminLifecycleFunction)) {
|
|
548
546
|
await adminLifecycleFunction({ strapi: this });
|
|
549
547
|
}
|
|
548
|
+
|
|
549
|
+
// user
|
|
550
|
+
const userLifecycleFunction = this.app && this.app[lifecycleName];
|
|
551
|
+
if (isFunction(userLifecycleFunction)) {
|
|
552
|
+
await userLifecycleFunction({ strapi: this });
|
|
553
|
+
}
|
|
550
554
|
}
|
|
551
555
|
|
|
552
556
|
getModel(uid) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { EOL } = require('os');
|
|
4
|
+
const strapi = require('../index');
|
|
5
|
+
|
|
6
|
+
module.exports = async ({ uuid, dependencies, all }) => {
|
|
7
|
+
const config = {
|
|
8
|
+
reportUUID: Boolean(all || uuid),
|
|
9
|
+
reportDependencies: Boolean(all || dependencies),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const appContext = await strapi.compile();
|
|
13
|
+
const app = await strapi(appContext).register();
|
|
14
|
+
|
|
15
|
+
let debugInfo = `Launched In: ${Date.now() - app.config.launchedAt} ms
|
|
16
|
+
Environment: ${app.config.environment}
|
|
17
|
+
OS: ${process.platform}-${process.arch}
|
|
18
|
+
Strapi Version: ${app.config.info.strapi}
|
|
19
|
+
Node/Yarn Version: ${process.env.npm_config_user_agent}
|
|
20
|
+
Edition: ${app.EE ? 'Enterprise' : 'Community'}
|
|
21
|
+
Database: ${app?.config?.database?.connection?.client ?? 'unknown'}`;
|
|
22
|
+
|
|
23
|
+
if (config.reportUUID) {
|
|
24
|
+
debugInfo += `${EOL}UUID: ${app.config.uuid}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (config.reportDependencies) {
|
|
28
|
+
debugInfo += `${EOL}Dependencies: ${JSON.stringify(app.config.info.dependencies, null, 2)}
|
|
29
|
+
Dev Dependencies: ${JSON.stringify(app.config.info.devDependencies, null, 2)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(debugInfo);
|
|
33
|
+
|
|
34
|
+
await app.destroy();
|
|
35
|
+
};
|
|
@@ -22,15 +22,21 @@ const {
|
|
|
22
22
|
createStrapiInstance,
|
|
23
23
|
formatDiagnostic,
|
|
24
24
|
loadersFactory,
|
|
25
|
+
exitMessageText,
|
|
26
|
+
abortTransfer,
|
|
27
|
+
getTransferTelemetryPayload,
|
|
25
28
|
} = require('./utils');
|
|
26
29
|
const { exitWith } = require('../utils/helpers');
|
|
27
30
|
/**
|
|
28
31
|
* @typedef ExportCommandOptions Options given to the CLI import command
|
|
29
32
|
*
|
|
30
|
-
* @property {string} [file] The file path to
|
|
33
|
+
* @property {string} [file] The file path to export to
|
|
31
34
|
* @property {boolean} [encrypt] Used to encrypt the final archive
|
|
32
|
-
* @property {string} [key] Encryption key, only
|
|
35
|
+
* @property {string} [key] Encryption key, used only when encryption is enabled
|
|
33
36
|
* @property {boolean} [compress] Used to compress the final archive
|
|
37
|
+
* @property {(keyof import('@strapi/data-transfer/src/engine').TransferGroupFilter)[]} [only] If present, only include these filtered groups of data
|
|
38
|
+
* @property {(keyof import('@strapi/data-transfer/src/engine').TransferGroupFilter)[]} [exclude] If present, exclude these filtered groups of data
|
|
39
|
+
* @property {number|undefined} [throttle] Delay in ms after each record
|
|
34
40
|
*/
|
|
35
41
|
|
|
36
42
|
const BYTES_IN_MB = 1024 * 1024;
|
|
@@ -58,6 +64,7 @@ module.exports = async (opts) => {
|
|
|
58
64
|
schemaStrategy: 'ignore', // for an export to file, schemaStrategy will always be skipped
|
|
59
65
|
exclude: opts.exclude,
|
|
60
66
|
only: opts.only,
|
|
67
|
+
throttle: opts.throttle,
|
|
61
68
|
transforms: {
|
|
62
69
|
links: [
|
|
63
70
|
{
|
|
@@ -97,23 +104,21 @@ module.exports = async (opts) => {
|
|
|
97
104
|
updateLoader(stage, data);
|
|
98
105
|
});
|
|
99
106
|
|
|
100
|
-
const getTelemetryPayload = (/* payload */) => {
|
|
101
|
-
return {
|
|
102
|
-
eventProperties: {
|
|
103
|
-
source: engine.sourceProvider.name,
|
|
104
|
-
destination: engine.destinationProvider.name,
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
};
|
|
108
|
-
|
|
109
107
|
progress.on('transfer::start', async () => {
|
|
110
108
|
console.log(`Starting export...`);
|
|
111
|
-
|
|
109
|
+
|
|
110
|
+
await strapi.telemetry.send('didDEITSProcessStart', getTransferTelemetryPayload(engine));
|
|
112
111
|
});
|
|
113
112
|
|
|
114
113
|
let results;
|
|
115
114
|
let outFile;
|
|
116
115
|
try {
|
|
116
|
+
// Abort transfer if user interrupts process
|
|
117
|
+
['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => {
|
|
118
|
+
process.removeAllListeners(signal);
|
|
119
|
+
process.on(signal, () => abortTransfer({ engine, strapi }));
|
|
120
|
+
});
|
|
121
|
+
|
|
117
122
|
results = await engine.transfer();
|
|
118
123
|
outFile = results.destination.file.path;
|
|
119
124
|
const outFileExists = await fs.pathExists(outFile);
|
|
@@ -121,11 +126,13 @@ module.exports = async (opts) => {
|
|
|
121
126
|
throw new TransferEngineTransferError(`Export file not created "${outFile}"`);
|
|
122
127
|
}
|
|
123
128
|
} catch {
|
|
124
|
-
await strapi.telemetry.send('didDEITSProcessFail',
|
|
125
|
-
exitWith(1, '
|
|
129
|
+
await strapi.telemetry.send('didDEITSProcessFail', getTransferTelemetryPayload(engine));
|
|
130
|
+
exitWith(1, exitMessageText('export', true));
|
|
126
131
|
}
|
|
127
132
|
|
|
128
|
-
await
|
|
133
|
+
// Note: we need to await telemetry or else the process ends before it is sent
|
|
134
|
+
await strapi.telemetry.send('didDEITSProcessFinish', getTransferTelemetryPayload(engine));
|
|
135
|
+
|
|
129
136
|
try {
|
|
130
137
|
const table = buildTransferTable(results.engine);
|
|
131
138
|
console.log(table.toString());
|
|
@@ -133,8 +140,8 @@ module.exports = async (opts) => {
|
|
|
133
140
|
console.error('There was an error displaying the results of the transfer.');
|
|
134
141
|
}
|
|
135
142
|
|
|
136
|
-
console.log(
|
|
137
|
-
exitWith(0,
|
|
143
|
+
console.log(`Export archive is in ${chalk.green(outFile)}`);
|
|
144
|
+
exitWith(0, exitMessageText('export'));
|
|
138
145
|
};
|
|
139
146
|
|
|
140
147
|
/**
|
|
@@ -18,13 +18,33 @@ const {
|
|
|
18
18
|
createStrapiInstance,
|
|
19
19
|
formatDiagnostic,
|
|
20
20
|
loadersFactory,
|
|
21
|
+
exitMessageText,
|
|
22
|
+
abortTransfer,
|
|
23
|
+
getTransferTelemetryPayload,
|
|
21
24
|
} = require('./utils');
|
|
22
25
|
const { exitWith } = require('../utils/helpers');
|
|
23
26
|
|
|
24
27
|
/**
|
|
25
|
-
* @typedef {import('@strapi/data-transfer').ILocalFileSourceProviderOptions} ILocalFileSourceProviderOptions
|
|
28
|
+
* @typedef {import('@strapi/data-transfer/src/file/providers').ILocalFileSourceProviderOptions} ILocalFileSourceProviderOptions
|
|
26
29
|
*/
|
|
27
30
|
|
|
31
|
+
/**
|
|
32
|
+
* @typedef ImportCommandOptions Options given to the CLI import command
|
|
33
|
+
*
|
|
34
|
+
* @property {string} [file] The file path to import
|
|
35
|
+
* @property {string} [key] Encryption key, used when encryption is enabled
|
|
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
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Import command.
|
|
43
|
+
*
|
|
44
|
+
* It transfers data from a file to a local Strapi instance
|
|
45
|
+
*
|
|
46
|
+
* @param {ImportCommandOptions} opts
|
|
47
|
+
*/
|
|
28
48
|
module.exports = async (opts) => {
|
|
29
49
|
// validate inputs from Commander
|
|
30
50
|
if (!isObject(opts)) {
|
|
@@ -63,6 +83,7 @@ module.exports = async (opts) => {
|
|
|
63
83
|
schemaStrategy: opts.schemaStrategy || DEFAULT_SCHEMA_STRATEGY,
|
|
64
84
|
exclude: opts.exclude,
|
|
65
85
|
only: opts.only,
|
|
86
|
+
throttle: opts.throttle,
|
|
66
87
|
rules: {
|
|
67
88
|
links: [
|
|
68
89
|
{
|
|
@@ -102,27 +123,26 @@ module.exports = async (opts) => {
|
|
|
102
123
|
updateLoader(stage, data);
|
|
103
124
|
});
|
|
104
125
|
|
|
105
|
-
const getTelemetryPayload = () => {
|
|
106
|
-
return {
|
|
107
|
-
eventProperties: {
|
|
108
|
-
source: engine.sourceProvider.name,
|
|
109
|
-
destination: engine.destinationProvider.name,
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
};
|
|
113
|
-
|
|
114
126
|
progress.on('transfer::start', async () => {
|
|
115
127
|
console.log('Starting import...');
|
|
116
|
-
await strapiInstance.telemetry.send(
|
|
128
|
+
await strapiInstance.telemetry.send(
|
|
129
|
+
'didDEITSProcessStart',
|
|
130
|
+
getTransferTelemetryPayload(engine)
|
|
131
|
+
);
|
|
117
132
|
});
|
|
118
133
|
|
|
119
134
|
let results;
|
|
120
135
|
try {
|
|
136
|
+
// Abort transfer if user interrupts process
|
|
137
|
+
['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => {
|
|
138
|
+
process.removeAllListeners(signal);
|
|
139
|
+
process.on(signal, () => abortTransfer({ engine, strapi }));
|
|
140
|
+
});
|
|
141
|
+
|
|
121
142
|
results = await engine.transfer();
|
|
122
143
|
} catch (e) {
|
|
123
|
-
await strapiInstance.telemetry.send('didDEITSProcessFail',
|
|
124
|
-
|
|
125
|
-
process.exit(1);
|
|
144
|
+
await strapiInstance.telemetry.send('didDEITSProcessFail', getTransferTelemetryPayload(engine));
|
|
145
|
+
exitWith(1, exitMessageText('import', true));
|
|
126
146
|
}
|
|
127
147
|
|
|
128
148
|
try {
|
|
@@ -132,10 +152,11 @@ module.exports = async (opts) => {
|
|
|
132
152
|
console.error('There was an error displaying the results of the transfer.');
|
|
133
153
|
}
|
|
134
154
|
|
|
135
|
-
await
|
|
155
|
+
// Note: we need to await telemetry or else the process ends before it is sent
|
|
156
|
+
await strapiInstance.telemetry.send('didDEITSProcessFinish', getTransferTelemetryPayload(engine));
|
|
136
157
|
await strapiInstance.destroy();
|
|
137
158
|
|
|
138
|
-
exitWith(0, '
|
|
159
|
+
exitWith(0, exitMessageText('import'));
|
|
139
160
|
};
|
|
140
161
|
|
|
141
162
|
/**
|
|
@@ -7,11 +7,11 @@ const {
|
|
|
7
7
|
createRemoteStrapiDestinationProvider,
|
|
8
8
|
createLocalStrapiSourceProvider,
|
|
9
9
|
createLocalStrapiDestinationProvider,
|
|
10
|
+
createRemoteStrapiSourceProvider,
|
|
10
11
|
},
|
|
11
12
|
},
|
|
12
13
|
} = require('@strapi/data-transfer');
|
|
13
14
|
const { isObject } = require('lodash/fp');
|
|
14
|
-
const chalk = require('chalk');
|
|
15
15
|
|
|
16
16
|
const {
|
|
17
17
|
buildTransferTable,
|
|
@@ -19,6 +19,9 @@ const {
|
|
|
19
19
|
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
20
20
|
formatDiagnostic,
|
|
21
21
|
loadersFactory,
|
|
22
|
+
exitMessageText,
|
|
23
|
+
abortTransfer,
|
|
24
|
+
getTransferTelemetryPayload,
|
|
22
25
|
} = require('./utils');
|
|
23
26
|
const { exitWith } = require('../utils/helpers');
|
|
24
27
|
|
|
@@ -29,6 +32,9 @@ const { exitWith } = require('../utils/helpers');
|
|
|
29
32
|
* @property {URL|undefined} [from] The url of a remote Strapi to use as remote source
|
|
30
33
|
* @property {string|undefined} [toToken] The transfer token for the remote Strapi destination
|
|
31
34
|
* @property {string|undefined} [fromToken] The transfer token for the remote Strapi source
|
|
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
|
|
32
38
|
*/
|
|
33
39
|
|
|
34
40
|
/**
|
|
@@ -44,15 +50,14 @@ module.exports = async (opts) => {
|
|
|
44
50
|
exitWith(1, 'Could not parse command arguments');
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
|
|
53
|
+
if (!(opts.from || opts.to) || (opts.from && opts.to)) {
|
|
54
|
+
exitWith(1, 'Exactly one source (from) or destination (to) option must be provided');
|
|
55
|
+
}
|
|
48
56
|
|
|
57
|
+
const strapi = await createStrapiInstance();
|
|
49
58
|
let source;
|
|
50
59
|
let destination;
|
|
51
60
|
|
|
52
|
-
if (!opts.from && !opts.to) {
|
|
53
|
-
exitWith(1, 'At least one source (from) or destination (to) option must be provided');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
61
|
// if no URL provided, use local Strapi
|
|
57
62
|
if (!opts.from) {
|
|
58
63
|
source = createLocalStrapiSourceProvider({
|
|
@@ -61,13 +66,28 @@ module.exports = async (opts) => {
|
|
|
61
66
|
}
|
|
62
67
|
// if URL provided, set up a remote source provider
|
|
63
68
|
else {
|
|
64
|
-
|
|
69
|
+
if (!opts.fromToken) {
|
|
70
|
+
exitWith(1, 'Missing token for remote destination');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
source = createRemoteStrapiSourceProvider({
|
|
74
|
+
getStrapi: () => strapi,
|
|
75
|
+
url: opts.from,
|
|
76
|
+
auth: {
|
|
77
|
+
type: 'token',
|
|
78
|
+
token: opts.fromToken,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
// if no URL provided, use local Strapi
|
|
68
84
|
if (!opts.to) {
|
|
69
85
|
destination = createLocalStrapiDestinationProvider({
|
|
70
86
|
getStrapi: () => strapi,
|
|
87
|
+
strategy: 'restore',
|
|
88
|
+
restore: {
|
|
89
|
+
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
|
90
|
+
},
|
|
71
91
|
});
|
|
72
92
|
}
|
|
73
93
|
// if URL provided, set up a remote destination provider
|
|
@@ -96,6 +116,9 @@ module.exports = async (opts) => {
|
|
|
96
116
|
const engine = createTransferEngine(source, destination, {
|
|
97
117
|
versionStrategy: 'exact',
|
|
98
118
|
schemaStrategy: 'strict',
|
|
119
|
+
exclude: opts.exclude,
|
|
120
|
+
only: opts.only,
|
|
121
|
+
throttle: opts.throttle,
|
|
99
122
|
transforms: {
|
|
100
123
|
links: [
|
|
101
124
|
{
|
|
@@ -135,15 +158,39 @@ module.exports = async (opts) => {
|
|
|
135
158
|
updateLoader(stage, data);
|
|
136
159
|
});
|
|
137
160
|
|
|
161
|
+
progress.on('stage::error', ({ stage, data }) => {
|
|
162
|
+
updateLoader(stage, data).fail();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
progress.on('transfer::start', async () => {
|
|
166
|
+
console.log(`Starting transfer...`);
|
|
167
|
+
|
|
168
|
+
await strapi.telemetry.send('didDEITSProcessStart', getTransferTelemetryPayload(engine));
|
|
169
|
+
});
|
|
170
|
+
|
|
138
171
|
let results;
|
|
139
172
|
try {
|
|
140
|
-
|
|
173
|
+
// Abort transfer if user interrupts process
|
|
174
|
+
['SIGTERM', 'SIGINT', 'SIGQUIT'].forEach((signal) => {
|
|
175
|
+
process.removeAllListeners(signal);
|
|
176
|
+
process.on(signal, () => abortTransfer({ engine, strapi }));
|
|
177
|
+
});
|
|
178
|
+
|
|
141
179
|
results = await engine.transfer();
|
|
142
180
|
} catch (e) {
|
|
143
|
-
|
|
181
|
+
await strapi.telemetry.send('didDEITSProcessFail', getTransferTelemetryPayload(engine));
|
|
182
|
+
exitWith(1, exitMessageText('transfer', true));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Note: we need to await telemetry or else the process ends before it is sent
|
|
186
|
+
await strapi.telemetry.send('didDEITSProcessFinish', getTransferTelemetryPayload(engine));
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const table = buildTransferTable(results.engine);
|
|
190
|
+
console.log(table.toString());
|
|
191
|
+
} catch (e) {
|
|
192
|
+
console.error('There was an error displaying the results of the transfer.');
|
|
144
193
|
}
|
|
145
194
|
|
|
146
|
-
|
|
147
|
-
console.log(table.toString());
|
|
148
|
-
exitWith(0, `${chalk.bold('Transfer process has been completed successfully!')}`);
|
|
195
|
+
exitWith(0, exitMessageText('transfer'));
|
|
149
196
|
};
|
|
@@ -14,7 +14,19 @@ const {
|
|
|
14
14
|
const ora = require('ora');
|
|
15
15
|
const { readableBytes, exitWith } = require('../utils/helpers');
|
|
16
16
|
const strapi = require('../../index');
|
|
17
|
-
const { getParseListWithChoices } = require('../utils/commander');
|
|
17
|
+
const { getParseListWithChoices, parseInteger } = require('../utils/commander');
|
|
18
|
+
|
|
19
|
+
const exitMessageText = (process, error = false) => {
|
|
20
|
+
const processCapitalized = process[0].toUpperCase() + process.slice(1);
|
|
21
|
+
|
|
22
|
+
if (!error) {
|
|
23
|
+
return chalk.bold(
|
|
24
|
+
chalk.green(`${processCapitalized} process has been completed successfully!`)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return chalk.bold(chalk.red(`${processCapitalized} process failed.`));
|
|
29
|
+
};
|
|
18
30
|
|
|
19
31
|
const pad = (n) => {
|
|
20
32
|
return (n < 10 ? '0' : '') + String(n);
|
|
@@ -90,12 +102,23 @@ const DEFAULT_IGNORED_CONTENT_TYPES = [
|
|
|
90
102
|
'admin::audit-log',
|
|
91
103
|
];
|
|
92
104
|
|
|
93
|
-
const
|
|
105
|
+
const abortTransfer = async ({ engine, strapi }) => {
|
|
106
|
+
try {
|
|
107
|
+
await engine.abortTransfer();
|
|
108
|
+
await strapi.destroy();
|
|
109
|
+
} catch (e) {
|
|
110
|
+
// ignore because there's not much else we can do
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const createStrapiInstance = async (opts = {}) => {
|
|
94
117
|
try {
|
|
95
118
|
const appContext = await strapi.compile();
|
|
96
|
-
const app = strapi(appContext);
|
|
119
|
+
const app = strapi({ ...opts, ...appContext });
|
|
97
120
|
|
|
98
|
-
app.log.level = logLevel;
|
|
121
|
+
app.log.level = opts.logLevel || 'error';
|
|
99
122
|
return await app.load();
|
|
100
123
|
} catch (err) {
|
|
101
124
|
if (err.code === 'ECONNREFUSED') {
|
|
@@ -107,6 +130,13 @@ const createStrapiInstance = async (logLevel = 'error') => {
|
|
|
107
130
|
|
|
108
131
|
const transferDataTypes = Object.keys(TransferGroupPresets);
|
|
109
132
|
|
|
133
|
+
const throttleOption = new Option(
|
|
134
|
+
'--throttle <delay after each entity>',
|
|
135
|
+
`Add a delay in milliseconds between each transferred entity`
|
|
136
|
+
)
|
|
137
|
+
.argParser(parseInteger)
|
|
138
|
+
.hideHelp(); // This option is not publicly documented
|
|
139
|
+
|
|
110
140
|
const excludeOption = new Option(
|
|
111
141
|
'--exclude <comma-separated data types>',
|
|
112
142
|
`Exclude data using comma-separated types. Available types: ${transferDataTypes.join(',')}`
|
|
@@ -212,14 +242,33 @@ const loadersFactory = (defaultLoaders = {}) => {
|
|
|
212
242
|
};
|
|
213
243
|
};
|
|
214
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Get the telemetry data to be sent for a didDEITSProcess* event from an initialized transfer engine object
|
|
247
|
+
*
|
|
248
|
+
* @param {import('@strapi/data-transfer/types').ITransferEngine} engine Initialized transfer engine
|
|
249
|
+
* @returns {object} Telemetry properties object
|
|
250
|
+
*/
|
|
251
|
+
const getTransferTelemetryPayload = (engine) => {
|
|
252
|
+
return {
|
|
253
|
+
eventProperties: {
|
|
254
|
+
source: engine?.sourceProvider?.name,
|
|
255
|
+
destination: engine?.destinationProvider?.name,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
|
|
215
260
|
module.exports = {
|
|
216
261
|
loadersFactory,
|
|
217
262
|
buildTransferTable,
|
|
218
263
|
getDefaultExportName,
|
|
264
|
+
getTransferTelemetryPayload,
|
|
219
265
|
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
220
266
|
createStrapiInstance,
|
|
221
267
|
excludeOption,
|
|
268
|
+
exitMessageText,
|
|
222
269
|
onlyOption,
|
|
270
|
+
throttleOption,
|
|
223
271
|
validateExcludeOnly,
|
|
224
272
|
formatDiagnostic,
|
|
273
|
+
abortTransfer,
|
|
225
274
|
};
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const inquirer = require('inquirer');
|
|
8
8
|
const { InvalidOptionArgumentError, Option } = require('commander');
|
|
9
9
|
const { bold, green, cyan } = require('chalk');
|
|
10
|
+
const { isNaN } = require('lodash/fp');
|
|
10
11
|
const { exitWith } = require('./helpers');
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -40,6 +41,18 @@ const getParseListWithChoices = (choices, errorMessage = 'Invalid options:') =>
|
|
|
40
41
|
};
|
|
41
42
|
};
|
|
42
43
|
|
|
44
|
+
/**
|
|
45
|
+
* argParser: Parse a string as an integer
|
|
46
|
+
*/
|
|
47
|
+
const parseInteger = (value) => {
|
|
48
|
+
// parseInt takes a string and a radix
|
|
49
|
+
const parsedValue = parseInt(value, 10);
|
|
50
|
+
if (isNaN(parsedValue)) {
|
|
51
|
+
throw new InvalidOptionArgumentError(`Not an integer: ${value}`);
|
|
52
|
+
}
|
|
53
|
+
return parsedValue;
|
|
54
|
+
};
|
|
55
|
+
|
|
43
56
|
/**
|
|
44
57
|
* argParser: Parse a string as a URL object
|
|
45
58
|
*/
|
|
@@ -131,6 +144,7 @@ module.exports = {
|
|
|
131
144
|
getParseListWithChoices,
|
|
132
145
|
parseList,
|
|
133
146
|
parseURL,
|
|
147
|
+
parseInteger,
|
|
134
148
|
promptEncryptionKey,
|
|
135
149
|
confirmMessage,
|
|
136
150
|
forceOption,
|
|
@@ -61,7 +61,7 @@ const contentTypeSchemaValidator = yup.object().shape({
|
|
|
61
61
|
|
|
62
62
|
// should match the GraphQL regex
|
|
63
63
|
if (!regressedValues.every((value) => GRAPHQL_ENUM_REGEX.test(value))) {
|
|
64
|
-
const message = `Invalid enumeration value. Values should have at least one alphabetical character
|
|
64
|
+
const message = `Invalid enumeration value. Values should have at least one alphabetical character preceding the first occurence of a number. Update your enumeration '${attrName}'.`;
|
|
65
65
|
|
|
66
66
|
return this.createError({ message });
|
|
67
67
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { join } = require('path');
|
|
4
4
|
const fse = require('fs-extra');
|
|
5
|
-
const { defaultsDeep, getOr, get } = require('lodash/fp');
|
|
5
|
+
const { defaultsDeep, defaults, getOr, get } = require('lodash/fp');
|
|
6
6
|
const { env } = require('@strapi/utils');
|
|
7
7
|
const loadConfigFile = require('../../app-configuration/load-config-file');
|
|
8
8
|
const loadFiles = require('../../../load/load-files');
|
|
@@ -101,7 +101,12 @@ const loadPlugins = async (strapi) => {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
const pluginServer = loadConfigFile(serverEntrypointPath);
|
|
104
|
-
plugins[pluginName] =
|
|
104
|
+
plugins[pluginName] = {
|
|
105
|
+
...defaultPlugin,
|
|
106
|
+
...pluginServer,
|
|
107
|
+
config: defaults(defaultPlugin.config, pluginServer.config),
|
|
108
|
+
routes: pluginServer.routes ?? defaultPlugin.routes,
|
|
109
|
+
};
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
// TODO: validate plugin format
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ExtendableContext } from 'koa';
|
|
2
2
|
import { Strapi } from '../../';
|
|
3
3
|
|
|
4
|
-
interface PolicyContext extends
|
|
4
|
+
interface PolicyContext extends ExtendableContext {
|
|
5
5
|
type: string;
|
|
6
|
-
is(name): boolean;
|
|
6
|
+
is(name: string): boolean;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export type Policy<T = unknown> = (
|
|
@@ -22,7 +22,6 @@ const createCollectionTypeController = ({ contentType }) => {
|
|
|
22
22
|
const sanitizedQuery = await this.sanitizeQuery(ctx);
|
|
23
23
|
const { results, pagination } = await strapi.service(uid).find(sanitizedQuery);
|
|
24
24
|
const sanitizedResults = await this.sanitizeOutput(results, ctx);
|
|
25
|
-
|
|
26
25
|
return this.transformResponse(sanitizedResults, { pagination });
|
|
27
26
|
},
|
|
28
27
|
|
|
@@ -18,7 +18,7 @@ const createController = ({ contentType }) => {
|
|
|
18
18
|
return transformResponse(data, meta, { contentType });
|
|
19
19
|
},
|
|
20
20
|
|
|
21
|
-
sanitizeOutput(data, ctx) {
|
|
21
|
+
async sanitizeOutput(data, ctx) {
|
|
22
22
|
const auth = getAuthFromKoaContext(ctx);
|
|
23
23
|
|
|
24
24
|
return sanitize.contentAPI.output(data, contentType, { auth });
|
package/lib/factories.js
CHANGED
|
@@ -45,13 +45,12 @@ const createCoreService = (uid, cfg = {}) => {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const createCoreRouter = (uid, cfg = {}) => {
|
|
48
|
-
const { prefix, config = {}, only, except } = cfg;
|
|
48
|
+
const { prefix, config = {}, only, except, type } = cfg;
|
|
49
49
|
let routes;
|
|
50
50
|
|
|
51
51
|
return {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
},
|
|
52
|
+
type,
|
|
53
|
+
prefix,
|
|
55
54
|
get routes() {
|
|
56
55
|
if (!routes) {
|
|
57
56
|
const contentType = strapi.contentType(uid);
|
|
@@ -1,21 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
/* eslint-disable no-nested-ternary */
|
|
4
|
-
|
|
5
|
-
const chalk = require('chalk');
|
|
6
|
-
|
|
7
|
-
const codeToColor = (code) => {
|
|
8
|
-
return code >= 500
|
|
9
|
-
? chalk.red(code)
|
|
10
|
-
: code >= 400
|
|
11
|
-
? chalk.yellow(code)
|
|
12
|
-
: code >= 300
|
|
13
|
-
? chalk.cyan(code)
|
|
14
|
-
: code >= 200
|
|
15
|
-
? chalk.green(code)
|
|
16
|
-
: code;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
3
|
/**
|
|
20
4
|
* @type {import('./').MiddlewareFactory}
|
|
21
5
|
*/
|
|
@@ -25,6 +9,6 @@ module.exports = (_, { strapi }) => {
|
|
|
25
9
|
await next();
|
|
26
10
|
const delta = Math.ceil(Date.now() - start);
|
|
27
11
|
|
|
28
|
-
strapi.log.http(`${ctx.method} ${ctx.url} (${delta} ms) ${
|
|
12
|
+
strapi.log.http(`${ctx.method} ${ctx.url} (${delta} ms) ${ctx.status}`);
|
|
29
13
|
};
|
|
30
14
|
};
|
|
@@ -12,7 +12,7 @@ const defaults = {
|
|
|
12
12
|
useDefaults: true,
|
|
13
13
|
directives: {
|
|
14
14
|
'connect-src': ["'self'", 'https:'],
|
|
15
|
-
'img-src': ["'self'", 'data:', 'blob:', 'https://
|
|
15
|
+
'img-src': ["'self'", 'data:', 'blob:', 'https://market-assets.strapi.io'],
|
|
16
16
|
'media-src': ["'self'", 'data:', 'blob:'],
|
|
17
17
|
upgradeInsecureRequests: null,
|
|
18
18
|
},
|
|
@@ -35,7 +35,12 @@ module.exports = function createEventHub() {
|
|
|
35
35
|
},
|
|
36
36
|
|
|
37
37
|
unsubscribe(subscriber) {
|
|
38
|
-
subscribers.
|
|
38
|
+
const subscriberIndex = subscribers.indexOf(subscriber);
|
|
39
|
+
|
|
40
|
+
// Only remove the subscriber if it exists
|
|
41
|
+
if (subscriberIndex >= 0) {
|
|
42
|
+
subscribers.splice(subscriberIndex, 1);
|
|
43
|
+
}
|
|
39
44
|
},
|
|
40
45
|
|
|
41
46
|
on(eventName, listener) {
|
|
@@ -3,7 +3,6 @@
|
|
|
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');
|
|
7
6
|
const isDocker = require('is-docker');
|
|
8
7
|
const fetch = require('node-fetch');
|
|
9
8
|
const ciEnv = require('ci-info');
|
|
@@ -41,14 +40,6 @@ module.exports = (strapi) => {
|
|
|
41
40
|
const serverRootPath = strapi.dirs.app.root;
|
|
42
41
|
const adminRootPath = path.join(strapi.dirs.app.root, 'src', 'admin');
|
|
43
42
|
|
|
44
|
-
const getNumberOfDynamicZones = () => {
|
|
45
|
-
return pipe(
|
|
46
|
-
map('attributes'),
|
|
47
|
-
flatMap(values),
|
|
48
|
-
sumBy(propEq('type', 'dynamiczone'))
|
|
49
|
-
)(strapi.contentTypes);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
43
|
const anonymousUserProperties = {
|
|
53
44
|
environment: strapi.config.environment,
|
|
54
45
|
os: os.type(),
|
|
@@ -66,9 +57,6 @@ module.exports = (strapi) => {
|
|
|
66
57
|
useTypescriptOnAdmin: isUsingTypeScriptSync(adminRootPath),
|
|
67
58
|
projectId: uuid,
|
|
68
59
|
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(),
|
|
72
60
|
};
|
|
73
61
|
|
|
74
62
|
addPackageJsonStrapiMetadata(anonymousGroupProperties, strapi);
|
|
@@ -69,7 +69,7 @@ const registerPluginRoutes = (strapi) => {
|
|
|
69
69
|
} else {
|
|
70
70
|
_.forEach(plugin.routes, (router) => {
|
|
71
71
|
router.type = router.type || 'admin';
|
|
72
|
-
router.prefix = `/${pluginName}`;
|
|
72
|
+
router.prefix = router.prefix || `/${pluginName}`;
|
|
73
73
|
router.routes.forEach((route) => {
|
|
74
74
|
generateRouteScope(route);
|
|
75
75
|
route.info = { pluginName };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { map, values, sumBy, pipe, flatMap, propEq } = require('lodash/fp');
|
|
4
|
+
|
|
5
|
+
const getNumberOfDynamicZones = () => {
|
|
6
|
+
return pipe(
|
|
7
|
+
map('attributes'),
|
|
8
|
+
flatMap(values),
|
|
9
|
+
sumBy(propEq('type', 'dynamiczone'))
|
|
10
|
+
)(strapi.contentTypes);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
module.exports = getNumberOfDynamicZones;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/strapi",
|
|
3
|
-
"version": "4.10.0-beta.
|
|
3
|
+
"version": "4.10.0-beta.1",
|
|
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",
|
|
@@ -66,9 +66,7 @@
|
|
|
66
66
|
],
|
|
67
67
|
"main": "./lib",
|
|
68
68
|
"types": "./lib/index.d.ts",
|
|
69
|
-
"bin":
|
|
70
|
-
"strapi": "./bin/strapi.js"
|
|
71
|
-
},
|
|
69
|
+
"bin": "./bin/strapi.js",
|
|
72
70
|
"directories": {
|
|
73
71
|
"lib": "./lib",
|
|
74
72
|
"bin": "./bin",
|
|
@@ -76,24 +74,26 @@
|
|
|
76
74
|
},
|
|
77
75
|
"scripts": {
|
|
78
76
|
"postinstall": "node lib/utils/success.js",
|
|
79
|
-
"test:unit": "jest
|
|
77
|
+
"test:unit": "run -T jest",
|
|
78
|
+
"test:unit:watch": "run -T jest --watch",
|
|
79
|
+
"lint": "run -T eslint ."
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
82
|
"@koa/cors": "3.4.3",
|
|
83
83
|
"@koa/router": "10.1.1",
|
|
84
|
-
"@strapi/admin": "4.10.0-beta.
|
|
85
|
-
"@strapi/data-transfer": "4.10.0-beta.
|
|
86
|
-
"@strapi/database": "4.10.0-beta.
|
|
87
|
-
"@strapi/generate-new": "4.10.0-beta.
|
|
88
|
-
"@strapi/generators": "4.10.0-beta.
|
|
89
|
-
"@strapi/logger": "4.10.0-beta.
|
|
90
|
-
"@strapi/permissions": "4.10.0-beta.
|
|
91
|
-
"@strapi/plugin-content-manager": "4.10.0-beta.
|
|
92
|
-
"@strapi/plugin-content-type-builder": "4.10.0-beta.
|
|
93
|
-
"@strapi/plugin-email": "4.10.0-beta.
|
|
94
|
-
"@strapi/plugin-upload": "4.10.0-beta.
|
|
95
|
-
"@strapi/typescript-utils": "4.10.0-beta.
|
|
96
|
-
"@strapi/utils": "4.10.0-beta.
|
|
84
|
+
"@strapi/admin": "4.10.0-beta.1",
|
|
85
|
+
"@strapi/data-transfer": "4.10.0-beta.1",
|
|
86
|
+
"@strapi/database": "4.10.0-beta.1",
|
|
87
|
+
"@strapi/generate-new": "4.10.0-beta.1",
|
|
88
|
+
"@strapi/generators": "4.10.0-beta.1",
|
|
89
|
+
"@strapi/logger": "4.10.0-beta.1",
|
|
90
|
+
"@strapi/permissions": "4.10.0-beta.1",
|
|
91
|
+
"@strapi/plugin-content-manager": "4.10.0-beta.1",
|
|
92
|
+
"@strapi/plugin-content-type-builder": "4.10.0-beta.1",
|
|
93
|
+
"@strapi/plugin-email": "4.10.0-beta.1",
|
|
94
|
+
"@strapi/plugin-upload": "4.10.0-beta.1",
|
|
95
|
+
"@strapi/typescript-utils": "4.10.0-beta.1",
|
|
96
|
+
"@strapi/utils": "4.10.0-beta.1",
|
|
97
97
|
"bcryptjs": "2.4.3",
|
|
98
98
|
"boxen": "5.1.2",
|
|
99
99
|
"chalk": "4.1.2",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"open": "8.4.0",
|
|
129
129
|
"ora": "5.4.1",
|
|
130
130
|
"package-json": "7.0.0",
|
|
131
|
-
"qs": "6.11.
|
|
131
|
+
"qs": "6.11.1",
|
|
132
132
|
"resolve-cwd": "3.0.0",
|
|
133
133
|
"semver": "7.3.8",
|
|
134
134
|
"statuses": "2.0.1",
|
|
@@ -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": "95d581b31bee464af42e5d8db408fa578d8532c7"
|
|
146
146
|
}
|