@storyblok/migrations 0.1.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/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ The MIT License (MIT)
2
+ Copyright © 2025 Storyblok GmbH
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ <div align="center">
2
+
3
+ ![Storyblok ImagoType](https://raw.githubusercontent.com/storyblok/.github/refs/heads/main/profile/public/github-banner.png)
4
+
5
+ <h1 align="center">@storyblok/migrations</h1>
6
+ <p>
7
+ Pure utility helpers to migrate content into <a href="https://www.storyblok.com?utm_source=github.com&utm_medium=readme&utm_campaign=storyblok-migrations" target="_blank">Storyblok</a>.
8
+ </p>
9
+ <br />
10
+ </div>
11
+
12
+ <p align="center">
13
+ <a href="https://npmjs.com/package/@storyblok/migrations">
14
+ <img src="https://img.shields.io/npm/v/@storyblok/migrations/latest.svg?style=flat-square&color=8d60ff" alt="@storyblok/migrations" />
15
+ </a>
16
+ <a href="https://npmjs.com/package/@storyblok/migrations" rel="nofollow">
17
+ <img src="https://img.shields.io/npm/dt/@storyblok/migrations.svg?style=appveyor&color=8d60ff" alt="npm">
18
+ </a>
19
+ <a href="https://storyblok.com/join-discord">
20
+ <img src="https://img.shields.io/discord/700316478792138842?label=Join%20Our%20Discord%20Community&style=appveyor&logo=discord&color=8d60ff">
21
+ </a>
22
+ <a href="https://twitter.com/intent/follow?screen_name=storyblok">
23
+ <img src="https://img.shields.io/badge/Follow-%40storyblok-8d60ff?style=appveyor&logo=twitter" alt="Follow @Storyblok" />
24
+ </a><br/>
25
+ <a href="https://app.storyblok.com/#!/signup?utm_source=github.com&utm_medium=readme&utm_campaign=storyblok-migrations">
26
+ <img src="https://img.shields.io/badge/Try%20Storyblok-Free-8d60ff?style=appveyor&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAHqADAAQAAAABAAAAHgAAAADpiRU/AAACRElEQVRIDWNgGGmAEd3D3Js3LPrP8D8WXZwSPiMjw6qvPoHhyGYwIXNAbGpbCjbzP0MYuj0YFqMroBV/wCxmIeSju64eDNzMBJUxvP/9i2Hnq5cM1devMnz984eQsQwETeRhYWHgIcJiXqC6VHlFBjUeXgav40cIWkz1oLYXFmGwFBImaDFBHyObcOzdW4aSq5eRhRiE2dgYlpuYoYSKJi8vw3GgWnyAJIs/AuPu4scPGObd/fqVQZ+PHy7+6udPOBsXgySLDfn5GRYYmaKYJcXBgWLpsx8/GPa8foWiBhuHJIsl2DkYQqWksZkDFgP5PObcKYYff//iVAOTIDlx/QPqRMb/YSYBaWlOToZIaVkGZmAZSQiQ5OPtwHwacuo4iplMQEu6tXUZMhSUGDiYmBjylFQYvv/7x9B04xqKOnQOyT5GN+Df//8M59ASXKyMHLoyDD5JPtbj42OYrm+EYgg70JfuYuIoYmLs7AwMjIzA+uY/zjAnyWJpDk6GOFnCvrn86SOwmsNtKciVFAc1ileBHFDC67lzG10Yg0+SjzF0ownsf/OaofvOLYaDQJoQIGix94ljv1gIZI8Pv38zPvj2lQWYf3HGKbpDCFp85v07NnRN1OBTPY6JdRSGxcCw2k6sZuLVMZ5AV4s1TozPnGGFKbz+/PE7IJsHmC//MDMyhXBw8e6FyRFLv3Z0/IKuFqvFyIqAzd1PwBzJw8jAGPfVx38JshwlbIygxmYY43/GQmpais0ODDHuzevLMARHBcgIAQAbOJHZW0/EyQAAAABJRU5ErkJggg==" alt="Follow @Storyblok" />
27
+ </a>
28
+ </p>
29
+
30
+ ## Features
31
+
32
+ - **Content Converters**: Convert HTML and Markdown to Storyblok rich text format.
33
+ - **Link and Asset Helpers**: Transform URLs into Storyblok link and asset objects.
34
+ - **Local File Workflows**: Read and update locally pulled stories, assets, components, and datasources.
35
+ - **Reference Mapping**: Remap story and asset references when migrating between spaces.
36
+ - **Schema Cleanup**: Remove out-of-schema fields and rename datasource values.
37
+
38
+ ## Documentation
39
+
40
+ For complete documentation, please visit the [package reference](https://www.storyblok.com/docs/libraries/js/migrations).
41
+
42
+ ## Contributing
43
+
44
+ If you're interested in contributing, please read our [contributing docs](https://github.com/storyblok/.github/blob/main/contributing.md) before submitting a pull request.
45
+
46
+ ## Community
47
+
48
+ For help, discussion about best practices, or any other conversation that would benefit from being searchable:
49
+
50
+ - [Discuss Storyblok on Github Discussions](https://github.com/storyblok/monoblok/discussions)
51
+
52
+ For community support, chatting with other users, please visit:
53
+
54
+ - [Discuss Storyblok on Discord](https://storyblok.com/join-discord)
55
+
56
+ ## Support
57
+
58
+ For bugs or feature requests, please [submit an issue](https://github.com/storyblok/monoblok/issues/new/choose).
59
+
60
+ > [!IMPORTANT]
61
+ > Please search existing issues before submitting a new one. Issues without a minimal reproducible example will be closed. [Why reproductions are Required](https://antfu.me/posts/why-reproductions-are-required).
62
+
63
+ ### I can't share my company project code
64
+
65
+ We understand that you might not be able to share your company's project code. Please provide a minimal reproducible example that demonstrates the issue by using tools like [Stackblitz](https://stackblitz.com) or a link to a Github Repo lease make sure you include a README file with the instructions to build and run the project, important not to include any access token, password or personal information of any kind.
66
+
67
+ ### Feedback
68
+
69
+ If you have a question, please ask in the [Discuss Storyblok on Discord](https://storyblok.com/join-discord) channel.
70
+
71
+ ## License
72
+
73
+ [License](/LICENSE)
package/dist/index.cjs ADDED
@@ -0,0 +1,445 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let node_fs_promises = require("node:fs/promises");
3
+ let pathe = require("pathe");
4
+ let _storyblok_richtext_html_parser = require("@storyblok/richtext/html-parser");
5
+ let _storyblok_richtext_markdown_parser = require("@storyblok/richtext/markdown-parser");
6
+
7
+ //#region src/delete-out-of-schema-fields.ts
8
+ const SYSTEM_FIELDS = new Set([
9
+ "_uid",
10
+ "component",
11
+ "_editable"
12
+ ]);
13
+ function cleanBlok(blok, schemaDefinition, removedFields) {
14
+ const componentName = blok.component;
15
+ if (!componentName) return blok;
16
+ const schema = schemaDefinition[componentName];
17
+ if (!schema) return blok;
18
+ const validFieldSet = new Set(Object.keys(schema));
19
+ const result = {};
20
+ for (const [key, value] of Object.entries(blok)) {
21
+ if (SYSTEM_FIELDS.has(key)) {
22
+ result[key] = value;
23
+ continue;
24
+ }
25
+ const baseFieldName = key.replace(/__i18n__.*/, "");
26
+ if (!validFieldSet.has(baseFieldName)) {
27
+ removedFields.push({
28
+ component: componentName,
29
+ field: key
30
+ });
31
+ continue;
32
+ }
33
+ if (Array.isArray(value) && value.some((item) => item && typeof item === "object" && !Array.isArray(item) && item.component)) result[key] = value.map((item) => {
34
+ if (item && typeof item === "object" && !Array.isArray(item) && item.component) return cleanBlok(item, schemaDefinition, removedFields);
35
+ return item;
36
+ });
37
+ else result[key] = value;
38
+ }
39
+ return result;
40
+ }
41
+ function deleteOutOfSchemaFields(story, schemaDefinition) {
42
+ const removedFields = [];
43
+ if (!story.content) return {
44
+ story,
45
+ removedFields
46
+ };
47
+ const newContent = cleanBlok(story.content, schemaDefinition, removedFields);
48
+ return {
49
+ story: {
50
+ ...story,
51
+ content: newContent
52
+ },
53
+ removedFields
54
+ };
55
+ }
56
+
57
+ //#endregion
58
+ //#region src/local-utils.ts
59
+ async function readLocalJsonFiles(dir) {
60
+ let files;
61
+ try {
62
+ files = await (0, node_fs_promises.readdir)(dir);
63
+ } catch (error) {
64
+ if (error.code === "ENOENT") return [];
65
+ throw error;
66
+ }
67
+ const jsonFiles = files.filter((f) => (0, pathe.extname)(f) === ".json");
68
+ return await Promise.all(jsonFiles.map(async (file) => {
69
+ const filePath = (0, pathe.join)(dir, file);
70
+ const content = await (0, node_fs_promises.readFile)(filePath, "utf8");
71
+ try {
72
+ return JSON.parse(content);
73
+ } catch (error) {
74
+ throw new Error(`Failed to parse ${filePath}: ${error.message}`);
75
+ }
76
+ }));
77
+ }
78
+ async function writeLocalJsonFile(dir, filename, data) {
79
+ await (0, node_fs_promises.mkdir)(dir, { recursive: true });
80
+ await (0, node_fs_promises.writeFile)((0, pathe.join)(dir, filename), JSON.stringify(data, void 0, 2), "utf8");
81
+ }
82
+
83
+ //#endregion
84
+ //#region src/local-assets.ts
85
+ function getAssetFilename(asset) {
86
+ return `${(asset.short_filename || String(asset.id)).replace(/\.[^.]+$/, "")}_${asset.id}.json`;
87
+ }
88
+ async function getLocalAssets(dir) {
89
+ return readLocalJsonFiles(dir);
90
+ }
91
+ async function updateLocalAsset(dir, asset) {
92
+ await writeLocalJsonFile(dir, getAssetFilename(asset), asset);
93
+ }
94
+
95
+ //#endregion
96
+ //#region src/local-components.ts
97
+ function getComponentFilename(component) {
98
+ return `${component.name}.json`;
99
+ }
100
+ async function getLocalComponents(dir) {
101
+ return readLocalJsonFiles(dir);
102
+ }
103
+ async function updateLocalComponent(dir, component) {
104
+ await writeLocalJsonFile(dir, getComponentFilename(component), component);
105
+ }
106
+
107
+ //#endregion
108
+ //#region src/local-datasources.ts
109
+ function getDatasourceFilename(datasource) {
110
+ return `${datasource.slug}_${datasource.id}.json`;
111
+ }
112
+ async function getLocalDatasources(dir) {
113
+ return readLocalJsonFiles(dir);
114
+ }
115
+ async function updateLocalDatasource(dir, datasource) {
116
+ await writeLocalJsonFile(dir, getDatasourceFilename(datasource), datasource);
117
+ }
118
+
119
+ //#endregion
120
+ //#region src/local-stories.ts
121
+ function getStoryFilename(story) {
122
+ return `${story.slug}_${story.uuid}.json`;
123
+ }
124
+ async function getLocalStories(dir) {
125
+ return readLocalJsonFiles(dir);
126
+ }
127
+ async function updateLocalStory(dir, story) {
128
+ await writeLocalJsonFile(dir, getStoryFilename(story), story);
129
+ }
130
+
131
+ //#endregion
132
+ //#region src/map-refs.ts
133
+ function isRecord(value) {
134
+ return value !== null && typeof value === "object" && !Array.isArray(value);
135
+ }
136
+ function asRecord(value) {
137
+ return isRecord(value) ? value : {};
138
+ }
139
+ function asArray(value) {
140
+ return Array.isArray(value) ? value : [];
141
+ }
142
+ const traverseAndMapBySchema = (data, { schemas, maps, fieldRefMappers, processedFields, missingSchemas }) => {
143
+ if (!isRecord(data) || typeof data.component !== "string") return data ?? {};
144
+ const schema = schemas[data.component];
145
+ if (!schema) {
146
+ missingSchemas.add(data.component);
147
+ return data;
148
+ }
149
+ const dataNew = { ...data };
150
+ for (const [fieldName, fieldValue] of Object.entries(data)) {
151
+ const fieldSchema = schema[fieldName.replace(/__i18n__.*/, "")];
152
+ const fieldType = fieldSchema && typeof fieldSchema === "object" && "type" in fieldSchema && fieldSchema.type;
153
+ const fieldRefMapper = typeof fieldType === "string" && fieldRefMappers[fieldType];
154
+ if (fieldSchema) processedFields.add(fieldSchema);
155
+ if (fieldRefMapper) dataNew[fieldName] = fieldRefMapper(fieldValue, {
156
+ schema: fieldSchema,
157
+ schemas,
158
+ maps,
159
+ fieldRefMappers,
160
+ processedFields,
161
+ missingSchemas
162
+ });
163
+ }
164
+ return dataNew;
165
+ };
166
+ const traverseAndMapRichtextDoc = (data, { schemas, maps, fieldRefMappers, processedFields, missingSchemas }) => {
167
+ if (Array.isArray(data)) return data.map((item) => traverseAndMapRichtextDoc(item, {
168
+ schemas,
169
+ maps,
170
+ fieldRefMappers,
171
+ processedFields,
172
+ missingSchemas
173
+ }));
174
+ if (isRecord(data)) {
175
+ if (data.type === "link" && asRecord(data.attrs).linktype === "story") return {
176
+ ...data,
177
+ attrs: {
178
+ ...asRecord(data.attrs),
179
+ uuid: maps.stories?.get(asRecord(data.attrs).uuid) ?? asRecord(data.attrs).uuid
180
+ }
181
+ };
182
+ if (data.type === "blok") return {
183
+ ...data,
184
+ attrs: {
185
+ ...asRecord(data.attrs),
186
+ body: asArray(asRecord(data.attrs).body).map((d) => traverseAndMapBySchema(d, {
187
+ schemas,
188
+ maps,
189
+ fieldRefMappers,
190
+ processedFields,
191
+ missingSchemas
192
+ }))
193
+ }
194
+ };
195
+ const newData = {};
196
+ for (const [k, value] of Object.entries(data)) newData[k] = traverseAndMapRichtextDoc(value, {
197
+ schemas,
198
+ maps,
199
+ fieldRefMappers,
200
+ processedFields,
201
+ missingSchemas
202
+ });
203
+ return newData;
204
+ }
205
+ return data;
206
+ };
207
+ const richtextFieldRefMapper = (data, { schemas, maps, fieldRefMappers, processedFields, missingSchemas }) => traverseAndMapRichtextDoc(data, {
208
+ schemas,
209
+ maps,
210
+ fieldRefMappers,
211
+ processedFields,
212
+ missingSchemas
213
+ });
214
+ const multilinkFieldRefMapper = (data, { maps }) => {
215
+ if (!isRecord(data) || data.linktype !== "story") return data;
216
+ return {
217
+ ...data,
218
+ id: maps.stories?.get(data.id) ?? data.id
219
+ };
220
+ };
221
+ const bloksFieldRefMapper = (data, { schemas, maps, fieldRefMappers, processedFields, missingSchemas }) => {
222
+ if (!Array.isArray(data)) throw new TypeError(`Invalid bloks field: expected an array, but received ${JSON.stringify(data)}.`);
223
+ return data.map((d) => traverseAndMapBySchema(d, {
224
+ schemas,
225
+ maps,
226
+ fieldRefMappers,
227
+ processedFields,
228
+ missingSchemas
229
+ }));
230
+ };
231
+ const assetFieldRefMapper = (data, { maps }) => {
232
+ if (!isRecord(data)) return data;
233
+ const newId = typeof data.id === "number" ? maps.assets?.get(data.id) : void 0;
234
+ return newId === void 0 ? data : {
235
+ ...data,
236
+ id: newId
237
+ };
238
+ };
239
+ const multiassetFieldRefMapper = (data, options) => {
240
+ if (!Array.isArray(data)) throw new TypeError(`Invalid multiasset field: expected an array, but received ${JSON.stringify(data)}.`);
241
+ return data.map((d) => assetFieldRefMapper(d, options));
242
+ };
243
+ const optionsFieldRefMapper = (data, { schema, maps }) => {
244
+ if (!Array.isArray(data)) return data;
245
+ const sourceMap = {
246
+ internal_stories: maps.stories,
247
+ internal_users: maps.users,
248
+ internal_tags: maps.tags,
249
+ internal_datasources: maps.datasources
250
+ }[schema.source ?? ""];
251
+ if (!sourceMap) return data;
252
+ return data.map((d) => sourceMap.get(d) ?? d);
253
+ };
254
+ const fieldRefMappers = {
255
+ asset: assetFieldRefMapper,
256
+ bloks: bloksFieldRefMapper,
257
+ multiasset: multiassetFieldRefMapper,
258
+ multilink: multilinkFieldRefMapper,
259
+ options: optionsFieldRefMapper,
260
+ richtext: richtextFieldRefMapper
261
+ };
262
+ function mapRefs(story, options) {
263
+ const { schemas, maps } = options;
264
+ const processedFields = /* @__PURE__ */ new Set();
265
+ const missingSchemas = /* @__PURE__ */ new Set();
266
+ const alternates = story.alternates ? story.alternates.map((alternate) => {
267
+ const mappedAlternate = asRecord(alternate);
268
+ return {
269
+ ...mappedAlternate,
270
+ id: maps.stories?.get(mappedAlternate.id) ?? mappedAlternate.id,
271
+ parent_id: maps.stories?.get(mappedAlternate.parent_id) ?? mappedAlternate.parent_id
272
+ };
273
+ }) : story.alternates;
274
+ const parentId = maps.stories?.get(story.parent_id) ?? story.parent_id;
275
+ const mappedContentRaw = story.content?.component ? traverseAndMapBySchema(story.content, {
276
+ schemas,
277
+ maps,
278
+ fieldRefMappers,
279
+ processedFields,
280
+ missingSchemas
281
+ }) : story.content;
282
+ return {
283
+ mappedStory: {
284
+ ...story,
285
+ content: mappedContentRaw,
286
+ id: Number(maps.stories?.get(story.id) ?? story.id),
287
+ uuid: String(maps.stories?.get(story.uuid) ?? story.uuid),
288
+ parent_id: parentId != null ? Number(parentId) : null,
289
+ alternates
290
+ },
291
+ processedFields,
292
+ missingSchemas
293
+ };
294
+ }
295
+
296
+ //#endregion
297
+ //#region src/rename-datasource-value.ts
298
+ function traverseRichtext(data, componentsToUpdate, oldValue, newValue, changes, path) {
299
+ if (Array.isArray(data)) return data.map((item, index) => traverseRichtext(item, componentsToUpdate, oldValue, newValue, changes, `${path}[${index}]`));
300
+ if (data && typeof data === "object" && !Array.isArray(data)) {
301
+ const record = data;
302
+ if (record.type === "blok" && record.attrs && typeof record.attrs === "object") {
303
+ const attrs = record.attrs;
304
+ if (Array.isArray(attrs.body)) return {
305
+ ...record,
306
+ attrs: {
307
+ ...attrs,
308
+ body: attrs.body.map((item, index) => {
309
+ if (item && typeof item === "object" && !Array.isArray(item)) return traverseObject(item, componentsToUpdate, oldValue, newValue, changes, `${path}.attrs.body[${index}]`);
310
+ return item;
311
+ })
312
+ }
313
+ };
314
+ }
315
+ const result = {};
316
+ for (const [key, value] of Object.entries(record)) result[key] = traverseRichtext(value, componentsToUpdate, oldValue, newValue, changes, `${path}.${key}`);
317
+ return result;
318
+ }
319
+ return data;
320
+ }
321
+ function traverseObject(obj, componentsToUpdate, oldValue, newValue, changes, path) {
322
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) return obj;
323
+ const result = { ...obj };
324
+ const componentName = obj.component;
325
+ if (componentName) for (const { field, name } of componentsToUpdate) {
326
+ if (name !== componentName) continue;
327
+ const fieldValue = result[field];
328
+ if (typeof fieldValue === "string" && fieldValue === oldValue) {
329
+ result[field] = newValue;
330
+ changes.push({
331
+ component: componentName,
332
+ field,
333
+ path: `${path}.${field}`
334
+ });
335
+ } else if (Array.isArray(fieldValue)) result[field] = fieldValue.map((item) => {
336
+ if (item === oldValue) {
337
+ changes.push({
338
+ component: componentName,
339
+ field,
340
+ path: `${path}.${field}[]`
341
+ });
342
+ return newValue;
343
+ }
344
+ return item;
345
+ });
346
+ }
347
+ for (const [key, value] of Object.entries(result)) if (Array.isArray(value)) result[key] = value.map((item, index) => {
348
+ if (item && typeof item === "object" && !Array.isArray(item)) return traverseObject(item, componentsToUpdate, oldValue, newValue, changes, `${path}.${key}[${index}]`);
349
+ return item;
350
+ });
351
+ else if (value && typeof value === "object" && !Array.isArray(value) && value.type === "doc" && Array.isArray(value.content)) result[key] = traverseRichtext(value, componentsToUpdate, oldValue, newValue, changes, `${path}.${key}`);
352
+ return result;
353
+ }
354
+ function renameDataSourceValue(story, componentsToUpdate, oldValue, newValue) {
355
+ const changes = [];
356
+ const newContent = traverseObject(story.content, componentsToUpdate, oldValue, newValue, changes, "content");
357
+ return {
358
+ story: {
359
+ ...story,
360
+ content: newContent
361
+ },
362
+ changes
363
+ };
364
+ }
365
+
366
+ //#endregion
367
+ //#region src/url-to-asset.ts
368
+ function urlToAsset(url, options) {
369
+ return {
370
+ fieldtype: "asset",
371
+ id: 0,
372
+ filename: url,
373
+ src: url,
374
+ name: url.split("/").at(-1) || url,
375
+ alt: null,
376
+ title: null,
377
+ copyright: null,
378
+ focus: null,
379
+ meta_data: {},
380
+ source: null,
381
+ is_external_url: true,
382
+ is_private: false,
383
+ updated_at: "",
384
+ width: null,
385
+ height: null,
386
+ aspect_ratio: null,
387
+ public_id: null,
388
+ content_type: "",
389
+ ...options
390
+ };
391
+ }
392
+
393
+ //#endregion
394
+ //#region src/url-to-link.ts
395
+ function urlToLink(url, options) {
396
+ if (url.startsWith("mailto:")) return {
397
+ fieldtype: "multilink",
398
+ id: "",
399
+ url: "",
400
+ cached_url: "",
401
+ linktype: "email",
402
+ email: url.slice(7),
403
+ ...options
404
+ };
405
+ const hashIndex = url.indexOf("#");
406
+ const anchor = hashIndex === -1 ? void 0 : url.slice(hashIndex + 1);
407
+ const cleanUrl = hashIndex === -1 ? url : url.slice(0, hashIndex);
408
+ return {
409
+ fieldtype: "multilink",
410
+ id: "",
411
+ url: cleanUrl,
412
+ cached_url: cleanUrl,
413
+ linktype: "url",
414
+ ...anchor ? { anchor } : {},
415
+ ...options
416
+ };
417
+ }
418
+
419
+ //#endregion
420
+ exports.deleteOutOfSchemaFields = deleteOutOfSchemaFields;
421
+ exports.getLocalAssets = getLocalAssets;
422
+ exports.getLocalComponents = getLocalComponents;
423
+ exports.getLocalDatasources = getLocalDatasources;
424
+ exports.getLocalStories = getLocalStories;
425
+ Object.defineProperty(exports, 'htmlToStoryblokRichtext', {
426
+ enumerable: true,
427
+ get: function () {
428
+ return _storyblok_richtext_html_parser.htmlToStoryblokRichtext;
429
+ }
430
+ });
431
+ exports.mapRefs = mapRefs;
432
+ Object.defineProperty(exports, 'markdownToStoryblokRichtext', {
433
+ enumerable: true,
434
+ get: function () {
435
+ return _storyblok_richtext_markdown_parser.markdownToStoryblokRichtext;
436
+ }
437
+ });
438
+ exports.renameDataSourceValue = renameDataSourceValue;
439
+ exports.updateLocalAsset = updateLocalAsset;
440
+ exports.updateLocalComponent = updateLocalComponent;
441
+ exports.updateLocalDatasource = updateLocalDatasource;
442
+ exports.updateLocalStory = updateLocalStory;
443
+ exports.urlToAsset = urlToAsset;
444
+ exports.urlToLink = urlToLink;
445
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/delete-out-of-schema-fields.ts","../src/local-utils.ts","../src/local-assets.ts","../src/local-components.ts","../src/local-datasources.ts","../src/local-stories.ts","../src/map-refs.ts","../src/rename-datasource-value.ts","../src/url-to-asset.ts","../src/url-to-link.ts"],"sourcesContent":["import type { Story } from '@storyblok/management-api-client/resources/stories';\n\nimport type { ComponentSchemas } from './map-refs';\n\nconst SYSTEM_FIELDS = new Set(['_uid', 'component', '_editable']);\n\ninterface RemovedField {\n component: string;\n field: string;\n}\n\nfunction cleanBlok(\n blok: Record<string, unknown>,\n schemaDefinition: ComponentSchemas,\n removedFields: RemovedField[],\n): Record<string, unknown> {\n const componentName = blok.component as string | undefined;\n if (!componentName) {\n return blok;\n }\n\n const schema = schemaDefinition[componentName];\n if (!schema) {\n // Unknown component — leave untouched\n return blok;\n }\n\n const validFieldSet = new Set(Object.keys(schema));\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(blok)) {\n // Always keep system fields\n if (SYSTEM_FIELDS.has(key)) {\n result[key] = value;\n continue;\n }\n\n // Strip i18n suffix to get base field name\n const baseFieldName = key.replace(/__i18n__.*/, '');\n\n // Field not in schema — remove it\n if (!validFieldSet.has(baseFieldName)) {\n removedFields.push({ component: componentName, field: key });\n continue;\n }\n\n // Check if this is an array of bloks (objects with component property)\n const isBlokArray\n = Array.isArray(value)\n && value.some(\n item =>\n item\n && typeof item === 'object'\n && !Array.isArray(item)\n && (item as Record<string, unknown>).component,\n );\n\n if (isBlokArray) {\n // Traverse blok arrays and process nested bloks recursively\n result[key] = (value as unknown[]).map((item) => {\n if (\n item\n && typeof item === 'object'\n && !Array.isArray(item)\n && (item as Record<string, unknown>).component\n ) {\n return cleanBlok(\n item as Record<string, unknown>,\n schemaDefinition,\n removedFields,\n );\n }\n return item;\n });\n }\n else {\n // Valid field — keep it\n result[key] = value;\n }\n }\n\n return result;\n}\n\nexport function deleteOutOfSchemaFields(\n story: Story,\n schemaDefinition: ComponentSchemas,\n): { story: Story; removedFields: RemovedField[] } {\n const removedFields: RemovedField[] = [];\n\n if (!story.content) {\n return { story, removedFields };\n }\n\n const newContent = cleanBlok(\n story.content as Record<string, unknown>,\n schemaDefinition,\n removedFields,\n );\n\n return {\n // cleanBlok returns Record<string,unknown>; runtime shape satisfies Blok\n story: { ...story, content: newContent as unknown as Story['content'] },\n removedFields,\n };\n}\n","import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport { extname, join } from 'pathe';\n\nexport async function readLocalJsonFiles<T>(dir: string): Promise<T[]> {\n let files: string[];\n try {\n files = await readdir(dir);\n }\n catch (error: unknown) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return [];\n }\n throw error;\n }\n\n const jsonFiles = files.filter(f => extname(f) === '.json');\n\n const items = await Promise.all(\n jsonFiles.map(async (file) => {\n const filePath = join(dir, file);\n const content = await readFile(filePath, 'utf8');\n try {\n return JSON.parse(content) as T;\n }\n catch (error: unknown) {\n throw new Error(\n `Failed to parse ${filePath}: ${(error as Error).message}`,\n );\n }\n }),\n );\n\n return items;\n}\n\nexport async function writeLocalJsonFile(\n dir: string,\n filename: string,\n data: unknown,\n): Promise<void> {\n await mkdir(dir, { recursive: true });\n const filePath = join(dir, filename);\n await writeFile(filePath, JSON.stringify(data, undefined, 2), 'utf8');\n}\n","import type { Asset } from '@storyblok/management-api-client/resources/assets';\n\nimport { readLocalJsonFiles, writeLocalJsonFile } from './local-utils';\n\nfunction getAssetFilename(asset: Pick<Asset, 'id' | 'short_filename'>): string {\n const name = (asset.short_filename || String(asset.id)).replace(\n /\\.[^.]+$/,\n '',\n ); // strip extension\n return `${name}_${asset.id}.json`;\n}\n\nexport async function getLocalAssets(dir: string): Promise<Asset[]> {\n return readLocalJsonFiles<Asset>(dir);\n}\n\nexport async function updateLocalAsset(\n dir: string,\n asset: Asset,\n): Promise<void> {\n await writeLocalJsonFile(dir, getAssetFilename(asset), asset);\n}\n","import type { Component } from '@storyblok/management-api-client/resources/components';\n\nimport { readLocalJsonFiles, writeLocalJsonFile } from './local-utils';\n\nfunction getComponentFilename(component: Pick<Component, 'name'>): string {\n return `${component.name}.json`;\n}\n\nexport async function getLocalComponents(dir: string): Promise<Component[]> {\n return readLocalJsonFiles<Component>(dir);\n}\n\nexport async function updateLocalComponent(\n dir: string,\n component: Component,\n): Promise<void> {\n await writeLocalJsonFile(dir, getComponentFilename(component), component);\n}\n","import type { Datasource } from '@storyblok/management-api-client/resources/datasources';\n\nimport { readLocalJsonFiles, writeLocalJsonFile } from './local-utils';\n\nfunction getDatasourceFilename(\n datasource: Pick<Datasource, 'slug' | 'id'>,\n): string {\n return `${datasource.slug}_${datasource.id}.json`;\n}\n\nexport async function getLocalDatasources(dir: string): Promise<Datasource[]> {\n return readLocalJsonFiles<Datasource>(dir);\n}\n\nexport async function updateLocalDatasource(\n dir: string,\n datasource: Datasource,\n): Promise<void> {\n await writeLocalJsonFile(dir, getDatasourceFilename(datasource), datasource);\n}\n\nexport type { Datasource };\n","import type { Story } from '@storyblok/management-api-client/resources/stories';\n\nimport { readLocalJsonFiles, writeLocalJsonFile } from './local-utils';\n\nfunction getStoryFilename(story: Pick<Story, 'slug' | 'uuid'>): string {\n return `${story.slug}_${story.uuid}.json`;\n}\n\nexport async function getLocalStories(dir: string): Promise<Story[]> {\n return readLocalJsonFiles<Story>(dir);\n}\n\nexport async function updateLocalStory(\n dir: string,\n story: Story,\n): Promise<void> {\n await writeLocalJsonFile(dir, getStoryFilename(story), story);\n}\n","import type { Component } from '@storyblok/management-api-client/resources/components';\nimport type { Story } from '@storyblok/management-api-client/resources/stories';\n\nexport interface RefMaps {\n assets?: Map<unknown, string | number>;\n stories?: Map<unknown, string | number>;\n users?: Map<unknown, string | number>;\n tags?: Map<unknown, string | number>;\n datasources?: Map<unknown, string | number>;\n}\n\nexport type ComponentSchemas = Record<\n string,\n Record<string, { type: string; source?: string }>\n>;\n\nexport interface MapRefsOptions {\n schemas: ComponentSchemas;\n maps: RefMaps;\n}\n\ntype ProcessedFields = Set<Component['schema']>;\ntype MissingSchemas = Set<Component['name']>;\ntype UnknownRecord = Record<string, unknown>;\n\ntype RefMapper = (\n data: unknown,\n options: {\n schema: Component['schema'];\n schemas: ComponentSchemas;\n maps: RefMaps;\n fieldRefMappers: FieldRefMappers;\n processedFields: ProcessedFields;\n missingSchemas: MissingSchemas;\n },\n) => unknown;\n\ntype FieldRefMappers = Record<string, RefMapper>;\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction asRecord(value: unknown): UnknownRecord {\n return isRecord(value) ? value : {};\n}\n\nfunction asArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nconst traverseAndMapBySchema = (\n data: unknown,\n {\n schemas,\n maps,\n fieldRefMappers,\n processedFields,\n missingSchemas,\n }: {\n schemas: ComponentSchemas;\n maps: RefMaps;\n fieldRefMappers: FieldRefMappers;\n processedFields: ProcessedFields;\n missingSchemas: MissingSchemas;\n },\n): unknown => {\n if (!isRecord(data) || typeof data.component !== 'string') {\n return data ?? {};\n }\n\n const schema = schemas[data.component];\n if (!schema) {\n missingSchemas.add(data.component);\n return data;\n }\n\n const dataNew: UnknownRecord = { ...data };\n\n for (const [fieldName, fieldValue] of Object.entries(data)) {\n const fieldSchema = schema[\n fieldName.replace(/__i18n__.*/, '')\n ] as Component['schema'];\n const fieldType\n = fieldSchema\n && typeof fieldSchema === 'object'\n && 'type' in fieldSchema\n && fieldSchema.type;\n const fieldRefMapper\n = typeof fieldType === 'string' && fieldRefMappers[fieldType];\n\n if (fieldSchema) {\n processedFields.add(fieldSchema);\n }\n\n if (fieldRefMapper) {\n dataNew[fieldName] = fieldRefMapper(fieldValue, {\n schema: fieldSchema,\n schemas,\n maps,\n fieldRefMappers,\n processedFields,\n missingSchemas,\n });\n }\n }\n\n return dataNew;\n};\n\nconst traverseAndMapRichtextDoc = (\n data: unknown,\n {\n schemas,\n maps,\n fieldRefMappers,\n processedFields,\n missingSchemas,\n }: {\n schemas: ComponentSchemas;\n maps: RefMaps;\n fieldRefMappers: FieldRefMappers;\n processedFields: ProcessedFields;\n missingSchemas: MissingSchemas;\n },\n): unknown => {\n if (Array.isArray(data)) {\n return data.map(item =>\n traverseAndMapRichtextDoc(item, {\n schemas,\n maps,\n fieldRefMappers,\n processedFields,\n missingSchemas,\n }),\n );\n }\n\n if (isRecord(data)) {\n if (data.type === 'link' && asRecord(data.attrs).linktype === 'story') {\n return {\n ...data,\n attrs: {\n ...asRecord(data.attrs),\n uuid:\n maps.stories?.get(asRecord(data.attrs).uuid)\n ?? asRecord(data.attrs).uuid,\n },\n };\n }\n\n if (data.type === 'blok') {\n return {\n ...data,\n attrs: {\n ...asRecord(data.attrs),\n body: asArray(asRecord(data.attrs).body).map(d =>\n traverseAndMapBySchema(d, {\n schemas,\n maps,\n fieldRefMappers,\n processedFields,\n missingSchemas,\n }),\n ),\n },\n };\n }\n\n const newData: UnknownRecord = {};\n for (const [k, value] of Object.entries(data)) {\n newData[k] = traverseAndMapRichtextDoc(value, {\n schemas,\n maps,\n fieldRefMappers,\n processedFields,\n missingSchemas,\n });\n }\n\n return newData;\n }\n\n return data;\n};\n\nconst richtextFieldRefMapper: RefMapper = (\n data,\n { schemas, maps, fieldRefMappers, processedFields, missingSchemas },\n) =>\n traverseAndMapRichtextDoc(data, {\n schemas,\n maps,\n fieldRefMappers,\n processedFields,\n missingSchemas,\n });\n\nconst multilinkFieldRefMapper: RefMapper = (data, { maps }) => {\n if (!isRecord(data) || data.linktype !== 'story') {\n return data;\n }\n\n return {\n ...data,\n id: maps.stories?.get(data.id) ?? data.id,\n };\n};\n\nconst bloksFieldRefMapper: RefMapper = (\n data,\n { schemas, maps, fieldRefMappers, processedFields, missingSchemas },\n) => {\n if (!Array.isArray(data)) {\n throw new TypeError(\n `Invalid bloks field: expected an array, but received ${JSON.stringify(data)}.`,\n );\n }\n\n return data.map(d =>\n traverseAndMapBySchema(d, {\n schemas,\n maps,\n fieldRefMappers,\n processedFields,\n missingSchemas,\n }),\n );\n};\n\nconst assetFieldRefMapper: RefMapper = (data, { maps }) => {\n if (!isRecord(data)) {\n return data;\n }\n\n const newId\n = typeof data.id === 'number' ? maps.assets?.get(data.id) : undefined;\n return newId === undefined ? data : { ...data, id: newId };\n};\n\nconst multiassetFieldRefMapper: RefMapper = (data, options) => {\n if (!Array.isArray(data)) {\n throw new TypeError(\n `Invalid multiasset field: expected an array, but received ${JSON.stringify(data)}.`,\n );\n }\n\n return data.map(d => assetFieldRefMapper(d, options));\n};\n\nconst optionsFieldRefMapper: RefMapper = (data, { schema, maps }) => {\n if (!Array.isArray(data)) {\n return data;\n }\n\n const sourceMapBySchema: Record<\n string,\n Map<unknown, string | number> | undefined\n > = {\n internal_stories: maps.stories,\n internal_users: maps.users,\n internal_tags: maps.tags,\n internal_datasources: maps.datasources,\n };\n\n const sourceMap\n = sourceMapBySchema[(schema as { source?: string }).source ?? ''];\n if (!sourceMap) {\n return data;\n }\n\n return data.map(d => sourceMap.get(d) ?? d);\n};\n\nconst fieldRefMappers = {\n asset: assetFieldRefMapper,\n bloks: bloksFieldRefMapper,\n multiasset: multiassetFieldRefMapper,\n multilink: multilinkFieldRefMapper,\n options: optionsFieldRefMapper,\n richtext: richtextFieldRefMapper,\n} as const;\n\nexport function mapRefs(\n story: Story,\n options: MapRefsOptions,\n): {\n mappedStory: Story;\n processedFields: ProcessedFields;\n missingSchemas: MissingSchemas;\n } {\n const { schemas, maps } = options;\n const processedFields: ProcessedFields = new Set();\n const missingSchemas: MissingSchemas = new Set();\n\n const alternatesRaw = story.alternates\n ? (story.alternates as Required<Story>['alternates']).map((alternate) => {\n const mappedAlternate = asRecord(alternate);\n return {\n ...mappedAlternate,\n id: maps.stories?.get(mappedAlternate.id) ?? mappedAlternate.id,\n parent_id:\n maps.stories?.get(mappedAlternate.parent_id)\n ?? mappedAlternate.parent_id,\n };\n })\n : story.alternates;\n // mapped ids may be string|number at runtime but shape is compatible\n const alternates = alternatesRaw as Story['alternates'];\n\n const parentId = maps.stories?.get(story.parent_id) ?? story.parent_id;\n const mappedContentRaw = story.content?.component\n ? traverseAndMapBySchema(story.content, {\n schemas,\n maps,\n fieldRefMappers,\n processedFields,\n missingSchemas,\n })\n : story.content;\n\n const mappedStory = {\n ...story,\n // traverseAndMapBySchema returns unknown; runtime shape satisfies Blok\n content: mappedContentRaw as unknown as Story['content'],\n id: Number(maps.stories?.get(story.id) ?? story.id),\n uuid: String(maps.stories?.get(story.uuid) ?? story.uuid),\n parent_id: parentId != null ? Number(parentId) : null,\n alternates,\n } satisfies Story;\n\n return {\n mappedStory,\n processedFields,\n missingSchemas,\n };\n}\n","import type { Story } from '@storyblok/management-api-client/resources/stories';\n\ninterface ComponentToUpdate {\n field: string;\n name: string;\n}\n\ninterface Change {\n component: string;\n field: string;\n path: string;\n}\n\nfunction traverseRichtext(\n data: unknown,\n componentsToUpdate: ComponentToUpdate[],\n oldValue: string,\n newValue: string,\n changes: Change[],\n path: string,\n): unknown {\n if (Array.isArray(data)) {\n return data.map((item, index) =>\n traverseRichtext(item, componentsToUpdate, oldValue, newValue, changes, `${path}[${index}]`),\n );\n }\n\n if (data && typeof data === 'object' && !Array.isArray(data)) {\n const record = data as Record<string, unknown>;\n\n if (record.type === 'blok' && record.attrs && typeof record.attrs === 'object') {\n const attrs = record.attrs as Record<string, unknown>;\n if (Array.isArray(attrs.body)) {\n return {\n ...record,\n attrs: {\n ...attrs,\n body: attrs.body.map((item: unknown, index: number) => {\n if (item && typeof item === 'object' && !Array.isArray(item)) {\n return traverseObject(\n item as Record<string, unknown>,\n componentsToUpdate,\n oldValue,\n newValue,\n changes,\n `${path}.attrs.body[${index}]`,\n );\n }\n return item;\n }),\n },\n };\n }\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(record)) {\n result[key] = traverseRichtext(value, componentsToUpdate, oldValue, newValue, changes, `${path}.${key}`);\n }\n return result;\n }\n\n return data;\n}\n\nfunction traverseObject(\n obj: Record<string, unknown>,\n componentsToUpdate: ComponentToUpdate[],\n oldValue: string,\n newValue: string,\n changes: Change[],\n path: string,\n): Record<string, unknown> {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n return obj;\n }\n\n const result = { ...obj };\n const componentName = obj.component as string | undefined;\n\n if (componentName) {\n for (const { field, name } of componentsToUpdate) {\n if (name !== componentName) {\n continue;\n }\n\n const fieldValue = result[field];\n\n if (typeof fieldValue === 'string' && fieldValue === oldValue) {\n result[field] = newValue;\n changes.push({\n component: componentName,\n field,\n path: `${path}.${field}`,\n });\n }\n else if (Array.isArray(fieldValue)) {\n const newArray = fieldValue.map((item) => {\n if (item === oldValue) {\n changes.push({\n component: componentName,\n field,\n path: `${path}.${field}[]`,\n });\n return newValue;\n }\n return item;\n });\n result[field] = newArray;\n }\n }\n }\n\n // Traverse nested arrays (bloks fields) and richtext fields\n for (const [key, value] of Object.entries(result)) {\n if (Array.isArray(value)) {\n result[key] = value.map((item, index) => {\n if (item && typeof item === 'object' && !Array.isArray(item)) {\n return traverseObject(\n item as Record<string, unknown>,\n componentsToUpdate,\n oldValue,\n newValue,\n changes,\n `${path}.${key}[${index}]`,\n );\n }\n return item;\n });\n }\n else if (\n value\n && typeof value === 'object'\n && !Array.isArray(value)\n && (value as Record<string, unknown>).type === 'doc'\n && Array.isArray((value as Record<string, unknown>).content)\n ) {\n result[key] = traverseRichtext(value, componentsToUpdate, oldValue, newValue, changes, `${path}.${key}`);\n }\n }\n\n return result;\n}\n\nexport function renameDataSourceValue(\n story: Story,\n componentsToUpdate: ComponentToUpdate[],\n oldValue: string,\n newValue: string,\n): { story: Story; changes: Change[] } {\n const changes: Change[] = [];\n\n const newContent = traverseObject(\n story.content as Record<string, unknown>,\n componentsToUpdate,\n oldValue,\n newValue,\n changes,\n 'content',\n );\n\n return {\n // traverseObject returns Record<string,unknown>; runtime shape satisfies Blok\n story: { ...story, content: newContent as unknown as Story['content'] },\n changes,\n };\n}\n","import type { StoryblokAsset } from './types';\n\nexport interface UrlToAssetOptions {\n alt?: string | null;\n title?: string | null;\n copyright?: string | null;\n focus?: string | null;\n}\n\nexport function urlToAsset(\n url: string,\n options?: UrlToAssetOptions,\n): StoryblokAsset {\n // Derive name from last path segment\n const pathSegments = url.split('/');\n const name = pathSegments.at(-1) || url;\n\n return {\n fieldtype: 'asset',\n id: 0,\n filename: url,\n src: url,\n name,\n alt: null,\n title: null,\n copyright: null,\n focus: null,\n meta_data: {},\n source: null,\n is_external_url: true,\n is_private: false,\n updated_at: '',\n width: null,\n height: null,\n aspect_ratio: null,\n public_id: null,\n content_type: '',\n ...options,\n };\n}\n","import type { StoryblokMultilink } from './types';\n\nexport interface UrlToLinkOptions {\n target?: '_blank' | '_self';\n title?: string;\n rel?: string;\n anchor?: string;\n}\n\nexport function urlToLink(\n url: string,\n options?: UrlToLinkOptions,\n): StoryblokMultilink {\n // Detect mailto: links\n if (url.startsWith('mailto:')) {\n return {\n fieldtype: 'multilink',\n id: '',\n url: '',\n cached_url: '',\n linktype: 'email',\n email: url.slice('mailto:'.length),\n ...options,\n };\n }\n\n // Extract anchor from fragment\n const hashIndex = url.indexOf('#');\n const anchor = hashIndex === -1 ? undefined : url.slice(hashIndex + 1);\n const cleanUrl = hashIndex === -1 ? url : url.slice(0, hashIndex);\n\n return {\n fieldtype: 'multilink',\n id: '',\n url: cleanUrl,\n cached_url: cleanUrl,\n linktype: 'url',\n ...(anchor ? { anchor } : {}),\n ...options,\n };\n}\n"],"mappings":";;;;;;;AAIA,MAAM,gBAAgB,IAAI,IAAI;CAAC;CAAQ;CAAa;CAAY,CAAC;AAOjE,SAAS,UACP,MACA,kBACA,eACyB;CACzB,MAAM,gBAAgB,KAAK;AAC3B,KAAI,CAAC,cACH,QAAO;CAGT,MAAM,SAAS,iBAAiB;AAChC,KAAI,CAAC,OAEH,QAAO;CAGT,MAAM,gBAAgB,IAAI,IAAI,OAAO,KAAK,OAAO,CAAC;CAClD,MAAM,SAAkC,EAAE;AAE1C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,EAAE;AAE/C,MAAI,cAAc,IAAI,IAAI,EAAE;AAC1B,UAAO,OAAO;AACd;;EAIF,MAAM,gBAAgB,IAAI,QAAQ,cAAc,GAAG;AAGnD,MAAI,CAAC,cAAc,IAAI,cAAc,EAAE;AACrC,iBAAc,KAAK;IAAE,WAAW;IAAe,OAAO;IAAK,CAAC;AAC5D;;AAcF,MATI,MAAM,QAAQ,MAAM,IACjB,MAAM,MACP,SACE,QACG,OAAO,SAAS,YAChB,CAAC,MAAM,QAAQ,KAAK,IACnB,KAAiC,UACxC,CAIH,QAAO,OAAQ,MAAoB,KAAK,SAAS;AAC/C,OACE,QACG,OAAO,SAAS,YAChB,CAAC,MAAM,QAAQ,KAAK,IACnB,KAAiC,UAErC,QAAO,UACL,MACA,kBACA,cACD;AAEH,UAAO;IACP;MAIF,QAAO,OAAO;;AAIlB,QAAO;;AAGT,SAAgB,wBACd,OACA,kBACiD;CACjD,MAAM,gBAAgC,EAAE;AAExC,KAAI,CAAC,MAAM,QACT,QAAO;EAAE;EAAO;EAAe;CAGjC,MAAM,aAAa,UACjB,MAAM,SACN,kBACA,cACD;AAED,QAAO;EAEL,OAAO;GAAE,GAAG;GAAO,SAAS;GAA2C;EACvE;EACD;;;;;ACrGH,eAAsB,mBAAsB,KAA2B;CACrE,IAAI;AACJ,KAAI;AACF,UAAQ,oCAAc,IAAI;UAErB,OAAgB;AACrB,MAAK,MAAgC,SAAS,SAC5C,QAAO,EAAE;AAEX,QAAM;;CAGR,MAAM,YAAY,MAAM,QAAO,yBAAa,EAAE,KAAK,QAAQ;AAiB3D,QAfc,MAAM,QAAQ,IAC1B,UAAU,IAAI,OAAO,SAAS;EAC5B,MAAM,2BAAgB,KAAK,KAAK;EAChC,MAAM,UAAU,qCAAe,UAAU,OAAO;AAChD,MAAI;AACF,UAAO,KAAK,MAAM,QAAQ;WAErB,OAAgB;AACrB,SAAM,IAAI,MACR,mBAAmB,SAAS,IAAK,MAAgB,UAClD;;GAEH,CACH;;AAKH,eAAsB,mBACpB,KACA,UACA,MACe;AACf,mCAAY,KAAK,EAAE,WAAW,MAAM,CAAC;AAErC,uDADsB,KAAK,SAAS,EACV,KAAK,UAAU,MAAM,QAAW,EAAE,EAAE,OAAO;;;;;ACtCvE,SAAS,iBAAiB,OAAqD;AAK7E,QAAO,IAJO,MAAM,kBAAkB,OAAO,MAAM,GAAG,EAAE,QACtD,YACA,GACD,CACc,GAAG,MAAM,GAAG;;AAG7B,eAAsB,eAAe,KAA+B;AAClE,QAAO,mBAA0B,IAAI;;AAGvC,eAAsB,iBACpB,KACA,OACe;AACf,OAAM,mBAAmB,KAAK,iBAAiB,MAAM,EAAE,MAAM;;;;;AChB/D,SAAS,qBAAqB,WAA4C;AACxE,QAAO,GAAG,UAAU,KAAK;;AAG3B,eAAsB,mBAAmB,KAAmC;AAC1E,QAAO,mBAA8B,IAAI;;AAG3C,eAAsB,qBACpB,KACA,WACe;AACf,OAAM,mBAAmB,KAAK,qBAAqB,UAAU,EAAE,UAAU;;;;;ACZ3E,SAAS,sBACP,YACQ;AACR,QAAO,GAAG,WAAW,KAAK,GAAG,WAAW,GAAG;;AAG7C,eAAsB,oBAAoB,KAAoC;AAC5E,QAAO,mBAA+B,IAAI;;AAG5C,eAAsB,sBACpB,KACA,YACe;AACf,OAAM,mBAAmB,KAAK,sBAAsB,WAAW,EAAE,WAAW;;;;;ACd9E,SAAS,iBAAiB,OAA6C;AACrE,QAAO,GAAG,MAAM,KAAK,GAAG,MAAM,KAAK;;AAGrC,eAAsB,gBAAgB,KAA+B;AACnE,QAAO,mBAA0B,IAAI;;AAGvC,eAAsB,iBACpB,KACA,OACe;AACf,OAAM,mBAAmB,KAAK,iBAAiB,MAAM,EAAE,MAAM;;;;;ACuB/D,SAAS,SAAS,OAAwC;AACxD,QAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM;;AAG7E,SAAS,SAAS,OAA+B;AAC/C,QAAO,SAAS,MAAM,GAAG,QAAQ,EAAE;;AAGrC,SAAS,QAAQ,OAA2B;AAC1C,QAAO,MAAM,QAAQ,MAAM,GAAG,QAAQ,EAAE;;AAG1C,MAAM,0BACJ,MACA,EACE,SACA,MACA,iBACA,iBACA,qBAQU;AACZ,KAAI,CAAC,SAAS,KAAK,IAAI,OAAO,KAAK,cAAc,SAC/C,QAAO,QAAQ,EAAE;CAGnB,MAAM,SAAS,QAAQ,KAAK;AAC5B,KAAI,CAAC,QAAQ;AACX,iBAAe,IAAI,KAAK,UAAU;AAClC,SAAO;;CAGT,MAAM,UAAyB,EAAE,GAAG,MAAM;AAE1C,MAAK,MAAM,CAAC,WAAW,eAAe,OAAO,QAAQ,KAAK,EAAE;EAC1D,MAAM,cAAc,OAClB,UAAU,QAAQ,cAAc,GAAG;EAErC,MAAM,YACF,eACG,OAAO,gBAAgB,YACvB,UAAU,eACV,YAAY;EACnB,MAAM,iBACF,OAAO,cAAc,YAAY,gBAAgB;AAErD,MAAI,YACF,iBAAgB,IAAI,YAAY;AAGlC,MAAI,eACF,SAAQ,aAAa,eAAe,YAAY;GAC9C,QAAQ;GACR;GACA;GACA;GACA;GACA;GACD,CAAC;;AAIN,QAAO;;AAGT,MAAM,6BACJ,MACA,EACE,SACA,MACA,iBACA,iBACA,qBAQU;AACZ,KAAI,MAAM,QAAQ,KAAK,CACrB,QAAO,KAAK,KAAI,SACd,0BAA0B,MAAM;EAC9B;EACA;EACA;EACA;EACA;EACD,CAAC,CACH;AAGH,KAAI,SAAS,KAAK,EAAE;AAClB,MAAI,KAAK,SAAS,UAAU,SAAS,KAAK,MAAM,CAAC,aAAa,QAC5D,QAAO;GACL,GAAG;GACH,OAAO;IACL,GAAG,SAAS,KAAK,MAAM;IACvB,MACE,KAAK,SAAS,IAAI,SAAS,KAAK,MAAM,CAAC,KAAK,IACzC,SAAS,KAAK,MAAM,CAAC;IAC3B;GACF;AAGH,MAAI,KAAK,SAAS,OAChB,QAAO;GACL,GAAG;GACH,OAAO;IACL,GAAG,SAAS,KAAK,MAAM;IACvB,MAAM,QAAQ,SAAS,KAAK,MAAM,CAAC,KAAK,CAAC,KAAI,MAC3C,uBAAuB,GAAG;KACxB;KACA;KACA;KACA;KACA;KACD,CAAC,CACH;IACF;GACF;EAGH,MAAM,UAAyB,EAAE;AACjC,OAAK,MAAM,CAAC,GAAG,UAAU,OAAO,QAAQ,KAAK,CAC3C,SAAQ,KAAK,0BAA0B,OAAO;GAC5C;GACA;GACA;GACA;GACA;GACD,CAAC;AAGJ,SAAO;;AAGT,QAAO;;AAGT,MAAM,0BACJ,MACA,EAAE,SAAS,MAAM,iBAAiB,iBAAiB,qBAEnD,0BAA0B,MAAM;CAC9B;CACA;CACA;CACA;CACA;CACD,CAAC;AAEJ,MAAM,2BAAsC,MAAM,EAAE,WAAW;AAC7D,KAAI,CAAC,SAAS,KAAK,IAAI,KAAK,aAAa,QACvC,QAAO;AAGT,QAAO;EACL,GAAG;EACH,IAAI,KAAK,SAAS,IAAI,KAAK,GAAG,IAAI,KAAK;EACxC;;AAGH,MAAM,uBACJ,MACA,EAAE,SAAS,MAAM,iBAAiB,iBAAiB,qBAChD;AACH,KAAI,CAAC,MAAM,QAAQ,KAAK,CACtB,OAAM,IAAI,UACR,wDAAwD,KAAK,UAAU,KAAK,CAAC,GAC9E;AAGH,QAAO,KAAK,KAAI,MACd,uBAAuB,GAAG;EACxB;EACA;EACA;EACA;EACA;EACD,CAAC,CACH;;AAGH,MAAM,uBAAkC,MAAM,EAAE,WAAW;AACzD,KAAI,CAAC,SAAS,KAAK,CACjB,QAAO;CAGT,MAAM,QACF,OAAO,KAAK,OAAO,WAAW,KAAK,QAAQ,IAAI,KAAK,GAAG,GAAG;AAC9D,QAAO,UAAU,SAAY,OAAO;EAAE,GAAG;EAAM,IAAI;EAAO;;AAG5D,MAAM,4BAAuC,MAAM,YAAY;AAC7D,KAAI,CAAC,MAAM,QAAQ,KAAK,CACtB,OAAM,IAAI,UACR,6DAA6D,KAAK,UAAU,KAAK,CAAC,GACnF;AAGH,QAAO,KAAK,KAAI,MAAK,oBAAoB,GAAG,QAAQ,CAAC;;AAGvD,MAAM,yBAAoC,MAAM,EAAE,QAAQ,WAAW;AACnE,KAAI,CAAC,MAAM,QAAQ,KAAK,CACtB,QAAO;CAaT,MAAM,YAPF;EACF,kBAAkB,KAAK;EACvB,gBAAgB,KAAK;EACrB,eAAe,KAAK;EACpB,sBAAsB,KAAK;EAC5B,CAGsB,OAA+B,UAAU;AAChE,KAAI,CAAC,UACH,QAAO;AAGT,QAAO,KAAK,KAAI,MAAK,UAAU,IAAI,EAAE,IAAI,EAAE;;AAG7C,MAAM,kBAAkB;CACtB,OAAO;CACP,OAAO;CACP,YAAY;CACZ,WAAW;CACX,SAAS;CACT,UAAU;CACX;AAED,SAAgB,QACd,OACA,SAKE;CACF,MAAM,EAAE,SAAS,SAAS;CAC1B,MAAM,kCAAmC,IAAI,KAAK;CAClD,MAAM,iCAAiC,IAAI,KAAK;CAehD,MAAM,aAbgB,MAAM,aACvB,MAAM,WAA6C,KAAK,cAAc;EACrE,MAAM,kBAAkB,SAAS,UAAU;AAC3C,SAAO;GACL,GAAG;GACH,IAAI,KAAK,SAAS,IAAI,gBAAgB,GAAG,IAAI,gBAAgB;GAC7D,WACE,KAAK,SAAS,IAAI,gBAAgB,UAAU,IACzC,gBAAgB;GACtB;GACD,GACF,MAAM;CAIV,MAAM,WAAW,KAAK,SAAS,IAAI,MAAM,UAAU,IAAI,MAAM;CAC7D,MAAM,mBAAmB,MAAM,SAAS,YACpC,uBAAuB,MAAM,SAAS;EACpC;EACA;EACA;EACA;EACA;EACD,CAAC,GACF,MAAM;AAYV,QAAO;EACL,aAXkB;GAClB,GAAG;GAEH,SAAS;GACT,IAAI,OAAO,KAAK,SAAS,IAAI,MAAM,GAAG,IAAI,MAAM,GAAG;GACnD,MAAM,OAAO,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,MAAM,KAAK;GACzD,WAAW,YAAY,OAAO,OAAO,SAAS,GAAG;GACjD;GACD;EAIC;EACA;EACD;;;;;AClUH,SAAS,iBACP,MACA,oBACA,UACA,UACA,SACA,MACS;AACT,KAAI,MAAM,QAAQ,KAAK,CACrB,QAAO,KAAK,KAAK,MAAM,UACrB,iBAAiB,MAAM,oBAAoB,UAAU,UAAU,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,CAC7F;AAGH,KAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,KAAK,EAAE;EAC5D,MAAM,SAAS;AAEf,MAAI,OAAO,SAAS,UAAU,OAAO,SAAS,OAAO,OAAO,UAAU,UAAU;GAC9E,MAAM,QAAQ,OAAO;AACrB,OAAI,MAAM,QAAQ,MAAM,KAAK,CAC3B,QAAO;IACL,GAAG;IACH,OAAO;KACL,GAAG;KACH,MAAM,MAAM,KAAK,KAAK,MAAe,UAAkB;AACrD,UAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,KAAK,CAC1D,QAAO,eACL,MACA,oBACA,UACA,UACA,SACA,GAAG,KAAK,cAAc,MAAM,GAC7B;AAEH,aAAO;OACP;KACH;IACF;;EAIL,MAAM,SAAkC,EAAE;AAC1C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,CAC/C,QAAO,OAAO,iBAAiB,OAAO,oBAAoB,UAAU,UAAU,SAAS,GAAG,KAAK,GAAG,MAAM;AAE1G,SAAO;;AAGT,QAAO;;AAGT,SAAS,eACP,KACA,oBACA,UACA,UACA,SACA,MACyB;AACzB,KAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,IAAI,CACvD,QAAO;CAGT,MAAM,SAAS,EAAE,GAAG,KAAK;CACzB,MAAM,gBAAgB,IAAI;AAE1B,KAAI,cACF,MAAK,MAAM,EAAE,OAAO,UAAU,oBAAoB;AAChD,MAAI,SAAS,cACX;EAGF,MAAM,aAAa,OAAO;AAE1B,MAAI,OAAO,eAAe,YAAY,eAAe,UAAU;AAC7D,UAAO,SAAS;AAChB,WAAQ,KAAK;IACX,WAAW;IACX;IACA,MAAM,GAAG,KAAK,GAAG;IAClB,CAAC;aAEK,MAAM,QAAQ,WAAW,CAYhC,QAAO,SAXU,WAAW,KAAK,SAAS;AACxC,OAAI,SAAS,UAAU;AACrB,YAAQ,KAAK;KACX,WAAW;KACX;KACA,MAAM,GAAG,KAAK,GAAG,MAAM;KACxB,CAAC;AACF,WAAO;;AAET,UAAO;IACP;;AAOR,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,CAC/C,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,OAAO,MAAM,KAAK,MAAM,UAAU;AACvC,MAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,KAAK,CAC1D,QAAO,eACL,MACA,oBACA,UACA,UACA,SACA,GAAG,KAAK,GAAG,IAAI,GAAG,MAAM,GACzB;AAEH,SAAO;GACP;UAGF,SACG,OAAO,UAAU,YACjB,CAAC,MAAM,QAAQ,MAAM,IACpB,MAAkC,SAAS,SAC5C,MAAM,QAAS,MAAkC,QAAQ,CAE5D,QAAO,OAAO,iBAAiB,OAAO,oBAAoB,UAAU,UAAU,SAAS,GAAG,KAAK,GAAG,MAAM;AAI5G,QAAO;;AAGT,SAAgB,sBACd,OACA,oBACA,UACA,UACqC;CACrC,MAAM,UAAoB,EAAE;CAE5B,MAAM,aAAa,eACjB,MAAM,SACN,oBACA,UACA,UACA,SACA,UACD;AAED,QAAO;EAEL,OAAO;GAAE,GAAG;GAAO,SAAS;GAA2C;EACvE;EACD;;;;;AC5JH,SAAgB,WACd,KACA,SACgB;AAKhB,QAAO;EACL,WAAW;EACX,IAAI;EACJ,UAAU;EACV,KAAK;EACL,MARmB,IAAI,MAAM,IAAI,CACT,GAAG,GAAG,IAAI;EAQlC,KAAK;EACL,OAAO;EACP,WAAW;EACX,OAAO;EACP,WAAW,EAAE;EACb,QAAQ;EACR,iBAAiB;EACjB,YAAY;EACZ,YAAY;EACZ,OAAO;EACP,QAAQ;EACR,cAAc;EACd,WAAW;EACX,cAAc;EACd,GAAG;EACJ;;;;;AC7BH,SAAgB,UACd,KACA,SACoB;AAEpB,KAAI,IAAI,WAAW,UAAU,CAC3B,QAAO;EACL,WAAW;EACX,IAAI;EACJ,KAAK;EACL,YAAY;EACZ,UAAU;EACV,OAAO,IAAI,MAAM,EAAiB;EAClC,GAAG;EACJ;CAIH,MAAM,YAAY,IAAI,QAAQ,IAAI;CAClC,MAAM,SAAS,cAAc,KAAK,SAAY,IAAI,MAAM,YAAY,EAAE;CACtE,MAAM,WAAW,cAAc,KAAK,MAAM,IAAI,MAAM,GAAG,UAAU;AAEjE,QAAO;EACL,WAAW;EACX,IAAI;EACJ,KAAK;EACL,YAAY;EACZ,UAAU;EACV,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;EAC5B,GAAG;EACJ"}