@strapi/strapi 4.6.0-alpha.1 → 4.6.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/bin/strapi.js +68 -1
- package/lib/Strapi.js +8 -5
- package/lib/commands/opt-in-telemetry.js +2 -2
- package/lib/commands/opt-out-telemetry.js +2 -2
- package/lib/commands/transfer/export.js +161 -0
- package/lib/commands/transfer/import.js +152 -0
- package/lib/commands/transfer/utils.js +94 -0
- package/lib/commands/utils/commander.js +73 -0
- package/lib/commands/utils/index.js +20 -0
- package/lib/services/entity-service/components.js +10 -4
- package/lib/services/entity-validator/index.js +38 -30
- package/lib/services/event-hub.js +8 -70
- package/lib/services/metrics/admin-user-hash.js +21 -0
- package/lib/services/metrics/index.js +6 -4
- package/lib/services/metrics/middleware.js +1 -1
- package/lib/services/metrics/sender.js +17 -9
- package/lib/types/core/attributes/relation.d.ts +9 -12
- package/lib/types/core/schemas/index.d.ts +6 -1
- package/lib/types/core/strapi/index.d.ts +10 -4
- package/lib/types/factories.d.ts +2 -2
- package/lib/utils/success.js +1 -1
- package/package.json +17 -16
package/bin/strapi.js
CHANGED
|
@@ -5,13 +5,16 @@
|
|
|
5
5
|
// FIXME
|
|
6
6
|
/* eslint-disable import/extensions */
|
|
7
7
|
const _ = require('lodash');
|
|
8
|
+
const path = require('path');
|
|
8
9
|
const resolveCwd = require('resolve-cwd');
|
|
9
10
|
const { yellow } = require('chalk');
|
|
10
|
-
const { Command } = require('commander');
|
|
11
|
+
const { Command, Option } = require('commander');
|
|
12
|
+
const inquirer = require('inquirer');
|
|
11
13
|
|
|
12
14
|
const program = new Command();
|
|
13
15
|
|
|
14
16
|
const packageJSON = require('../package.json');
|
|
17
|
+
const { promptEncryptionKey, confirmMessage } = require('../lib/commands/utils/commander');
|
|
15
18
|
|
|
16
19
|
const checkCwdIsStrapiApp = (name) => {
|
|
17
20
|
const logErrorAndExit = () => {
|
|
@@ -255,4 +258,68 @@ program
|
|
|
255
258
|
.option('-s, --silent', `Run the generation silently, without any output`, false)
|
|
256
259
|
.action(getLocalScript('ts/generate-types'));
|
|
257
260
|
|
|
261
|
+
// `$ strapi export`
|
|
262
|
+
program
|
|
263
|
+
.command('export')
|
|
264
|
+
.description('Export data from Strapi to file')
|
|
265
|
+
.addOption(
|
|
266
|
+
new Option('--no-encrypt', `Disables 'aes-128-ecb' encryption of the output file`).default(true)
|
|
267
|
+
)
|
|
268
|
+
.addOption(new Option('--no-compress', 'Disables gzip compression of output file').default(true))
|
|
269
|
+
.addOption(
|
|
270
|
+
new Option(
|
|
271
|
+
'-k, --key <string>',
|
|
272
|
+
'Provide encryption key in command instead of using the prompt'
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
.addOption(new Option('-f, --file <file>', 'name to use for exported file (without extensions)'))
|
|
276
|
+
.allowExcessArguments(false)
|
|
277
|
+
.hook('preAction', promptEncryptionKey)
|
|
278
|
+
.action(getLocalScript('transfer/export'));
|
|
279
|
+
|
|
280
|
+
// `$ strapi import`
|
|
281
|
+
program
|
|
282
|
+
.command('import')
|
|
283
|
+
.description('Import data from file to Strapi')
|
|
284
|
+
.requiredOption(
|
|
285
|
+
'-f, --file <file>',
|
|
286
|
+
'path and filename for the Strapi export file you want to import'
|
|
287
|
+
)
|
|
288
|
+
.addOption(
|
|
289
|
+
new Option(
|
|
290
|
+
'-k, --key <string>',
|
|
291
|
+
'Provide encryption key in command instead of using the prompt'
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
.allowExcessArguments(false)
|
|
295
|
+
.hook('preAction', async (thisCommand) => {
|
|
296
|
+
const opts = thisCommand.opts();
|
|
297
|
+
const ext = path.extname(String(opts.file));
|
|
298
|
+
|
|
299
|
+
// check extension to guess if we should prompt for key
|
|
300
|
+
if (ext === '.enc') {
|
|
301
|
+
if (!opts.key) {
|
|
302
|
+
const answers = await inquirer.prompt([
|
|
303
|
+
{
|
|
304
|
+
type: 'password',
|
|
305
|
+
message: 'Please enter your decryption key',
|
|
306
|
+
name: 'key',
|
|
307
|
+
},
|
|
308
|
+
]);
|
|
309
|
+
if (!answers.key?.length) {
|
|
310
|
+
console.log('No key entered, aborting import.');
|
|
311
|
+
process.exit(0);
|
|
312
|
+
}
|
|
313
|
+
opts.key = answers.key;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
.hook(
|
|
318
|
+
'preAction',
|
|
319
|
+
confirmMessage(
|
|
320
|
+
'The import will delete all data in your database. Are you sure you want to proceed?'
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
.action(getLocalScript('transfer/import'));
|
|
324
|
+
|
|
258
325
|
program.parseAsync(process.argv);
|
package/lib/Strapi.js
CHANGED
|
@@ -225,7 +225,7 @@ class Strapi {
|
|
|
225
225
|
|
|
226
226
|
await this.runLifecyclesFunctions(LIFECYCLES.DESTROY);
|
|
227
227
|
|
|
228
|
-
this.eventHub.
|
|
228
|
+
this.eventHub.removeAllListeners();
|
|
229
229
|
|
|
230
230
|
if (_.has(this, 'db')) {
|
|
231
231
|
await this.db.destroy();
|
|
@@ -242,11 +242,14 @@ class Strapi {
|
|
|
242
242
|
sendStartupTelemetry() {
|
|
243
243
|
// Emit started event.
|
|
244
244
|
// do not await to avoid slower startup
|
|
245
|
+
// This event is anonymous
|
|
245
246
|
this.telemetry.send('didStartServer', {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
groupProperties: {
|
|
248
|
+
database: strapi.config.get('database.connection.client'),
|
|
249
|
+
plugins: Object.keys(strapi.plugins),
|
|
250
|
+
// TODO: to add back
|
|
251
|
+
// providers: this.config.installedProviders,
|
|
252
|
+
},
|
|
250
253
|
});
|
|
251
254
|
}
|
|
252
255
|
|
|
@@ -53,12 +53,12 @@ const generateNewPackageJSON = (packageObj) => {
|
|
|
53
53
|
|
|
54
54
|
const sendEvent = async (uuid) => {
|
|
55
55
|
try {
|
|
56
|
-
await fetch('https://analytics.strapi.io/track', {
|
|
56
|
+
await fetch('https://analytics.strapi.io/api/v2/track', {
|
|
57
57
|
method: 'POST',
|
|
58
58
|
body: JSON.stringify({
|
|
59
59
|
event: 'didOptInTelemetry',
|
|
60
|
-
uuid,
|
|
61
60
|
deviceId: machineID(),
|
|
61
|
+
groupProperties: { projectId: uuid },
|
|
62
62
|
}),
|
|
63
63
|
headers: { 'Content-Type': 'application/json' },
|
|
64
64
|
});
|
|
@@ -28,12 +28,12 @@ const writePackageJSON = async (path, file, spacing) => {
|
|
|
28
28
|
|
|
29
29
|
const sendEvent = async (uuid) => {
|
|
30
30
|
try {
|
|
31
|
-
await fetch('https://analytics.strapi.io/track', {
|
|
31
|
+
await fetch('https://analytics.strapi.io/api/v2/track', {
|
|
32
32
|
method: 'POST',
|
|
33
33
|
body: JSON.stringify({
|
|
34
34
|
event: 'didOptOutTelemetry',
|
|
35
|
-
uuid,
|
|
36
35
|
deviceId: machineID(),
|
|
36
|
+
groupProperties: { projectId: uuid },
|
|
37
37
|
}),
|
|
38
38
|
headers: { 'Content-Type': 'application/json' },
|
|
39
39
|
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
createLocalFileDestinationProvider,
|
|
5
|
+
createLocalStrapiSourceProvider,
|
|
6
|
+
createTransferEngine,
|
|
7
|
+
// TODO: we need to solve this issue with typescript modules
|
|
8
|
+
// eslint-disable-next-line import/no-unresolved, node/no-missing-require
|
|
9
|
+
} = require('@strapi/data-transfer');
|
|
10
|
+
const { isObject, isString, isFinite, toNumber } = require('lodash/fp');
|
|
11
|
+
const fs = require('fs-extra');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
getDefaultExportName,
|
|
16
|
+
buildTransferTable,
|
|
17
|
+
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
18
|
+
createStrapiInstance,
|
|
19
|
+
} = require('./utils');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef ImportCommandOptions Options given to the CLI import command
|
|
23
|
+
*
|
|
24
|
+
* @property {string} [file] The file path to import
|
|
25
|
+
* @property {boolean} [encrypt] Used to encrypt the final archive
|
|
26
|
+
* @property {string} [key] Encryption key, only useful when encryption is enabled
|
|
27
|
+
* @property {boolean} [compress] Used to compress the final archive
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const logger = console;
|
|
31
|
+
|
|
32
|
+
const BYTES_IN_MB = 1024 * 1024;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Import command.
|
|
36
|
+
*
|
|
37
|
+
* It transfers data from a local file to a local strapi instance
|
|
38
|
+
*
|
|
39
|
+
* @param {ImportCommandOptions} opts
|
|
40
|
+
*/
|
|
41
|
+
module.exports = async (opts) => {
|
|
42
|
+
// Validate inputs from Commander
|
|
43
|
+
if (!isObject(opts)) {
|
|
44
|
+
logger.error('Could not parse command arguments');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const strapi = await createStrapiInstance();
|
|
49
|
+
|
|
50
|
+
const source = createSourceProvider(strapi);
|
|
51
|
+
const destination = createDestinationProvider(opts);
|
|
52
|
+
|
|
53
|
+
const engine = createTransferEngine(source, destination, {
|
|
54
|
+
versionStrategy: 'ignore', // for an export to file, versionStrategy will always be skipped
|
|
55
|
+
schemaStrategy: 'ignore', // for an export to file, schemaStrategy will always be skipped
|
|
56
|
+
transforms: {
|
|
57
|
+
links: [
|
|
58
|
+
{
|
|
59
|
+
filter(link) {
|
|
60
|
+
return (
|
|
61
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.left.type) &&
|
|
62
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.right.type)
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
entities: [
|
|
68
|
+
{
|
|
69
|
+
filter(entity) {
|
|
70
|
+
return !DEFAULT_IGNORED_CONTENT_TYPES.includes(entity.type);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
logger.log(`Starting export...`);
|
|
79
|
+
|
|
80
|
+
const progress = engine.progress.stream;
|
|
81
|
+
|
|
82
|
+
const telemetryPayload = (/* payload */) => {
|
|
83
|
+
return {
|
|
84
|
+
eventProperties: {
|
|
85
|
+
source: engine.sourceProvider.name,
|
|
86
|
+
destination: engine.destinationProvider.name,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
progress.on('transfer::start', (payload) => {
|
|
92
|
+
strapi.telemetry.send('didDEITSProcessStart', telemetryPayload(payload));
|
|
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
|
+
});
|
|
102
|
+
|
|
103
|
+
const results = await engine.transfer();
|
|
104
|
+
const outFile = results.destination.file.path;
|
|
105
|
+
|
|
106
|
+
const table = buildTransferTable(results.engine);
|
|
107
|
+
logger.log(table.toString());
|
|
108
|
+
|
|
109
|
+
const outFileExists = await fs.pathExists(outFile);
|
|
110
|
+
if (!outFileExists) {
|
|
111
|
+
throw new Error(`Export file not created "${outFile}"`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
logger.log(`${chalk.bold('Export process has been completed successfully!')}`);
|
|
115
|
+
logger.log(`Export archive is in ${chalk.green(outFile)}`);
|
|
116
|
+
process.exit(0);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
logger.error('Export process failed unexpectedly:', e.toString());
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* It creates a local strapi destination provider
|
|
125
|
+
*/
|
|
126
|
+
const createSourceProvider = (strapi) => {
|
|
127
|
+
return createLocalStrapiSourceProvider({
|
|
128
|
+
async getStrapi() {
|
|
129
|
+
return strapi;
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* It creates a local file destination provider based on the given options
|
|
136
|
+
*
|
|
137
|
+
* @param {ImportCommandOptions} opts
|
|
138
|
+
*/
|
|
139
|
+
const createDestinationProvider = (opts) => {
|
|
140
|
+
const { file, compress, encrypt, key, maxSizeJsonl } = opts;
|
|
141
|
+
|
|
142
|
+
const filepath = isString(file) && file.length > 0 ? file : getDefaultExportName();
|
|
143
|
+
|
|
144
|
+
const maxSizeJsonlInMb = isFinite(toNumber(maxSizeJsonl))
|
|
145
|
+
? toNumber(maxSizeJsonl) * BYTES_IN_MB
|
|
146
|
+
: undefined;
|
|
147
|
+
|
|
148
|
+
return createLocalFileDestinationProvider({
|
|
149
|
+
file: {
|
|
150
|
+
path: filepath,
|
|
151
|
+
maxSizeJsonl: maxSizeJsonlInMb,
|
|
152
|
+
},
|
|
153
|
+
encryption: {
|
|
154
|
+
enabled: encrypt,
|
|
155
|
+
key: encrypt ? key : undefined,
|
|
156
|
+
},
|
|
157
|
+
compression: {
|
|
158
|
+
enabled: compress,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
createLocalFileSourceProvider,
|
|
5
|
+
createLocalStrapiDestinationProvider,
|
|
6
|
+
createTransferEngine,
|
|
7
|
+
DEFAULT_VERSION_STRATEGY,
|
|
8
|
+
DEFAULT_SCHEMA_STRATEGY,
|
|
9
|
+
DEFAULT_CONFLICT_STRATEGY,
|
|
10
|
+
// TODO: we need to solve this issue with typescript modules
|
|
11
|
+
// eslint-disable-next-line import/no-unresolved, node/no-missing-require
|
|
12
|
+
} = require('@strapi/data-transfer');
|
|
13
|
+
const { isObject } = require('lodash/fp');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const strapi = require('../../index');
|
|
17
|
+
const { buildTransferTable, DEFAULT_IGNORED_CONTENT_TYPES } = require('./utils');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {import('@strapi/data-transfer').ILocalFileSourceProviderOptions} ILocalFileSourceProviderOptions
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const logger = console;
|
|
24
|
+
|
|
25
|
+
module.exports = async (opts) => {
|
|
26
|
+
// validate inputs from Commander
|
|
27
|
+
if (!isObject(opts)) {
|
|
28
|
+
logger.error('Could not parse arguments');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* From strapi backup file
|
|
34
|
+
*/
|
|
35
|
+
const sourceOptions = getLocalFileSourceOptions(opts);
|
|
36
|
+
|
|
37
|
+
const source = createLocalFileSourceProvider(sourceOptions);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* To local Strapi instance
|
|
41
|
+
*/
|
|
42
|
+
const strapiInstance = await strapi(await strapi.compile()).load();
|
|
43
|
+
|
|
44
|
+
const destinationOptions = {
|
|
45
|
+
async getStrapi() {
|
|
46
|
+
return strapiInstance;
|
|
47
|
+
},
|
|
48
|
+
strategy: opts.conflictStrategy || DEFAULT_CONFLICT_STRATEGY,
|
|
49
|
+
restore: {
|
|
50
|
+
entities: { exclude: DEFAULT_IGNORED_CONTENT_TYPES },
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
const destination = createLocalStrapiDestinationProvider(destinationOptions);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Configure and run the transfer engine
|
|
57
|
+
*/
|
|
58
|
+
const engineOptions = {
|
|
59
|
+
versionStrategy: opts.versionStrategy || DEFAULT_VERSION_STRATEGY,
|
|
60
|
+
schemaStrategy: opts.schemaStrategy || DEFAULT_SCHEMA_STRATEGY,
|
|
61
|
+
exclude: opts.exclude,
|
|
62
|
+
rules: {
|
|
63
|
+
links: [
|
|
64
|
+
{
|
|
65
|
+
filter(link) {
|
|
66
|
+
return (
|
|
67
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.left.type) &&
|
|
68
|
+
!DEFAULT_IGNORED_CONTENT_TYPES.includes(link.right.type)
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
entities: [
|
|
74
|
+
{
|
|
75
|
+
filter: (entity) => !DEFAULT_IGNORED_CONTENT_TYPES.includes(entity.type),
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
const engine = createTransferEngine(source, destination, engineOptions);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
logger.info('Starting import...');
|
|
84
|
+
|
|
85
|
+
const progress = engine.progress.stream;
|
|
86
|
+
const telemetryPayload = (/* payload */) => {
|
|
87
|
+
return {
|
|
88
|
+
eventProperties: {
|
|
89
|
+
source: engine.sourceProvider.name,
|
|
90
|
+
destination: engine.destinationProvider.name,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
progress.on('transfer::start', (payload) => {
|
|
96
|
+
strapiInstance.telemetry.send('didDEITSProcessStart', telemetryPayload(payload));
|
|
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
|
+
});
|
|
106
|
+
|
|
107
|
+
const results = await engine.transfer();
|
|
108
|
+
const table = buildTransferTable(results.engine);
|
|
109
|
+
logger.info(table.toString());
|
|
110
|
+
|
|
111
|
+
logger.info('Import process has been completed successfully!');
|
|
112
|
+
process.exit(0);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
logger.error('Import process failed unexpectedly:');
|
|
115
|
+
logger.error(e);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Infer local file source provider options based on a given filename
|
|
122
|
+
*
|
|
123
|
+
* @param {{ file: string; key?: string }} opts
|
|
124
|
+
*
|
|
125
|
+
* @return {ILocalFileSourceProviderOptions}
|
|
126
|
+
*/
|
|
127
|
+
const getLocalFileSourceOptions = (opts) => {
|
|
128
|
+
/**
|
|
129
|
+
* @type {ILocalFileSourceProviderOptions}
|
|
130
|
+
*/
|
|
131
|
+
const options = {
|
|
132
|
+
file: { path: opts.file },
|
|
133
|
+
compression: { enabled: false },
|
|
134
|
+
encryption: { enabled: false },
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const { extname, parse } = path;
|
|
138
|
+
|
|
139
|
+
let file = options.file.path;
|
|
140
|
+
|
|
141
|
+
if (extname(file) === '.enc') {
|
|
142
|
+
file = parse(file).name;
|
|
143
|
+
options.encryption = { enabled: true, key: opts.key };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (extname(file) === '.gz') {
|
|
147
|
+
file = parse(file).name;
|
|
148
|
+
options.compression = { enabled: true };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return options;
|
|
152
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const Table = require('cli-table3');
|
|
5
|
+
const { readableBytes } = require('../utils');
|
|
6
|
+
const strapi = require('../../index');
|
|
7
|
+
|
|
8
|
+
const pad = (n) => {
|
|
9
|
+
return (n < 10 ? '0' : '') + String(n);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const yyyymmddHHMMSS = () => {
|
|
13
|
+
const date = new Date();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
date.getFullYear() +
|
|
17
|
+
pad(date.getMonth() + 1) +
|
|
18
|
+
pad(date.getDate()) +
|
|
19
|
+
pad(date.getHours()) +
|
|
20
|
+
pad(date.getMinutes()) +
|
|
21
|
+
pad(date.getSeconds())
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const getDefaultExportName = () => {
|
|
26
|
+
return `export_${yyyymmddHHMMSS()}`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const buildTransferTable = (resultData) => {
|
|
30
|
+
// Build pretty table
|
|
31
|
+
const table = new Table({
|
|
32
|
+
head: ['Type', 'Count', 'Size'].map((text) => chalk.bold.blue(text)),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
let totalBytes = 0;
|
|
36
|
+
let totalItems = 0;
|
|
37
|
+
Object.keys(resultData).forEach((key) => {
|
|
38
|
+
const item = resultData[key];
|
|
39
|
+
|
|
40
|
+
table.push([
|
|
41
|
+
{ hAlign: 'left', content: chalk.bold(key) },
|
|
42
|
+
{ hAlign: 'right', content: item.count },
|
|
43
|
+
{ hAlign: 'right', content: `${readableBytes(item.bytes, 1, 11)} ` },
|
|
44
|
+
]);
|
|
45
|
+
totalBytes += item.bytes;
|
|
46
|
+
totalItems += item.count;
|
|
47
|
+
|
|
48
|
+
if (item.aggregates) {
|
|
49
|
+
Object.keys(item.aggregates)
|
|
50
|
+
.sort()
|
|
51
|
+
.forEach((subkey) => {
|
|
52
|
+
const subitem = item.aggregates[subkey];
|
|
53
|
+
|
|
54
|
+
table.push([
|
|
55
|
+
{ hAlign: 'left', content: `-- ${chalk.bold.grey(subkey)}` },
|
|
56
|
+
{ hAlign: 'right', content: chalk.grey(subitem.count) },
|
|
57
|
+
{ hAlign: 'right', content: chalk.grey(`(${readableBytes(subitem.bytes, 1, 11)})`) },
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
table.push([
|
|
63
|
+
{ hAlign: 'left', content: chalk.bold.green('Total') },
|
|
64
|
+
{ hAlign: 'right', content: chalk.bold.green(totalItems) },
|
|
65
|
+
{ hAlign: 'right', content: `${chalk.bold.green(readableBytes(totalBytes, 1, 11))} ` },
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
return table;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const DEFAULT_IGNORED_CONTENT_TYPES = [
|
|
72
|
+
'admin::permission',
|
|
73
|
+
'admin::user',
|
|
74
|
+
'admin::role',
|
|
75
|
+
'admin::api-token',
|
|
76
|
+
'admin::api-token-permission',
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const createStrapiInstance = async (logLevel = 'error') => {
|
|
80
|
+
const appContext = await strapi.compile();
|
|
81
|
+
const app = strapi(appContext);
|
|
82
|
+
|
|
83
|
+
app.log.level = logLevel;
|
|
84
|
+
|
|
85
|
+
return app.load();
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
buildTransferTable,
|
|
90
|
+
getDefaultExportName,
|
|
91
|
+
yyyymmddHHMMSS,
|
|
92
|
+
DEFAULT_IGNORED_CONTENT_TYPES,
|
|
93
|
+
createStrapiInstance,
|
|
94
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* argsParser: Parse a comma-delimited string as an array
|
|
7
|
+
*/
|
|
8
|
+
const parseInputList = (value) => {
|
|
9
|
+
return value.split(',');
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* hook: if encrypt==true and key not provided, prompt for it
|
|
14
|
+
*/
|
|
15
|
+
const promptEncryptionKey = async (thisCommand) => {
|
|
16
|
+
const opts = thisCommand.opts();
|
|
17
|
+
|
|
18
|
+
if (!opts.encrypt && opts.key) {
|
|
19
|
+
console.error('Key may not be present unless encryption is used');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// if encrypt==true but we have no key, prompt for it
|
|
24
|
+
if (opts.encrypt && !(opts.key && opts.key.length > 0)) {
|
|
25
|
+
try {
|
|
26
|
+
const answers = await inquirer.prompt([
|
|
27
|
+
{
|
|
28
|
+
type: 'password',
|
|
29
|
+
message: 'Please enter an encryption key',
|
|
30
|
+
name: 'key',
|
|
31
|
+
validate(key) {
|
|
32
|
+
if (key.length > 0) return true;
|
|
33
|
+
|
|
34
|
+
return 'Key must be present when using the encrypt option';
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
opts.key = answers.key;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error('Failed to get encryption key');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
if (!opts.key) {
|
|
44
|
+
console.error('Failed to get encryption key');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* hook: require a confirmation message to be accepted
|
|
52
|
+
*/
|
|
53
|
+
const confirmMessage = (message) => {
|
|
54
|
+
return async () => {
|
|
55
|
+
const answers = await inquirer.prompt([
|
|
56
|
+
{
|
|
57
|
+
type: 'confirm',
|
|
58
|
+
message,
|
|
59
|
+
name: `confirm`,
|
|
60
|
+
default: false,
|
|
61
|
+
},
|
|
62
|
+
]);
|
|
63
|
+
if (!answers.confirm) {
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
parseInputList,
|
|
71
|
+
promptEncryptionKey,
|
|
72
|
+
confirmMessage,
|
|
73
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
|
-
const { has, prop, omit, toString } = require('lodash/fp');
|
|
4
|
+
const { has, prop, omit, toString, pipe, assign } = require('lodash/fp');
|
|
5
5
|
|
|
6
6
|
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
|
|
7
7
|
const { ApplicationError } = require('@strapi/utils').errors;
|
|
@@ -311,10 +311,16 @@ const createComponent = async (uid, data) => {
|
|
|
311
311
|
const model = strapi.getModel(uid);
|
|
312
312
|
|
|
313
313
|
const componentData = await createComponents(uid, data);
|
|
314
|
+
const transform = pipe(
|
|
315
|
+
// Make sure we don't save the component with a pre-defined ID
|
|
316
|
+
omit('id'),
|
|
317
|
+
// Remove the component data from the original data object ...
|
|
318
|
+
(payload) => omitComponentData(model, payload),
|
|
319
|
+
// ... and assign the newly created component instead
|
|
320
|
+
assign(componentData)
|
|
321
|
+
);
|
|
314
322
|
|
|
315
|
-
return strapi.query(uid).create({
|
|
316
|
-
data: Object.assign(omitComponentData(model, data), componentData),
|
|
317
|
-
});
|
|
323
|
+
return strapi.query(uid).create({ data: transform(data) });
|
|
318
324
|
};
|
|
319
325
|
|
|
320
326
|
// components can have nested compos so this must be recursive
|
|
@@ -293,40 +293,48 @@ const buildRelationsStore = ({ uid, data }) => {
|
|
|
293
293
|
break;
|
|
294
294
|
}
|
|
295
295
|
case 'component': {
|
|
296
|
-
return castArray(value).reduce(
|
|
297
|
-
(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
296
|
+
return castArray(value).reduce((relationsStore, componentValue) => {
|
|
297
|
+
if (!attribute.component) {
|
|
298
|
+
throw new ValidationError(
|
|
299
|
+
`Cannot build relations store from component, component identifier is undefined`
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return mergeWith(
|
|
304
|
+
relationsStore,
|
|
305
|
+
buildRelationsStore({
|
|
306
|
+
uid: attribute.component,
|
|
307
|
+
data: componentValue,
|
|
308
|
+
}),
|
|
309
|
+
(objValue, srcValue) => {
|
|
310
|
+
if (isArray(objValue)) {
|
|
311
|
+
return objValue.concat(srcValue);
|
|
308
312
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
);
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
}, result);
|
|
312
316
|
}
|
|
313
317
|
case 'dynamiczone': {
|
|
314
|
-
return value.reduce(
|
|
315
|
-
(
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
318
|
+
return value.reduce((relationsStore, dzValue) => {
|
|
319
|
+
if (!dzValue.__component) {
|
|
320
|
+
throw new ValidationError(
|
|
321
|
+
`Cannot build relations store from dynamiczone, component identifier is undefined`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return mergeWith(
|
|
326
|
+
relationsStore,
|
|
327
|
+
buildRelationsStore({
|
|
328
|
+
uid: dzValue.__component,
|
|
329
|
+
data: dzValue,
|
|
330
|
+
}),
|
|
331
|
+
(objValue, srcValue) => {
|
|
332
|
+
if (isArray(objValue)) {
|
|
333
|
+
return objValue.concat(srcValue);
|
|
326
334
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
);
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
}, result);
|
|
330
338
|
}
|
|
331
339
|
default:
|
|
332
340
|
break;
|
|
@@ -1,78 +1,16 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* The event hub is Strapi's event control center.
|
|
5
3
|
*/
|
|
6
|
-
module.exports = function createEventHub() {
|
|
7
|
-
const listeners = new Map();
|
|
8
|
-
|
|
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
|
-
};
|
|
17
|
-
|
|
18
|
-
// Store of subscribers that will be called when an event is emitted
|
|
19
|
-
const subscribers = [defaultSubscriber];
|
|
20
|
-
|
|
21
|
-
const eventHub = {
|
|
22
|
-
async emit(eventName, ...args) {
|
|
23
|
-
for (const subscriber of subscribers) {
|
|
24
|
-
await subscriber(eventName, ...args);
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
|
|
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
4
|
|
|
37
|
-
|
|
38
|
-
subscribers.splice(subscribers.indexOf(subscriber), 1);
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
on(eventName, listener) {
|
|
42
|
-
if (!listeners.has(eventName)) {
|
|
43
|
-
listeners.set(eventName, []);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
listeners.get(eventName).push(listener);
|
|
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
|
-
},
|
|
5
|
+
'use strict';
|
|
57
6
|
|
|
58
|
-
|
|
59
|
-
return eventHub.on(eventName, async (...args) => {
|
|
60
|
-
eventHub.off(eventName, listener);
|
|
61
|
-
return listener(...args);
|
|
62
|
-
});
|
|
63
|
-
},
|
|
7
|
+
const EventEmitter = require('events');
|
|
64
8
|
|
|
65
|
-
|
|
66
|
-
listeners.clear();
|
|
67
|
-
subscribers.length = 0;
|
|
68
|
-
return this;
|
|
69
|
-
},
|
|
70
|
-
};
|
|
9
|
+
class EventHub extends EventEmitter {}
|
|
71
10
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
};
|
|
11
|
+
/**
|
|
12
|
+
* Expose a factory function instead of the class
|
|
13
|
+
*/
|
|
14
|
+
module.exports = function createEventHub(opts) {
|
|
15
|
+
return new EventHub(opts);
|
|
78
16
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate an admin user hash
|
|
7
|
+
*
|
|
8
|
+
* @param {Strapi.Strapi} strapi
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
const generateAdminUserHash = (strapi) => {
|
|
12
|
+
const ctx = strapi?.requestContext?.get();
|
|
13
|
+
if (!ctx?.state?.user) {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
return crypto.createHash('sha256').update(ctx.state.user.email).digest('hex');
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
generateAdminUserHash,
|
|
21
|
+
};
|
|
@@ -55,10 +55,12 @@ const createTelemetryInstance = (strapi) => {
|
|
|
55
55
|
return sendEvent(
|
|
56
56
|
'didCheckLicense',
|
|
57
57
|
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
groupProperties: {
|
|
59
|
+
licenseInfo: {
|
|
60
|
+
...ee.licenseInfo,
|
|
61
|
+
projectHash: hashProject(strapi),
|
|
62
|
+
dependencyHash: hashDep(strapi),
|
|
63
|
+
},
|
|
62
64
|
},
|
|
63
65
|
},
|
|
64
66
|
{
|
|
@@ -19,7 +19,7 @@ const createMiddleware = ({ sendEvent }) => {
|
|
|
19
19
|
|
|
20
20
|
// Send max. 1000 events per day.
|
|
21
21
|
if (_state.counter < 1000) {
|
|
22
|
-
sendEvent('didReceiveRequest', { url: ctx.request.url });
|
|
22
|
+
sendEvent('didReceiveRequest', { eventProperties: { url: ctx.request.url } });
|
|
23
23
|
|
|
24
24
|
// Increase counter.
|
|
25
25
|
_state.counter += 1;
|
|
@@ -10,7 +10,7 @@ const { isUsingTypeScriptSync } = require('@strapi/typescript-utils');
|
|
|
10
10
|
const { env } = require('@strapi/utils');
|
|
11
11
|
const ee = require('../../utils/ee');
|
|
12
12
|
const machineID = require('../../utils/machine-id');
|
|
13
|
-
const
|
|
13
|
+
const { generateAdminUserHash } = require('./admin-user-hash');
|
|
14
14
|
|
|
15
15
|
const defaultQueryOpts = {
|
|
16
16
|
timeout: 1000,
|
|
@@ -42,41 +42,49 @@ module.exports = (strapi) => {
|
|
|
42
42
|
const serverRootPath = strapi.dirs.app.root;
|
|
43
43
|
const adminRootPath = path.join(strapi.dirs.app.root, 'src', 'admin');
|
|
44
44
|
|
|
45
|
-
const
|
|
45
|
+
const anonymousUserProperties = {
|
|
46
46
|
environment: strapi.config.environment,
|
|
47
47
|
os: os.type(),
|
|
48
48
|
osPlatform: os.platform(),
|
|
49
49
|
osArch: os.arch(),
|
|
50
50
|
osRelease: os.release(),
|
|
51
51
|
nodeVersion: process.versions.node,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const anonymousGroupProperties = {
|
|
52
55
|
docker: process.env.DOCKER || isDocker(),
|
|
53
56
|
isCI: ciEnv.isCI,
|
|
54
57
|
version: strapi.config.get('info.strapi'),
|
|
55
58
|
projectType: isEE ? 'Enterprise' : 'Community',
|
|
56
59
|
useTypescriptOnServer: isUsingTypeScriptSync(serverRootPath),
|
|
57
60
|
useTypescriptOnAdmin: isUsingTypeScriptSync(adminRootPath),
|
|
61
|
+
projectId: uuid,
|
|
58
62
|
isHostedOnStrapiCloud: env('STRAPI_HOSTING', null) === 'strapi.cloud',
|
|
59
63
|
};
|
|
60
64
|
|
|
61
|
-
addPackageJsonStrapiMetadata(
|
|
65
|
+
addPackageJsonStrapiMetadata(anonymousGroupProperties, strapi);
|
|
62
66
|
|
|
63
67
|
return async (event, payload = {}, opts = {}) => {
|
|
68
|
+
const userId = generateAdminUserHash(strapi);
|
|
69
|
+
|
|
64
70
|
const reqParams = {
|
|
65
71
|
method: 'POST',
|
|
66
72
|
body: JSON.stringify({
|
|
67
73
|
event,
|
|
68
|
-
|
|
74
|
+
userId,
|
|
69
75
|
deviceId,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
eventProperties: payload.eventProperties,
|
|
77
|
+
userProperties: userId ? { ...anonymousUserProperties, ...payload.userProperties } : {},
|
|
78
|
+
groupProperties: {
|
|
79
|
+
...anonymousGroupProperties,
|
|
80
|
+
...payload.groupProperties,
|
|
81
|
+
},
|
|
74
82
|
}),
|
|
75
83
|
..._.merge({}, defaultQueryOpts, opts),
|
|
76
84
|
};
|
|
77
85
|
|
|
78
86
|
try {
|
|
79
|
-
const res = await fetch(`${ANALYTICS_URI}/track`, reqParams);
|
|
87
|
+
const res = await fetch(`${ANALYTICS_URI}/api/v2/track`, reqParams);
|
|
80
88
|
return res.ok;
|
|
81
89
|
} catch (err) {
|
|
82
90
|
return false;
|
|
@@ -3,7 +3,7 @@ import { Attribute, ConfigurableOption, PrivateOption } from './base';
|
|
|
3
3
|
import { GetAttributesByType, GetAttributesValues } from './utils';
|
|
4
4
|
|
|
5
5
|
export type BasicRelationsType = 'oneToOne' | 'oneToMany' | 'manyToOne' | 'manyToMany';
|
|
6
|
-
export type PolymorphicRelationsType =
|
|
6
|
+
export type PolymorphicRelationsType = 'morphToOne' | 'morphToMany' | 'morphOne' | 'morphMany';
|
|
7
7
|
export type RelationsType = BasicRelationsType | PolymorphicRelationsType;
|
|
8
8
|
|
|
9
9
|
export interface BasicRelationAttributeProperties<
|
|
@@ -17,16 +17,14 @@ export interface BasicRelationAttributeProperties<
|
|
|
17
17
|
mappedBy?: RelationsKeysFromTo<T, S>;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export interface PolymorphicRelationAttributeProperties<
|
|
21
|
-
R extends RelationsType,
|
|
22
|
-
> {
|
|
20
|
+
export interface PolymorphicRelationAttributeProperties<R extends RelationsType> {
|
|
23
21
|
relation: R;
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
export type RelationAttribute<
|
|
27
25
|
S extends SchemaUID,
|
|
28
26
|
R extends RelationsType,
|
|
29
|
-
T extends R extends PolymorphicRelationsType ? never: SchemaUID = never
|
|
27
|
+
T extends R extends PolymorphicRelationsType ? never : SchemaUID = never
|
|
30
28
|
> = Attribute<'relation'> &
|
|
31
29
|
// Properties
|
|
32
30
|
(R extends BasicRelationsType
|
|
@@ -34,22 +32,21 @@ export type RelationAttribute<
|
|
|
34
32
|
: PolymorphicRelationAttributeProperties<R>) &
|
|
35
33
|
// Options
|
|
36
34
|
ConfigurableOption &
|
|
37
|
-
PrivateOption
|
|
35
|
+
PrivateOption;
|
|
38
36
|
|
|
39
37
|
export type RelationsKeysFromTo<
|
|
40
38
|
TTarget extends SchemaUID,
|
|
41
39
|
TSource extends SchemaUID
|
|
42
40
|
> = keyof PickRelationsFromTo<TTarget, TSource>;
|
|
43
41
|
|
|
44
|
-
export type PickRelationsFromTo<
|
|
45
|
-
TTarget,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
>;
|
|
42
|
+
export type PickRelationsFromTo<
|
|
43
|
+
TTarget extends SchemaUID,
|
|
44
|
+
TSource extends SchemaUID
|
|
45
|
+
> = GetAttributesByType<TTarget, 'relation', { target: TSource }>;
|
|
49
46
|
|
|
50
47
|
export type RelationPluralityModifier<
|
|
51
48
|
TRelation extends RelationsType,
|
|
52
|
-
TValue extends
|
|
49
|
+
TValue extends Record<string, unknown>
|
|
53
50
|
> = TRelation extends `${string}Many` ? TValue[] : TValue;
|
|
54
51
|
|
|
55
52
|
export type RelationValue<
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Attribute, ComponentAttribute } from '../attributes';
|
|
2
|
-
import { KeysBy, StringRecord } from '../../utils';
|
|
2
|
+
import { KeysBy, SchemaUID, StringRecord } from '../../utils';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Literal union type representing the possible natures of a content type
|
|
@@ -98,6 +98,11 @@ export interface PluginOptions {}
|
|
|
98
98
|
export interface ContentTypeSchema extends Schema {
|
|
99
99
|
modelType: 'contentType';
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Unique identifier of the schema
|
|
103
|
+
*/
|
|
104
|
+
uid: SchemaUID;
|
|
105
|
+
|
|
101
106
|
/**
|
|
102
107
|
* Determine the type of the content type (single-type or collection-type)
|
|
103
108
|
*/
|
|
@@ -2,8 +2,8 @@ import type Koa from 'koa';
|
|
|
2
2
|
import { Database } from '@strapi/database';
|
|
3
3
|
|
|
4
4
|
import type { StringMap } from './utils';
|
|
5
|
-
import type { GenericController } from '../../../core-api/controller'
|
|
6
|
-
import type { GenericService } from '../../../core-api/service'
|
|
5
|
+
import type { GenericController } from '../../../core-api/controller';
|
|
6
|
+
import type { GenericService } from '../../../core-api/service';
|
|
7
7
|
|
|
8
8
|
// TODO move custom fields types to a separate file
|
|
9
9
|
interface CustomFieldServerOptions {
|
|
@@ -92,9 +92,16 @@ export interface Strapi {
|
|
|
92
92
|
*/
|
|
93
93
|
contentType(uid: string): any;
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Getter for the Strapi component container
|
|
97
|
+
*
|
|
98
|
+
* It returns all the registered components
|
|
99
|
+
*/
|
|
100
|
+
readonly components: any;
|
|
101
|
+
|
|
95
102
|
/**
|
|
96
103
|
* The custom fields registry
|
|
97
|
-
*
|
|
104
|
+
*
|
|
98
105
|
* It returns the custom fields interface
|
|
99
106
|
*/
|
|
100
107
|
readonly customFields: CustomFields;
|
|
@@ -361,7 +368,6 @@ export interface Strapi {
|
|
|
361
368
|
*/
|
|
362
369
|
log: any;
|
|
363
370
|
|
|
364
|
-
|
|
365
371
|
/**
|
|
366
372
|
* Used to manage cron within Strapi
|
|
367
373
|
*/
|
package/lib/types/factories.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Service,GenericService } from '../core-api/service';
|
|
1
|
+
import { Service, GenericService } from '../core-api/service';
|
|
2
2
|
import { Controller, GenericController } from '../core-api/controller';
|
|
3
3
|
import { Middleware } from '../middlewares';
|
|
4
4
|
import { Policy } from '../core/registries/policies';
|
|
5
|
-
import { Strapi } from '
|
|
5
|
+
import { Strapi } from './core/strapi';
|
|
6
6
|
|
|
7
7
|
type ControllerConfig<T extends Controller = Controller> = T;
|
|
8
8
|
|
package/lib/utils/success.js
CHANGED
|
@@ -17,7 +17,7 @@ try {
|
|
|
17
17
|
process.env.npm_config_global === 'true' ||
|
|
18
18
|
JSON.parse(process.env.npm_config_argv).original.includes('global')
|
|
19
19
|
) {
|
|
20
|
-
fetch('https://analytics.strapi.io/track', {
|
|
20
|
+
fetch('https://analytics.strapi.io/api/v2/track', {
|
|
21
21
|
method: 'POST',
|
|
22
22
|
body: JSON.stringify({
|
|
23
23
|
event: 'didInstallStrapi',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/strapi",
|
|
3
|
-
"version": "4.6.0-
|
|
3
|
+
"version": "4.6.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",
|
|
@@ -80,18 +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-
|
|
84
|
-
"@strapi/
|
|
85
|
-
"@strapi/
|
|
86
|
-
"@strapi/
|
|
87
|
-
"@strapi/
|
|
88
|
-
"@strapi/
|
|
89
|
-
"@strapi/
|
|
90
|
-
"@strapi/plugin-content-
|
|
91
|
-
"@strapi/plugin-
|
|
92
|
-
"@strapi/plugin-
|
|
93
|
-
"@strapi/
|
|
94
|
-
"@strapi/utils": "4.6.0-
|
|
83
|
+
"@strapi/admin": "4.6.0-beta.1",
|
|
84
|
+
"@strapi/data-transfer": "4.6.0-beta.1",
|
|
85
|
+
"@strapi/database": "4.6.0-beta.1",
|
|
86
|
+
"@strapi/generate-new": "4.6.0-beta.1",
|
|
87
|
+
"@strapi/generators": "4.6.0-beta.1",
|
|
88
|
+
"@strapi/logger": "4.6.0-beta.1",
|
|
89
|
+
"@strapi/permissions": "4.6.0-beta.1",
|
|
90
|
+
"@strapi/plugin-content-manager": "4.6.0-beta.1",
|
|
91
|
+
"@strapi/plugin-content-type-builder": "4.6.0-beta.1",
|
|
92
|
+
"@strapi/plugin-email": "4.6.0-beta.1",
|
|
93
|
+
"@strapi/plugin-upload": "4.6.0-beta.1",
|
|
94
|
+
"@strapi/typescript-utils": "4.6.0-beta.1",
|
|
95
|
+
"@strapi/utils": "4.6.0-beta.1",
|
|
95
96
|
"bcryptjs": "2.4.3",
|
|
96
97
|
"boxen": "5.1.2",
|
|
97
98
|
"chalk": "4.1.2",
|
|
@@ -100,7 +101,7 @@
|
|
|
100
101
|
"cli-table3": "0.6.2",
|
|
101
102
|
"commander": "8.2.0",
|
|
102
103
|
"configstore": "5.0.1",
|
|
103
|
-
"debug": "4.3.
|
|
104
|
+
"debug": "4.3.4",
|
|
104
105
|
"delegates": "1.0.0",
|
|
105
106
|
"dotenv": "10.0.0",
|
|
106
107
|
"execa": "5.1.1",
|
|
@@ -126,7 +127,7 @@
|
|
|
126
127
|
"open": "8.4.0",
|
|
127
128
|
"ora": "5.4.1",
|
|
128
129
|
"package-json": "7.0.0",
|
|
129
|
-
"qs": "6.
|
|
130
|
+
"qs": "6.11.0",
|
|
130
131
|
"resolve-cwd": "3.0.0",
|
|
131
132
|
"semver": "7.3.8",
|
|
132
133
|
"statuses": "2.0.1",
|
|
@@ -140,5 +141,5 @@
|
|
|
140
141
|
"node": ">=14.19.1 <=18.x.x",
|
|
141
142
|
"npm": ">=6.0.0"
|
|
142
143
|
},
|
|
143
|
-
"gitHead": "
|
|
144
|
+
"gitHead": "2c0bcabdf0bf2a269fed50c6f23ba777845968a0"
|
|
144
145
|
}
|