sb-mig 6.2.0 → 6.3.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 +15 -4
- package/dist/api/assets/asset-folders.d.ts +3 -0
- package/dist/api/assets/asset-folders.js +56 -0
- package/dist/api/assets/asset-folders.types.d.ts +29 -0
- package/dist/api/assets/asset-folders.types.js +1 -0
- package/dist/api/assets/assets.d.ts +4 -1
- package/dist/api/assets/assets.js +61 -16
- package/dist/api/assets/assets.types.d.ts +12 -1
- package/dist/api/assets/index.d.ts +4 -2
- package/dist/api/assets/index.js +2 -1
- package/dist/api/copy/assets.d.ts +12 -0
- package/dist/api/copy/assets.js +84 -0
- package/dist/api/copy/graph.d.ts +8 -0
- package/dist/api/copy/graph.js +27 -0
- package/dist/api/copy/index.d.ts +6 -0
- package/dist/api/copy/index.js +6 -0
- package/dist/api/copy/manifest.d.ts +23 -0
- package/dist/api/copy/manifest.js +115 -0
- package/dist/api/copy/reference-rewriter.d.ts +6 -0
- package/dist/api/copy/reference-rewriter.js +153 -0
- package/dist/api/copy/reference-scanner.d.ts +23 -0
- package/dist/api/copy/reference-scanner.js +322 -0
- package/dist/api/copy/types.d.ts +196 -0
- package/dist/api/copy/types.js +1 -0
- package/dist/api/data-migration/component-data-migration.d.ts +81 -0
- package/dist/api/data-migration/component-data-migration.js +391 -95
- package/dist/api/data-migration/migration-run-log.d.ts +3 -1
- package/dist/api/data-migration/migration-run-log.js +2 -1
- package/dist/api/managementApi.d.ts +6 -0
- package/dist/api/stories/stories.js +3 -3
- package/dist/api/stories/stories.types.d.ts +3 -1
- package/dist/api/testApi.d.ts +6 -0
- package/dist/api-v2/requestConfig.d.ts +17 -1
- package/dist/api-v2/requestConfig.js +28 -0
- package/dist/cli/cli-descriptions.d.ts +3 -3
- package/dist/cli/cli-descriptions.js +92 -18
- package/dist/cli/commands/copy.js +2091 -145
- package/dist/cli/commands/migrate.js +27 -1
- package/dist/cli/index.js +3 -0
- package/dist/utils/files.d.ts +2 -0
- package/dist/utils/files.js +86 -3
- package/dist-cjs/api/stories/stories.js +3 -3
- package/dist-cjs/api-v2/requestConfig.js +29 -0
- package/package.json +2 -2
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const RESERVED_CONTENT_KEYS = new Set(["_uid", "component", "_editable"]);
|
|
2
|
+
export const rewriteCopyReferences = ({ value, maps, schemas, }) => {
|
|
3
|
+
const clonedValue = cloneJson(value);
|
|
4
|
+
const state = {
|
|
5
|
+
maps,
|
|
6
|
+
schemas,
|
|
7
|
+
records: [],
|
|
8
|
+
warnings: [],
|
|
9
|
+
};
|
|
10
|
+
rewriteNode(clonedValue, "$", state);
|
|
11
|
+
return {
|
|
12
|
+
value: clonedValue,
|
|
13
|
+
records: state.records,
|
|
14
|
+
warnings: state.warnings,
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
const cloneJson = (value) => JSON.parse(JSON.stringify(value));
|
|
18
|
+
const rewriteNode = (node, path, state) => {
|
|
19
|
+
if (Array.isArray(node)) {
|
|
20
|
+
node.forEach((item, index) => rewriteNode(item, `${path}[${index}]`, state));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!isRecord(node)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
rewriteAssetObject(node, path, state);
|
|
27
|
+
rewriteStoryLinkObject(node, path, state);
|
|
28
|
+
rewriteRichtextLinkObject(node, path, state);
|
|
29
|
+
rewriteSchemaAwareOptions(node, path, state);
|
|
30
|
+
if (node.type === "blok" &&
|
|
31
|
+
isRecord(node.attrs) &&
|
|
32
|
+
Array.isArray(node.attrs.body)) {
|
|
33
|
+
node.attrs.body.forEach((blok, index) => rewriteNode(blok, `${path}.attrs.body[${index}]`, state));
|
|
34
|
+
}
|
|
35
|
+
for (const [key, value] of Object.entries(node)) {
|
|
36
|
+
if (key === "attrs" && (node.type === "link" || node.type === "blok")) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
rewriteNode(value, `${path}.${key}`, state);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const rewriteAssetObject = (node, path, state) => {
|
|
43
|
+
const hasAssetShape = typeof node.filename === "string" &&
|
|
44
|
+
(typeof node.id === "number" ||
|
|
45
|
+
state.maps.assetFilenames.has(node.filename));
|
|
46
|
+
if (!hasAssetShape) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const targetById = typeof node.id === "number"
|
|
50
|
+
? state.maps.assetIds.get(node.id)
|
|
51
|
+
: undefined;
|
|
52
|
+
const targetFilename = targetById?.filename ?? state.maps.assetFilenames.get(node.filename);
|
|
53
|
+
if (typeof node.id === "number" && targetById) {
|
|
54
|
+
addRecord(state, {
|
|
55
|
+
type: "asset",
|
|
56
|
+
path: `${path}.id`,
|
|
57
|
+
sourceValue: node.id,
|
|
58
|
+
targetValue: targetById.id,
|
|
59
|
+
field: "id",
|
|
60
|
+
});
|
|
61
|
+
node.id = targetById.id;
|
|
62
|
+
}
|
|
63
|
+
if (targetFilename && node.filename !== targetFilename) {
|
|
64
|
+
addRecord(state, {
|
|
65
|
+
type: "asset",
|
|
66
|
+
path: `${path}.filename`,
|
|
67
|
+
sourceValue: node.filename,
|
|
68
|
+
targetValue: targetFilename,
|
|
69
|
+
field: "filename",
|
|
70
|
+
});
|
|
71
|
+
node.filename = targetFilename;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const rewriteStoryLinkObject = (node, path, state) => {
|
|
75
|
+
if (node.linktype !== "story") {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (typeof node.id === "number") {
|
|
79
|
+
const targetId = state.maps.storyIds.get(node.id);
|
|
80
|
+
if (targetId !== undefined) {
|
|
81
|
+
addRecord(state, {
|
|
82
|
+
type: "story",
|
|
83
|
+
path: `${path}.id`,
|
|
84
|
+
sourceValue: node.id,
|
|
85
|
+
targetValue: targetId,
|
|
86
|
+
field: "id",
|
|
87
|
+
});
|
|
88
|
+
node.id = targetId;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (typeof node.uuid === "string") {
|
|
92
|
+
const targetUuid = state.maps.storyUuids.get(node.uuid);
|
|
93
|
+
if (targetUuid !== undefined) {
|
|
94
|
+
addRecord(state, {
|
|
95
|
+
type: "story",
|
|
96
|
+
path: `${path}.uuid`,
|
|
97
|
+
sourceValue: node.uuid,
|
|
98
|
+
targetValue: targetUuid,
|
|
99
|
+
field: "uuid",
|
|
100
|
+
});
|
|
101
|
+
node.uuid = targetUuid;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const rewriteRichtextLinkObject = (node, path, state) => {
|
|
106
|
+
if (node.type !== "link" || !isRecord(node.attrs)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
rewriteStoryLinkObject(node.attrs, `${path}.attrs`, state);
|
|
110
|
+
};
|
|
111
|
+
const rewriteSchemaAwareOptions = (node, path, state) => {
|
|
112
|
+
if (!state.schemas || typeof node.component !== "string") {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const schema = state.schemas[node.component];
|
|
116
|
+
if (!schema) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
for (const [fieldName, fieldValue] of Object.entries(node)) {
|
|
120
|
+
if (RESERVED_CONTENT_KEYS.has(fieldName) ||
|
|
121
|
+
!Array.isArray(fieldValue)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const fieldSchema = getFieldSchema(schema, fieldName);
|
|
125
|
+
if (fieldSchema?.type !== "options" ||
|
|
126
|
+
fieldSchema.source !== "internal_stories") {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
fieldValue.forEach((item, index) => {
|
|
130
|
+
if (typeof item !== "number") {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const targetId = state.maps.storyIds.get(item);
|
|
134
|
+
if (targetId === undefined) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
addRecord(state, {
|
|
138
|
+
type: "story",
|
|
139
|
+
path: `${path}.${fieldName}[${index}]`,
|
|
140
|
+
sourceValue: item,
|
|
141
|
+
targetValue: targetId,
|
|
142
|
+
field: "id",
|
|
143
|
+
});
|
|
144
|
+
fieldValue[index] = targetId;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const getFieldSchema = (schema, fieldName) => schema[fieldName] ?? schema[normalizeFieldName(fieldName)];
|
|
149
|
+
const normalizeFieldName = (fieldName) => fieldName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
150
|
+
const addRecord = (state, record) => {
|
|
151
|
+
state.records.push(record);
|
|
152
|
+
};
|
|
153
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CopyComponentSchemaRegistry, CopyReferenceScannerOptions, CopyReferenceScannerResult } from "./types.js";
|
|
2
|
+
type StoryLike = {
|
|
3
|
+
id?: number;
|
|
4
|
+
uuid?: string;
|
|
5
|
+
full_slug?: string;
|
|
6
|
+
parent_id?: number | null;
|
|
7
|
+
alternates?: Array<{
|
|
8
|
+
id?: number;
|
|
9
|
+
parent_id?: number | null;
|
|
10
|
+
}>;
|
|
11
|
+
content?: Record<string, unknown>;
|
|
12
|
+
};
|
|
13
|
+
export declare const scanStoryReferences: ({ story, schemas, options, }: {
|
|
14
|
+
story: StoryLike;
|
|
15
|
+
schemas: CopyComponentSchemaRegistry;
|
|
16
|
+
options?: CopyReferenceScannerOptions;
|
|
17
|
+
}) => CopyReferenceScannerResult;
|
|
18
|
+
export declare const scanStoriesReferences: ({ stories, schemas, options, }: {
|
|
19
|
+
stories: StoryLike[];
|
|
20
|
+
schemas: CopyComponentSchemaRegistry;
|
|
21
|
+
options?: CopyReferenceScannerOptions;
|
|
22
|
+
}) => CopyReferenceScannerResult;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
const RESERVED_CONTENT_KEYS = new Set(["_uid", "component", "_editable"]);
|
|
2
|
+
export const scanStoryReferences = ({ story, schemas, options = {}, }) => {
|
|
3
|
+
const state = {
|
|
4
|
+
schemas,
|
|
5
|
+
options: {
|
|
6
|
+
referencePolicy: options.referencePolicy ?? "preserve",
|
|
7
|
+
},
|
|
8
|
+
context: {
|
|
9
|
+
sourceStoryId: story.id,
|
|
10
|
+
sourceStoryUuid: story.uuid,
|
|
11
|
+
sourceStoryFullSlug: story.full_slug,
|
|
12
|
+
},
|
|
13
|
+
storyReferences: [],
|
|
14
|
+
assetReferences: [],
|
|
15
|
+
assetNodesByKey: new Map(),
|
|
16
|
+
opaqueFields: [],
|
|
17
|
+
warnings: [],
|
|
18
|
+
errors: [],
|
|
19
|
+
missingSchemas: new Set(),
|
|
20
|
+
};
|
|
21
|
+
scanStoryMetadata(story, state);
|
|
22
|
+
scanComponentNode(story.content, "content", state);
|
|
23
|
+
return {
|
|
24
|
+
storyReferences: state.storyReferences,
|
|
25
|
+
assetReferences: state.assetReferences,
|
|
26
|
+
assetNodes: Array.from(state.assetNodesByKey.values()),
|
|
27
|
+
opaqueFields: state.opaqueFields,
|
|
28
|
+
warnings: state.warnings,
|
|
29
|
+
errors: state.errors,
|
|
30
|
+
missingSchemas: Array.from(state.missingSchemas),
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export const scanStoriesReferences = ({ stories, schemas, options = {}, }) => {
|
|
34
|
+
const combined = {
|
|
35
|
+
storyReferences: [],
|
|
36
|
+
assetReferences: [],
|
|
37
|
+
assetNodes: [],
|
|
38
|
+
opaqueFields: [],
|
|
39
|
+
warnings: [],
|
|
40
|
+
errors: [],
|
|
41
|
+
missingSchemas: [],
|
|
42
|
+
};
|
|
43
|
+
const assetNodesByKey = new Map();
|
|
44
|
+
const missingSchemas = new Set();
|
|
45
|
+
stories.forEach((story, index) => {
|
|
46
|
+
const result = scanStoryReferences({ story, schemas, options });
|
|
47
|
+
combined.storyReferences.push(...result.storyReferences);
|
|
48
|
+
combined.assetReferences.push(...result.assetReferences);
|
|
49
|
+
combined.opaqueFields.push(...result.opaqueFields);
|
|
50
|
+
combined.warnings.push(...result.warnings);
|
|
51
|
+
combined.errors.push(...result.errors);
|
|
52
|
+
for (const schemaName of result.missingSchemas) {
|
|
53
|
+
missingSchemas.add(schemaName);
|
|
54
|
+
}
|
|
55
|
+
for (const assetNode of result.assetNodes) {
|
|
56
|
+
assetNodesByKey.set(getAssetNodeKey(assetNode), assetNode);
|
|
57
|
+
}
|
|
58
|
+
const scanned = index + 1;
|
|
59
|
+
if (options.onProgress &&
|
|
60
|
+
(scanned === stories.length ||
|
|
61
|
+
scanned % 10 === 0 ||
|
|
62
|
+
stories.length <= 10)) {
|
|
63
|
+
options.onProgress({
|
|
64
|
+
scanned,
|
|
65
|
+
total: stories.length,
|
|
66
|
+
storyFullSlug: story.full_slug,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
combined.assetNodes = Array.from(assetNodesByKey.values());
|
|
71
|
+
combined.missingSchemas = Array.from(missingSchemas);
|
|
72
|
+
return combined;
|
|
73
|
+
};
|
|
74
|
+
const scanStoryMetadata = (story, state) => {
|
|
75
|
+
if (typeof story.parent_id === "number") {
|
|
76
|
+
addStoryReference(state, {
|
|
77
|
+
path: "parent_id",
|
|
78
|
+
referencedStoryId: story.parent_id,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
story.alternates?.forEach((alternate, index) => {
|
|
82
|
+
if (typeof alternate.id === "number") {
|
|
83
|
+
addStoryReference(state, {
|
|
84
|
+
path: `alternates[${index}].id`,
|
|
85
|
+
referencedStoryId: alternate.id,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (typeof alternate.parent_id === "number") {
|
|
89
|
+
addStoryReference(state, {
|
|
90
|
+
path: `alternates[${index}].parent_id`,
|
|
91
|
+
referencedStoryId: alternate.parent_id,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
const scanComponentNode = (node, path, state) => {
|
|
97
|
+
if (!isRecord(node)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const component = node.component;
|
|
101
|
+
if (typeof component !== "string" || component.length === 0) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const schema = state.schemas[component];
|
|
105
|
+
if (!schema) {
|
|
106
|
+
state.missingSchemas.add(component);
|
|
107
|
+
state.warnings.push({
|
|
108
|
+
code: "missing_component_schema",
|
|
109
|
+
message: `Component schema '${component}' was not found. References inside this component were not scanned.`,
|
|
110
|
+
path,
|
|
111
|
+
});
|
|
112
|
+
state.opaqueFields.push({
|
|
113
|
+
type: "opaque_field",
|
|
114
|
+
...state.context,
|
|
115
|
+
component,
|
|
116
|
+
field: component,
|
|
117
|
+
path,
|
|
118
|
+
reason: "unknown_schema",
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
for (const [fieldName, fieldValue] of Object.entries(node)) {
|
|
123
|
+
if (RESERVED_CONTENT_KEYS.has(fieldName)) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const normalizedFieldName = normalizeFieldName(fieldName);
|
|
127
|
+
const fieldSchema = schema[normalizedFieldName];
|
|
128
|
+
const fieldPath = `${path}.${fieldName}`;
|
|
129
|
+
if (!fieldSchema) {
|
|
130
|
+
addOpaqueField(state, {
|
|
131
|
+
component,
|
|
132
|
+
field: normalizedFieldName,
|
|
133
|
+
path: fieldPath,
|
|
134
|
+
reason: "unsupported_field",
|
|
135
|
+
});
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (typeof fieldSchema.plugin === "string") {
|
|
139
|
+
addOpaqueField(state, {
|
|
140
|
+
component,
|
|
141
|
+
field: normalizedFieldName,
|
|
142
|
+
fieldType: fieldSchema.type,
|
|
143
|
+
plugin: fieldSchema.plugin,
|
|
144
|
+
path: fieldPath,
|
|
145
|
+
reason: "plugin_field",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
scanField({
|
|
149
|
+
component,
|
|
150
|
+
fieldName: normalizedFieldName,
|
|
151
|
+
fieldValue,
|
|
152
|
+
fieldSchema,
|
|
153
|
+
path: fieldPath,
|
|
154
|
+
state,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const scanField = ({ fieldValue, fieldSchema, path, state, }) => {
|
|
159
|
+
switch (fieldSchema.type) {
|
|
160
|
+
case "asset":
|
|
161
|
+
scanAssetField(fieldValue, path, state);
|
|
162
|
+
return;
|
|
163
|
+
case "multiasset":
|
|
164
|
+
scanMultiassetField(fieldValue, path, state);
|
|
165
|
+
return;
|
|
166
|
+
case "multilink":
|
|
167
|
+
scanMultilinkField(fieldValue, path, state);
|
|
168
|
+
return;
|
|
169
|
+
case "options":
|
|
170
|
+
scanOptionsField(fieldValue, fieldSchema, path, state);
|
|
171
|
+
return;
|
|
172
|
+
case "bloks":
|
|
173
|
+
scanBloksField(fieldValue, path, state);
|
|
174
|
+
return;
|
|
175
|
+
case "richtext":
|
|
176
|
+
scanRichtextField(fieldValue, path, state);
|
|
177
|
+
return;
|
|
178
|
+
default:
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
const scanAssetField = (fieldValue, path, state) => {
|
|
183
|
+
if (!isRecord(fieldValue)) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const assetId = typeof fieldValue.id === "number" ? fieldValue.id : undefined;
|
|
187
|
+
const filename = typeof fieldValue.filename === "string"
|
|
188
|
+
? fieldValue.filename
|
|
189
|
+
: undefined;
|
|
190
|
+
if (assetId === undefined && !filename) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const reference = {
|
|
194
|
+
type: "asset_reference",
|
|
195
|
+
...state.context,
|
|
196
|
+
assetId,
|
|
197
|
+
filename,
|
|
198
|
+
path,
|
|
199
|
+
status: "planned",
|
|
200
|
+
};
|
|
201
|
+
state.assetReferences.push(reference);
|
|
202
|
+
if (assetId !== undefined && filename) {
|
|
203
|
+
const node = {
|
|
204
|
+
type: "asset",
|
|
205
|
+
sourceId: assetId,
|
|
206
|
+
sourceFilename: filename,
|
|
207
|
+
action: "unknown",
|
|
208
|
+
};
|
|
209
|
+
state.assetNodesByKey.set(getAssetNodeKey(node), node);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
const scanMultiassetField = (fieldValue, path, state) => {
|
|
213
|
+
if (!Array.isArray(fieldValue)) {
|
|
214
|
+
state.warnings.push({
|
|
215
|
+
code: "invalid_multiasset_field",
|
|
216
|
+
message: "Expected multiasset field to be an array.",
|
|
217
|
+
path,
|
|
218
|
+
sourceValue: fieldValue,
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
fieldValue.forEach((asset, index) => scanAssetField(asset, `${path}[${index}]`, state));
|
|
223
|
+
};
|
|
224
|
+
const scanMultilinkField = (fieldValue, path, state) => {
|
|
225
|
+
if (!isRecord(fieldValue) || fieldValue.linktype !== "story") {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const referencedStoryId = typeof fieldValue.id === "number" ? fieldValue.id : undefined;
|
|
229
|
+
if (referencedStoryId === undefined) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
addStoryReference(state, {
|
|
233
|
+
path: `${path}.id`,
|
|
234
|
+
referencedStoryId,
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
const scanOptionsField = (fieldValue, fieldSchema, path, state) => {
|
|
238
|
+
if (fieldSchema.source !== "internal_stories") {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (!Array.isArray(fieldValue)) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
fieldValue.forEach((value, index) => {
|
|
245
|
+
if (typeof value === "number") {
|
|
246
|
+
addStoryReference(state, {
|
|
247
|
+
path: `${path}[${index}]`,
|
|
248
|
+
referencedStoryId: value,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
const scanBloksField = (fieldValue, path, state) => {
|
|
254
|
+
if (!Array.isArray(fieldValue)) {
|
|
255
|
+
state.warnings.push({
|
|
256
|
+
code: "invalid_bloks_field",
|
|
257
|
+
message: "Expected bloks field to be an array.",
|
|
258
|
+
path,
|
|
259
|
+
sourceValue: fieldValue,
|
|
260
|
+
});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
fieldValue.forEach((blok, index) => scanComponentNode(blok, `${path}[${index}]`, state));
|
|
264
|
+
};
|
|
265
|
+
const scanRichtextField = (fieldValue, path, state) => {
|
|
266
|
+
scanRichtextNode(fieldValue, path, state);
|
|
267
|
+
};
|
|
268
|
+
const scanRichtextNode = (node, path, state) => {
|
|
269
|
+
if (Array.isArray(node)) {
|
|
270
|
+
node.forEach((item, index) => scanRichtextNode(item, `${path}[${index}]`, state));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (!isRecord(node)) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (node.type === "link" && isRecord(node.attrs)) {
|
|
277
|
+
const attrs = node.attrs;
|
|
278
|
+
if (attrs.linktype === "story" && typeof attrs.uuid === "string") {
|
|
279
|
+
addStoryReference(state, {
|
|
280
|
+
path: `${path}.attrs.uuid`,
|
|
281
|
+
referencedStoryUuid: attrs.uuid,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (node.type === "blok" && isRecord(node.attrs)) {
|
|
286
|
+
const body = node.attrs.body;
|
|
287
|
+
if (Array.isArray(body)) {
|
|
288
|
+
body.forEach((blok, index) => scanComponentNode(blok, `${path}.attrs.body[${index}]`, state));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
for (const [key, value] of Object.entries(node)) {
|
|
292
|
+
if (key === "attrs" && (node.type === "link" || node.type === "blok")) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
scanRichtextNode(value, `${path}.${key}`, state);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
const addStoryReference = (state, reference) => {
|
|
299
|
+
state.storyReferences.push({
|
|
300
|
+
type: "story_reference",
|
|
301
|
+
...state.context,
|
|
302
|
+
...reference,
|
|
303
|
+
status: state.options.referencePolicy === "preserve"
|
|
304
|
+
? "preserved_external"
|
|
305
|
+
: "unresolved",
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
const addOpaqueField = (state, field) => {
|
|
309
|
+
state.opaqueFields.push({
|
|
310
|
+
type: "opaque_field",
|
|
311
|
+
...state.context,
|
|
312
|
+
...field,
|
|
313
|
+
});
|
|
314
|
+
state.warnings.push({
|
|
315
|
+
code: field.reason,
|
|
316
|
+
message: `Field '${field.component}.${field.field}' may contain references that cannot be safely scanned yet.`,
|
|
317
|
+
path: field.path,
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
const normalizeFieldName = (fieldName) => fieldName.replace(/__i18n__.*/, "");
|
|
321
|
+
const getAssetNodeKey = (asset) => `${asset.sourceId}:${asset.sourceFilename}`;
|
|
322
|
+
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
export type CopyResourceType = "story" | "asset" | "asset_folder";
|
|
2
|
+
export type CopyAction = "created" | "updated" | "skipped" | "matched_by_manifest" | "matched_by_target_key" | "failed";
|
|
3
|
+
export type CopyGraphAction = "create" | "update" | "skip" | "match" | "unknown";
|
|
4
|
+
export type CopyWarning = {
|
|
5
|
+
code: string;
|
|
6
|
+
message: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
sourceValue?: unknown;
|
|
9
|
+
targetValue?: unknown;
|
|
10
|
+
};
|
|
11
|
+
export type CopyError = {
|
|
12
|
+
code: string;
|
|
13
|
+
message: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
sourceValue?: unknown;
|
|
16
|
+
};
|
|
17
|
+
type CopyManifestEntryBase = {
|
|
18
|
+
source_space_id: string;
|
|
19
|
+
target_space_id: string;
|
|
20
|
+
action: CopyAction;
|
|
21
|
+
created_at: string;
|
|
22
|
+
};
|
|
23
|
+
export type CopyStoryManifestEntry = CopyManifestEntryBase & {
|
|
24
|
+
type: "story";
|
|
25
|
+
source_id: number;
|
|
26
|
+
target_id: number;
|
|
27
|
+
source_uuid: string;
|
|
28
|
+
target_uuid: string;
|
|
29
|
+
source_full_slug?: string;
|
|
30
|
+
target_full_slug?: string;
|
|
31
|
+
};
|
|
32
|
+
export type CopyAssetManifestEntry = CopyManifestEntryBase & {
|
|
33
|
+
type: "asset";
|
|
34
|
+
source_id: number;
|
|
35
|
+
target_id: number;
|
|
36
|
+
source_filename: string;
|
|
37
|
+
target_filename: string;
|
|
38
|
+
source_asset_folder_id?: number | null;
|
|
39
|
+
target_asset_folder_id?: number | null;
|
|
40
|
+
};
|
|
41
|
+
export type CopyAssetFolderManifestEntry = CopyManifestEntryBase & {
|
|
42
|
+
type: "asset_folder";
|
|
43
|
+
source_id: number;
|
|
44
|
+
target_id: number;
|
|
45
|
+
source_name?: string;
|
|
46
|
+
target_name?: string;
|
|
47
|
+
source_parent_id?: number | null;
|
|
48
|
+
target_parent_id?: number | null;
|
|
49
|
+
source_path?: string;
|
|
50
|
+
target_path?: string;
|
|
51
|
+
};
|
|
52
|
+
export type CopyManifestEntry = CopyStoryManifestEntry | CopyAssetManifestEntry | CopyAssetFolderManifestEntry;
|
|
53
|
+
export type CopyMaps = {
|
|
54
|
+
storyIds: Map<number, number>;
|
|
55
|
+
storyUuids: Map<string, string>;
|
|
56
|
+
assetIds: Map<number, {
|
|
57
|
+
id: number;
|
|
58
|
+
filename: string;
|
|
59
|
+
}>;
|
|
60
|
+
assetFilenames: Map<string, string>;
|
|
61
|
+
assetFolderIds: Map<number, number>;
|
|
62
|
+
};
|
|
63
|
+
export type CopyScope = {
|
|
64
|
+
command: "copy stories" | "copy assets" | "copy space";
|
|
65
|
+
source?: string;
|
|
66
|
+
destination?: string;
|
|
67
|
+
mode?: "subtree" | "children" | "self";
|
|
68
|
+
withAssets?: boolean;
|
|
69
|
+
referencePolicy?: "preserve" | "fail" | "include-referenced";
|
|
70
|
+
};
|
|
71
|
+
export type CopyGraphStoryNode = {
|
|
72
|
+
type: "story";
|
|
73
|
+
sourceId: number;
|
|
74
|
+
sourceUuid?: string;
|
|
75
|
+
sourceFullSlug: string;
|
|
76
|
+
targetFullSlug?: string;
|
|
77
|
+
isFolder?: boolean;
|
|
78
|
+
action: CopyGraphAction;
|
|
79
|
+
};
|
|
80
|
+
export type CopyGraphAssetNode = {
|
|
81
|
+
type: "asset";
|
|
82
|
+
sourceId: number;
|
|
83
|
+
sourceFilename: string;
|
|
84
|
+
targetFilename?: string;
|
|
85
|
+
sourceAssetFolderId?: number | null;
|
|
86
|
+
targetAssetFolderId?: number | null;
|
|
87
|
+
action: CopyGraphAction;
|
|
88
|
+
};
|
|
89
|
+
export type CopyGraphAssetFolderNode = {
|
|
90
|
+
type: "asset_folder";
|
|
91
|
+
sourceId: number;
|
|
92
|
+
sourcePath?: string;
|
|
93
|
+
targetPath?: string;
|
|
94
|
+
sourceParentId?: number | null;
|
|
95
|
+
targetParentId?: number | null;
|
|
96
|
+
action: CopyGraphAction;
|
|
97
|
+
};
|
|
98
|
+
export type CopyGraphStoryReference = {
|
|
99
|
+
type: "story_reference";
|
|
100
|
+
sourceStoryId?: number;
|
|
101
|
+
sourceStoryUuid?: string;
|
|
102
|
+
sourceStoryFullSlug?: string;
|
|
103
|
+
referencedStoryId?: number;
|
|
104
|
+
referencedStoryUuid?: string;
|
|
105
|
+
path: string;
|
|
106
|
+
status: "mapped" | "preserved_external" | "unresolved" | "unsupported";
|
|
107
|
+
};
|
|
108
|
+
export type CopyGraphAssetReference = {
|
|
109
|
+
type: "asset_reference";
|
|
110
|
+
sourceStoryId?: number;
|
|
111
|
+
sourceStoryUuid?: string;
|
|
112
|
+
sourceStoryFullSlug?: string;
|
|
113
|
+
assetId?: number;
|
|
114
|
+
filename?: string;
|
|
115
|
+
path: string;
|
|
116
|
+
status: "planned" | "mapped" | "unresolved" | "unsupported";
|
|
117
|
+
};
|
|
118
|
+
export type CopyGraphOpaqueField = {
|
|
119
|
+
type: "opaque_field";
|
|
120
|
+
sourceStoryId?: number;
|
|
121
|
+
sourceStoryUuid?: string;
|
|
122
|
+
sourceStoryFullSlug?: string;
|
|
123
|
+
component: string;
|
|
124
|
+
field: string;
|
|
125
|
+
fieldType?: string;
|
|
126
|
+
plugin?: string;
|
|
127
|
+
path: string;
|
|
128
|
+
reason: "plugin_field" | "unknown_schema" | "unsupported_field";
|
|
129
|
+
};
|
|
130
|
+
export type CopyGraph = {
|
|
131
|
+
schemaVersion: 1;
|
|
132
|
+
sourceSpaceId: string;
|
|
133
|
+
targetSpaceId: string;
|
|
134
|
+
generatedAt: string;
|
|
135
|
+
scope: CopyScope;
|
|
136
|
+
stories: CopyGraphStoryNode[];
|
|
137
|
+
assets: CopyGraphAssetNode[];
|
|
138
|
+
assetFolders: CopyGraphAssetFolderNode[];
|
|
139
|
+
storyReferences: CopyGraphStoryReference[];
|
|
140
|
+
assetReferences: CopyGraphAssetReference[];
|
|
141
|
+
opaqueFields: CopyGraphOpaqueField[];
|
|
142
|
+
warnings: CopyWarning[];
|
|
143
|
+
errors: CopyError[];
|
|
144
|
+
limitations: string[];
|
|
145
|
+
};
|
|
146
|
+
export type CopyGraphSummary = {
|
|
147
|
+
stories: number;
|
|
148
|
+
assets: number;
|
|
149
|
+
assetFolders: number;
|
|
150
|
+
storyReferences: number;
|
|
151
|
+
assetReferences: number;
|
|
152
|
+
opaqueFields: number;
|
|
153
|
+
warnings: number;
|
|
154
|
+
errors: number;
|
|
155
|
+
limitations: number;
|
|
156
|
+
};
|
|
157
|
+
export type CopyComponentSchemaField = {
|
|
158
|
+
type?: string;
|
|
159
|
+
source?: string;
|
|
160
|
+
plugin?: string;
|
|
161
|
+
component_whitelist?: string[];
|
|
162
|
+
restrict_components?: boolean;
|
|
163
|
+
[key: string]: unknown;
|
|
164
|
+
};
|
|
165
|
+
export type CopyComponentSchema = Record<string, CopyComponentSchemaField>;
|
|
166
|
+
export type CopyComponentSchemaRegistry = Record<string, CopyComponentSchema>;
|
|
167
|
+
export type CopyReferenceScannerOptions = {
|
|
168
|
+
referencePolicy?: "preserve" | "fail" | "include-referenced";
|
|
169
|
+
onProgress?: (progress: {
|
|
170
|
+
scanned: number;
|
|
171
|
+
total: number;
|
|
172
|
+
storyFullSlug?: string;
|
|
173
|
+
}) => void;
|
|
174
|
+
};
|
|
175
|
+
export type CopyReferenceScannerResult = {
|
|
176
|
+
storyReferences: CopyGraphStoryReference[];
|
|
177
|
+
assetReferences: CopyGraphAssetReference[];
|
|
178
|
+
assetNodes: CopyGraphAssetNode[];
|
|
179
|
+
opaqueFields: CopyGraphOpaqueField[];
|
|
180
|
+
warnings: CopyWarning[];
|
|
181
|
+
errors: CopyError[];
|
|
182
|
+
missingSchemas: string[];
|
|
183
|
+
};
|
|
184
|
+
export type CopyRewriteRecord = {
|
|
185
|
+
type: "asset" | "story";
|
|
186
|
+
path: string;
|
|
187
|
+
sourceValue: unknown;
|
|
188
|
+
targetValue: unknown;
|
|
189
|
+
field: "id" | "uuid" | "filename";
|
|
190
|
+
};
|
|
191
|
+
export type CopyRewriteResult<T> = {
|
|
192
|
+
value: T;
|
|
193
|
+
records: CopyRewriteRecord[];
|
|
194
|
+
warnings: CopyWarning[];
|
|
195
|
+
};
|
|
196
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|