sb-mig 5.7.0 → 5.8.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.
Files changed (36) hide show
  1. package/dist/api/components/components.sync.d.ts +1 -0
  2. package/dist/api/components/components.sync.js +50 -8
  3. package/dist/api/datasources/datasource-entries.js +31 -15
  4. package/dist/api/datasources/datasources.d.ts +2 -1
  5. package/dist/api/datasources/datasources.js +33 -3
  6. package/dist/api/datasources/datasources.sync.js +2 -2
  7. package/dist/api/datasources/datasources.types.d.ts +6 -1
  8. package/dist/api/datasources/error-formatting.d.ts +1 -0
  9. package/dist/api/datasources/error-formatting.js +24 -0
  10. package/dist/api/migrate.js +50 -12
  11. package/dist/api/migrate.types.d.ts +14 -6
  12. package/dist/api/plugins/plugins.d.ts +2 -1
  13. package/dist/api/plugins/plugins.js +19 -1
  14. package/dist/api/plugins/plugins.sync.js +8 -4
  15. package/dist/api/plugins/plugins.types.d.ts +1 -0
  16. package/dist/api/roles/roles.d.ts +2 -1
  17. package/dist/api/roles/roles.js +16 -3
  18. package/dist/api/roles/roles.sync.js +2 -2
  19. package/dist/api/roles/roles.types.d.ts +6 -3
  20. package/dist/api/stories/stories.js +29 -1
  21. package/dist/api/sync/sync.types.d.ts +3 -0
  22. package/dist/api-v2/sync/index.js +4 -89
  23. package/dist/cli/cli-descriptions.d.ts +1 -1
  24. package/dist/cli/cli-descriptions.js +4 -0
  25. package/dist/cli/commands/sync.js +109 -64
  26. package/dist/cli/datasources/sync.js +5 -2
  27. package/dist/cli/roles/sync.js +4 -4
  28. package/dist-cjs/api/components/components.sync.js +50 -8
  29. package/dist-cjs/api/datasources/datasource-entries.js +31 -15
  30. package/dist-cjs/api/datasources/datasources.js +33 -3
  31. package/dist-cjs/api/datasources/error-formatting.js +28 -0
  32. package/dist-cjs/api/plugins/plugins.js +19 -1
  33. package/dist-cjs/api/roles/roles.js +16 -3
  34. package/dist-cjs/api/stories/stories.js +29 -1
  35. package/dist-cjs/api-v2/sync/index.js +4 -89
  36. package/package.json +1 -1
@@ -4,5 +4,6 @@ export declare function syncComponentsData(args: {
4
4
  components: any[];
5
5
  presets: boolean;
6
6
  ssot?: boolean;
7
+ dryRun?: boolean;
7
8
  onProgress?: SyncProgressCallback;
8
9
  }, config: RequestBaseConfig): Promise<SyncResult>;
@@ -27,12 +27,16 @@ const defaultProgress = (event) => {
27
27
  }
28
28
  };
29
29
  import { createComponent, createComponentsGroup, getAllComponents, getAllComponentsGroups, removeComponent, removeComponentGroup, updateComponent, } from "./components.js";
30
- async function ensureComponentGroupsExist(groupNames, config) {
30
+ async function ensureComponentGroupsExist(groupNames, config, options = {}) {
31
31
  try {
32
32
  const existing = await getAllComponentsGroups(config);
33
33
  const existingNames = new Set((existing ?? []).map((g) => g.name));
34
34
  for (const groupName of groupNames) {
35
35
  if (!existingNames.has(groupName)) {
36
+ if (options.dryRun) {
37
+ Logger.warning(`[dry-run] Would create component group '${groupName}'.`);
38
+ continue;
39
+ }
36
40
  await createComponentsGroup(groupName, config);
37
41
  }
38
42
  }
@@ -52,7 +56,7 @@ function resolveGroupUuid(component, remoteGroups) {
52
56
  return { ...component, component_group_uuid: match.uuid };
53
57
  }
54
58
  export async function syncComponentsData(args, config) {
55
- const { components, presets, ssot, onProgress } = args;
59
+ const { components, presets, ssot, dryRun, onProgress } = args;
56
60
  const progress = onProgress ?? defaultProgress;
57
61
  const result = {
58
62
  created: [],
@@ -60,23 +64,37 @@ export async function syncComponentsData(args, config) {
60
64
  skipped: [],
61
65
  errors: [],
62
66
  };
67
+ if (dryRun) {
68
+ Logger.warning("[dry-run] Component sync will only read remote data and report planned changes.");
69
+ }
63
70
  if (ssot) {
64
71
  const existingComponents = await getAllComponents(config);
65
72
  const existingGroups = await getAllComponentsGroups(config);
66
- await Promise.allSettled([
67
- ...(existingComponents ?? []).map((c) => removeComponent(c, config)),
68
- ...(existingGroups ?? []).map((g) => removeComponentGroup(g, config)),
69
- ]);
73
+ if (dryRun) {
74
+ for (const component of existingComponents ?? []) {
75
+ Logger.warning(`[dry-run] Would remove component '${component.name}'.`);
76
+ }
77
+ for (const group of existingGroups ?? []) {
78
+ Logger.warning(`[dry-run] Would remove component group '${group.name}'.`);
79
+ }
80
+ }
81
+ else {
82
+ await Promise.allSettled([
83
+ ...(existingComponents ?? []).map((c) => removeComponent(c, config)),
84
+ ...(existingGroups ?? []).map((g) => removeComponentGroup(g, config)),
85
+ ]);
86
+ }
70
87
  }
71
88
  const nonEmptyComponents = components.filter((c) => !isObjectEmpty(c));
72
89
  const groupsToCheck = uniqueValuesFrom(nonEmptyComponents
73
90
  .filter((c) => c.component_group_name)
74
91
  .map((c) => c.component_group_name));
75
- await ensureComponentGroupsExist(groupsToCheck, config);
92
+ await ensureComponentGroupsExist(groupsToCheck, config, { dryRun });
76
93
  let remoteComponents = [];
77
94
  let remoteGroups = [];
78
95
  try {
79
- remoteComponents = (await getAllComponents(config)) ?? [];
96
+ remoteComponents =
97
+ ssot && dryRun ? [] : ((await getAllComponents(config)) ?? []);
80
98
  }
81
99
  catch (error) {
82
100
  Logger.warning(`Could not fetch remote components: ${error instanceof Error ? error.message : String(error)}`);
@@ -121,6 +139,18 @@ export async function syncComponentsData(args, config) {
121
139
  action: "updating",
122
140
  });
123
141
  try {
142
+ if (dryRun) {
143
+ Logger.warning(`[dry-run] Would update component '${name}'.`);
144
+ result.updated.push(name);
145
+ progress({
146
+ type: "progress",
147
+ current: currentIndex,
148
+ total: totalComponents,
149
+ name,
150
+ action: "updated",
151
+ });
152
+ continue;
153
+ }
124
154
  await updateComponent(component, presets, config);
125
155
  result.updated.push(name);
126
156
  progress({
@@ -158,6 +188,18 @@ export async function syncComponentsData(args, config) {
158
188
  action: "creating",
159
189
  });
160
190
  try {
191
+ if (dryRun) {
192
+ Logger.warning(`[dry-run] Would create component '${name}'.`);
193
+ result.created.push(name);
194
+ progress({
195
+ type: "progress",
196
+ current: currentIndex,
197
+ total: totalComponents,
198
+ name,
199
+ action: "created",
200
+ });
201
+ continue;
202
+ }
161
203
  await createComponent(component, presets, config);
162
204
  result.created.push(name);
163
205
  progress({
@@ -1,7 +1,9 @@
1
1
  import chalk from "chalk";
2
2
  import Logger from "../../utils/logger.js";
3
3
  import { isObjectEmpty } from "../../utils/object-utils.js";
4
+ import { getAllItemsWithPagination } from "../utils/request.js";
4
5
  import { getDatasource } from "./datasources.js";
6
+ import { formatDatasourceApiError } from "./error-formatting.js";
5
7
  const _decorateWithDimensions = async (args, config) => {
6
8
  const { currentDatasource, dimensionsData, _callback } = args;
7
9
  const { spaceId, sbApi } = config;
@@ -35,29 +37,43 @@ export const getDatasourceEntries = async (args, config) => {
35
37
  Logger.log(`Trying to get '${datasourceName}' datasource entries.`);
36
38
  const data = await getDatasource({ datasourceName }, config); // TODO: maybe this step is not needed, i think we can retrieve entries directly using slug (but using delivery api, not management)
37
39
  if (data) {
38
- return sbApi
39
- .get(`spaces/${spaceId}/datasource_entries/`, {
40
- datasource_id: data[0].id,
41
- })
42
- .then(async (response) => {
43
- Logger.success(`Datasource Entries for '${datasourceName}' datasource successfully retrieved.`);
44
- const { data } = response;
45
- return data;
46
- })
47
- .catch((err) => Logger.error(err));
40
+ const datasourceEntries = await getAllItemsWithPagination({
41
+ apiFn: ({ per_page, page }) => sbApi.get(`spaces/${spaceId}/datasource_entries/`, {
42
+ datasource_id: data[0].id,
43
+ per_page,
44
+ page,
45
+ }),
46
+ params: {},
47
+ itemsKey: "datasource_entries",
48
+ });
49
+ Logger.success(`Datasource Entries for '${datasourceName}' datasource successfully retrieved.`);
50
+ return { datasource_entries: datasourceEntries };
48
51
  }
52
+ return { datasource_entries: [] };
49
53
  };
50
54
  export const createDatasourceEntries = (args, config) => {
51
- const { datasource_entries, remoteDatasourceEntries, data } = args;
55
+ const { datasource_entries, remoteDatasourceEntries, data, dryRun } = args;
56
+ const remoteEntries = Array.isArray(remoteDatasourceEntries?.datasource_entries)
57
+ ? remoteDatasourceEntries.datasource_entries
58
+ : [];
52
59
  return Promise.all(datasource_entries.map((datasourceEntry) => {
53
- const datasourceToBeUpdated = remoteDatasourceEntries.datasource_entries.find((remoteDatasourceEntry) => remoteDatasourceEntry.name ===
54
- Object.values(datasourceEntry)[0]);
60
+ const datasourceEntryName = datasourceEntry.name ?? Object.values(datasourceEntry)[0];
61
+ const datasourceToBeUpdated = remoteEntries.find((remoteDatasourceEntry) => remoteDatasourceEntry.name === datasourceEntryName);
62
+ if (dryRun) {
63
+ const action = datasourceToBeUpdated ? "update" : "create";
64
+ Logger.warning(`[dry-run] Would ${action} datasource entry '${datasourceEntryName}' in '${data.datasource.name}' datasource.`);
65
+ return data;
66
+ }
55
67
  if (datasourceToBeUpdated) {
56
68
  return updateDatasourceEntry({ data, datasourceEntry, datasourceToBeUpdated }, config);
57
69
  }
58
70
  return createDatasourceEntry({ data, datasourceEntry }, config);
59
71
  }))
60
72
  .then((_) => {
73
+ if (dryRun) {
74
+ Logger.warning(`[dry-run] Datasource entries for ${data.datasource.id} datasource id were planned without API writes.`);
75
+ return data;
76
+ }
61
77
  Logger.success(`Datasource entries for ${data.datasource.id} datasource id has been successfully synced.`);
62
78
  return data;
63
79
  })
@@ -84,7 +100,7 @@ const _createDatasourceEntry = (args, config) => {
84
100
  console.log("Full Create error: ");
85
101
  console.log(err);
86
102
  }
87
- Logger.error(`Unable to create datasource entry in ${currentDatasource.datasource.name} datasource.`);
103
+ Logger.error(`Unable to create datasource entry '${finalDatasource_entry.name}' in ${currentDatasource.datasource.name} datasource. Value: '${finalDatasource_entry.value}'. ${formatDatasourceApiError(err)}`);
88
104
  });
89
105
  };
90
106
  export const createDatasourceEntry = (args, config) => {
@@ -125,7 +141,7 @@ const _updateDatasourceEntry = (args, config) => {
125
141
  console.log("Full update error: ");
126
142
  console.log(err);
127
143
  }
128
- Logger.error(`Unable to update datasource entry in ${currentDatasource.datasource.name} datasource.`);
144
+ Logger.error(`Unable to update datasource entry '${finalDatasource_entry.name}' in ${currentDatasource.datasource.name} datasource. Value: '${finalDatasource_entry.value}'. ${formatDatasourceApiError(err)}`);
129
145
  });
130
146
  };
131
147
  export const updateDatasourceEntry = (args, config) => {
@@ -4,6 +4,7 @@ export declare const getAllDatasources: GetAllDatasources;
4
4
  export declare const getDatasource: GetDatasource;
5
5
  export declare const createDatasource: CreateDatasource;
6
6
  export declare const updateDatasource: UpdateDatasource;
7
- export declare const syncDatasourcesData: ({ datasources }: {
7
+ export declare const syncDatasourcesData: ({ datasources, dryRun }: {
8
8
  datasources: any[];
9
+ dryRun?: boolean;
9
10
  }, config: any) => Promise<SyncResult>;
@@ -1,6 +1,7 @@
1
1
  import Logger from "../../utils/logger.js";
2
2
  import { getAllItemsWithPagination } from "../utils/request.js";
3
3
  import { createDatasourceEntries, getDatasourceEntries, } from "./datasource-entries.js";
4
+ import { formatDatasourceApiError } from "./error-formatting.js";
4
5
  // GET
5
6
  export const getAllDatasources = (config) => {
6
7
  const { sbApi, spaceId } = config;
@@ -75,7 +76,7 @@ export const createDatasource = (args, config) => {
75
76
  datasource_entries: datasource.datasource_entries,
76
77
  };
77
78
  })
78
- .catch((err) => Logger.error(err));
79
+ .catch((err) => Logger.error(`Unable to create datasource '${datasource.name}' with slug '${datasource.slug}'. ${formatDatasourceApiError(err)}`));
79
80
  };
80
81
  export const updateDatasource = (args, config) => {
81
82
  const { datasource, datasourceToBeUpdated } = args;
@@ -107,16 +108,19 @@ export const updateDatasource = (args, config) => {
107
108
  datasource_entries: datasource.datasource_entries,
108
109
  };
109
110
  })
110
- .catch((err) => Logger.error(err));
111
+ .catch((err) => Logger.error(`Unable to update datasource '${datasource.name}' with id '${datasourceToBeUpdated.id}'. ${formatDatasourceApiError(err)}`));
111
112
  };
112
113
  // File-based sync wrapper lives in `datasources.sync.ts` to keep this module CJS-safe.
113
- export const syncDatasourcesData = async ({ datasources }, config) => {
114
+ export const syncDatasourcesData = async ({ datasources, dryRun }, config) => {
114
115
  const result = {
115
116
  created: [],
116
117
  updated: [],
117
118
  skipped: [],
118
119
  errors: [],
119
120
  };
121
+ if (dryRun) {
122
+ Logger.warning("[dry-run] Datasource sync will only read remote data and report planned changes.");
123
+ }
120
124
  const remoteDatasourcesRaw = await getAllDatasources(config);
121
125
  const remoteDatasources = Array.isArray(remoteDatasourcesRaw)
122
126
  ? remoteDatasourcesRaw
@@ -129,6 +133,32 @@ export const syncDatasourcesData = async ({ datasources }, config) => {
129
133
  }
130
134
  try {
131
135
  const datasourceToBeUpdated = remoteDatasources.find((remoteDatasource) => datasource.name === remoteDatasource.name);
136
+ if (dryRun) {
137
+ if (datasourceToBeUpdated) {
138
+ result.updated.push(name);
139
+ Logger.warning(`[dry-run] Would update datasource '${name}'.`);
140
+ const remoteDatasourceEntries = await getDatasourceEntries({
141
+ datasourceName: name,
142
+ }, config);
143
+ await createDatasourceEntries({
144
+ data: { datasource: datasourceToBeUpdated },
145
+ datasource_entries: datasource.datasource_entries ?? [],
146
+ remoteDatasourceEntries,
147
+ dryRun,
148
+ }, config);
149
+ }
150
+ else {
151
+ const entriesCount = Array.isArray(datasource.datasource_entries)
152
+ ? datasource.datasource_entries.length
153
+ : 0;
154
+ result.created.push(name);
155
+ Logger.warning(`[dry-run] Would create datasource '${name}'.`);
156
+ if (entriesCount > 0) {
157
+ Logger.warning(`[dry-run] Would create ${entriesCount} datasource entries for '${name}' after datasource creation.`);
158
+ }
159
+ }
160
+ continue;
161
+ }
132
162
  const opResult = datasourceToBeUpdated
133
163
  ? await updateDatasource({ datasource, datasourceToBeUpdated }, config)
134
164
  : await createDatasource({ datasource }, config);
@@ -2,10 +2,10 @@ import { getFileContentWithRequire } from "../../utils/files.js";
2
2
  import Logger from "../../utils/logger.js";
3
3
  import { syncDatasourcesData } from "./datasources.js";
4
4
  export const syncDatasources = async (args, config) => {
5
- const { providedDatasources } = args;
5
+ const { providedDatasources, dryRun } = args;
6
6
  Logger.log(`Trying to sync provided datasources: `);
7
7
  const providedDatasourcesContent = await Promise.all(providedDatasources.map((datasource) => {
8
8
  return getFileContentWithRequire({ file: datasource.p });
9
9
  }));
10
- await syncDatasourcesData({ datasources: providedDatasourcesContent }, config);
10
+ await syncDatasourcesData({ datasources: providedDatasourcesContent, dryRun }, config);
11
11
  };
@@ -13,11 +13,15 @@ export type UpdateDatasource = (args: {
13
13
  }, config: RequestBaseConfig) => Promise<any>;
14
14
  export type SyncDatasources = (args: {
15
15
  providedDatasources: OneFileElement[];
16
+ dryRun?: boolean;
16
17
  }, config: RequestBaseConfig) => Promise<any>;
17
18
  export type SyncProvidedDatasources = (args: {
18
19
  datasources: string[];
20
+ dryRun?: boolean;
19
21
  }, config: RequestBaseConfig) => Promise<void>;
20
- export type SyncAllDatasources = (config: RequestBaseConfig) => Promise<void>;
22
+ export type SyncAllDatasources = (config: RequestBaseConfig, args?: {
23
+ dryRun?: boolean;
24
+ }) => Promise<void>;
21
25
  export type GetDatasourceEntries = (args: {
22
26
  datasourceName: string;
23
27
  }, config: RequestBaseConfig) => Promise<any>;
@@ -25,6 +29,7 @@ export type CreateDatasourceEntries = (args: {
25
29
  data: any;
26
30
  datasource_entries: any;
27
31
  remoteDatasourceEntries: any;
32
+ dryRun?: boolean;
28
33
  }, config: RequestBaseConfig) => Promise<any> | void;
29
34
  export type CreateDatasourceEntry = (args: {
30
35
  data: any;
@@ -0,0 +1 @@
1
+ export declare const formatDatasourceApiError: (err: any) => string;
@@ -0,0 +1,24 @@
1
+ export const formatDatasourceApiError = (err) => {
2
+ const details = [];
3
+ const status = err?.response?.status;
4
+ const statusText = err?.response?.statusText;
5
+ const responseData = err?.response?.data;
6
+ if (status || statusText) {
7
+ details.push(`status: ${[status, statusText].filter(Boolean).join(" ")}`);
8
+ }
9
+ if (responseData?.message) {
10
+ details.push(`message: ${responseData.message}`);
11
+ }
12
+ if (responseData?.error) {
13
+ details.push(`error: ${responseData.error}`);
14
+ }
15
+ if (responseData?.errors) {
16
+ details.push(`errors: ${typeof responseData.errors === "string"
17
+ ? responseData.errors
18
+ : JSON.stringify(responseData.errors)}`);
19
+ }
20
+ if (!details.length && err?.message) {
21
+ details.push(`message: ${err.message}`);
22
+ }
23
+ return details.length ? details.join("; ") : String(err);
24
+ };
@@ -53,7 +53,7 @@ const _resolveGroups = async (component, existedGroups, remoteComponentsGroups)
53
53
  return { ...component, component_group_uuid };
54
54
  }
55
55
  };
56
- export const syncComponents = async (specifiedComponents, presets, config) => {
56
+ export const syncComponents = async (specifiedComponents, presets, config, options = {}) => {
57
57
  Logger.log("sync2Components: ");
58
58
  let specifiedComponentsContent = await Promise.all(specifiedComponents.map((component) => {
59
59
  return getFileContentWithRequire({ file: component.p });
@@ -64,9 +64,14 @@ export const syncComponents = async (specifiedComponents, presets, config) => {
64
64
  * - .sb.resolvers.ts files resolvers
65
65
  */
66
66
  specifiedComponentsContent = await resolveGlobalTransformations(specifiedComponentsContent);
67
- await syncComponentsData({ components: specifiedComponentsContent, presets }, config);
67
+ await syncComponentsData({
68
+ components: specifiedComponentsContent,
69
+ presets,
70
+ ssot: options.ssot,
71
+ dryRun: options.dryRun,
72
+ }, config);
68
73
  };
69
- export const syncAllComponents = async (presets, config) => {
74
+ export const syncAllComponents = async (presets, config, options = {}) => {
70
75
  // #1: discover all external .sb.js files
71
76
  const allLocalSbComponentsSchemaFiles = await discover({
72
77
  scope: SCOPE.local,
@@ -83,9 +88,12 @@ export const syncAllComponents = async (presets, config) => {
83
88
  external: allExternalSbComponentsSchemaFiles,
84
89
  });
85
90
  // #4: sync - do all stuff already done (groups resolving, and so on)
86
- return await syncComponents([...local, ...external], presets, config);
91
+ return await syncComponents([...local, ...external], presets, config, {
92
+ dryRun: options.dryRun,
93
+ ssot: options.ssot,
94
+ });
87
95
  };
88
- export const syncProvidedComponents = async (presets, components, packageName, config) => {
96
+ export const syncProvidedComponents = async (presets, components, packageName, config, options = {}) => {
89
97
  if (!packageName) {
90
98
  // #1: discover all external .sb.js files
91
99
  const allLocalSbComponentsSchemaFiles = await discoverMany({
@@ -105,7 +113,9 @@ export const syncProvidedComponents = async (presets, components, packageName, c
105
113
  external: allExternalSbComponentsSchemaFiles,
106
114
  });
107
115
  // #4: sync - do all stuff already done (groups resolving, and so on)
108
- return await syncComponents([...local, ...external], presets, config);
116
+ return await syncComponents([...local, ...external], presets, config, {
117
+ dryRun: options.dryRun,
118
+ });
109
119
  }
110
120
  else {
111
121
  // implement discovering and syncrhonizing with packageName
@@ -125,13 +135,20 @@ export const syncProvidedComponents = async (presets, components, packageName, c
125
135
  external: allExternalSbComponentsSchemaFiles,
126
136
  });
127
137
  // #4: sync - do all stuff already done (groups resolving, and so on)
128
- return syncComponents([...local, ...external], presets, config);
138
+ return syncComponents([...local, ...external], presets, config, {
139
+ dryRun: options.dryRun,
140
+ });
129
141
  }
130
142
  };
131
- export const syncAssets = async ({ transmission: { from, to }, syncDirection }, config) => {
143
+ export const syncAssets = async ({ transmission: { from, to }, syncDirection, dryRun }, config) => {
132
144
  Logger.log(`We would try to migrate Assets data from: ${from} to: ${to}`);
133
145
  const allAssets = await getAllAssets({ spaceId: from }, config);
134
- await Promise.all(allAssets.assets.map((asset) => {
146
+ const assets = Array.isArray(allAssets?.assets) ? allAssets.assets : [];
147
+ if (dryRun) {
148
+ Logger.warning(`[dry-run] Would sync ${assets.length} assets from ${from} to ${to}.`);
149
+ return true;
150
+ }
151
+ await Promise.all(assets.map((asset) => {
135
152
  const { id, created_at, updated_at, ...newAssetPayload } = asset;
136
153
  return migrateAsset({
137
154
  migrateTo: to,
@@ -139,13 +156,18 @@ export const syncAssets = async ({ transmission: { from, to }, syncDirection },
139
156
  syncDirection,
140
157
  }, config);
141
158
  }));
159
+ return true;
142
160
  };
143
- const syncStories = async ({ transmission: { from, to }, stories, toSpaceId }, config) => {
161
+ const syncStories = async ({ transmission: { from, to }, stories, toSpaceId, dryRun }, config) => {
144
162
  Logger.log(`We would try to migrate Stories data from: ${from} to: ${to}`);
145
163
  const storiesToPass = stories
146
164
  .map((item) => item.story)
147
165
  .map((item) => item.parent_id === 0 ? { ...item, parent_id: null } : item);
148
166
  Logger.warning(`Amount of all stories to migrate: ${storiesToPass.length}`);
167
+ if (dryRun) {
168
+ Logger.warning(`[dry-run] Would sync ${storiesToPass.length} stories from ${from} to ${to}.`);
169
+ return true;
170
+ }
149
171
  const storiesToPassJson = JSON.stringify(storiesToPass, null, 2);
150
172
  if (config.debug) {
151
173
  dumpToFile("storiesToPass.json", storiesToPassJson);
@@ -156,10 +178,23 @@ const syncStories = async ({ transmission: { from, to }, stories, toSpaceId }, c
156
178
  dumpToFile("tree.json", jsonString);
157
179
  }
158
180
  await traverseAndCreate({ tree, realParentId: null, spaceId: toSpaceId }, config);
181
+ return true;
159
182
  };
160
- export const syncContent = async ({ type, transmission, syncDirection, filename }, config) => {
183
+ export const syncContent = async ({ type, transmission, syncDirection, filename, dryRun }, config) => {
184
+ if (dryRun) {
185
+ Logger.warning("[dry-run] Content sync will only read source data and report planned changes.");
186
+ }
161
187
  if (type === "stories") {
162
188
  if (syncDirection === "fromSpaceToFile") {
189
+ if (dryRun) {
190
+ const stories = await getAllStories({}, {
191
+ ...config,
192
+ spaceId: transmission.from,
193
+ sbApi: config.sbApi,
194
+ });
195
+ Logger.warning(`[dry-run] Would back up ${stories.length} stories from ${transmission.from} to '${transmission.to}'.`);
196
+ return true;
197
+ }
163
198
  await backupStories({
164
199
  filename: transmission.to,
165
200
  suffix: ".sb.stories",
@@ -176,6 +211,7 @@ export const syncContent = async ({ type, transmission, syncDirection, filename
176
211
  transmission,
177
212
  stories,
178
213
  toSpaceId: transmission.to,
214
+ dryRun,
179
215
  }, config);
180
216
  }
181
217
  if (syncDirection === "fromFileToSpace") {
@@ -191,6 +227,7 @@ export const syncContent = async ({ type, transmission, syncDirection, filename
191
227
  transmission,
192
228
  stories: storiesFileContent[0],
193
229
  toSpaceId: transmission.to,
230
+ dryRun,
194
231
  }, config);
195
232
  }
196
233
  if (syncDirection === "fromAWSToSpace") {
@@ -199,6 +236,7 @@ export const syncContent = async ({ type, transmission, syncDirection, filename
199
236
  transmission,
200
237
  stories: data.stories,
201
238
  toSpaceId: transmission.to,
239
+ dryRun,
202
240
  }, config);
203
241
  }
204
242
  return true;
@@ -209,7 +247,7 @@ export const syncContent = async ({ type, transmission, syncDirection, filename
209
247
  }
210
248
  else if (syncDirection === "fromSpaceToSpace" ||
211
249
  syncDirection === "fromSpaceToFile") {
212
- await syncAssets({ transmission, syncDirection }, config);
250
+ await syncAssets({ transmission, syncDirection, dryRun }, config);
213
251
  }
214
252
  else {
215
253
  Logger.warning(`${syncDirection} with ${type} This is not implemented yet!`);
@@ -1,13 +1,19 @@
1
1
  import type { RequestBaseConfig } from "./utils/request.js";
2
2
  import type { SyncDirection } from "../cli/sync.types.js";
3
+ import type { SyncOptions } from "./sync/sync.types.js";
3
4
  import type { OneFileElement } from "../cli/utils/discover.js";
4
- export type SyncComponents = (specifiedComponents: OneFileElement[], presets: boolean, config: RequestBaseConfig) => Promise<any>;
5
- export type SyncAllComponents = (presets: boolean, config: RequestBaseConfig) => Promise<any>;
6
- export type SyncProvidedComponents = (presets: boolean, components: string[], packageName: boolean, config: RequestBaseConfig) => Promise<any>;
7
- export type SyncStories = ({ transmission, stories, toSpaceId, }: {
5
+ export type SyncComponents = (specifiedComponents: OneFileElement[], presets: boolean, config: RequestBaseConfig, options?: SyncOptions & {
6
+ ssot?: boolean;
7
+ }) => Promise<any>;
8
+ export type SyncAllComponents = (presets: boolean, config: RequestBaseConfig, options?: SyncOptions & {
9
+ ssot?: boolean;
10
+ }) => Promise<any>;
11
+ export type SyncProvidedComponents = (presets: boolean, components: string[], packageName: boolean, config: RequestBaseConfig, options?: SyncOptions) => Promise<any>;
12
+ export type SyncStories = ({ transmission, stories, toSpaceId, dryRun, }: {
8
13
  transmission: SyncContent["transmission"];
9
14
  stories: any[];
10
15
  toSpaceId: string;
16
+ dryRun?: boolean;
11
17
  }, config: RequestBaseConfig) => Promise<any>;
12
18
  export interface SyncContent {
13
19
  type: "stories" | "assets";
@@ -17,9 +23,11 @@ export interface SyncContent {
17
23
  };
18
24
  syncDirection: SyncDirection;
19
25
  filename?: string;
26
+ dryRun?: boolean;
20
27
  }
21
- export type SyncContentFunction = ({ type, transmission, syncDirection, filename }: SyncContent, config: RequestBaseConfig) => Promise<any>;
22
- export type SyncAssets = ({ transmission, }: {
28
+ export type SyncContentFunction = ({ type, transmission, syncDirection, filename, dryRun }: SyncContent, config: RequestBaseConfig) => Promise<any>;
29
+ export type SyncAssets = ({ transmission, dryRun, }: {
23
30
  transmission: SyncContent["transmission"];
24
31
  syncDirection: SyncDirection;
32
+ dryRun?: boolean;
25
33
  }, config: RequestBaseConfig) => Promise<any>;
@@ -5,9 +5,10 @@ export declare const getPlugin: GetPlugin;
5
5
  export declare const getPluginDetails: GetPluginDetails;
6
6
  export declare const updatePlugin: UpdatePlugin;
7
7
  export declare const createPlugin: CreatePlugin;
8
- export declare const syncPluginsData: ({ plugins }: {
8
+ export declare const syncPluginsData: ({ plugins, dryRun, }: {
9
9
  plugins: {
10
10
  name: string;
11
11
  body: string;
12
12
  }[];
13
+ dryRun?: boolean;
13
14
  }, config: any) => Promise<SyncResult>;
@@ -87,13 +87,17 @@ export const createPlugin = (pluginName, config) => {
87
87
  });
88
88
  };
89
89
  // File-based sync wrapper lives in `plugins.sync.ts` to keep this module CJS-safe.
90
- export const syncPluginsData = async ({ plugins }, config) => {
90
+ export const syncPluginsData = async ({ plugins, dryRun, }, config) => {
91
91
  const result = {
92
92
  created: [],
93
93
  updated: [],
94
94
  skipped: [],
95
95
  errors: [],
96
96
  };
97
+ if (dryRun) {
98
+ Logger.warning("[dry-run] Plugin sync will only read remote data and report planned changes.");
99
+ }
100
+ const remotePlugins = dryRun ? await getAllPlugins(config) : [];
97
101
  for (const p of plugins) {
98
102
  const name = String(p?.name ?? "unknown");
99
103
  if (!p?.name) {
@@ -101,6 +105,20 @@ export const syncPluginsData = async ({ plugins }, config) => {
101
105
  continue;
102
106
  }
103
107
  try {
108
+ if (dryRun) {
109
+ const plugin = Array.isArray(remotePlugins)
110
+ ? remotePlugins.find((remotePlugin) => remotePlugin.name === name)
111
+ : undefined;
112
+ if (plugin) {
113
+ Logger.warning(`[dry-run] Would update plugin '${name}'.`);
114
+ result.updated.push(name);
115
+ }
116
+ else {
117
+ Logger.warning(`[dry-run] Would create plugin '${name}'.`);
118
+ result.created.push(name);
119
+ }
120
+ continue;
121
+ }
104
122
  const plugin = await getPlugin(name, config);
105
123
  if (plugin) {
106
124
  await updatePlugin({ plugin: plugin.field_type, body: p.body }, config);
@@ -1,11 +1,15 @@
1
1
  import { readFile } from "../../utils/files.js";
2
2
  import { syncPluginsData } from "./plugins.js";
3
- export const syncProvidedPlugins = async ({ plugins }, config) => {
4
- const body = await readFile("dist/export.js");
5
- if (!body) {
3
+ export const syncProvidedPlugins = async ({ plugins, dryRun }, config) => {
4
+ const body = dryRun ? "" : await readFile("dist/export.js");
5
+ if (!body && !dryRun) {
6
6
  throw new Error("Unable to read plugin bundle from dist/export.js");
7
7
  }
8
8
  await syncPluginsData({
9
- plugins: plugins.map((name) => ({ name: String(name), body })),
9
+ plugins: plugins.map((name) => ({
10
+ name: String(name),
11
+ body: body ?? "",
12
+ })),
13
+ dryRun,
10
14
  }, config);
11
15
  };
@@ -9,6 +9,7 @@ interface UpdatePluginDTO {
9
9
  }
10
10
  interface SyncProvidedPluginsDTO {
11
11
  plugins: string[];
12
+ dryRun?: boolean;
12
13
  }
13
14
  export type GetPlugin = (pluginName: string | undefined, config: RequestBaseConfig) => Promise<any>;
14
15
  export type GetAllPlugins = (config: RequestBaseConfig) => Promise<any>;
@@ -4,6 +4,7 @@ export declare const createRole: CreateRole;
4
4
  export declare const updateRole: UpdateRole;
5
5
  export declare const getAllRoles: GetAllRoles;
6
6
  export declare const getRole: GetRole;
7
- export declare const syncRolesData: ({ roles }: {
7
+ export declare const syncRolesData: ({ roles, dryRun }: {
8
8
  roles: any[];
9
+ dryRun?: boolean;
9
10
  }, config: any) => Promise<SyncResult>;