sb-mig 6.0.0-beta.4 → 6.0.0-beta.5

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.
@@ -1,3 +1,4 @@
1
+ import type { PublishLanguagesOption } from "../stories/stories.types.js";
1
2
  import type { RequestBaseConfig } from "../utils/request.js";
2
3
  import { type MigrationComponentAliasesByMigration, type MigrationComponentOverridesByMigration } from "./migration-component-scope.js";
3
4
  import { type PreparedMigrationValidator } from "./migration-validation.js";
@@ -44,6 +45,7 @@ interface MigrateItems {
44
45
  };
45
46
  dryRun?: boolean;
46
47
  publish?: boolean;
48
+ publishLanguages?: PublishLanguagesOption;
47
49
  fromFilePath?: string;
48
50
  fileName?: string;
49
51
  preparedMigrationConfigs?: PreparedMigrationConfig[];
@@ -68,8 +70,8 @@ export declare const runMigrationPipelineInMemory: ({ itemType, itemsToMigrate,
68
70
  itemsToMigrate: any[];
69
71
  preparedMigrationConfigs: PreparedMigrationConfig[];
70
72
  }) => MigrationPipelineResult;
71
- export declare const migrateAllComponentsDataInStories: ({ itemType, migrationConfig, migrateFrom, from, to, filters, dryRun, publish, fromFilePath, fileName, migrationComponentAliases, migrationComponentOverrides, }: Omit<MigrateItems, "componentsToMigrate" | "preparedMigrationConfigs">, config: RequestBaseConfig) => Promise<void>;
72
- export declare const doTheMigration: ({ itemType, from, itemsToMigrate, migrationConfig, migrationConfigs, to, dryRun, publish, migrateFrom, fromFilePath, fileName, }: {
73
+ export declare const migrateAllComponentsDataInStories: ({ itemType, migrationConfig, migrateFrom, from, to, filters, dryRun, publish, publishLanguages, fromFilePath, fileName, migrationComponentAliases, migrationComponentOverrides, }: Omit<MigrateItems, "componentsToMigrate" | "preparedMigrationConfigs">, config: RequestBaseConfig) => Promise<void>;
74
+ export declare const doTheMigration: ({ itemType, from, itemsToMigrate, migrationConfig, migrationConfigs, to, dryRun, publish, publishLanguages, migrateFrom, fromFilePath, fileName, }: {
73
75
  itemType?: "story" | "preset";
74
76
  from: string;
75
77
  itemsToMigrate: any[];
@@ -78,9 +80,10 @@ export declare const doTheMigration: ({ itemType, from, itemsToMigrate, migratio
78
80
  to: string;
79
81
  dryRun?: boolean;
80
82
  publish?: boolean;
83
+ publishLanguages?: PublishLanguagesOption;
81
84
  migrateFrom: MigrateFrom;
82
85
  fromFilePath?: string;
83
86
  fileName?: string;
84
87
  }, config: RequestBaseConfig) => Promise<void>;
85
- export declare const migrateProvidedComponentsDataInStories: ({ itemType, migrationConfig, migrateFrom, from, to, componentsToMigrate, filters, dryRun, publish, fromFilePath, fileName, preparedMigrationConfigs, migrationComponentAliases, migrationComponentOverrides, }: MigrateItems, config: RequestBaseConfig) => Promise<void>;
88
+ export declare const migrateProvidedComponentsDataInStories: ({ itemType, migrationConfig, migrateFrom, from, to, componentsToMigrate, filters, dryRun, publish, publishLanguages, fromFilePath, fileName, preparedMigrationConfigs, migrationComponentAliases, migrationComponentOverrides, }: MigrateItems, config: RequestBaseConfig) => Promise<void>;
86
89
  export {};
@@ -296,7 +296,7 @@ export const runMigrationPipelineInMemory = ({ itemType, itemsToMigrate, prepare
296
296
  totalItems: workingItems.length,
297
297
  };
298
298
  };
299
- const savePipelineSummary = async ({ artifactBaseName, useDatestamp, from, itemType, dryRun, publish, migrateFrom, fromFilePath, pipelineResult, }, config) => {
299
+ const savePipelineSummary = async ({ artifactBaseName, useDatestamp, from, itemType, dryRun, publish, publishLanguages, migrateFrom, fromFilePath, pipelineResult, }, config) => {
300
300
  await createAndSaveToFile({
301
301
  datestamp: useDatestamp,
302
302
  ext: "json",
@@ -310,6 +310,11 @@ const savePipelineSummary = async ({ artifactBaseName, useDatestamp, from, itemT
310
310
  fromFilePath: fromFilePath || null,
311
311
  },
312
312
  writeMode: itemType === "story" && publish ? "publish" : "save",
313
+ publishLanguages: itemType === "story" && publish
314
+ ? {
315
+ requested: publishLanguages,
316
+ }
317
+ : null,
313
318
  totalItems: pipelineResult.totalItems,
314
319
  totalChangedItems: pipelineResult.changedItems.length,
315
320
  steps: pipelineResult.stepReports,
@@ -380,7 +385,7 @@ const loadItemsToMigrate = async ({ itemType, migrateFrom, from, filters, fromFi
380
385
  spaceId: from,
381
386
  });
382
387
  };
383
- export const migrateAllComponentsDataInStories = async ({ itemType, migrationConfig, migrateFrom, from, to, filters, dryRun, publish, fromFilePath, fileName, migrationComponentAliases, migrationComponentOverrides, }, config) => {
388
+ export const migrateAllComponentsDataInStories = async ({ itemType, migrationConfig, migrateFrom, from, to, filters, dryRun, publish, publishLanguages, fromFilePath, fileName, migrationComponentAliases, migrationComponentOverrides, }, config) => {
384
389
  Logger.warning(`Trying to migrate all ${itemType} from ${migrateFrom}, ${from} to ${to}...`);
385
390
  const preparedMigrationConfigs = prepareMigrationConfigs({
386
391
  migrationConfig,
@@ -400,12 +405,13 @@ export const migrateAllComponentsDataInStories = async ({ itemType, migrationCon
400
405
  filters,
401
406
  dryRun,
402
407
  publish,
408
+ publishLanguages,
403
409
  fromFilePath,
404
410
  fileName,
405
411
  preparedMigrationConfigs,
406
412
  }, config);
407
413
  };
408
- export const doTheMigration = async ({ itemType = "story", from, itemsToMigrate, migrationConfig, migrationConfigs, to, dryRun, publish, migrateFrom, fromFilePath, fileName, }, config) => {
414
+ export const doTheMigration = async ({ itemType = "story", from, itemsToMigrate, migrationConfig, migrationConfigs, to, dryRun, publish, publishLanguages, migrateFrom, fromFilePath, fileName, }, config) => {
409
415
  const preparedMigrationConfigs = migrationConfigs ||
410
416
  prepareMigrationConfigs({
411
417
  migrationConfig: migrationConfig || [],
@@ -476,6 +482,7 @@ export const doTheMigration = async ({ itemType = "story", from, itemsToMigrate,
476
482
  itemType,
477
483
  dryRun,
478
484
  publish,
485
+ publishLanguages,
479
486
  migrateFrom,
480
487
  fromFilePath,
481
488
  pipelineResult,
@@ -493,11 +500,22 @@ export const doTheMigration = async ({ itemType = "story", from, itemsToMigrate,
493
500
  return;
494
501
  }
495
502
  let writeResults = [];
503
+ let resolvedPublishLanguages;
496
504
  if (itemType === "story") {
505
+ if (publish && publishLanguages !== undefined) {
506
+ resolvedPublishLanguages =
507
+ await managementApi.stories.resolvePublishLanguageCodes(publishLanguages, {
508
+ ...config,
509
+ spaceId: to,
510
+ });
511
+ }
497
512
  writeResults = await managementApi.stories.updateStories({
498
513
  stories: pipelineResult.changedItems,
499
514
  spaceId: to,
500
- options: { publish: Boolean(publish) },
515
+ options: {
516
+ publish: Boolean(publish),
517
+ publishLanguages: resolvedPublishLanguages,
518
+ },
501
519
  }, config);
502
520
  }
503
521
  else if (itemType === "preset") {
@@ -517,6 +535,8 @@ export const doTheMigration = async ({ itemType = "story", from, itemsToMigrate,
517
535
  itemType,
518
536
  dryRun,
519
537
  publish,
538
+ publishLanguages,
539
+ resolvedPublishLanguages,
520
540
  migrateFrom,
521
541
  fromFilePath,
522
542
  pipelineResult,
@@ -550,7 +570,7 @@ const saveBackupToFile = async ({ itemType, res, folder, filename }, config) =>
550
570
  res: res,
551
571
  }, config);
552
572
  };
553
- export const migrateProvidedComponentsDataInStories = async ({ itemType, migrationConfig, migrateFrom, from, to, componentsToMigrate, filters, dryRun, publish, fromFilePath, fileName, preparedMigrationConfigs, migrationComponentAliases, migrationComponentOverrides, }, config) => {
573
+ export const migrateProvidedComponentsDataInStories = async ({ itemType, migrationConfig, migrateFrom, from, to, componentsToMigrate, filters, dryRun, publish, publishLanguages, fromFilePath, fileName, preparedMigrationConfigs, migrationComponentAliases, migrationComponentOverrides, }, config) => {
554
574
  const resolvedMigrationConfigs = preparedMigrationConfigs ||
555
575
  prepareMigrationConfigs({
556
576
  migrationConfig,
@@ -586,6 +606,7 @@ export const migrateProvidedComponentsDataInStories = async ({ itemType, migrati
586
606
  to,
587
607
  dryRun,
588
608
  publish,
609
+ publishLanguages,
589
610
  migrateFrom,
590
611
  fromFilePath,
591
612
  fileName,
@@ -1,7 +1,8 @@
1
1
  import type { MigrateFrom, MigrationPipelineResult } from "./component-data-migration.js";
2
2
  import type { MutationWriteResult, MutationWriteSummary } from "./write-summary.js";
3
+ import type { PublishLanguagesOption } from "../stories/stories.types.js";
3
4
  import type { RequestBaseConfig } from "../utils/request.js";
4
- type MigrationRunLogEvent = "update_success" | "update_failed" | "migration_write_summary";
5
+ type MigrationRunLogEvent = "update_success" | "update_failed" | "publish_success" | "publish_failed" | "migration_write_summary";
5
6
  export interface MigrationRunLogRecord {
6
7
  timestamp: string;
7
8
  event: MigrationRunLogEvent;
@@ -16,6 +17,10 @@ export interface MigrationRunLogRecord {
16
17
  to: string;
17
18
  };
18
19
  writeMode: "publish" | "save";
20
+ publishLanguages?: {
21
+ requested?: PublishLanguagesOption;
22
+ resolved?: string[];
23
+ };
19
24
  dryRun: boolean;
20
25
  migrationConfigs: string[];
21
26
  totalItems: number;
@@ -31,6 +36,7 @@ export interface MigrationRunLogRecord {
31
36
  spaceId?: string;
32
37
  status?: number | string;
33
38
  response?: string | null;
39
+ stage?: "update" | "publish";
34
40
  }>;
35
41
  };
36
42
  item?: {
@@ -42,6 +48,7 @@ export interface MigrationRunLogRecord {
42
48
  };
43
49
  status?: number | string | null;
44
50
  response?: string | null;
51
+ stage?: "update" | "publish";
45
52
  error?: unknown;
46
53
  }
47
54
  interface SaveMigrationRunLogArgs {
@@ -52,13 +59,15 @@ interface SaveMigrationRunLogArgs {
52
59
  itemType: "story" | "preset";
53
60
  dryRun?: boolean;
54
61
  publish?: boolean;
62
+ publishLanguages?: PublishLanguagesOption;
63
+ resolvedPublishLanguages?: string[];
55
64
  migrateFrom: MigrateFrom;
56
65
  fromFilePath?: string;
57
66
  pipelineResult: MigrationPipelineResult;
58
67
  writeResults: PromiseSettledResult<MutationWriteResult>[];
59
68
  writeSummary: MutationWriteSummary;
60
69
  }
61
- export declare const buildMigrationRunLogRecords: ({ from, to, itemType, dryRun, publish, migrateFrom, fromFilePath, pipelineResult, writeResults, writeSummary, }: Omit<SaveMigrationRunLogArgs, "artifactBaseName" | "useDatestamp">) => MigrationRunLogRecord[];
70
+ export declare const buildMigrationRunLogRecords: ({ from, to, itemType, dryRun, publish, publishLanguages, resolvedPublishLanguages, migrateFrom, fromFilePath, pipelineResult, writeResults, writeSummary, }: Omit<SaveMigrationRunLogArgs, "artifactBaseName" | "useDatestamp">) => MigrationRunLogRecord[];
62
71
  export declare const recordsToJsonl: (records: MigrationRunLogRecord[]) => string;
63
72
  export declare const saveMigrationRunLog: (args: SaveMigrationRunLogArgs, config: RequestBaseConfig) => Promise<void>;
64
73
  export {};
@@ -31,7 +31,7 @@ const resolveWriteResultValue = (result) => {
31
31
  error: result.reason,
32
32
  };
33
33
  };
34
- export const buildMigrationRunLogRecords = ({ from, to, itemType, dryRun, publish, migrateFrom, fromFilePath, pipelineResult, writeResults, writeSummary, }) => {
34
+ export const buildMigrationRunLogRecords = ({ from, to, itemType, dryRun, publish, publishLanguages, resolvedPublishLanguages, migrateFrom, fromFilePath, pipelineResult, writeResults, writeSummary, }) => {
35
35
  const timestamp = new Date().toISOString();
36
36
  const runId = `${itemType}-${timestamp}`;
37
37
  const baseRecord = {
@@ -47,6 +47,14 @@ export const buildMigrationRunLogRecords = ({ from, to, itemType, dryRun, publis
47
47
  to,
48
48
  },
49
49
  writeMode: itemType === "story" && publish ? "publish" : "save",
50
+ ...(publish
51
+ ? {
52
+ publishLanguages: {
53
+ requested: publishLanguages,
54
+ resolved: resolvedPublishLanguages,
55
+ },
56
+ }
57
+ : {}),
50
58
  dryRun: Boolean(dryRun),
51
59
  migrationConfigs: pipelineResult.stepReports.map((step) => step.migrationConfig),
52
60
  totalItems: pipelineResult.totalItems,
@@ -55,9 +63,14 @@ export const buildMigrationRunLogRecords = ({ from, to, itemType, dryRun, publis
55
63
  const updateRecords = writeResults.map((result, index) => {
56
64
  const value = resolveWriteResultValue(result);
57
65
  const changedItem = resolveChangedItemPayload(pipelineResult.changedItems[index]);
66
+ const stage = value.stage || "update";
58
67
  const event = value.ok
59
- ? "update_success"
60
- : "update_failed";
68
+ ? stage === "publish"
69
+ ? "publish_success"
70
+ : "update_success"
71
+ : stage === "publish"
72
+ ? "publish_failed"
73
+ : "update_failed";
61
74
  return {
62
75
  ...baseRecord,
63
76
  event,
@@ -72,6 +85,7 @@ export const buildMigrationRunLogRecords = ({ from, to, itemType, dryRun, publis
72
85
  },
73
86
  status: value.status || null,
74
87
  response: value.response || null,
88
+ stage,
75
89
  ...(value.ok ? {} : { error: serializeError(value.error) }),
76
90
  };
77
91
  });
@@ -89,6 +103,7 @@ export const buildMigrationRunLogRecords = ({ from, to, itemType, dryRun, publis
89
103
  spaceId: item.spaceId,
90
104
  status: item.status,
91
105
  response: item.response || null,
106
+ stage: item.stage,
92
107
  })),
93
108
  },
94
109
  };
@@ -1,11 +1,13 @@
1
1
  export interface MutationWriteResult {
2
2
  ok: boolean;
3
+ stage?: "update" | "publish";
3
4
  id?: number | string;
4
5
  name?: string;
5
6
  slug?: string;
6
7
  spaceId?: string;
7
8
  status?: number | string;
8
9
  response?: string | null;
10
+ publishLanguages?: string[];
9
11
  error?: unknown;
10
12
  }
11
13
  export interface MutationWriteSummary {
@@ -58,6 +58,19 @@ export declare const managementApi: {
58
58
  removeStory: import("./stories/stories.types.js").RemoveStory;
59
59
  getStoryBySlug: import("./stories/stories.types.js").GetStoryBySlug;
60
60
  updateStories: import("./stories/stories.types.js").UpdateStories;
61
+ publishStoryLanguages: ({ storyId, story, languages, }: {
62
+ storyId: string | number;
63
+ story?: Record<string, any>;
64
+ languages: string[];
65
+ }, config: {
66
+ spaceId: string;
67
+ sbApi: any;
68
+ }) => Promise<any>;
69
+ parsePublishLanguagesOption: (publishLanguages?: string) => import("./stories/stories.types.js").PublishLanguagesOption;
70
+ resolvePublishLanguageCodes: (publishLanguages: import("./stories/stories.types.js").PublishLanguagesOption | undefined, config: {
71
+ spaceId: string;
72
+ sbApi: any;
73
+ }) => Promise<string[]>;
61
74
  getAllStories: import("./stories/stories.types.js").GetAllStories;
62
75
  removeAllStories: import("./stories/stories.types.js").RemoveAllStories;
63
76
  upsertStory: import("./stories/stories.types.js").UpsertStory;
@@ -1,2 +1,2 @@
1
- export { createStory, updateStory, getStoryById, removeStory, getStoryBySlug, updateStories, getAllStories, removeAllStories, upsertStory, } from "./stories.js";
1
+ export { createStory, updateStory, getStoryById, removeStory, getStoryBySlug, updateStories, publishStoryLanguages, parsePublishLanguagesOption, resolvePublishLanguageCodes, getAllStories, removeAllStories, upsertStory, } from "./stories.js";
2
2
  export { backupStories } from "./backup.js";
@@ -1,2 +1,2 @@
1
- export { createStory, updateStory, getStoryById, removeStory, getStoryBySlug, updateStories, getAllStories, removeAllStories, upsertStory, } from "./stories.js";
1
+ export { createStory, updateStory, getStoryById, removeStory, getStoryBySlug, updateStories, publishStoryLanguages, parsePublishLanguagesOption, resolvePublishLanguageCodes, getAllStories, removeAllStories, upsertStory, } from "./stories.js";
2
2
  export { backupStories } from "./backup.js";
@@ -1,4 +1,9 @@
1
- import type { GetAllStories, GetStoryById, RemoveStory, CreateStory, UpdateStory, UpdateStories, RemoveAllStories, UpsertStory, DeepUpsertStory, GetStoryBySlug } from "./stories.types.js";
1
+ import type { GetAllStories, GetStoryById, RemoveStory, CreateStory, UpdateStory, UpdateStories, RemoveAllStories, UpsertStory, DeepUpsertStory, GetStoryBySlug, PublishLanguagesOption } from "./stories.types.js";
2
+ export declare const parsePublishLanguagesOption: (publishLanguages?: string) => PublishLanguagesOption;
3
+ export declare const resolvePublishLanguageCodes: (publishLanguages: PublishLanguagesOption | undefined, config: {
4
+ spaceId: string;
5
+ sbApi: any;
6
+ }) => Promise<string[]>;
2
7
  export declare const removeStory: RemoveStory;
3
8
  export declare const removeAllStories: RemoveAllStories;
4
9
  export declare const getAllStories: GetAllStories;
@@ -6,6 +11,14 @@ export declare const getStoryById: GetStoryById;
6
11
  export declare const getStoryBySlug: GetStoryBySlug;
7
12
  export declare const createStory: CreateStory;
8
13
  export declare const updateStory: UpdateStory;
14
+ export declare const publishStoryLanguages: ({ storyId, story, languages, }: {
15
+ storyId: string | number;
16
+ story?: Record<string, any>;
17
+ languages: string[];
18
+ }, config: {
19
+ spaceId: string;
20
+ sbApi: any;
21
+ }) => Promise<any>;
9
22
  export declare const updateStories: UpdateStories;
10
23
  export declare const upsertStory: UpsertStory;
11
24
  export declare const deepUpsertStory: DeepUpsertStory;
@@ -3,6 +3,81 @@ import Logger from "../../utils/logger.js";
3
3
  import { notNullish } from "../../utils/object-utils.js";
4
4
  import { getAllItemsWithPagination } from "../utils/request.js";
5
5
  const resolveStoryLabel = (content, storyId) => content?.full_slug || content?.slug || content?.name || String(storyId);
6
+ const DEFAULT_PUBLISH_LANGUAGE = "[default]";
7
+ const isDefaultLanguageToken = (language) => language.toLowerCase() === "default" ||
8
+ language === DEFAULT_PUBLISH_LANGUAGE;
9
+ const normalizePublishLanguageCodes = (languages) => {
10
+ const normalized = languages.map((language) => {
11
+ const trimmed = language.trim();
12
+ return isDefaultLanguageToken(trimmed)
13
+ ? DEFAULT_PUBLISH_LANGUAGE
14
+ : trimmed;
15
+ });
16
+ const cleanLanguages = normalized.filter((language) => language.length > 0);
17
+ if (cleanLanguages.length === 0) {
18
+ throw new Error("Publish languages cannot be empty.");
19
+ }
20
+ return Array.from(new Set(cleanLanguages));
21
+ };
22
+ export const parsePublishLanguagesOption = (publishLanguages) => {
23
+ if (!publishLanguages) {
24
+ return "default";
25
+ }
26
+ const trimmed = publishLanguages.trim();
27
+ if (trimmed.length === 0) {
28
+ return "default";
29
+ }
30
+ if (trimmed.toLowerCase() === "all") {
31
+ return "all";
32
+ }
33
+ const languages = normalizePublishLanguageCodes(trimmed.split(","));
34
+ if (languages.length === 1 && languages[0] === DEFAULT_PUBLISH_LANGUAGE) {
35
+ return "default";
36
+ }
37
+ return languages;
38
+ };
39
+ const readSpaceLanguageCodes = (spaceResponse) => {
40
+ const space = spaceResponse?.data?.space || spaceResponse?.space || {};
41
+ const languageSources = [
42
+ space.languages,
43
+ space.language_codes,
44
+ space.options?.languages,
45
+ ];
46
+ for (const source of languageSources) {
47
+ if (!Array.isArray(source)) {
48
+ continue;
49
+ }
50
+ return source
51
+ .map((language) => {
52
+ if (typeof language === "string") {
53
+ return language;
54
+ }
55
+ if (typeof language?.code === "string") {
56
+ return language.code;
57
+ }
58
+ return "";
59
+ })
60
+ .filter((language) => language.trim().length > 0);
61
+ }
62
+ return [];
63
+ };
64
+ export const resolvePublishLanguageCodes = async (publishLanguages, config) => {
65
+ if (!publishLanguages || publishLanguages === "default") {
66
+ return [DEFAULT_PUBLISH_LANGUAGE];
67
+ }
68
+ if (Array.isArray(publishLanguages)) {
69
+ return normalizePublishLanguageCodes(publishLanguages);
70
+ }
71
+ const spaceResponse = await config.sbApi.get(`spaces/${config.spaceId}`);
72
+ const spaceLanguageCodes = readSpaceLanguageCodes(spaceResponse);
73
+ if (spaceLanguageCodes.length === 0) {
74
+ Logger.warning(`No configured Storyblok languages were found for space '${config.spaceId}'. Publishing only ${DEFAULT_PUBLISH_LANGUAGE}.`);
75
+ }
76
+ return normalizePublishLanguageCodes([
77
+ DEFAULT_PUBLISH_LANGUAGE,
78
+ ...spaceLanguageCodes,
79
+ ]);
80
+ };
6
81
  const resolveStoryblokErrorResponse = (err) => {
7
82
  if (typeof err?.response === "string" && err.response.trim().length > 0) {
8
83
  return err.response.trim();
@@ -139,6 +214,7 @@ export const updateStory = (content, storyId, options, config) => {
139
214
  console.log(`${chalk.green(res.data.story.full_slug)} updated.`);
140
215
  return {
141
216
  ok: true,
217
+ stage: "update",
142
218
  id: res.data.story.id,
143
219
  name: res.data.story.name,
144
220
  slug: res.data.story.full_slug,
@@ -155,6 +231,7 @@ export const updateStory = (content, storyId, options, config) => {
155
231
  Logger.error(`Failed to update story '${storyLabel}' in space '${spaceId}' (${statusLabel}).${responseLabel}`);
156
232
  return {
157
233
  ok: false,
234
+ stage: "update",
158
235
  id: storyId,
159
236
  name: content?.name,
160
237
  slug: content?.full_slug || content?.slug,
@@ -165,12 +242,78 @@ export const updateStory = (content, storyId, options, config) => {
165
242
  };
166
243
  });
167
244
  };
168
- export const updateStories = (args, config) => {
245
+ export const publishStoryLanguages = async ({ storyId, story, languages, }, config) => {
246
+ const { spaceId, sbApi } = config;
247
+ const lang = languages.join(",");
248
+ const storyLabel = resolveStoryLabel(story, String(storyId));
249
+ Logger.log(`Publishing story '${storyLabel}' in space '${spaceId}' for languages: ${lang}`);
250
+ return sbApi
251
+ .get(`spaces/${spaceId}/stories/${storyId}/publish`, { lang })
252
+ .then((res) => {
253
+ const publishedStory = res?.data?.story || story || {};
254
+ Logger.success(`Published story '${storyLabel}' for languages: ${lang}`);
255
+ return {
256
+ ok: true,
257
+ stage: "publish",
258
+ id: publishedStory.id || storyId,
259
+ name: publishedStory.name || story?.name,
260
+ slug: publishedStory.full_slug ||
261
+ publishedStory.slug ||
262
+ story?.full_slug ||
263
+ story?.slug,
264
+ spaceId,
265
+ publishLanguages: languages,
266
+ data: res?.data,
267
+ };
268
+ })
269
+ .catch((err) => {
270
+ const status = err?.status || err?.response?.status;
271
+ const responseMessage = resolveStoryblokErrorResponse(err);
272
+ const statusLabel = status ? `status ${status}` : "unknown status";
273
+ const responseLabel = responseMessage
274
+ ? ` Response: ${responseMessage}`
275
+ : "";
276
+ Logger.error(`Failed to publish story '${storyLabel}' in space '${spaceId}' (${statusLabel}).${responseLabel}`);
277
+ return {
278
+ ok: false,
279
+ stage: "publish",
280
+ id: storyId,
281
+ name: story?.name,
282
+ slug: story?.full_slug || story?.slug,
283
+ spaceId,
284
+ status,
285
+ response: responseMessage,
286
+ publishLanguages: languages,
287
+ error: err,
288
+ };
289
+ });
290
+ };
291
+ export const updateStories = async (args, config) => {
169
292
  const { stories, options, spaceId } = args;
293
+ const shouldPublishLanguages = options.publish && options.publishLanguages !== undefined;
294
+ const publishLanguages = shouldPublishLanguages
295
+ ? await resolvePublishLanguageCodes(options.publishLanguages, {
296
+ ...config,
297
+ spaceId,
298
+ })
299
+ : undefined;
170
300
  return Promise.allSettled(
171
301
  // Run through stories, and update the space with migrated version of stories
172
302
  stories.map(async (stories) => {
173
- return updateStory(stories.story, stories.story.id, { publish: options.publish }, { ...config, spaceId });
303
+ const story = stories.story;
304
+ const updateResult = await updateStory(story, story.id, {
305
+ publish: options.publish && !shouldPublishLanguages,
306
+ }, { ...config, spaceId });
307
+ if (!shouldPublishLanguages ||
308
+ !publishLanguages ||
309
+ !updateResult?.ok) {
310
+ return updateResult;
311
+ }
312
+ return publishStoryLanguages({
313
+ storyId: story.id,
314
+ story,
315
+ languages: publishLanguages,
316
+ }, { ...config, spaceId });
174
317
  }));
175
318
  };
176
319
  export const upsertStory = async (args, config) => {
@@ -6,7 +6,9 @@ export interface ExtendedISbStoriesParams extends ISbStoriesParams {
6
6
  interface ModifyStoryOptions {
7
7
  publish?: boolean;
8
8
  force_update?: boolean;
9
+ publishLanguages?: PublishLanguagesOption;
9
10
  }
11
+ export type PublishLanguagesOption = "default" | "all" | string[];
10
12
  export type RemoveStory = (args: {
11
13
  storyId: string;
12
14
  }, config: RequestBaseConfig) => Promise<any>;
@@ -58,6 +58,19 @@ export declare const testApi: {
58
58
  removeStory: import("./stories/stories.types.js").RemoveStory;
59
59
  getStoryBySlug: import("./stories/stories.types.js").GetStoryBySlug;
60
60
  updateStories: import("./stories/stories.types.js").UpdateStories;
61
+ publishStoryLanguages: ({ storyId, story, languages, }: {
62
+ storyId: string | number;
63
+ story?: Record<string, any>;
64
+ languages: string[];
65
+ }, config: {
66
+ spaceId: string;
67
+ sbApi: any;
68
+ }) => Promise<any>;
69
+ parsePublishLanguagesOption: (publishLanguages?: string) => import("./stories/stories.types.js").PublishLanguagesOption;
70
+ resolvePublishLanguageCodes: (publishLanguages: import("./stories/stories.types.js").PublishLanguagesOption | undefined, config: {
71
+ spaceId: string;
72
+ sbApi: any;
73
+ }) => Promise<string[]>;
61
74
  getAllStories: import("./stories/stories.types.js").GetAllStories;
62
75
  removeAllStories: import("./stories/stories.types.js").RemoveAllStories;
63
76
  upsertStory: import("./stories/stories.types.js").UpsertStory;
@@ -1,7 +1,7 @@
1
1
  export declare const mainDescription = "\n USAGE\n $ sb-mig [command]\n \n COMMANDS\n sync Synchronize components, datasources, roles, stories, assets with Storyblok space.\n discover Discover components, migration configs and write to file or stdout.\n backup Command for backing up anything related to Storyblok\n migrate Migrate content from space to space, or from file to space.\n debug Output extra debugging information\n help This screen\n \n Examples\n $ sb-mig sync components --all\n $ sb-mig debug \n";
2
2
  export declare const syncDescription = "\n Usage\n $ sb-mig sync [components|roles|datasources|plugins|content] [space separated file names] or --all\n \n Description\n Synchronize components, roles, datasources, plugins, content with Storyblok space.\n \n COMMANDS\n components - sync components\n roles - sync roles\n datasources - sync datasources\n plugins - sync plugins\n content - sync content (stories, assets) - ! right now destructive, it will move content from 1 space to another, completelly overwriting it\n \n FLAGS\n --all - Sync all components, roles, datasources [components, roles, datasources]\n --presets - Pass it, if u want to sync also with presets (will take longer) [components only]\n --dry-run - Preview planned changes without making writes [components, roles, datasources, plugins, content]\n \n --yes - Skip ask for confirmation (dangerous, but useful in CI/CD) [content only]\n --from - Space ID from which you want to sync content [content only]\n --to - Space ID to which you want to sync content [content only]\n --syncDirection [fromSpaceToFile|fromFileToSpace|fromSpaceToSpace|fromAWStoSpace] \n - Sync direction (from, to) [content only]\n \n EXAMPLES\n $ sb-mig sync components --all\n $ sb-mig sync components --all --dry-run\n $ sb-mig sync components --all --presets\n $ sb-mig sync components accordion accordion-item\n $ sb-mig sync components accordion accordion-item --presets\n \n $ sb-mig sync roles --all\n $ sb-mig sync roles --all --dry-run\n \n $ sb-mig sync datasources --all\n $ sb-mig sync datasources --all --dry-run\n \n $ sb-mig sync plugins my-awesome-plugin - (you have to be in catalog which has ./dist/export.js file with compiled plugin)\n \n $ sb-mig sync content --all --from 12345 --to 12345\n $ sb-mig sync content --stories --from 12345 --to 12345\n $ sb-mig sync content --assets --from 12345 --to 12345\n";
3
3
  export declare const copyDescription = "\n Usage\n $ sb-mig copy\n \n Description\n Copy stuff\n \n COMMANDS\n ?\n \n FLAGS\n ?\n \n EXAMPLES\n $ sb-mig copy ?\n";
4
- export declare const migrateDescription = "\n Usage\n $ sb-mig migrate [content] [space separated file names] or --all --from [spaceId] --to [spaceId] --migration [migration-config-filename]\n $ sb-mig migrate content --all --migration migration-a --migration migration-b --migration migration-c\n \n Description\n Migrate content from space to space, or from file to space. It's potentially dangerous command, so it will ask for confirmation.\n Use with care.\n \n COMMANDS\n content - migrate content \n \n FLAGS\n --from - Space ID from which you want to migrate / or file name if passed '--migrate-from file'\n --fromFilePath - Direct path to stories JSON file when using '--migrate-from file'\n --to - Space ID to which you want to migrate\n --migrate-from - Migrate from (space, file) default: space\n --migration - File name of migration file (without extension). Can be repeated for ordered pipeline in content migration.\n --migrationComponentAlias - Add extra component aliases for a migration. Repeatable. Format: <migration>:<source>=<alias1>,<alias2>\n --migrationComponents - Override the exact component scope for a migration. Repeatable. Format: <migration>:<component1>,<component2>\n --withSlug - Filter stories by full slug (can be repeated)\n --startsWith - Filter stories by starts_with prefix\n --yes - Skip ask for confirmation (dangerous, but useful in CI/CD)\n --dry-run - Preview what would be migrated without making any API changes\n --publish - Publish changed stories immediately after migration. Default: save draft. [content only]\n --fileName - Stable base name for migration output files (disables timestamp suffix for migration artifacts)\n\n EXAMPLES\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration migration-a --migration migration-b --migration migration-c\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration colorPickerModeValues --migrationComponentAlias colorPickerModeValues:sb-button=sb-open-drift-button\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration colorPickerModeValues --migrationComponentAlias colorPickerModeValues:sb-section=sb-tour-page-section --migrationComponents colorPickerModeValues:sb-section,sb-tour-page-section\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --withSlug blog/home --withSlug docs/getting-started\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --startsWith blog/\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --publish --yes\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration v3toV4AllMigrations --dry-run --fileName brand-hub-v3-v4-run\n $ sb-mig migrate content --all --migrate-from file --from file-with-stories --to 12345 --migration file-with-migration\n $ sb-mig migrate content --all --migrate-from file --fromFilePath sbmig/migrations/dry-run--123---story-to-migrate__2026-2-9_20-51.json --to 12345 --migration migration-a --migration migration-b\n $ sb-mig migrate content my-component-1 my-component-2 --from 12345 --to 12345 --migration file-with-migration\n $ sb-mig migrate content my-component-1 my-component-2 --migrate-from file --from file-with-stories --to 12345 --migration file-with-migration \n";
4
+ export declare const migrateDescription = "\n Usage\n $ sb-mig migrate [content] [space separated file names] or --all --from [spaceId] --to [spaceId] --migration [migration-config-filename]\n $ sb-mig migrate content --all --migration migration-a --migration migration-b --migration migration-c\n \n Description\n Migrate content from space to space, or from file to space. It's potentially dangerous command, so it will ask for confirmation.\n Use with care.\n \n COMMANDS\n content - migrate content \n \n FLAGS\n --from - Space ID from which you want to migrate / or file name if passed '--migrate-from file'\n --fromFilePath - Direct path to stories JSON file when using '--migrate-from file'\n --to - Space ID to which you want to migrate\n --migrate-from - Migrate from (space, file) default: space\n --migration - File name of migration file (without extension). Can be repeated for ordered pipeline in content migration.\n --migrationComponentAlias - Add extra component aliases for a migration. Repeatable. Format: <migration>:<source>=<alias1>,<alias2>\n --migrationComponents - Override the exact component scope for a migration. Repeatable. Format: <migration>:<component1>,<component2>\n --withSlug - Filter stories by full slug (can be repeated)\n --startsWith - Filter stories by starts_with prefix\n --yes - Skip ask for confirmation (dangerous, but useful in CI/CD)\n --dry-run - Preview what would be migrated without making any API changes\n --publish - Publish changed stories immediately after migration. Default: save draft. [content only]\n --publishLanguages - Languages to publish when --publish is set. Values: default, all, or comma-separated Storyblok language codes. [content only]\n --fileName - Stable base name for migration output files (disables timestamp suffix for migration artifacts)\n\n EXAMPLES\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration migration-a --migration migration-b --migration migration-c\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration colorPickerModeValues --migrationComponentAlias colorPickerModeValues:sb-button=sb-open-drift-button\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration colorPickerModeValues --migrationComponentAlias colorPickerModeValues:sb-section=sb-tour-page-section --migrationComponents colorPickerModeValues:sb-section,sb-tour-page-section\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --withSlug blog/home --withSlug docs/getting-started\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --startsWith blog/\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --publish --yes\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --publish --publishLanguages all --yes\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --publish --publishLanguages default,fr,de --yes\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration v3toV4AllMigrations --dry-run --fileName brand-hub-v3-v4-run\n $ sb-mig migrate content --all --migrate-from file --from file-with-stories --to 12345 --migration file-with-migration\n $ sb-mig migrate content --all --migrate-from file --fromFilePath sbmig/migrations/dry-run--123---story-to-migrate__2026-2-9_20-51.json --to 12345 --migration migration-a --migration migration-b\n $ sb-mig migrate content my-component-1 my-component-2 --from 12345 --to 12345 --migration file-with-migration\n $ sb-mig migrate content my-component-1 my-component-2 --migrate-from file --from file-with-stories --to 12345 --migration file-with-migration \n";
5
5
  export declare const revertDescription = "\n Usage\n $ sb-mig revert [content] --migration\n \n Description\n Revert content migration\n \n COMMANDS\n content - revert content migration \n \n FLAGS\n --migration - ???\n --yes - Skip ask for confirmation (dangerous, but useful in CI/CD) \n \n EXAMPLES\n $ sb-mig revert content --migration \n";
6
6
  export declare const discoverDescription = "\n Usage\n $ sb-mig discover [components|migrations] --all --write\n\n Description\n Discover all components or migration configs and write to file or stdout\n\n COMMANDS\n components - discover components\n migrations - discover migration config files\n\n FLAGS\n --all - Discover all components or migration configs\n --write - Write to file\n\n EXAMPLES\n $ sb-mig discover components --all\n $ sb-mig discover components --all --write\n $ sb-mig discover migrations --all\n";
7
7
  export declare const migrationsDescription = "\n Usage\n $ sb-mig migrations recognize\n \n Description\n Recognize migrations you need to apply\n \n COMMANDS\n recognize - recognize migrations\n \n FLAGS \n \n EXAMPLES\n $ sb-mig migrations recognize\n\n";
@@ -99,6 +99,7 @@ export const migrateDescription = `
99
99
  --yes - Skip ask for confirmation (dangerous, but useful in CI/CD)
100
100
  --dry-run - Preview what would be migrated without making any API changes
101
101
  --publish - Publish changed stories immediately after migration. Default: save draft. [content only]
102
+ --publishLanguages - Languages to publish when --publish is set. Values: default, all, or comma-separated Storyblok language codes. [content only]
102
103
  --fileName - Stable base name for migration output files (disables timestamp suffix for migration artifacts)
103
104
 
104
105
  EXAMPLES
@@ -109,6 +110,8 @@ export const migrateDescription = `
109
110
  $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --withSlug blog/home --withSlug docs/getting-started
110
111
  $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --startsWith blog/
111
112
  $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --publish --yes
113
+ $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --publish --publishLanguages all --yes
114
+ $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --publish --publishLanguages default,fr,de --yes
112
115
  $ sb-mig migrate content --all --from 12345 --to 12345 --migration v3toV4AllMigrations --dry-run --fileName brand-hub-v3-v4-run
113
116
  $ sb-mig migrate content --all --migrate-from file --from file-with-stories --to 12345 --migration file-with-migration
114
117
  $ sb-mig migrate content --all --migrate-from file --fromFilePath sbmig/migrations/dry-run--123---story-to-migrate__2026-2-9_20-51.json --to 12345 --migration migration-a --migration migration-b
@@ -4,6 +4,7 @@ import { buildStoryBackupBaseName } from "../../api/data-migration/file-naming.j
4
4
  import { parseMigrationComponentAliasFlags, parseMigrationComponentOverrideFlags, } from "../../api/data-migration/migration-component-scope.js";
5
5
  import { managementApi } from "../../api/managementApi.js";
6
6
  import { backupStories } from "../../api/stories/backup.js";
7
+ import { parsePublishLanguagesOption } from "../../api/stories/stories.js";
7
8
  import { createAndSaveToFile } from "../../utils/files.js";
8
9
  import Logger from "../../utils/logger.js";
9
10
  import { apiConfig } from "../api-config.js";
@@ -42,6 +43,7 @@ export const migrate = async (props) => {
42
43
  "startsWith",
43
44
  "dryRun",
44
45
  "publish",
46
+ "publishLanguages",
45
47
  "fileName",
46
48
  ]);
47
49
  Logger.warning(`This feature is in BETA. Use it at your own risk. The API might change in the future. (Probably in a standard Prisma like migration way)`);
@@ -62,6 +64,10 @@ export const migrate = async (props) => {
62
64
  const migrationComponentOverrides = parseMigrationComponentOverrideFlags(flags["migrationComponents"]);
63
65
  const dryRun = flags["dryRun"];
64
66
  const publish = Boolean(flags["publish"]);
67
+ const publishLanguagesFlag = flags["publishLanguages"];
68
+ const publishLanguages = publishLanguagesFlag
69
+ ? parsePublishLanguagesOption(publishLanguagesFlag)
70
+ : undefined;
65
71
  const fileName = flags["fileName"];
66
72
  const withSlugFlag = flags["withSlug"];
67
73
  const withSlug = Array.isArray(withSlugFlag)
@@ -73,6 +79,9 @@ export const migrate = async (props) => {
73
79
  if (migrationConfigs.length === 0) {
74
80
  throw new Error("Missing migration config. Pass at least one --migration value.");
75
81
  }
82
+ if (!publish && publishLanguagesFlag) {
83
+ throw new Error("--publishLanguages requires --publish for 'migrate content'.");
84
+ }
76
85
  if (isIt("empty")) {
77
86
  const componentsToMigrate = unpackElements(input) || [""];
78
87
  const migrateFrom = "space";
@@ -100,6 +109,7 @@ export const migrate = async (props) => {
100
109
  filters: { withSlug, startsWith },
101
110
  dryRun,
102
111
  publish,
112
+ publishLanguages,
103
113
  fromFilePath,
104
114
  fileName,
105
115
  }, apiConfig);
@@ -128,6 +138,7 @@ export const migrate = async (props) => {
128
138
  filters: { withSlug, startsWith },
129
139
  dryRun,
130
140
  publish,
141
+ publishLanguages,
131
142
  fromFilePath,
132
143
  fileName,
133
144
  }, apiConfig);
@@ -163,12 +174,16 @@ export const migrate = async (props) => {
163
174
  getFrom(flags, apiConfig);
164
175
  const to = getTo(flags, apiConfig);
165
176
  const publish = Boolean(flags["publish"]);
177
+ const publishLanguagesFlag = flags["publishLanguages"];
166
178
  if (migrationConfigs.length === 0) {
167
179
  throw new Error("Missing migration config. Pass exactly one --migration value for presets.");
168
180
  }
169
181
  if (publish) {
170
182
  throw new Error("--publish is only supported for 'migrate content'. Presets cannot be published.");
171
183
  }
184
+ if (publishLanguagesFlag) {
185
+ throw new Error("--publishLanguages is only supported for 'migrate content'. Presets cannot be published.");
186
+ }
172
187
  if (migrationConfigs.length > 1) {
173
188
  throw new Error("Multiple --migration values are currently supported only for 'migrate content'. Presets support a single migration config.");
174
189
  }
package/dist/cli/index.js CHANGED
@@ -84,6 +84,9 @@ app.migrate = () => ({
84
84
  type: "boolean",
85
85
  default: false,
86
86
  },
87
+ publishLanguages: {
88
+ type: "string",
89
+ },
87
90
  fileName: {
88
91
  type: "string",
89
92
  },
@@ -3,12 +3,89 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.deepUpsertStory = exports.upsertStory = exports.updateStories = exports.updateStory = exports.createStory = exports.getStoryBySlug = exports.getStoryById = exports.getAllStories = exports.removeAllStories = exports.removeStory = void 0;
6
+ exports.deepUpsertStory = exports.upsertStory = exports.updateStories = exports.publishStoryLanguages = exports.updateStory = exports.createStory = exports.getStoryBySlug = exports.getStoryById = exports.getAllStories = exports.removeAllStories = exports.removeStory = exports.resolvePublishLanguageCodes = exports.parsePublishLanguagesOption = void 0;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  const logger_js_1 = __importDefault(require("../../utils/logger.js"));
9
9
  const object_utils_js_1 = require("../../utils/object-utils.js");
10
10
  const request_js_1 = require("../utils/request.js");
11
11
  const resolveStoryLabel = (content, storyId) => content?.full_slug || content?.slug || content?.name || String(storyId);
12
+ const DEFAULT_PUBLISH_LANGUAGE = "[default]";
13
+ const isDefaultLanguageToken = (language) => language.toLowerCase() === "default" ||
14
+ language === DEFAULT_PUBLISH_LANGUAGE;
15
+ const normalizePublishLanguageCodes = (languages) => {
16
+ const normalized = languages.map((language) => {
17
+ const trimmed = language.trim();
18
+ return isDefaultLanguageToken(trimmed)
19
+ ? DEFAULT_PUBLISH_LANGUAGE
20
+ : trimmed;
21
+ });
22
+ const cleanLanguages = normalized.filter((language) => language.length > 0);
23
+ if (cleanLanguages.length === 0) {
24
+ throw new Error("Publish languages cannot be empty.");
25
+ }
26
+ return Array.from(new Set(cleanLanguages));
27
+ };
28
+ const parsePublishLanguagesOption = (publishLanguages) => {
29
+ if (!publishLanguages) {
30
+ return "default";
31
+ }
32
+ const trimmed = publishLanguages.trim();
33
+ if (trimmed.length === 0) {
34
+ return "default";
35
+ }
36
+ if (trimmed.toLowerCase() === "all") {
37
+ return "all";
38
+ }
39
+ const languages = normalizePublishLanguageCodes(trimmed.split(","));
40
+ if (languages.length === 1 && languages[0] === DEFAULT_PUBLISH_LANGUAGE) {
41
+ return "default";
42
+ }
43
+ return languages;
44
+ };
45
+ exports.parsePublishLanguagesOption = parsePublishLanguagesOption;
46
+ const readSpaceLanguageCodes = (spaceResponse) => {
47
+ const space = spaceResponse?.data?.space || spaceResponse?.space || {};
48
+ const languageSources = [
49
+ space.languages,
50
+ space.language_codes,
51
+ space.options?.languages,
52
+ ];
53
+ for (const source of languageSources) {
54
+ if (!Array.isArray(source)) {
55
+ continue;
56
+ }
57
+ return source
58
+ .map((language) => {
59
+ if (typeof language === "string") {
60
+ return language;
61
+ }
62
+ if (typeof language?.code === "string") {
63
+ return language.code;
64
+ }
65
+ return "";
66
+ })
67
+ .filter((language) => language.trim().length > 0);
68
+ }
69
+ return [];
70
+ };
71
+ const resolvePublishLanguageCodes = async (publishLanguages, config) => {
72
+ if (!publishLanguages || publishLanguages === "default") {
73
+ return [DEFAULT_PUBLISH_LANGUAGE];
74
+ }
75
+ if (Array.isArray(publishLanguages)) {
76
+ return normalizePublishLanguageCodes(publishLanguages);
77
+ }
78
+ const spaceResponse = await config.sbApi.get(`spaces/${config.spaceId}`);
79
+ const spaceLanguageCodes = readSpaceLanguageCodes(spaceResponse);
80
+ if (spaceLanguageCodes.length === 0) {
81
+ logger_js_1.default.warning(`No configured Storyblok languages were found for space '${config.spaceId}'. Publishing only ${DEFAULT_PUBLISH_LANGUAGE}.`);
82
+ }
83
+ return normalizePublishLanguageCodes([
84
+ DEFAULT_PUBLISH_LANGUAGE,
85
+ ...spaceLanguageCodes,
86
+ ]);
87
+ };
88
+ exports.resolvePublishLanguageCodes = resolvePublishLanguageCodes;
12
89
  const resolveStoryblokErrorResponse = (err) => {
13
90
  if (typeof err?.response === "string" && err.response.trim().length > 0) {
14
91
  return err.response.trim();
@@ -151,6 +228,7 @@ const updateStory = (content, storyId, options, config) => {
151
228
  console.log(`${chalk_1.default.green(res.data.story.full_slug)} updated.`);
152
229
  return {
153
230
  ok: true,
231
+ stage: "update",
154
232
  id: res.data.story.id,
155
233
  name: res.data.story.name,
156
234
  slug: res.data.story.full_slug,
@@ -167,6 +245,7 @@ const updateStory = (content, storyId, options, config) => {
167
245
  logger_js_1.default.error(`Failed to update story '${storyLabel}' in space '${spaceId}' (${statusLabel}).${responseLabel}`);
168
246
  return {
169
247
  ok: false,
248
+ stage: "update",
170
249
  id: storyId,
171
250
  name: content?.name,
172
251
  slug: content?.full_slug || content?.slug,
@@ -178,12 +257,79 @@ const updateStory = (content, storyId, options, config) => {
178
257
  });
179
258
  };
180
259
  exports.updateStory = updateStory;
181
- const updateStories = (args, config) => {
260
+ const publishStoryLanguages = async ({ storyId, story, languages, }, config) => {
261
+ const { spaceId, sbApi } = config;
262
+ const lang = languages.join(",");
263
+ const storyLabel = resolveStoryLabel(story, String(storyId));
264
+ logger_js_1.default.log(`Publishing story '${storyLabel}' in space '${spaceId}' for languages: ${lang}`);
265
+ return sbApi
266
+ .get(`spaces/${spaceId}/stories/${storyId}/publish`, { lang })
267
+ .then((res) => {
268
+ const publishedStory = res?.data?.story || story || {};
269
+ logger_js_1.default.success(`Published story '${storyLabel}' for languages: ${lang}`);
270
+ return {
271
+ ok: true,
272
+ stage: "publish",
273
+ id: publishedStory.id || storyId,
274
+ name: publishedStory.name || story?.name,
275
+ slug: publishedStory.full_slug ||
276
+ publishedStory.slug ||
277
+ story?.full_slug ||
278
+ story?.slug,
279
+ spaceId,
280
+ publishLanguages: languages,
281
+ data: res?.data,
282
+ };
283
+ })
284
+ .catch((err) => {
285
+ const status = err?.status || err?.response?.status;
286
+ const responseMessage = resolveStoryblokErrorResponse(err);
287
+ const statusLabel = status ? `status ${status}` : "unknown status";
288
+ const responseLabel = responseMessage
289
+ ? ` Response: ${responseMessage}`
290
+ : "";
291
+ logger_js_1.default.error(`Failed to publish story '${storyLabel}' in space '${spaceId}' (${statusLabel}).${responseLabel}`);
292
+ return {
293
+ ok: false,
294
+ stage: "publish",
295
+ id: storyId,
296
+ name: story?.name,
297
+ slug: story?.full_slug || story?.slug,
298
+ spaceId,
299
+ status,
300
+ response: responseMessage,
301
+ publishLanguages: languages,
302
+ error: err,
303
+ };
304
+ });
305
+ };
306
+ exports.publishStoryLanguages = publishStoryLanguages;
307
+ const updateStories = async (args, config) => {
182
308
  const { stories, options, spaceId } = args;
309
+ const shouldPublishLanguages = options.publish && options.publishLanguages !== undefined;
310
+ const publishLanguages = shouldPublishLanguages
311
+ ? await (0, exports.resolvePublishLanguageCodes)(options.publishLanguages, {
312
+ ...config,
313
+ spaceId,
314
+ })
315
+ : undefined;
183
316
  return Promise.allSettled(
184
317
  // Run through stories, and update the space with migrated version of stories
185
318
  stories.map(async (stories) => {
186
- return (0, exports.updateStory)(stories.story, stories.story.id, { publish: options.publish }, { ...config, spaceId });
319
+ const story = stories.story;
320
+ const updateResult = await (0, exports.updateStory)(story, story.id, {
321
+ publish: options.publish && !shouldPublishLanguages,
322
+ }, { ...config, spaceId });
323
+ if (!shouldPublishLanguages ||
324
+ !publishLanguages ||
325
+ !updateResult?.ok) {
326
+ return updateResult;
327
+ }
328
+ return (0, exports.publishStoryLanguages)({
329
+ storyId: story.id,
330
+ story,
331
+ languages: publishLanguages,
332
+ }, { ...config, spaceId });
187
333
  }));
188
334
  };
189
335
  exports.updateStories = updateStories;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sb-mig",
3
- "version": "6.0.0-beta.4",
3
+ "version": "6.0.0-beta.5",
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",