storyblok 4.16.9 → 4.17.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/dist/config/index.d.mts +4 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/index.mjs +234 -148
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/config/index.d.mts
CHANGED
|
@@ -64,6 +64,10 @@ interface MigrationsGenerateOptions extends CommandOptions {
|
|
|
64
64
|
interface MigrationsRunOptions extends CommandOptions {
|
|
65
65
|
dryRun?: boolean;
|
|
66
66
|
filter?: string;
|
|
67
|
+
/**
|
|
68
|
+
* The source space id to read migration files from.
|
|
69
|
+
*/
|
|
70
|
+
from?: string;
|
|
67
71
|
query?: string;
|
|
68
72
|
startsWith?: string;
|
|
69
73
|
publish?: 'all' | 'published' | 'published-with-changes';
|
package/dist/config/index.d.ts
CHANGED
|
@@ -64,6 +64,10 @@ interface MigrationsGenerateOptions extends CommandOptions {
|
|
|
64
64
|
interface MigrationsRunOptions extends CommandOptions {
|
|
65
65
|
dryRun?: boolean;
|
|
66
66
|
filter?: string;
|
|
67
|
+
/**
|
|
68
|
+
* The source space id to read migration files from.
|
|
69
|
+
*/
|
|
70
|
+
from?: string;
|
|
67
71
|
query?: string;
|
|
68
72
|
startsWith?: string;
|
|
69
73
|
publish?: 'all' | 'published' | 'published-with-changes';
|
package/dist/index.mjs
CHANGED
|
@@ -26,7 +26,6 @@ import { Octokit } from 'octokit';
|
|
|
26
26
|
import { pipeline as pipeline$1 } from 'node:stream/promises';
|
|
27
27
|
import { Buffer } from 'node:buffer';
|
|
28
28
|
import Storyblok from 'storyblok-js-client';
|
|
29
|
-
import { createHash } from 'node:crypto';
|
|
30
29
|
|
|
31
30
|
const commands = {
|
|
32
31
|
LOGIN: "login",
|
|
@@ -4429,7 +4428,7 @@ class MigrationStream extends Transform {
|
|
|
4429
4428
|
}
|
|
4430
4429
|
const migrationFunction = await getMigrationFunction(
|
|
4431
4430
|
migrationFile.name,
|
|
4432
|
-
this.options.
|
|
4431
|
+
this.options.sourceSpace,
|
|
4433
4432
|
this.options.path
|
|
4434
4433
|
);
|
|
4435
4434
|
this.migrationFunctions.set(migrationFile.name, migrationFunction);
|
|
@@ -4756,7 +4755,7 @@ class UpdateStream extends Writable {
|
|
|
4756
4755
|
}
|
|
4757
4756
|
}
|
|
4758
4757
|
|
|
4759
|
-
const runCmd = migrationsCommand.command("run [componentName]").description("Run migrations").option("--fi, --filter <filter>", "glob filter to apply to the components before pushing").option("-d, --dry-run", "Preview changes without applying them to Storyblok").option("-q, --query <query>", 'Filter stories by content attributes using Storyblok filter query syntax. Example: --query="[highlighted][in]=true"').option("--starts-with <path>", 'Filter stories by path. Example: --starts-with="/en/blog/"').option("--publish <publish>", "Options for publication mode: all | published | published-with-changes").option("-s, --space <space>", "space ID");
|
|
4758
|
+
const runCmd = migrationsCommand.command("run [componentName]").description("Run migrations").option("--fi, --filter <filter>", "glob filter to apply to the components before pushing").option("-d, --dry-run", "Preview changes without applying them to Storyblok").option("-q, --query <query>", 'Filter stories by content attributes using Storyblok filter query syntax. Example: --query="[highlighted][in]=true"').option("--starts-with <path>", 'Filter stories by path. Example: --starts-with="/en/blog/"').option("--publish <publish>", "Options for publication mode: all | published | published-with-changes").option("-f, --from <from>", "source space id").option("-s, --space <space>", "space ID");
|
|
4760
4759
|
runCmd.action(async (componentName, options, command) => {
|
|
4761
4760
|
const ui = getUI();
|
|
4762
4761
|
const logger = getLogger();
|
|
@@ -4777,11 +4776,16 @@ runCmd.action(async (componentName, options, command) => {
|
|
|
4777
4776
|
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose);
|
|
4778
4777
|
return;
|
|
4779
4778
|
}
|
|
4780
|
-
const { filter, dryRun = false, query, startsWith, publish } = options;
|
|
4779
|
+
const { filter, from, dryRun = false, query, startsWith, publish } = options;
|
|
4780
|
+
const fromSpace = from || space;
|
|
4781
|
+
if (from) {
|
|
4782
|
+
ui.info(`Running migrations ${chalk.bold("from")} space ${chalk.hex(colorPalette.MIGRATIONS)(fromSpace)} ${chalk.bold("on")} space ${chalk.hex(colorPalette.MIGRATIONS)(space)}`);
|
|
4783
|
+
ui.br();
|
|
4784
|
+
}
|
|
4781
4785
|
try {
|
|
4782
4786
|
const spinner = ui.createSpinner(`Fetching migration files and stories...`);
|
|
4783
4787
|
const migrationFiles = await readMigrationFiles({
|
|
4784
|
-
space,
|
|
4788
|
+
space: fromSpace,
|
|
4785
4789
|
path,
|
|
4786
4790
|
filter
|
|
4787
4791
|
});
|
|
@@ -4789,7 +4793,7 @@ runCmd.action(async (componentName, options, command) => {
|
|
|
4789
4793
|
return file.name.match(new RegExp(`^${componentName}(\\..*)?.js$`));
|
|
4790
4794
|
}) : migrationFiles;
|
|
4791
4795
|
if (filteredMigrations.length === 0) {
|
|
4792
|
-
spinner.failed(`No migration files found${componentName ? ` for component "${componentName}"` : ""}${filter ? ` matching filter "${filter}"` : ""} in space "${
|
|
4796
|
+
spinner.failed(`No migration files found${componentName ? ` for component "${componentName}"` : ""}${filter ? ` matching filter "${filter}"` : ""} in space "${fromSpace}".`);
|
|
4793
4797
|
logger.warn("No migration files found");
|
|
4794
4798
|
logger.info("Migration finished");
|
|
4795
4799
|
return;
|
|
@@ -4817,6 +4821,7 @@ runCmd.action(async (componentName, options, command) => {
|
|
|
4817
4821
|
const migrationStream = new MigrationStream({
|
|
4818
4822
|
migrationFiles: filteredMigrations,
|
|
4819
4823
|
space,
|
|
4824
|
+
sourceSpace: fromSpace,
|
|
4820
4825
|
path,
|
|
4821
4826
|
componentName,
|
|
4822
4827
|
onTotal: (total) => {
|
|
@@ -7037,10 +7042,6 @@ const updateAssetFolder = async (id, folder, {
|
|
|
7037
7042
|
handleAPIError("push_asset_folder", toError(maybeError));
|
|
7038
7043
|
}
|
|
7039
7044
|
};
|
|
7040
|
-
const sha256 = (data) => {
|
|
7041
|
-
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
7042
|
-
return createHash("sha256").update(buffer).digest("hex");
|
|
7043
|
-
};
|
|
7044
7045
|
const downloadAssetFile = async (asset, options) => {
|
|
7045
7046
|
let url = asset.filename;
|
|
7046
7047
|
if (asset.is_private) {
|
|
@@ -7564,24 +7565,6 @@ const hasId = (a) => {
|
|
|
7564
7565
|
const hasShortFilename = (a) => {
|
|
7565
7566
|
return !!a && typeof a === "object" && "short_filename" in a && typeof a.short_filename === "string";
|
|
7566
7567
|
};
|
|
7567
|
-
const isDataUnchanged = (localAsset, remoteAsset) => {
|
|
7568
|
-
for (const key of Object.keys(localAsset)) {
|
|
7569
|
-
const local = localAsset[key];
|
|
7570
|
-
const remote = remoteAsset[key];
|
|
7571
|
-
if (typeof local === "object" || typeof remote === "object") {
|
|
7572
|
-
if (JSON.stringify(local) !== JSON.stringify(remote)) {
|
|
7573
|
-
return false;
|
|
7574
|
-
}
|
|
7575
|
-
} else if (local !== remote) {
|
|
7576
|
-
return false;
|
|
7577
|
-
}
|
|
7578
|
-
}
|
|
7579
|
-
return true;
|
|
7580
|
-
};
|
|
7581
|
-
const makeDownloadAssetFileTransport = ({
|
|
7582
|
-
assetToken,
|
|
7583
|
-
region
|
|
7584
|
-
}) => (asset) => downloadAssetFile(asset, { assetToken, region });
|
|
7585
7568
|
const processAsset = async ({
|
|
7586
7569
|
localAsset,
|
|
7587
7570
|
fileBuffer,
|
|
@@ -7609,20 +7592,13 @@ const processAsset = async ({
|
|
|
7609
7592
|
internal_tag_ids: "internal_tag_ids" in localAsset ? localAsset.internal_tag_ids : remoteAsset.internal_tag_ids,
|
|
7610
7593
|
meta_data: "meta_data" in localAsset ? localAsset.meta_data : remoteAsset.meta_data
|
|
7611
7594
|
};
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
remoteAsset.id,
|
|
7620
|
-
{ ...updatePayload, short_filename: remoteAsset.short_filename },
|
|
7621
|
-
isFileUnchanged ? void 0 : fileBuffer
|
|
7622
|
-
);
|
|
7623
|
-
newRemoteAsset = { ...remoteAsset, ...updatePayload };
|
|
7624
|
-
status = "updated";
|
|
7625
|
-
}
|
|
7595
|
+
await transports.updateAsset(
|
|
7596
|
+
remoteAsset.id,
|
|
7597
|
+
{ ...updatePayload, short_filename: remoteAsset.short_filename },
|
|
7598
|
+
fileBuffer
|
|
7599
|
+
);
|
|
7600
|
+
newRemoteAsset = { ...remoteAsset, ...updatePayload };
|
|
7601
|
+
status = "updated";
|
|
7626
7602
|
} else if (hasShortFilename(localAsset)) {
|
|
7627
7603
|
const createPayload = {
|
|
7628
7604
|
...localAsset,
|
|
@@ -7644,7 +7620,6 @@ const upsertAssetStream = ({
|
|
|
7644
7620
|
maps,
|
|
7645
7621
|
onIncrement,
|
|
7646
7622
|
onAssetSuccess,
|
|
7647
|
-
onAssetSkipped,
|
|
7648
7623
|
onAssetError
|
|
7649
7624
|
}) => {
|
|
7650
7625
|
const processing = /* @__PURE__ */ new Set();
|
|
@@ -7654,7 +7629,7 @@ const upsertAssetStream = ({
|
|
|
7654
7629
|
await apiConcurrencyLock$1.acquire();
|
|
7655
7630
|
const task = (async () => {
|
|
7656
7631
|
try {
|
|
7657
|
-
const {
|
|
7632
|
+
const { remoteAsset } = await processAsset({
|
|
7658
7633
|
localAsset,
|
|
7659
7634
|
fileBuffer: context.fileBuffer,
|
|
7660
7635
|
assetBinaryPath: context.assetBinaryPath,
|
|
@@ -7662,11 +7637,7 @@ const upsertAssetStream = ({
|
|
|
7662
7637
|
transports,
|
|
7663
7638
|
maps
|
|
7664
7639
|
});
|
|
7665
|
-
|
|
7666
|
-
onAssetSkipped?.(localAsset, remoteAsset);
|
|
7667
|
-
} else {
|
|
7668
|
-
onAssetSuccess?.(localAsset, remoteAsset);
|
|
7669
|
-
}
|
|
7640
|
+
onAssetSuccess?.(localAsset, remoteAsset);
|
|
7670
7641
|
} catch (maybeError) {
|
|
7671
7642
|
onAssetError?.(toError(maybeError), localAsset);
|
|
7672
7643
|
}
|
|
@@ -7849,16 +7820,13 @@ pullCmd$1.action(async (options, command) => {
|
|
|
7849
7820
|
const traverseAndMapBySchema = (data, {
|
|
7850
7821
|
schemas,
|
|
7851
7822
|
maps,
|
|
7852
|
-
fieldRefMappers: fieldRefMappers2
|
|
7853
|
-
processedFields,
|
|
7854
|
-
missingSchemas
|
|
7823
|
+
fieldRefMappers: fieldRefMappers2
|
|
7855
7824
|
}) => {
|
|
7856
7825
|
if (!data?.component) {
|
|
7857
7826
|
return data ?? {};
|
|
7858
7827
|
}
|
|
7859
7828
|
const schema = schemas[data.component];
|
|
7860
7829
|
if (!schema) {
|
|
7861
|
-
missingSchemas.add(data.component);
|
|
7862
7830
|
return data;
|
|
7863
7831
|
}
|
|
7864
7832
|
const dataNew = { ...data };
|
|
@@ -7866,17 +7834,12 @@ const traverseAndMapBySchema = (data, {
|
|
|
7866
7834
|
const fieldSchema = schema[fieldName.replace(/__i18n__.*/, "")];
|
|
7867
7835
|
const fieldType = fieldSchema && typeof fieldSchema === "object" && "type" in fieldSchema && fieldSchema.type;
|
|
7868
7836
|
const fieldRefMapper = typeof fieldType === "string" && fieldRefMappers2[fieldType];
|
|
7869
|
-
if (fieldSchema) {
|
|
7870
|
-
processedFields.add(fieldSchema);
|
|
7871
|
-
}
|
|
7872
7837
|
if (fieldRefMapper) {
|
|
7873
7838
|
dataNew[fieldName] = fieldRefMapper(fieldValue, {
|
|
7874
7839
|
schema: fieldSchema,
|
|
7875
7840
|
schemas,
|
|
7876
7841
|
maps,
|
|
7877
|
-
fieldRefMappers: fieldRefMappers2
|
|
7878
|
-
processedFields,
|
|
7879
|
-
missingSchemas
|
|
7842
|
+
fieldRefMappers: fieldRefMappers2
|
|
7880
7843
|
});
|
|
7881
7844
|
}
|
|
7882
7845
|
}
|
|
@@ -7885,17 +7848,13 @@ const traverseAndMapBySchema = (data, {
|
|
|
7885
7848
|
const traverseAndMapRichtextDoc = (data, {
|
|
7886
7849
|
schemas,
|
|
7887
7850
|
maps,
|
|
7888
|
-
fieldRefMappers: fieldRefMappers2
|
|
7889
|
-
processedFields,
|
|
7890
|
-
missingSchemas
|
|
7851
|
+
fieldRefMappers: fieldRefMappers2
|
|
7891
7852
|
}) => {
|
|
7892
7853
|
if (Array.isArray(data)) {
|
|
7893
7854
|
return data.map((item) => traverseAndMapRichtextDoc(item, {
|
|
7894
7855
|
schemas,
|
|
7895
7856
|
maps,
|
|
7896
|
-
fieldRefMappers: fieldRefMappers2
|
|
7897
|
-
processedFields,
|
|
7898
|
-
missingSchemas
|
|
7857
|
+
fieldRefMappers: fieldRefMappers2
|
|
7899
7858
|
}));
|
|
7900
7859
|
}
|
|
7901
7860
|
if (data && typeof data === "object") {
|
|
@@ -7916,9 +7875,7 @@ const traverseAndMapRichtextDoc = (data, {
|
|
|
7916
7875
|
body: (data.attrs?.body ?? []).map((d) => traverseAndMapBySchema(d, {
|
|
7917
7876
|
schemas,
|
|
7918
7877
|
maps,
|
|
7919
|
-
fieldRefMappers: fieldRefMappers2
|
|
7920
|
-
processedFields,
|
|
7921
|
-
missingSchemas
|
|
7878
|
+
fieldRefMappers: fieldRefMappers2
|
|
7922
7879
|
}))
|
|
7923
7880
|
}
|
|
7924
7881
|
};
|
|
@@ -7928,21 +7885,17 @@ const traverseAndMapRichtextDoc = (data, {
|
|
|
7928
7885
|
newData[k] = traverseAndMapRichtextDoc(value, {
|
|
7929
7886
|
schemas,
|
|
7930
7887
|
maps,
|
|
7931
|
-
fieldRefMappers: fieldRefMappers2
|
|
7932
|
-
processedFields,
|
|
7933
|
-
missingSchemas
|
|
7888
|
+
fieldRefMappers: fieldRefMappers2
|
|
7934
7889
|
});
|
|
7935
7890
|
}
|
|
7936
7891
|
return newData;
|
|
7937
7892
|
}
|
|
7938
7893
|
return data;
|
|
7939
7894
|
};
|
|
7940
|
-
const richtextFieldRefMapper = (data, { schemas, maps, fieldRefMappers: fieldRefMappers2
|
|
7895
|
+
const richtextFieldRefMapper = (data, { schemas, maps, fieldRefMappers: fieldRefMappers2 }) => traverseAndMapRichtextDoc(data, {
|
|
7941
7896
|
schemas,
|
|
7942
7897
|
maps,
|
|
7943
|
-
fieldRefMappers: fieldRefMappers2
|
|
7944
|
-
processedFields,
|
|
7945
|
-
missingSchemas
|
|
7898
|
+
fieldRefMappers: fieldRefMappers2
|
|
7946
7899
|
});
|
|
7947
7900
|
const multilinkFieldRefMapper = (data, { maps }) => {
|
|
7948
7901
|
if (data.linktype !== "story") {
|
|
@@ -7953,16 +7906,14 @@ const multilinkFieldRefMapper = (data, { maps }) => {
|
|
|
7953
7906
|
id: maps.stories?.get(data.id) || data.id
|
|
7954
7907
|
};
|
|
7955
7908
|
};
|
|
7956
|
-
const bloksFieldRefMapper = (data, { schemas, maps, fieldRefMappers: fieldRefMappers2
|
|
7909
|
+
const bloksFieldRefMapper = (data, { schemas, maps, fieldRefMappers: fieldRefMappers2 }) => {
|
|
7957
7910
|
if (!Array.isArray(data)) {
|
|
7958
7911
|
throw new TypeError(`Invalid bloks field: expected an array, but received ${JSON.stringify(data)}. Please make sure your bloks field value is an array of components (e.g. [{ component: "my_blok", ... }]).`);
|
|
7959
7912
|
}
|
|
7960
7913
|
return data.map((d) => traverseAndMapBySchema(d, {
|
|
7961
7914
|
schemas,
|
|
7962
7915
|
maps,
|
|
7963
|
-
fieldRefMappers: fieldRefMappers2
|
|
7964
|
-
processedFields,
|
|
7965
|
-
missingSchemas
|
|
7916
|
+
fieldRefMappers: fieldRefMappers2
|
|
7966
7917
|
}));
|
|
7967
7918
|
};
|
|
7968
7919
|
const assetFieldRefMapper = (data, { maps }) => {
|
|
@@ -7997,33 +7948,24 @@ const fieldRefMappers = {
|
|
|
7997
7948
|
richtext: richtextFieldRefMapper
|
|
7998
7949
|
};
|
|
7999
7950
|
const storyRefMapper = (story, { schemas, maps }) => {
|
|
8000
|
-
const processedFields = /* @__PURE__ */ new Set();
|
|
8001
|
-
const missingSchemas = /* @__PURE__ */ new Set();
|
|
8002
7951
|
const alternates = story.alternates ? story.alternates.map((a) => ({
|
|
8003
7952
|
...a,
|
|
8004
7953
|
id: maps.stories?.get(a.id) ?? a.id,
|
|
8005
7954
|
parent_id: maps.stories?.get(a.parent_id) ?? a.parent_id
|
|
8006
7955
|
})) : story.alternates;
|
|
8007
7956
|
const parentId = maps.stories?.get(story.parent_id) ?? story.parent_id;
|
|
8008
|
-
|
|
7957
|
+
return {
|
|
8009
7958
|
...story,
|
|
8010
7959
|
content: story.content?.component ? traverseAndMapBySchema(story.content, {
|
|
8011
7960
|
schemas,
|
|
8012
7961
|
maps,
|
|
8013
|
-
fieldRefMappers
|
|
8014
|
-
processedFields,
|
|
8015
|
-
missingSchemas
|
|
7962
|
+
fieldRefMappers
|
|
8016
7963
|
}) : story.content,
|
|
8017
7964
|
id: Number(maps.stories?.get(story.id) ?? story.id),
|
|
8018
7965
|
uuid: String(maps.stories?.get(story.uuid) ?? story.uuid),
|
|
8019
7966
|
parent_id: parentId != null ? Number(parentId) : 0,
|
|
8020
7967
|
alternates
|
|
8021
7968
|
};
|
|
8022
|
-
return {
|
|
8023
|
-
mappedStory,
|
|
8024
|
-
processedFields,
|
|
8025
|
-
missingSchemas
|
|
8026
|
-
};
|
|
8027
7969
|
};
|
|
8028
7970
|
|
|
8029
7971
|
const apiConcurrencyLock = new Sema(12);
|
|
@@ -8150,8 +8092,8 @@ const mapReferencesStream = ({
|
|
|
8150
8092
|
objectMode: true,
|
|
8151
8093
|
transform(localStory, _encoding, callback) {
|
|
8152
8094
|
try {
|
|
8153
|
-
const
|
|
8154
|
-
onStorySuccess?.(mappedStory
|
|
8095
|
+
const mappedStory = storyRefMapper(localStory, { schemas, maps });
|
|
8096
|
+
onStorySuccess?.(mappedStory);
|
|
8155
8097
|
this.push(mappedStory);
|
|
8156
8098
|
} catch (maybeError) {
|
|
8157
8099
|
onStoryError?.(toError(maybeError), localStory);
|
|
@@ -8394,6 +8336,162 @@ const writeStoryStream = ({
|
|
|
8394
8336
|
});
|
|
8395
8337
|
};
|
|
8396
8338
|
|
|
8339
|
+
const walkRichtextBloks = (node, onBlok) => {
|
|
8340
|
+
if (Array.isArray(node)) {
|
|
8341
|
+
for (const item of node) {
|
|
8342
|
+
walkRichtextBloks(item, onBlok);
|
|
8343
|
+
}
|
|
8344
|
+
return;
|
|
8345
|
+
}
|
|
8346
|
+
if (!node || typeof node !== "object") {
|
|
8347
|
+
return;
|
|
8348
|
+
}
|
|
8349
|
+
const obj = node;
|
|
8350
|
+
if (obj.type === "blok" && obj.attrs && typeof obj.attrs === "object") {
|
|
8351
|
+
const body = obj.attrs.body;
|
|
8352
|
+
if (Array.isArray(body)) {
|
|
8353
|
+
for (const blok of body) {
|
|
8354
|
+
onBlok(blok);
|
|
8355
|
+
}
|
|
8356
|
+
}
|
|
8357
|
+
}
|
|
8358
|
+
for (const value of Object.values(obj)) {
|
|
8359
|
+
walkRichtextBloks(value, onBlok);
|
|
8360
|
+
}
|
|
8361
|
+
};
|
|
8362
|
+
|
|
8363
|
+
const RESERVED_KEYS = /* @__PURE__ */ new Set(["component"]);
|
|
8364
|
+
const isReservedKey = (key) => RESERVED_KEYS.has(key) || key.startsWith("_");
|
|
8365
|
+
const MAX_STORIES_PER_ENTRY = 5;
|
|
8366
|
+
const addDrift = (driftByComponent, component, field) => {
|
|
8367
|
+
const existing = driftByComponent.get(component) ?? /* @__PURE__ */ new Set();
|
|
8368
|
+
existing.add(field);
|
|
8369
|
+
driftByComponent.set(component, existing);
|
|
8370
|
+
};
|
|
8371
|
+
const validateStoryAgainstSchemas = (story, schemas) => {
|
|
8372
|
+
const driftByComponent = /* @__PURE__ */ new Map();
|
|
8373
|
+
const missingSchemas = /* @__PURE__ */ new Set();
|
|
8374
|
+
const visit = (data) => {
|
|
8375
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
8376
|
+
return;
|
|
8377
|
+
}
|
|
8378
|
+
const node = data;
|
|
8379
|
+
const componentName = node.component;
|
|
8380
|
+
if (typeof componentName !== "string" || componentName.length === 0) {
|
|
8381
|
+
return;
|
|
8382
|
+
}
|
|
8383
|
+
const schema = schemas[componentName];
|
|
8384
|
+
if (!schema) {
|
|
8385
|
+
missingSchemas.add(componentName);
|
|
8386
|
+
return;
|
|
8387
|
+
}
|
|
8388
|
+
for (const [fieldName, fieldValue] of Object.entries(node)) {
|
|
8389
|
+
if (isReservedKey(fieldName)) {
|
|
8390
|
+
continue;
|
|
8391
|
+
}
|
|
8392
|
+
const normalized = fieldName.replace(/__i18n__.*/, "");
|
|
8393
|
+
const fieldSchema = schema[normalized];
|
|
8394
|
+
if (!fieldSchema) {
|
|
8395
|
+
addDrift(driftByComponent, componentName, normalized);
|
|
8396
|
+
continue;
|
|
8397
|
+
}
|
|
8398
|
+
const fieldType = typeof fieldSchema.type === "string" ? fieldSchema.type : void 0;
|
|
8399
|
+
if (fieldType === "bloks" && Array.isArray(fieldValue)) {
|
|
8400
|
+
for (const item of fieldValue) {
|
|
8401
|
+
visit(item);
|
|
8402
|
+
}
|
|
8403
|
+
} else if (fieldType === "richtext" && fieldValue && typeof fieldValue === "object") {
|
|
8404
|
+
walkRichtextBloks(fieldValue, (blok) => visit(blok));
|
|
8405
|
+
}
|
|
8406
|
+
}
|
|
8407
|
+
};
|
|
8408
|
+
visit(story.content);
|
|
8409
|
+
return { driftByComponent, missingSchemas };
|
|
8410
|
+
};
|
|
8411
|
+
const addStoryToSet = (bag, key, storySlug) => {
|
|
8412
|
+
const existing = bag.get(key) ?? /* @__PURE__ */ new Set();
|
|
8413
|
+
existing.add(storySlug);
|
|
8414
|
+
bag.set(key, existing);
|
|
8415
|
+
};
|
|
8416
|
+
const collectSchemaIssues = async ({
|
|
8417
|
+
directoryPath,
|
|
8418
|
+
schemas
|
|
8419
|
+
}) => {
|
|
8420
|
+
const issues = {
|
|
8421
|
+
driftByComponent: /* @__PURE__ */ new Map(),
|
|
8422
|
+
missingSchemas: /* @__PURE__ */ new Map(),
|
|
8423
|
+
total: 0
|
|
8424
|
+
};
|
|
8425
|
+
try {
|
|
8426
|
+
await pipeline$1(
|
|
8427
|
+
readLocalStoriesStream({ directoryPath }),
|
|
8428
|
+
new Writable({
|
|
8429
|
+
objectMode: true,
|
|
8430
|
+
write(story, _encoding, callback) {
|
|
8431
|
+
issues.total += 1;
|
|
8432
|
+
const storyIdentifier = story.full_slug || story.slug;
|
|
8433
|
+
const { driftByComponent, missingSchemas } = validateStoryAgainstSchemas(story, schemas);
|
|
8434
|
+
for (const component of missingSchemas) {
|
|
8435
|
+
addStoryToSet(issues.missingSchemas, component, storyIdentifier);
|
|
8436
|
+
}
|
|
8437
|
+
for (const [component, fields] of driftByComponent) {
|
|
8438
|
+
const fieldMap = issues.driftByComponent.get(component) ?? /* @__PURE__ */ new Map();
|
|
8439
|
+
for (const field of fields) {
|
|
8440
|
+
addStoryToSet(fieldMap, field, storyIdentifier);
|
|
8441
|
+
}
|
|
8442
|
+
issues.driftByComponent.set(component, fieldMap);
|
|
8443
|
+
}
|
|
8444
|
+
callback();
|
|
8445
|
+
}
|
|
8446
|
+
})
|
|
8447
|
+
);
|
|
8448
|
+
} catch (error) {
|
|
8449
|
+
if (error?.code !== "ENOENT") {
|
|
8450
|
+
throw error;
|
|
8451
|
+
}
|
|
8452
|
+
}
|
|
8453
|
+
return issues;
|
|
8454
|
+
};
|
|
8455
|
+
const formatStoryList = (stories) => {
|
|
8456
|
+
const sorted = [...stories].sort();
|
|
8457
|
+
if (sorted.length <= MAX_STORIES_PER_ENTRY) {
|
|
8458
|
+
return sorted.join(", ");
|
|
8459
|
+
}
|
|
8460
|
+
const shown = sorted.slice(0, MAX_STORIES_PER_ENTRY).join(", ");
|
|
8461
|
+
return `${shown}, and ${sorted.length - MAX_STORIES_PER_ENTRY} more`;
|
|
8462
|
+
};
|
|
8463
|
+
const formatSchemaIssues = (issues) => {
|
|
8464
|
+
const lines = ["Schema validation failed. Push aborted."];
|
|
8465
|
+
if (issues.missingSchemas.size > 0) {
|
|
8466
|
+
lines.push("");
|
|
8467
|
+
lines.push("Missing component schemas:");
|
|
8468
|
+
const sortedMissing = [...issues.missingSchemas.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
8469
|
+
for (const [component, stories] of sortedMissing) {
|
|
8470
|
+
lines.push(` - ${component} (in stories: ${formatStoryList(stories)})`);
|
|
8471
|
+
}
|
|
8472
|
+
lines.push("");
|
|
8473
|
+
lines.push("If these components exist in Storyblok, run `storyblok components pull` to sync them locally.");
|
|
8474
|
+
lines.push("Otherwise, create them in Storyblok first, or remove the references from the affected stories.");
|
|
8475
|
+
}
|
|
8476
|
+
if (issues.driftByComponent.size > 0) {
|
|
8477
|
+
lines.push("");
|
|
8478
|
+
lines.push("Fields not declared in local schemas:");
|
|
8479
|
+
const sortedComponents = [...issues.driftByComponent.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
8480
|
+
for (const [component, fieldMap] of sortedComponents) {
|
|
8481
|
+
const sortedFields = [...fieldMap.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
8482
|
+
for (const [field, stories] of sortedFields) {
|
|
8483
|
+
lines.push(` - ${component}.${field} (in stories: ${formatStoryList(stories)})`);
|
|
8484
|
+
}
|
|
8485
|
+
}
|
|
8486
|
+
lines.push("");
|
|
8487
|
+
lines.push("These fields will be lost when the stories are pushed. To fix, either:");
|
|
8488
|
+
lines.push(" - Add the field to the component in Storyblok, then run `storyblok components pull`");
|
|
8489
|
+
lines.push(" - Or remove the field from the affected story JSON files");
|
|
8490
|
+
}
|
|
8491
|
+
return lines.join("\n");
|
|
8492
|
+
};
|
|
8493
|
+
const hasSchemaIssues = (issues) => issues.missingSchemas.size > 0 || issues.driftByComponent.size > 0;
|
|
8494
|
+
|
|
8397
8495
|
const PROGRESS_BAR_PADDING = 23;
|
|
8398
8496
|
const upsertAssetFoldersPipeline = async ({
|
|
8399
8497
|
directoryPath,
|
|
@@ -8443,7 +8541,7 @@ const upsertAssetsPipeline = async ({
|
|
|
8443
8541
|
ui
|
|
8444
8542
|
}) => {
|
|
8445
8543
|
const assetProgress = ui.createProgressBar({ title: "Assets...".padEnd(PROGRESS_BAR_PADDING) });
|
|
8446
|
-
const summary = { total: 0, succeeded: 0, failed: 0
|
|
8544
|
+
const summary = { total: 0, succeeded: 0, failed: 0 };
|
|
8447
8545
|
const steps = [];
|
|
8448
8546
|
if (assetBinaryPath && assetData) {
|
|
8449
8547
|
summary.total = 1;
|
|
@@ -8489,20 +8587,6 @@ const upsertAssetsPipeline = async ({
|
|
|
8489
8587
|
summary.succeeded += 1;
|
|
8490
8588
|
logger.info("Uploaded asset", { assetId: remoteAsset.id });
|
|
8491
8589
|
},
|
|
8492
|
-
onAssetSkipped: (localAssetResult, remoteAsset) => {
|
|
8493
|
-
if ("id" in localAssetResult && localAssetResult.id) {
|
|
8494
|
-
maps.assets.set(localAssetResult.id, {
|
|
8495
|
-
old: localAssetResult,
|
|
8496
|
-
new: {
|
|
8497
|
-
id: remoteAsset.id,
|
|
8498
|
-
filename: remoteAsset.filename,
|
|
8499
|
-
meta_data: remoteAsset.meta_data
|
|
8500
|
-
}
|
|
8501
|
-
});
|
|
8502
|
-
}
|
|
8503
|
-
summary.skipped += 1;
|
|
8504
|
-
logger.debug("Skipped asset (unchanged)", { assetId: remoteAsset.id });
|
|
8505
|
-
},
|
|
8506
8590
|
onAssetError: (error, asset) => {
|
|
8507
8591
|
summary.failed += 1;
|
|
8508
8592
|
logOnlyError(error, { assetId: asset.id });
|
|
@@ -8601,7 +8685,8 @@ const mapAssetReferencesInStoriesPipeline = async ({
|
|
|
8601
8685
|
onIncrement() {
|
|
8602
8686
|
processProgress.increment();
|
|
8603
8687
|
},
|
|
8604
|
-
onStorySuccess(localStory
|
|
8688
|
+
onStorySuccess(localStory) {
|
|
8689
|
+
const { missingSchemas } = validateStoryAgainstSchemas(localStory, schemas);
|
|
8605
8690
|
warnAboutMissingSchemas(missingSchemas, localStory);
|
|
8606
8691
|
logger.info("Processed story", { storyId: localStory.uuid });
|
|
8607
8692
|
summaries.storyProcessResults.succeeded += 1;
|
|
@@ -8634,7 +8719,7 @@ const mapAssetReferencesInStoriesPipeline = async ({
|
|
|
8634
8719
|
return Object.entries(summaries);
|
|
8635
8720
|
};
|
|
8636
8721
|
|
|
8637
|
-
const pushCmd$1 = assetsCommand.command("push").argument("[asset]", "path or URL of a single asset to push").option("-s, --space <space>", "space ID").option("-f, --from <from>", "source space id").option("--data <data>", "inline asset data as JSON").option("--short-filename <short-filename>", "override the asset filename").option("--folder <folderId>", "destination asset folder ID").option("--cleanup", "delete local assets and metadata after a successful push (note: does not cleanup manifests)").option("--update-stories", "update file references in stories if necessary", false).option("
|
|
8722
|
+
const pushCmd$1 = assetsCommand.command("push").argument("[asset]", "path or URL of a single asset to push").option("-s, --space <space>", "space ID").option("-f, --from <from>", "source space id").option("--data <data>", "inline asset data as JSON").option("--short-filename <short-filename>", "override the asset filename").option("--folder <folderId>", "destination asset folder ID").option("--cleanup", "delete local assets and metadata after a successful push (note: does not cleanup manifests)").option("--update-stories", "update file references in stories if necessary", false).option("-d, --dry-run", "Preview changes without applying them to Storyblok").description(`Push local assets to a Storyblok space.`);
|
|
8638
8723
|
pushCmd$1.action(async (assetInput, options, command) => {
|
|
8639
8724
|
const ui = getUI();
|
|
8640
8725
|
const logger = getLogger();
|
|
@@ -8648,7 +8733,6 @@ pushCmd$1.action(async (assetInput, options, command) => {
|
|
|
8648
8733
|
}
|
|
8649
8734
|
const { space: targetSpace, path: basePath, verbose } = command.optsWithGlobals();
|
|
8650
8735
|
const fromSpace = options.from || targetSpace;
|
|
8651
|
-
const assetToken = options.assetToken;
|
|
8652
8736
|
const { state } = session();
|
|
8653
8737
|
if (!requireAuthentication(state, verbose)) {
|
|
8654
8738
|
process.exitCode = 2;
|
|
@@ -8659,7 +8743,6 @@ pushCmd$1.action(async (assetInput, options, command) => {
|
|
|
8659
8743
|
process.exitCode = 2;
|
|
8660
8744
|
return;
|
|
8661
8745
|
}
|
|
8662
|
-
const { region } = state;
|
|
8663
8746
|
const summaries = [];
|
|
8664
8747
|
let fatalError = false;
|
|
8665
8748
|
const manifestFile = join(resolveCommandPath(directories.assets, fromSpace, basePath), "manifest.jsonl");
|
|
@@ -8706,10 +8789,6 @@ pushCmd$1.action(async (assetInput, options, command) => {
|
|
|
8706
8789
|
const createAssetTransport = options.dryRun ? async (asset) => asset : makeCreateAssetAPITransport({ spaceId: targetSpace });
|
|
8707
8790
|
const updateAssetTransport = options.dryRun ? async () => {
|
|
8708
8791
|
} : makeUpdateAssetAPITransport({ spaceId: targetSpace });
|
|
8709
|
-
const downloadAssetFileTransport = makeDownloadAssetFileTransport({
|
|
8710
|
-
assetToken,
|
|
8711
|
-
region
|
|
8712
|
-
});
|
|
8713
8792
|
const assetManifestTransport = options.dryRun ? () => Promise.resolve() : makeAppendAssetManifestFSTransport({ manifestFile });
|
|
8714
8793
|
const cleanupAssetTransport = options.cleanup && !options.dryRun ? makeCleanupAssetFSTransport() : () => Promise.resolve();
|
|
8715
8794
|
summaries.push(...await upsertAssetsPipeline({
|
|
@@ -8722,7 +8801,6 @@ pushCmd$1.action(async (assetInput, options, command) => {
|
|
|
8722
8801
|
getAsset: getAssetTransport,
|
|
8723
8802
|
createAsset: createAssetTransport,
|
|
8724
8803
|
updateAsset: updateAssetTransport,
|
|
8725
|
-
downloadAssetFile: downloadAssetFileTransport,
|
|
8726
8804
|
appendAssetManifest: assetManifestTransport,
|
|
8727
8805
|
cleanupAsset: cleanupAssetTransport
|
|
8728
8806
|
},
|
|
@@ -8757,12 +8835,11 @@ pushCmd$1.action(async (assetInput, options, command) => {
|
|
|
8757
8835
|
logger.info("Pushing assets finished", { summary });
|
|
8758
8836
|
const assetsTotal = summary.assetResults?.total ?? 0;
|
|
8759
8837
|
const assetsSucceeded = summary.assetResults?.succeeded ?? 0;
|
|
8760
|
-
const assetsSkipped = summary.assetResults?.skipped ?? 0;
|
|
8761
8838
|
const assetsFailed = summary.assetResults?.failed ?? 0;
|
|
8762
8839
|
ui.info(`Push results: ${assetsTotal} processed, ${assetsFailed} assets failed`);
|
|
8763
8840
|
ui.list([
|
|
8764
8841
|
`Folders: ${summary.assetFolderResults?.succeeded ?? 0}/${summary.assetFolderResults?.total ?? 0} succeeded, ${summary.assetFolderResults?.failed ?? 0} failed.`,
|
|
8765
|
-
`Assets: ${assetsSucceeded}/${assetsTotal} succeeded, ${
|
|
8842
|
+
`Assets: ${assetsSucceeded}/${assetsTotal} succeeded, ${assetsFailed} failed.`
|
|
8766
8843
|
]);
|
|
8767
8844
|
for (const [name, reportSummary] of summaries) {
|
|
8768
8845
|
reporter.addSummary(name, reportSummary);
|
|
@@ -8914,32 +8991,6 @@ pushCmd.action(async (options, command) => {
|
|
|
8914
8991
|
return;
|
|
8915
8992
|
}
|
|
8916
8993
|
const pendingWarnings = [];
|
|
8917
|
-
const warnedPlugins = /* @__PURE__ */ new Set();
|
|
8918
|
-
const warnAboutCustomPlugins = (fields, story) => {
|
|
8919
|
-
for (const field of fields) {
|
|
8920
|
-
if (field.type === "custom" && typeof field.field_type === "string") {
|
|
8921
|
-
if (warnedPlugins.has(field.field_type)) {
|
|
8922
|
-
continue;
|
|
8923
|
-
}
|
|
8924
|
-
warnedPlugins.add(field.field_type);
|
|
8925
|
-
const message = `The custom plugin "${field.field_type}" may contain references that require manual updates.`;
|
|
8926
|
-
pendingWarnings.push(message);
|
|
8927
|
-
logger.warn(message, { storyId: story.uuid });
|
|
8928
|
-
}
|
|
8929
|
-
}
|
|
8930
|
-
};
|
|
8931
|
-
const missingSchemaWarnings = /* @__PURE__ */ new Set();
|
|
8932
|
-
const warnAboutMissingSchemas = (missingSchemas, story) => {
|
|
8933
|
-
for (const schemaName of missingSchemas) {
|
|
8934
|
-
if (missingSchemaWarnings.has(schemaName)) {
|
|
8935
|
-
continue;
|
|
8936
|
-
}
|
|
8937
|
-
const message = `The component "${schemaName}" was not found. Please run \`storyblok components pull\` to fetch the latest components.`;
|
|
8938
|
-
pendingWarnings.push(message);
|
|
8939
|
-
logger.warn(message, { storyId: story.uuid });
|
|
8940
|
-
missingSchemaWarnings.add(schemaName);
|
|
8941
|
-
}
|
|
8942
|
-
};
|
|
8943
8994
|
const summary = {
|
|
8944
8995
|
creationResults: { total: 0, succeeded: 0, skipped: 0, failed: 0 },
|
|
8945
8996
|
processResults: { total: 0, succeeded: 0, failed: 0 },
|
|
@@ -8960,13 +9011,49 @@ pushCmd.action(async (options, command) => {
|
|
|
8960
9011
|
logger.error(message);
|
|
8961
9012
|
return;
|
|
8962
9013
|
}
|
|
9014
|
+
const storiesDirectoryPath = resolveCommandPath(directories.stories, fromSpace, basePath);
|
|
9015
|
+
const schemaIssues = await collectSchemaIssues({ directoryPath: storiesDirectoryPath, schemas });
|
|
9016
|
+
if (hasSchemaIssues(schemaIssues)) {
|
|
9017
|
+
const message = formatSchemaIssues(schemaIssues);
|
|
9018
|
+
ui.error(message);
|
|
9019
|
+
logger.error(message);
|
|
9020
|
+
const total = Math.max(schemaIssues.total, 1);
|
|
9021
|
+
summary.creationResults.total = total;
|
|
9022
|
+
summary.creationResults.failed = total;
|
|
9023
|
+
return;
|
|
9024
|
+
}
|
|
9025
|
+
const warnAboutCustomPlugins = (story) => {
|
|
9026
|
+
const warned = /* @__PURE__ */ new Set();
|
|
9027
|
+
const visit = (node) => {
|
|
9028
|
+
if (Array.isArray(node)) {
|
|
9029
|
+
for (const item of node) {
|
|
9030
|
+
visit(item);
|
|
9031
|
+
}
|
|
9032
|
+
return;
|
|
9033
|
+
}
|
|
9034
|
+
if (!node || typeof node !== "object") {
|
|
9035
|
+
return;
|
|
9036
|
+
}
|
|
9037
|
+
const obj = node;
|
|
9038
|
+
const plugin = obj.plugin;
|
|
9039
|
+
if (typeof plugin === "string" && !warned.has(plugin)) {
|
|
9040
|
+
warned.add(plugin);
|
|
9041
|
+
const message = `The custom plugin "${plugin}" may contain references that require manual updates.`;
|
|
9042
|
+
pendingWarnings.push(message);
|
|
9043
|
+
logger.warn(message, { storyId: story.uuid });
|
|
9044
|
+
}
|
|
9045
|
+
for (const value of Object.values(obj)) {
|
|
9046
|
+
visit(value);
|
|
9047
|
+
}
|
|
9048
|
+
};
|
|
9049
|
+
visit(story.content);
|
|
9050
|
+
};
|
|
8963
9051
|
const fetchProgress = ui.createProgressBar({ title: "Matching Stories...".padEnd(21) });
|
|
8964
9052
|
const existingTargetStories = await prefetchTargetStories(space, {
|
|
8965
9053
|
onTotal: (total) => fetchProgress.setTotal(total),
|
|
8966
9054
|
onIncrement: (count) => fetchProgress.increment(count)
|
|
8967
9055
|
});
|
|
8968
9056
|
fetchProgress.stop();
|
|
8969
|
-
const storiesDirectoryPath = resolveCommandPath(directories.stories, fromSpace, basePath);
|
|
8970
9057
|
const scanProgress = ui.createProgressBar({ title: "Scanning Stories...".padEnd(21) });
|
|
8971
9058
|
const storyIndex = await scanLocalStoryIndex({
|
|
8972
9059
|
directoryPath: storiesDirectoryPath,
|
|
@@ -9070,9 +9157,8 @@ pushCmd.action(async (options, command) => {
|
|
|
9070
9157
|
onIncrement() {
|
|
9071
9158
|
processProgress.increment();
|
|
9072
9159
|
},
|
|
9073
|
-
onStorySuccess(localStory
|
|
9074
|
-
warnAboutCustomPlugins(
|
|
9075
|
-
warnAboutMissingSchemas(missingSchemas, localStory);
|
|
9160
|
+
onStorySuccess(localStory) {
|
|
9161
|
+
warnAboutCustomPlugins(localStory);
|
|
9076
9162
|
logger.info("Processed story", { storyId: localStory.uuid });
|
|
9077
9163
|
summary.processResults.succeeded += 1;
|
|
9078
9164
|
},
|