sb-mig 5.6.0-beta.2 → 5.6.0-beta.3

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.
@@ -38,7 +38,7 @@ export const getAssetByName = async ({ spaceId, fileName }, config) => {
38
38
  };
39
39
  const requestSignedUploadUrl = ({ spaceId, payload }, config) => {
40
40
  const { sbApi, debug } = config;
41
- const { filename: _1, asset_folder_id, ext_id, space_id, ...restPayload } = payload;
41
+ const { filename: _1, asset_folder_id, ext_id, space_id, deleted_at: _2, ...restPayload } = payload;
42
42
  const filename = getFileName(payload.filename);
43
43
  const size = getSizeFromURL(payload.filename);
44
44
  return sbApi
@@ -1,5 +1,5 @@
1
1
  import type { RequestBaseConfig } from "../utils/request.js";
2
- import type { ISbResult } from "storyblok-js-client/src/interfaces";
2
+ import type { ISbResult } from "storyblok-js-client";
3
3
  export interface Org {
4
4
  }
5
5
  export interface CurrentUserResult extends ISbResult {
@@ -18,14 +18,25 @@ export declare function loadResourceContent(filePath: string): Promise<any>;
18
18
  * Load multiple resources by file path
19
19
  */
20
20
  export declare function loadResources(filePaths: string[]): Promise<LoadedResource[]>;
21
+ /**
22
+ * Options for component discovery
23
+ */
24
+ export interface DiscoverComponentsOptions {
25
+ /** File extensions to search for (default: [".sb.ts", ".sb.cjs"]) */
26
+ extensions?: string[];
27
+ /** Whether to include external (node_modules) components (default: true) */
28
+ includeExternal?: boolean;
29
+ /** Maximum depth to scan (default: 20, prevents runaway scanning) */
30
+ maxDepth?: number;
31
+ }
21
32
  /**
22
33
  * Discover components in the working directory
23
34
  * Prefers .ts for local files and .cjs for external (node_modules) files
24
35
  * to avoid duplicates when both ESM and CJS versions exist
36
+ *
37
+ * Security: Stays within project bounds and doesn't follow symlinks outside
25
38
  */
26
- export declare function discoverComponents(workingDir: string, options?: {
27
- extensions?: string[];
28
- }): Promise<DiscoveredResource[]>;
39
+ export declare function discoverComponents(workingDir: string, options?: DiscoverComponentsOptions): Promise<DiscoveredResource[]>;
29
40
  /**
30
41
  * Discover datasources in the working directory
31
42
  */
@@ -1,5 +1,5 @@
1
- import { readdir, stat, readFile } from "fs/promises";
2
- import { join } from "path";
1
+ import { readdir, stat, readFile, realpath } from "fs/promises";
2
+ import { join, resolve } from "path";
3
3
  import { pathToFileURL } from "url";
4
4
  /**
5
5
  * Load the content of a resource file (.sb.js, .datasource.js, etc.)
@@ -72,32 +72,75 @@ async function readComponentDirectories(workingDir) {
72
72
  }
73
73
  return ["src", "components", "storyblok"];
74
74
  }
75
+ /**
76
+ * Check if a path is within the project directory
77
+ * Resolves symlinks and ensures we don't escape the project bounds
78
+ */
79
+ async function isWithinProject(targetPath, projectRoot) {
80
+ try {
81
+ // Resolve both paths to handle symlinks
82
+ const resolvedTarget = await realpath(targetPath);
83
+ const resolvedRoot = await realpath(projectRoot);
84
+ // Check if target is within project root
85
+ return (resolvedTarget.startsWith(resolvedRoot + "/") ||
86
+ resolvedTarget === resolvedRoot);
87
+ }
88
+ catch {
89
+ // If we can't resolve the path, assume it's not safe
90
+ return false;
91
+ }
92
+ }
75
93
  /**
76
94
  * Discover components in the working directory
77
95
  * Prefers .ts for local files and .cjs for external (node_modules) files
78
96
  * to avoid duplicates when both ESM and CJS versions exist
97
+ *
98
+ * Security: Stays within project bounds and doesn't follow symlinks outside
79
99
  */
80
100
  export async function discoverComponents(workingDir, options) {
81
101
  const components = [];
82
102
  // Priority order: .ts first (local), then .cjs (for node_modules)
83
103
  // Skip .js and .mjs to avoid duplicates
84
104
  const extensions = options?.extensions ?? [".sb.ts", ".sb.cjs"];
105
+ const includeExternal = options?.includeExternal ?? true;
106
+ const maxDepth = options?.maxDepth ?? 20;
107
+ // Resolve the project root for security checks
108
+ const projectRoot = resolve(workingDir);
85
109
  const componentDirs = await readComponentDirectories(workingDir);
86
- const scanDir = async (dir, isExternal) => {
110
+ const scanDir = async (dir, isExternal, depth) => {
111
+ // Prevent excessive depth
112
+ if (depth > maxDepth) {
113
+ return;
114
+ }
115
+ // Security: Ensure we're still within project bounds
116
+ if (!(await isWithinProject(dir, projectRoot))) {
117
+ return;
118
+ }
87
119
  try {
88
120
  const entries = await readdir(dir, { withFileTypes: true });
89
121
  for (const entry of entries) {
90
122
  const fullPath = join(dir, entry.name);
91
123
  if (entry.isDirectory()) {
124
+ // Skip common non-source directories
92
125
  if (entry.name === ".git" ||
93
126
  entry.name === ".next" ||
94
- entry.name === "dist") {
127
+ entry.name === "dist" ||
128
+ entry.name === ".cache" ||
129
+ entry.name === "coverage") {
130
+ continue;
131
+ }
132
+ // Skip node_modules entirely if not including external
133
+ if (entry.name === "node_modules" && !includeExternal) {
95
134
  continue;
96
135
  }
97
136
  const isNowExternal = isExternal || entry.name === "node_modules";
98
- await scanDir(fullPath, isNowExternal);
137
+ await scanDir(fullPath, isNowExternal, depth + 1);
99
138
  }
100
139
  else if (entry.isFile()) {
140
+ // Skip external files if not including them
141
+ if (isExternal && !includeExternal) {
142
+ continue;
143
+ }
101
144
  for (const ext of extensions) {
102
145
  if (entry.name.endsWith(ext) &&
103
146
  !entry.name.startsWith("_")) {
@@ -119,10 +162,14 @@ export async function discoverComponents(workingDir, options) {
119
162
  };
120
163
  for (const dir of componentDirs) {
121
164
  const fullDir = join(workingDir, dir);
165
+ // Skip if the directory path includes node_modules and we're not including external
166
+ if (dir.includes("node_modules") && !includeExternal) {
167
+ continue;
168
+ }
122
169
  try {
123
170
  const dirStat = await stat(fullDir);
124
171
  if (dirStat.isDirectory()) {
125
- await scanDir(fullDir, dir.includes("node_modules"));
172
+ await scanDir(fullDir, dir.includes("node_modules"), 0);
126
173
  }
127
174
  }
128
175
  catch {
@@ -1,2 +1,2 @@
1
1
  export { discoverComponents, discoverDatasources, discoverRoles, loadResourceContent, loadResources, } from "./discover.js";
2
- export type { DiscoveredResource, LoadedResource } from "./discover.js";
2
+ export type { DiscoverComponentsOptions, DiscoveredResource, LoadedResource, } from "./discover.js";
@@ -2,7 +2,7 @@
2
2
  // https://github.com/maoberlehner/storyblok-migrate
3
3
  // edit: changed a lot in here, but inspiration still is valid :)
4
4
  import path from "path";
5
- import glob from "glob";
5
+ import { globSync } from "glob";
6
6
  import storyblokConfig, { SCHEMA } from "../../config/config.js";
7
7
  import { buildOnTheFly } from "../../rollup/build-on-the-fly.js";
8
8
  import { getFileContentWithRequire } from "../../utils/files.js";
@@ -34,7 +34,7 @@ export const discoverManyByPackageName = (request) => {
34
34
  pattern = path.join(`${directory}`, `${normalizeDiscover({
35
35
  segments: onlyLocalComponentsDirectories,
36
36
  })}`, "**", "package.json");
37
- listOfPackagesJsonFiles = glob.sync(pattern.replace(/\\/g, "/"), {
37
+ listOfPackagesJsonFiles = globSync(pattern.replace(/\\/g, "/"), {
38
38
  follow: true,
39
39
  });
40
40
  listOfFiles = listOfPackagesJsonFiles
@@ -46,7 +46,7 @@ export const discoverManyByPackageName = (request) => {
46
46
  .slice(0, -1)
47
47
  .join(path.sep);
48
48
  const allStoryblokSchemaFilesWithinFolderPattern = path.join(`${fileFolderPath}`, "**", `[^_]*.${storyblokConfig.schemaFileExt}`);
49
- return glob.sync(allStoryblokSchemaFilesWithinFolderPattern.replace(/\\/g, "/"), { follow: true });
49
+ return globSync(allStoryblokSchemaFilesWithinFolderPattern.replace(/\\/g, "/"), { follow: true });
50
50
  })
51
51
  .flat();
52
52
  break;
@@ -56,7 +56,7 @@ export const discoverManyByPackageName = (request) => {
56
56
  pattern = path.join(`${directory}`, `${normalizeDiscover({
57
57
  segments: onlyNodeModulesPackagesComponentsDirectories,
58
58
  })}`, "**", "package.json");
59
- listOfPackagesJsonFiles = glob.sync(pattern.replace(/\\/g, "/"), {
59
+ listOfPackagesJsonFiles = globSync(pattern.replace(/\\/g, "/"), {
60
60
  follow: true,
61
61
  });
62
62
  listOfFiles = listOfPackagesJsonFiles
@@ -68,7 +68,7 @@ export const discoverManyByPackageName = (request) => {
68
68
  .slice(0, -1)
69
69
  .join(path.sep);
70
70
  const allStoryblokSchemaFilesWithinFolderPattern = path.join(`${fileFolderPath}`, "**", `[^_]*.${storyblokConfig.schemaFileExt}`);
71
- return glob.sync(allStoryblokSchemaFilesWithinFolderPattern.replace(/\\/g, "/"), { follow: true });
71
+ return globSync(allStoryblokSchemaFilesWithinFolderPattern.replace(/\\/g, "/"), { follow: true });
72
72
  })
73
73
  .flat();
74
74
  break;
@@ -77,7 +77,7 @@ export const discoverManyByPackageName = (request) => {
77
77
  pattern = path.join(`${directory}`, `${normalizeDiscover({
78
78
  segments: storyblokConfig.componentsDirectories,
79
79
  })}`, "**", "package.json");
80
- listOfPackagesJsonFiles = glob.sync(pattern.replace(/\\/g, "/"), {
80
+ listOfPackagesJsonFiles = globSync(pattern.replace(/\\/g, "/"), {
81
81
  follow: true,
82
82
  });
83
83
  listOfFiles = listOfPackagesJsonFiles
@@ -89,7 +89,7 @@ export const discoverManyByPackageName = (request) => {
89
89
  .slice(0, -1)
90
90
  .join(path.sep);
91
91
  const allStoryblokSchemaFilesWithinFolderPattern = path.join(`${fileFolderPath}`, "**", `[^_]*.${storyblokConfig.schemaFileExt}`);
92
- return glob.sync(allStoryblokSchemaFilesWithinFolderPattern.replace(/\\/g, "/"), { follow: true });
92
+ return globSync(allStoryblokSchemaFilesWithinFolderPattern.replace(/\\/g, "/"), { follow: true });
93
93
  })
94
94
  .flat();
95
95
  break;
@@ -111,7 +111,7 @@ export const discoverOneByPackageName = (request) => {
111
111
  pattern = path.join(`${directory}`, `${normalizeDiscover({
112
112
  segments: onlyLocalComponentsDirectories,
113
113
  })}`, "**", "package.json");
114
- listOfPackagesJsonFiles = glob.sync(pattern.replace(/\\/g, "/"), {
114
+ listOfPackagesJsonFiles = globSync(pattern.replace(/\\/g, "/"), {
115
115
  follow: true,
116
116
  });
117
117
  listOfFiles = listOfPackagesJsonFiles
@@ -124,7 +124,7 @@ export const discoverOneByPackageName = (request) => {
124
124
  .slice(0, -1)
125
125
  .join(path.sep);
126
126
  const allStoryblokSchemaFilesWithinFolderPattern = path.join(`${fileFolderPath}`, "**", `[^_]*.${storyblokConfig.schemaFileExt}`);
127
- return glob.sync(allStoryblokSchemaFilesWithinFolderPattern.replace(/\\/g, "/"), { follow: true });
127
+ return globSync(allStoryblokSchemaFilesWithinFolderPattern.replace(/\\/g, "/"), { follow: true });
128
128
  })
129
129
  .flat();
130
130
  break;
@@ -134,7 +134,7 @@ export const discoverOneByPackageName = (request) => {
134
134
  pattern = path.join(`${directory}`, `${normalizeDiscover({
135
135
  segments: onlyNodeModulesPackagesComponentsDirectories,
136
136
  })}`, "**", "package.json");
137
- listOfPackagesJsonFiles = glob.sync(pattern.replace(/\\/g, "/"), {
137
+ listOfPackagesJsonFiles = globSync(pattern.replace(/\\/g, "/"), {
138
138
  follow: true,
139
139
  });
140
140
  listOfFiles = listOfPackagesJsonFiles
@@ -147,7 +147,7 @@ export const discoverOneByPackageName = (request) => {
147
147
  .slice(0, -1)
148
148
  .join(path.sep);
149
149
  const allStoryblokSchemaFilesWithinFolderPattern = path.join(`${fileFolderPath}`, "**", `[^_]*.${storyblokConfig.schemaFileExt}`);
150
- return glob.sync(allStoryblokSchemaFilesWithinFolderPattern.replace(/\\/g, "/"), { follow: true });
150
+ return globSync(allStoryblokSchemaFilesWithinFolderPattern.replace(/\\/g, "/"), { follow: true });
151
151
  })
152
152
  .flat();
153
153
  break;
@@ -156,7 +156,7 @@ export const discoverOneByPackageName = (request) => {
156
156
  pattern = path.join(`${directory}`, `${normalizeDiscover({
157
157
  segments: storyblokConfig.componentsDirectories,
158
158
  })}`, "**", "package.json");
159
- listOfPackagesJsonFiles = glob.sync(pattern.replace(/\\/g, "/"), {
159
+ listOfPackagesJsonFiles = globSync(pattern.replace(/\\/g, "/"), {
160
160
  follow: true,
161
161
  });
162
162
  listOfFiles = listOfPackagesJsonFiles
@@ -169,7 +169,7 @@ export const discoverOneByPackageName = (request) => {
169
169
  .slice(0, -1)
170
170
  .join(path.sep);
171
171
  const allStoryblokSchemaFilesWithinFolderPattern = path.join(`${fileFolderPath}`, "**", `[^_]*.${storyblokConfig.schemaFileExt}`);
172
- return glob.sync(allStoryblokSchemaFilesWithinFolderPattern.replace(/\\/g, "/"), { follow: true });
172
+ return globSync(allStoryblokSchemaFilesWithinFolderPattern.replace(/\\/g, "/"), { follow: true });
173
173
  })
174
174
  .flat();
175
175
  break;
@@ -192,19 +192,19 @@ export const discoverMany = async (request) => {
192
192
  pattern = path.join(`${directory}`, `${normalizeDiscover({
193
193
  segments: onlyLocalComponentsDirectories,
194
194
  })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.sb.${storyblokConfig.schemaType}`);
195
- const listOfFilesToCompile = glob.sync(pattern.replace(/\\/g, "/"), {
195
+ const listOfFilesToCompile = globSync(pattern.replace(/\\/g, "/"), {
196
196
  follow: true,
197
197
  });
198
198
  await buildOnTheFly({ files: listOfFilesToCompile });
199
199
  pattern = path.join(directory, ".next", "cache", "sb-mig", "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.schemaFileExt}`);
200
- listOFSchemaTSFilesCompiled = glob.sync(pattern.replace(/\\/g, "/"), {
200
+ listOFSchemaTSFilesCompiled = globSync(pattern.replace(/\\/g, "/"), {
201
201
  follow: true,
202
202
  });
203
203
  }
204
204
  pattern = path.join(`${directory}`, `${normalizeDiscover({
205
205
  segments: onlyLocalComponentsDirectories,
206
206
  })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.schemaFileExt}`);
207
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
207
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
208
208
  follow: true,
209
209
  });
210
210
  listOfFiles = [...listOfFiles, ...listOFSchemaTSFilesCompiled];
@@ -215,7 +215,7 @@ export const discoverMany = async (request) => {
215
215
  pattern = path.join(`${directory}`, `${normalizeDiscover({
216
216
  segments: onlyNodeModulesPackagesComponentsDirectories,
217
217
  })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.schemaFileExt}`);
218
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
218
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
219
219
  follow: true,
220
220
  });
221
221
  break;
@@ -226,7 +226,7 @@ export const discoverMany = async (request) => {
226
226
  pattern = path.join(`${directory}`, `${normalizeDiscover({
227
227
  segments: storyblokConfig.componentsDirectories,
228
228
  })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.schemaFileExt}`);
229
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
229
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
230
230
  follow: true,
231
231
  });
232
232
  break;
@@ -251,14 +251,14 @@ export const discoverManyDatasources = async (request) => {
251
251
  })}`, "**", `${normalizeDiscover({
252
252
  segments: request.fileNames,
253
253
  })}.sb.datasource.${storyblokConfig.schemaType}`);
254
- const listOfFilesToCompile = glob.sync(pattern.replace(/\\/g, "/"), {
254
+ const listOfFilesToCompile = globSync(pattern.replace(/\\/g, "/"), {
255
255
  follow: true,
256
256
  });
257
257
  await buildOnTheFly({ files: listOfFilesToCompile });
258
258
  pattern = path.join(directory, ".next", "cache", "sb-mig", "**", `${normalizeDiscover({
259
259
  segments: request.fileNames,
260
260
  })}.${storyblokConfig.datasourceExt}`);
261
- listOFSchemaTSFilesCompiled = glob.sync(pattern.replace(/\\/g, "/"), {
261
+ listOFSchemaTSFilesCompiled = globSync(pattern.replace(/\\/g, "/"), {
262
262
  follow: true,
263
263
  });
264
264
  }
@@ -267,7 +267,7 @@ export const discoverManyDatasources = async (request) => {
267
267
  })}`, "**", `${normalizeDiscover({
268
268
  segments: request.fileNames,
269
269
  })}.${storyblokConfig.datasourceExt}`);
270
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
270
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
271
271
  follow: true,
272
272
  });
273
273
  listOfFiles = [...listOfFiles, ...listOFSchemaTSFilesCompiled];
@@ -278,7 +278,7 @@ export const discoverManyDatasources = async (request) => {
278
278
  pattern = path.join(`${directory}`, `${normalizeDiscover({
279
279
  segments: onlyNodeModulesPackagesComponentsDirectories,
280
280
  })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.datasourceExt}`);
281
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
281
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
282
282
  follow: true,
283
283
  });
284
284
  break;
@@ -287,7 +287,7 @@ export const discoverManyDatasources = async (request) => {
287
287
  pattern = path.join(`${directory}`, `${normalizeDiscover({
288
288
  segments: storyblokConfig.componentsDirectories,
289
289
  })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.datasourceExt}`);
290
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
290
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
291
291
  follow: true,
292
292
  });
293
293
  break;
@@ -308,7 +308,7 @@ export const discoverStories = (request) => {
308
308
  const pattern = path.join(`${directory}`, `${normalizeDiscover({
309
309
  segments: onlyLocalComponentsDirectories,
310
310
  })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.storiesExt}`);
311
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
311
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
312
312
  follow: true,
313
313
  });
314
314
  break;
@@ -332,7 +332,7 @@ export const discoverMigrationConfig = (request) => {
332
332
  pattern = path.join(`${directory}`, `${normalizeDiscover({
333
333
  segments: storyblokConfig.componentsDirectories,
334
334
  })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.migrationConfigExt}`);
335
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
335
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
336
336
  follow: true,
337
337
  });
338
338
  break;
@@ -353,7 +353,7 @@ export const discoverVersionMapping = (request) => {
353
353
  })}`, "**", `${normalizeDiscover({
354
354
  segments: request.fileNames,
355
355
  })}.${"sb.migrations.cjs"}`);
356
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
356
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
357
357
  follow: true,
358
358
  });
359
359
  break;
@@ -376,7 +376,7 @@ export const discoverDatasources = async (request) => {
376
376
  pattern = path.join(`${directory}`, `${normalizeDiscover({
377
377
  segments: onlyLocalComponentsDirectories,
378
378
  })}`, "**", `[^_]*.sb.datasource.${storyblokConfig.schemaType}`);
379
- const listOfFilesToCompile = glob.sync(pattern.replace(/\\/g, "/"), {
379
+ const listOfFilesToCompile = globSync(pattern.replace(/\\/g, "/"), {
380
380
  follow: true,
381
381
  });
382
382
  if (storyblokConfig.debug) {
@@ -386,14 +386,14 @@ export const discoverDatasources = async (request) => {
386
386
  }
387
387
  await buildOnTheFly({ files: listOfFilesToCompile });
388
388
  pattern = path.join(directory, ".next", "cache", "sb-mig", "**", `[^_]*.${storyblokConfig.datasourceExt}`);
389
- listOFSchemaTSFilesCompiled = glob.sync(pattern.replace(/\\/g, "/"), {
389
+ listOFSchemaTSFilesCompiled = globSync(pattern.replace(/\\/g, "/"), {
390
390
  follow: true,
391
391
  });
392
392
  }
393
393
  pattern = path.join(`${directory}`, `${normalizeDiscover({
394
394
  segments: onlyLocalComponentsDirectories,
395
395
  })}`, "**", `[^_]*.${storyblokConfig.datasourceExt}`);
396
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
396
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
397
397
  follow: true,
398
398
  });
399
399
  listOfFiles = [...listOfFiles, ...listOFSchemaTSFilesCompiled];
@@ -404,7 +404,7 @@ export const discoverDatasources = async (request) => {
404
404
  pattern = path.join(`${directory}`, `${normalizeDiscover({
405
405
  segments: onlyNodeModulesPackagesComponentsDirectories,
406
406
  })}`, "**", `[^_]*.${storyblokConfig.datasourceExt}`);
407
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
407
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
408
408
  follow: true,
409
409
  });
410
410
  break;
@@ -413,7 +413,7 @@ export const discoverDatasources = async (request) => {
413
413
  pattern = path.join(`${directory}`, `${normalizeDiscover({
414
414
  segments: storyblokConfig.componentsDirectories,
415
415
  })}`, "**", `[^_]*.${storyblokConfig.datasourceExt}`);
416
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
416
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
417
417
  follow: true,
418
418
  });
419
419
  break;
@@ -438,12 +438,12 @@ export const discover = async (request) => {
438
438
  componentDirectories: onlyLocalComponentsDirectories,
439
439
  ext: "sb.ts",
440
440
  });
441
- const listOfFilesToCompile = glob.sync(pattern.replace(/\\/g, "/"), {
441
+ const listOfFilesToCompile = globSync(pattern.replace(/\\/g, "/"), {
442
442
  follow: true,
443
443
  });
444
444
  await buildOnTheFly({ files: listOfFilesToCompile });
445
445
  pattern = path.join(directory, ".next", "cache", "sb-mig", "**", `[^_]*.${storyblokConfig.schemaFileExt}`);
446
- listOFSchemaTSFilesCompiled = glob.sync(pattern.replace(/\\/g, "/"), {
446
+ listOFSchemaTSFilesCompiled = globSync(pattern.replace(/\\/g, "/"), {
447
447
  follow: true,
448
448
  });
449
449
  }
@@ -452,7 +452,7 @@ export const discover = async (request) => {
452
452
  componentDirectories: onlyLocalComponentsDirectories,
453
453
  ext: storyblokConfig.schemaFileExt,
454
454
  });
455
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
455
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
456
456
  follow: true,
457
457
  });
458
458
  listOfFiles = [...listOfFiles, ...listOFSchemaTSFilesCompiled];
@@ -465,7 +465,7 @@ export const discover = async (request) => {
465
465
  componentDirectories: onlyNodeModulesPackagesComponentsDirectories,
466
466
  ext: storyblokConfig.schemaFileExt,
467
467
  });
468
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
468
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
469
469
  follow: true,
470
470
  });
471
471
  break;
@@ -476,7 +476,7 @@ export const discover = async (request) => {
476
476
  componentDirectories: storyblokConfig.componentsDirectories,
477
477
  ext: storyblokConfig.schemaFileExt,
478
478
  });
479
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
479
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
480
480
  follow: true,
481
481
  });
482
482
  break;
@@ -501,12 +501,12 @@ export const discoverResolvers = async (request) => {
501
501
  componentDirectories: onlyLocalComponentsDirectories,
502
502
  ext: "sb.resolvers.ts",
503
503
  });
504
- const listOfFilesToCompile = glob.sync(pattern.replace(/\\/g, "/"), {
504
+ const listOfFilesToCompile = globSync(pattern.replace(/\\/g, "/"), {
505
505
  follow: true,
506
506
  });
507
507
  await buildOnTheFly({ files: listOfFilesToCompile });
508
508
  pattern = path.join(directory, ".next", "cache", "sb-mig", "**", `[^_]*.sb.resolvers.cjs`);
509
- listOFSchemaTSFilesCompiled = glob.sync(pattern.replace(/\\/g, "/"), {
509
+ listOFSchemaTSFilesCompiled = globSync(pattern.replace(/\\/g, "/"), {
510
510
  follow: true,
511
511
  });
512
512
  }
@@ -515,7 +515,7 @@ export const discoverResolvers = async (request) => {
515
515
  componentDirectories: onlyLocalComponentsDirectories,
516
516
  ext: "sb.resolvers.cjs",
517
517
  });
518
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
518
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
519
519
  follow: true,
520
520
  });
521
521
  listOfFiles = [...listOfFiles, ...listOFSchemaTSFilesCompiled];
@@ -539,19 +539,19 @@ export const discoverRoles = async (request) => {
539
539
  pattern = path.join(`${directory}`, `${normalizeDiscover({
540
540
  segments: onlyLocalComponentsDirectories,
541
541
  })}`, "**", `[^_]*.sb.roles.${storyblokConfig.schemaType}`);
542
- const listOfFilesToCompile = glob.sync(pattern.replace(/\\/g, "/"), {
542
+ const listOfFilesToCompile = globSync(pattern.replace(/\\/g, "/"), {
543
543
  follow: true,
544
544
  });
545
545
  await buildOnTheFly({ files: listOfFilesToCompile });
546
546
  pattern = path.join(directory, ".next", "cache", "sb-mig", "**", `[^_]*.${storyblokConfig.rolesExt}`);
547
- listOFSchemaTSFilesCompiled = glob.sync(pattern.replace(/\\/g, "/"), {
547
+ listOFSchemaTSFilesCompiled = globSync(pattern.replace(/\\/g, "/"), {
548
548
  follow: true,
549
549
  });
550
550
  }
551
551
  pattern = path.join(`${directory}`, `${normalizeDiscover({
552
552
  segments: onlyLocalComponentsDirectories,
553
553
  })}`, "**", `[^_]*.${storyblokConfig.rolesExt}`);
554
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
554
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
555
555
  follow: true,
556
556
  });
557
557
  listOfFiles = [...listOfFiles, ...listOFSchemaTSFilesCompiled];
@@ -562,7 +562,7 @@ export const discoverRoles = async (request) => {
562
562
  pattern = path.join(`${directory}`, `${normalizeDiscover({
563
563
  segments: onlyNodeModulesPackagesComponentsDirectories,
564
564
  })}`, "**", `[^_]*.${storyblokConfig.rolesExt}`);
565
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
565
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
566
566
  follow: true,
567
567
  });
568
568
  break;
@@ -571,7 +571,7 @@ export const discoverRoles = async (request) => {
571
571
  pattern = path.join(`${directory}`, `${normalizeDiscover({
572
572
  segments: storyblokConfig.componentsDirectories,
573
573
  })}`, "**", `[^_]*.${storyblokConfig.rolesExt}`);
574
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
574
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
575
575
  follow: true,
576
576
  });
577
577
  break;
@@ -596,21 +596,21 @@ export const discoverManyRoles = async (request) => {
596
596
  })}`, "**", `${normalizeDiscover({
597
597
  segments: request.fileNames,
598
598
  })}.sb.roles.${storyblokConfig.schemaType}`);
599
- const listOfFilesToCompile = glob.sync(pattern.replace(/\\/g, "/"), {
599
+ const listOfFilesToCompile = globSync(pattern.replace(/\\/g, "/"), {
600
600
  follow: true,
601
601
  });
602
602
  await buildOnTheFly({ files: listOfFilesToCompile });
603
603
  pattern = path.join(directory, ".next", "cache", "sb-mig", "**", `${normalizeDiscover({
604
604
  segments: request.fileNames,
605
605
  })}.${storyblokConfig.rolesExt}`);
606
- listOFSchemaTSFilesCompiled = glob.sync(pattern.replace(/\\/g, "/"), {
606
+ listOFSchemaTSFilesCompiled = globSync(pattern.replace(/\\/g, "/"), {
607
607
  follow: true,
608
608
  });
609
609
  }
610
610
  pattern = path.join(`${directory}`, `${normalizeDiscover({
611
611
  segments: onlyLocalComponentsDirectories,
612
612
  })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.rolesExt}`);
613
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
613
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
614
614
  follow: true,
615
615
  });
616
616
  listOfFiles = [...listOfFiles, ...listOFSchemaTSFilesCompiled];
@@ -621,7 +621,7 @@ export const discoverManyRoles = async (request) => {
621
621
  pattern = path.join(`${directory}`, `${normalizeDiscover({
622
622
  segments: onlyNodeModulesPackagesComponentsDirectories,
623
623
  })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.rolesExt}`);
624
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
624
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
625
625
  follow: true,
626
626
  });
627
627
  break;
@@ -630,7 +630,7 @@ export const discoverManyRoles = async (request) => {
630
630
  pattern = path.join(`${directory}`, `${normalizeDiscover({
631
631
  segments: storyblokConfig.componentsDirectories,
632
632
  })}`, "**", `${normalizeDiscover({ segments: request.fileNames })}.${storyblokConfig.rolesExt}`);
633
- listOfFiles = glob.sync(pattern.replace(/\\/g, "/"), {
633
+ listOfFiles = globSync(pattern.replace(/\\/g, "/"), {
634
634
  follow: true,
635
635
  });
636
636
  break;
@@ -112,32 +112,75 @@ async function readComponentDirectories(workingDir) {
112
112
  }
113
113
  return ["src", "components", "storyblok"];
114
114
  }
115
+ /**
116
+ * Check if a path is within the project directory
117
+ * Resolves symlinks and ensures we don't escape the project bounds
118
+ */
119
+ async function isWithinProject(targetPath, projectRoot) {
120
+ try {
121
+ // Resolve both paths to handle symlinks
122
+ const resolvedTarget = await (0, promises_1.realpath)(targetPath);
123
+ const resolvedRoot = await (0, promises_1.realpath)(projectRoot);
124
+ // Check if target is within project root
125
+ return (resolvedTarget.startsWith(resolvedRoot + "/") ||
126
+ resolvedTarget === resolvedRoot);
127
+ }
128
+ catch {
129
+ // If we can't resolve the path, assume it's not safe
130
+ return false;
131
+ }
132
+ }
115
133
  /**
116
134
  * Discover components in the working directory
117
135
  * Prefers .ts for local files and .cjs for external (node_modules) files
118
136
  * to avoid duplicates when both ESM and CJS versions exist
137
+ *
138
+ * Security: Stays within project bounds and doesn't follow symlinks outside
119
139
  */
120
140
  async function discoverComponents(workingDir, options) {
121
141
  const components = [];
122
142
  // Priority order: .ts first (local), then .cjs (for node_modules)
123
143
  // Skip .js and .mjs to avoid duplicates
124
144
  const extensions = options?.extensions ?? [".sb.ts", ".sb.cjs"];
145
+ const includeExternal = options?.includeExternal ?? true;
146
+ const maxDepth = options?.maxDepth ?? 20;
147
+ // Resolve the project root for security checks
148
+ const projectRoot = (0, path_1.resolve)(workingDir);
125
149
  const componentDirs = await readComponentDirectories(workingDir);
126
- const scanDir = async (dir, isExternal) => {
150
+ const scanDir = async (dir, isExternal, depth) => {
151
+ // Prevent excessive depth
152
+ if (depth > maxDepth) {
153
+ return;
154
+ }
155
+ // Security: Ensure we're still within project bounds
156
+ if (!(await isWithinProject(dir, projectRoot))) {
157
+ return;
158
+ }
127
159
  try {
128
160
  const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
129
161
  for (const entry of entries) {
130
162
  const fullPath = (0, path_1.join)(dir, entry.name);
131
163
  if (entry.isDirectory()) {
164
+ // Skip common non-source directories
132
165
  if (entry.name === ".git" ||
133
166
  entry.name === ".next" ||
134
- entry.name === "dist") {
167
+ entry.name === "dist" ||
168
+ entry.name === ".cache" ||
169
+ entry.name === "coverage") {
170
+ continue;
171
+ }
172
+ // Skip node_modules entirely if not including external
173
+ if (entry.name === "node_modules" && !includeExternal) {
135
174
  continue;
136
175
  }
137
176
  const isNowExternal = isExternal || entry.name === "node_modules";
138
- await scanDir(fullPath, isNowExternal);
177
+ await scanDir(fullPath, isNowExternal, depth + 1);
139
178
  }
140
179
  else if (entry.isFile()) {
180
+ // Skip external files if not including them
181
+ if (isExternal && !includeExternal) {
182
+ continue;
183
+ }
141
184
  for (const ext of extensions) {
142
185
  if (entry.name.endsWith(ext) &&
143
186
  !entry.name.startsWith("_")) {
@@ -159,10 +202,14 @@ async function discoverComponents(workingDir, options) {
159
202
  };
160
203
  for (const dir of componentDirs) {
161
204
  const fullDir = (0, path_1.join)(workingDir, dir);
205
+ // Skip if the directory path includes node_modules and we're not including external
206
+ if (dir.includes("node_modules") && !includeExternal) {
207
+ continue;
208
+ }
162
209
  try {
163
210
  const dirStat = await (0, promises_1.stat)(fullDir);
164
211
  if (dirStat.isDirectory()) {
165
- await scanDir(fullDir, dir.includes("node_modules"));
212
+ await scanDir(fullDir, dir.includes("node_modules"), 0);
166
213
  }
167
214
  }
168
215
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sb-mig",
3
- "version": "5.6.0-beta.2",
3
+ "version": "5.6.0-beta.3",
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",
@@ -33,7 +33,7 @@
33
33
  "./dist/*": "./dist/*"
34
34
  },
35
35
  "engines": {
36
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
36
+ "node": ">=18.0.0"
37
37
  },
38
38
  "files": [
39
39
  "/dist",
@@ -76,19 +76,19 @@
76
76
  },
77
77
  "dependencies": {
78
78
  "@swc/core": "1.3.41",
79
- "@swc/helpers": "0.4.14",
79
+ "@swc/helpers": "^0.5.18",
80
80
  "chalk": "^4.1.2",
81
- "dotenv": "^16.4.5",
81
+ "dotenv": "^17.2.3",
82
82
  "form-data": "^4.0.0",
83
83
  "fs-extra": "^11.2.0",
84
- "glob": "8",
84
+ "glob": "^11.0.3",
85
85
  "meow": "^11.0.0",
86
86
  "ncp": "^2.0.0",
87
87
  "node-fetch": "^3.3.2",
88
88
  "rollup": "^3.28.0",
89
89
  "rollup-plugin-ts": "^3.4.4",
90
90
  "semver": "^7.6.2",
91
- "storyblok-js-client": "5.12.0",
91
+ "storyblok-js-client": "^7.2.1",
92
92
  "storyblok-schema-types": "^1.2.4",
93
93
  "typescript": "^5.1.6",
94
94
  "uuid": "^9.0.0"
@@ -104,10 +104,9 @@
104
104
  "@sindresorhus/tsconfig": "^3.0.1",
105
105
  "@storyblok/react": "^3.0.10",
106
106
  "@types/fs-extra": "^11.0.4",
107
- "@types/glob": "^8.1.0",
108
107
  "@types/ncp": "^2.0.8",
109
- "@types/node": "18.16.18",
110
- "@types/uuid": "^9.0.7",
108
+ "@types/node": "^22.15.0",
109
+ "@types/uuid": "^10.0.0",
111
110
  "@typescript-eslint/eslint-plugin": "^6.4.1",
112
111
  "@typescript-eslint/parser": "^6.4.1",
113
112
  "@vitest/coverage-v8": "^2.1.0",