sb-mig 6.3.0 → 6.4.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/api/data-migration/component-data-migration.d.ts +81 -0
- package/dist/api/data-migration/component-data-migration.js +391 -95
- package/dist/api/data-migration/migration-run-log.d.ts +3 -1
- package/dist/api/data-migration/migration-run-log.js +2 -1
- package/dist/cli/cli-descriptions.d.ts +1 -1
- package/dist/cli/cli-descriptions.js +9 -0
- package/dist/cli/commands/migrate.js +27 -1
- package/dist/cli/index.js +3 -0
- package/dist/utils/files.d.ts +1 -0
- package/dist/utils/files.js +9 -0
- package/package.json +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { PublishLanguagesOption } from "../stories/stories.types.js";
|
|
2
2
|
import type { RequestBaseConfig } from "../utils/request.js";
|
|
3
|
+
import { type LanguagePublishStateMap } from "../stories/language-publish-state.js";
|
|
3
4
|
import { type MigrationComponentAliasesByMigration, type MigrationComponentOverridesByMigration } from "./migration-component-scope.js";
|
|
4
5
|
import { type PreparedMigrationValidator } from "./migration-validation.js";
|
|
6
|
+
import { type PublishedLayerContext } from "./published-layer.js";
|
|
5
7
|
export type MigrateFrom = "file" | "space";
|
|
6
8
|
export type PublicationMode = "preserve-layers" | "collapse-draft" | "save-only";
|
|
7
9
|
export interface PreparedMigrationConfig {
|
|
@@ -73,6 +75,60 @@ export declare const runMigrationPipelineInMemory: ({ itemType, itemsToMigrate,
|
|
|
73
75
|
preparedMigrationConfigs: PreparedMigrationConfig[];
|
|
74
76
|
}) => MigrationPipelineResult;
|
|
75
77
|
export declare const migrateAllComponentsDataInStories: ({ itemType, migrationConfig, migrateFrom, from, to, filters, dryRun, publicationMode, publicationLanguages, fromFilePath, fileName, languagePublishStatePath, migrationComponentAliases, migrationComponentOverrides, }: Omit<MigrateItems, "componentsToMigrate" | "preparedMigrationConfigs">, config: RequestBaseConfig) => Promise<void>;
|
|
78
|
+
export interface ContinueManifest {
|
|
79
|
+
manifestVersion: 1;
|
|
80
|
+
createdAt?: string;
|
|
81
|
+
kind: "migrate-content-continue-manifest";
|
|
82
|
+
itemType: "story" | "preset";
|
|
83
|
+
from: string;
|
|
84
|
+
to: string;
|
|
85
|
+
migrateFrom: MigrateFrom;
|
|
86
|
+
fromFilePath?: string | null;
|
|
87
|
+
publicationMode: PublicationMode;
|
|
88
|
+
migrationConfigNames: string[];
|
|
89
|
+
publishLanguages?: {
|
|
90
|
+
requested?: PublishLanguagesOption;
|
|
91
|
+
} | null;
|
|
92
|
+
resolvedPublishLanguages?: string[] | null;
|
|
93
|
+
artifactBaseName: string;
|
|
94
|
+
useDatestamp: boolean;
|
|
95
|
+
artifacts: {
|
|
96
|
+
changedItems: string;
|
|
97
|
+
pipelineSummary: string;
|
|
98
|
+
inputFull?: string | null;
|
|
99
|
+
languageStateMap?: string | null;
|
|
100
|
+
draftAfterFull?: string | null;
|
|
101
|
+
publishedAfterFull?: string | null;
|
|
102
|
+
dirtyPublishedRecords?: string | null;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
interface FinalizeMigrationArgs {
|
|
106
|
+
itemType: "story" | "preset";
|
|
107
|
+
from: string;
|
|
108
|
+
to: string;
|
|
109
|
+
migrateFrom: MigrateFrom;
|
|
110
|
+
fromFilePath?: string;
|
|
111
|
+
languagePublishStatePath?: string;
|
|
112
|
+
publicationMode: PublicationMode;
|
|
113
|
+
effectivePublicationLanguages?: PublishLanguagesOption;
|
|
114
|
+
resolvedPublishLanguages?: string[];
|
|
115
|
+
languagePublishStateMap?: LanguagePublishStateMap;
|
|
116
|
+
appliedMigrationNames: string[];
|
|
117
|
+
pipelineResult: MigrationPipelineResult;
|
|
118
|
+
publishedLayerContext?: PublishedLayerContext;
|
|
119
|
+
publishedLayerPipelineResult: MigrationPipelineResult | null;
|
|
120
|
+
artifactBaseName: string;
|
|
121
|
+
useDatestamp: boolean;
|
|
122
|
+
continuedFromManifest?: string;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Everything `doTheMigration` does AFTER the dry-run preview returns:
|
|
126
|
+
* mark migrations as applied, write the changes to Storyblok, and write the
|
|
127
|
+
* run log. Extracted so `migrate continue` can run the exact same path against
|
|
128
|
+
* inputs reconstructed from dry-run artifacts (instead of re-pulling + re-transforming).
|
|
129
|
+
*/
|
|
130
|
+
export declare const finalizeMigration: ({ itemType, from, to, migrateFrom, fromFilePath, languagePublishStatePath, publicationMode, effectivePublicationLanguages, resolvedPublishLanguages, languagePublishStateMap, appliedMigrationNames, pipelineResult, publishedLayerContext, publishedLayerPipelineResult, artifactBaseName, useDatestamp, continuedFromManifest, }: FinalizeMigrationArgs, config: RequestBaseConfig) => Promise<void>;
|
|
131
|
+
export declare const findArtifactFilename: (files: string[], stem: string) => string | null;
|
|
76
132
|
export declare const doTheMigration: ({ itemType, from, itemsToMigrate, migrationConfig, migrationConfigs, to, dryRun, publicationMode, publicationLanguages, migrateFrom, fromFilePath, fileName, languagePublishStatePath, }: {
|
|
77
133
|
itemType?: "story" | "preset";
|
|
78
134
|
from: string;
|
|
@@ -89,4 +145,29 @@ export declare const doTheMigration: ({ itemType, from, itemsToMigrate, migratio
|
|
|
89
145
|
languagePublishStatePath?: string;
|
|
90
146
|
}, config: RequestBaseConfig) => Promise<void>;
|
|
91
147
|
export declare const migrateProvidedComponentsDataInStories: ({ itemType, migrationConfig, migrateFrom, from, to, componentsToMigrate, filters, dryRun, publicationMode, publicationLanguages, fromFilePath, fileName, languagePublishStatePath, preparedMigrationConfigs, migrationComponentAliases, migrationComponentOverrides, }: MigrateItems, config: RequestBaseConfig) => Promise<void>;
|
|
148
|
+
export interface ContinuePlanSummary {
|
|
149
|
+
manifestFileName: string;
|
|
150
|
+
from: string;
|
|
151
|
+
to: string;
|
|
152
|
+
itemType: "story" | "preset";
|
|
153
|
+
publicationMode: PublicationMode;
|
|
154
|
+
migrationConfigNames: string[];
|
|
155
|
+
resolvedPublishLanguages: string[];
|
|
156
|
+
changedCount: number;
|
|
157
|
+
dirtyPublishedCount: number;
|
|
158
|
+
artifactFiles: string[];
|
|
159
|
+
}
|
|
160
|
+
export interface ContinuePlan {
|
|
161
|
+
summary: ContinuePlanSummary;
|
|
162
|
+
run: () => Promise<void>;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Discover the dry-run `continue` manifest in the migrations folder, load the
|
|
166
|
+
* artifacts it points at, and reconstruct the exact inputs `finalizeMigration`
|
|
167
|
+
* needs. Returns a summary (for the confirmation prompt) and a `run()` thunk that
|
|
168
|
+
* performs the write. Does NOT pull stories, transform, or fetch published layers.
|
|
169
|
+
*/
|
|
170
|
+
export declare const prepareContinueMigration: ({ manifestFileName }: {
|
|
171
|
+
manifestFileName?: string;
|
|
172
|
+
}, config: RequestBaseConfig) => Promise<ContinuePlan>;
|
|
92
173
|
export {};
|
|
@@ -2,7 +2,7 @@ import path from "path";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { discoverMigrationConfig, discoverStories, LOOKUP_TYPE, SCOPE, } from "../../cli/utils/discover.js";
|
|
4
4
|
import storyblokConfig from "../../config/config.js";
|
|
5
|
-
import { createAndSaveToFile, getFileContentWithRequire, getFilesContentWithRequire, } from "../../utils/files.js";
|
|
5
|
+
import { createAndSaveToFile, getFileContentWithRequire, getFilesContentWithRequire, listFilesInDir, readFile, } from "../../utils/files.js";
|
|
6
6
|
import Logger from "../../utils/logger.js";
|
|
7
7
|
import { modifyOrCreateAppliedMigrationsFile } from "../../utils/migrations.js";
|
|
8
8
|
import { isObjectEmpty } from "../../utils/object-utils.js";
|
|
@@ -810,6 +810,222 @@ export const migrateAllComponentsDataInStories = async ({ itemType, migrationCon
|
|
|
810
810
|
preparedMigrationConfigs,
|
|
811
811
|
}, config);
|
|
812
812
|
};
|
|
813
|
+
/**
|
|
814
|
+
* Everything `doTheMigration` does AFTER the dry-run preview returns:
|
|
815
|
+
* mark migrations as applied, write the changes to Storyblok, and write the
|
|
816
|
+
* run log. Extracted so `migrate continue` can run the exact same path against
|
|
817
|
+
* inputs reconstructed from dry-run artifacts (instead of re-pulling + re-transforming).
|
|
818
|
+
*/
|
|
819
|
+
export const finalizeMigration = async ({ itemType, from, to, migrateFrom, fromFilePath, languagePublishStatePath, publicationMode, effectivePublicationLanguages, resolvedPublishLanguages, languagePublishStateMap, appliedMigrationNames, pipelineResult, publishedLayerContext, publishedLayerPipelineResult, artifactBaseName, useDatestamp, continuedFromManifest, }, config) => {
|
|
820
|
+
for (const migrationConfigName of appliedMigrationNames) {
|
|
821
|
+
await modifyOrCreateAppliedMigrationsFile(migrationConfigName, itemType);
|
|
822
|
+
}
|
|
823
|
+
const publishedLayerChangedCount = publishedLayerPipelineResult?.changedItems.length ?? 0;
|
|
824
|
+
if (pipelineResult.changedItems.length === 0 &&
|
|
825
|
+
publishedLayerChangedCount === 0) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
let writeResults = [];
|
|
829
|
+
if (itemType === "story") {
|
|
830
|
+
if (shouldUsePublishedLayerMode(publicationMode) &&
|
|
831
|
+
publishedLayerContext) {
|
|
832
|
+
const dirtyStoryIds = new Set(publishedLayerContext.dirtyPublishedRecords.map((record) => String(record.storyId)));
|
|
833
|
+
const nonDirtyChangedItems = pipelineResult.changedItems.filter((item) => !dirtyStoryIds.has(storyIdOf(item)));
|
|
834
|
+
const nonDirtyWriteResults = nonDirtyChangedItems.length > 0
|
|
835
|
+
? await managementApi.stories.updateStories({
|
|
836
|
+
stories: nonDirtyChangedItems,
|
|
837
|
+
spaceId: to,
|
|
838
|
+
options: {
|
|
839
|
+
publish: shouldPublishForPublicationMode(publicationMode),
|
|
840
|
+
publishLanguages: resolvedPublishLanguages,
|
|
841
|
+
preservePublishState: shouldPublishForPublicationMode(publicationMode),
|
|
842
|
+
languagePublishStateMap,
|
|
843
|
+
},
|
|
844
|
+
}, config)
|
|
845
|
+
: [];
|
|
846
|
+
const dirtyWriteResults = publishedLayerPipelineResult
|
|
847
|
+
? await writeDirtyPublishedLayerStories({
|
|
848
|
+
context: publishedLayerContext,
|
|
849
|
+
draftPipelineResult: pipelineResult,
|
|
850
|
+
publishedPipelineResult: publishedLayerPipelineResult,
|
|
851
|
+
to,
|
|
852
|
+
resolvedPublishLanguages,
|
|
853
|
+
languagePublishStateMap,
|
|
854
|
+
}, config)
|
|
855
|
+
: [];
|
|
856
|
+
writeResults = [...nonDirtyWriteResults, ...dirtyWriteResults];
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
writeResults = await managementApi.stories.updateStories({
|
|
860
|
+
stories: pipelineResult.changedItems,
|
|
861
|
+
spaceId: to,
|
|
862
|
+
options: {
|
|
863
|
+
publish: shouldPublishForPublicationMode(publicationMode),
|
|
864
|
+
publishLanguages: resolvedPublishLanguages,
|
|
865
|
+
preservePublishState: shouldPublishForPublicationMode(publicationMode),
|
|
866
|
+
publishDirtyPublishedStories: shouldPublishDirtyDrafts(publicationMode),
|
|
867
|
+
languagePublishStateMap,
|
|
868
|
+
},
|
|
869
|
+
}, config);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
else if (itemType === "preset") {
|
|
873
|
+
writeResults = await managementApi.presets.updatePresets({
|
|
874
|
+
presets: pipelineResult.changedItems,
|
|
875
|
+
spaceId: to,
|
|
876
|
+
options: {},
|
|
877
|
+
}, config);
|
|
878
|
+
}
|
|
879
|
+
const writeSummary = summarizeMutationWriteResults(writeResults);
|
|
880
|
+
try {
|
|
881
|
+
await saveMigrationRunLog({
|
|
882
|
+
artifactBaseName,
|
|
883
|
+
useDatestamp,
|
|
884
|
+
from,
|
|
885
|
+
to,
|
|
886
|
+
itemType,
|
|
887
|
+
dryRun: false,
|
|
888
|
+
publish: shouldPublishForPublicationMode(publicationMode),
|
|
889
|
+
publishLanguages: effectivePublicationLanguages,
|
|
890
|
+
resolvedPublishLanguages,
|
|
891
|
+
migrateFrom,
|
|
892
|
+
fromFilePath,
|
|
893
|
+
languagePublishStatePath,
|
|
894
|
+
pipelineResult,
|
|
895
|
+
writeResults,
|
|
896
|
+
writeSummary,
|
|
897
|
+
continuedFromManifest,
|
|
898
|
+
}, config);
|
|
899
|
+
}
|
|
900
|
+
catch (error) {
|
|
901
|
+
Logger.warning(`[MIGRATION] Could not write migration run log: ${error instanceof Error ? error.message : String(error)}`);
|
|
902
|
+
}
|
|
903
|
+
if (writeSummary.failed === 0) {
|
|
904
|
+
Logger.success(`[MIGRATION] Update complete. ${writeSummary.successful}/${writeSummary.total} ${itemType}(s) updated successfully.`);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
Logger.warning(`[MIGRATION] Update complete with partial failures. ${writeSummary.successful}/${writeSummary.total} ${itemType}(s) updated successfully, ${writeSummary.failed} failed.`);
|
|
908
|
+
writeSummary.failedItems.slice(0, 10).forEach((item) => {
|
|
909
|
+
const label = item.slug || item.name || item.id || "unknown";
|
|
910
|
+
Logger.error(`[MIGRATION] Failed ${itemType}: ${String(label)}`);
|
|
911
|
+
});
|
|
912
|
+
if (writeSummary.failedItems.length > 10) {
|
|
913
|
+
Logger.warning(`[MIGRATION] Showing first 10 failed ${itemType}(s) only.`);
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
/**
|
|
917
|
+
* Sortable key for an artifact's datestamp suffix.
|
|
918
|
+
*
|
|
919
|
+
* Datestamps use the non-zero-padded `YYYY-M-D_H-M` format from
|
|
920
|
+
* generateDatestamp, so a plain string sort is NOT chronological
|
|
921
|
+
* (e.g. "2026-1-15_9-30" sorts before "2026-1-9_9-30"). Parse the parts and
|
|
922
|
+
* compare numerically instead. Returns -1 for the non-datestamped variant
|
|
923
|
+
* (`${stem}.json`), which only exists when --fileName is used (single match,
|
|
924
|
+
* never mixed with datestamped files for the same stem).
|
|
925
|
+
*/
|
|
926
|
+
const artifactDatestampKey = (filename, stem) => {
|
|
927
|
+
const prefix = `${stem}__`;
|
|
928
|
+
if (!filename.startsWith(prefix) || !filename.endsWith(".json")) {
|
|
929
|
+
return -1;
|
|
930
|
+
}
|
|
931
|
+
const stamp = filename.slice(prefix.length, -".json".length);
|
|
932
|
+
const match = /^(\d+)-(\d+)-(\d+)_(\d+)-(\d+)$/.exec(stamp);
|
|
933
|
+
if (!match) {
|
|
934
|
+
return -1;
|
|
935
|
+
}
|
|
936
|
+
const [, year, month, day, hour, minute] = match;
|
|
937
|
+
// month/day/hour/minute each fit in two decimal digits, so this packs the
|
|
938
|
+
// parts into one monotonically increasing key.
|
|
939
|
+
return (Number(year) * 100_000_000 +
|
|
940
|
+
Number(month) * 1_000_000 +
|
|
941
|
+
Number(day) * 10_000 +
|
|
942
|
+
Number(hour) * 100 +
|
|
943
|
+
Number(minute));
|
|
944
|
+
};
|
|
945
|
+
export const findArtifactFilename = (files, stem) => {
|
|
946
|
+
const matches = files.filter((file) => file === `${stem}.json` || file.startsWith(`${stem}__`));
|
|
947
|
+
if (matches.length === 0) {
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
// Pick the chronologically newest match. Do not lexicographically sort:
|
|
951
|
+
// the datestamp format is not zero-padded, so string order is not time order.
|
|
952
|
+
return matches.reduce((newest, candidate) => artifactDatestampKey(candidate, stem) >=
|
|
953
|
+
artifactDatestampKey(newest, stem)
|
|
954
|
+
? candidate
|
|
955
|
+
: newest);
|
|
956
|
+
};
|
|
957
|
+
/**
|
|
958
|
+
* Written at the end of a dry-run only. A small machine file that lets
|
|
959
|
+
* `migrate continue` finish the migration without re-pulling/re-transforming:
|
|
960
|
+
* carries the scalars that aren't otherwise saved (target space, exact migration
|
|
961
|
+
* names, run-log metadata) and references the heavy dry-run artifacts by filename.
|
|
962
|
+
*/
|
|
963
|
+
const saveContinueManifest = async ({ artifactBaseName, useDatestamp, dryRun, itemType, from, to, migrateFrom, fromFilePath, publicationMode, effectivePublicationLanguages, resolvedPublishLanguages, appliedMigrationNames, publishedLayerContext, publishedLayerPipelineResult, }, config) => {
|
|
964
|
+
if (!dryRun) {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const isPreserveLayers = shouldUsePublishedLayerMode(publicationMode) &&
|
|
968
|
+
Boolean(publishedLayerContext);
|
|
969
|
+
if (isPreserveLayers && publishedLayerContext) {
|
|
970
|
+
const publishedChangedIds = (publishedLayerPipelineResult?.changedItems ?? []).map((item) => storyIdOf(item));
|
|
971
|
+
await createAndSaveToFile({
|
|
972
|
+
datestamp: useDatestamp,
|
|
973
|
+
ext: "json",
|
|
974
|
+
filename: `dry-run--${artifactBaseName}---dirty-published-records`,
|
|
975
|
+
folder: "migrations",
|
|
976
|
+
res: {
|
|
977
|
+
dirtyPublishedRecords: publishedLayerContext.dirtyPublishedRecords,
|
|
978
|
+
publishedChangedIds,
|
|
979
|
+
},
|
|
980
|
+
}, config);
|
|
981
|
+
}
|
|
982
|
+
// Resolve the real sibling filenames from disk. The datestamp suffix has
|
|
983
|
+
// minute granularity, so sibling artifacts can in rare cases differ; reading
|
|
984
|
+
// the folder avoids guessing the exact name.
|
|
985
|
+
const files = await listFilesInDir(`${config.sbmigWorkingDirectory}/migrations`);
|
|
986
|
+
const resolve = (stem) => findArtifactFilename(files, `dry-run--${artifactBaseName}---${stem}`);
|
|
987
|
+
const artifacts = {
|
|
988
|
+
changedItems: resolve(`${itemType}-to-migrate`) ?? "",
|
|
989
|
+
pipelineSummary: resolve(`${itemType}-migration-pipeline-summary`) ?? "",
|
|
990
|
+
inputFull: resolve(`${itemType}-input-full`),
|
|
991
|
+
languageStateMap: resolve("language-publish-state-map"),
|
|
992
|
+
draftAfterFull: isPreserveLayers
|
|
993
|
+
? resolve("draft-current-after-full")
|
|
994
|
+
: null,
|
|
995
|
+
publishedAfterFull: isPreserveLayers
|
|
996
|
+
? resolve("published-layer-after-full")
|
|
997
|
+
: null,
|
|
998
|
+
dirtyPublishedRecords: isPreserveLayers
|
|
999
|
+
? resolve("dirty-published-records")
|
|
1000
|
+
: null,
|
|
1001
|
+
};
|
|
1002
|
+
const manifest = {
|
|
1003
|
+
manifestVersion: 1,
|
|
1004
|
+
createdAt: new Date().toISOString(),
|
|
1005
|
+
kind: "migrate-content-continue-manifest",
|
|
1006
|
+
itemType,
|
|
1007
|
+
from,
|
|
1008
|
+
to,
|
|
1009
|
+
migrateFrom,
|
|
1010
|
+
fromFilePath: fromFilePath ?? null,
|
|
1011
|
+
publicationMode,
|
|
1012
|
+
migrationConfigNames: appliedMigrationNames,
|
|
1013
|
+
publishLanguages: publicationMode === "save-only"
|
|
1014
|
+
? null
|
|
1015
|
+
: { requested: effectivePublicationLanguages },
|
|
1016
|
+
resolvedPublishLanguages: resolvedPublishLanguages ?? null,
|
|
1017
|
+
artifactBaseName,
|
|
1018
|
+
useDatestamp,
|
|
1019
|
+
artifacts,
|
|
1020
|
+
};
|
|
1021
|
+
await createAndSaveToFile({
|
|
1022
|
+
datestamp: useDatestamp,
|
|
1023
|
+
ext: "json",
|
|
1024
|
+
filename: `dry-run--${artifactBaseName}---${itemType}-continue-manifest`,
|
|
1025
|
+
folder: "migrations",
|
|
1026
|
+
res: manifest,
|
|
1027
|
+
}, config);
|
|
1028
|
+
};
|
|
813
1029
|
export const doTheMigration = async ({ itemType = "story", from, itemsToMigrate, migrationConfig, migrationConfigs, to, dryRun, publicationMode = DEFAULT_PUBLICATION_MODE, publicationLanguages, migrateFrom, fromFilePath, fileName, languagePublishStatePath, }, config) => {
|
|
814
1030
|
const preparedMigrationConfigs = migrationConfigs ||
|
|
815
1031
|
prepareMigrationConfigs({
|
|
@@ -952,6 +1168,23 @@ export const doTheMigration = async ({ itemType = "story", from, itemsToMigrate,
|
|
|
952
1168
|
languagePublishStatePath,
|
|
953
1169
|
pipelineResult,
|
|
954
1170
|
}, config);
|
|
1171
|
+
await saveContinueManifest({
|
|
1172
|
+
artifactBaseName,
|
|
1173
|
+
useDatestamp,
|
|
1174
|
+
dryRun,
|
|
1175
|
+
itemType,
|
|
1176
|
+
from,
|
|
1177
|
+
to,
|
|
1178
|
+
migrateFrom,
|
|
1179
|
+
fromFilePath,
|
|
1180
|
+
publicationMode,
|
|
1181
|
+
effectivePublicationLanguages,
|
|
1182
|
+
resolvedPublishLanguages,
|
|
1183
|
+
appliedMigrationNames: preparedMigrationConfigs.map((preparedMigrationConfig) => preparedMigrationConfig.migrationConfigName),
|
|
1184
|
+
pipelineResult,
|
|
1185
|
+
publishedLayerContext,
|
|
1186
|
+
publishedLayerPipelineResult,
|
|
1187
|
+
}, config);
|
|
955
1188
|
if (dryRun) {
|
|
956
1189
|
console.log(" ");
|
|
957
1190
|
if (publishedLayerPipelineResult) {
|
|
@@ -961,100 +1194,24 @@ export const doTheMigration = async ({ itemType = "story", from, itemsToMigrate,
|
|
|
961
1194
|
Logger.success(`[DRY RUN] No API changes were made. Review the saved migration file for details.`);
|
|
962
1195
|
return;
|
|
963
1196
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
options: {
|
|
983
|
-
publish: shouldPublishForPublicationMode(publicationMode),
|
|
984
|
-
publishLanguages: resolvedPublishLanguages,
|
|
985
|
-
preservePublishState: shouldPublishForPublicationMode(publicationMode),
|
|
986
|
-
languagePublishStateMap,
|
|
987
|
-
},
|
|
988
|
-
}, config)
|
|
989
|
-
: [];
|
|
990
|
-
const dirtyWriteResults = publishedLayerPipelineResult
|
|
991
|
-
? await writeDirtyPublishedLayerStories({
|
|
992
|
-
context: publishedLayerContext,
|
|
993
|
-
draftPipelineResult: pipelineResult,
|
|
994
|
-
publishedPipelineResult: publishedLayerPipelineResult,
|
|
995
|
-
to,
|
|
996
|
-
resolvedPublishLanguages,
|
|
997
|
-
languagePublishStateMap,
|
|
998
|
-
}, config)
|
|
999
|
-
: [];
|
|
1000
|
-
writeResults = [...nonDirtyWriteResults, ...dirtyWriteResults];
|
|
1001
|
-
}
|
|
1002
|
-
else {
|
|
1003
|
-
writeResults = await managementApi.stories.updateStories({
|
|
1004
|
-
stories: pipelineResult.changedItems,
|
|
1005
|
-
spaceId: to,
|
|
1006
|
-
options: {
|
|
1007
|
-
publish: shouldPublishForPublicationMode(publicationMode),
|
|
1008
|
-
publishLanguages: resolvedPublishLanguages,
|
|
1009
|
-
preservePublishState: shouldPublishForPublicationMode(publicationMode),
|
|
1010
|
-
publishDirtyPublishedStories: shouldPublishDirtyDrafts(publicationMode),
|
|
1011
|
-
languagePublishStateMap,
|
|
1012
|
-
},
|
|
1013
|
-
}, config);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
else if (itemType === "preset") {
|
|
1017
|
-
writeResults = await managementApi.presets.updatePresets({
|
|
1018
|
-
presets: pipelineResult.changedItems,
|
|
1019
|
-
spaceId: to,
|
|
1020
|
-
options: {},
|
|
1021
|
-
}, config);
|
|
1022
|
-
}
|
|
1023
|
-
const writeSummary = summarizeMutationWriteResults(writeResults);
|
|
1024
|
-
try {
|
|
1025
|
-
await saveMigrationRunLog({
|
|
1026
|
-
artifactBaseName,
|
|
1027
|
-
useDatestamp,
|
|
1028
|
-
from,
|
|
1029
|
-
to,
|
|
1030
|
-
itemType,
|
|
1031
|
-
dryRun,
|
|
1032
|
-
publish: shouldPublishForPublicationMode(publicationMode),
|
|
1033
|
-
publishLanguages: effectivePublicationLanguages,
|
|
1034
|
-
resolvedPublishLanguages,
|
|
1035
|
-
migrateFrom,
|
|
1036
|
-
fromFilePath,
|
|
1037
|
-
languagePublishStatePath,
|
|
1038
|
-
pipelineResult,
|
|
1039
|
-
writeResults,
|
|
1040
|
-
writeSummary,
|
|
1041
|
-
}, config);
|
|
1042
|
-
}
|
|
1043
|
-
catch (error) {
|
|
1044
|
-
Logger.warning(`[MIGRATION] Could not write migration run log: ${error instanceof Error ? error.message : String(error)}`);
|
|
1045
|
-
}
|
|
1046
|
-
if (writeSummary.failed === 0) {
|
|
1047
|
-
Logger.success(`[MIGRATION] Update complete. ${writeSummary.successful}/${writeSummary.total} ${itemType}(s) updated successfully.`);
|
|
1048
|
-
return;
|
|
1049
|
-
}
|
|
1050
|
-
Logger.warning(`[MIGRATION] Update complete with partial failures. ${writeSummary.successful}/${writeSummary.total} ${itemType}(s) updated successfully, ${writeSummary.failed} failed.`);
|
|
1051
|
-
writeSummary.failedItems.slice(0, 10).forEach((item) => {
|
|
1052
|
-
const label = item.slug || item.name || item.id || "unknown";
|
|
1053
|
-
Logger.error(`[MIGRATION] Failed ${itemType}: ${String(label)}`);
|
|
1054
|
-
});
|
|
1055
|
-
if (writeSummary.failedItems.length > 10) {
|
|
1056
|
-
Logger.warning(`[MIGRATION] Showing first 10 failed ${itemType}(s) only.`);
|
|
1057
|
-
}
|
|
1197
|
+
await finalizeMigration({
|
|
1198
|
+
itemType,
|
|
1199
|
+
from,
|
|
1200
|
+
to,
|
|
1201
|
+
migrateFrom,
|
|
1202
|
+
fromFilePath,
|
|
1203
|
+
languagePublishStatePath,
|
|
1204
|
+
publicationMode,
|
|
1205
|
+
effectivePublicationLanguages,
|
|
1206
|
+
resolvedPublishLanguages,
|
|
1207
|
+
languagePublishStateMap,
|
|
1208
|
+
appliedMigrationNames: preparedMigrationConfigs.map((preparedMigrationConfig) => preparedMigrationConfig.migrationConfigName),
|
|
1209
|
+
pipelineResult,
|
|
1210
|
+
publishedLayerContext,
|
|
1211
|
+
publishedLayerPipelineResult,
|
|
1212
|
+
artifactBaseName,
|
|
1213
|
+
useDatestamp,
|
|
1214
|
+
}, config);
|
|
1058
1215
|
};
|
|
1059
1216
|
const saveBackupToFile = async ({ itemType, res, folder, filename }, config) => {
|
|
1060
1217
|
await createAndSaveToFile({
|
|
@@ -1109,3 +1266,142 @@ export const migrateProvidedComponentsDataInStories = async ({ itemType, migrati
|
|
|
1109
1266
|
languagePublishStatePath,
|
|
1110
1267
|
}, config);
|
|
1111
1268
|
};
|
|
1269
|
+
const CONTINUE_MANIFEST_RE = /---(?:story|preset)-continue-manifest(?:__.*)?\.json$/;
|
|
1270
|
+
const readMigrationJson = async (config, filename) => {
|
|
1271
|
+
const content = await readFile(`${config.sbmigWorkingDirectory}/migrations/${filename}`);
|
|
1272
|
+
if (content === undefined) {
|
|
1273
|
+
throw new Error(`Could not read migration artifact: migrations/${filename}`);
|
|
1274
|
+
}
|
|
1275
|
+
try {
|
|
1276
|
+
return JSON.parse(content);
|
|
1277
|
+
}
|
|
1278
|
+
catch {
|
|
1279
|
+
throw new Error(`Migration artifact is not valid JSON: migrations/${filename}`);
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
/**
|
|
1283
|
+
* Discover the dry-run `continue` manifest in the migrations folder, load the
|
|
1284
|
+
* artifacts it points at, and reconstruct the exact inputs `finalizeMigration`
|
|
1285
|
+
* needs. Returns a summary (for the confirmation prompt) and a `run()` thunk that
|
|
1286
|
+
* performs the write. Does NOT pull stories, transform, or fetch published layers.
|
|
1287
|
+
*/
|
|
1288
|
+
export const prepareContinueMigration = async ({ manifestFileName }, config) => {
|
|
1289
|
+
const files = await listFilesInDir(`${config.sbmigWorkingDirectory}/migrations`);
|
|
1290
|
+
const manifestFiles = files.filter((file) => CONTINUE_MANIFEST_RE.test(file));
|
|
1291
|
+
let chosen;
|
|
1292
|
+
if (manifestFileName) {
|
|
1293
|
+
if (!files.includes(manifestFileName)) {
|
|
1294
|
+
throw new Error(`Manifest not found in migrations folder: ${manifestFileName}`);
|
|
1295
|
+
}
|
|
1296
|
+
chosen = manifestFileName;
|
|
1297
|
+
}
|
|
1298
|
+
else if (manifestFiles.length === 1) {
|
|
1299
|
+
chosen = manifestFiles[0];
|
|
1300
|
+
}
|
|
1301
|
+
else if (manifestFiles.length === 0) {
|
|
1302
|
+
throw new Error("No dry-run found to continue. Run `sb-mig migrate content … --dryRun` first.");
|
|
1303
|
+
}
|
|
1304
|
+
else {
|
|
1305
|
+
throw new Error(`Multiple dry-run manifests found. Re-run with --manifest <filename>:\n${manifestFiles
|
|
1306
|
+
.map((file) => ` - ${file}`)
|
|
1307
|
+
.join("\n")}`);
|
|
1308
|
+
}
|
|
1309
|
+
const manifest = (await readMigrationJson(config, chosen));
|
|
1310
|
+
if (manifest.kind !== "migrate-content-continue-manifest") {
|
|
1311
|
+
throw new Error(`Not a continue manifest: ${chosen}`);
|
|
1312
|
+
}
|
|
1313
|
+
if (manifest.manifestVersion !== 1) {
|
|
1314
|
+
throw new Error(`Unsupported continue manifest version (${manifest.manifestVersion}) in ${chosen}.`);
|
|
1315
|
+
}
|
|
1316
|
+
const { artifacts } = manifest;
|
|
1317
|
+
if (!artifacts?.changedItems) {
|
|
1318
|
+
throw new Error(`Manifest ${chosen} is missing the changed-items artifact reference.`);
|
|
1319
|
+
}
|
|
1320
|
+
const changedItems = (await readMigrationJson(config, artifacts.changedItems));
|
|
1321
|
+
const pipelineSummary = artifacts.pipelineSummary
|
|
1322
|
+
? await readMigrationJson(config, artifacts.pipelineSummary)
|
|
1323
|
+
: null;
|
|
1324
|
+
const languagePublishStateMap = artifacts.languageStateMap
|
|
1325
|
+
? (await readMigrationJson(config, artifacts.languageStateMap))
|
|
1326
|
+
: undefined;
|
|
1327
|
+
const isPreserveLayers = shouldUsePublishedLayerMode(manifest.publicationMode) &&
|
|
1328
|
+
Boolean(artifacts.dirtyPublishedRecords);
|
|
1329
|
+
let publishedLayerContext;
|
|
1330
|
+
let publishedLayerPipelineResult = null;
|
|
1331
|
+
if (isPreserveLayers && artifacts.dirtyPublishedRecords) {
|
|
1332
|
+
const dirty = (await readMigrationJson(config, artifacts.dirtyPublishedRecords));
|
|
1333
|
+
publishedLayerContext = {
|
|
1334
|
+
records: [],
|
|
1335
|
+
publishedLayerInputItems: [],
|
|
1336
|
+
dirtyPublishedRecords: dirty.dirtyPublishedRecords ?? [],
|
|
1337
|
+
missingPublishedLayerRecords: [],
|
|
1338
|
+
};
|
|
1339
|
+
const publishedFinalItems = artifacts.publishedAfterFull
|
|
1340
|
+
? (await readMigrationJson(config, artifacts.publishedAfterFull))
|
|
1341
|
+
: [];
|
|
1342
|
+
const publishedChangedIdSet = new Set((dirty.publishedChangedIds ?? []).map(String));
|
|
1343
|
+
publishedLayerPipelineResult = {
|
|
1344
|
+
changedItems: publishedFinalItems.filter((item) => publishedChangedIdSet.has(storyIdOf(item))),
|
|
1345
|
+
finalItems: publishedFinalItems,
|
|
1346
|
+
stepReports: [],
|
|
1347
|
+
totalItems: publishedFinalItems.length,
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
const draftFinalItems = artifacts.draftAfterFull
|
|
1351
|
+
? (await readMigrationJson(config, artifacts.draftAfterFull))
|
|
1352
|
+
: changedItems;
|
|
1353
|
+
const pipelineResult = {
|
|
1354
|
+
changedItems,
|
|
1355
|
+
finalItems: draftFinalItems,
|
|
1356
|
+
stepReports: Array.isArray(pipelineSummary?.steps)
|
|
1357
|
+
? pipelineSummary.steps
|
|
1358
|
+
: [],
|
|
1359
|
+
totalItems: typeof pipelineSummary?.totalItems === "number"
|
|
1360
|
+
? pipelineSummary.totalItems
|
|
1361
|
+
: changedItems.length,
|
|
1362
|
+
};
|
|
1363
|
+
const resolvedPublishLanguages = manifest.resolvedPublishLanguages ?? undefined;
|
|
1364
|
+
const effectivePublicationLanguages = manifest.publishLanguages?.requested;
|
|
1365
|
+
const artifactFiles = [
|
|
1366
|
+
artifacts.changedItems,
|
|
1367
|
+
artifacts.pipelineSummary,
|
|
1368
|
+
artifacts.draftAfterFull,
|
|
1369
|
+
artifacts.publishedAfterFull,
|
|
1370
|
+
artifacts.dirtyPublishedRecords,
|
|
1371
|
+
artifacts.languageStateMap,
|
|
1372
|
+
artifacts.inputFull,
|
|
1373
|
+
].filter((file) => Boolean(file));
|
|
1374
|
+
const summary = {
|
|
1375
|
+
manifestFileName: chosen,
|
|
1376
|
+
from: manifest.from,
|
|
1377
|
+
to: manifest.to,
|
|
1378
|
+
itemType: manifest.itemType,
|
|
1379
|
+
publicationMode: manifest.publicationMode,
|
|
1380
|
+
migrationConfigNames: manifest.migrationConfigNames ?? [],
|
|
1381
|
+
resolvedPublishLanguages: resolvedPublishLanguages ?? [],
|
|
1382
|
+
changedCount: changedItems.length,
|
|
1383
|
+
dirtyPublishedCount: publishedLayerContext?.dirtyPublishedRecords.length ?? 0,
|
|
1384
|
+
artifactFiles,
|
|
1385
|
+
};
|
|
1386
|
+
const run = async () => {
|
|
1387
|
+
await finalizeMigration({
|
|
1388
|
+
itemType: manifest.itemType,
|
|
1389
|
+
from: manifest.from,
|
|
1390
|
+
to: manifest.to,
|
|
1391
|
+
migrateFrom: manifest.migrateFrom,
|
|
1392
|
+
fromFilePath: manifest.fromFilePath ?? undefined,
|
|
1393
|
+
publicationMode: manifest.publicationMode,
|
|
1394
|
+
effectivePublicationLanguages,
|
|
1395
|
+
resolvedPublishLanguages,
|
|
1396
|
+
languagePublishStateMap,
|
|
1397
|
+
appliedMigrationNames: manifest.migrationConfigNames ?? [],
|
|
1398
|
+
pipelineResult,
|
|
1399
|
+
publishedLayerContext,
|
|
1400
|
+
publishedLayerPipelineResult,
|
|
1401
|
+
artifactBaseName: manifest.artifactBaseName,
|
|
1402
|
+
useDatestamp: manifest.useDatestamp,
|
|
1403
|
+
continuedFromManifest: chosen,
|
|
1404
|
+
}, config);
|
|
1405
|
+
};
|
|
1406
|
+
return { summary, run };
|
|
1407
|
+
};
|
|
@@ -23,6 +23,7 @@ export interface MigrationRunLogRecord {
|
|
|
23
23
|
resolved?: string[];
|
|
24
24
|
};
|
|
25
25
|
dryRun: boolean;
|
|
26
|
+
continuedFromManifest?: string;
|
|
26
27
|
migrationConfigs: string[];
|
|
27
28
|
totalItems: number;
|
|
28
29
|
totalChangedItems: number;
|
|
@@ -76,8 +77,9 @@ interface SaveMigrationRunLogArgs {
|
|
|
76
77
|
pipelineResult: MigrationPipelineResult;
|
|
77
78
|
writeResults: PromiseSettledResult<MutationWriteResult>[];
|
|
78
79
|
writeSummary: MutationWriteSummary;
|
|
80
|
+
continuedFromManifest?: string;
|
|
79
81
|
}
|
|
80
|
-
export declare const buildMigrationRunLogRecords: ({ from, to, itemType, dryRun, publish, publishLanguages, resolvedPublishLanguages, migrateFrom, fromFilePath, languagePublishStatePath, pipelineResult, writeResults, writeSummary, }: Omit<SaveMigrationRunLogArgs, "artifactBaseName" | "useDatestamp">) => MigrationRunLogRecord[];
|
|
82
|
+
export declare const buildMigrationRunLogRecords: ({ from, to, itemType, dryRun, publish, publishLanguages, resolvedPublishLanguages, migrateFrom, fromFilePath, languagePublishStatePath, pipelineResult, writeResults, writeSummary, continuedFromManifest, }: Omit<SaveMigrationRunLogArgs, "artifactBaseName" | "useDatestamp">) => MigrationRunLogRecord[];
|
|
81
83
|
export declare const recordsToJsonl: (records: MigrationRunLogRecord[]) => string;
|
|
82
84
|
export declare const saveMigrationRunLog: (args: SaveMigrationRunLogArgs, config: RequestBaseConfig) => Promise<void>;
|
|
83
85
|
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, publishLanguages, resolvedPublishLanguages, migrateFrom, fromFilePath, languagePublishStatePath, pipelineResult, writeResults, writeSummary, }) => {
|
|
34
|
+
export const buildMigrationRunLogRecords = ({ from, to, itemType, dryRun, publish, publishLanguages, resolvedPublishLanguages, migrateFrom, fromFilePath, languagePublishStatePath, pipelineResult, writeResults, writeSummary, continuedFromManifest, }) => {
|
|
35
35
|
const timestamp = new Date().toISOString();
|
|
36
36
|
const runId = `${itemType}-${timestamp}`;
|
|
37
37
|
const baseRecord = {
|
|
@@ -57,6 +57,7 @@ export const buildMigrationRunLogRecords = ({ from, to, itemType, dryRun, publis
|
|
|
57
57
|
}
|
|
58
58
|
: {}),
|
|
59
59
|
dryRun: Boolean(dryRun),
|
|
60
|
+
...(continuedFromManifest ? { continuedFromManifest } : {}),
|
|
60
61
|
migrationConfigs: pipelineResult.stepReports.map((step) => step.migrationConfig),
|
|
61
62
|
totalItems: pipelineResult.totalItems,
|
|
62
63
|
totalChangedItems: pipelineResult.changedItems.length,
|
|
@@ -5,7 +5,7 @@ export declare const publishedLayerExportDescription = "\n USAGE\n $ s
|
|
|
5
5
|
export declare const languagePublishStateDescription = "\n USAGE\n $ sb-mig language-publish-state --from [spaceId]\n\n DESCRIPTION\n Read stories from a source Storyblok space and write a JSON map of default and translated language publication states.\n This command is read-only against Storyblok. It uses Management API for story listing and default-language state, and Delivery API for translated language published/draft comparisons.\n\n FLAGS\n --from Source space ID to inspect. Required.\n --accessToken Optional source space Delivery API access token override. Falls back to configured accessToken.\n --languages Languages to inspect: all, default,fr,de. Default: all.\n --withSlug Exact story full_slug to inspect. Can be repeated.\n --startsWith Filter stories by starts_with prefix.\n --fileName Stable output base name under sbmig/language-publish-state.\n --outputPath Explicit output path for the generated JSON file.\n\n SIDE EFFECTS\n Read-only against Storyblok. Writes a local JSON publish-state map.\n\n EXAMPLES\n $ sb-mig language-publish-state --from 12345 --startsWith about-ef --languages all --fileName about-ef-prod\n $ sb-mig language-publish-state --from 12345 --accessToken xxx --withSlug about-ef/testimonials --languages default,fr\n";
|
|
6
6
|
export declare const syncDescription = "\n USAGE\n $ sb-mig sync components [component-name ...] | --all [--presets] [--ssot] [--dry-run]\n $ sb-mig sync roles [role-name ...] | --all [--dry-run]\n $ sb-mig sync datasources [datasource-name ...] | --all [--dry-run]\n $ sb-mig sync plugins [plugin-name ...] [--dry-run]\n $ sb-mig sync content (--all | --stories | --assets) --from [spaceId] --to [spaceId-or-file] --syncDirection [direction]\n\n DESCRIPTION\n Synchronize components, roles, datasources, plugins, stories, and assets with Storyblok.\n\n COMMANDS\n components Sync local component schema files to Storyblok.\n roles Sync local role schema files to Storyblok.\n datasources Sync local datasource schema files to Storyblok.\n plugins Sync a provided plugin. Run from a plugin folder with ./dist/export.js.\n content Sync stories and/or assets between spaces, local files, or AWS content hub data.\n\n FLAGS\n --all Sync all supported resources for the selected command.\n --stories Sync only stories. [content only]\n --assets Sync only assets. [content only]\n --presets Also sync component presets and set default presets. [components only]\n --ssot Single Source of Truth mode. Removes GUI-only components and replaces them with code versions. [components only]\n --packageName External package name used when resolving provided components. [components only]\n --dry-run Preview planned changes without making writes. [components, roles, datasources, plugins, content]\n --yes Skip confirmation prompts. [components --ssot, destructive content sync]\n --from Source space ID or local story file name, depending on --syncDirection. [content only]\n --to Target space ID or local output file name, depending on --syncDirection. [content only]\n --syncDirection Sync direction. Values: fromSpaceToFile, fromFileToSpace, fromSpaceToSpace, fromAWSToSpace. [content only]\n\n SIDE EFFECTS\n components, roles, datasources, and plugins write to Storyblok unless --dry-run is passed.\n content with fromSpaceToSpace or fromFileToSpace can delete all target stories before recreating them.\n content with fromSpaceToFile writes local story and asset backup files.\n\n GOTCHAS\n --syncDirection is required for content sync.\n fromFileToSpace is implemented for stories, but assets log that it is not implemented.\n fromAWSToSpace is implemented for stories. Assets log that unsupported directions are not implemented.\n --ssot is destructive for components and prompts unless --yes is passed.\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 --all --ssot --yes\n $ sb-mig sync components accordion accordion-item --presets\n\n $ sb-mig sync roles --all\n $ sb-mig sync roles admin editor --dry-run\n\n $ sb-mig sync datasources --all\n $ sb-mig sync datasources countries cities --dry-run\n\n $ sb-mig sync plugins my-awesome-plugin\n\n $ sb-mig sync content --all --from 12345 --to 67890 --syncDirection fromSpaceToSpace --yes\n $ sb-mig sync content --stories --from 12345 --to all-stories-backup --syncDirection fromSpaceToFile\n $ sb-mig sync content --stories --from all-stories-backup --to 67890 --syncDirection fromFileToSpace --dry-run\n $ sb-mig sync content --stories --from 12345 --to 67890 --syncDirection fromAWSToSpace\n";
|
|
7
7
|
export declare const copyDescription = "\n USAGE\n $ sb-mig copy stories --from [spaceId] --to [spaceId] --source [full_slug] --destination [target_folder_full_slug]\n $ sb-mig copy stories --from [spaceId] --to [spaceId] --source [folder_full_slug]\n $ sb-mig copy stories --from [spaceId] --to [spaceId] --source [folder_full_slug]/* --destination [target_folder_full_slug]\n $ sb-mig copy stories --from [spaceId] --to [spaceId] --source [folder_full_slug] --mode self --destination /\n $ sb-mig copy assets --from [spaceId] --to [spaceId] --all\n $ sb-mig copy assets --from [spaceId] --to [spaceId] --asset [asset_id_or_filename]\n $ sb-mig copy assets --from [spaceId] --to [spaceId] --assetFolder [folder_id_or_path]\n $ sb-mig copy assets --from [spaceId] --to [spaceId] --referenced-by-stories --source [story_or_folder_full_slug]\n $ sb-mig copy assets --from [spaceId] --to [spaceId] --all --dry-run\n\n DESCRIPTION\n Copy Storyblok stories, folders, assets, or asset folders from one space to another.\n\n COMMANDS\n stories Copy one story, one folder subtree, a folder's children, or one folder shell.\n assets Copy or plan all assets and asset folders with durable manifests.\n\n FLAGS\n --from Source Storyblok space ID. Falls back to configured spaceId.\n --to Target Storyblok space ID. Falls back to configured spaceId.\n --source Source story or folder full_slug. Use folder/* to copy a folder's children without the folder root.\n --destination Target folder full_slug where copied stories are attached. Omit, '/', or 'root' to copy into target root.\n --mode Copy mode: subtree, children, or self. Default: subtree. folder/* defaults to children.\n --with-assets For copy stories, copy referenced assets first and rewrite copied stories to target asset IDs/filenames.\n --publicationMode\n How copy stories should preserve Storyblok publication state. Values: preserve-layers, collapse-draft, save-only. Default: preserve-layers. [stories only]\n --publicationLanguages\n Language scope to publish when publicationMode publishes stories. Values: default, all, or comma-separated Storyblok language codes. Default: all. [stories only]\n --all Select all assets and asset folders. [assets only]\n --asset Select one source asset by numeric ID, exact Storyblok asset URL, or unique file name. Repeatable. [assets only]\n --assetFolder Select one source asset folder by numeric ID or folder path. Includes descendants and assets in that subtree. Repeatable. [assets only]\n --referenced-by-stories\n Select assets referenced by a story/folder scope. Requires --source. [assets only]\n --dry-run Preview story paths, manifest-mapped references, and likely target conflicts without writing to Storyblok.\n --outputPath Optional JSON file path for a dry-run copy plan artifact. Only writes locally when passed.\n\n LEGACY FLAGS\n --sourceSpace Alias for --from.\n --targetSpace Alias for --to.\n --what Alias for --source.\n --where Alias for --destination.\n\n SIDE EFFECTS\n copy stories writes copied stories into the target Storyblok space unless --dry-run is passed.\n copy stories writes story ID/UUID manifests under .sb-mig/copy/<source>/<target>/ during apply.\n copy stories --with-assets writes referenced asset folders/assets before story writes unless --dry-run is passed.\n copy assets writes asset folders and assets into the target Storyblok space unless --dry-run is passed.\n copy assets writes JSONL manifests under .sb-mig/copy/<source>/<target>/ during apply.\n --outputPath writes a local JSON report for dry-run or apply.\n\n GOTCHAS\n --source must resolve to an existing source story or folder.\n --destination must resolve to an existing target folder unless it is omitted, '/', or 'root'.\n mode 'subtree' copies a folder and all descendants. This is the default for folders.\n mode 'children' copies a folder's descendants without the folder root.\n mode 'self' copies only the source story or folder shell.\n copy stories creates or matches target story shells, then fills them with rewritten source content.\n copy stories matches existing targets by manifest first, then target full_slug when safe, so reruns can reuse mapped target stories.\n copy stories rewrites mapped asset and story references after story manifests exist.\n copy stories creates shells as save-only drafts, then applies publicationMode after full rewritten content is saved.\n publicationMode preserve-layers publishes clean published source stories; for dirty published source stories it copies the source published version, publishes it in target, then restores the source draft/current layer as save-only.\n publicationMode collapse-draft publishes published source stories from their current draft/current JSON.\n publicationMode save-only never publishes copied stories.\n --publicationLanguages cannot be used with --publicationMode save-only.\n copy stories --with-assets scans selected stories with source component schemas and only copies referenced assets it can resolve from the source asset list.\n --dry-run checks likely target path conflicts and reports mapped/planned/unresolved story and asset references, with occurrence and unique-asset counts in JSON output.\n Without --with-assets, run copy assets first when copied stories should point to target-space assets.\n copy assets supports --all, --asset, or --assetFolder. Use only one selector family per run.\n copy assets --asset includes the selected asset's folder ancestors so target folder mappings can be preserved.\n copy assets --assetFolder includes the selected folder, descendants, folder ancestors, and assets inside that selected subtree.\n copy assets --referenced-by-stories scans selected stories with source component schemas and copies only source-space assets referenced by that story scope.\n copy assets matches by manifest first, then safe target folder path or unique asset file name before creating.\n copy assets uploads assets, finalizes the upload, and writes source-to-target asset/folder manifests.\n\n EXAMPLES\n $ sb-mig copy stories --from 12345 --to 67890 --source blog/post-1 --destination imported\n $ sb-mig copy stories --from 12345 --to 67890 --source blog --destination imported\n $ sb-mig copy stories --from 12345 --to 67890 --source blog/* --destination imported\n $ sb-mig copy stories --from 12345 --to 67890 --source blog --mode self --destination /\n $ sb-mig copy stories --from 12345 --to 67890 --source blog --destination imported --with-assets\n $ sb-mig copy stories --from 12345 --to 67890 --source blog --destination imported --with-assets --publicationMode preserve-layers\n $ sb-mig copy stories --from 12345 --to 67890 --source blog --destination imported --publicationMode collapse-draft --publicationLanguages default,fr,de\n $ sb-mig copy stories --from 12345 --to 67890 --source blog --destination imported --publicationMode save-only\n $ sb-mig copy stories --from 12345 --to 67890 --source blog --destination imported --dry-run\n $ sb-mig copy stories --from 12345 --to 67890 --source blog --destination imported --with-assets --dry-run --outputPath sbmig/copy-plans/blog-copy.json\n $ sb-mig copy stories --from 12345 --to 67890 --source blog --destination imported --dry-run --outputPath sbmig/copy-plans/blog-copy.json\n $ sb-mig copy assets --from 12345 --to 67890 --all\n $ sb-mig copy assets --from 12345 --to 67890 --asset 987654\n $ sb-mig copy assets --from 12345 --to 67890 --asset hero.jpg --dry-run\n $ sb-mig copy assets --from 12345 --to 67890 --assetFolder Marketing/Heroes --dry-run\n $ sb-mig copy assets --from 12345 --to 67890 --referenced-by-stories --source blog --dry-run --outputPath sbmig/copy-plans/blog-assets.json\n $ sb-mig copy assets --from 12345 --to 67890 --all --dry-run\n $ sb-mig copy assets --from 12345 --to 67890 --all --dry-run --outputPath sbmig/copy-plans/assets-copy.json\n";
|
|
8
|
-
export declare const migrateDescription = "\n USAGE\n $ sb-mig migrate content [component-name ...] --from [spaceId] --to [spaceId] --migration [migration-config]\n $ sb-mig migrate content --all --from [spaceId] --to [spaceId] --migration [migration-config]\n $ sb-mig migrate presets --all --from [spaceId-or-file] --to [spaceId] --migration [migration-config]\n\n DESCRIPTION\n Migrate story content or presets using local migration config files.\n This is a potentially destructive command. It prompts for confirmation unless --yes or --dry-run is passed.\n\n COMMANDS\n content Migrate story content for all components or provided component names.\n presets Migrate presets. Supports --all and exactly one --migration value.\n\n FLAGS\n --from Source space ID, or local file name when --migrate-from file is used.\n --fromFilePath Direct path to stories or presets JSON when using --migrate-from file.\n --to Target Storyblok space ID.\n --migrate-from Migrate from space or file. Default: space.\n --migration Migration file name without extension. Can be repeated for ordered content pipelines. Presets support exactly one.\n --migrationComponentAlias\n Add extra component aliases for a migration. Repeatable. Format: <migration>:<source>=<alias1>,<alias2>.\n --migrationComponents\n Override the exact component scope for a migration. Repeatable. Format: <migration>:<component1>,<component2>.\n --withSlug Filter stories by full_slug. Can be repeated. [content only]\n --startsWith Filter stories by starts_with prefix. [content only]\n --yes Skip confirmation prompts.\n --dry-run Preview what would be migrated without making API changes.\n --publicationMode How migrate content should preserve Storyblok publication state. Values: preserve-layers, collapse-draft, save-only. Default: preserve-layers. [content only]\n --publicationLanguages\n Language scope to inspect and preserve when publicationMode publishes stories. Values: default, all, or comma-separated Storyblok language codes. Default: all. [content only]\n --languagePublishStatePath\n Optional JSON file generated by language-publish-state. When omitted, migrate builds the map automatically for selected stories. [content only]\n --fileName Stable base name for migration output files.\n\n SIDE EFFECTS\n content writes migrated stories to Storyblok unless --dry-run is passed.\n content creates a story backup before provided-component migrations unless --dry-run is passed.\n presets backs up all remote presets before writing migrated presets unless --dry-run is passed.\n\n GOTCHAS\n At least one --migration value is required.\n preserve-layers currently requires --migrate-from space and requires --from and --to to be the same Storyblok space.\n --publicationLanguages cannot be used with --publicationMode save-only.\n --languagePublishStatePath cannot be used with --publicationMode save-only.\n --publicationMode, --publicationLanguages, and --languagePublishStatePath are only supported for migrate content, not migrate presets.\n Legacy flags --publish, --publishLanguages, and --preservePublishedLayer are rejected. Use --publicationMode and --publicationLanguages instead.\n\n EXAMPLES\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --dry-run\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration migration-a --migration migration-b --migration migration-c --yes\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 --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 --publicationMode preserve-layers --yes\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --publicationMode collapse-draft --publicationLanguages default,fr,de --yes\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.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 presets --all --from 12345 --to 12345 --migration preset-migration --dry-run\n $ sb-mig migrate presets --all --migrate-from file --fromFilePath sbmig/presets/presets-backup.json --to 12345 --migration preset-migration\n";
|
|
8
|
+
export declare const migrateDescription = "\n USAGE\n $ sb-mig migrate content [component-name ...] --from [spaceId] --to [spaceId] --migration [migration-config]\n $ sb-mig migrate content --all --from [spaceId] --to [spaceId] --migration [migration-config]\n $ sb-mig migrate presets --all --from [spaceId-or-file] --to [spaceId] --migration [migration-config]\n\n DESCRIPTION\n Migrate story content or presets using local migration config files.\n This is a potentially destructive command. It prompts for confirmation unless --yes or --dry-run is passed.\n\n COMMANDS\n content Migrate story content for all components or provided component names.\n presets Migrate presets. Supports --all and exactly one --migration value.\n continue Finish a previous --dry-run by writing its already-computed result to\n Storyblok. Skips pulling stories and re-running the migration, so it is\n much faster. Requires a content freeze between the dry-run and continue.\n\n FLAGS\n --from Source space ID, or local file name when --migrate-from file is used.\n --manifest [continue only] Continue manifest filename inside the migrations folder.\n Only needed when more than one dry-run manifest is present.\n --fromFilePath Direct path to stories or presets JSON when using --migrate-from file.\n --to Target Storyblok space ID.\n --migrate-from Migrate from space or file. Default: space.\n --migration Migration file name without extension. Can be repeated for ordered content pipelines. Presets support exactly one.\n --migrationComponentAlias\n Add extra component aliases for a migration. Repeatable. Format: <migration>:<source>=<alias1>,<alias2>.\n --migrationComponents\n Override the exact component scope for a migration. Repeatable. Format: <migration>:<component1>,<component2>.\n --withSlug Filter stories by full_slug. Can be repeated. [content only]\n --startsWith Filter stories by starts_with prefix. [content only]\n --yes Skip confirmation prompts.\n --dry-run Preview what would be migrated without making API changes.\n --publicationMode How migrate content should preserve Storyblok publication state. Values: preserve-layers, collapse-draft, save-only. Default: preserve-layers. [content only]\n --publicationLanguages\n Language scope to inspect and preserve when publicationMode publishes stories. Values: default, all, or comma-separated Storyblok language codes. Default: all. [content only]\n --languagePublishStatePath\n Optional JSON file generated by language-publish-state. When omitted, migrate builds the map automatically for selected stories. [content only]\n --fileName Stable base name for migration output files.\n\n SIDE EFFECTS\n content writes migrated stories to Storyblok unless --dry-run is passed.\n content creates a story backup before provided-component migrations unless --dry-run is passed.\n content --dry-run also writes a continue manifest so the run can later be finished with migrate continue.\n continue writes the dry-run's migrated stories to Storyblok and updates applied-backpack-migrations.json. It does not pull or re-transform stories.\n presets backs up all remote presets before writing migrated presets unless --dry-run is passed.\n\n GOTCHAS\n At least one --migration value is required.\n preserve-layers currently requires --migrate-from space and requires --from and --to to be the same Storyblok space.\n --publicationLanguages cannot be used with --publicationMode save-only.\n --languagePublishStatePath cannot be used with --publicationMode save-only.\n --publicationMode, --publicationLanguages, and --languagePublishStatePath are only supported for migrate content, not migrate presets.\n Legacy flags --publish, --publishLanguages, and --preservePublishedLayer are rejected. Use --publicationMode and --publicationLanguages instead.\n\n EXAMPLES\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --dry-run\n $ sb-mig migrate continue\n $ sb-mig migrate continue --manifest dry-run--12345--2026-6-2_13-5---story-continue-manifest.json --yes\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration migration-a --migration migration-b --migration migration-c --yes\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 --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 --publicationMode preserve-layers --yes\n $ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --publicationMode collapse-draft --publicationLanguages default,fr,de --yes\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.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 presets --all --from 12345 --to 12345 --migration preset-migration --dry-run\n $ sb-mig migrate presets --all --migrate-from file --fromFilePath sbmig/presets/presets-backup.json --to 12345 --migration preset-migration\n";
|
|
9
9
|
export declare const revertDescription = "\n USAGE\n $ sb-mig revert content --from [stories-file-name] --to [spaceId] [--yes]\n\n DESCRIPTION\n Restore stories from a local story backup file into a Storyblok space.\n\n COMMANDS\n content Revert content by updating target stories from a local story backup.\n\n FLAGS\n --from Local story backup file name to discover and load.\n --to Target Storyblok space ID to update.\n --yes Skip confirmation prompt.\n\n SIDE EFFECTS\n Writes stories to the target Storyblok space.\n Creates a target-space story backup before restoring.\n Restored stories are updated with publish: false.\n\n GOTCHAS\n This command restores local story files directly and does not run migration configs.\n The --from value is resolved as a local story file name, not a source space ID.\n\n EXAMPLES\n $ sb-mig revert content --from 12345--backup-before-migration --to 12345\n $ sb-mig revert content --from prod-stories-backup --to 12345 --yes\n";
|
|
10
10
|
export declare const discoverDescription = "\n USAGE\n $ sb-mig discover components --all [--write] [--file file-name]\n $ sb-mig discover migrations --all\n\n DESCRIPTION\n Discover local and external component schema files or migration config files.\n\n COMMANDS\n components Discover component schema files.\n migrations Discover migration config files.\n\n FLAGS\n --all Discover all components or migration config files.\n --write Write discovered component names to a local file. [components only]\n --file Output file name when --write is passed. [components only]\n\n SIDE EFFECTS\n Read-only unless --write is passed.\n --write creates or overwrites a local component list file.\n\n EXAMPLES\n $ sb-mig discover components --all\n $ sb-mig discover components --all --write\n $ sb-mig discover components --all --write --file all-components\n $ sb-mig discover migrations --all\n";
|
|
11
11
|
export declare const migrationsDescription = "\n USAGE\n $ sb-mig migrations recognize --from [version] [--to version]\n\n DESCRIPTION\n Recognize migration commands to run for a package upgrade by comparing an old version with a target version.\n\n COMMANDS\n recognize Print recommended story and preset migration commands.\n\n FLAGS\n --from Previous package version. Required.\n --to Target package version. Optional. Falls back to the installed @ef-global/backpack dependency version.\n\n SIDE EFFECTS\n Reads local applied-backpack-migrations.json when present.\n Reads local package.json when --to is omitted.\n Does not write files or call Storyblok.\n\n EXAMPLES\n $ sb-mig migrations recognize --from 3.4.0\n $ sb-mig migrations recognize --from 3.4.0 --to 4.0.0\n";
|
|
@@ -315,9 +315,14 @@ export const migrateDescription = `
|
|
|
315
315
|
COMMANDS
|
|
316
316
|
content Migrate story content for all components or provided component names.
|
|
317
317
|
presets Migrate presets. Supports --all and exactly one --migration value.
|
|
318
|
+
continue Finish a previous --dry-run by writing its already-computed result to
|
|
319
|
+
Storyblok. Skips pulling stories and re-running the migration, so it is
|
|
320
|
+
much faster. Requires a content freeze between the dry-run and continue.
|
|
318
321
|
|
|
319
322
|
FLAGS
|
|
320
323
|
--from Source space ID, or local file name when --migrate-from file is used.
|
|
324
|
+
--manifest [continue only] Continue manifest filename inside the migrations folder.
|
|
325
|
+
Only needed when more than one dry-run manifest is present.
|
|
321
326
|
--fromFilePath Direct path to stories or presets JSON when using --migrate-from file.
|
|
322
327
|
--to Target Storyblok space ID.
|
|
323
328
|
--migrate-from Migrate from space or file. Default: space.
|
|
@@ -340,6 +345,8 @@ export const migrateDescription = `
|
|
|
340
345
|
SIDE EFFECTS
|
|
341
346
|
content writes migrated stories to Storyblok unless --dry-run is passed.
|
|
342
347
|
content creates a story backup before provided-component migrations unless --dry-run is passed.
|
|
348
|
+
content --dry-run also writes a continue manifest so the run can later be finished with migrate continue.
|
|
349
|
+
continue writes the dry-run's migrated stories to Storyblok and updates applied-backpack-migrations.json. It does not pull or re-transform stories.
|
|
343
350
|
presets backs up all remote presets before writing migrated presets unless --dry-run is passed.
|
|
344
351
|
|
|
345
352
|
GOTCHAS
|
|
@@ -352,6 +359,8 @@ export const migrateDescription = `
|
|
|
352
359
|
|
|
353
360
|
EXAMPLES
|
|
354
361
|
$ sb-mig migrate content --all --from 12345 --to 12345 --migration file-with-migration --dry-run
|
|
362
|
+
$ sb-mig migrate continue
|
|
363
|
+
$ sb-mig migrate continue --manifest dry-run--12345--2026-6-2_13-5---story-continue-manifest.json --yes
|
|
355
364
|
$ sb-mig migrate content --all --from 12345 --to 12345 --migration migration-a --migration migration-b --migration migration-c --yes
|
|
356
365
|
$ sb-mig migrate content --all --from 12345 --to 12345 --migration colorPickerModeValues --migrationComponentAlias colorPickerModeValues:sb-button=sb-open-drift-button
|
|
357
366
|
$ sb-mig migrate content --all --from 12345 --to 12345 --migration colorPickerModeValues --migrationComponents colorPickerModeValues:sb-section,sb-tour-page-section
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import { migrateAllComponentsDataInStories, migrateProvidedComponentsDataInStories, } from "../../api/data-migration/component-data-migration.js";
|
|
2
|
+
import { migrateAllComponentsDataInStories, migrateProvidedComponentsDataInStories, prepareContinueMigration, } from "../../api/data-migration/component-data-migration.js";
|
|
3
3
|
import { buildStoryBackupBaseName } from "../../api/data-migration/file-naming.js";
|
|
4
4
|
import { parseMigrationComponentAliasFlags, parseMigrationComponentOverrideFlags, } from "../../api/data-migration/migration-component-scope.js";
|
|
5
5
|
import { managementApi } from "../../api/managementApi.js";
|
|
@@ -13,6 +13,7 @@ import { isItFactory, unpackElements, getFrom, getTo, } from "../utils/cli-utils
|
|
|
13
13
|
const MIGRATE_COMMANDS = {
|
|
14
14
|
content: "content",
|
|
15
15
|
presets: "presets",
|
|
16
|
+
continue: "continue",
|
|
16
17
|
};
|
|
17
18
|
const normalizeMigrationFlags = (migrationFlag) => {
|
|
18
19
|
if (Array.isArray(migrationFlag)) {
|
|
@@ -280,6 +281,31 @@ export const migrate = async (props) => {
|
|
|
280
281
|
}
|
|
281
282
|
break;
|
|
282
283
|
}
|
|
284
|
+
case MIGRATE_COMMANDS.continue: {
|
|
285
|
+
const manifestFileName = flags["manifest"];
|
|
286
|
+
const plan = await prepareContinueMigration({ manifestFileName }, apiConfig);
|
|
287
|
+
const { summary } = plan;
|
|
288
|
+
Logger.log("Found dry-run to continue:");
|
|
289
|
+
console.log(` manifest: ${summary.manifestFileName}`);
|
|
290
|
+
console.log(` space (from→to): ${summary.from} → ${summary.to}`);
|
|
291
|
+
console.log(` publication mode: ${summary.publicationMode}`);
|
|
292
|
+
console.log(` migrations: ${summary.migrationConfigNames.join(", ") || "(none recorded)"}`);
|
|
293
|
+
if (summary.resolvedPublishLanguages.length > 0) {
|
|
294
|
+
console.log(` languages: ${summary.resolvedPublishLanguages.join(", ")}`);
|
|
295
|
+
}
|
|
296
|
+
console.log(` ${summary.itemType}s to write: ${summary.changedCount} changed${summary.dirtyPublishedCount > 0
|
|
297
|
+
? ` (${summary.dirtyPublishedCount} dirty-published dual-layer write(s))`
|
|
298
|
+
: ""}`);
|
|
299
|
+
console.log(" using artifacts:");
|
|
300
|
+
summary.artifactFiles.forEach((file) => console.log(` - ${file}`));
|
|
301
|
+
console.log(" ");
|
|
302
|
+
await askForConfirmation(`Continue and write these to Storyblok space ${summary.to}? (it will overwrite ${summary.itemType}s)`, async () => {
|
|
303
|
+
await plan.run();
|
|
304
|
+
}, () => {
|
|
305
|
+
Logger.warning("Continue not started, exiting the program...");
|
|
306
|
+
}, flags["yes"]);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
283
309
|
default:
|
|
284
310
|
console.log(`no command like that: ${command}`);
|
|
285
311
|
}
|
package/dist/cli/index.js
CHANGED
package/dist/utils/files.d.ts
CHANGED
|
@@ -60,6 +60,7 @@ type CreateAndSaveToFile = (args: {
|
|
|
60
60
|
}, config: RequestBaseConfig) => void;
|
|
61
61
|
export declare const createAndSaveToFile: CreateAndSaveToFile;
|
|
62
62
|
export declare const createAndSaveComponentListToFile: CreateAndSaveComponentListToFile;
|
|
63
|
+
export declare const listFilesInDir: (dirPath: string) => Promise<string[]>;
|
|
63
64
|
export declare const readFile: (pathToFile: string) => Promise<string | undefined>;
|
|
64
65
|
export declare const dumpToFile: (path: string, content: string) => Promise<void>;
|
|
65
66
|
export declare const getConsumerPackageJson: () => Promise<any>;
|
package/dist/utils/files.js
CHANGED
|
@@ -253,6 +253,15 @@ export const createAndSaveComponentListToFile = async ({ file, folder, res, time
|
|
|
253
253
|
: `${sbmigWorkingDirectory}/${filename}.ts`, timestamp);
|
|
254
254
|
Logger.success(`All components written to a file: ${filename}`);
|
|
255
255
|
};
|
|
256
|
+
export const listFilesInDir = async (dirPath) => {
|
|
257
|
+
const absolutePath = resolveFromCwd(dirPath);
|
|
258
|
+
try {
|
|
259
|
+
return await fs.promises.readdir(absolutePath);
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
};
|
|
256
265
|
export const readFile = async (pathToFile) => {
|
|
257
266
|
const absolutePath = resolveFromCwd(pathToFile);
|
|
258
267
|
try {
|