sb-mig 6.4.1-beta.1 → 6.4.1-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.
@@ -17,7 +17,13 @@ export const rewriteCopyReferences = ({ value, maps, schemas, }) => {
17
17
  const cloneJson = (value) => JSON.parse(JSON.stringify(value));
18
18
  const rewriteNode = (node, path, state) => {
19
19
  if (Array.isArray(node)) {
20
- node.forEach((item, index) => rewriteNode(item, `${path}[${index}]`, state));
20
+ node.forEach((item, index) => {
21
+ if (typeof item === "string") {
22
+ rewriteBareStoryUuid(node, index, item, `${path}[${index}]`, state);
23
+ return;
24
+ }
25
+ rewriteNode(item, `${path}[${index}]`, state);
26
+ });
21
27
  return;
22
28
  }
23
29
  if (!isRecord(node)) {
@@ -27,6 +33,7 @@ const rewriteNode = (node, path, state) => {
27
33
  rewriteStoryLinkObject(node, path, state);
28
34
  rewriteRichtextLinkObject(node, path, state);
29
35
  rewriteSchemaAwareOptions(node, path, state);
36
+ rewriteBareStoryUuidValues(node, path, state);
30
37
  if (node.type === "blok" &&
31
38
  isRecord(node.attrs) &&
32
39
  Array.isArray(node.attrs.body)) {
@@ -88,6 +95,19 @@ const rewriteStoryLinkObject = (node, path, state) => {
88
95
  node.id = targetId;
89
96
  }
90
97
  }
98
+ if (typeof node.id === "string") {
99
+ const targetUuid = state.maps.storyUuids.get(node.id);
100
+ if (targetUuid !== undefined) {
101
+ addRecord(state, {
102
+ type: "story",
103
+ path: `${path}.id`,
104
+ sourceValue: node.id,
105
+ targetValue: targetUuid,
106
+ field: "id",
107
+ });
108
+ node.id = targetUuid;
109
+ }
110
+ }
91
111
  if (typeof node.uuid === "string") {
92
112
  const targetUuid = state.maps.storyUuids.get(node.uuid);
93
113
  if (targetUuid !== undefined) {
@@ -117,33 +137,80 @@ const rewriteSchemaAwareOptions = (node, path, state) => {
117
137
  return;
118
138
  }
119
139
  for (const [fieldName, fieldValue] of Object.entries(node)) {
120
- if (RESERVED_CONTENT_KEYS.has(fieldName) ||
121
- !Array.isArray(fieldValue)) {
140
+ if (RESERVED_CONTENT_KEYS.has(fieldName)) {
122
141
  continue;
123
142
  }
124
143
  const fieldSchema = getFieldSchema(schema, fieldName);
125
- if (fieldSchema?.type !== "options" ||
126
- fieldSchema.source !== "internal_stories") {
144
+ if (fieldSchema?.source !== "internal_stories") {
127
145
  continue;
128
146
  }
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",
147
+ if (fieldSchema.type === "options" && Array.isArray(fieldValue)) {
148
+ fieldValue.forEach((item, index) => {
149
+ rewriteStoryOptionItem(fieldValue, index, item, `${path}.${fieldName}[${index}]`, state);
143
150
  });
144
- fieldValue[index] = targetId;
151
+ continue;
152
+ }
153
+ if (fieldSchema.type === "option") {
154
+ rewriteStoryOptionItem(node, fieldName, fieldValue, `${path}.${fieldName}`, state);
155
+ }
156
+ }
157
+ };
158
+ const rewriteStoryOptionItem = (container, key, item, path, state) => {
159
+ if (typeof item === "number") {
160
+ const targetId = state.maps.storyIds.get(item);
161
+ if (targetId === undefined) {
162
+ return;
163
+ }
164
+ addRecord(state, {
165
+ type: "story",
166
+ path,
167
+ sourceValue: item,
168
+ targetValue: targetId,
169
+ field: "id",
145
170
  });
171
+ container[key] = targetId;
172
+ return;
173
+ }
174
+ if (typeof item === "string") {
175
+ const targetUuid = state.maps.storyUuids.get(item);
176
+ if (targetUuid === undefined) {
177
+ return;
178
+ }
179
+ addRecord(state, {
180
+ type: "story",
181
+ path,
182
+ sourceValue: item,
183
+ targetValue: targetUuid,
184
+ field: "uuid",
185
+ });
186
+ container[key] = targetUuid;
187
+ }
188
+ };
189
+ // Safety net: any string value that is a key in storyUuids is a known
190
+ // copied-story source uuid (block _uids, space ids and story ids live in other
191
+ // namespaces), so rewriting exact matches is safe and covers custom plugin
192
+ // fields and shared-selector.shared_component without schema knowledge.
193
+ const rewriteBareStoryUuidValues = (node, path, state) => {
194
+ for (const [key, value] of Object.entries(node)) {
195
+ if (RESERVED_CONTENT_KEYS.has(key) || typeof value !== "string") {
196
+ continue;
197
+ }
198
+ rewriteBareStoryUuid(node, key, value, `${path}.${key}`, state);
199
+ }
200
+ };
201
+ const rewriteBareStoryUuid = (container, key, value, path, state) => {
202
+ const targetUuid = state.maps.storyUuids.get(value);
203
+ if (targetUuid === undefined) {
204
+ return;
146
205
  }
206
+ addRecord(state, {
207
+ type: "story",
208
+ path,
209
+ sourceValue: value,
210
+ targetValue: targetUuid,
211
+ field: "uuid",
212
+ });
213
+ container[key] = targetUuid;
147
214
  };
148
215
  const getFieldSchema = (schema, fieldName) => schema[fieldName] ?? schema[normalizeFieldName(fieldName)];
149
216
  const normalizeFieldName = (fieldName) => fieldName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
@@ -169,6 +169,9 @@ const scanField = ({ fieldValue, fieldSchema, path, state, }) => {
169
169
  case "options":
170
170
  scanOptionsField(fieldValue, fieldSchema, path, state);
171
171
  return;
172
+ case "option":
173
+ scanOptionField(fieldValue, fieldSchema, path, state);
174
+ return;
172
175
  case "bloks":
173
176
  scanBloksField(fieldValue, path, state);
174
177
  return;
@@ -225,14 +228,19 @@ const scanMultilinkField = (fieldValue, path, state) => {
225
228
  if (!isRecord(fieldValue) || fieldValue.linktype !== "story") {
226
229
  return;
227
230
  }
228
- const referencedStoryId = typeof fieldValue.id === "number" ? fieldValue.id : undefined;
229
- if (referencedStoryId === undefined) {
231
+ if (typeof fieldValue.id === "number") {
232
+ addStoryReference(state, {
233
+ path: `${path}.id`,
234
+ referencedStoryId: fieldValue.id,
235
+ });
230
236
  return;
231
237
  }
232
- addStoryReference(state, {
233
- path: `${path}.id`,
234
- referencedStoryId,
235
- });
238
+ if (typeof fieldValue.id === "string" && isUuidLike(fieldValue.id)) {
239
+ addStoryReference(state, {
240
+ path: `${path}.id`,
241
+ referencedStoryUuid: fieldValue.id,
242
+ });
243
+ }
236
244
  };
237
245
  const scanOptionsField = (fieldValue, fieldSchema, path, state) => {
238
246
  if (fieldSchema.source !== "internal_stories") {
@@ -247,9 +255,34 @@ const scanOptionsField = (fieldValue, fieldSchema, path, state) => {
247
255
  path: `${path}[${index}]`,
248
256
  referencedStoryId: value,
249
257
  });
258
+ return;
259
+ }
260
+ if (typeof value === "string" && isUuidLike(value)) {
261
+ addStoryReference(state, {
262
+ path: `${path}[${index}]`,
263
+ referencedStoryUuid: value,
264
+ });
250
265
  }
251
266
  });
252
267
  };
268
+ const scanOptionField = (fieldValue, fieldSchema, path, state) => {
269
+ if (fieldSchema.source !== "internal_stories") {
270
+ return;
271
+ }
272
+ if (typeof fieldValue === "number") {
273
+ addStoryReference(state, {
274
+ path,
275
+ referencedStoryId: fieldValue,
276
+ });
277
+ return;
278
+ }
279
+ if (typeof fieldValue === "string" && isUuidLike(fieldValue)) {
280
+ addStoryReference(state, {
281
+ path,
282
+ referencedStoryUuid: fieldValue,
283
+ });
284
+ }
285
+ };
253
286
  const scanBloksField = (fieldValue, path, state) => {
254
287
  if (!Array.isArray(fieldValue)) {
255
288
  state.warnings.push({
@@ -317,6 +350,8 @@ const addOpaqueField = (state, field) => {
317
350
  path: field.path,
318
351
  });
319
352
  };
353
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
354
+ const isUuidLike = (value) => UUID_PATTERN.test(value);
320
355
  const normalizeFieldName = (fieldName) => fieldName.replace(/__i18n__.*/, "");
321
356
  const getAssetNodeKey = (asset) => `${asset.sourceId}:${asset.sourceFilename}`;
322
357
  const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sb-mig",
3
- "version": "6.4.1-beta.1",
3
+ "version": "6.4.1-beta.2",
4
4
  "description": "CLI to rule the world. (and handle stuff related to Storyblok CMS)",
5
5
  "author": "Marcin Krawczyk <marckraw@icloud.com>",
6
6
  "license": "MIT",