@strapi/strapi 4.6.0-beta.1 → 4.6.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/bin/strapi.js +76 -5
- package/lib/Strapi.js +1 -1
- package/lib/commands/transfer/export.js +26 -29
- package/lib/commands/transfer/import.js +31 -29
- package/lib/commands/transfer/transfer.js +127 -0
- package/lib/commands/transfer/utils.js +40 -2
- package/lib/commands/utils/commander.js +76 -13
- package/lib/commands/utils/helpers.js +108 -0
- package/lib/core-api/service/index.d.ts +1 -1
- package/lib/core-api/service/single-type.js +14 -1
- package/lib/services/entity-service/index.js +12 -2
- package/lib/services/errors.js +5 -1
- package/lib/services/event-hub.js +70 -8
- package/lib/types/core/strapi/index.d.ts +5 -0
- package/lib/types/factories.d.ts +1 -1
- package/lib/utils/ee.js +1 -1
- package/package.json +15 -15
- package/lib/commands/utils/index.js +0 -20
package/README.md
CHANGED
|
@@ -80,14 +80,18 @@ Complete installation requirements can be found in the documentation under <a hr
|
|
|
80
80
|
- CentOS/RHEL 8
|
|
81
81
|
- macOS Mojave
|
|
82
82
|
- Windows 10
|
|
83
|
-
- Docker
|
|
83
|
+
- Docker
|
|
84
84
|
|
|
85
85
|
(Please note that Strapi may work on other operating systems, but these are not tested nor officially supported at this time.)
|
|
86
86
|
|
|
87
87
|
**Node:**
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
Strapi only supports maintenance and LTS versions of Node.js. Please refer to the <a href="https://nodejs.org/en/about/releases/">Node.js release schedule</a> for more information. NPM versions installed by default with Node.js are supported. Generally it's recommended to use yarn over npm where possible.
|
|
90
|
+
|
|
91
|
+
| Strapi Version | Recommended | Minimum |
|
|
92
|
+
| -------------- | ----------- | ------- |
|
|
93
|
+
| 4.3.9 and up | 18.x | 14.x |
|
|
94
|
+
| 4.0.x to 4.3.8 | 16.x | 14.x |
|
|
91
95
|
|
|
92
96
|
**Database:**
|
|
93
97
|
|
|
@@ -98,7 +102,7 @@ Complete installation requirements can be found in the documentation under <a hr
|
|
|
98
102
|
| PostgreSQL | 11.0 | 14.0 |
|
|
99
103
|
| SQLite | 3 | 3 |
|
|
100
104
|
|
|
101
|
-
**We recommend always using the latest version of Strapi to start your new projects**.
|
|
105
|
+
**We recommend always using the latest version of Strapi stable to start your new projects**.
|
|
102
106
|
|
|
103
107
|
## Features
|
|
104
108
|
|
package/bin/strapi.js
CHANGED
|
@@ -14,7 +14,18 @@ const inquirer = require('inquirer');
|
|
|
14
14
|
const program = new Command();
|
|
15
15
|
|
|
16
16
|
const packageJSON = require('../package.json');
|
|
17
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
promptEncryptionKey,
|
|
19
|
+
confirmMessage,
|
|
20
|
+
parseURL,
|
|
21
|
+
forceOption,
|
|
22
|
+
} = require('../lib/commands/utils/commander');
|
|
23
|
+
const { ifOptions, assertUrlHasProtocol, exitWith } = require('../lib/commands/utils/helpers');
|
|
24
|
+
const {
|
|
25
|
+
excludeOption,
|
|
26
|
+
onlyOption,
|
|
27
|
+
validateExcludeOnly,
|
|
28
|
+
} = require('../lib/commands/transfer/utils');
|
|
18
29
|
|
|
19
30
|
const checkCwdIsStrapiApp = (name) => {
|
|
20
31
|
const logErrorAndExit = () => {
|
|
@@ -258,10 +269,65 @@ program
|
|
|
258
269
|
.option('-s, --silent', `Run the generation silently, without any output`, false)
|
|
259
270
|
.action(getLocalScript('ts/generate-types'));
|
|
260
271
|
|
|
272
|
+
if (process.env.STRAPI_EXPERIMENTAL === 'true') {
|
|
273
|
+
// `$ strapi transfer`
|
|
274
|
+
program
|
|
275
|
+
.command('transfer')
|
|
276
|
+
.description('Transfer data from one source to another')
|
|
277
|
+
.allowExcessArguments(false)
|
|
278
|
+
.addOption(
|
|
279
|
+
new Option(
|
|
280
|
+
'--from <sourceURL>',
|
|
281
|
+
`URL of the remote Strapi instance to get data from`
|
|
282
|
+
).argParser(parseURL)
|
|
283
|
+
)
|
|
284
|
+
.addOption(
|
|
285
|
+
new Option(
|
|
286
|
+
'--to <destinationURL>',
|
|
287
|
+
`URL of the remote Strapi instance to send data to`
|
|
288
|
+
).argParser(parseURL)
|
|
289
|
+
)
|
|
290
|
+
.addOption(forceOption)
|
|
291
|
+
// Validate URLs
|
|
292
|
+
.hook(
|
|
293
|
+
'preAction',
|
|
294
|
+
ifOptions(
|
|
295
|
+
(opts) => opts.from,
|
|
296
|
+
(thisCommand) => assertUrlHasProtocol(thisCommand.opts().from, ['https:', 'http:'])
|
|
297
|
+
)
|
|
298
|
+
)
|
|
299
|
+
.hook(
|
|
300
|
+
'preAction',
|
|
301
|
+
ifOptions(
|
|
302
|
+
(opts) => opts.to,
|
|
303
|
+
(thisCommand) => assertUrlHasProtocol(thisCommand.opts().to, ['https:', 'http:'])
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
.hook(
|
|
307
|
+
'preAction',
|
|
308
|
+
ifOptions(
|
|
309
|
+
(opts) => !opts.from && !opts.to,
|
|
310
|
+
() => exitWith(1, 'At least one source (from) or destination (to) option must be provided')
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
.addOption(forceOption)
|
|
314
|
+
.addOption(excludeOption)
|
|
315
|
+
.addOption(onlyOption)
|
|
316
|
+
.hook('preAction', validateExcludeOnly)
|
|
317
|
+
.hook(
|
|
318
|
+
'preAction',
|
|
319
|
+
confirmMessage(
|
|
320
|
+
'The import will delete all data in the remote database. Are you sure you want to proceed?'
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
.action(getLocalScript('transfer/transfer'));
|
|
324
|
+
}
|
|
325
|
+
|
|
261
326
|
// `$ strapi export`
|
|
262
327
|
program
|
|
263
328
|
.command('export')
|
|
264
329
|
.description('Export data from Strapi to file')
|
|
330
|
+
.allowExcessArguments(false)
|
|
265
331
|
.addOption(
|
|
266
332
|
new Option('--no-encrypt', `Disables 'aes-128-ecb' encryption of the output file`).default(true)
|
|
267
333
|
)
|
|
@@ -273,7 +339,9 @@ program
|
|
|
273
339
|
)
|
|
274
340
|
)
|
|
275
341
|
.addOption(new Option('-f, --file <file>', 'name to use for exported file (without extensions)'))
|
|
276
|
-
.
|
|
342
|
+
.addOption(excludeOption)
|
|
343
|
+
.addOption(onlyOption)
|
|
344
|
+
.hook('preAction', validateExcludeOnly)
|
|
277
345
|
.hook('preAction', promptEncryptionKey)
|
|
278
346
|
.action(getLocalScript('transfer/export'));
|
|
279
347
|
|
|
@@ -281,6 +349,7 @@ program
|
|
|
281
349
|
program
|
|
282
350
|
.command('import')
|
|
283
351
|
.description('Import data from file to Strapi')
|
|
352
|
+
.allowExcessArguments(false)
|
|
284
353
|
.requiredOption(
|
|
285
354
|
'-f, --file <file>',
|
|
286
355
|
'path and filename for the Strapi export file you want to import'
|
|
@@ -291,7 +360,10 @@ program
|
|
|
291
360
|
'Provide encryption key in command instead of using the prompt'
|
|
292
361
|
)
|
|
293
362
|
)
|
|
294
|
-
.
|
|
363
|
+
.addOption(forceOption)
|
|
364
|
+
.addOption(excludeOption)
|
|
365
|
+
.addOption(onlyOption)
|
|
366
|
+
.hook('preAction', validateExcludeOnly)
|
|
295
367
|
.hook('preAction', async (thisCommand) => {
|
|
296
368
|
const opts = thisCommand.opts();
|
|
297
369
|
const ext = path.extname(String(opts.file));
|
|
@@ -307,8 +379,7 @@ program
|
|
|
307
379
|
},
|
|
308
380
|
]);
|
|
309
381
|
if (!answers.key?.length) {
|
|
310
|
-
|
|
311
|
-
process.exit(0);
|
|
382
|
+
exitWith(0, 'No key entered, aborting import.');
|
|
312
383
|
}
|
|
313
384
|
opts.key = answers.key;
|
|
314
385
|
}
|
package/lib/Strapi.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
createLocalFileDestinationProvider,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} = require('@strapi/data-transfer');
|
|
4
|
+
providers: { createLocalFileDestinationProvider },
|
|
5
|
+
} = require('@strapi/data-transfer/lib/file');
|
|
6
|
+
const {
|
|
7
|
+
providers: { createLocalStrapiSourceProvider },
|
|
8
|
+
} = require('@strapi/data-transfer/lib/strapi');
|
|
9
|
+
const { createTransferEngine } = require('@strapi/data-transfer/lib/engine');
|
|
10
10
|
const { isObject, isString, isFinite, toNumber } = require('lodash/fp');
|
|
11
11
|
const fs = require('fs-extra');
|
|
12
12
|
const chalk = require('chalk');
|
|
@@ -53,6 +53,8 @@ module.exports = async (opts) => {
|
|
|
53
53
|
const engine = createTransferEngine(source, destination, {
|
|
54
54
|
versionStrategy: 'ignore', // for an export to file, versionStrategy will always be skipped
|
|
55
55
|
schemaStrategy: 'ignore', // for an export to file, schemaStrategy will always be skipped
|
|
56
|
+
exclude: opts.exclude,
|
|
57
|
+
only: opts.only,
|
|
56
58
|
transforms: {
|
|
57
59
|
links: [
|
|
58
60
|
{
|
|
@@ -74,32 +76,23 @@ module.exports = async (opts) => {
|
|
|
74
76
|
},
|
|
75
77
|
});
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
logger.log(`Starting export...`);
|
|
79
|
-
|
|
80
|
-
const progress = engine.progress.stream;
|
|
79
|
+
const progress = engine.progress.stream;
|
|
81
80
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
};
|
|
81
|
+
const getTelemetryPayload = (/* payload */) => {
|
|
82
|
+
return {
|
|
83
|
+
eventProperties: {
|
|
84
|
+
source: engine.sourceProvider.name,
|
|
85
|
+
destination: engine.destinationProvider.name,
|
|
86
|
+
},
|
|
89
87
|
};
|
|
88
|
+
};
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
progress.on('transfer::finish', (payload) => {
|
|
96
|
-
strapi.telemetry.send('didDEITSProcessFinish', telemetryPayload(payload));
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
progress.on('transfer::error', (payload) => {
|
|
100
|
-
strapi.telemetry.send('didDEITSProcessFail', telemetryPayload(payload));
|
|
101
|
-
});
|
|
90
|
+
progress.on('transfer::start', async () => {
|
|
91
|
+
logger.log(`Starting export...`);
|
|
92
|
+
await strapi.telemetry.send('didDEITSProcessStart', getTelemetryPayload());
|
|
93
|
+
});
|
|
102
94
|
|
|
95
|
+
try {
|
|
103
96
|
const results = await engine.transfer();
|
|
104
97
|
const outFile = results.destination.file.path;
|
|
105
98
|
|
|
@@ -113,11 +106,15 @@ module.exports = async (opts) => {
|
|
|
113
106
|
|
|
114
107
|
logger.log(`${chalk.bold('Export process has been completed successfully!')}`);
|
|
115
108
|
logger.log(`Export archive is in ${chalk.green(outFile)}`);
|
|
116
|
-
process.exit(0);
|
|
117
109
|
} catch (e) {
|
|
110
|
+
await strapi.telemetry.send('didDEITSProcessFail', getTelemetryPayload());
|
|
118
111
|
logger.error('Export process failed unexpectedly:', e.toString());
|
|
119
112
|
process.exit(1);
|
|
120
113
|
}
|
|
114
|
+
|
|
115
|
+
// Note: Telemetry can't be sent in a finish event, because it runs async after this block but we can't await it, so if process.exit is used it won't send
|
|
116
|
+
await strapi.telemetry.send('didDEITSProcessFinish', getTelemetryPayload());
|
|
117
|
+
process.exit(0);
|
|
121
118
|
};
|
|
122
119
|
|
|
123
120
|
/**
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
createLocalFileSourceProvider,
|
|
5
|
-
|
|
4
|
+
providers: { createLocalFileSourceProvider },
|
|
5
|
+
} = require('@strapi/data-transfer/lib/file');
|
|
6
|
+
const {
|
|
7
|
+
providers: { createLocalStrapiDestinationProvider, DEFAULT_CONFLICT_STRATEGY },
|
|
8
|
+
} = require('@strapi/data-transfer/lib/strapi');
|
|
9
|
+
const {
|
|
6
10
|
createTransferEngine,
|
|
7
11
|
DEFAULT_VERSION_STRATEGY,
|
|
8
12
|
DEFAULT_SCHEMA_STRATEGY,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// eslint-disable-next-line import/no-unresolved, node/no-missing-require
|
|
12
|
-
} = require('@strapi/data-transfer');
|
|
13
|
+
} = require('@strapi/data-transfer/lib/engine');
|
|
14
|
+
|
|
13
15
|
const { isObject } = require('lodash/fp');
|
|
14
16
|
const path = require('path');
|
|
15
17
|
|
|
@@ -45,6 +47,7 @@ module.exports = async (opts) => {
|
|
|
45
47
|
async getStrapi() {
|
|
46
48
|
return strapiInstance;
|
|
47
49
|
},
|
|
50
|
+
autoDestroy: false,
|
|
48
51
|
strategy: opts.conflictStrategy || DEFAULT_CONFLICT_STRATEGY,
|
|
49
52
|
restore: {
|
|
50
53
|
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
|
@@ -59,6 +62,7 @@ module.exports = async (opts) => {
|
|
|
59
62
|
versionStrategy: opts.versionStrategy || DEFAULT_VERSION_STRATEGY,
|
|
60
63
|
schemaStrategy: opts.schemaStrategy || DEFAULT_SCHEMA_STRATEGY,
|
|
61
64
|
exclude: opts.exclude,
|
|
65
|
+
only: opts.only,
|
|
62
66
|
rules: {
|
|
63
67
|
links: [
|
|
64
68
|
{
|
|
@@ -77,44 +81,42 @@ module.exports = async (opts) => {
|
|
|
77
81
|
],
|
|
78
82
|
},
|
|
79
83
|
};
|
|
80
|
-
const engine = createTransferEngine(source, destination, engineOptions);
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
logger.info('Starting import...');
|
|
85
|
+
const engine = createTransferEngine(source, destination, engineOptions);
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
};
|
|
87
|
+
const progress = engine.progress.stream;
|
|
88
|
+
const getTelemetryPayload = () => {
|
|
89
|
+
return {
|
|
90
|
+
eventProperties: {
|
|
91
|
+
source: engine.sourceProvider.name,
|
|
92
|
+
destination: engine.destinationProvider.name,
|
|
93
|
+
},
|
|
93
94
|
};
|
|
95
|
+
};
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
progress.on('transfer::finish', (payload) => {
|
|
100
|
-
strapiInstance.telemetry.send('didDEITSProcessFinish', telemetryPayload(payload));
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
progress.on('transfer::error', (payload) => {
|
|
104
|
-
strapiInstance.telemetry.send('didDEITSProcessFail', telemetryPayload(payload));
|
|
105
|
-
});
|
|
97
|
+
progress.on('transfer::start', async () => {
|
|
98
|
+
logger.info('Starting import...');
|
|
99
|
+
await strapiInstance.telemetry.send('didDEITSProcessStart', getTelemetryPayload());
|
|
100
|
+
});
|
|
106
101
|
|
|
102
|
+
try {
|
|
107
103
|
const results = await engine.transfer();
|
|
108
104
|
const table = buildTransferTable(results.engine);
|
|
109
105
|
logger.info(table.toString());
|
|
110
106
|
|
|
111
107
|
logger.info('Import process has been completed successfully!');
|
|
112
|
-
process.exit(0);
|
|
113
108
|
} catch (e) {
|
|
109
|
+
await strapiInstance.telemetry.send('didDEITSProcessFail', getTelemetryPayload());
|
|
114
110
|
logger.error('Import process failed unexpectedly:');
|
|
115
111
|
logger.error(e);
|
|
116
112
|
process.exit(1);
|
|
117
113
|
}
|
|
114
|
+
|
|
115
|
+
// Note: Telemetry can't be sent in a finish event, because it runs async after this block but we can't await it, so if process.exit is used it won't send
|
|
116
|
+
await strapiInstance.telemetry.send('didDEITSProcessFinish', getTelemetryPayload());
|
|
117
|
+
await strapiInstance.destroy();
|
|
118
|
+
|
|
119
|
+
process.exit(0);
|
|
118
120
|
};
|
|
119
121
|
|
|
120
122
|
/**
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createTransferEngine } = require('@strapi/data-transfer/lib/engine');
|
|
4
|
+
const {
|
|
5
|
+
providers: {
|
|
6
|
+
createRemoteStrapiDestinationProvider,
|
|
7
|
+
createLocalStrapiSourceProvider,
|
|
8
|
+
createLocalStrapiDestinationProvider,
|
|
9
|
+
},
|
|
10
|
+
} = require('@strapi/data-transfer/lib/strapi');
|
|
11
|
+
const { isObject } = require('lodash/fp');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
buildTransferTable,
|
|
16
|
+
createStrapiInstance,
|
|
17
|
+
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
18
|
+
} = require('./utils');
|
|
19
|
+
|
|
20
|
+
const logger = console;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef TransferCommandOptions Options given to the CLI transfer command
|
|
24
|
+
*
|
|
25
|
+
* @property {URL|undefined} [to] The url of a remote Strapi to use as remote destination
|
|
26
|
+
* @property {URL|undefined} [from] The url of a remote Strapi to use as remote source
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Transfer command.
|
|
31
|
+
*
|
|
32
|
+
* It transfers data from a local file to a local strapi instance
|
|
33
|
+
*
|
|
34
|
+
* @param {TransferCommandOptions} opts
|
|
35
|
+
*/
|
|
36
|
+
module.exports = async (opts) => {
|
|
37
|
+
// Validate inputs from Commander
|
|
38
|
+
if (!isObject(opts)) {
|
|
39
|
+
logger.error('Could not parse command arguments');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const strapi = await createStrapiInstance();
|
|
44
|
+
|
|
45
|
+
let source;
|
|
46
|
+
let destination;
|
|
47
|
+
|
|
48
|
+
if (!opts.from && !opts.to) {
|
|
49
|
+
logger.error('At least one source (from) or destination (to) option must be provided');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// if no URL provided, use local Strapi
|
|
54
|
+
if (!opts.from) {
|
|
55
|
+
source = createLocalStrapiSourceProvider({
|
|
56
|
+
getStrapi: () => strapi,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// if URL provided, set up a remote source provider
|
|
60
|
+
else {
|
|
61
|
+
logger.error(`Remote Strapi source provider not yet implemented`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// if no URL provided, use local Strapi
|
|
66
|
+
if (!opts.to) {
|
|
67
|
+
destination = createLocalStrapiDestinationProvider({
|
|
68
|
+
getStrapi: () => strapi,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// if URL provided, set up a remote destination provider
|
|
72
|
+
else {
|
|
73
|
+
destination = createRemoteStrapiDestinationProvider({
|
|
74
|
+
url: opts.to,
|
|
75
|
+
auth: false,
|
|
76
|
+
strategy: 'restore',
|
|
77
|
+
restore: {
|
|
78
|
+
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!source || !destination) {
|
|
84
|
+
logger.error('Could not create providers');
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const engine = createTransferEngine(source, destination, {
|
|
89
|
+
versionStrategy: 'strict',
|
|
90
|
+
schemaStrategy: 'strict',
|
|
91
|
+
transforms: {
|
|
92
|
+
links: [
|
|
93
|
+
{
|
|
94
|
+
filter(link) {
|
|
95
|
+
return (
|
|
96
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.left.type) &&
|
|
97
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.right.type)
|
|
98
|
+
);
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
entities: [
|
|
103
|
+
{
|
|
104
|
+
filter(entity) {
|
|
105
|
+
return !DEFAULT_IGNORED_CONTENT_TYPES.includes(entity.type);
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
logger.log(`Starting transfer...`);
|
|
114
|
+
|
|
115
|
+
const results = await engine.transfer();
|
|
116
|
+
|
|
117
|
+
const table = buildTransferTable(results.engine);
|
|
118
|
+
logger.log(table.toString());
|
|
119
|
+
|
|
120
|
+
logger.log(`${chalk.bold('Transfer process has been completed successfully!')}`);
|
|
121
|
+
process.exit(0);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
logger.error('Transfer process failed unexpectedly');
|
|
124
|
+
logger.error(e);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const Table = require('cli-table3');
|
|
5
|
-
const {
|
|
5
|
+
const { Option } = require('commander');
|
|
6
|
+
const { TransferGroupPresets } = require('@strapi/data-transfer/lib/engine');
|
|
7
|
+
const { readableBytes, exitWith } = require('../utils/helpers');
|
|
6
8
|
const strapi = require('../../index');
|
|
9
|
+
const { getParseListWithChoices } = require('../utils/commander');
|
|
7
10
|
|
|
8
11
|
const pad = (n) => {
|
|
9
12
|
return (n < 10 ? '0' : '') + String(n);
|
|
@@ -85,10 +88,45 @@ const createStrapiInstance = async (logLevel = 'error') => {
|
|
|
85
88
|
return app.load();
|
|
86
89
|
};
|
|
87
90
|
|
|
91
|
+
const transferDataTypes = Object.keys(TransferGroupPresets);
|
|
92
|
+
|
|
93
|
+
const excludeOption = new Option(
|
|
94
|
+
'--exclude <comma-separated data types>',
|
|
95
|
+
`Exclude this data. Options used here override --only. Available types: ${transferDataTypes.join(
|
|
96
|
+
','
|
|
97
|
+
)}`
|
|
98
|
+
).argParser(getParseListWithChoices(transferDataTypes, 'Invalid options for "exclude"'));
|
|
99
|
+
|
|
100
|
+
const onlyOption = new Option(
|
|
101
|
+
'--only <command-separated data types>',
|
|
102
|
+
`Include only this data (plus schemas). Available types: ${transferDataTypes.join(',')}`
|
|
103
|
+
).argParser(getParseListWithChoices(transferDataTypes, 'Invalid options for "only"'));
|
|
104
|
+
|
|
105
|
+
const validateExcludeOnly = (command) => {
|
|
106
|
+
const { exclude, only } = command.opts();
|
|
107
|
+
if (!only || !exclude) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const choicesInBoth = only.filter((n) => {
|
|
112
|
+
return exclude.indexOf(n) !== -1;
|
|
113
|
+
});
|
|
114
|
+
if (choicesInBoth.length > 0) {
|
|
115
|
+
exitWith(
|
|
116
|
+
1,
|
|
117
|
+
`Data types may not be used in both "exclude" and "only" in the same command. Found in both: ${choicesInBoth.join(
|
|
118
|
+
','
|
|
119
|
+
)}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
88
124
|
module.exports = {
|
|
89
125
|
buildTransferTable,
|
|
90
126
|
getDefaultExportName,
|
|
91
|
-
yyyymmddHHMMSS,
|
|
92
127
|
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
93
128
|
createStrapiInstance,
|
|
129
|
+
excludeOption,
|
|
130
|
+
onlyOption,
|
|
131
|
+
validateExcludeOnly,
|
|
94
132
|
};
|
|
@@ -1,12 +1,59 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* This file includes hooks to use for commander.hook and argParsers for commander.argParser
|
|
5
|
+
*/
|
|
6
|
+
|
|
3
7
|
const inquirer = require('inquirer');
|
|
8
|
+
const { InvalidOptionArgumentError, Option } = require('commander');
|
|
9
|
+
const { bold, green, cyan } = require('chalk');
|
|
10
|
+
const { exitWith } = require('./helpers');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* argParser: Parse a comma-delimited string as an array
|
|
14
|
+
*/
|
|
15
|
+
const parseList = (value) => {
|
|
16
|
+
let list;
|
|
17
|
+
try {
|
|
18
|
+
list = value.split(',').map((item) => item.trim()); // trim shouldn't be necessary but might help catch unexpected whitespace characters
|
|
19
|
+
} catch (e) {
|
|
20
|
+
exitWith(1, `Unrecognized input: ${value}`);
|
|
21
|
+
}
|
|
22
|
+
return list;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns an argParser that returns a list
|
|
27
|
+
*/
|
|
28
|
+
const getParseListWithChoices = (choices, errorMessage = 'Invalid options:') => {
|
|
29
|
+
return (value) => {
|
|
30
|
+
const list = parseList(value);
|
|
31
|
+
const invalid = list.filter((item) => {
|
|
32
|
+
return !choices.includes(item);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (invalid.length > 0) {
|
|
36
|
+
exitWith(1, `${errorMessage}: ${invalid.join(',')}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return list;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
4
42
|
|
|
5
43
|
/**
|
|
6
|
-
*
|
|
44
|
+
* argParser: Parse a string as a URL object
|
|
7
45
|
*/
|
|
8
|
-
const
|
|
9
|
-
|
|
46
|
+
const parseURL = (value) => {
|
|
47
|
+
try {
|
|
48
|
+
const url = new URL(value);
|
|
49
|
+
if (!url.host) {
|
|
50
|
+
throw new InvalidOptionArgumentError(`Could not parse url ${value}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return url;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
throw new InvalidOptionArgumentError(`Could not parse url ${value}`);
|
|
56
|
+
}
|
|
10
57
|
};
|
|
11
58
|
|
|
12
59
|
/**
|
|
@@ -16,8 +63,7 @@ const promptEncryptionKey = async (thisCommand) => {
|
|
|
16
63
|
const opts = thisCommand.opts();
|
|
17
64
|
|
|
18
65
|
if (!opts.encrypt && opts.key) {
|
|
19
|
-
|
|
20
|
-
process.exit(1);
|
|
66
|
+
return exitWith(1, 'Key may not be present unless encryption is used');
|
|
21
67
|
}
|
|
22
68
|
|
|
23
69
|
// if encrypt==true but we have no key, prompt for it
|
|
@@ -37,21 +83,30 @@ const promptEncryptionKey = async (thisCommand) => {
|
|
|
37
83
|
]);
|
|
38
84
|
opts.key = answers.key;
|
|
39
85
|
} catch (e) {
|
|
40
|
-
|
|
41
|
-
process.exit(1);
|
|
86
|
+
return exitWith(1, 'Failed to get encryption key');
|
|
42
87
|
}
|
|
43
88
|
if (!opts.key) {
|
|
44
|
-
|
|
45
|
-
process.exit(1);
|
|
89
|
+
return exitWith(1, 'Failed to get encryption key');
|
|
46
90
|
}
|
|
47
91
|
}
|
|
48
92
|
};
|
|
49
93
|
|
|
50
94
|
/**
|
|
51
|
-
* hook: require a confirmation message to be accepted
|
|
95
|
+
* hook: require a confirmation message to be accepted unless forceOption (-f,--force) is used
|
|
96
|
+
*
|
|
97
|
+
* @param {string} message The message to confirm with user
|
|
98
|
+
* @param {object} options Additional options
|
|
52
99
|
*/
|
|
53
100
|
const confirmMessage = (message) => {
|
|
54
|
-
return async () => {
|
|
101
|
+
return async (command) => {
|
|
102
|
+
// if we have a force option, assume yes
|
|
103
|
+
const opts = command.opts();
|
|
104
|
+
if (opts?.force === true) {
|
|
105
|
+
// attempt to mimic the inquirer prompt exactly
|
|
106
|
+
console.log(`${green('?')} ${bold(message)} ${cyan('Yes')}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
55
110
|
const answers = await inquirer.prompt([
|
|
56
111
|
{
|
|
57
112
|
type: 'confirm',
|
|
@@ -61,13 +116,21 @@ const confirmMessage = (message) => {
|
|
|
61
116
|
},
|
|
62
117
|
]);
|
|
63
118
|
if (!answers.confirm) {
|
|
64
|
-
|
|
119
|
+
exitWith(0);
|
|
65
120
|
}
|
|
66
121
|
};
|
|
67
122
|
};
|
|
68
123
|
|
|
124
|
+
const forceOption = new Option(
|
|
125
|
+
'-f, --force',
|
|
126
|
+
`Automatically answer "yes" to all prompts, including potentially destructive requests, and run non-interactively.`
|
|
127
|
+
);
|
|
128
|
+
|
|
69
129
|
module.exports = {
|
|
70
|
-
|
|
130
|
+
getParseListWithChoices,
|
|
131
|
+
parseList,
|
|
132
|
+
parseURL,
|
|
71
133
|
promptEncryptionKey,
|
|
72
134
|
confirmMessage,
|
|
135
|
+
forceOption,
|
|
73
136
|
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper functions for the Strapi CLI
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const { isString, isArray } = require('lodash/fp');
|
|
9
|
+
|
|
10
|
+
const bytesPerKb = 1024;
|
|
11
|
+
const sizes = ['B ', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Convert bytes to a human readable formatted string, for example "1024" becomes "1KB"
|
|
15
|
+
*
|
|
16
|
+
* @param {number} bytes The bytes to be converted
|
|
17
|
+
* @param {number} decimals How many decimals to include in the final number
|
|
18
|
+
* @param {number} padStart Pad the string with space at the beginning so that it has at least this many characters
|
|
19
|
+
*/
|
|
20
|
+
const readableBytes = (bytes, decimals = 1, padStart = 0) => {
|
|
21
|
+
if (!bytes) {
|
|
22
|
+
return '0';
|
|
23
|
+
}
|
|
24
|
+
const i = Math.floor(Math.log(bytes) / Math.log(bytesPerKb));
|
|
25
|
+
const result = `${parseFloat((bytes / bytesPerKb ** i).toFixed(decimals))} ${sizes[i].padStart(
|
|
26
|
+
2
|
|
27
|
+
)}`;
|
|
28
|
+
|
|
29
|
+
return result.padStart(padStart);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* Display message(s) to console and then call process.exit with code.
|
|
35
|
+
* If code is zero, console.log and green text is used for messages, otherwise console.error and red text.
|
|
36
|
+
*
|
|
37
|
+
* @param {number} code Code to exit process with
|
|
38
|
+
* @param {string | Array} message Message(s) to display before exiting
|
|
39
|
+
*/
|
|
40
|
+
const exitWith = (code, message = undefined) => {
|
|
41
|
+
const logger = (message) => {
|
|
42
|
+
if (code === 0) {
|
|
43
|
+
console.log(chalk.green(message));
|
|
44
|
+
} else {
|
|
45
|
+
console.error(chalk.red(message));
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (isString(message)) {
|
|
50
|
+
logger(message);
|
|
51
|
+
} else if (isArray(message)) {
|
|
52
|
+
message.forEach((msg) => logger(msg));
|
|
53
|
+
}
|
|
54
|
+
process.exit(code);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* assert that a URL object has a protocol value
|
|
59
|
+
*
|
|
60
|
+
* @param {URL} url
|
|
61
|
+
* @param {string[]|string|undefined} [protocol]
|
|
62
|
+
*/
|
|
63
|
+
const assertUrlHasProtocol = (url, protocol = undefined) => {
|
|
64
|
+
if (!url.protocol) {
|
|
65
|
+
exitWith(1, `${url.toString()} does not have a protocol`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// if just checking for the existence of a protocol, return
|
|
69
|
+
if (!protocol) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (isString(protocol)) {
|
|
74
|
+
if (protocol !== url.protocol) {
|
|
75
|
+
exitWith(1, `${url.toString()} must have the protocol ${protocol}`);
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// assume an array
|
|
81
|
+
if (!protocol.some((protocol) => url.protocol === protocol)) {
|
|
82
|
+
return exitWith(
|
|
83
|
+
1,
|
|
84
|
+
`${url.toString()} must have one of the following protocols: ${protocol.join(',')}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Passes commander options to conditionCallback(). If it returns true, call isMetCallback otherwise call isNotMetCallback
|
|
91
|
+
*/
|
|
92
|
+
const ifOptions = (conditionCallback, isMetCallback = () => {}, isNotMetCallback = () => {}) => {
|
|
93
|
+
return async (command) => {
|
|
94
|
+
const opts = command.opts();
|
|
95
|
+
if (await conditionCallback(opts)) {
|
|
96
|
+
await isMetCallback(command);
|
|
97
|
+
} else {
|
|
98
|
+
await isNotMetCallback(command);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
exitWith,
|
|
105
|
+
assertUrlHasProtocol,
|
|
106
|
+
ifOptions,
|
|
107
|
+
readableBytes,
|
|
108
|
+
};
|
|
@@ -21,5 +21,5 @@ export interface CollectionTypeService extends BaseService {
|
|
|
21
21
|
export type Service = SingleTypeService | CollectionTypeService;
|
|
22
22
|
|
|
23
23
|
export type GenericService = Partial<Service> & {
|
|
24
|
-
[method: string | number | symbol]:
|
|
24
|
+
[method: string | number | symbol]: (...args: any) => any;
|
|
25
25
|
};
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { propOr } = require('lodash/fp');
|
|
4
|
+
|
|
3
5
|
const { ValidationError } = require('@strapi/utils').errors;
|
|
6
|
+
const {
|
|
7
|
+
hasDraftAndPublish,
|
|
8
|
+
constants: { PUBLISHED_AT_ATTRIBUTE },
|
|
9
|
+
} = require('@strapi/utils').contentTypes;
|
|
10
|
+
|
|
11
|
+
const setPublishedAt = (data) => {
|
|
12
|
+
data[PUBLISHED_AT_ATTRIBUTE] = propOr(new Date(), PUBLISHED_AT_ATTRIBUTE, data);
|
|
13
|
+
};
|
|
4
14
|
|
|
5
15
|
/**
|
|
6
16
|
* Returns a single type service to handle default core-api actions
|
|
@@ -27,7 +37,7 @@ const createSingleTypeService = ({ contentType }) => {
|
|
|
27
37
|
* @return {Promise}
|
|
28
38
|
*/
|
|
29
39
|
async createOrUpdate({ data, ...params } = {}) {
|
|
30
|
-
const entity = await this.find(params);
|
|
40
|
+
const entity = await this.find({ ...params, publicationState: 'preview' });
|
|
31
41
|
|
|
32
42
|
if (!entity) {
|
|
33
43
|
const count = await strapi.query(uid).count();
|
|
@@ -35,6 +45,9 @@ const createSingleTypeService = ({ contentType }) => {
|
|
|
35
45
|
throw new ValidationError('singleType.alreadyExists');
|
|
36
46
|
}
|
|
37
47
|
|
|
48
|
+
if (hasDraftAndPublish(contentType)) {
|
|
49
|
+
setPublishedAt(data);
|
|
50
|
+
}
|
|
38
51
|
return strapi.entityService.create(uid, { ...params, data });
|
|
39
52
|
}
|
|
40
53
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const delegate = require('delegates');
|
|
5
|
-
const { InvalidTimeError, InvalidDateError, InvalidDateTimeError } =
|
|
5
|
+
const { InvalidTimeError, InvalidDateError, InvalidDateTimeError, InvalidRelationError } =
|
|
6
6
|
require('@strapi/database').errors;
|
|
7
7
|
const {
|
|
8
8
|
webhook: webhookUtils,
|
|
@@ -34,7 +34,12 @@ const transformLoadParamsToQuery = (uid, field, params = {}, pagination = {}) =>
|
|
|
34
34
|
// TODO: those should be strapi events used by the webhooks not the other way arround
|
|
35
35
|
const { ENTRY_CREATE, ENTRY_UPDATE, ENTRY_DELETE } = webhookUtils.webhookEvents;
|
|
36
36
|
|
|
37
|
-
const databaseErrorsToTransform = [
|
|
37
|
+
const databaseErrorsToTransform = [
|
|
38
|
+
InvalidTimeError,
|
|
39
|
+
InvalidDateTimeError,
|
|
40
|
+
InvalidDateError,
|
|
41
|
+
InvalidRelationError,
|
|
42
|
+
];
|
|
38
43
|
|
|
39
44
|
const creationPipeline = (data, context) => {
|
|
40
45
|
return applyTransforms(data, context);
|
|
@@ -55,6 +60,11 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
55
60
|
},
|
|
56
61
|
|
|
57
62
|
async emitEvent(uid, event, entity) {
|
|
63
|
+
// Ignore audit log events to prevent infinite loops
|
|
64
|
+
if (uid === 'admin::audit-log') {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
58
68
|
const model = strapi.getModel(uid);
|
|
59
69
|
const sanitizedEntity = await sanitize.sanitizers.defaultSanitizeOutput(model, entity);
|
|
60
70
|
|
package/lib/services/errors.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const createError = require('http-errors');
|
|
4
|
-
const { NotFoundError, UnauthorizedError, ForbiddenError, PayloadTooLargeError } =
|
|
4
|
+
const { NotFoundError, UnauthorizedError, ForbiddenError, PayloadTooLargeError, RateLimitError } =
|
|
5
5
|
require('@strapi/utils').errors;
|
|
6
6
|
|
|
7
7
|
const mapErrorsAndStatus = [
|
|
@@ -21,6 +21,10 @@ const mapErrorsAndStatus = [
|
|
|
21
21
|
classError: PayloadTooLargeError,
|
|
22
22
|
status: 413,
|
|
23
23
|
},
|
|
24
|
+
{
|
|
25
|
+
classError: RateLimitError,
|
|
26
|
+
status: 429,
|
|
27
|
+
},
|
|
24
28
|
];
|
|
25
29
|
|
|
26
30
|
const formatApplicationError = (error) => {
|
|
@@ -1,16 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The event hub is Strapi's event control center.
|
|
3
5
|
*/
|
|
6
|
+
module.exports = function createEventHub() {
|
|
7
|
+
const listeners = new Map();
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
// Default subscriber to easily add listeners with the on() method
|
|
10
|
+
const defaultSubscriber = async (eventName, ...args) => {
|
|
11
|
+
if (listeners.has(eventName)) {
|
|
12
|
+
for (const listener of listeners.get(eventName)) {
|
|
13
|
+
await listener(...args);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
6
17
|
|
|
7
|
-
|
|
18
|
+
// Store of subscribers that will be called when an event is emitted
|
|
19
|
+
const subscribers = [defaultSubscriber];
|
|
8
20
|
|
|
9
|
-
|
|
21
|
+
const eventHub = {
|
|
22
|
+
async emit(eventName, ...args) {
|
|
23
|
+
for (const subscriber of subscribers) {
|
|
24
|
+
await subscriber(eventName, ...args);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
10
27
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
subscribe(subscriber) {
|
|
29
|
+
subscribers.push(subscriber);
|
|
30
|
+
|
|
31
|
+
// Return a function to remove the subscriber
|
|
32
|
+
return () => {
|
|
33
|
+
eventHub.unsubscribe(subscriber);
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
unsubscribe(subscriber) {
|
|
38
|
+
subscribers.splice(subscribers.indexOf(subscriber), 1);
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
on(eventName, listener) {
|
|
42
|
+
if (!listeners.has(eventName)) {
|
|
43
|
+
listeners.set(eventName, [listener]);
|
|
44
|
+
} else {
|
|
45
|
+
listeners.get(eventName).push(listener);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Return a function to remove the listener
|
|
49
|
+
return () => {
|
|
50
|
+
eventHub.off(eventName, listener);
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
off(eventName, listener) {
|
|
55
|
+
listeners.get(eventName).splice(listeners.get(eventName).indexOf(listener), 1);
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
once(eventName, listener) {
|
|
59
|
+
return eventHub.on(eventName, async (...args) => {
|
|
60
|
+
eventHub.off(eventName, listener);
|
|
61
|
+
return listener(...args);
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
destroy() {
|
|
66
|
+
listeners.clear();
|
|
67
|
+
subscribers.length = 0;
|
|
68
|
+
return this;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
...eventHub,
|
|
74
|
+
removeListener: eventHub.off,
|
|
75
|
+
removeAllListeners: eventHub.destroy,
|
|
76
|
+
addListener: eventHub.on,
|
|
77
|
+
};
|
|
16
78
|
};
|
package/lib/types/factories.d.ts
CHANGED
package/lib/utils/ee.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/strapi",
|
|
3
|
-
"version": "4.6.0-beta.
|
|
3
|
+
"version": "4.6.0-beta.2",
|
|
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",
|
|
@@ -80,19 +80,19 @@
|
|
|
80
80
|
"dependencies": {
|
|
81
81
|
"@koa/cors": "3.4.3",
|
|
82
82
|
"@koa/router": "10.1.1",
|
|
83
|
-
"@strapi/admin": "4.6.0-beta.
|
|
84
|
-
"@strapi/data-transfer": "4.6.0-beta.
|
|
85
|
-
"@strapi/database": "4.6.0-beta.
|
|
86
|
-
"@strapi/generate-new": "4.6.0-beta.
|
|
87
|
-
"@strapi/generators": "4.6.0-beta.
|
|
88
|
-
"@strapi/logger": "4.6.0-beta.
|
|
89
|
-
"@strapi/permissions": "4.6.0-beta.
|
|
90
|
-
"@strapi/plugin-content-manager": "4.6.0-beta.
|
|
91
|
-
"@strapi/plugin-content-type-builder": "4.6.0-beta.
|
|
92
|
-
"@strapi/plugin-email": "4.6.0-beta.
|
|
93
|
-
"@strapi/plugin-upload": "4.6.0-beta.
|
|
94
|
-
"@strapi/typescript-utils": "4.6.0-beta.
|
|
95
|
-
"@strapi/utils": "4.6.0-beta.
|
|
83
|
+
"@strapi/admin": "4.6.0-beta.2",
|
|
84
|
+
"@strapi/data-transfer": "4.6.0-beta.2",
|
|
85
|
+
"@strapi/database": "4.6.0-beta.2",
|
|
86
|
+
"@strapi/generate-new": "4.6.0-beta.2",
|
|
87
|
+
"@strapi/generators": "4.6.0-beta.2",
|
|
88
|
+
"@strapi/logger": "4.6.0-beta.2",
|
|
89
|
+
"@strapi/permissions": "4.6.0-beta.2",
|
|
90
|
+
"@strapi/plugin-content-manager": "4.6.0-beta.2",
|
|
91
|
+
"@strapi/plugin-content-type-builder": "4.6.0-beta.2",
|
|
92
|
+
"@strapi/plugin-email": "4.6.0-beta.2",
|
|
93
|
+
"@strapi/plugin-upload": "4.6.0-beta.2",
|
|
94
|
+
"@strapi/typescript-utils": "4.6.0-beta.2",
|
|
95
|
+
"@strapi/utils": "4.6.0-beta.2",
|
|
96
96
|
"bcryptjs": "2.4.3",
|
|
97
97
|
"boxen": "5.1.2",
|
|
98
98
|
"chalk": "4.1.2",
|
|
@@ -141,5 +141,5 @@
|
|
|
141
141
|
"node": ">=14.19.1 <=18.x.x",
|
|
142
142
|
"npm": ">=6.0.0"
|
|
143
143
|
},
|
|
144
|
-
"gitHead": "
|
|
144
|
+
"gitHead": "b852090f931cd21868c4016f24db2f9fdfc7a7ab"
|
|
145
145
|
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const bytesPerKb = 1024;
|
|
4
|
-
const sizes = ['B ', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
5
|
-
|
|
6
|
-
const readableBytes = (bytes, decimals = 1, padStart = 0) => {
|
|
7
|
-
if (!bytes) {
|
|
8
|
-
return '0';
|
|
9
|
-
}
|
|
10
|
-
const i = Math.floor(Math.log(bytes) / Math.log(bytesPerKb));
|
|
11
|
-
const result = `${parseFloat((bytes / bytesPerKb ** i).toFixed(decimals))} ${sizes[i].padStart(
|
|
12
|
-
2
|
|
13
|
-
)}`;
|
|
14
|
-
|
|
15
|
-
return result.padStart(padStart);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
module.exports = {
|
|
19
|
-
readableBytes,
|
|
20
|
-
};
|