workspace-utils 1.0.0 → 1.0.2

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.
Files changed (49) hide show
  1. package/dist/index.js +15464 -0
  2. package/dist/package.json +57 -0
  3. package/package.json +4 -1
  4. package/.github/workflows/mdbook.yml +0 -64
  5. package/.prettierignore +0 -22
  6. package/.prettierrc +0 -13
  7. package/docs/book.toml +0 -10
  8. package/docs/src/SUMMARY.md +0 -24
  9. package/docs/src/commands/build.md +0 -110
  10. package/docs/src/commands/dev.md +0 -118
  11. package/docs/src/commands/overview.md +0 -239
  12. package/docs/src/commands/run.md +0 -153
  13. package/docs/src/configuration.md +0 -249
  14. package/docs/src/examples.md +0 -567
  15. package/docs/src/installation.md +0 -148
  16. package/docs/src/introduction.md +0 -117
  17. package/docs/src/quick-start.md +0 -278
  18. package/docs/src/troubleshooting.md +0 -533
  19. package/index.ts +0 -84
  20. package/src/commands/build.ts +0 -158
  21. package/src/commands/dev.ts +0 -192
  22. package/src/commands/run.test.ts +0 -329
  23. package/src/commands/run.ts +0 -118
  24. package/src/core/dependency-graph.ts +0 -262
  25. package/src/core/process-runner.ts +0 -355
  26. package/src/core/workspace.test.ts +0 -404
  27. package/src/core/workspace.ts +0 -228
  28. package/src/package-managers/bun.test.ts +0 -209
  29. package/src/package-managers/bun.ts +0 -79
  30. package/src/package-managers/detector.test.ts +0 -199
  31. package/src/package-managers/detector.ts +0 -111
  32. package/src/package-managers/index.ts +0 -10
  33. package/src/package-managers/npm.ts +0 -79
  34. package/src/package-managers/pnpm.ts +0 -101
  35. package/src/package-managers/types.ts +0 -42
  36. package/src/utils/output.ts +0 -301
  37. package/src/utils/package-utils.ts +0 -243
  38. package/tests/bun-workspace/apps/web-app/package.json +0 -18
  39. package/tests/bun-workspace/bun.lockb +0 -0
  40. package/tests/bun-workspace/package.json +0 -18
  41. package/tests/bun-workspace/packages/shared-utils/package.json +0 -15
  42. package/tests/bun-workspace/packages/ui-components/package.json +0 -17
  43. package/tests/npm-workspace/package-lock.json +0 -0
  44. package/tests/npm-workspace/package.json +0 -18
  45. package/tests/npm-workspace/packages/core/package.json +0 -15
  46. package/tests/pnpm-workspace/package.json +0 -14
  47. package/tests/pnpm-workspace/packages/utils/package.json +0 -15
  48. package/tests/pnpm-workspace/pnpm-workspace.yaml +0 -3
  49. package/tsconfig.json +0 -29
@@ -1,118 +0,0 @@
1
- import pc from 'picocolors';
2
- import { WorkspaceParser } from '../core/workspace.ts';
3
- import { validatePackagesHaveScript, prepareCommandExecution } from '../utils/package-utils.ts';
4
- import { ProcessRunner } from '../core/process-runner.ts';
5
- import { Output } from '../utils/output.ts';
6
-
7
- interface RunCommandOptions {
8
- parallel?: boolean;
9
- concurrency?: string;
10
- filter?: string;
11
- sequential?: boolean;
12
- }
13
-
14
- export async function runCommand(scriptName: string, options: RunCommandOptions): Promise<void> {
15
- try {
16
- Output.info(`Running script "${scriptName}" across packages...\n`);
17
-
18
- // Parse workspace
19
- const parser = new WorkspaceParser();
20
- const workspace = await parser.parseWorkspace();
21
-
22
- Output.dim(`Workspace root: ${workspace.root}`, 'folder');
23
- Output.dim(`Found ${workspace.packages.length} packages\n`, 'package');
24
-
25
- // Filter packages if pattern provided
26
- let targetPackages = workspace.packages;
27
- if (options.filter) {
28
- targetPackages = parser.filterPackages(workspace.packages, options.filter);
29
- Output.log(
30
- `Filtered to ${targetPackages.length} packages matching "${options.filter}"`,
31
- 'magnifying',
32
- 'yellow'
33
- );
34
- }
35
-
36
- // Validate packages have the script
37
- const { valid: packagesWithScript, invalid: packagesWithoutScript } =
38
- validatePackagesHaveScript(targetPackages, scriptName);
39
-
40
- if (packagesWithoutScript.length > 0) {
41
- Output.warning(`The following packages don't have the "${scriptName}" script:`);
42
- packagesWithoutScript.forEach(pkg => {
43
- Output.listItem(pkg.name);
44
- });
45
- console.log();
46
- }
47
-
48
- if (packagesWithScript.length === 0) {
49
- Output.error(`No packages found with the "${scriptName}" script.`);
50
- process.exit(1);
51
- }
52
-
53
- Output.success(`Running "${scriptName}" in ${packagesWithScript.length} packages:`);
54
- packagesWithScript.forEach(pkg => {
55
- Output.listItem(pkg.name);
56
- });
57
- console.log();
58
-
59
- // Determine execution mode (parallel by default unless explicitly sequential)
60
- const isParallel = !options.sequential;
61
- const concurrency = parseInt(options.concurrency || '4', 10);
62
-
63
- Output.log(`Package manager: ${workspace.packageManager.name}`, 'wrench', 'blue');
64
- Output.log(
65
- `Execution mode: ${isParallel ? `parallel (concurrency: ${concurrency})` : 'sequential'}`,
66
- 'lightning',
67
- 'blue'
68
- );
69
- console.log();
70
-
71
- // Prepare command execution
72
- const commands = prepareCommandExecution(
73
- packagesWithScript,
74
- scriptName,
75
- workspace.packageManager
76
- );
77
-
78
- // Execute commands
79
- const startTime = Date.now();
80
- let results;
81
-
82
- if (isParallel) {
83
- results = await ProcessRunner.runParallel(commands, concurrency);
84
- } else {
85
- results = await ProcessRunner.runSequential(commands);
86
- }
87
-
88
- const totalDuration = Date.now() - startTime;
89
-
90
- // Print summary
91
- const successful = results.filter(r => r.success);
92
- const failed = results.filter(r => !r.success);
93
-
94
- Output.executionSummary(successful.length, failed.length, totalDuration);
95
-
96
- if (failed.length > 0) {
97
- console.log(pc.red('\nFailed packages:'));
98
- failed.forEach(f => {
99
- Output.listItem(`${f.packageName} (exit code ${f.exitCode})`);
100
- });
101
- }
102
-
103
- if (successful.length > 0) {
104
- const avgDuration = Math.round(
105
- successful.reduce((sum, r) => sum + r.duration, 0) / successful.length
106
- );
107
- Output.dim(`Average package duration: ${Output.formatDuration(avgDuration)}`, 'chart');
108
- }
109
-
110
- // Exit with error code if any commands failed
111
- if (failed.length > 0) {
112
- process.exit(1);
113
- }
114
- } catch (error) {
115
- Output.log(`Error: ${error instanceof Error ? error.message : String(error)}`, 'fire', 'red');
116
- process.exit(1);
117
- }
118
- }
@@ -1,262 +0,0 @@
1
- export interface DependencyNode {
2
- name: string;
3
- dependencies: Set<string>;
4
- dependents: Set<string>;
5
- }
6
-
7
- export interface TopologicalResult {
8
- order: string[];
9
- cycles: string[][];
10
- }
11
-
12
- export class DependencyGraph {
13
- private nodes: Map<string, DependencyNode> = new Map();
14
-
15
- /**
16
- * Add a package to the dependency graph
17
- */
18
- addPackage(packageName: string): void {
19
- if (!this.nodes.has(packageName)) {
20
- this.nodes.set(packageName, {
21
- name: packageName,
22
- dependencies: new Set(),
23
- dependents: new Set(),
24
- });
25
- }
26
- }
27
-
28
- /**
29
- * Add a dependency relationship between two packages
30
- */
31
- addDependency(packageName: string, dependencyName: string): void {
32
- this.addPackage(packageName);
33
- this.addPackage(dependencyName);
34
-
35
- const packageNode = this.nodes.get(packageName)!;
36
- const dependencyNode = this.nodes.get(dependencyName)!;
37
-
38
- packageNode.dependencies.add(dependencyName);
39
- dependencyNode.dependents.add(packageName);
40
- }
41
-
42
- /**
43
- * Get all packages in the graph
44
- */
45
- getPackages(): string[] {
46
- return Array.from(this.nodes.keys());
47
- }
48
-
49
- /**
50
- * Get direct dependencies of a package
51
- */
52
- getDependencies(packageName: string): string[] {
53
- const node = this.nodes.get(packageName);
54
- return node ? Array.from(node.dependencies) : [];
55
- }
56
-
57
- /**
58
- * Get direct dependents of a package
59
- */
60
- getDependents(packageName: string): string[] {
61
- const node = this.nodes.get(packageName);
62
- return node ? Array.from(node.dependents) : [];
63
- }
64
-
65
- /**
66
- * Perform topological sort using Kahn's algorithm
67
- * Returns the build order and any detected cycles
68
- */
69
- topologicalSort(): TopologicalResult {
70
- const result: string[] = [];
71
- const inDegree: Map<string, number> = new Map();
72
- const queue: string[] = [];
73
-
74
- // Initialize in-degree count for all nodes
75
- for (const [packageName, node] of this.nodes) {
76
- inDegree.set(packageName, node.dependencies.size);
77
-
78
- // Add nodes with no dependencies to the queue
79
- if (node.dependencies.size === 0) {
80
- queue.push(packageName);
81
- }
82
- }
83
-
84
- // Process nodes with no incoming edges
85
- while (queue.length > 0) {
86
- const currentPackage = queue.shift()!;
87
- result.push(currentPackage);
88
-
89
- // Reduce in-degree for all dependents
90
- const dependents = this.getDependents(currentPackage);
91
- for (const dependent of dependents) {
92
- const currentInDegree = inDegree.get(dependent)! - 1;
93
- inDegree.set(dependent, currentInDegree);
94
-
95
- // If no more dependencies, add to queue
96
- if (currentInDegree === 0) {
97
- queue.push(dependent);
98
- }
99
- }
100
- }
101
-
102
- // Detect cycles
103
- const cycles: string[][] = [];
104
- if (result.length !== this.nodes.size) {
105
- const remainingNodes = this.getPackages().filter(pkg => !result.includes(pkg));
106
- const detectedCycles = this.detectCycles(remainingNodes);
107
- cycles.push(...detectedCycles);
108
- }
109
-
110
- return { order: result, cycles };
111
- }
112
-
113
- /**
114
- * Detect cycles in the remaining nodes using DFS
115
- */
116
- private detectCycles(remainingNodes: string[]): string[][] {
117
- const cycles: string[][] = [];
118
- const visited = new Set<string>();
119
- const recStack = new Set<string>();
120
-
121
- const dfs = (node: string, path: string[]): void => {
122
- if (recStack.has(node)) {
123
- // Found a cycle
124
- const cycleStart = path.indexOf(node);
125
- if (cycleStart !== -1) {
126
- cycles.push(path.slice(cycleStart).concat([node]));
127
- }
128
- return;
129
- }
130
-
131
- if (visited.has(node)) {
132
- return;
133
- }
134
-
135
- visited.add(node);
136
- recStack.add(node);
137
-
138
- const dependencies = this.getDependencies(node);
139
- for (const dep of dependencies) {
140
- if (remainingNodes.includes(dep)) {
141
- dfs(dep, [...path, node]);
142
- }
143
- }
144
-
145
- recStack.delete(node);
146
- };
147
-
148
- for (const node of remainingNodes) {
149
- if (!visited.has(node)) {
150
- dfs(node, []);
151
- }
152
- }
153
-
154
- return cycles;
155
- }
156
-
157
- /**
158
- * Get build batches - packages that can be built in parallel
159
- */
160
- getBuildBatches(): string[][] {
161
- const { order, cycles } = this.topologicalSort();
162
-
163
- if (cycles.length > 0) {
164
- throw new Error(
165
- `Circular dependencies detected: ${cycles.map(cycle => cycle.join(' -> ')).join(', ')}`
166
- );
167
- }
168
-
169
- const batches: string[][] = [];
170
- const processed = new Set<string>();
171
-
172
- while (processed.size < order.length) {
173
- const currentBatch: string[] = [];
174
-
175
- for (const packageName of order) {
176
- if (processed.has(packageName)) {
177
- continue;
178
- }
179
-
180
- // Check if all dependencies are already processed
181
- const dependencies = this.getDependencies(packageName);
182
- const allDepsProcessed = dependencies.every(dep => processed.has(dep));
183
-
184
- if (allDepsProcessed) {
185
- currentBatch.push(packageName);
186
- }
187
- }
188
-
189
- if (currentBatch.length === 0) {
190
- // This shouldn't happen if topological sort worked correctly
191
- throw new Error('Unable to determine build order - possible circular dependency');
192
- }
193
-
194
- batches.push(currentBatch);
195
- currentBatch.forEach(pkg => processed.add(pkg));
196
- }
197
-
198
- return batches;
199
- }
200
-
201
- /**
202
- * Filter the graph to only include specified packages
203
- */
204
- filterGraph(packageNames: string[]): DependencyGraph {
205
- const filteredGraph = new DependencyGraph();
206
- const packageSet = new Set(packageNames);
207
-
208
- // Add all specified packages
209
- for (const packageName of packageNames) {
210
- if (this.nodes.has(packageName)) {
211
- filteredGraph.addPackage(packageName);
212
- }
213
- }
214
-
215
- // Add dependencies between filtered packages
216
- for (const packageName of packageNames) {
217
- if (this.nodes.has(packageName)) {
218
- const dependencies = this.getDependencies(packageName);
219
- for (const dep of dependencies) {
220
- if (packageSet.has(dep)) {
221
- filteredGraph.addDependency(packageName, dep);
222
- }
223
- }
224
- }
225
- }
226
-
227
- return filteredGraph;
228
- }
229
-
230
- /**
231
- * Get packages that have no dependencies (root packages)
232
- */
233
- getRootPackages(): string[] {
234
- return this.getPackages().filter(pkg => this.getDependencies(pkg).length === 0);
235
- }
236
-
237
- /**
238
- * Get packages that have no dependents (leaf packages)
239
- */
240
- getLeafPackages(): string[] {
241
- return this.getPackages().filter(pkg => this.getDependents(pkg).length === 0);
242
- }
243
-
244
- /**
245
- * Print the dependency graph for debugging
246
- */
247
- printGraph(): void {
248
- console.log('\nšŸ“Š Dependency Graph:');
249
- for (const [packageName, node] of this.nodes) {
250
- const deps = Array.from(node.dependencies);
251
- const dependents = Array.from(node.dependents);
252
-
253
- console.log(`\nšŸ“¦ ${packageName}`);
254
- if (deps.length > 0) {
255
- console.log(` ā¬‡ļø Dependencies: ${deps.join(', ')}`);
256
- }
257
- if (dependents.length > 0) {
258
- console.log(` ā¬†ļø Dependents: ${dependents.join(', ')}`);
259
- }
260
- }
261
- }
262
- }