@jungvonmatt/contentful-migrations 6.2.5 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -11
- package/cli.js +314 -171
- package/index.d.ts +9 -9
- package/lib/backend.js +155 -107
- package/lib/content.js +5 -5
- package/lib/contentful.js +90 -43
- package/lib/diff.js +46 -32
- package/lib/helpers/locale.d.ts +3 -3
- package/lib/helpers/validation.d.ts +94 -15
- package/lib/helpers/validation.js +4 -4
- package/lib/migration.js +68 -51
- package/package.json +54 -117
- package/lib/helpers/validation.test.js +0 -381
- package/lib/helpers/validation.utils.test.js +0 -45
package/index.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import type Migration from
|
|
2
|
-
import type { MigrationContext, MigrationFunction } from
|
|
3
|
-
import type { LocaleHelpers } from
|
|
4
|
-
import type { ValidationHelpers } from
|
|
1
|
+
import type Migration from 'contentful-migration';
|
|
2
|
+
import type { MigrationContext, MigrationFunction } from 'contentful-migration';
|
|
3
|
+
import type { LocaleHelpers } from './lib/helpers/locale';
|
|
4
|
+
import type { ValidationHelpers } from './lib/helpers/validation';
|
|
5
5
|
|
|
6
6
|
export interface MigrationHelpers {
|
|
7
|
-
locale: LocaleHelpers
|
|
8
|
-
validation: ValidationHelpers
|
|
7
|
+
locale: LocaleHelpers;
|
|
8
|
+
validation: ValidationHelpers;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export type EnhancedMigrationFunction = (
|
|
12
12
|
migration: Migration,
|
|
13
13
|
context?: MigrationContext,
|
|
14
|
-
helpers?: MigrationHelpers
|
|
14
|
+
helpers?: MigrationHelpers,
|
|
15
15
|
) => void;
|
|
16
16
|
|
|
17
17
|
export function withHelpers(cb: EnhancedMigrationFunction): MigrationFunction;
|
|
18
|
-
export { getLocaleHelpers } from
|
|
19
|
-
export { getValidationHelpers } from
|
|
18
|
+
export { getLocaleHelpers } from './lib/helpers/locale';
|
|
19
|
+
export { getValidationHelpers } from './lib/helpers/validation';
|
package/lib/backend.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
const path = require(
|
|
2
|
-
const fs = require(
|
|
3
|
-
const pc = require(
|
|
4
|
-
const cliProgress = require(
|
|
5
|
-
const {
|
|
6
|
-
const {
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs/promises");
|
|
3
|
+
const pc = require("picocolors");
|
|
4
|
+
const cliProgress = require("cli-progress");
|
|
5
|
+
const { glob } = require("tinyglobby");
|
|
6
|
+
const {
|
|
7
|
+
getEnvironment,
|
|
8
|
+
getDefaultLocale,
|
|
9
|
+
getMigrationItems,
|
|
10
|
+
} = require("./contentful");
|
|
11
|
+
const {
|
|
12
|
+
STORAGE_TAG,
|
|
13
|
+
STORAGE_CONTENT,
|
|
14
|
+
STATE_SUCCESS,
|
|
15
|
+
STATE_FAILURE,
|
|
16
|
+
} = require("./config");
|
|
7
17
|
|
|
8
18
|
/**
|
|
9
19
|
* Create contentful-migrations content-type
|
|
@@ -15,79 +25,90 @@ const initializeContentModel = async (config) => {
|
|
|
15
25
|
const environmentId = client.sys.id;
|
|
16
26
|
const { items: contentTypes } = await client.getContentTypes();
|
|
17
27
|
|
|
18
|
-
const exists = (contentTypes || []).some(
|
|
28
|
+
const exists = (contentTypes || []).some(
|
|
29
|
+
(contentType) => contentType.sys.id === migrationContentTypeId,
|
|
30
|
+
);
|
|
19
31
|
|
|
20
32
|
if (!exists) {
|
|
21
33
|
console.log(
|
|
22
|
-
`\nCreating content-type: ${pc.green(migrationContentTypeId)} in environment ${pc.green(environmentId)}
|
|
34
|
+
`\nCreating content-type: ${pc.green(migrationContentTypeId)} in environment ${pc.green(environmentId)}`,
|
|
35
|
+
);
|
|
36
|
+
const contentType = await client.createContentTypeWithId(
|
|
37
|
+
migrationContentTypeId,
|
|
38
|
+
{
|
|
39
|
+
name: "Migrations",
|
|
40
|
+
description: "Internal data model holding references to all migrations",
|
|
41
|
+
displayField: "name",
|
|
42
|
+
fields: [
|
|
43
|
+
{
|
|
44
|
+
id: "version",
|
|
45
|
+
name: "Version",
|
|
46
|
+
type: "Symbol",
|
|
47
|
+
localized: false,
|
|
48
|
+
required: true,
|
|
49
|
+
validations: [],
|
|
50
|
+
disabled: false,
|
|
51
|
+
omitted: false,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "name",
|
|
55
|
+
name: "Name",
|
|
56
|
+
type: "Symbol",
|
|
57
|
+
localized: false,
|
|
58
|
+
required: false,
|
|
59
|
+
validations: [],
|
|
60
|
+
disabled: false,
|
|
61
|
+
omitted: false,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "state",
|
|
65
|
+
name: "State",
|
|
66
|
+
type: "Symbol",
|
|
67
|
+
localized: false,
|
|
68
|
+
required: false,
|
|
69
|
+
validations: [
|
|
70
|
+
{
|
|
71
|
+
in: [STATE_SUCCESS, STATE_FAILURE],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
disabled: false,
|
|
75
|
+
omitted: false,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "message",
|
|
79
|
+
name: "Message",
|
|
80
|
+
type: "Text",
|
|
81
|
+
localized: false,
|
|
82
|
+
required: false,
|
|
83
|
+
validations: [],
|
|
84
|
+
disabled: false,
|
|
85
|
+
omitted: false,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
23
89
|
);
|
|
24
|
-
const contentType = await client.createContentTypeWithId(migrationContentTypeId, {
|
|
25
|
-
name: 'Migrations',
|
|
26
|
-
description: 'Internal data model holding references to all migrations',
|
|
27
|
-
displayField: 'name',
|
|
28
|
-
fields: [
|
|
29
|
-
{
|
|
30
|
-
id: 'version',
|
|
31
|
-
name: 'Version',
|
|
32
|
-
type: 'Symbol',
|
|
33
|
-
localized: false,
|
|
34
|
-
required: true,
|
|
35
|
-
validations: [],
|
|
36
|
-
disabled: false,
|
|
37
|
-
omitted: false,
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
id: 'name',
|
|
41
|
-
name: 'Name',
|
|
42
|
-
type: 'Symbol',
|
|
43
|
-
localized: false,
|
|
44
|
-
required: false,
|
|
45
|
-
validations: [],
|
|
46
|
-
disabled: false,
|
|
47
|
-
omitted: false,
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: 'state',
|
|
51
|
-
name: 'State',
|
|
52
|
-
type: 'Symbol',
|
|
53
|
-
localized: false,
|
|
54
|
-
required: false,
|
|
55
|
-
validations: [
|
|
56
|
-
{
|
|
57
|
-
in: [STATE_SUCCESS, STATE_FAILURE],
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
disabled: false,
|
|
61
|
-
omitted: false,
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
id: 'message',
|
|
65
|
-
name: 'Message',
|
|
66
|
-
type: 'Text',
|
|
67
|
-
localized: false,
|
|
68
|
-
required: false,
|
|
69
|
-
validations: [],
|
|
70
|
-
disabled: false,
|
|
71
|
-
omitted: false,
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
});
|
|
75
90
|
await contentType.publish();
|
|
76
91
|
|
|
77
|
-
const editorInterface = await client.getEditorInterfaceForContentType(
|
|
92
|
+
const editorInterface = await client.getEditorInterfaceForContentType(
|
|
93
|
+
migrationContentTypeId,
|
|
94
|
+
);
|
|
78
95
|
if (editorInterface) {
|
|
79
|
-
const messageIndex = editorInterface.controls.findIndex(
|
|
96
|
+
const messageIndex = editorInterface.controls.findIndex(
|
|
97
|
+
(value) => value.fieldId === "message",
|
|
98
|
+
);
|
|
80
99
|
editorInterface.controls[messageIndex] = {
|
|
81
100
|
...editorInterface.controls[messageIndex],
|
|
82
|
-
widgetNamespace:
|
|
83
|
-
widgetId:
|
|
101
|
+
widgetNamespace: "builtin",
|
|
102
|
+
widgetId: "multipleLine",
|
|
84
103
|
};
|
|
85
104
|
|
|
86
|
-
const stateIndex = editorInterface.controls.findIndex(
|
|
105
|
+
const stateIndex = editorInterface.controls.findIndex(
|
|
106
|
+
(value) => value.fieldId === "state",
|
|
107
|
+
);
|
|
87
108
|
editorInterface.controls[stateIndex] = {
|
|
88
109
|
...editorInterface.controls[stateIndex],
|
|
89
|
-
widgetNamespace:
|
|
90
|
-
widgetId:
|
|
110
|
+
widgetNamespace: "builtin",
|
|
111
|
+
widgetId: "radio",
|
|
91
112
|
};
|
|
92
113
|
|
|
93
114
|
await editorInterface.update();
|
|
@@ -112,30 +133,34 @@ const addMigrationEntry = async (data, config) => {
|
|
|
112
133
|
|
|
113
134
|
let entry;
|
|
114
135
|
try {
|
|
115
|
-
entry = await client.getEntry(version);
|
|
136
|
+
entry = await client.getEntry(`${version}`);
|
|
116
137
|
} catch {}
|
|
117
138
|
|
|
118
139
|
if (!entry) {
|
|
119
|
-
entry = await client.createEntryWithId(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
entry = await client.createEntryWithId(
|
|
141
|
+
migrationContentTypeId,
|
|
142
|
+
`${version}`,
|
|
143
|
+
{
|
|
144
|
+
fields: {
|
|
145
|
+
version: {
|
|
146
|
+
[defaultLocale]: `${version}`,
|
|
147
|
+
},
|
|
148
|
+
name: {
|
|
149
|
+
[defaultLocale]: name,
|
|
150
|
+
},
|
|
151
|
+
state: {
|
|
152
|
+
[defaultLocale]: state,
|
|
153
|
+
},
|
|
154
|
+
message: {
|
|
155
|
+
[defaultLocale]: message || "",
|
|
156
|
+
},
|
|
132
157
|
},
|
|
133
158
|
},
|
|
134
|
-
|
|
159
|
+
);
|
|
135
160
|
} else {
|
|
136
161
|
entry.fields.name = { [defaultLocale]: name };
|
|
137
162
|
entry.fields.state = { [defaultLocale]: state };
|
|
138
|
-
entry.fields.message = { [defaultLocale]: message ||
|
|
163
|
+
entry.fields.message = { [defaultLocale]: message || "" };
|
|
139
164
|
}
|
|
140
165
|
|
|
141
166
|
try {
|
|
@@ -200,8 +225,9 @@ const migrateToContentStorage = async (config) => {
|
|
|
200
225
|
const client = await getEnvironment(config);
|
|
201
226
|
const version = await getMigrationVersionFromTag(config);
|
|
202
227
|
|
|
203
|
-
const
|
|
204
|
-
|
|
228
|
+
const migrations = await glob([`${directory}/*.js`, `${directory}/*.cjs`], {
|
|
229
|
+
absolute: true,
|
|
230
|
+
});
|
|
205
231
|
const environmentId = client.sys.id;
|
|
206
232
|
const filtered = migrations.filter((file) => {
|
|
207
233
|
const name = path.basename(file);
|
|
@@ -210,11 +236,13 @@ const migrateToContentStorage = async (config) => {
|
|
|
210
236
|
return version && parseInt(version, 10) >= parseInt(num, 10);
|
|
211
237
|
});
|
|
212
238
|
|
|
213
|
-
console.log(
|
|
239
|
+
console.log(
|
|
240
|
+
`\nFound ${pc.green(filtered.length)} executed migrations in environment ${pc.green(environmentId)}`,
|
|
241
|
+
);
|
|
214
242
|
|
|
215
243
|
const bar = new cliProgress.SingleBar(
|
|
216
|
-
{ format:
|
|
217
|
-
cliProgress.Presets.legacy
|
|
244
|
+
{ format: "Adding content-entries: {value}/{total} | ETA: {eta}s" },
|
|
245
|
+
cliProgress.Presets.legacy,
|
|
218
246
|
);
|
|
219
247
|
let progress = 0;
|
|
220
248
|
|
|
@@ -223,7 +251,10 @@ const migrateToContentStorage = async (config) => {
|
|
|
223
251
|
const name = path.basename(file);
|
|
224
252
|
const [, num] = /^(\d+)-/.exec(name);
|
|
225
253
|
|
|
226
|
-
await addMigrationEntry(
|
|
254
|
+
await addMigrationEntry(
|
|
255
|
+
{ version: num, name, state: STATE_SUCCESS },
|
|
256
|
+
config,
|
|
257
|
+
);
|
|
227
258
|
progress++;
|
|
228
259
|
bar.update(progress);
|
|
229
260
|
}
|
|
@@ -235,7 +266,7 @@ const migrateToContentStorage = async (config) => {
|
|
|
235
266
|
await migrationTag.delete();
|
|
236
267
|
} catch {}
|
|
237
268
|
}
|
|
238
|
-
console.log(
|
|
269
|
+
console.log("All done 👍🏼");
|
|
239
270
|
};
|
|
240
271
|
|
|
241
272
|
/**
|
|
@@ -248,24 +279,30 @@ const migrateToTagStorage = async (config) => {
|
|
|
248
279
|
const oldVersion = await getMigrationVersionFromTag(config);
|
|
249
280
|
if (migrationContentTypeId) {
|
|
250
281
|
const client = await getEnvironment(config);
|
|
251
|
-
const { items } = await client.getEntries({
|
|
282
|
+
const { items } = await client.getEntries({
|
|
283
|
+
content_type: migrationContentTypeId,
|
|
284
|
+
});
|
|
252
285
|
|
|
253
286
|
const version =
|
|
254
287
|
items.length &&
|
|
255
288
|
Math.max(
|
|
256
289
|
...items
|
|
257
|
-
.filter(
|
|
258
|
-
|
|
290
|
+
.filter(
|
|
291
|
+
(item) => item?.fields?.state?.[defaultLocale] === STATE_SUCCESS,
|
|
292
|
+
)
|
|
293
|
+
.map((item) => parseInt(item.sys.id, 10)),
|
|
259
294
|
);
|
|
260
295
|
|
|
261
296
|
if (version && (oldVersion || 0) < version) {
|
|
262
|
-
console.log(
|
|
297
|
+
console.log(
|
|
298
|
+
`\nAdding ${pc.green(fieldId)} tag with version ${pc.green(version)}`,
|
|
299
|
+
);
|
|
263
300
|
await setMigrationTag(version, config);
|
|
264
301
|
}
|
|
265
302
|
|
|
266
303
|
const bar = new cliProgress.SingleBar(
|
|
267
|
-
{ format:
|
|
268
|
-
cliProgress.Presets.legacy
|
|
304
|
+
{ format: "Removing content-entries: {value}/{total} | ETA: {eta}s" },
|
|
305
|
+
cliProgress.Presets.legacy,
|
|
269
306
|
);
|
|
270
307
|
let progress = 0;
|
|
271
308
|
|
|
@@ -288,7 +325,7 @@ const migrateToTagStorage = async (config) => {
|
|
|
288
325
|
}
|
|
289
326
|
}
|
|
290
327
|
|
|
291
|
-
console.log(
|
|
328
|
+
console.log("All done 👍🏼");
|
|
292
329
|
};
|
|
293
330
|
|
|
294
331
|
/**
|
|
@@ -358,8 +395,11 @@ const getLatestVersion = async (config) => {
|
|
|
358
395
|
|
|
359
396
|
const getVersionFromFile = (file) => {
|
|
360
397
|
const name = path.basename(file);
|
|
361
|
-
const
|
|
362
|
-
|
|
398
|
+
const versionMatch = /^\d+/.exec(name);
|
|
399
|
+
if (!versionMatch) {
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
return Number.parseInt(versionMatch[0], 10);
|
|
363
403
|
};
|
|
364
404
|
|
|
365
405
|
/**
|
|
@@ -368,11 +408,12 @@ const getVersionFromFile = (file) => {
|
|
|
368
408
|
*/
|
|
369
409
|
const getNewMigrations = async (config) => {
|
|
370
410
|
const { directory, storage, migrationContentTypeId } = config || {};
|
|
371
|
-
const
|
|
372
|
-
|
|
411
|
+
const migrations = (
|
|
412
|
+
await glob([`${directory}/*.js`, `${directory}/*.cjs`], { absolute: true })
|
|
413
|
+
).sort((a, b) => {
|
|
373
414
|
const numA = getVersionFromFile(a);
|
|
374
415
|
const numB = getVersionFromFile(b);
|
|
375
|
-
return numA - numB;
|
|
416
|
+
return (numA || 0) - (numB || 0);
|
|
376
417
|
});
|
|
377
418
|
|
|
378
419
|
if (storage === STORAGE_CONTENT) {
|
|
@@ -380,25 +421,31 @@ const getNewMigrations = async (config) => {
|
|
|
380
421
|
const versions = await getMigrationVersions(config);
|
|
381
422
|
const result = migrations.filter((file) => {
|
|
382
423
|
const num = getVersionFromFile(file);
|
|
383
|
-
return !(versions || []).includes(num);
|
|
424
|
+
return !!num && !(versions || []).includes(num);
|
|
384
425
|
});
|
|
385
426
|
|
|
386
427
|
return result;
|
|
387
|
-
} catch
|
|
428
|
+
} catch {
|
|
388
429
|
// check if we have a migration scheduled which adds the initial content-type
|
|
389
|
-
const regexp = new RegExp(
|
|
430
|
+
const regexp = new RegExp(
|
|
431
|
+
`createContentType\\(['"]${migrationContentTypeId}['"]\\)`,
|
|
432
|
+
"mg",
|
|
433
|
+
);
|
|
390
434
|
const initial = (
|
|
391
435
|
await Promise.all(
|
|
392
436
|
migrations.map(async (file) => {
|
|
393
|
-
return fs.readFile(file,
|
|
394
|
-
})
|
|
437
|
+
return fs.readFile(file, "utf8");
|
|
438
|
+
}),
|
|
395
439
|
)
|
|
396
440
|
).some((content) => regexp.test(content));
|
|
397
441
|
|
|
398
442
|
if (initial) {
|
|
399
443
|
return migrations;
|
|
400
444
|
}
|
|
401
|
-
console.error(
|
|
445
|
+
console.error(
|
|
446
|
+
pc.red("\nError:"),
|
|
447
|
+
`Missing migration content type. Run ${pc.cyan("npx migrations init")}`,
|
|
448
|
+
);
|
|
402
449
|
process.exit(1);
|
|
403
450
|
}
|
|
404
451
|
}
|
|
@@ -420,3 +467,4 @@ module.exports.getMigrationVersionFromTag = getMigrationVersionFromTag;
|
|
|
420
467
|
module.exports.getLatestVersion = getLatestVersion;
|
|
421
468
|
module.exports.storeMigration = storeMigration;
|
|
422
469
|
module.exports.getNewMigrations = getNewMigrations;
|
|
470
|
+
module.exports.getVersionFromFile = getVersionFromFile;
|
package/lib/content.js
CHANGED
|
@@ -61,8 +61,8 @@ const transferContent = async (config) => {
|
|
|
61
61
|
if (sourceVersion !== destVersion) {
|
|
62
62
|
throw new Error(
|
|
63
63
|
`Different migration states detected. ${pc.bold(sourceEnvironmentId)} (${sourceVersion}) !== ${pc.bold(
|
|
64
|
-
destEnvironmentId
|
|
65
|
-
)} (${destVersion})
|
|
64
|
+
destEnvironmentId,
|
|
65
|
+
)} (${destVersion})`,
|
|
66
66
|
);
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -72,7 +72,7 @@ const transferContent = async (config) => {
|
|
|
72
72
|
entries: sourceEntries,
|
|
73
73
|
assets: sourceAssetsBase,
|
|
74
74
|
filteredEntries,
|
|
75
|
-
contentTypes:
|
|
75
|
+
contentTypes: _sourceContentTypes,
|
|
76
76
|
} = await getContent({
|
|
77
77
|
...config,
|
|
78
78
|
environmentId: sourceEnvironmentId,
|
|
@@ -147,8 +147,8 @@ const transferContent = async (config) => {
|
|
|
147
147
|
|
|
148
148
|
console.log(
|
|
149
149
|
`${br}Transfering ${pc.cyan(`${entries.length} Entries`)} and ${pc.cyan(`${assets.length} Assets`)} from ${pc.cyan(
|
|
150
|
-
sourceEnvironmentId
|
|
151
|
-
)} to ${pc.cyan(destEnvironmentId)}
|
|
150
|
+
sourceEnvironmentId,
|
|
151
|
+
)} to ${pc.cyan(destEnvironmentId)}`,
|
|
152
152
|
);
|
|
153
153
|
|
|
154
154
|
proceed = await confirm(config);
|