@strapi/strapi 4.6.0-alpha.0 → 4.6.0-alpha.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 +1 -108
- package/lib/Strapi.js +1 -1
- package/lib/core/domain/content-type/index.js +1 -1
- package/lib/services/entity-service/index.js +16 -10
- package/lib/services/event-hub.js +70 -8
- package/lib/types/core/attributes/relation.d.ts +12 -9
- package/lib/types/core/schemas/index.d.ts +1 -6
- package/lib/types/core/strapi/index.d.ts +4 -10
- package/lib/types/factories.d.ts +2 -2
- package/package.json +14 -15
- package/lib/commands/transfer/export.js +0 -166
- package/lib/commands/transfer/import.js +0 -65
- package/lib/commands/utils/commander.js +0 -92
- package/lib/commands/utils/index.js +0 -20
package/bin/strapi.js
CHANGED
|
@@ -7,18 +7,11 @@
|
|
|
7
7
|
const _ = require('lodash');
|
|
8
8
|
const resolveCwd = require('resolve-cwd');
|
|
9
9
|
const { yellow } = require('chalk');
|
|
10
|
-
const { Command
|
|
11
|
-
const inquirer = require('inquirer');
|
|
10
|
+
const { Command } = require('commander');
|
|
12
11
|
|
|
13
12
|
const program = new Command();
|
|
14
13
|
|
|
15
14
|
const packageJSON = require('../package.json');
|
|
16
|
-
const {
|
|
17
|
-
parseInputList,
|
|
18
|
-
parseInputBool,
|
|
19
|
-
promptEncryptionKey,
|
|
20
|
-
confirmKeyValue,
|
|
21
|
-
} = require('../lib/commands/utils/commander');
|
|
22
15
|
|
|
23
16
|
const checkCwdIsStrapiApp = (name) => {
|
|
24
17
|
const logErrorAndExit = () => {
|
|
@@ -67,13 +60,6 @@ const getLocalScript =
|
|
|
67
60
|
});
|
|
68
61
|
};
|
|
69
62
|
|
|
70
|
-
// option to exclude types of data for the export, import, and transfer commands
|
|
71
|
-
// TODO: validate these inputs. Hopefully here, but worst case it may require adding a hook on each command
|
|
72
|
-
const excludeOption = new Option(
|
|
73
|
-
'--exclude <data,to,exclude>',
|
|
74
|
-
'Comma-separated list of data to exclude (files [localMediaFiles, providerMediaFiles], content [entities, links], schema, configuration)' // ['webhooks', 'content', 'localmedia', 'providermedia', 'relations']
|
|
75
|
-
).argParser(parseInputList);
|
|
76
|
-
|
|
77
63
|
// Initial program setup
|
|
78
64
|
program.storeOptionsAsProperties(false).allowUnknownOption(true);
|
|
79
65
|
|
|
@@ -269,97 +255,4 @@ program
|
|
|
269
255
|
.option('-s, --silent', `Run the generation silently, without any output`, false)
|
|
270
256
|
.action(getLocalScript('ts/generate-types'));
|
|
271
257
|
|
|
272
|
-
// `$ strapi export`
|
|
273
|
-
program
|
|
274
|
-
.command('export')
|
|
275
|
-
.description('Export data from Strapi to file')
|
|
276
|
-
.addOption(
|
|
277
|
-
new Option(
|
|
278
|
-
'--encrypt <boolean>',
|
|
279
|
-
`Encrypt output file using the 'aes-128-ecb' algorithm. Prompts for key unless key option is used.`
|
|
280
|
-
)
|
|
281
|
-
.default(true)
|
|
282
|
-
.argParser(parseInputBool)
|
|
283
|
-
)
|
|
284
|
-
.addOption(
|
|
285
|
-
new Option('--compress <boolean>', 'Compress output file using gzip compression')
|
|
286
|
-
.default(true)
|
|
287
|
-
.argParser(parseInputBool)
|
|
288
|
-
)
|
|
289
|
-
.addOption(
|
|
290
|
-
new Option('--key <string>', 'Provide encryption key in command instead of using a prompt')
|
|
291
|
-
)
|
|
292
|
-
.addOption(
|
|
293
|
-
new Option('--max-size <max MB per file>', 'split final file when exceeding size in MB')
|
|
294
|
-
)
|
|
295
|
-
.addOption(
|
|
296
|
-
new Option(
|
|
297
|
-
'--max-size-jsonl <max MB per internal backup file>',
|
|
298
|
-
'split internal jsonl files when exceeding max size in MB'
|
|
299
|
-
)
|
|
300
|
-
)
|
|
301
|
-
.addOption(new Option('-f, --file <file>', 'name to use for exported file (without extensions)'))
|
|
302
|
-
.addOption(excludeOption)
|
|
303
|
-
.allowExcessArguments(false)
|
|
304
|
-
.hook('preAction', promptEncryptionKey)
|
|
305
|
-
.action(getLocalScript('transfer/export'));
|
|
306
|
-
|
|
307
|
-
// `$ strapi import`
|
|
308
|
-
program
|
|
309
|
-
.command('import')
|
|
310
|
-
.description('Import data from file to Strapi')
|
|
311
|
-
.addOption(
|
|
312
|
-
new Option('--conflictStrategy <conflictStrategy>', 'Which strategy to use for ID conflicts')
|
|
313
|
-
.choices(['restore', 'abort', 'keep', 'replace'])
|
|
314
|
-
.default('restore')
|
|
315
|
-
)
|
|
316
|
-
.addOption(excludeOption)
|
|
317
|
-
.addOption(
|
|
318
|
-
new Option(
|
|
319
|
-
'--schemaComparison <schemaComparison>',
|
|
320
|
-
'exact requires every field to match, strict requires Strapi version and content type schema fields do not break, subset requires source schema to exist in destination, bypass skips checks',
|
|
321
|
-
parseInputList
|
|
322
|
-
)
|
|
323
|
-
.choices(['exact', 'strict', 'subset', 'bypass'])
|
|
324
|
-
.default('exact')
|
|
325
|
-
)
|
|
326
|
-
.requiredOption(
|
|
327
|
-
'-f, --file <file>',
|
|
328
|
-
'path and filename to the Strapi export file you want to import'
|
|
329
|
-
)
|
|
330
|
-
.addOption(
|
|
331
|
-
new Option('--key <string>', 'Provide encryption key in command instead of using a prompt')
|
|
332
|
-
)
|
|
333
|
-
.allowExcessArguments(false)
|
|
334
|
-
.hook('preAction', async (thisCommand) => {
|
|
335
|
-
const opts = thisCommand.opts();
|
|
336
|
-
|
|
337
|
-
// check extension to guess if we should prompt for key
|
|
338
|
-
if (String(opts.file).endsWith('.enc')) {
|
|
339
|
-
if (!opts.key) {
|
|
340
|
-
const answers = await inquirer.prompt([
|
|
341
|
-
{
|
|
342
|
-
type: 'password',
|
|
343
|
-
message: 'Please enter your decryption key',
|
|
344
|
-
name: 'key',
|
|
345
|
-
},
|
|
346
|
-
]);
|
|
347
|
-
if (!answers.key?.length) {
|
|
348
|
-
console.log('No key entered, aborting import.');
|
|
349
|
-
process.exit(0);
|
|
350
|
-
}
|
|
351
|
-
opts.key = answers.key;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
})
|
|
355
|
-
.hook(
|
|
356
|
-
'preAction',
|
|
357
|
-
confirmKeyValue(
|
|
358
|
-
'conflictStrategy',
|
|
359
|
-
'restore',
|
|
360
|
-
"Using strategy 'restore' will delete all data in your database. Are you sure you want to proceed?"
|
|
361
|
-
)
|
|
362
|
-
)
|
|
363
|
-
.action(getLocalScript('transfer/import'));
|
|
364
|
-
|
|
365
258
|
program.parseAsync(process.argv);
|
package/lib/Strapi.js
CHANGED
|
@@ -54,7 +54,7 @@ const createContentType = (uid, definition) => {
|
|
|
54
54
|
});
|
|
55
55
|
} else {
|
|
56
56
|
throw new Error(
|
|
57
|
-
`Incorrect Content Type UID "${uid}". The UID should start with api::, plugin:: or
|
|
57
|
+
`Incorrect Content Type UID "${uid}". The UID should start with api::, plugin:: or admin::.`
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -134,17 +134,20 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
134
134
|
// TODO: wrap into transaction
|
|
135
135
|
const componentData = await createComponents(uid, validData);
|
|
136
136
|
|
|
137
|
+
const entityData = creationPipeline(
|
|
138
|
+
Object.assign(omitComponentData(model, validData), componentData),
|
|
139
|
+
{
|
|
140
|
+
contentType: model,
|
|
141
|
+
}
|
|
142
|
+
);
|
|
137
143
|
let entity = await db.query(uid).create({
|
|
138
144
|
...query,
|
|
139
|
-
data:
|
|
140
|
-
contentType: model,
|
|
141
|
-
}),
|
|
145
|
+
data: entityData,
|
|
142
146
|
});
|
|
143
147
|
|
|
144
148
|
// TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
|
|
145
|
-
// FIXME: upload in components
|
|
146
149
|
if (files && Object.keys(files).length > 0) {
|
|
147
|
-
await this.uploadFiles(uid, entity, files);
|
|
150
|
+
await this.uploadFiles(uid, Object.assign(entityData, entity), files);
|
|
148
151
|
entity = await this.findOne(uid, entity.id, wrappedParams);
|
|
149
152
|
}
|
|
150
153
|
|
|
@@ -180,19 +183,22 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
180
183
|
|
|
181
184
|
// TODO: wrap in transaction
|
|
182
185
|
const componentData = await updateComponents(uid, entityToUpdate, validData);
|
|
186
|
+
const entityData = updatePipeline(
|
|
187
|
+
Object.assign(omitComponentData(model, validData), componentData),
|
|
188
|
+
{
|
|
189
|
+
contentType: model,
|
|
190
|
+
}
|
|
191
|
+
);
|
|
183
192
|
|
|
184
193
|
let entity = await db.query(uid).update({
|
|
185
194
|
...query,
|
|
186
195
|
where: { id: entityId },
|
|
187
|
-
data:
|
|
188
|
-
contentType: model,
|
|
189
|
-
}),
|
|
196
|
+
data: entityData,
|
|
190
197
|
});
|
|
191
198
|
|
|
192
199
|
// TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
|
|
193
|
-
// FIXME: upload in components
|
|
194
200
|
if (files && Object.keys(files).length > 0) {
|
|
195
|
-
await this.uploadFiles(uid, entity, files);
|
|
201
|
+
await this.uploadFiles(uid, Object.assign(entityData, entity), files);
|
|
196
202
|
entity = await this.findOne(uid, entity.id, wrappedParams);
|
|
197
203
|
}
|
|
198
204
|
|
|
@@ -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, []);
|
|
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
|
+
},
|
|
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
|
};
|
|
@@ -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,14 +17,16 @@ export interface BasicRelationAttributeProperties<
|
|
|
17
17
|
mappedBy?: RelationsKeysFromTo<T, S>;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export interface PolymorphicRelationAttributeProperties<
|
|
20
|
+
export interface PolymorphicRelationAttributeProperties<
|
|
21
|
+
R extends RelationsType,
|
|
22
|
+
> {
|
|
21
23
|
relation: R;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export type RelationAttribute<
|
|
25
27
|
S extends SchemaUID,
|
|
26
28
|
R extends RelationsType,
|
|
27
|
-
T extends R extends PolymorphicRelationsType ? never
|
|
29
|
+
T extends R extends PolymorphicRelationsType ? never: SchemaUID = never
|
|
28
30
|
> = Attribute<'relation'> &
|
|
29
31
|
// Properties
|
|
30
32
|
(R extends BasicRelationsType
|
|
@@ -32,21 +34,22 @@ export type RelationAttribute<
|
|
|
32
34
|
: PolymorphicRelationAttributeProperties<R>) &
|
|
33
35
|
// Options
|
|
34
36
|
ConfigurableOption &
|
|
35
|
-
PrivateOption
|
|
37
|
+
PrivateOption
|
|
36
38
|
|
|
37
39
|
export type RelationsKeysFromTo<
|
|
38
40
|
TTarget extends SchemaUID,
|
|
39
41
|
TSource extends SchemaUID
|
|
40
42
|
> = keyof PickRelationsFromTo<TTarget, TSource>;
|
|
41
43
|
|
|
42
|
-
export type PickRelationsFromTo<
|
|
43
|
-
TTarget
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
export type PickRelationsFromTo<TTarget extends SchemaUID, TSource extends SchemaUID> = GetAttributesByType<
|
|
45
|
+
TTarget,
|
|
46
|
+
'relation',
|
|
47
|
+
{ target: TSource }
|
|
48
|
+
>;
|
|
46
49
|
|
|
47
50
|
export type RelationPluralityModifier<
|
|
48
51
|
TRelation extends RelationsType,
|
|
49
|
-
TValue extends
|
|
52
|
+
TValue extends Object
|
|
50
53
|
> = TRelation extends `${string}Many` ? TValue[] : TValue;
|
|
51
54
|
|
|
52
55
|
export type RelationValue<
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Attribute, ComponentAttribute } from '../attributes';
|
|
2
|
-
import { KeysBy,
|
|
2
|
+
import { KeysBy, StringRecord } from '../../utils';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Literal union type representing the possible natures of a content type
|
|
@@ -98,11 +98,6 @@ 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
|
-
|
|
106
101
|
/**
|
|
107
102
|
* Determine the type of the content type (single-type or collection-type)
|
|
108
103
|
*/
|
|
@@ -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,16 +92,9 @@ 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
|
-
|
|
102
95
|
/**
|
|
103
96
|
* The custom fields registry
|
|
104
|
-
*
|
|
97
|
+
*
|
|
105
98
|
* It returns the custom fields interface
|
|
106
99
|
*/
|
|
107
100
|
readonly customFields: CustomFields;
|
|
@@ -368,6 +361,7 @@ export interface Strapi {
|
|
|
368
361
|
*/
|
|
369
362
|
log: any;
|
|
370
363
|
|
|
364
|
+
|
|
371
365
|
/**
|
|
372
366
|
* Used to manage cron within Strapi
|
|
373
367
|
*/
|
package/lib/types/factories.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { 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 '@strapi/strapi';
|
|
6
6
|
|
|
7
7
|
type ControllerConfig<T extends Controller = Controller> = T;
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/strapi",
|
|
3
|
-
"version": "4.6.0-alpha.
|
|
3
|
+
"version": "4.6.0-alpha.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,19 +80,18 @@
|
|
|
80
80
|
"dependencies": {
|
|
81
81
|
"@koa/cors": "3.4.3",
|
|
82
82
|
"@koa/router": "10.1.1",
|
|
83
|
-
"@strapi/admin": "4.6.0-alpha.
|
|
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/
|
|
95
|
-
"@strapi/utils": "4.6.0-alpha.0",
|
|
83
|
+
"@strapi/admin": "4.6.0-alpha.1",
|
|
84
|
+
"@strapi/database": "4.6.0-alpha.1",
|
|
85
|
+
"@strapi/generate-new": "4.6.0-alpha.1",
|
|
86
|
+
"@strapi/generators": "4.6.0-alpha.1",
|
|
87
|
+
"@strapi/logger": "4.6.0-alpha.1",
|
|
88
|
+
"@strapi/permissions": "4.6.0-alpha.1",
|
|
89
|
+
"@strapi/plugin-content-manager": "4.6.0-alpha.1",
|
|
90
|
+
"@strapi/plugin-content-type-builder": "4.6.0-alpha.1",
|
|
91
|
+
"@strapi/plugin-email": "4.6.0-alpha.1",
|
|
92
|
+
"@strapi/plugin-upload": "4.6.0-alpha.1",
|
|
93
|
+
"@strapi/typescript-utils": "4.6.0-alpha.1",
|
|
94
|
+
"@strapi/utils": "4.6.0-alpha.1",
|
|
96
95
|
"bcryptjs": "2.4.3",
|
|
97
96
|
"boxen": "5.1.2",
|
|
98
97
|
"chalk": "4.1.2",
|
|
@@ -141,5 +140,5 @@
|
|
|
141
140
|
"node": ">=14.19.1 <=18.x.x",
|
|
142
141
|
"npm": ">=6.0.0"
|
|
143
142
|
},
|
|
144
|
-
"gitHead": "
|
|
143
|
+
"gitHead": "9171c48104548f5f6da21abf2a8098009f1a40e9"
|
|
145
144
|
}
|
|
@@ -1,166 +0,0 @@
|
|
|
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 _ = require('lodash/fp');
|
|
11
|
-
const Table = require('cli-table3');
|
|
12
|
-
const fs = require('fs-extra');
|
|
13
|
-
|
|
14
|
-
const chalk = require('chalk');
|
|
15
|
-
const strapi = require('../../index');
|
|
16
|
-
const { readableBytes } = require('../utils');
|
|
17
|
-
|
|
18
|
-
const pad = (n) => {
|
|
19
|
-
return (n < 10 ? '0' : '') + String(n);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const yyyymmddHHMMSS = () => {
|
|
23
|
-
const date = new Date();
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
date.getFullYear() +
|
|
27
|
-
pad(date.getMonth() + 1) +
|
|
28
|
-
pad(date.getDate()) +
|
|
29
|
-
pad(date.getHours()) +
|
|
30
|
-
pad(date.getMinutes()) +
|
|
31
|
-
pad(date.getSeconds())
|
|
32
|
-
);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const getDefaultExportName = () => {
|
|
36
|
-
return `export_${yyyymmddHHMMSS()}`;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const logger = console;
|
|
40
|
-
|
|
41
|
-
const BYTES_IN_MB = 1024 * 1024;
|
|
42
|
-
|
|
43
|
-
module.exports = async (opts) => {
|
|
44
|
-
// validate inputs from Commander
|
|
45
|
-
if (!_.isObject(opts)) {
|
|
46
|
-
logger.error('Could not parse arguments');
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
const filename = opts.file;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* From local Strapi instance
|
|
53
|
-
*/
|
|
54
|
-
const sourceOptions = {
|
|
55
|
-
async getStrapi() {
|
|
56
|
-
return strapi(await strapi.compile()).load();
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
const source = createLocalStrapiSourceProvider(sourceOptions);
|
|
60
|
-
|
|
61
|
-
const file = _.isString(filename) && filename.length > 0 ? filename : getDefaultExportName();
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* To a Strapi backup file
|
|
65
|
-
*/
|
|
66
|
-
// treat any unknown arguments as filenames
|
|
67
|
-
const destinationOptions = {
|
|
68
|
-
file: {
|
|
69
|
-
path: file,
|
|
70
|
-
maxSize: _.isFinite(opts.maxSize) ? Math.floor(opts.maxSize) * BYTES_IN_MB : undefined,
|
|
71
|
-
maxSizeJsonl: _.isFinite(opts.maxSizeJsonl)
|
|
72
|
-
? Math.floor(opts.maxSizeJsonl) * BYTES_IN_MB
|
|
73
|
-
: undefined,
|
|
74
|
-
},
|
|
75
|
-
encryption: {
|
|
76
|
-
enabled: opts.encrypt,
|
|
77
|
-
key: opts.key,
|
|
78
|
-
},
|
|
79
|
-
compression: {
|
|
80
|
-
enabled: opts.compress,
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
const destination = createLocalFileDestinationProvider(destinationOptions);
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Configure and run the transfer engine
|
|
87
|
-
*/
|
|
88
|
-
const engineOptions = {
|
|
89
|
-
strategy: 'restore', // for an export to file, strategy will always be 'restore'
|
|
90
|
-
versionMatching: 'ignore', // for an export to file, versionMatching will always be skipped
|
|
91
|
-
exclude: opts.exclude,
|
|
92
|
-
};
|
|
93
|
-
const engine = createTransferEngine(source, destination, engineOptions);
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
let resultData = [];
|
|
97
|
-
logger.log(`Starting export...`);
|
|
98
|
-
|
|
99
|
-
engine.progress.stream.on('start', ({ stage }) => {
|
|
100
|
-
logger.log(`Starting transfer of ${stage}...`);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// engine.progress.stream..on('progress', ({ stage, data }) => {
|
|
104
|
-
// logger.log('progress');
|
|
105
|
-
// });
|
|
106
|
-
|
|
107
|
-
engine.progress.stream.on('complete', ({ stage, data }) => {
|
|
108
|
-
logger.log(`...${stage} complete`);
|
|
109
|
-
resultData = data;
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const results = await engine.transfer();
|
|
113
|
-
|
|
114
|
-
// Build pretty table
|
|
115
|
-
const table = new Table({
|
|
116
|
-
head: ['Type', 'Count', 'Size'],
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
let totalBytes = 0;
|
|
120
|
-
let totalItems = 0;
|
|
121
|
-
Object.keys(resultData).forEach((key) => {
|
|
122
|
-
const item = resultData[key];
|
|
123
|
-
|
|
124
|
-
table.push([
|
|
125
|
-
{ hAlign: 'left', content: chalk.bold(key) },
|
|
126
|
-
{ hAlign: 'right', content: item.count },
|
|
127
|
-
{ hAlign: 'right', content: `${readableBytes(item.bytes, 1, 11)} ` },
|
|
128
|
-
]);
|
|
129
|
-
totalBytes += item.bytes;
|
|
130
|
-
totalItems += item.count;
|
|
131
|
-
|
|
132
|
-
if (item.aggregates) {
|
|
133
|
-
Object.keys(item.aggregates).forEach((subkey) => {
|
|
134
|
-
const subitem = item.aggregates[subkey];
|
|
135
|
-
|
|
136
|
-
table.push([
|
|
137
|
-
{ hAlign: 'left', content: `-- ${chalk.bold(subkey)}` },
|
|
138
|
-
{ hAlign: 'right', content: subitem.count },
|
|
139
|
-
{ hAlign: 'right', content: `(${chalk.grey(readableBytes(subitem.bytes, 1, 11))})` },
|
|
140
|
-
]);
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
table.push([
|
|
145
|
-
{ hAlign: 'left', content: chalk.bold.green('Total') },
|
|
146
|
-
{ hAlign: 'right', content: chalk.bold.green(totalItems) },
|
|
147
|
-
{ hAlign: 'right', content: `${chalk.bold.green(readableBytes(totalBytes, 1, 11))} ` },
|
|
148
|
-
]);
|
|
149
|
-
logger.log(table.toString());
|
|
150
|
-
|
|
151
|
-
// TODO: once archiving is implemented, we need to check file extensions
|
|
152
|
-
if (!fs.pathExistsSync(results.destination.file.path)) {
|
|
153
|
-
logger.log(file);
|
|
154
|
-
throw new Error('Export file not created');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
logger.log(`
|
|
158
|
-
${chalk.bold('Export process has been completed successfully!')}
|
|
159
|
-
Export archive is in ${chalk.green(results.destination.file.path)}
|
|
160
|
-
`);
|
|
161
|
-
process.exit(0);
|
|
162
|
-
} catch (e) {
|
|
163
|
-
logger.error('Export process failed unexpectedly:', e.toString());
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}
|
|
166
|
-
};
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
createLocalFileSourceProvider,
|
|
5
|
-
createLocalStrapiDestinationProvider,
|
|
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 } = require('lodash/fp');
|
|
11
|
-
const strapi = require('../../index');
|
|
12
|
-
|
|
13
|
-
const logger = console;
|
|
14
|
-
|
|
15
|
-
module.exports = async (opts) => {
|
|
16
|
-
// validate inputs from Commander
|
|
17
|
-
if (!isObject(opts)) {
|
|
18
|
-
logger.error('Could not parse arguments');
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
const filename = opts.file;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* From strapi backup file
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
// treat any unknown arguments as filenames
|
|
28
|
-
const sourceOptions = {
|
|
29
|
-
backupFilePath: filename,
|
|
30
|
-
};
|
|
31
|
-
const source = createLocalFileSourceProvider(sourceOptions);
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* To local Strapi instance
|
|
35
|
-
*/
|
|
36
|
-
const destinationOptions = {
|
|
37
|
-
async getStrapi() {
|
|
38
|
-
return strapi(await strapi.compile()).load();
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
const destination = createLocalStrapiDestinationProvider(destinationOptions);
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Configure and run the transfer engine
|
|
45
|
-
*/
|
|
46
|
-
const engineOptions = {
|
|
47
|
-
strategy: opts.conflictStrategy,
|
|
48
|
-
versionMatching: opts.schemaComparison,
|
|
49
|
-
exclude: opts.exclude,
|
|
50
|
-
};
|
|
51
|
-
const engine = createTransferEngine(source, destination, engineOptions);
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
logger.log('Importing data...');
|
|
55
|
-
const result = await engine.transfer();
|
|
56
|
-
logger.log('Import process has been completed successfully!');
|
|
57
|
-
|
|
58
|
-
// TODO: this won't dump the entire results, we will print a pretty summary
|
|
59
|
-
logger.log('Results:', result);
|
|
60
|
-
process.exit(0);
|
|
61
|
-
} catch (e) {
|
|
62
|
-
logger.log(`Import process failed unexpectedly: ${e.message}`);
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { parseType } = require('@strapi/utils/lib');
|
|
4
|
-
const inquirer = require('inquirer');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* argsParser: Parse a string argument from the command line as a boolean
|
|
8
|
-
*/
|
|
9
|
-
const parseInputBool = (arg) => {
|
|
10
|
-
try {
|
|
11
|
-
return parseType({ type: 'boolean', value: arg });
|
|
12
|
-
} catch (e) {
|
|
13
|
-
console.error(e.message);
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* argsParser: Parse a comma-delimited string as an array
|
|
20
|
-
*/
|
|
21
|
-
const parseInputList = (value) => {
|
|
22
|
-
return value.split(',');
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* hook: if encrpyt=true and key not provided, prompt for it
|
|
27
|
-
*/
|
|
28
|
-
const promptEncryptionKey = async (thisCommand) => {
|
|
29
|
-
const opts = thisCommand.opts();
|
|
30
|
-
|
|
31
|
-
if (!opts.encrypt && opts.key) {
|
|
32
|
-
console.error('Key may not be present unless encryption is used');
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// if encrypt is set but we have no key, prompt for it
|
|
37
|
-
if (opts.encrypt && !(opts.key && opts.key.length > 0)) {
|
|
38
|
-
try {
|
|
39
|
-
const answers = await inquirer.prompt([
|
|
40
|
-
{
|
|
41
|
-
type: 'password',
|
|
42
|
-
message: 'Please enter an encryption key',
|
|
43
|
-
name: 'key',
|
|
44
|
-
validate(key) {
|
|
45
|
-
if (key.length > 0) return true;
|
|
46
|
-
|
|
47
|
-
return 'Key must be present when using the encrypt option';
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
]);
|
|
51
|
-
opts.key = answers.key;
|
|
52
|
-
} catch (e) {
|
|
53
|
-
console.error('Failed to get encryption key');
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
if (!opts.key) {
|
|
57
|
-
console.error('Failed to get encryption key');
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* hook: confirm that key has a value with a provided message
|
|
65
|
-
*/
|
|
66
|
-
const confirmKeyValue = (key, value, message) => {
|
|
67
|
-
return async (thisCommand) => {
|
|
68
|
-
const opts = thisCommand.opts();
|
|
69
|
-
|
|
70
|
-
if (!opts[key] || opts[key] !== value) {
|
|
71
|
-
console.error(`Could not confirm key ${key}, halting operation.`);
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
const answers = await inquirer.prompt([
|
|
75
|
-
{
|
|
76
|
-
type: 'confirm',
|
|
77
|
-
message,
|
|
78
|
-
name: `confirm_${key}`,
|
|
79
|
-
},
|
|
80
|
-
]);
|
|
81
|
-
if (!answers[`confirm_${key}`]) {
|
|
82
|
-
process.exit(0);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
module.exports = {
|
|
88
|
-
parseInputList,
|
|
89
|
-
parseInputBool,
|
|
90
|
-
promptEncryptionKey,
|
|
91
|
-
confirmKeyValue,
|
|
92
|
-
};
|
|
@@ -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
|
-
};
|