ts-repo-utils 5.0.2 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/functions/assert-ext.d.mts +12 -7
- package/dist/functions/assert-ext.d.mts.map +1 -1
- package/dist/functions/assert-ext.mjs +17 -12
- package/dist/functions/assert-ext.mjs.map +1 -1
- package/dist/functions/assert-path-exists.d.mts +2 -0
- package/dist/functions/assert-path-exists.d.mts.map +1 -1
- package/dist/functions/assert-path-exists.mjs +2 -0
- package/dist/functions/assert-path-exists.mjs.map +1 -1
- package/dist/functions/assert-repo-is-clean.d.mts +3 -2
- package/dist/functions/assert-repo-is-clean.d.mts.map +1 -1
- package/dist/functions/assert-repo-is-clean.mjs +4 -2
- package/dist/functions/assert-repo-is-clean.mjs.map +1 -1
- package/dist/functions/diff.d.mts +2 -6
- package/dist/functions/diff.d.mts.map +1 -1
- package/dist/functions/diff.mjs +2 -6
- package/dist/functions/diff.mjs.map +1 -1
- package/dist/functions/exec-async.d.mts +1 -0
- package/dist/functions/exec-async.d.mts.map +1 -1
- package/dist/functions/exec-async.mjs +2 -0
- package/dist/functions/exec-async.mjs.map +1 -1
- package/dist/functions/format.d.mts +10 -3
- package/dist/functions/format.d.mts.map +1 -1
- package/dist/functions/format.mjs +10 -3
- package/dist/functions/format.mjs.map +1 -1
- package/dist/functions/gen-index.d.mts +19 -6
- package/dist/functions/gen-index.d.mts.map +1 -1
- package/dist/functions/gen-index.mjs +63 -42
- package/dist/functions/gen-index.mjs.map +1 -1
- package/dist/functions/should-run.d.mts +8 -5
- package/dist/functions/should-run.d.mts.map +1 -1
- package/dist/functions/should-run.mjs +8 -5
- package/dist/functions/should-run.mjs.map +1 -1
- package/dist/functions/workspace-utils/execute-parallel.d.mts +11 -6
- package/dist/functions/workspace-utils/execute-parallel.d.mts.map +1 -1
- package/dist/functions/workspace-utils/execute-parallel.mjs +50 -38
- package/dist/functions/workspace-utils/execute-parallel.mjs.map +1 -1
- package/dist/functions/workspace-utils/get-workspace-packages.d.mts +7 -4
- package/dist/functions/workspace-utils/get-workspace-packages.d.mts.map +1 -1
- package/dist/functions/workspace-utils/get-workspace-packages.mjs +7 -4
- package/dist/functions/workspace-utils/get-workspace-packages.mjs.map +1 -1
- package/dist/functions/workspace-utils/run-cmd-in-parallel.d.mts +7 -3
- package/dist/functions/workspace-utils/run-cmd-in-parallel.d.mts.map +1 -1
- package/dist/functions/workspace-utils/run-cmd-in-parallel.mjs +9 -5
- package/dist/functions/workspace-utils/run-cmd-in-parallel.mjs.map +1 -1
- package/dist/functions/workspace-utils/run-cmd-in-stages.d.mts +10 -6
- package/dist/functions/workspace-utils/run-cmd-in-stages.d.mts.map +1 -1
- package/dist/functions/workspace-utils/run-cmd-in-stages.mjs +12 -8
- package/dist/functions/workspace-utils/run-cmd-in-stages.mjs.map +1 -1
- package/dist/globals.d.mts +1 -0
- package/dist/node-global.d.mts.map +1 -1
- package/dist/node-global.mjs +1 -0
- package/dist/node-global.mjs.map +1 -1
- package/package.json +19 -8
- package/src/functions/assert-ext.mts +38 -19
- package/src/functions/assert-path-exists.mts +2 -0
- package/src/functions/assert-repo-is-clean.mts +4 -2
- package/src/functions/diff.mts +2 -6
- package/src/functions/diff.test.mts +31 -24
- package/src/functions/exec-async.mts +2 -0
- package/src/functions/format.mts +10 -3
- package/src/functions/format.test.mts +8 -4
- package/src/functions/gen-index.mts +146 -62
- package/src/functions/should-run.mts +8 -5
- package/src/functions/workspace-utils/execute-parallel.mts +52 -39
- package/src/functions/workspace-utils/get-workspace-packages.mts +7 -4
- package/src/functions/workspace-utils/run-cmd-in-parallel.mts +9 -5
- package/src/functions/workspace-utils/run-cmd-in-stages.mts +12 -8
- package/src/globals.d.mts +1 -0
- package/src/node-global.mts +3 -0
- package/src/functions/gen-index.test.mts +0 -18
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
import micromatch from 'micromatch';
|
|
2
|
-
import { Arr, ISet, isString, Result } from 'ts-data-forge';
|
|
2
|
+
import { Arr, ISet, isString, pipe, Result } from 'ts-data-forge';
|
|
3
3
|
import '../node-global.mjs';
|
|
4
4
|
import { assertPathExists } from './assert-path-exists.mjs';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Configuration for index file generation.
|
|
8
|
-
*/
|
|
6
|
+
/** Configuration for index file generation. */
|
|
9
7
|
export type GenIndexConfig = DeepReadonly<{
|
|
10
8
|
/** Target directories to generate index files for (string or array of strings) */
|
|
11
9
|
targetDirectory: string | readonly string[];
|
|
12
10
|
|
|
13
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Glob patterns of files or predicate function to exclude from exports
|
|
13
|
+
* (default: excludes `'**\/*.{test,spec}.?(c|m)[jt]s?(x)'`)
|
|
14
|
+
*/
|
|
15
|
+
exclude?:
|
|
16
|
+
| readonly string[]
|
|
17
|
+
| ((
|
|
18
|
+
args: Readonly<{
|
|
19
|
+
absolutePath: string;
|
|
20
|
+
relativePath: string;
|
|
21
|
+
fileName: string;
|
|
22
|
+
}>,
|
|
23
|
+
) => boolean);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Glob patterns of files or predicate function to exclude from exports
|
|
27
|
+
* (default: excludes `'**\/*.{test,spec}.?(c|m)[jt]s?(x)'`)
|
|
28
|
+
*
|
|
29
|
+
* @deprecated Use `exclude` instead.
|
|
30
|
+
*/
|
|
14
31
|
excludePatterns?: readonly string[];
|
|
15
32
|
|
|
16
33
|
/** File extensions of source files to export (default: ['.ts', '.tsx']) */
|
|
@@ -20,9 +37,9 @@ export type GenIndexConfig = DeepReadonly<{
|
|
|
20
37
|
indexExtension?: `.${string}`;
|
|
21
38
|
|
|
22
39
|
/** File extension to use in export statements (default: '.js') */
|
|
23
|
-
exportExtension?: `.${string}
|
|
40
|
+
exportExtension?: `.${string}` | 'none';
|
|
24
41
|
|
|
25
|
-
/** Command to run for formatting generated files (
|
|
42
|
+
/** Command to run for formatting generated files (optional) */
|
|
26
43
|
formatCommand?: string;
|
|
27
44
|
|
|
28
45
|
/** Whether to suppress output during execution (default: false) */
|
|
@@ -32,15 +49,22 @@ export type GenIndexConfig = DeepReadonly<{
|
|
|
32
49
|
type GenIndexConfigInternal = DeepReadonly<{
|
|
33
50
|
formatCommand: string | undefined;
|
|
34
51
|
targetDirectory: ISet<string>;
|
|
35
|
-
|
|
52
|
+
exclude: (
|
|
53
|
+
args: Readonly<{
|
|
54
|
+
absolutePath: string;
|
|
55
|
+
relativePath: string;
|
|
56
|
+
fileName: string;
|
|
57
|
+
}>,
|
|
58
|
+
) => boolean;
|
|
36
59
|
sourceExtensions: ISet<`.${string}`>;
|
|
37
60
|
indexExtension: `.${string}`;
|
|
38
|
-
exportExtension: `.${string}
|
|
61
|
+
exportExtension: `.${string}` | 'none';
|
|
39
62
|
silent: boolean;
|
|
40
63
|
}>;
|
|
41
64
|
|
|
42
65
|
/**
|
|
43
66
|
* Generates index.ts files recursively in `config.targetDirectory`.
|
|
67
|
+
*
|
|
44
68
|
* @param config - Configuration for index file generation
|
|
45
69
|
* @throws Error if any step fails.
|
|
46
70
|
*/
|
|
@@ -65,7 +89,7 @@ export const genIndex = async (config: GenIndexConfig): Promise<void> => {
|
|
|
65
89
|
}
|
|
66
90
|
|
|
67
91
|
// Step 2: Generate index files
|
|
68
|
-
echo('
|
|
92
|
+
echo('Generating index files...');
|
|
69
93
|
for (const dir of targetDirs) {
|
|
70
94
|
const resolvedDir = path.resolve(dir);
|
|
71
95
|
// eslint-disable-next-line no-await-in-loop
|
|
@@ -75,7 +99,7 @@ export const genIndex = async (config: GenIndexConfig): Promise<void> => {
|
|
|
75
99
|
|
|
76
100
|
// Step 3: Format generated files
|
|
77
101
|
if (filledConfig.formatCommand !== undefined) {
|
|
78
|
-
echo('
|
|
102
|
+
echo('Formatting generated files...');
|
|
79
103
|
const fmtResult = await $(filledConfig.formatCommand, {
|
|
80
104
|
silent: filledConfig.silent,
|
|
81
105
|
});
|
|
@@ -92,17 +116,6 @@ export const genIndex = async (config: GenIndexConfig): Promise<void> => {
|
|
|
92
116
|
}
|
|
93
117
|
};
|
|
94
118
|
|
|
95
|
-
/**
|
|
96
|
-
* Fills the configuration with default values.
|
|
97
|
-
* Default values:
|
|
98
|
-
* - sourceExtensions: ['.ts']
|
|
99
|
-
* - indexExtension: '.ts'
|
|
100
|
-
* - exportExtension: '.js'
|
|
101
|
-
* - excludePatterns: ['**\/*.{test,spec}.?(c|m)[jt]s?(x)']
|
|
102
|
-
* - silent: false
|
|
103
|
-
* @param config - The input configuration object.
|
|
104
|
-
* @returns The configuration object with all required properties filled with defaults.
|
|
105
|
-
*/
|
|
106
119
|
const fillConfig = (config: GenIndexConfig): GenIndexConfigInternal => {
|
|
107
120
|
const sourceExtensions = config.sourceExtensions ?? ['.ts'];
|
|
108
121
|
const exportExtension = config.exportExtension ?? '.js'; // For ESM imports, .mts resolves to .mjs
|
|
@@ -114,14 +127,41 @@ const fillConfig = (config: GenIndexConfig): GenIndexConfigInternal => {
|
|
|
114
127
|
? [config.targetDirectory]
|
|
115
128
|
: config.targetDirectory,
|
|
116
129
|
),
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
131
|
+
exclude: pipe(config.exclude ?? config.excludePatterns).map((exclude) =>
|
|
132
|
+
typeof exclude === 'function'
|
|
133
|
+
? exclude
|
|
134
|
+
: pipe(
|
|
135
|
+
ISet.create<string>(
|
|
136
|
+
Arr.generate(function* () {
|
|
137
|
+
if (exclude !== undefined && Array.isArray(exclude)) {
|
|
138
|
+
yield* exclude;
|
|
139
|
+
}
|
|
140
|
+
yield '**/*.{test,spec}.?(c|m)[jt]s?(x)';
|
|
141
|
+
}),
|
|
142
|
+
),
|
|
143
|
+
).map(
|
|
144
|
+
(set) =>
|
|
145
|
+
({
|
|
146
|
+
relativePath,
|
|
147
|
+
fileName,
|
|
148
|
+
}: Readonly<{
|
|
149
|
+
absolutePath: string;
|
|
150
|
+
relativePath: string;
|
|
151
|
+
fileName: string;
|
|
152
|
+
}>) => {
|
|
153
|
+
for (const pattern of set.values()) {
|
|
154
|
+
if (
|
|
155
|
+
micromatch.isMatch(relativePath, pattern) ||
|
|
156
|
+
micromatch.isMatch(fileName, pattern)
|
|
157
|
+
) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
},
|
|
163
|
+
).value,
|
|
164
|
+
).value,
|
|
125
165
|
sourceExtensions: ISet.create(sourceExtensions),
|
|
126
166
|
indexExtension: config.indexExtension ?? '.ts',
|
|
127
167
|
exportExtension,
|
|
@@ -130,11 +170,13 @@ const fillConfig = (config: GenIndexConfig): GenIndexConfigInternal => {
|
|
|
130
170
|
};
|
|
131
171
|
|
|
132
172
|
/**
|
|
133
|
-
* Generates an index.ts file for the given directory.
|
|
134
|
-
*
|
|
173
|
+
* Generates an index.ts file for the given directory. Recursively calls itself
|
|
174
|
+
* for subdirectories.
|
|
175
|
+
*
|
|
135
176
|
* @param dirPath - The absolute path to the directory to process.
|
|
136
177
|
* @param config - The merged configuration object.
|
|
137
|
-
* @param baseDir - The base directory path for calculating relative paths
|
|
178
|
+
* @param baseDir - The base directory path for calculating relative paths
|
|
179
|
+
* (optional, defaults to dirPath).
|
|
138
180
|
* @throws Error if directory processing fails.
|
|
139
181
|
*/
|
|
140
182
|
const generateIndexFileForDir = async (
|
|
@@ -146,8 +188,8 @@ const generateIndexFileForDir = async (
|
|
|
146
188
|
const actualBaseDir = baseDir ?? dirPath;
|
|
147
189
|
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
148
190
|
|
|
149
|
-
const
|
|
150
|
-
const
|
|
191
|
+
const mut_subDirectories: string[] = [];
|
|
192
|
+
const mut_filesToExport: string[] = [];
|
|
151
193
|
|
|
152
194
|
for (const entry of entries) {
|
|
153
195
|
const entryName = entry.name;
|
|
@@ -155,28 +197,35 @@ const generateIndexFileForDir = async (
|
|
|
155
197
|
const relativePath = path.relative(actualBaseDir, entryPath);
|
|
156
198
|
|
|
157
199
|
if (
|
|
158
|
-
config.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
)
|
|
200
|
+
config.exclude({
|
|
201
|
+
absolutePath: entryPath,
|
|
202
|
+
relativePath,
|
|
203
|
+
fileName: entryName,
|
|
204
|
+
})
|
|
163
205
|
) {
|
|
164
206
|
continue; // Skip excluded directories/files
|
|
165
207
|
}
|
|
166
208
|
|
|
167
209
|
if (entry.isDirectory()) {
|
|
168
|
-
|
|
210
|
+
mut_subDirectories.push(entryName);
|
|
169
211
|
// Recursively call for subdirectories first
|
|
170
212
|
// eslint-disable-next-line no-await-in-loop
|
|
171
213
|
await generateIndexFileForDir(entryPath, config, actualBaseDir);
|
|
172
|
-
} else if (
|
|
173
|
-
|
|
214
|
+
} else if (
|
|
215
|
+
entry.isFile() &&
|
|
216
|
+
shouldExportFile({
|
|
217
|
+
absolutePath: entryPath,
|
|
218
|
+
filePath: relativePath,
|
|
219
|
+
config,
|
|
220
|
+
})
|
|
221
|
+
) {
|
|
222
|
+
mut_filesToExport.push(entryName);
|
|
174
223
|
}
|
|
175
224
|
}
|
|
176
225
|
|
|
177
226
|
const indexContent = generateIndexContent(
|
|
178
|
-
|
|
179
|
-
|
|
227
|
+
mut_subDirectories,
|
|
228
|
+
mut_filesToExport,
|
|
180
229
|
config,
|
|
181
230
|
);
|
|
182
231
|
|
|
@@ -191,20 +240,30 @@ const generateIndexFileForDir = async (
|
|
|
191
240
|
}
|
|
192
241
|
};
|
|
193
242
|
|
|
243
|
+
const indexRegex = /^index\.[cm]?[jt]s[x]?$/u;
|
|
244
|
+
|
|
194
245
|
/**
|
|
195
|
-
* Determines if a file should be exported in the index file.
|
|
196
|
-
*
|
|
246
|
+
* Determines if a file should be exported in the index file. A file is exported
|
|
247
|
+
* if:
|
|
248
|
+
*
|
|
197
249
|
* - It has one of the configured source extensions
|
|
198
250
|
* - It's not an index file itself
|
|
199
251
|
* - It doesn't match any exclusion patterns
|
|
252
|
+
*
|
|
200
253
|
* @param filePath - The relative path to the file from the target directory.
|
|
254
|
+
* @param absolutePath - The absolute path to the file.
|
|
201
255
|
* @param config - The merged configuration object.
|
|
202
256
|
* @returns True if the file should be exported.
|
|
203
257
|
*/
|
|
204
|
-
const shouldExportFile = (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
258
|
+
const shouldExportFile = ({
|
|
259
|
+
absolutePath,
|
|
260
|
+
filePath,
|
|
261
|
+
config,
|
|
262
|
+
}: Readonly<{
|
|
263
|
+
absolutePath: string;
|
|
264
|
+
filePath: string;
|
|
265
|
+
config: GenIndexConfigInternal;
|
|
266
|
+
}>): boolean => {
|
|
208
267
|
const fileName = path.basename(filePath);
|
|
209
268
|
|
|
210
269
|
const ext = path.extname(fileName);
|
|
@@ -216,26 +275,47 @@ const shouldExportFile = (
|
|
|
216
275
|
|
|
217
276
|
// Don't export the index file itself
|
|
218
277
|
if (
|
|
219
|
-
|
|
278
|
+
indexRegex.test(fileName) // Matches index.ts, index.mts, index.js, index.tsx
|
|
220
279
|
) {
|
|
221
280
|
return false;
|
|
222
281
|
}
|
|
223
282
|
|
|
224
283
|
// Check against exclusion patterns
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
284
|
+
if (
|
|
285
|
+
config.exclude({
|
|
286
|
+
absolutePath,
|
|
287
|
+
relativePath: filePath,
|
|
288
|
+
fileName,
|
|
289
|
+
})
|
|
290
|
+
) {
|
|
291
|
+
return false;
|
|
232
292
|
}
|
|
233
293
|
|
|
234
294
|
return true;
|
|
235
295
|
};
|
|
236
296
|
|
|
297
|
+
if (import.meta.vitest !== undefined) {
|
|
298
|
+
describe('index file regex', () => {
|
|
299
|
+
test.each([
|
|
300
|
+
['index.ts', true],
|
|
301
|
+
['index.js', true],
|
|
302
|
+
['index.mts', true],
|
|
303
|
+
['index.mjs', true],
|
|
304
|
+
['index.cts', true],
|
|
305
|
+
['index.cjs', true],
|
|
306
|
+
['index.tsx', true],
|
|
307
|
+
['index.jsx', true],
|
|
308
|
+
['not-index.ts', false],
|
|
309
|
+
['index.txt', false],
|
|
310
|
+
] as const)('indexRegex.test($0) to be $1', (fileName, expected) => {
|
|
311
|
+
expect(indexRegex.test(fileName)).toBe(expected);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
237
316
|
/**
|
|
238
317
|
* Generates the content for an index file.
|
|
318
|
+
*
|
|
239
319
|
* @param subDirectories - Array of subdirectory names.
|
|
240
320
|
* @param filesToExport - Array of file names to export.
|
|
241
321
|
* @param config - The merged configuration object.
|
|
@@ -247,13 +327,17 @@ const generateIndexContent = (
|
|
|
247
327
|
config: GenIndexConfigInternal,
|
|
248
328
|
): string => {
|
|
249
329
|
const exportStatements = [
|
|
250
|
-
...subDirectories.map(
|
|
251
|
-
|
|
330
|
+
...subDirectories.map((subDir) =>
|
|
331
|
+
config.exportExtension === 'none'
|
|
332
|
+
? `export * from "./${subDir}";`
|
|
333
|
+
: `export * from "./${subDir}/index${config.exportExtension}";`,
|
|
252
334
|
),
|
|
253
335
|
...filesToExport.map((file) => {
|
|
254
336
|
const fileNameWithoutExt = path.basename(file, path.extname(file));
|
|
255
337
|
|
|
256
|
-
return
|
|
338
|
+
return config.exportExtension === 'none'
|
|
339
|
+
? `export * from "./${fileNameWithoutExt}";`
|
|
340
|
+
: `export * from "./${fileNameWithoutExt}${config.exportExtension}";`;
|
|
257
341
|
}),
|
|
258
342
|
];
|
|
259
343
|
|
|
@@ -3,18 +3,21 @@ import '../node-global.mjs';
|
|
|
3
3
|
import { getDiffFrom } from './diff.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Checks if TypeScript type checks should run based on the diff from
|
|
7
|
-
* Skips type checks if all changed files are documentation files,
|
|
8
|
-
* or other non-TypeScript files that don't affect type
|
|
6
|
+
* Checks if TypeScript type checks should run based on the diff from
|
|
7
|
+
* origin/main. Skips type checks if all changed files are documentation files,
|
|
8
|
+
* spell check config, or other non-TypeScript files that don't affect type
|
|
9
|
+
* checking.
|
|
9
10
|
*
|
|
10
11
|
* Ignored file patterns:
|
|
12
|
+
*
|
|
11
13
|
* - '.cspell.json'
|
|
12
14
|
* - '**.md'
|
|
13
15
|
* - '**.txt'
|
|
14
16
|
* - 'docs/**'
|
|
15
17
|
*
|
|
16
|
-
* @returns A promise that resolves when the check is complete. Sets
|
|
17
|
-
* environment variable with should_run=true/false if running in
|
|
18
|
+
* @returns A promise that resolves when the check is complete. Sets
|
|
19
|
+
* GITHUB_OUTPUT environment variable with should_run=true/false if running in
|
|
20
|
+
* GitHub Actions.
|
|
18
21
|
*/
|
|
19
22
|
export const checkShouldRunTypeChecks = async (): Promise<void> => {
|
|
20
23
|
// paths-ignore:
|
|
@@ -13,10 +13,13 @@ import { type Package } from './types.mjs';
|
|
|
13
13
|
const DEBUG = false as boolean;
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Executes a npm script across multiple packages in parallel with a concurrency
|
|
16
|
+
* Executes a npm script across multiple packages in parallel with a concurrency
|
|
17
|
+
* limit.
|
|
18
|
+
*
|
|
17
19
|
* @param packages - Array of Package objects to execute the script in
|
|
18
20
|
* @param scriptName - The name of the npm script to execute
|
|
19
|
-
* @param concurrency - Maximum number of packages to process simultaneously
|
|
21
|
+
* @param concurrency - Maximum number of packages to process simultaneously
|
|
22
|
+
* (default: 3)
|
|
20
23
|
* @returns A promise that resolves to an array of execution results
|
|
21
24
|
*/
|
|
22
25
|
export const executeParallel = async (
|
|
@@ -30,7 +33,7 @@ export const executeParallel = async (
|
|
|
30
33
|
Result<Readonly<{ code?: number; skipped?: boolean }>, Error>
|
|
31
34
|
>[] = [];
|
|
32
35
|
|
|
33
|
-
const
|
|
36
|
+
const mut_executing = new Set<Promise<unknown>>();
|
|
34
37
|
|
|
35
38
|
for (const pkg of packages) {
|
|
36
39
|
const promise = executeScript(pkg, scriptName);
|
|
@@ -38,22 +41,22 @@ export const executeParallel = async (
|
|
|
38
41
|
mut_resultPromises.push(promise);
|
|
39
42
|
|
|
40
43
|
const wrappedPromise = promise.finally(() => {
|
|
41
|
-
|
|
44
|
+
mut_executing.delete(wrappedPromise);
|
|
42
45
|
if (DEBUG) {
|
|
43
|
-
console.debug('executing size',
|
|
46
|
+
console.debug('executing size', mut_executing.size);
|
|
44
47
|
}
|
|
45
48
|
});
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
mut_executing.add(wrappedPromise);
|
|
48
51
|
|
|
49
52
|
if (DEBUG) {
|
|
50
|
-
console.debug('executing size',
|
|
53
|
+
console.debug('executing size', mut_executing.size);
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
// If we reach concurrency limit, wait for one to finish
|
|
54
|
-
if (
|
|
57
|
+
if (mut_executing.size >= concurrency) {
|
|
55
58
|
// eslint-disable-next-line no-await-in-loop
|
|
56
|
-
await Promise.race(
|
|
59
|
+
await Promise.race(mut_executing);
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
|
|
@@ -61,12 +64,14 @@ export const executeParallel = async (
|
|
|
61
64
|
};
|
|
62
65
|
|
|
63
66
|
/**
|
|
64
|
-
* Executes a npm script across packages in dependency order stages.
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
+
* Executes a npm script across packages in dependency order stages. Packages
|
|
68
|
+
* are grouped into stages where each stage contains packages whose dependencies
|
|
69
|
+
* have been completed in previous stages.
|
|
70
|
+
*
|
|
67
71
|
* @param packages - Array of Package objects to execute the script in
|
|
68
72
|
* @param scriptName - The name of the npm script to execute
|
|
69
|
-
* @param concurrency - Maximum number of packages to process simultaneously
|
|
73
|
+
* @param concurrency - Maximum number of packages to process simultaneously
|
|
74
|
+
* within each stage (default: 3)
|
|
70
75
|
* @returns A promise that resolves when all stages are complete
|
|
71
76
|
*/
|
|
72
77
|
export const executeStages = async (
|
|
@@ -78,34 +83,36 @@ export const executeStages = async (
|
|
|
78
83
|
|
|
79
84
|
const sorted = topologicalSortPackages(packages, dependencyGraph);
|
|
80
85
|
|
|
81
|
-
const
|
|
82
|
-
const
|
|
86
|
+
const mut_stages: (readonly Package[])[] = [];
|
|
87
|
+
const mut_completed = new Set<string>();
|
|
83
88
|
|
|
84
|
-
while (
|
|
85
|
-
const
|
|
89
|
+
while (mut_completed.size < sorted.length) {
|
|
90
|
+
const mut_stage: Package[] = [];
|
|
86
91
|
|
|
87
92
|
for (const pkg of sorted) {
|
|
88
|
-
if (
|
|
93
|
+
if (mut_completed.has(pkg.name)) continue;
|
|
89
94
|
|
|
90
95
|
const deps = dependencyGraph.get(pkg.name) ?? [];
|
|
91
|
-
const depsCompleted = deps.every((dep) =>
|
|
96
|
+
const depsCompleted = deps.every((dep) => mut_completed.has(dep));
|
|
92
97
|
|
|
93
98
|
if (depsCompleted) {
|
|
94
|
-
|
|
99
|
+
mut_stage.push(pkg);
|
|
95
100
|
}
|
|
96
101
|
}
|
|
97
102
|
|
|
98
|
-
if (
|
|
103
|
+
if (mut_stage.length === 0) {
|
|
99
104
|
throw new Error('Circular dependency detected');
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
for (const pkg of
|
|
107
|
+
mut_stages.push(mut_stage);
|
|
108
|
+
for (const pkg of mut_stage) {
|
|
109
|
+
mut_completed.add(pkg.name);
|
|
110
|
+
}
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
console.log(`\nExecuting ${scriptName} in ${
|
|
113
|
+
console.log(`\nExecuting ${scriptName} in ${mut_stages.length} stages...\n`);
|
|
107
114
|
|
|
108
|
-
for (const [i, stage] of
|
|
115
|
+
for (const [i, stage] of mut_stages.entries()) {
|
|
109
116
|
if (stage.length > 0) {
|
|
110
117
|
console.log(`Stage ${i + 1}: ${stage.map((p) => p.name).join(', ')}`);
|
|
111
118
|
// eslint-disable-next-line no-await-in-loop
|
|
@@ -116,11 +123,14 @@ export const executeStages = async (
|
|
|
116
123
|
|
|
117
124
|
/**
|
|
118
125
|
* Executes a npm script in a specific package directory.
|
|
126
|
+
*
|
|
119
127
|
* @param pkg - The package object containing path and metadata
|
|
120
128
|
* @param scriptName - The name of the npm script to execute
|
|
121
129
|
* @param options - Configuration options
|
|
122
|
-
* @param options.prefix - Whether to prefix output with package name (default:
|
|
123
|
-
*
|
|
130
|
+
* @param options.prefix - Whether to prefix output with package name (default:
|
|
131
|
+
* true)
|
|
132
|
+
* @returns A promise that resolves to execution result with exit code or
|
|
133
|
+
* skipped flag
|
|
124
134
|
*/
|
|
125
135
|
const executeScript = (
|
|
126
136
|
pkg: Package,
|
|
@@ -189,8 +199,9 @@ const executeScript = (
|
|
|
189
199
|
).value;
|
|
190
200
|
|
|
191
201
|
/**
|
|
192
|
-
* Performs a topological sort on packages based on their dependencies,
|
|
193
|
-
*
|
|
202
|
+
* Performs a topological sort on packages based on their dependencies, ensuring
|
|
203
|
+
* dependencies are ordered before their dependents.
|
|
204
|
+
*
|
|
194
205
|
* @param packages - Array of Package objects to sort
|
|
195
206
|
* @returns An array of packages in dependency order (dependencies first)
|
|
196
207
|
*/
|
|
@@ -198,28 +209,28 @@ const topologicalSortPackages = (
|
|
|
198
209
|
packages: readonly Package[],
|
|
199
210
|
dependencyGraph: ReadonlyMap<string, readonly string[]>,
|
|
200
211
|
): readonly Package[] => {
|
|
201
|
-
const
|
|
202
|
-
const
|
|
212
|
+
const mut_visited = new Set<string>();
|
|
213
|
+
const mut_result: string[] = [];
|
|
203
214
|
|
|
204
215
|
const packageMap = new Map(packages.map((p) => [p.name, p]));
|
|
205
216
|
|
|
206
217
|
const visit = (pkgName: string): void => {
|
|
207
|
-
if (
|
|
208
|
-
|
|
218
|
+
if (mut_visited.has(pkgName)) return;
|
|
219
|
+
mut_visited.add(pkgName);
|
|
209
220
|
|
|
210
221
|
const deps = dependencyGraph.get(pkgName) ?? [];
|
|
211
222
|
for (const dep of deps) {
|
|
212
223
|
visit(dep);
|
|
213
224
|
}
|
|
214
225
|
|
|
215
|
-
|
|
226
|
+
mut_result.push(pkgName);
|
|
216
227
|
};
|
|
217
228
|
|
|
218
229
|
for (const pkg of packages) {
|
|
219
230
|
visit(pkg.name);
|
|
220
231
|
}
|
|
221
232
|
|
|
222
|
-
return
|
|
233
|
+
return mut_result
|
|
223
234
|
.map((pkgName) => packageMap.get(pkgName))
|
|
224
235
|
.filter(isNotUndefined);
|
|
225
236
|
};
|
|
@@ -227,21 +238,23 @@ const topologicalSortPackages = (
|
|
|
227
238
|
/**
|
|
228
239
|
* Builds a dependency graph from the given packages, mapping each package name
|
|
229
240
|
* to its internal workspace dependencies.
|
|
241
|
+
*
|
|
230
242
|
* @param packages - Array of Package objects to analyze
|
|
231
|
-
* @returns A readonly map where keys are package names and values are arrays of
|
|
243
|
+
* @returns A readonly map where keys are package names and values are arrays of
|
|
244
|
+
* their dependency package names
|
|
232
245
|
*/
|
|
233
246
|
const buildDependencyGraph = (
|
|
234
247
|
packages: readonly Package[],
|
|
235
248
|
): ReadonlyMap<string, readonly string[]> => {
|
|
236
249
|
const packageMap = new Map(packages.map((p) => [p.name, p]));
|
|
237
|
-
const
|
|
250
|
+
const mut_graph = new Map<string, readonly string[]>();
|
|
238
251
|
|
|
239
252
|
for (const pkg of packages) {
|
|
240
253
|
const deps = Object.keys(pkg.dependencies).filter((depName) =>
|
|
241
254
|
packageMap.has(depName),
|
|
242
255
|
);
|
|
243
|
-
|
|
256
|
+
mut_graph.set(pkg.name, deps);
|
|
244
257
|
}
|
|
245
258
|
|
|
246
|
-
return
|
|
259
|
+
return mut_graph;
|
|
247
260
|
};
|
|
@@ -12,10 +12,13 @@ import '../../node-global.mjs';
|
|
|
12
12
|
import { type Package } from './types.mjs';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Retrieves all workspace packages from a monorepo based on the workspace
|
|
16
|
-
* defined in the root package.json file.
|
|
17
|
-
*
|
|
18
|
-
* @
|
|
15
|
+
* Retrieves all workspace packages from a monorepo based on the workspace
|
|
16
|
+
* patterns defined in the root package.json file.
|
|
17
|
+
*
|
|
18
|
+
* @param rootPackageJsonDir - The directory containing the root package.json
|
|
19
|
+
* file
|
|
20
|
+
* @returns A promise that resolves to an array of Package objects containing
|
|
21
|
+
* package metadata
|
|
19
22
|
*/
|
|
20
23
|
export const getWorkspacePackages = async (
|
|
21
24
|
rootPackageJsonDir: string,
|
|
@@ -5,11 +5,15 @@ import { getWorkspacePackages } from './get-workspace-packages.mjs';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Executes a npm script command across all workspace packages in parallel.
|
|
8
|
+
*
|
|
8
9
|
* @param options - Configuration options for the parallel execution
|
|
9
|
-
* @param options.rootPackageJsonDir - The directory containing the root
|
|
10
|
+
* @param options.rootPackageJsonDir - The directory containing the root
|
|
11
|
+
* package.json file
|
|
10
12
|
* @param options.cmd - The npm script command to execute in each package
|
|
11
|
-
* @param options.concurrency - Maximum number of packages to process
|
|
12
|
-
*
|
|
13
|
+
* @param options.concurrency - Maximum number of packages to process
|
|
14
|
+
* simultaneously (default: 3)
|
|
15
|
+
* @param options.filterWorkspacePattern - Optional function to filter packages
|
|
16
|
+
* by name
|
|
13
17
|
* @returns A promise that resolves when all packages have completed execution
|
|
14
18
|
*/
|
|
15
19
|
export const runCmdInParallelAcrossWorkspaces = async ({
|
|
@@ -33,10 +37,10 @@ export const runCmdInParallelAcrossWorkspaces = async ({
|
|
|
33
37
|
|
|
34
38
|
await executeParallel(filteredPackages, cmd, concurrency);
|
|
35
39
|
console.log(`\n✅ ${cmd} completed successfully`);
|
|
36
|
-
} catch (
|
|
40
|
+
} catch (error) {
|
|
37
41
|
console.error(
|
|
38
42
|
`\n❌ ${cmd} failed:`,
|
|
39
|
-
|
|
43
|
+
error instanceof Error ? error.message : (error?.toString() ?? ''),
|
|
40
44
|
);
|
|
41
45
|
process.exit(1);
|
|
42
46
|
}
|
|
@@ -4,14 +4,18 @@ import { executeStages } from './execute-parallel.mjs';
|
|
|
4
4
|
import { getWorkspacePackages } from './get-workspace-packages.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Executes a npm script command across all workspace packages in dependency
|
|
8
|
-
* Packages are grouped into stages where each stage contains
|
|
9
|
-
* dependencies have been completed in previous stages.
|
|
7
|
+
* Executes a npm script command across all workspace packages in dependency
|
|
8
|
+
* order stages. Packages are grouped into stages where each stage contains
|
|
9
|
+
* packages whose dependencies have been completed in previous stages.
|
|
10
|
+
*
|
|
10
11
|
* @param options - Configuration options for the staged execution
|
|
11
|
-
* @param options.rootPackageJsonDir - The directory containing the root
|
|
12
|
+
* @param options.rootPackageJsonDir - The directory containing the root
|
|
13
|
+
* package.json file
|
|
12
14
|
* @param options.cmd - The npm script command to execute in each package
|
|
13
|
-
* @param options.concurrency - Maximum number of packages to process
|
|
14
|
-
*
|
|
15
|
+
* @param options.concurrency - Maximum number of packages to process
|
|
16
|
+
* simultaneously within each stage (default: 3)
|
|
17
|
+
* @param options.filterWorkspacePattern - Optional function to filter packages
|
|
18
|
+
* by name
|
|
15
19
|
* @returns A promise that resolves when all stages have completed execution
|
|
16
20
|
*/
|
|
17
21
|
export const runCmdInStagesAcrossWorkspaces = async ({
|
|
@@ -35,10 +39,10 @@ export const runCmdInStagesAcrossWorkspaces = async ({
|
|
|
35
39
|
|
|
36
40
|
await executeStages(filteredPackages, cmd, concurrency);
|
|
37
41
|
console.log(`\n✅ ${cmd} completed successfully`);
|
|
38
|
-
} catch (
|
|
42
|
+
} catch (error) {
|
|
39
43
|
console.error(
|
|
40
44
|
`\n❌ ${cmd} failed:`,
|
|
41
|
-
|
|
45
|
+
error instanceof Error ? error.message : (error?.toString() ?? ''),
|
|
42
46
|
);
|
|
43
47
|
process.exit(1);
|
|
44
48
|
}
|
package/src/globals.d.mts
CHANGED