sb-mig 6.0.0-beta.9 → 6.0.1-beta.1

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/README.md CHANGED
@@ -113,6 +113,40 @@ module.exports = {
113
113
  };
114
114
  ```
115
115
 
116
+ ## Programmatic asset API
117
+
118
+ The public management API exposes asset helpers for uploading a local file and updating existing asset metadata:
119
+
120
+ ```ts
121
+ import { managementApi } from "sb-mig/dist/api/managementApi.js";
122
+
123
+ await managementApi.assets.createAsset(
124
+ {
125
+ spaceId: "12345",
126
+ pathToFile: "./public/image.jpg",
127
+ payload: {
128
+ asset_folder_id: 67890,
129
+ validate_upload: 1,
130
+ },
131
+ },
132
+ config,
133
+ );
134
+
135
+ await managementApi.assets.updateAsset(
136
+ {
137
+ spaceId: "12345",
138
+ assetId: 98765,
139
+ payload: {
140
+ meta_data: {
141
+ alt: "Image alt text",
142
+ title: "Image title",
143
+ },
144
+ },
145
+ },
146
+ config,
147
+ );
148
+ ```
149
+
116
150
  # Schema documentation:
117
151
 
118
152
  ## Basics
@@ -1,6 +1,8 @@
1
- import type { GetAllAssets, GetAssetById, GetAssetByName, MigrateAsset } from "./assets.types.js";
1
+ import type { CreateAsset, GetAllAssets, GetAssetById, GetAssetByName, MigrateAsset, UpdateAsset } from "./assets.types.js";
2
2
  export declare const getAllAssets: GetAllAssets;
3
3
  export declare const getAssetByName: GetAssetByName;
4
4
  export declare const migrateAsset: MigrateAsset;
5
+ export declare const createAsset: CreateAsset;
6
+ export declare const updateAsset: UpdateAsset;
5
7
  export declare const getAssetById: GetAssetById;
6
8
  export declare const getAsset: (assetName: string | undefined) => Promise<void>;
@@ -5,6 +5,24 @@ import FormData from "form-data";
5
5
  import { createDir, isDirectoryExists } from "../../utils/files.js";
6
6
  import Logger from "../../utils/logger.js";
7
7
  import { getFileName, getSizeFromURL } from "../../utils/string-utils.js";
8
+ const isStoryblokSize = (size) => Boolean(size && /^\d+x\d+$/i.test(size));
9
+ const prepareSignedUploadPayload = (payload) => {
10
+ const { filename, asset_folder_id, id, size, validate_upload } = payload;
11
+ const inferredSize = isStoryblokSize(size)
12
+ ? size
13
+ : isStoryblokSize(getSizeFromURL(filename))
14
+ ? getSizeFromURL(filename)
15
+ : undefined;
16
+ return {
17
+ filename: getFileName(filename),
18
+ ...(asset_folder_id === undefined || asset_folder_id === null
19
+ ? {}
20
+ : { asset_folder_id }),
21
+ ...(id === undefined ? {} : { id }),
22
+ ...(inferredSize ? { size: inferredSize } : {}),
23
+ ...(validate_upload === undefined ? {} : { validate_upload }),
24
+ };
25
+ };
8
26
  // GET
9
27
  export const getAllAssets = async (args, config) => {
10
28
  const { spaceId, search } = args;
@@ -38,18 +56,12 @@ export const getAssetByName = async ({ spaceId, fileName }, config) => {
38
56
  };
39
57
  const requestSignedUploadUrl = ({ spaceId, payload }, config) => {
40
58
  const { sbApi, debug } = config;
41
- const { filename: _1, asset_folder_id, ext_id, space_id, deleted_at: _2, ...restPayload } = payload;
42
- const filename = getFileName(payload.filename);
43
- const size = getSizeFromURL(payload.filename);
59
+ const signedUploadPayload = prepareSignedUploadPayload(payload);
44
60
  return sbApi
45
- .post(`spaces/${spaceId}/assets/`, {
46
- filename,
47
- size,
48
- ...restPayload,
49
- })
61
+ .post(`spaces/${spaceId}/assets/`, signedUploadPayload)
50
62
  .then((signedResponseObject) => {
51
63
  if (debug) {
52
- Logger.log(`Signed upload URL has been requested for ${filename}.`);
64
+ Logger.log(`Signed upload URL has been requested for ${signedUploadPayload.filename}.`);
53
65
  }
54
66
  return signedResponseObject.data; // this is very bad... but storyblok-js-client types are pretty broken
55
67
  })
@@ -67,15 +79,20 @@ const uploadFile = ({ signedResponseObject, pathToFile }) => {
67
79
  // also append the file read stream
68
80
  form.append("file", fs.createReadStream(file));
69
81
  // submit your form
70
- form.submit(signedResponseObject.post_url, async (err, res) => {
71
- const statusCode = res.statusCode;
72
- if (statusCode === 204) {
73
- Logger.upload(`Asset uploaded ${getFileName(file)}`);
74
- }
75
- else {
76
- if (err)
77
- throw err;
78
- }
82
+ return new Promise((resolve, reject) => {
83
+ form.submit(signedResponseObject.post_url, (err, res) => {
84
+ if (err) {
85
+ reject(err);
86
+ return;
87
+ }
88
+ const statusCode = res?.statusCode;
89
+ if (statusCode === 204) {
90
+ Logger.upload(`Asset uploaded ${getFileName(file)}`);
91
+ resolve();
92
+ return;
93
+ }
94
+ reject(new Error(`Asset upload failed with status code ${statusCode ?? "unknown"}`));
95
+ });
79
96
  });
80
97
  };
81
98
  const downloadAsset = async (args, config) => {
@@ -121,6 +138,31 @@ export const migrateAsset = async ({ migrateTo, payload, syncDirection }, config
121
138
  }
122
139
  return true;
123
140
  };
141
+ export const createAsset = async ({ spaceId, pathToFile, payload = {} }, config) => {
142
+ const signedResponseObject = await requestSignedUploadUrl({
143
+ spaceId,
144
+ payload: {
145
+ ...payload,
146
+ filename: payload.filename ?? pathToFile,
147
+ },
148
+ }, config);
149
+ await uploadFile({ signedResponseObject, pathToFile });
150
+ return signedResponseObject;
151
+ };
152
+ export const updateAsset = async ({ spaceId, assetId, payload }, config) => {
153
+ const { sbApi } = config;
154
+ Logger.log(`Trying to update asset with id ${assetId}.`);
155
+ return sbApi
156
+ .put(`spaces/${spaceId}/assets/${assetId}`, payload)
157
+ .then((res) => {
158
+ Logger.success(`Asset '${assetId}' has been updated.`);
159
+ return res.data;
160
+ })
161
+ .catch((err) => {
162
+ Logger.error(`${err.message} in updateAsset function for asset ${assetId}`);
163
+ throw err;
164
+ });
165
+ };
124
166
  // GET
125
167
  export const getAssetById = async ({ spaceId, assetId }, config) => {
126
168
  const { sbApi } = config;
@@ -35,6 +35,29 @@ export interface SBAllAssetRequestResult {
35
35
  }
36
36
  export type SignedResponseObject = any;
37
37
  export type AssetPayload = Omit<SBAsset, "updated_at" | "created_at" | "id">;
38
+ export interface SignedUploadPayload {
39
+ filename: string;
40
+ id?: number;
41
+ asset_folder_id?: number | null;
42
+ size?: string;
43
+ validate_upload?: number;
44
+ }
45
+ export type CreateAssetPayload = Omit<SignedUploadPayload, "filename" | "id"> & Partial<Pick<SignedUploadPayload, "filename">>;
46
+ export type UpdateAssetPayload = {
47
+ asset_folder_id?: number | null;
48
+ internal_tag_ids?: number[];
49
+ is_private?: boolean;
50
+ locked?: boolean;
51
+ meta_data?: {
52
+ alt?: string;
53
+ copyright?: string;
54
+ source?: string;
55
+ title?: string;
56
+ [key: string]: unknown;
57
+ };
58
+ publish_at?: string | null;
59
+ [key: string]: unknown;
60
+ };
38
61
  export type GetAllAssets = ({ spaceId, }: {
39
62
  spaceId: string;
40
63
  search?: string;
@@ -52,16 +75,26 @@ export type MigrateAsset = ({ migrateTo, payload, syncDirection, }: {
52
75
  payload: AssetPayload;
53
76
  syncDirection: SyncDirection;
54
77
  }, config: RequestBaseConfig) => Promise<boolean>;
78
+ export type CreateAsset = ({ spaceId, pathToFile, payload, }: {
79
+ spaceId: string;
80
+ pathToFile: string;
81
+ payload?: CreateAssetPayload;
82
+ }, config: RequestBaseConfig) => Promise<SignedResponseObject>;
83
+ export type UpdateAsset = ({ spaceId, assetId, payload, }: {
84
+ spaceId: string;
85
+ assetId: number;
86
+ payload: UpdateAssetPayload;
87
+ }, config: RequestBaseConfig) => Promise<any>;
55
88
  export type UploadFile = ({ signedResponseObject, pathToFile, }: {
56
89
  signedResponseObject: SignedResponseObject;
57
90
  pathToFile: string;
58
- }) => void;
91
+ }) => Promise<void>;
59
92
  export type FinalizeUpload = ({ signedResponseObject, }: {
60
93
  signedResponseObject: SignedResponseObject;
61
94
  }) => void;
62
95
  export type RequestSignedUploadUrl = ({ spaceId, payload, }: {
63
96
  spaceId: string;
64
- payload: AssetPayload;
97
+ payload: AssetPayload | SignedUploadPayload;
65
98
  }, config: RequestBaseConfig) => Promise<any>;
66
99
  export type DownloadAsset = (args: {
67
100
  payload: AssetPayload;
@@ -1 +1,2 @@
1
- export { getAllAssets, migrateAsset, getAsset, getAssetById, getAssetByName, } from "./assets.js";
1
+ export { getAllAssets, createAsset, migrateAsset, getAsset, getAssetById, getAssetByName, updateAsset, } from "./assets.js";
2
+ export type { CreateAssetPayload, SignedUploadPayload, UpdateAssetPayload, } from "./assets.types.js";
@@ -1 +1 @@
1
- export { getAllAssets, migrateAsset, getAsset, getAssetById, getAssetByName, } from "./assets.js";
1
+ export { getAllAssets, createAsset, migrateAsset, getAsset, getAssetById, getAssetByName, updateAsset, } from "./assets.js";
@@ -109,7 +109,10 @@ export const removeComponent = (component, config) => {
109
109
  return sbApi
110
110
  .delete(`spaces/${spaceId}/components/${id}`, {})
111
111
  .then((res) => res.data)
112
- .catch((err) => console.error(err));
112
+ .catch((err) => {
113
+ console.error(err);
114
+ throw err;
115
+ });
113
116
  };
114
117
  /*
115
118
  *
@@ -165,7 +168,10 @@ export const removeComponentGroup = (componentGroup, config) => {
165
168
  return sbApi
166
169
  .delete(`spaces/${spaceId}/component_groups/${id}`, {})
167
170
  .then((res) => res.data)
168
- .catch((err) => console.error(err));
171
+ .catch((err) => {
172
+ console.error(err);
173
+ throw err;
174
+ });
169
175
  };
170
176
  export const createComponentsGroup = (groupName, config) => {
171
177
  const { spaceId, sbApi } = config;
@@ -27,6 +27,9 @@ const defaultProgress = (event) => {
27
27
  }
28
28
  };
29
29
  import { createComponent, createComponentsGroup, getAllComponents, getAllComponentsGroups, removeComponent, removeComponentGroup, updateComponent, } from "./components.js";
30
+ function getErrorMessage(error) {
31
+ return error instanceof Error ? error.message : String(error);
32
+ }
30
33
  async function ensureComponentGroupsExist(groupNames, config, options = {}) {
31
34
  try {
32
35
  const existing = await getAllComponentsGroups(config);
@@ -79,10 +82,34 @@ export async function syncComponentsData(args, config) {
79
82
  }
80
83
  }
81
84
  else {
82
- await Promise.allSettled([
83
- ...(existingComponents ?? []).map((c) => removeComponent(c, config)),
84
- ...(existingGroups ?? []).map((g) => removeComponentGroup(g, config)),
85
- ]);
85
+ const removalTargets = [
86
+ ...(existingComponents ?? []).map((component) => ({
87
+ type: "component",
88
+ name: String(component?.name ?? component?.id ?? "unknown"),
89
+ remove: () => removeComponent(component, config),
90
+ })),
91
+ ...(existingGroups ?? []).map((group) => ({
92
+ type: "component group",
93
+ name: String(group?.name ?? group?.id ?? "unknown"),
94
+ remove: () => removeComponentGroup(group, config),
95
+ })),
96
+ ];
97
+ const removalResults = await Promise.allSettled(removalTargets.map((target) => target.remove()));
98
+ removalResults.forEach((removalResult, index) => {
99
+ if (removalResult.status === "fulfilled")
100
+ return;
101
+ const target = removalTargets[index];
102
+ if (!target)
103
+ return;
104
+ const name = `${target.type} '${target.name}'`;
105
+ const message = getErrorMessage(removalResult.reason);
106
+ result.skipped.push(name);
107
+ result.errors.push({
108
+ name,
109
+ message: `SSOT removal failed: ${message}`,
110
+ });
111
+ Logger.warning(`Could not remove ${name} during SSOT sync: ${message}`);
112
+ });
86
113
  }
87
114
  }
88
115
  const nonEmptyComponents = components.filter((c) => !isObjectEmpty(c));
@@ -164,7 +191,7 @@ export async function syncComponentsData(args, config) {
164
191
  catch (error) {
165
192
  result.errors.push({
166
193
  name,
167
- message: error instanceof Error ? error.message : String(error),
194
+ message: getErrorMessage(error),
168
195
  });
169
196
  progress({
170
197
  type: "progress",
@@ -172,7 +199,7 @@ export async function syncComponentsData(args, config) {
172
199
  total: totalComponents,
173
200
  name,
174
201
  action: "error",
175
- message: error instanceof Error ? error.message : String(error),
202
+ message: getErrorMessage(error),
176
203
  });
177
204
  }
178
205
  }
@@ -213,7 +240,7 @@ export async function syncComponentsData(args, config) {
213
240
  catch (error) {
214
241
  result.errors.push({
215
242
  name,
216
- message: error instanceof Error ? error.message : String(error),
243
+ message: getErrorMessage(error),
217
244
  });
218
245
  progress({
219
246
  type: "progress",
@@ -221,7 +248,7 @@ export async function syncComponentsData(args, config) {
221
248
  total: totalComponents,
222
249
  name,
223
250
  action: "error",
224
- message: error instanceof Error ? error.message : String(error),
251
+ message: getErrorMessage(error),
225
252
  });
226
253
  }
227
254
  }
@@ -1,15 +1,25 @@
1
1
  import Logger from "../utils/logger.js";
2
+ import { buildUrl } from "../utils/url-utils.js";
2
3
  const getAllStories = async (args, config) => {
3
4
  const { spaceId, storiesFilename } = args;
4
5
  Logger.warning("Trying to get all stories from Content Hub...");
5
- const queryParams = `spaceId=${spaceId}&storiesFilename=${storiesFilename}`;
6
- const url = `${config.contentHubOriginUrl}/getStories?${queryParams}`;
6
+ if (!config.contentHubOriginUrl) {
7
+ throw new Error("contentHubOriginUrl is required to fetch stories.");
8
+ }
9
+ const url = buildUrl({
10
+ baseUrl: config.contentHubOriginUrl,
11
+ pathname: "getStories",
12
+ searchParams: {
13
+ spaceId,
14
+ ...(storiesFilename ? { storiesFilename } : {}),
15
+ },
16
+ });
7
17
  const authorizationToken = config.contentHubAuthorizationToken;
8
18
  if (config.debug) {
9
- console.log("This is url: ", url);
19
+ console.log("This is url: ", url.toString());
10
20
  }
11
21
  try {
12
- const response = await fetch(url, {
22
+ const response = await fetch(url.toString(), {
13
23
  method: "GET",
14
24
  headers: {
15
25
  Authorization: authorizationToken,
@@ -1,10 +1,12 @@
1
1
  export declare const managementApi: {
2
2
  assets: {
3
3
  getAllAssets: import("./assets/assets.types.js").GetAllAssets;
4
+ createAsset: import("./assets/assets.types.js").CreateAsset;
4
5
  migrateAsset: import("./assets/assets.types.js").MigrateAsset;
5
6
  getAsset: (assetName: string | undefined) => Promise<void>;
6
7
  getAssetById: import("./assets/assets.types.js").GetAssetById;
7
8
  getAssetByName: import("./assets/assets.types.js").GetAssetByName;
9
+ updateAsset: import("./assets/assets.types.js").UpdateAsset;
8
10
  };
9
11
  auth: {
10
12
  getCurrentUser: import("./auth/auth.types.js").GetCurrentUser;
@@ -1,10 +1,12 @@
1
1
  export declare const testApi: {
2
2
  assets: {
3
3
  getAllAssets: import("./assets/assets.types.js").GetAllAssets;
4
+ createAsset: import("./assets/assets.types.js").CreateAsset;
4
5
  migrateAsset: import("./assets/assets.types.js").MigrateAsset;
5
6
  getAsset: (assetName: string | undefined) => Promise<void>;
6
7
  getAssetById: import("./assets/assets.types.js").GetAssetById;
7
8
  getAssetByName: import("./assets/assets.types.js").GetAssetByName;
9
+ updateAsset: import("./assets/assets.types.js").UpdateAsset;
8
10
  };
9
11
  auth: {
10
12
  getCurrentUser: import("./auth/auth.types.js").GetCurrentUser;
@@ -4,6 +4,7 @@ import { v4 as uuidv4 } from "uuid";
4
4
  import { managementApi } from "../../api/managementApi.js";
5
5
  import { storyblokApiMapping } from "../../config/constants.js";
6
6
  import Logger from "../../utils/logger.js";
7
+ import { buildUrl } from "../../utils/url-utils.js";
7
8
  import { apiConfig } from "../api-config.js";
8
9
  const INIT_COMMANDS = {
9
10
  project: "project",
@@ -59,10 +60,18 @@ export const init = async (props) => {
59
60
  console.log(e);
60
61
  }
61
62
  try {
63
+ const previewDomainUrl = buildUrl({
64
+ baseUrl: "https://localhost:3000",
65
+ pathname: "api/preview/preview",
66
+ searchParams: {
67
+ secret: STORYBLOK_PREVIEW_SECRET,
68
+ slug: "",
69
+ },
70
+ });
62
71
  await managementApi.spaces.updateSpace({
63
72
  spaceId,
64
73
  params: {
65
- domain: `https://localhost:3000/api/preview/preview?secret=${STORYBLOK_PREVIEW_SECRET}&slug=`,
74
+ domain: previewDomainUrl.toString(),
66
75
  },
67
76
  }, { ...apiConfig, sbApi: localSbApi });
68
77
  Logger.success("Successfully updated space domain");
@@ -1,5 +1,5 @@
1
1
  import { managementApi } from "../../api/managementApi.js";
2
- import { removeAllComponents, syncAllComponents, syncContent, syncProvidedComponents, setComponentDefaultPreset, } from "../../api/migrate.js";
2
+ import { syncAllComponents, syncContent, syncProvidedComponents, setComponentDefaultPreset, } from "../../api/migrate.js";
3
3
  import storyblokConfig from "../../config/config.js";
4
4
  import Logger from "../../utils/logger.js";
5
5
  import { apiConfig } from "../api-config.js";
@@ -74,8 +74,9 @@ export const sync = async (props) => {
74
74
  }
75
75
  else {
76
76
  await askForConfirmation("Are you sure you want to use Single Source of truth? It will remove all your components added in GUI and replace them 1 to 1 with code versions.", async () => {
77
- await removeAllComponents(apiConfig);
78
- await syncAllComponents(presets, apiConfig);
77
+ await syncAllComponents(presets, apiConfig, {
78
+ ssot: true,
79
+ });
79
80
  }, () => {
80
81
  Logger.success("Syncing components aborted.");
81
82
  }, flags["yes"]);
@@ -19,6 +19,14 @@ export var LOOKUP_TYPE;
19
19
  LOOKUP_TYPE["packagName"] = "packageName";
20
20
  LOOKUP_TYPE["fileName"] = "fileName";
21
21
  })(LOOKUP_TYPE || (LOOKUP_TYPE = {}));
22
+ const discoverExactFiles = ({ mainDirectory, componentDirectories, fileNames, ext, }) => exactFilesPatterns({
23
+ mainDirectory,
24
+ componentDirectories,
25
+ fileNames,
26
+ ext,
27
+ }).flatMap((exactPattern) => globSync(exactPattern.replace(/\\/g, "/"), {
28
+ follow: true,
29
+ }));
22
30
  // Re-export functions for backwards compatibility
23
31
  export { normalizeDiscover, compare, filesPattern, exactFilesPatterns, } from "../../utils/path-utils.js";
24
32
  export const discoverManyByPackageName = (request) => {
@@ -189,11 +197,11 @@ export const discoverMany = async (request) => {
189
197
  let listOFSchemaTSFilesCompiled = [];
190
198
  const onlyLocalComponentsDirectories = storyblokConfig.componentsDirectories.filter((p) => !p.includes("node_modules"));
191
199
  if (storyblokConfig.schemaType === SCHEMA.TS) {
192
- pattern = path.join(`${directory}`, `${normalizeDiscover({
193
- segments: onlyLocalComponentsDirectories,
194
- })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.sb.${storyblokConfig.schemaType}`);
195
- const listOfFilesToCompile = globSync(pattern.replace(/\\/g, "/"), {
196
- follow: true,
200
+ const listOfFilesToCompile = discoverExactFiles({
201
+ mainDirectory: directory,
202
+ componentDirectories: onlyLocalComponentsDirectories,
203
+ fileNames: request.fileNames,
204
+ ext: `sb.${storyblokConfig.schemaType}`,
197
205
  });
198
206
  await buildOnTheFly({ files: listOfFilesToCompile });
199
207
  pattern = path.join(directory, ".next", "cache", "sb-mig", "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.schemaFileExt}`);
@@ -201,33 +209,33 @@ export const discoverMany = async (request) => {
201
209
  follow: true,
202
210
  });
203
211
  }
204
- pattern = path.join(`${directory}`, `${normalizeDiscover({
205
- segments: onlyLocalComponentsDirectories,
206
- })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.schemaFileExt}`);
207
- listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
208
- follow: true,
212
+ listOfFiles = discoverExactFiles({
213
+ mainDirectory: directory,
214
+ componentDirectories: onlyLocalComponentsDirectories,
215
+ fileNames: request.fileNames,
216
+ ext: storyblokConfig.schemaFileExt,
209
217
  });
210
218
  listOfFiles = [...listOfFiles, ...listOFSchemaTSFilesCompiled];
211
219
  break;
212
220
  case SCOPE.external:
213
221
  // ### MANY - EXTERNAL - fileName ###
214
222
  const onlyNodeModulesPackagesComponentsDirectories = storyblokConfig.componentsDirectories.filter((p) => p.includes("node_modules"));
215
- pattern = path.join(`${directory}`, `${normalizeDiscover({
216
- segments: onlyNodeModulesPackagesComponentsDirectories,
217
- })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.schemaFileExt}`);
218
- listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
219
- follow: true,
223
+ listOfFiles = discoverExactFiles({
224
+ mainDirectory: directory,
225
+ componentDirectories: onlyNodeModulesPackagesComponentsDirectories,
226
+ fileNames: request.fileNames,
227
+ ext: storyblokConfig.schemaFileExt,
220
228
  });
221
229
  break;
222
230
  case SCOPE.lock:
223
231
  break;
224
232
  case SCOPE.all:
225
233
  // ### MANY - ALL - fileName ###
226
- pattern = path.join(`${directory}`, `${normalizeDiscover({
227
- segments: storyblokConfig.componentsDirectories,
228
- })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.schemaFileExt}`);
229
- listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
230
- follow: true,
234
+ listOfFiles = discoverExactFiles({
235
+ mainDirectory: directory,
236
+ componentDirectories: storyblokConfig.componentsDirectories,
237
+ fileNames: request.fileNames,
238
+ ext: storyblokConfig.schemaFileExt,
231
239
  });
232
240
  break;
233
241
  default:
@@ -22,6 +22,10 @@ export const extractComponentName = (filePath) => {
22
22
  const lastElement = normalized.substring(normalized.lastIndexOf("/") + 1);
23
23
  return lastElement.replace(/\.ts$/, "");
24
24
  };
25
+ const getFileName = (filePath) => {
26
+ const normalized = filePath.replace(/\\/g, "/");
27
+ return normalized.substring(normalized.lastIndexOf("/") + 1);
28
+ };
25
29
  /**
26
30
  * Normalizes an array of directory segments for glob pattern usage.
27
31
  * Handles the glob.sync quirk where single segments don't need braces,
@@ -102,13 +106,13 @@ export const exactFilesPatterns = ({ mainDirectory, componentDirectories, fileNa
102
106
  export const compare = (request) => {
103
107
  const { local, external } = request;
104
108
  const splittedLocal = local.map((p) => ({
105
- name: path.basename(p),
109
+ name: getFileName(p),
106
110
  p,
107
111
  }));
108
112
  const localNames = new Set(splittedLocal.map((file) => file.name));
109
113
  const splittedExternal = external
110
114
  .map((p) => ({
111
- name: path.basename(p),
115
+ name: getFileName(p),
112
116
  p,
113
117
  }))
114
118
  .filter((file) => {
@@ -0,0 +1,8 @@
1
+ export type SearchParamValue = string | number | boolean | null | undefined;
2
+ type BuildUrlArgs = {
3
+ baseUrl: string;
4
+ pathname?: string;
5
+ searchParams?: Record<string, SearchParamValue>;
6
+ };
7
+ export declare const buildUrl: ({ baseUrl, pathname, searchParams, }: BuildUrlArgs) => URL;
8
+ export {};
@@ -0,0 +1,12 @@
1
+ const withTrailingSlash = (value) => value.endsWith("/") ? value : `${value}/`;
2
+ const normalizePathname = (pathname = "") => pathname.replace(/^\/+/, "");
3
+ export const buildUrl = ({ baseUrl, pathname, searchParams = {}, }) => {
4
+ const url = new URL(normalizePathname(pathname), withTrailingSlash(baseUrl));
5
+ for (const [key, value] of Object.entries(searchParams)) {
6
+ if (value === undefined || value === null) {
7
+ continue;
8
+ }
9
+ url.searchParams.set(key, String(value));
10
+ }
11
+ return url;
12
+ };
@@ -119,7 +119,10 @@ const removeComponent = (component, config) => {
119
119
  return sbApi
120
120
  .delete(`spaces/${spaceId}/components/${id}`, {})
121
121
  .then((res) => res.data)
122
- .catch((err) => console.error(err));
122
+ .catch((err) => {
123
+ console.error(err);
124
+ throw err;
125
+ });
123
126
  };
124
127
  exports.removeComponent = removeComponent;
125
128
  /*
@@ -178,7 +181,10 @@ const removeComponentGroup = (componentGroup, config) => {
178
181
  return sbApi
179
182
  .delete(`spaces/${spaceId}/component_groups/${id}`, {})
180
183
  .then((res) => res.data)
181
- .catch((err) => console.error(err));
184
+ .catch((err) => {
185
+ console.error(err);
186
+ throw err;
187
+ });
182
188
  };
183
189
  exports.removeComponentGroup = removeComponentGroup;
184
190
  const createComponentsGroup = (groupName, config) => {
@@ -33,6 +33,9 @@ const defaultProgress = (event) => {
33
33
  }
34
34
  };
35
35
  const components_js_1 = require("./components.js");
36
+ function getErrorMessage(error) {
37
+ return error instanceof Error ? error.message : String(error);
38
+ }
36
39
  async function ensureComponentGroupsExist(groupNames, config, options = {}) {
37
40
  try {
38
41
  const existing = await (0, components_js_1.getAllComponentsGroups)(config);
@@ -85,10 +88,34 @@ async function syncComponentsData(args, config) {
85
88
  }
86
89
  }
87
90
  else {
88
- await Promise.allSettled([
89
- ...(existingComponents ?? []).map((c) => (0, components_js_1.removeComponent)(c, config)),
90
- ...(existingGroups ?? []).map((g) => (0, components_js_1.removeComponentGroup)(g, config)),
91
- ]);
91
+ const removalTargets = [
92
+ ...(existingComponents ?? []).map((component) => ({
93
+ type: "component",
94
+ name: String(component?.name ?? component?.id ?? "unknown"),
95
+ remove: () => (0, components_js_1.removeComponent)(component, config),
96
+ })),
97
+ ...(existingGroups ?? []).map((group) => ({
98
+ type: "component group",
99
+ name: String(group?.name ?? group?.id ?? "unknown"),
100
+ remove: () => (0, components_js_1.removeComponentGroup)(group, config),
101
+ })),
102
+ ];
103
+ const removalResults = await Promise.allSettled(removalTargets.map((target) => target.remove()));
104
+ removalResults.forEach((removalResult, index) => {
105
+ if (removalResult.status === "fulfilled")
106
+ return;
107
+ const target = removalTargets[index];
108
+ if (!target)
109
+ return;
110
+ const name = `${target.type} '${target.name}'`;
111
+ const message = getErrorMessage(removalResult.reason);
112
+ result.skipped.push(name);
113
+ result.errors.push({
114
+ name,
115
+ message: `SSOT removal failed: ${message}`,
116
+ });
117
+ logger_js_1.default.warning(`Could not remove ${name} during SSOT sync: ${message}`);
118
+ });
92
119
  }
93
120
  }
94
121
  const nonEmptyComponents = components.filter((c) => !(0, object_utils_js_1.isObjectEmpty)(c));
@@ -170,7 +197,7 @@ async function syncComponentsData(args, config) {
170
197
  catch (error) {
171
198
  result.errors.push({
172
199
  name,
173
- message: error instanceof Error ? error.message : String(error),
200
+ message: getErrorMessage(error),
174
201
  });
175
202
  progress({
176
203
  type: "progress",
@@ -178,7 +205,7 @@ async function syncComponentsData(args, config) {
178
205
  total: totalComponents,
179
206
  name,
180
207
  action: "error",
181
- message: error instanceof Error ? error.message : String(error),
208
+ message: getErrorMessage(error),
182
209
  });
183
210
  }
184
211
  }
@@ -219,7 +246,7 @@ async function syncComponentsData(args, config) {
219
246
  catch (error) {
220
247
  result.errors.push({
221
248
  name,
222
- message: error instanceof Error ? error.message : String(error),
249
+ message: getErrorMessage(error),
223
250
  });
224
251
  progress({
225
252
  type: "progress",
@@ -227,7 +254,7 @@ async function syncComponentsData(args, config) {
227
254
  total: totalComponents,
228
255
  name,
229
256
  action: "error",
230
- message: error instanceof Error ? error.message : String(error),
257
+ message: getErrorMessage(error),
231
258
  });
232
259
  }
233
260
  }
@@ -29,6 +29,10 @@ const extractComponentName = (filePath) => {
29
29
  return lastElement.replace(/\.ts$/, "");
30
30
  };
31
31
  exports.extractComponentName = extractComponentName;
32
+ const getFileName = (filePath) => {
33
+ const normalized = filePath.replace(/\\/g, "/");
34
+ return normalized.substring(normalized.lastIndexOf("/") + 1);
35
+ };
32
36
  /**
33
37
  * Normalizes an array of directory segments for glob pattern usage.
34
38
  * Handles the glob.sync quirk where single segments don't need braces,
@@ -112,13 +116,13 @@ exports.exactFilesPatterns = exactFilesPatterns;
112
116
  const compare = (request) => {
113
117
  const { local, external } = request;
114
118
  const splittedLocal = local.map((p) => ({
115
- name: path_1.default.basename(p),
119
+ name: getFileName(p),
116
120
  p,
117
121
  }));
118
122
  const localNames = new Set(splittedLocal.map((file) => file.name));
119
123
  const splittedExternal = external
120
124
  .map((p) => ({
121
- name: path_1.default.basename(p),
125
+ name: getFileName(p),
122
126
  p,
123
127
  }))
124
128
  .filter((file) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sb-mig",
3
- "version": "6.0.0-beta.9",
3
+ "version": "6.0.1-beta.1",
4
4
  "description": "CLI to rule the world. (and handle stuff related to Storyblok CMS)",
5
5
  "author": "Marcin Krawczyk <marckraw@icloud.com>",
6
6
  "license": "MIT",