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 +34 -0
- package/dist/api/assets/assets.d.ts +3 -1
- package/dist/api/assets/assets.js +60 -18
- package/dist/api/assets/assets.types.d.ts +35 -2
- package/dist/api/assets/index.d.ts +2 -1
- package/dist/api/assets/index.js +1 -1
- package/dist/api/components/components.js +8 -2
- package/dist/api/components/components.sync.js +35 -8
- package/dist/api/contentHubApi.js +14 -4
- package/dist/api/managementApi.d.ts +2 -0
- package/dist/api/testApi.d.ts +2 -0
- package/dist/cli/commands/init.js +10 -1
- package/dist/cli/commands/sync.js +4 -3
- package/dist/cli/utils/discover.js +28 -20
- package/dist/utils/path-utils.js +6 -2
- package/dist/utils/url-utils.d.ts +8 -0
- package/dist/utils/url-utils.js +12 -0
- package/dist-cjs/api/components/components.js +8 -2
- package/dist-cjs/api/components/components.sync.js +35 -8
- package/dist-cjs/utils/path-utils.js +6 -2
- package/package.json +1 -1
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
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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";
|
package/dist/api/assets/index.js
CHANGED
|
@@ -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) =>
|
|
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) =>
|
|
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
|
-
|
|
83
|
-
...(existingComponents ?? []).map((
|
|
84
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
6
|
-
|
|
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;
|
package/dist/api/testApi.d.ts
CHANGED
|
@@ -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:
|
|
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 {
|
|
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
|
|
78
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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:
|
package/dist/utils/path-utils.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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) =>
|
|
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) =>
|
|
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
|
-
|
|
89
|
-
...(existingComponents ?? []).map((
|
|
90
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
125
|
+
name: getFileName(p),
|
|
122
126
|
p,
|
|
123
127
|
}))
|
|
124
128
|
.filter((file) => {
|
package/package.json
CHANGED