ts-repo-utils 5.1.0 → 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 +4 -2
- 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 +1 -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 +18 -5
- package/dist/functions/gen-index.d.mts.map +1 -1
- package/dist/functions/gen-index.mjs +48 -32
- 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 +40 -28
- 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 +7 -3
- 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 +10 -6
- package/dist/functions/workspace-utils/run-cmd-in-stages.mjs.map +1 -1
- package/package.json +3 -2
- package/src/functions/assert-ext.mts +13 -7
- 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 +13 -13
- package/src/functions/exec-async.mts +1 -0
- package/src/functions/format.mts +10 -3
- package/src/functions/gen-index.mts +107 -48
- package/src/functions/should-run.mts +8 -5
- package/src/functions/workspace-utils/execute-parallel.mts +42 -29
- package/src/functions/workspace-utils/get-workspace-packages.mts +7 -4
- package/src/functions/workspace-utils/run-cmd-in-parallel.mts +7 -3
- package/src/functions/workspace-utils/run-cmd-in-stages.mts +10 -6
|
@@ -26,12 +26,12 @@ describe('diff', () => {
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
test('should detect newly created files', async () => {
|
|
29
|
-
const
|
|
29
|
+
const mut_testFiles = new Set<string>();
|
|
30
30
|
|
|
31
31
|
// Create a new file in project root
|
|
32
32
|
const testFileName = 'test-new-file.tmp';
|
|
33
33
|
const testFilePath = path.join(process.cwd(), testFileName);
|
|
34
|
-
|
|
34
|
+
mut_testFiles.add(testFilePath);
|
|
35
35
|
|
|
36
36
|
await fs.writeFile(testFilePath, 'test content');
|
|
37
37
|
|
|
@@ -43,25 +43,25 @@ describe('diff', () => {
|
|
|
43
43
|
expect(files).toContain(testFileName);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
await cleanupTestFiles(
|
|
46
|
+
await cleanupTestFiles(mut_testFiles);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
test('should detect modified existing files', async () => {
|
|
50
|
-
const
|
|
50
|
+
const mut_testFiles = new Set<string>();
|
|
51
51
|
|
|
52
52
|
// Use an existing file in the project that we can modify safely
|
|
53
53
|
const testFileName = 'test-modify-file.tmp';
|
|
54
|
-
const
|
|
55
|
-
|
|
54
|
+
const mut_testFilePath = path.join(process.cwd(), testFileName);
|
|
55
|
+
mut_testFiles.add(mut_testFilePath);
|
|
56
56
|
|
|
57
57
|
// Create and commit the file first
|
|
58
|
-
await fs.writeFile(
|
|
58
|
+
await fs.writeFile(mut_testFilePath, 'initial content');
|
|
59
59
|
|
|
60
60
|
// Add to git to track it
|
|
61
61
|
await $(`git add ${testFileName}`, { silent: true });
|
|
62
62
|
|
|
63
63
|
// Modify the file
|
|
64
|
-
await fs.writeFile(
|
|
64
|
+
await fs.writeFile(mut_testFilePath, 'modified content');
|
|
65
65
|
|
|
66
66
|
const result = await getUntrackedFiles({ silent: true });
|
|
67
67
|
|
|
@@ -74,17 +74,17 @@ describe('diff', () => {
|
|
|
74
74
|
// Reset git state
|
|
75
75
|
await $(`git reset HEAD ${testFileName}`, { silent: true });
|
|
76
76
|
|
|
77
|
-
await cleanupTestFiles(
|
|
77
|
+
await cleanupTestFiles(mut_testFiles);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
test('should detect multiple types of changes', async () => {
|
|
81
|
-
const
|
|
81
|
+
const mut_testFiles = new Set<string>();
|
|
82
82
|
|
|
83
83
|
// Create multiple test files
|
|
84
84
|
const newFile = path.join(process.cwd(), 'test-new-file.tmp');
|
|
85
85
|
const modifyFile = path.join(process.cwd(), 'test-modify-file.tmp');
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
mut_testFiles.add(newFile);
|
|
87
|
+
mut_testFiles.add(modifyFile);
|
|
88
88
|
|
|
89
89
|
// Create new file
|
|
90
90
|
await fs.writeFile(newFile, 'new file content');
|
|
@@ -108,7 +108,7 @@ describe('diff', () => {
|
|
|
108
108
|
// Reset git state
|
|
109
109
|
await $(`git reset HEAD test-modify-file.tmp`, { silent: true });
|
|
110
110
|
|
|
111
|
-
await cleanupTestFiles(
|
|
111
|
+
await cleanupTestFiles(mut_testFiles);
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
test('should exclude deleted files from results', async () => {
|
package/src/functions/format.mts
CHANGED
|
@@ -5,6 +5,7 @@ import { getDiffFrom, getUntrackedFiles } from './diff.mjs';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Format a list of files using Prettier
|
|
8
|
+
*
|
|
8
9
|
* @param files - Array of file paths to format
|
|
9
10
|
* @returns 'ok' if successful, 'err' if any errors occurred
|
|
10
11
|
*/
|
|
@@ -74,6 +75,7 @@ export const formatFilesList = async (
|
|
|
74
75
|
|
|
75
76
|
/**
|
|
76
77
|
* Format files matching the given glob pattern using Prettier
|
|
78
|
+
*
|
|
77
79
|
* @param pathGlob - Glob pattern to match files
|
|
78
80
|
* @returns 'ok' if successful, 'err' if any errors occurred
|
|
79
81
|
*/
|
|
@@ -107,6 +109,7 @@ export const formatFiles = async (
|
|
|
107
109
|
|
|
108
110
|
/**
|
|
109
111
|
* Format only files that have been changed (git status)
|
|
112
|
+
*
|
|
110
113
|
* @param options - Options for formatting
|
|
111
114
|
* @returns 'ok' if successful, 'err' if any errors occurred
|
|
112
115
|
*/
|
|
@@ -169,10 +172,14 @@ export const formatUntracked = async (
|
|
|
169
172
|
|
|
170
173
|
/**
|
|
171
174
|
* Format only files that differ from the specified base branch or commit
|
|
172
|
-
*
|
|
175
|
+
*
|
|
176
|
+
* @param base - Base branch name or commit hash to compare against (defaults to
|
|
177
|
+
* 'main')
|
|
173
178
|
* @param options - Options for formatting
|
|
174
|
-
* @param options.includeUntracked - Include untracked files in addition to diff
|
|
175
|
-
*
|
|
179
|
+
* @param options.includeUntracked - Include untracked files in addition to diff
|
|
180
|
+
* files (default is true)
|
|
181
|
+
* @param options.silent - Silent mode to suppress command output (default is
|
|
182
|
+
* false)
|
|
176
183
|
* @returns 'ok' if successful, 'err' if any errors occurred
|
|
177
184
|
*/
|
|
178
185
|
export const formatDiffFrom = async (
|
|
@@ -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']) */
|
|
@@ -22,7 +39,7 @@ export type GenIndexConfig = DeepReadonly<{
|
|
|
22
39
|
/** File extension to use in export statements (default: '.js') */
|
|
23
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,7 +49,13 @@ 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
61
|
exportExtension: `.${string}` | 'none';
|
|
@@ -41,6 +64,7 @@ type GenIndexConfigInternal = DeepReadonly<{
|
|
|
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
|
*/
|
|
@@ -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 (
|
|
@@ -155,11 +197,11 @@ 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
|
}
|
|
@@ -169,7 +211,14 @@ const generateIndexFileForDir = async (
|
|
|
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 (
|
|
214
|
+
} else if (
|
|
215
|
+
entry.isFile() &&
|
|
216
|
+
shouldExportFile({
|
|
217
|
+
absolutePath: entryPath,
|
|
218
|
+
filePath: relativePath,
|
|
219
|
+
config,
|
|
220
|
+
})
|
|
221
|
+
) {
|
|
173
222
|
mut_filesToExport.push(entryName);
|
|
174
223
|
}
|
|
175
224
|
}
|
|
@@ -194,19 +243,27 @@ const generateIndexFileForDir = async (
|
|
|
194
243
|
const indexRegex = /^index\.[cm]?[jt]s[x]?$/u;
|
|
195
244
|
|
|
196
245
|
/**
|
|
197
|
-
* Determines if a file should be exported in the index file.
|
|
198
|
-
*
|
|
246
|
+
* Determines if a file should be exported in the index file. A file is exported
|
|
247
|
+
* if:
|
|
248
|
+
*
|
|
199
249
|
* - It has one of the configured source extensions
|
|
200
250
|
* - It's not an index file itself
|
|
201
251
|
* - It doesn't match any exclusion patterns
|
|
252
|
+
*
|
|
202
253
|
* @param filePath - The relative path to the file from the target directory.
|
|
254
|
+
* @param absolutePath - The absolute path to the file.
|
|
203
255
|
* @param config - The merged configuration object.
|
|
204
256
|
* @returns True if the file should be exported.
|
|
205
257
|
*/
|
|
206
|
-
const shouldExportFile = (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
258
|
+
const shouldExportFile = ({
|
|
259
|
+
absolutePath,
|
|
260
|
+
filePath,
|
|
261
|
+
config,
|
|
262
|
+
}: Readonly<{
|
|
263
|
+
absolutePath: string;
|
|
264
|
+
filePath: string;
|
|
265
|
+
config: GenIndexConfigInternal;
|
|
266
|
+
}>): boolean => {
|
|
210
267
|
const fileName = path.basename(filePath);
|
|
211
268
|
|
|
212
269
|
const ext = path.extname(fileName);
|
|
@@ -224,13 +281,14 @@ const shouldExportFile = (
|
|
|
224
281
|
}
|
|
225
282
|
|
|
226
283
|
// Check against exclusion patterns
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
284
|
+
if (
|
|
285
|
+
config.exclude({
|
|
286
|
+
absolutePath,
|
|
287
|
+
relativePath: filePath,
|
|
288
|
+
fileName,
|
|
289
|
+
})
|
|
290
|
+
) {
|
|
291
|
+
return false;
|
|
234
292
|
}
|
|
235
293
|
|
|
236
294
|
return true;
|
|
@@ -257,6 +315,7 @@ if (import.meta.vitest !== undefined) {
|
|
|
257
315
|
|
|
258
316
|
/**
|
|
259
317
|
* Generates the content for an index file.
|
|
318
|
+
*
|
|
260
319
|
* @param subDirectories - Array of subdirectory names.
|
|
261
320
|
* @param filesToExport - Array of file names to export.
|
|
262
321
|
* @param config - The merged configuration object.
|
|
@@ -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 (
|
|
@@ -79,16 +84,16 @@ export const executeStages = async (
|
|
|
79
84
|
const sorted = topologicalSortPackages(packages, dependencyGraph);
|
|
80
85
|
|
|
81
86
|
const mut_stages: (readonly Package[])[] = [];
|
|
82
|
-
const
|
|
87
|
+
const mut_completed = new Set<string>();
|
|
83
88
|
|
|
84
|
-
while (
|
|
89
|
+
while (mut_completed.size < sorted.length) {
|
|
85
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);
|
|
@@ -100,7 +105,9 @@ export const executeStages = async (
|
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
mut_stages.push(mut_stage);
|
|
103
|
-
for (const pkg of mut_stage)
|
|
108
|
+
for (const pkg of mut_stage) {
|
|
109
|
+
mut_completed.add(pkg.name);
|
|
110
|
+
}
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
console.log(`\nExecuting ${scriptName} in ${mut_stages.length} stages...\n`);
|
|
@@ -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,14 +209,14 @@ const topologicalSortPackages = (
|
|
|
198
209
|
packages: readonly Package[],
|
|
199
210
|
dependencyGraph: ReadonlyMap<string, readonly string[]>,
|
|
200
211
|
): readonly Package[] => {
|
|
201
|
-
const
|
|
212
|
+
const mut_visited = new Set<string>();
|
|
202
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) {
|
|
@@ -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 ({
|
|
@@ -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 ({
|