ts-repo-utils 3.0.1 → 3.1.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/index.d.mts +1 -0
- package/dist/functions/index.d.mts.map +1 -1
- package/dist/functions/index.mjs +3 -0
- package/dist/functions/index.mjs.map +1 -1
- package/dist/functions/should-run.d.mts +14 -0
- package/dist/functions/should-run.d.mts.map +1 -1
- package/dist/functions/should-run.mjs +14 -0
- package/dist/functions/should-run.mjs.map +1 -1
- package/dist/functions/workspace-utils/get-workspace-packages.d.mts +39 -0
- package/dist/functions/workspace-utils/get-workspace-packages.d.mts.map +1 -0
- package/dist/functions/workspace-utils/get-workspace-packages.mjs +225 -0
- package/dist/functions/workspace-utils/get-workspace-packages.mjs.map +1 -0
- package/dist/functions/workspace-utils/index.d.mts +4 -0
- package/dist/functions/workspace-utils/index.d.mts.map +1 -0
- package/dist/functions/workspace-utils/index.mjs +4 -0
- package/dist/functions/workspace-utils/index.mjs.map +1 -0
- package/dist/functions/workspace-utils/run-cmd-in-parallel.d.mts +17 -0
- package/dist/functions/workspace-utils/run-cmd-in-parallel.d.mts.map +1 -0
- package/dist/functions/workspace-utils/run-cmd-in-parallel.mjs +29 -0
- package/dist/functions/workspace-utils/run-cmd-in-parallel.mjs.map +1 -0
- package/dist/functions/workspace-utils/run-cmd-in-stages.d.mts +19 -0
- package/dist/functions/workspace-utils/run-cmd-in-stages.d.mts.map +1 -0
- package/dist/functions/workspace-utils/run-cmd-in-stages.mjs +31 -0
- package/dist/functions/workspace-utils/run-cmd-in-stages.mjs.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/functions/index.mts +1 -0
- package/src/functions/should-run.mts +14 -0
- package/src/functions/workspace-utils/get-workspace-packages.mts +357 -0
- package/src/functions/workspace-utils/index.mts +3 -0
- package/src/functions/workspace-utils/run-cmd-in-parallel.mts +45 -0
- package/src/functions/workspace-utils/run-cmd-in-stages.mts +47 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/functions/index.mts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/functions/index.mts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC"}
|
package/dist/functions/index.mjs
CHANGED
|
@@ -6,4 +6,7 @@ export { $ } from './exec-async.mjs';
|
|
|
6
6
|
export { formatDiffFrom, formatFiles, formatFilesList, formatUntracked } from './format.mjs';
|
|
7
7
|
export { genIndex } from './gen-index.mjs';
|
|
8
8
|
export { checkShouldRunTypeChecks } from './should-run.mjs';
|
|
9
|
+
export { executeParallel, executeStages, getWorkspacePackages } from './workspace-utils/get-workspace-packages.mjs';
|
|
10
|
+
export { runCmdInParallelAcrossWorkspaces } from './workspace-utils/run-cmd-in-parallel.mjs';
|
|
11
|
+
export { runCmdInStagesAcrossWorkspaces } from './workspace-utils/run-cmd-in-stages.mjs';
|
|
9
12
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
import '../node-global.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Checks if TypeScript type checks should run based on the diff from origin/main.
|
|
4
|
+
* Skips type checks if all changed files are documentation files, spell check config,
|
|
5
|
+
* or other non-TypeScript files that don't affect type checking.
|
|
6
|
+
*
|
|
7
|
+
* Ignored file patterns:
|
|
8
|
+
* - '.cspell.json'
|
|
9
|
+
* - '**.md'
|
|
10
|
+
* - '**.txt'
|
|
11
|
+
* - 'docs/**'
|
|
12
|
+
*
|
|
13
|
+
* @returns A promise that resolves when the check is complete. Sets GITHUB_OUTPUT
|
|
14
|
+
* environment variable with should_run=true/false if running in GitHub Actions.
|
|
15
|
+
*/
|
|
2
16
|
export declare const checkShouldRunTypeChecks: () => Promise<void>;
|
|
3
17
|
//# sourceMappingURL=should-run.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"should-run.d.mts","sourceRoot":"","sources":["../../src/functions/should-run.mts"],"names":[],"mappings":"AACA,OAAO,oBAAoB,CAAC;AAG5B,eAAO,MAAM,wBAAwB,QAAa,OAAO,CAAC,IAAI,CA4B7D,CAAC"}
|
|
1
|
+
{"version":3,"file":"should-run.d.mts","sourceRoot":"","sources":["../../src/functions/should-run.mts"],"names":[],"mappings":"AACA,OAAO,oBAAoB,CAAC;AAG5B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,QAAa,OAAO,CAAC,IAAI,CA4B7D,CAAC"}
|
|
@@ -2,6 +2,20 @@ import { Result, pipe } from 'ts-data-forge';
|
|
|
2
2
|
import '../node-global.mjs';
|
|
3
3
|
import { getDiffFrom } from './diff.mjs';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Checks if TypeScript type checks should run based on the diff from origin/main.
|
|
7
|
+
* Skips type checks if all changed files are documentation files, spell check config,
|
|
8
|
+
* or other non-TypeScript files that don't affect type checking.
|
|
9
|
+
*
|
|
10
|
+
* Ignored file patterns:
|
|
11
|
+
* - '.cspell.json'
|
|
12
|
+
* - '**.md'
|
|
13
|
+
* - '**.txt'
|
|
14
|
+
* - 'docs/**'
|
|
15
|
+
*
|
|
16
|
+
* @returns A promise that resolves when the check is complete. Sets GITHUB_OUTPUT
|
|
17
|
+
* environment variable with should_run=true/false if running in GitHub Actions.
|
|
18
|
+
*/
|
|
5
19
|
const checkShouldRunTypeChecks = async () => {
|
|
6
20
|
// paths-ignore:
|
|
7
21
|
// - '.cspell.json'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"should-run.mjs","sources":["../../src/functions/should-run.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"should-run.mjs","sources":["../../src/functions/should-run.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;AAIA;;;;;;;;;;;;;AAaG;AACI,MAAM,wBAAwB,GAAG,YAA0B;;;;;;IAOhE,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AAElD,IAAA,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,aAAa,CAAC;AAE9C,IAAA,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;QACvB,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,KAAK,CAAC,KAAK,CAAC;AACjD,QAAA,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;;AAGjB,IAAA,MAAM,iBAAiB,GAAY,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CACnD,CAAC,IAAI,KACH,IAAI,KAAK,cAAc;AACvB,QAAA,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;AACxB,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAC3B,CAAC,QAAQ,KAAK,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CACpE,CAAC,KAAK,CACV;AAED,IAAA,IAAI,aAAa,KAAK,SAAS,EAAE;QAC/B,MAAM,EAAE,CAAC,UAAU,CAAC,aAAa,EAAE,CAAA,WAAA,EAAc,iBAAiB,CAAA,EAAA,CAAI,CAAC;;AAE3E;;;;"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import { Result } from 'ts-data-forge';
|
|
3
|
+
import '../../node-global.mjs';
|
|
4
|
+
type Package = Readonly<{
|
|
5
|
+
name: string;
|
|
6
|
+
path: string;
|
|
7
|
+
packageJson: JsonValue;
|
|
8
|
+
dependencies: ReadonlyRecord<string, string>;
|
|
9
|
+
}>;
|
|
10
|
+
/**
|
|
11
|
+
* Retrieves all workspace packages from a monorepo based on the workspace patterns
|
|
12
|
+
* defined in the root package.json file.
|
|
13
|
+
* @param rootPackageJsonDir - The directory containing the root package.json file
|
|
14
|
+
* @returns A promise that resolves to an array of Package objects containing package metadata
|
|
15
|
+
*/
|
|
16
|
+
export declare const getWorkspacePackages: (rootPackageJsonDir: string) => Promise<readonly Package[]>;
|
|
17
|
+
/**
|
|
18
|
+
* Executes a npm script across multiple packages in parallel with a concurrency limit.
|
|
19
|
+
* @param packages - Array of Package objects to execute the script in
|
|
20
|
+
* @param scriptName - The name of the npm script to execute
|
|
21
|
+
* @param concurrency - Maximum number of packages to process simultaneously (default: 3)
|
|
22
|
+
* @returns A promise that resolves to an array of execution results
|
|
23
|
+
*/
|
|
24
|
+
export declare const executeParallel: (packages: readonly Package[], scriptName: string, concurrency?: number) => Promise<readonly Result<Readonly<{
|
|
25
|
+
code?: number;
|
|
26
|
+
skipped?: boolean;
|
|
27
|
+
}>, Error>[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Executes a npm script across packages in dependency order stages.
|
|
30
|
+
* Packages are grouped into stages where each stage contains packages whose
|
|
31
|
+
* dependencies have been completed in previous stages.
|
|
32
|
+
* @param packages - Array of Package objects to execute the script in
|
|
33
|
+
* @param scriptName - The name of the npm script to execute
|
|
34
|
+
* @param concurrency - Maximum number of packages to process simultaneously within each stage (default: 3)
|
|
35
|
+
* @returns A promise that resolves when all stages are complete
|
|
36
|
+
*/
|
|
37
|
+
export declare const executeStages: (packages: readonly Package[], scriptName: string, concurrency?: number) => Promise<void>;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=get-workspace-packages.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-workspace-packages.d.mts","sourceRoot":"","sources":["../../../src/functions/workspace-utils/get-workspace-packages.mts"],"names":[],"mappings":";AAGA,OAAO,EAOL,MAAM,EACP,MAAM,eAAe,CAAC;AACvB,OAAO,uBAAuB,CAAC;AAK/B,KAAK,OAAO,GAAG,QAAQ,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,SAAS,CAAC;IACvB,YAAY,EAAE,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C,CAAC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAC/B,oBAAoB,MAAM,KACzB,OAAO,CAAC,SAAS,OAAO,EAAE,CAyD5B,CAAC;AAuKF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,UAAU,SAAS,OAAO,EAAE,EAC5B,YAAY,MAAM,EAClB,cAAa,MAAU,KACtB,OAAO,CACR,SAAS,MAAM,CAAC,QAAQ,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAkCzE,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GACxB,UAAU,SAAS,OAAO,EAAE,EAC5B,YAAY,MAAM,EAClB,cAAa,MAAU,KACtB,OAAO,CAAC,IAAI,CAuCd,CAAC"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { Result, isNotUndefined, pipe, isRecord, hasKey, isString, createPromise } from 'ts-data-forge';
|
|
4
|
+
import '../../node-global.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Retrieves all workspace packages from a monorepo based on the workspace patterns
|
|
8
|
+
* defined in the root package.json file.
|
|
9
|
+
* @param rootPackageJsonDir - The directory containing the root package.json file
|
|
10
|
+
* @returns A promise that resolves to an array of Package objects containing package metadata
|
|
11
|
+
*/
|
|
12
|
+
const getWorkspacePackages = async (rootPackageJsonDir) => {
|
|
13
|
+
// Read root package.json
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
15
|
+
const rootPackageJson = JSON.parse(await fs.readFile(path.join(rootPackageJsonDir, 'package.json'), 'utf8'));
|
|
16
|
+
const workspacePatterns = getStrArrayFromJsonValue(rootPackageJson, 'workspaces');
|
|
17
|
+
const packagePromises = workspacePatterns.map(async (pattern) => {
|
|
18
|
+
const matches = await glob(pattern, {
|
|
19
|
+
cwd: rootPackageJsonDir,
|
|
20
|
+
ignore: ['**/node_modules/**'],
|
|
21
|
+
});
|
|
22
|
+
const packageJsonList = await Promise.all(matches.map(async (match) => {
|
|
23
|
+
const packagePath = path.join(rootPackageJsonDir, match);
|
|
24
|
+
const result = await Result.fromPromise(fs.readFile(path.join(packagePath, 'package.json'), 'utf8'));
|
|
25
|
+
if (Result.isErr(result))
|
|
26
|
+
return undefined;
|
|
27
|
+
return [packagePath, result.value];
|
|
28
|
+
}));
|
|
29
|
+
const packageInfos = await Promise.all(packageJsonList
|
|
30
|
+
.filter(isNotUndefined)
|
|
31
|
+
.map(([packagePath, packageJson]) => ({
|
|
32
|
+
name: getStrFromJsonValue(packageJson, 'name'),
|
|
33
|
+
path: packagePath,
|
|
34
|
+
packageJson,
|
|
35
|
+
dependencies: {
|
|
36
|
+
...getKeyValueRecordFromJsonValue(packageJson, 'dependencies'),
|
|
37
|
+
...getKeyValueRecordFromJsonValue(packageJson, 'devDependencies'),
|
|
38
|
+
...getKeyValueRecordFromJsonValue(packageJson, 'peerDependencies'),
|
|
39
|
+
},
|
|
40
|
+
})));
|
|
41
|
+
return packageInfos;
|
|
42
|
+
});
|
|
43
|
+
const allPackageArrays = await Promise.all(packagePromises);
|
|
44
|
+
const finalPackages = allPackageArrays.flat();
|
|
45
|
+
return finalPackages;
|
|
46
|
+
};
|
|
47
|
+
const getStrFromJsonValue = (value, key) => isRecord(value) && hasKey(value, key) && isString(value[key])
|
|
48
|
+
? value[key]
|
|
49
|
+
: '';
|
|
50
|
+
const getStrArrayFromJsonValue = (value, key) => isRecord(value) &&
|
|
51
|
+
hasKey(value, key) &&
|
|
52
|
+
Array.isArray(value[key]) &&
|
|
53
|
+
value[key].every(isString)
|
|
54
|
+
? value[key]
|
|
55
|
+
: [];
|
|
56
|
+
const getKeyValueRecordFromJsonValue = (value, key) => {
|
|
57
|
+
if (!isRecord(value) || !hasKey(value, key)) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
const obj = value[key];
|
|
61
|
+
if (!isRecord(obj)) {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
const entries = Object.entries(obj).filter((entry) => isString(entry[1]));
|
|
65
|
+
return Object.fromEntries(entries);
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Builds a dependency graph from the given packages, mapping each package name
|
|
69
|
+
* to its internal workspace dependencies.
|
|
70
|
+
* @param packages - Array of Package objects to analyze
|
|
71
|
+
* @returns A readonly map where keys are package names and values are arrays of their dependency package names
|
|
72
|
+
*/
|
|
73
|
+
const buildDependencyGraph = (packages) => {
|
|
74
|
+
const packageMap = new Map(packages.map((p) => [p.name, p]));
|
|
75
|
+
const graph = new Map();
|
|
76
|
+
for (const pkg of packages) {
|
|
77
|
+
const deps = Object.keys(pkg.dependencies).filter((depName) => packageMap.has(depName));
|
|
78
|
+
graph.set(pkg.name, deps);
|
|
79
|
+
}
|
|
80
|
+
return graph;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Performs a topological sort on packages based on their dependencies,
|
|
84
|
+
* ensuring dependencies are ordered before their dependents.
|
|
85
|
+
* @param packages - Array of Package objects to sort
|
|
86
|
+
* @returns An array of packages in dependency order (dependencies first)
|
|
87
|
+
*/
|
|
88
|
+
const topologicalSortPackages = (packages) => {
|
|
89
|
+
const graph = buildDependencyGraph(packages);
|
|
90
|
+
const visited = new Set();
|
|
91
|
+
const result = [];
|
|
92
|
+
const packageMap = new Map(packages.map((p) => [p.name, p]));
|
|
93
|
+
const visit = (pkgName) => {
|
|
94
|
+
if (visited.has(pkgName))
|
|
95
|
+
return;
|
|
96
|
+
visited.add(pkgName);
|
|
97
|
+
const deps = graph.get(pkgName) ?? [];
|
|
98
|
+
for (const dep of deps) {
|
|
99
|
+
visit(dep);
|
|
100
|
+
}
|
|
101
|
+
result.push(pkgName);
|
|
102
|
+
};
|
|
103
|
+
for (const pkg of packages) {
|
|
104
|
+
visit(pkg.name);
|
|
105
|
+
}
|
|
106
|
+
return result
|
|
107
|
+
.map((pkgName) => packageMap.get(pkgName))
|
|
108
|
+
.filter(isNotUndefined);
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Executes a npm script in a specific package directory.
|
|
112
|
+
* @param pkg - The package object containing path and metadata
|
|
113
|
+
* @param scriptName - The name of the npm script to execute
|
|
114
|
+
* @param options - Configuration options
|
|
115
|
+
* @param options.prefix - Whether to prefix output with package name (default: true)
|
|
116
|
+
* @returns A promise that resolves to execution result with exit code or skipped flag
|
|
117
|
+
*/
|
|
118
|
+
const executeScript = (pkg, scriptName, { prefix = true } = {}) => pipe(createPromise((resolve, reject) => {
|
|
119
|
+
const packageJsonScripts = isRecord(pkg.packageJson) && isRecord(pkg.packageJson['scripts'])
|
|
120
|
+
? pkg.packageJson['scripts']
|
|
121
|
+
: {};
|
|
122
|
+
const hasScript = hasKey(packageJsonScripts, scriptName);
|
|
123
|
+
if (!hasScript) {
|
|
124
|
+
resolve({ skipped: true });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const prefixStr = prefix ? `[${pkg.name}] ` : '';
|
|
128
|
+
const proc = spawn('npm', ['run', scriptName], {
|
|
129
|
+
cwd: pkg.path,
|
|
130
|
+
shell: true,
|
|
131
|
+
stdio: 'pipe',
|
|
132
|
+
});
|
|
133
|
+
proc.stdout.on('data', (data) => {
|
|
134
|
+
process.stdout.write(prefixStr + data.toString());
|
|
135
|
+
});
|
|
136
|
+
proc.stderr.on('data', (data) => {
|
|
137
|
+
process.stderr.write(prefixStr + data.toString());
|
|
138
|
+
});
|
|
139
|
+
proc.on('close', (code) => {
|
|
140
|
+
if (code === 0) {
|
|
141
|
+
resolve({ code });
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
reject(new Error(`${pkg.name} exited with code ${code}`));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
proc.on('error', reject);
|
|
148
|
+
})).map((result) => result.then(Result.mapErr((err) => {
|
|
149
|
+
const errorMessage = err instanceof Error
|
|
150
|
+
? err.message
|
|
151
|
+
: isRecord(err) && hasKey(err, 'message')
|
|
152
|
+
? (err.message?.toString() ?? 'Unknown error message')
|
|
153
|
+
: 'Unknown error';
|
|
154
|
+
console.error(`\nError in ${pkg.name}:`, errorMessage);
|
|
155
|
+
return err instanceof Error ? err : new Error(errorMessage);
|
|
156
|
+
}))).value;
|
|
157
|
+
/**
|
|
158
|
+
* Executes a npm script across multiple packages in parallel with a concurrency limit.
|
|
159
|
+
* @param packages - Array of Package objects to execute the script in
|
|
160
|
+
* @param scriptName - The name of the npm script to execute
|
|
161
|
+
* @param concurrency - Maximum number of packages to process simultaneously (default: 3)
|
|
162
|
+
* @returns A promise that resolves to an array of execution results
|
|
163
|
+
*/
|
|
164
|
+
const executeParallel = async (packages, scriptName, concurrency = 3) => {
|
|
165
|
+
const mut_resultPromises = [];
|
|
166
|
+
const executing = new Set();
|
|
167
|
+
for (const pkg of packages) {
|
|
168
|
+
const promise = executeScript(pkg, scriptName);
|
|
169
|
+
mut_resultPromises.push(promise);
|
|
170
|
+
const wrappedPromise = promise.finally(() => {
|
|
171
|
+
executing.delete(wrappedPromise);
|
|
172
|
+
});
|
|
173
|
+
executing.add(wrappedPromise);
|
|
174
|
+
// If we reach concurrency limit, wait for one to finish
|
|
175
|
+
if (executing.size >= concurrency) {
|
|
176
|
+
// eslint-disable-next-line no-await-in-loop
|
|
177
|
+
await Promise.race(executing);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return Promise.all(mut_resultPromises);
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Executes a npm script across packages in dependency order stages.
|
|
184
|
+
* Packages are grouped into stages where each stage contains packages whose
|
|
185
|
+
* dependencies have been completed in previous stages.
|
|
186
|
+
* @param packages - Array of Package objects to execute the script in
|
|
187
|
+
* @param scriptName - The name of the npm script to execute
|
|
188
|
+
* @param concurrency - Maximum number of packages to process simultaneously within each stage (default: 3)
|
|
189
|
+
* @returns A promise that resolves when all stages are complete
|
|
190
|
+
*/
|
|
191
|
+
const executeStages = async (packages, scriptName, concurrency = 3) => {
|
|
192
|
+
const sorted = topologicalSortPackages(packages);
|
|
193
|
+
const stages = [];
|
|
194
|
+
const completed = new Set();
|
|
195
|
+
const dependencyGraph = buildDependencyGraph(packages);
|
|
196
|
+
while (completed.size < sorted.length) {
|
|
197
|
+
const stage = [];
|
|
198
|
+
for (const pkg of sorted) {
|
|
199
|
+
if (completed.has(pkg.name))
|
|
200
|
+
continue;
|
|
201
|
+
const deps = dependencyGraph.get(pkg.name) ?? [];
|
|
202
|
+
const depsCompleted = deps.every((dep) => completed.has(dep));
|
|
203
|
+
if (depsCompleted) {
|
|
204
|
+
stage.push(pkg);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (stage.length === 0) {
|
|
208
|
+
throw new Error('Circular dependency detected');
|
|
209
|
+
}
|
|
210
|
+
stages.push(stage);
|
|
211
|
+
for (const pkg of stage)
|
|
212
|
+
completed.add(pkg.name);
|
|
213
|
+
}
|
|
214
|
+
console.log(`\nExecuting ${scriptName} in ${stages.length} stages...\n`);
|
|
215
|
+
for (const [i, stage] of stages.entries()) {
|
|
216
|
+
if (stage.length > 0) {
|
|
217
|
+
console.log(`Stage ${i + 1}: ${stage.map((p) => p.name).join(', ')}`);
|
|
218
|
+
// eslint-disable-next-line no-await-in-loop
|
|
219
|
+
await executeParallel(stage, scriptName, concurrency);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export { executeParallel, executeStages, getWorkspacePackages };
|
|
225
|
+
//# sourceMappingURL=get-workspace-packages.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-workspace-packages.mjs","sources":["../../../src/functions/workspace-utils/get-workspace-packages.mts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/functions/workspace-utils/index.mts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { executeParallel, executeStages, getWorkspacePackages } from './get-workspace-packages.mjs';
|
|
2
|
+
export { runCmdInParallelAcrossWorkspaces } from './run-cmd-in-parallel.mjs';
|
|
3
|
+
export { runCmdInStagesAcrossWorkspaces } from './run-cmd-in-stages.mjs';
|
|
4
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Executes a npm script command across all workspace packages in parallel.
|
|
4
|
+
* @param options - Configuration options for the parallel execution
|
|
5
|
+
* @param options.rootPackageJsonDir - The directory containing the root package.json file
|
|
6
|
+
* @param options.cmd - The npm script command to execute in each package
|
|
7
|
+
* @param options.concurrency - Maximum number of packages to process simultaneously (default: 3)
|
|
8
|
+
* @param options.filterWorkspacePattern - Optional function to filter packages by name
|
|
9
|
+
* @returns A promise that resolves when all packages have completed execution
|
|
10
|
+
*/
|
|
11
|
+
export declare const runCmdInParallelAcrossWorkspaces: ({ rootPackageJsonDir, cmd, concurrency, filterWorkspacePattern, }: Readonly<{
|
|
12
|
+
rootPackageJsonDir: string;
|
|
13
|
+
cmd: string;
|
|
14
|
+
concurrency?: number;
|
|
15
|
+
filterWorkspacePattern?: (packageName: string) => boolean;
|
|
16
|
+
}>) => Promise<void>;
|
|
17
|
+
//# sourceMappingURL=run-cmd-in-parallel.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-cmd-in-parallel.d.mts","sourceRoot":"","sources":["../../../src/functions/workspace-utils/run-cmd-in-parallel.mts"],"names":[],"mappings":";AAOA;;;;;;;;GAQG;AACH,eAAO,MAAM,gCAAgC,GAAU,mEAKpD,QAAQ,CAAC;IACV,kBAAkB,EAAE,MAAM,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sBAAsB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3D,CAAC,KAAG,OAAO,CAAC,IAAI,CAkBhB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import { getWorkspacePackages, executeParallel } from './get-workspace-packages.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Executes a npm script command across all workspace packages in parallel.
|
|
6
|
+
* @param options - Configuration options for the parallel execution
|
|
7
|
+
* @param options.rootPackageJsonDir - The directory containing the root package.json file
|
|
8
|
+
* @param options.cmd - The npm script command to execute in each package
|
|
9
|
+
* @param options.concurrency - Maximum number of packages to process simultaneously (default: 3)
|
|
10
|
+
* @param options.filterWorkspacePattern - Optional function to filter packages by name
|
|
11
|
+
* @returns A promise that resolves when all packages have completed execution
|
|
12
|
+
*/
|
|
13
|
+
const runCmdInParallelAcrossWorkspaces = async ({ rootPackageJsonDir, cmd, concurrency = 3, filterWorkspacePattern, }) => {
|
|
14
|
+
try {
|
|
15
|
+
const packages = await getWorkspacePackages(rootPackageJsonDir);
|
|
16
|
+
const filteredPackages = filterWorkspacePattern === undefined
|
|
17
|
+
? packages
|
|
18
|
+
: packages.filter((pkg) => filterWorkspacePattern(pkg.name));
|
|
19
|
+
await executeParallel(filteredPackages, cmd, concurrency);
|
|
20
|
+
console.log(`\n✅ ${cmd} completed successfully`);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
console.error(`\n❌ ${cmd} failed:`, err instanceof Error ? err.message : (err?.toString() ?? ''));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { runCmdInParallelAcrossWorkspaces };
|
|
29
|
+
//# sourceMappingURL=run-cmd-in-parallel.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-cmd-in-parallel.mjs","sources":["../../../src/functions/workspace-utils/run-cmd-in-parallel.mts"],"sourcesContent":[null],"names":[],"mappings":";;;AAOA;;;;;;;;AAQG;AACI,MAAM,gCAAgC,GAAG,OAAO,EACrD,kBAAkB,EAClB,GAAG,EACH,WAAW,GAAG,CAAC,EACf,sBAAsB,GAMtB,KAAmB;AACnB,IAAA,IAAI;AACF,QAAA,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,kBAAkB,CAAC;AAE/D,QAAA,MAAM,gBAAgB,GACpB,sBAAsB,KAAK;AACzB,cAAE;AACF,cAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAEhE,MAAM,eAAe,CAAC,gBAAgB,EAAE,GAAG,EAAE,WAAW,CAAC;AACzD,QAAA,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,CAAA,uBAAA,CAAyB,CAAC;;IAChD,OAAO,GAAG,EAAE;AACZ,QAAA,OAAO,CAAC,KAAK,CACX,CAAA,IAAA,EAAO,GAAG,CAAA,QAAA,CAAU,EACpB,GAAG,YAAY,KAAK,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAC7D;AACD,QAAA,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;;AAEnB;;;;"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Executes a npm script command across all workspace packages in dependency order stages.
|
|
4
|
+
* Packages are grouped into stages where each stage contains packages whose
|
|
5
|
+
* dependencies have been completed in previous stages.
|
|
6
|
+
* @param options - Configuration options for the staged execution
|
|
7
|
+
* @param options.rootPackageJsonDir - The directory containing the root package.json file
|
|
8
|
+
* @param options.cmd - The npm script command to execute in each package
|
|
9
|
+
* @param options.concurrency - Maximum number of packages to process simultaneously within each stage (default: 3)
|
|
10
|
+
* @param options.filterWorkspacePattern - Optional function to filter packages by name
|
|
11
|
+
* @returns A promise that resolves when all stages have completed execution
|
|
12
|
+
*/
|
|
13
|
+
export declare const runCmdInStagesAcrossWorkspaces: ({ rootPackageJsonDir, cmd, concurrency, filterWorkspacePattern, }: Readonly<{
|
|
14
|
+
rootPackageJsonDir: string;
|
|
15
|
+
cmd: string;
|
|
16
|
+
concurrency?: number;
|
|
17
|
+
filterWorkspacePattern?: (packageName: string) => boolean;
|
|
18
|
+
}>) => Promise<void>;
|
|
19
|
+
//# sourceMappingURL=run-cmd-in-stages.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-cmd-in-stages.d.mts","sourceRoot":"","sources":["../../../src/functions/workspace-utils/run-cmd-in-stages.mts"],"names":[],"mappings":";AAOA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAAU,mEAKlD,QAAQ,CAAC;IACV,kBAAkB,EAAE,MAAM,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sBAAsB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3D,CAAC,KAAG,OAAO,CAAC,IAAI,CAkBhB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import { getWorkspacePackages, executeStages } from './get-workspace-packages.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Executes a npm script command across all workspace packages in dependency order stages.
|
|
6
|
+
* Packages are grouped into stages where each stage contains packages whose
|
|
7
|
+
* dependencies have been completed in previous stages.
|
|
8
|
+
* @param options - Configuration options for the staged execution
|
|
9
|
+
* @param options.rootPackageJsonDir - The directory containing the root package.json file
|
|
10
|
+
* @param options.cmd - The npm script command to execute in each package
|
|
11
|
+
* @param options.concurrency - Maximum number of packages to process simultaneously within each stage (default: 3)
|
|
12
|
+
* @param options.filterWorkspacePattern - Optional function to filter packages by name
|
|
13
|
+
* @returns A promise that resolves when all stages have completed execution
|
|
14
|
+
*/
|
|
15
|
+
const runCmdInStagesAcrossWorkspaces = async ({ rootPackageJsonDir, cmd, concurrency = 3, filterWorkspacePattern, }) => {
|
|
16
|
+
try {
|
|
17
|
+
const packages = await getWorkspacePackages(rootPackageJsonDir);
|
|
18
|
+
const filteredPackages = filterWorkspacePattern === undefined
|
|
19
|
+
? packages
|
|
20
|
+
: packages.filter((pkg) => filterWorkspacePattern(pkg.name));
|
|
21
|
+
await executeStages(filteredPackages, cmd, concurrency);
|
|
22
|
+
console.log(`\n✅ ${cmd} completed successfully`);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.error(`\n❌ ${cmd} failed:`, err instanceof Error ? err.message : (err?.toString() ?? ''));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { runCmdInStagesAcrossWorkspaces };
|
|
31
|
+
//# sourceMappingURL=run-cmd-in-stages.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-cmd-in-stages.mjs","sources":["../../../src/functions/workspace-utils/run-cmd-in-stages.mts"],"sourcesContent":[null],"names":[],"mappings":";;;AAOA;;;;;;;;;;AAUG;AACI,MAAM,8BAA8B,GAAG,OAAO,EACnD,kBAAkB,EAClB,GAAG,EACH,WAAW,GAAG,CAAC,EACf,sBAAsB,GAMtB,KAAmB;AACnB,IAAA,IAAI;AACF,QAAA,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,kBAAkB,CAAC;AAE/D,QAAA,MAAM,gBAAgB,GACpB,sBAAsB,KAAK;AACzB,cAAE;AACF,cAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAEhE,MAAM,aAAa,CAAC,gBAAgB,EAAE,GAAG,EAAE,WAAW,CAAC;AACvD,QAAA,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,CAAA,uBAAA,CAAyB,CAAC;;IAChD,OAAO,GAAG,EAAE;AACZ,QAAA,OAAO,CAAC,KAAK,CACX,CAAA,IAAA,EAAO,GAAG,CAAA,QAAA,CAAU,EACpB,GAAG,YAAY,KAAK,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAC7D;AACD,QAAA,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;;AAEnB;;;;"}
|
package/dist/index.mjs
CHANGED
|
@@ -6,4 +6,7 @@ export { $ } from './functions/exec-async.mjs';
|
|
|
6
6
|
export { formatDiffFrom, formatFiles, formatFilesList, formatUntracked } from './functions/format.mjs';
|
|
7
7
|
export { genIndex } from './functions/gen-index.mjs';
|
|
8
8
|
export { checkShouldRunTypeChecks } from './functions/should-run.mjs';
|
|
9
|
+
export { executeParallel, executeStages, getWorkspacePackages } from './functions/workspace-utils/get-workspace-packages.mjs';
|
|
10
|
+
export { runCmdInParallelAcrossWorkspaces } from './functions/workspace-utils/run-cmd-in-parallel.mjs';
|
|
11
|
+
export { runCmdInStagesAcrossWorkspaces } from './functions/workspace-utils/run-cmd-in-stages.mjs';
|
|
9
12
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-repo-utils",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript"
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"fast-glob": "^3.3.3",
|
|
59
59
|
"micromatch": "^4.0.8",
|
|
60
60
|
"prettier": "^3.6.2",
|
|
61
|
-
"ts-data-forge": "^
|
|
61
|
+
"ts-data-forge": "^2.0.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@eslint/js": "^9.30.0",
|
|
@@ -72,21 +72,21 @@
|
|
|
72
72
|
"@semantic-release/github": "^11.0.3",
|
|
73
73
|
"@semantic-release/npm": "^12.0.1",
|
|
74
74
|
"@semantic-release/release-notes-generator": "^14.0.3",
|
|
75
|
-
"@types/node": "^20.19.
|
|
75
|
+
"@types/node": "^20.19.4",
|
|
76
76
|
"@vitest/coverage-v8": "^3.2.4",
|
|
77
77
|
"@vitest/ui": "^3.2.4",
|
|
78
78
|
"conventional-changelog-conventionalcommits": "^9.0.0",
|
|
79
79
|
"cspell": "^9.1.2",
|
|
80
80
|
"dedent": "^1.6.0",
|
|
81
|
-
"eslint": "^9.
|
|
81
|
+
"eslint": "^9.30.1",
|
|
82
82
|
"markdownlint-cli2": "^0.18.1",
|
|
83
83
|
"prettier-plugin-organize-imports": "^4.1.0",
|
|
84
|
-
"prettier-plugin-packagejson": "^2.5.
|
|
84
|
+
"prettier-plugin-packagejson": "^2.5.18",
|
|
85
85
|
"rollup": "^4.44.1",
|
|
86
|
-
"semantic-release": "^24.2.
|
|
86
|
+
"semantic-release": "^24.2.6",
|
|
87
87
|
"ts-type-forge": "^2.0.3",
|
|
88
88
|
"tsx": "^4.20.3",
|
|
89
|
-
"typedoc": "^0.28.
|
|
89
|
+
"typedoc": "^0.28.7",
|
|
90
90
|
"typedoc-plugin-markdown": "^4.7.0",
|
|
91
91
|
"typescript": "^5.8.3",
|
|
92
92
|
"typescript-eslint": "^8.35.0",
|
package/src/functions/index.mts
CHANGED
|
@@ -2,6 +2,20 @@ import { pipe, Result } from 'ts-data-forge';
|
|
|
2
2
|
import '../node-global.mjs';
|
|
3
3
|
import { getDiffFrom } from './diff.mjs';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Checks if TypeScript type checks should run based on the diff from origin/main.
|
|
7
|
+
* Skips type checks if all changed files are documentation files, spell check config,
|
|
8
|
+
* or other non-TypeScript files that don't affect type checking.
|
|
9
|
+
*
|
|
10
|
+
* Ignored file patterns:
|
|
11
|
+
* - '.cspell.json'
|
|
12
|
+
* - '**.md'
|
|
13
|
+
* - '**.txt'
|
|
14
|
+
* - 'docs/**'
|
|
15
|
+
*
|
|
16
|
+
* @returns A promise that resolves when the check is complete. Sets GITHUB_OUTPUT
|
|
17
|
+
* environment variable with should_run=true/false if running in GitHub Actions.
|
|
18
|
+
*/
|
|
5
19
|
export const checkShouldRunTypeChecks = async (): Promise<void> => {
|
|
6
20
|
// paths-ignore:
|
|
7
21
|
// - '.cspell.json'
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import {
|
|
5
|
+
createPromise,
|
|
6
|
+
hasKey,
|
|
7
|
+
isNotUndefined,
|
|
8
|
+
isRecord,
|
|
9
|
+
isString,
|
|
10
|
+
pipe,
|
|
11
|
+
Result,
|
|
12
|
+
} from 'ts-data-forge';
|
|
13
|
+
import '../../node-global.mjs';
|
|
14
|
+
|
|
15
|
+
const DEBUG = false as boolean;
|
|
16
|
+
|
|
17
|
+
// Get all workspace packages
|
|
18
|
+
type Package = Readonly<{
|
|
19
|
+
name: string;
|
|
20
|
+
path: string;
|
|
21
|
+
packageJson: JsonValue;
|
|
22
|
+
dependencies: ReadonlyRecord<string, string>;
|
|
23
|
+
}>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Retrieves all workspace packages from a monorepo based on the workspace patterns
|
|
27
|
+
* defined in the root package.json file.
|
|
28
|
+
* @param rootPackageJsonDir - The directory containing the root package.json file
|
|
29
|
+
* @returns A promise that resolves to an array of Package objects containing package metadata
|
|
30
|
+
*/
|
|
31
|
+
export const getWorkspacePackages = async (
|
|
32
|
+
rootPackageJsonDir: string,
|
|
33
|
+
): Promise<readonly Package[]> => {
|
|
34
|
+
// Read root package.json
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
36
|
+
const rootPackageJson: JsonValue = JSON.parse(
|
|
37
|
+
await fs.readFile(path.join(rootPackageJsonDir, 'package.json'), 'utf8'),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const workspacePatterns: readonly string[] = getStrArrayFromJsonValue(
|
|
41
|
+
rootPackageJson,
|
|
42
|
+
'workspaces',
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const packagePromises = workspacePatterns.map(async (pattern) => {
|
|
46
|
+
const matches = await glob(pattern, {
|
|
47
|
+
cwd: rootPackageJsonDir,
|
|
48
|
+
ignore: ['**/node_modules/**'],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const packageJsonList: readonly (
|
|
52
|
+
| readonly [string, JsonValue]
|
|
53
|
+
| undefined
|
|
54
|
+
)[] = await Promise.all(
|
|
55
|
+
matches.map(async (match) => {
|
|
56
|
+
const packagePath = path.join(rootPackageJsonDir, match);
|
|
57
|
+
|
|
58
|
+
const result = await Result.fromPromise(
|
|
59
|
+
fs.readFile(path.join(packagePath, 'package.json'), 'utf8'),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (Result.isErr(result)) return undefined;
|
|
63
|
+
|
|
64
|
+
return [packagePath, result.value] as const;
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const packageInfos: readonly Package[] = await Promise.all(
|
|
69
|
+
packageJsonList
|
|
70
|
+
.filter(isNotUndefined)
|
|
71
|
+
.map(([packagePath, packageJson]) => ({
|
|
72
|
+
name: getStrFromJsonValue(packageJson, 'name'),
|
|
73
|
+
path: packagePath,
|
|
74
|
+
packageJson,
|
|
75
|
+
dependencies: {
|
|
76
|
+
...getKeyValueRecordFromJsonValue(packageJson, 'dependencies'),
|
|
77
|
+
...getKeyValueRecordFromJsonValue(packageJson, 'devDependencies'),
|
|
78
|
+
...getKeyValueRecordFromJsonValue(packageJson, 'peerDependencies'),
|
|
79
|
+
},
|
|
80
|
+
})),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return packageInfos;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const allPackageArrays = await Promise.all(packagePromises);
|
|
87
|
+
const finalPackages = allPackageArrays.flat();
|
|
88
|
+
|
|
89
|
+
return finalPackages;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const getStrFromJsonValue = (value: JsonValue, key: string): string =>
|
|
93
|
+
isRecord(value) && hasKey(value, key) && isString(value[key])
|
|
94
|
+
? value[key]
|
|
95
|
+
: '';
|
|
96
|
+
|
|
97
|
+
const getStrArrayFromJsonValue = (
|
|
98
|
+
value: JsonValue,
|
|
99
|
+
key: string,
|
|
100
|
+
): readonly string[] =>
|
|
101
|
+
isRecord(value) &&
|
|
102
|
+
hasKey(value, key) &&
|
|
103
|
+
Array.isArray(value[key]) &&
|
|
104
|
+
value[key].every(isString)
|
|
105
|
+
? value[key]
|
|
106
|
+
: [];
|
|
107
|
+
|
|
108
|
+
const getKeyValueRecordFromJsonValue = (
|
|
109
|
+
value: JsonValue,
|
|
110
|
+
key: string,
|
|
111
|
+
): ReadonlyRecord<string, string> => {
|
|
112
|
+
if (!isRecord(value) || !hasKey(value, key)) {
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
const obj = value[key];
|
|
116
|
+
if (!isRecord(obj)) {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
const entries = Object.entries(obj).filter(
|
|
120
|
+
(entry): entry is [string, string] => isString(entry[1]),
|
|
121
|
+
);
|
|
122
|
+
return Object.fromEntries(entries);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Builds a dependency graph from the given packages, mapping each package name
|
|
127
|
+
* to its internal workspace dependencies.
|
|
128
|
+
* @param packages - Array of Package objects to analyze
|
|
129
|
+
* @returns A readonly map where keys are package names and values are arrays of their dependency package names
|
|
130
|
+
*/
|
|
131
|
+
const buildDependencyGraph = (
|
|
132
|
+
packages: readonly Package[],
|
|
133
|
+
): ReadonlyMap<string, readonly string[]> => {
|
|
134
|
+
const packageMap = new Map(packages.map((p) => [p.name, p]));
|
|
135
|
+
const graph = new Map<string, string[]>();
|
|
136
|
+
|
|
137
|
+
for (const pkg of packages) {
|
|
138
|
+
const deps = Object.keys(pkg.dependencies).filter((depName) =>
|
|
139
|
+
packageMap.has(depName),
|
|
140
|
+
);
|
|
141
|
+
graph.set(pkg.name, deps);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return graph;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Performs a topological sort on packages based on their dependencies,
|
|
149
|
+
* ensuring dependencies are ordered before their dependents.
|
|
150
|
+
* @param packages - Array of Package objects to sort
|
|
151
|
+
* @returns An array of packages in dependency order (dependencies first)
|
|
152
|
+
*/
|
|
153
|
+
const topologicalSortPackages = (
|
|
154
|
+
packages: readonly Package[],
|
|
155
|
+
): readonly Package[] => {
|
|
156
|
+
const graph = buildDependencyGraph(packages);
|
|
157
|
+
const visited = new Set<string>();
|
|
158
|
+
const result: string[] = [];
|
|
159
|
+
|
|
160
|
+
const packageMap = new Map(packages.map((p) => [p.name, p]));
|
|
161
|
+
|
|
162
|
+
const visit = (pkgName: string): void => {
|
|
163
|
+
if (visited.has(pkgName)) return;
|
|
164
|
+
visited.add(pkgName);
|
|
165
|
+
|
|
166
|
+
const deps = graph.get(pkgName) ?? [];
|
|
167
|
+
for (const dep of deps) {
|
|
168
|
+
visit(dep);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
result.push(pkgName);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
for (const pkg of packages) {
|
|
175
|
+
visit(pkg.name);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return result
|
|
179
|
+
.map((pkgName) => packageMap.get(pkgName))
|
|
180
|
+
.filter(isNotUndefined);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Executes a npm script in a specific package directory.
|
|
185
|
+
* @param pkg - The package object containing path and metadata
|
|
186
|
+
* @param scriptName - The name of the npm script to execute
|
|
187
|
+
* @param options - Configuration options
|
|
188
|
+
* @param options.prefix - Whether to prefix output with package name (default: true)
|
|
189
|
+
* @returns A promise that resolves to execution result with exit code or skipped flag
|
|
190
|
+
*/
|
|
191
|
+
const executeScript = (
|
|
192
|
+
pkg: Package,
|
|
193
|
+
scriptName: string,
|
|
194
|
+
{ prefix = true }: Readonly<{ prefix?: boolean }> = {},
|
|
195
|
+
): Promise<Result<Readonly<{ code?: number; skipped?: boolean }>, Error>> =>
|
|
196
|
+
pipe(
|
|
197
|
+
createPromise(
|
|
198
|
+
(
|
|
199
|
+
resolve: (
|
|
200
|
+
value: Readonly<{ code?: number; skipped?: boolean }>,
|
|
201
|
+
) => void,
|
|
202
|
+
reject: (reason: unknown) => void,
|
|
203
|
+
) => {
|
|
204
|
+
const packageJsonScripts =
|
|
205
|
+
isRecord(pkg.packageJson) && isRecord(pkg.packageJson['scripts'])
|
|
206
|
+
? pkg.packageJson['scripts']
|
|
207
|
+
: {};
|
|
208
|
+
|
|
209
|
+
const hasScript = hasKey(packageJsonScripts, scriptName);
|
|
210
|
+
if (!hasScript) {
|
|
211
|
+
resolve({ skipped: true });
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const prefixStr = prefix ? `[${pkg.name}] ` : '';
|
|
216
|
+
const proc = spawn('npm', ['run', scriptName], {
|
|
217
|
+
cwd: pkg.path,
|
|
218
|
+
shell: true,
|
|
219
|
+
stdio: 'pipe',
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
proc.stdout.on('data', (data: Readonly<Buffer>) => {
|
|
223
|
+
process.stdout.write(prefixStr + data.toString());
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
proc.stderr.on('data', (data: Readonly<Buffer>) => {
|
|
227
|
+
process.stderr.write(prefixStr + data.toString());
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
proc.on('close', (code: number | null) => {
|
|
231
|
+
if (code === 0) {
|
|
232
|
+
resolve({ code });
|
|
233
|
+
} else {
|
|
234
|
+
reject(new Error(`${pkg.name} exited with code ${code}`));
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
proc.on('error', reject);
|
|
239
|
+
},
|
|
240
|
+
),
|
|
241
|
+
).map((result) =>
|
|
242
|
+
result.then(
|
|
243
|
+
Result.mapErr((err) => {
|
|
244
|
+
const errorMessage: string =
|
|
245
|
+
err instanceof Error
|
|
246
|
+
? err.message
|
|
247
|
+
: isRecord(err) && hasKey(err, 'message')
|
|
248
|
+
? (err.message?.toString() ?? 'Unknown error message')
|
|
249
|
+
: 'Unknown error';
|
|
250
|
+
|
|
251
|
+
console.error(`\nError in ${pkg.name}:`, errorMessage);
|
|
252
|
+
return err instanceof Error ? err : new Error(errorMessage);
|
|
253
|
+
}),
|
|
254
|
+
),
|
|
255
|
+
).value;
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Executes a npm script across multiple packages in parallel with a concurrency limit.
|
|
259
|
+
* @param packages - Array of Package objects to execute the script in
|
|
260
|
+
* @param scriptName - The name of the npm script to execute
|
|
261
|
+
* @param concurrency - Maximum number of packages to process simultaneously (default: 3)
|
|
262
|
+
* @returns A promise that resolves to an array of execution results
|
|
263
|
+
*/
|
|
264
|
+
export const executeParallel = async (
|
|
265
|
+
packages: readonly Package[],
|
|
266
|
+
scriptName: string,
|
|
267
|
+
concurrency: number = 3,
|
|
268
|
+
): Promise<
|
|
269
|
+
readonly Result<Readonly<{ code?: number; skipped?: boolean }>, Error>[]
|
|
270
|
+
> => {
|
|
271
|
+
const mut_resultPromises: Promise<
|
|
272
|
+
Result<Readonly<{ code?: number; skipped?: boolean }>, Error>
|
|
273
|
+
>[] = [];
|
|
274
|
+
|
|
275
|
+
const executing = new Set<Promise<unknown>>();
|
|
276
|
+
|
|
277
|
+
for (const pkg of packages) {
|
|
278
|
+
const promise = executeScript(pkg, scriptName);
|
|
279
|
+
|
|
280
|
+
mut_resultPromises.push(promise);
|
|
281
|
+
|
|
282
|
+
const wrappedPromise = promise.finally(() => {
|
|
283
|
+
executing.delete(wrappedPromise);
|
|
284
|
+
if (DEBUG) {
|
|
285
|
+
console.debug('executing size', executing.size);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
executing.add(wrappedPromise);
|
|
290
|
+
|
|
291
|
+
if (DEBUG) {
|
|
292
|
+
console.debug('executing size', executing.size);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// If we reach concurrency limit, wait for one to finish
|
|
296
|
+
if (executing.size >= concurrency) {
|
|
297
|
+
// eslint-disable-next-line no-await-in-loop
|
|
298
|
+
await Promise.race(executing);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return Promise.all(mut_resultPromises);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Executes a npm script across packages in dependency order stages.
|
|
307
|
+
* Packages are grouped into stages where each stage contains packages whose
|
|
308
|
+
* dependencies have been completed in previous stages.
|
|
309
|
+
* @param packages - Array of Package objects to execute the script in
|
|
310
|
+
* @param scriptName - The name of the npm script to execute
|
|
311
|
+
* @param concurrency - Maximum number of packages to process simultaneously within each stage (default: 3)
|
|
312
|
+
* @returns A promise that resolves when all stages are complete
|
|
313
|
+
*/
|
|
314
|
+
export const executeStages = async (
|
|
315
|
+
packages: readonly Package[],
|
|
316
|
+
scriptName: string,
|
|
317
|
+
concurrency: number = 3,
|
|
318
|
+
): Promise<void> => {
|
|
319
|
+
const sorted = topologicalSortPackages(packages);
|
|
320
|
+
|
|
321
|
+
const stages: (readonly Package[])[] = [];
|
|
322
|
+
const completed = new Set<string>();
|
|
323
|
+
|
|
324
|
+
const dependencyGraph = buildDependencyGraph(packages);
|
|
325
|
+
|
|
326
|
+
while (completed.size < sorted.length) {
|
|
327
|
+
const stage: Package[] = [];
|
|
328
|
+
|
|
329
|
+
for (const pkg of sorted) {
|
|
330
|
+
if (completed.has(pkg.name)) continue;
|
|
331
|
+
|
|
332
|
+
const deps = dependencyGraph.get(pkg.name) ?? [];
|
|
333
|
+
const depsCompleted = deps.every((dep) => completed.has(dep));
|
|
334
|
+
|
|
335
|
+
if (depsCompleted) {
|
|
336
|
+
stage.push(pkg);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (stage.length === 0) {
|
|
341
|
+
throw new Error('Circular dependency detected');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
stages.push(stage);
|
|
345
|
+
for (const pkg of stage) completed.add(pkg.name);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
console.log(`\nExecuting ${scriptName} in ${stages.length} stages...\n`);
|
|
349
|
+
|
|
350
|
+
for (const [i, stage] of stages.entries()) {
|
|
351
|
+
if (stage.length > 0) {
|
|
352
|
+
console.log(`Stage ${i + 1}: ${stage.map((p) => p.name).join(', ')}`);
|
|
353
|
+
// eslint-disable-next-line no-await-in-loop
|
|
354
|
+
await executeParallel(stage, scriptName, concurrency);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
executeParallel,
|
|
5
|
+
getWorkspacePackages,
|
|
6
|
+
} from './get-workspace-packages.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Executes a npm script command across all workspace packages in parallel.
|
|
10
|
+
* @param options - Configuration options for the parallel execution
|
|
11
|
+
* @param options.rootPackageJsonDir - The directory containing the root package.json file
|
|
12
|
+
* @param options.cmd - The npm script command to execute in each package
|
|
13
|
+
* @param options.concurrency - Maximum number of packages to process simultaneously (default: 3)
|
|
14
|
+
* @param options.filterWorkspacePattern - Optional function to filter packages by name
|
|
15
|
+
* @returns A promise that resolves when all packages have completed execution
|
|
16
|
+
*/
|
|
17
|
+
export const runCmdInParallelAcrossWorkspaces = async ({
|
|
18
|
+
rootPackageJsonDir,
|
|
19
|
+
cmd,
|
|
20
|
+
concurrency = 3,
|
|
21
|
+
filterWorkspacePattern,
|
|
22
|
+
}: Readonly<{
|
|
23
|
+
rootPackageJsonDir: string;
|
|
24
|
+
cmd: string;
|
|
25
|
+
concurrency?: number;
|
|
26
|
+
filterWorkspacePattern?: (packageName: string) => boolean;
|
|
27
|
+
}>): Promise<void> => {
|
|
28
|
+
try {
|
|
29
|
+
const packages = await getWorkspacePackages(rootPackageJsonDir);
|
|
30
|
+
|
|
31
|
+
const filteredPackages =
|
|
32
|
+
filterWorkspacePattern === undefined
|
|
33
|
+
? packages
|
|
34
|
+
: packages.filter((pkg) => filterWorkspacePattern(pkg.name));
|
|
35
|
+
|
|
36
|
+
await executeParallel(filteredPackages, cmd, concurrency);
|
|
37
|
+
console.log(`\n✅ ${cmd} completed successfully`);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error(
|
|
40
|
+
`\n❌ ${cmd} failed:`,
|
|
41
|
+
err instanceof Error ? err.message : (err?.toString() ?? ''),
|
|
42
|
+
);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
executeStages,
|
|
5
|
+
getWorkspacePackages,
|
|
6
|
+
} from './get-workspace-packages.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Executes a npm script command across all workspace packages in dependency order stages.
|
|
10
|
+
* Packages are grouped into stages where each stage contains packages whose
|
|
11
|
+
* dependencies have been completed in previous stages.
|
|
12
|
+
* @param options - Configuration options for the staged execution
|
|
13
|
+
* @param options.rootPackageJsonDir - The directory containing the root package.json file
|
|
14
|
+
* @param options.cmd - The npm script command to execute in each package
|
|
15
|
+
* @param options.concurrency - Maximum number of packages to process simultaneously within each stage (default: 3)
|
|
16
|
+
* @param options.filterWorkspacePattern - Optional function to filter packages by name
|
|
17
|
+
* @returns A promise that resolves when all stages have completed execution
|
|
18
|
+
*/
|
|
19
|
+
export const runCmdInStagesAcrossWorkspaces = async ({
|
|
20
|
+
rootPackageJsonDir,
|
|
21
|
+
cmd,
|
|
22
|
+
concurrency = 3,
|
|
23
|
+
filterWorkspacePattern,
|
|
24
|
+
}: Readonly<{
|
|
25
|
+
rootPackageJsonDir: string;
|
|
26
|
+
cmd: string;
|
|
27
|
+
concurrency?: number;
|
|
28
|
+
filterWorkspacePattern?: (packageName: string) => boolean;
|
|
29
|
+
}>): Promise<void> => {
|
|
30
|
+
try {
|
|
31
|
+
const packages = await getWorkspacePackages(rootPackageJsonDir);
|
|
32
|
+
|
|
33
|
+
const filteredPackages =
|
|
34
|
+
filterWorkspacePattern === undefined
|
|
35
|
+
? packages
|
|
36
|
+
: packages.filter((pkg) => filterWorkspacePattern(pkg.name));
|
|
37
|
+
|
|
38
|
+
await executeStages(filteredPackages, cmd, concurrency);
|
|
39
|
+
console.log(`\n✅ ${cmd} completed successfully`);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(
|
|
42
|
+
`\n❌ ${cmd} failed:`,
|
|
43
|
+
err instanceof Error ? err.message : (err?.toString() ?? ''),
|
|
44
|
+
);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
};
|