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.
- package/dist/index.js +15464 -0
- package/dist/package.json +57 -0
- package/package.json +4 -1
- package/.github/workflows/mdbook.yml +0 -64
- package/.prettierignore +0 -22
- package/.prettierrc +0 -13
- package/docs/book.toml +0 -10
- package/docs/src/SUMMARY.md +0 -24
- package/docs/src/commands/build.md +0 -110
- package/docs/src/commands/dev.md +0 -118
- package/docs/src/commands/overview.md +0 -239
- package/docs/src/commands/run.md +0 -153
- package/docs/src/configuration.md +0 -249
- package/docs/src/examples.md +0 -567
- package/docs/src/installation.md +0 -148
- package/docs/src/introduction.md +0 -117
- package/docs/src/quick-start.md +0 -278
- package/docs/src/troubleshooting.md +0 -533
- package/index.ts +0 -84
- package/src/commands/build.ts +0 -158
- package/src/commands/dev.ts +0 -192
- package/src/commands/run.test.ts +0 -329
- package/src/commands/run.ts +0 -118
- package/src/core/dependency-graph.ts +0 -262
- package/src/core/process-runner.ts +0 -355
- package/src/core/workspace.test.ts +0 -404
- package/src/core/workspace.ts +0 -228
- package/src/package-managers/bun.test.ts +0 -209
- package/src/package-managers/bun.ts +0 -79
- package/src/package-managers/detector.test.ts +0 -199
- package/src/package-managers/detector.ts +0 -111
- package/src/package-managers/index.ts +0 -10
- package/src/package-managers/npm.ts +0 -79
- package/src/package-managers/pnpm.ts +0 -101
- package/src/package-managers/types.ts +0 -42
- package/src/utils/output.ts +0 -301
- package/src/utils/package-utils.ts +0 -243
- package/tests/bun-workspace/apps/web-app/package.json +0 -18
- package/tests/bun-workspace/bun.lockb +0 -0
- package/tests/bun-workspace/package.json +0 -18
- package/tests/bun-workspace/packages/shared-utils/package.json +0 -15
- package/tests/bun-workspace/packages/ui-components/package.json +0 -17
- package/tests/npm-workspace/package-lock.json +0 -0
- package/tests/npm-workspace/package.json +0 -18
- package/tests/npm-workspace/packages/core/package.json +0 -15
- package/tests/pnpm-workspace/package.json +0 -14
- package/tests/pnpm-workspace/packages/utils/package.json +0 -15
- package/tests/pnpm-workspace/pnpm-workspace.yaml +0 -3
- package/tsconfig.json +0 -29
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { parse as parseYaml } from 'yaml';
|
|
4
|
-
import type { PackageManager, WorkspaceConfig } from './types.ts';
|
|
5
|
-
|
|
6
|
-
export class PnpmPackageManager implements PackageManager {
|
|
7
|
-
readonly name = 'pnpm';
|
|
8
|
-
|
|
9
|
-
isActive(workspaceRoot: string): boolean {
|
|
10
|
-
// Check for pnpm-lock.yaml file
|
|
11
|
-
const lockFile = join(workspaceRoot, 'pnpm-lock.yaml');
|
|
12
|
-
if (existsSync(lockFile)) {
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Check for pnpm-workspace.yaml
|
|
17
|
-
const workspaceFile = join(workspaceRoot, 'pnpm-workspace.yaml');
|
|
18
|
-
if (existsSync(workspaceFile)) {
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Check for .pnpmfile.cjs or pnpm configuration in package.json
|
|
23
|
-
const pnpmFile = join(workspaceRoot, '.pnpmfile.cjs');
|
|
24
|
-
if (existsSync(pnpmFile)) {
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const packageJsonPath = join(workspaceRoot, 'package.json');
|
|
29
|
-
if (existsSync(packageJsonPath)) {
|
|
30
|
-
try {
|
|
31
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as Record<
|
|
32
|
-
string,
|
|
33
|
-
unknown
|
|
34
|
-
>;
|
|
35
|
-
// Check for pnpm-specific fields
|
|
36
|
-
const publishConfig = packageJson.publishConfig as Record<string, unknown> | undefined;
|
|
37
|
-
if (packageJson.pnpm || publishConfig?.registry) {
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
} catch {
|
|
41
|
-
// Ignore JSON parse errors
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
getRunCommand(scriptName: string): { command: string; args: string[] } {
|
|
49
|
-
return {
|
|
50
|
-
command: 'pnpm',
|
|
51
|
-
args: ['run', scriptName],
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
parseWorkspaceConfig(workspaceRoot: string): WorkspaceConfig {
|
|
56
|
-
// First try pnpm-workspace.yaml
|
|
57
|
-
const workspaceFile = join(workspaceRoot, 'pnpm-workspace.yaml');
|
|
58
|
-
if (existsSync(workspaceFile)) {
|
|
59
|
-
const content = readFileSync(workspaceFile, 'utf8');
|
|
60
|
-
const config = parseYaml(content) as { packages?: string[] };
|
|
61
|
-
|
|
62
|
-
if (!config.packages || !Array.isArray(config.packages)) {
|
|
63
|
-
throw new Error('Invalid pnpm-workspace.yaml: packages must be an array');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return { packages: config.packages };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Fallback to package.json workspaces
|
|
70
|
-
const packageJsonPath = join(workspaceRoot, 'package.json');
|
|
71
|
-
if (!existsSync(packageJsonPath)) {
|
|
72
|
-
throw new Error('No pnpm-workspace.yaml or package.json found in workspace root');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as Record<
|
|
76
|
-
string,
|
|
77
|
-
unknown
|
|
78
|
-
>;
|
|
79
|
-
|
|
80
|
-
if (!packageJson.workspaces) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
'No pnpm-workspace.yaml found and no workspaces configuration in package.json'
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const workspaces = packageJson.workspaces as string[] | { packages: string[] };
|
|
87
|
-
const packages = Array.isArray(workspaces) ? workspaces : workspaces.packages;
|
|
88
|
-
|
|
89
|
-
if (!Array.isArray(packages)) {
|
|
90
|
-
throw new Error(
|
|
91
|
-
'Invalid workspaces configuration: must be an array or object with packages array'
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return { packages };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
getLockFileName(): string {
|
|
99
|
-
return 'pnpm-lock.yaml';
|
|
100
|
-
}
|
|
101
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export interface PackageManagerConfig {
|
|
2
|
-
name: string;
|
|
3
|
-
lockFile: string;
|
|
4
|
-
command: string;
|
|
5
|
-
runArgs: string[];
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface WorkspaceConfig {
|
|
9
|
-
packages: string[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface PackageManager {
|
|
13
|
-
/**
|
|
14
|
-
* Name of the package manager
|
|
15
|
-
*/
|
|
16
|
-
readonly name: string;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Check if this package manager is being used in the current directory
|
|
20
|
-
*/
|
|
21
|
-
isActive(workspaceRoot: string): boolean;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Get the command and arguments to run a script
|
|
25
|
-
*/
|
|
26
|
-
getRunCommand(scriptName: string): { command: string; args: string[] };
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Parse workspace configuration
|
|
30
|
-
*/
|
|
31
|
-
parseWorkspaceConfig(workspaceRoot: string): WorkspaceConfig;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Get the lock file name for this package manager
|
|
35
|
-
*/
|
|
36
|
-
getLockFileName(): string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface PackageManagerDetectionResult {
|
|
40
|
-
packageManager: PackageManager;
|
|
41
|
-
confidence: number;
|
|
42
|
-
}
|
package/src/utils/output.ts
DELETED
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
import pc from 'picocolors';
|
|
2
|
-
|
|
3
|
-
interface OutputSymbols {
|
|
4
|
-
rocket: string;
|
|
5
|
-
folder: string;
|
|
6
|
-
package: string;
|
|
7
|
-
checkmark: string;
|
|
8
|
-
crossmark: string;
|
|
9
|
-
warning: string;
|
|
10
|
-
wrench: string;
|
|
11
|
-
lightning: string;
|
|
12
|
-
clock: string;
|
|
13
|
-
target: string;
|
|
14
|
-
magnifying: string;
|
|
15
|
-
chart: string;
|
|
16
|
-
fire: string;
|
|
17
|
-
trophy: string;
|
|
18
|
-
seedling: string;
|
|
19
|
-
leaf: string;
|
|
20
|
-
books: string;
|
|
21
|
-
gear: string;
|
|
22
|
-
construction: string;
|
|
23
|
-
movie: string;
|
|
24
|
-
lightbulb: string;
|
|
25
|
-
sparkles: string;
|
|
26
|
-
party: string;
|
|
27
|
-
boom: string;
|
|
28
|
-
building: string;
|
|
29
|
-
arrow: string;
|
|
30
|
-
dot: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Always use ASCII for maximum compatibility
|
|
34
|
-
function supportsUnicode(): boolean {
|
|
35
|
-
// Only use Unicode if explicitly requested
|
|
36
|
-
if (process.env.WSU_UNICODE === '1' || process.env.WSU_UNICODE === 'true') {
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Default to ASCII for all environments
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Create symbol sets based on Unicode support
|
|
45
|
-
const UNICODE_SYMBOLS: OutputSymbols = {
|
|
46
|
-
rocket: '🚀',
|
|
47
|
-
folder: '📁',
|
|
48
|
-
package: '📦',
|
|
49
|
-
checkmark: '✅',
|
|
50
|
-
crossmark: '❌',
|
|
51
|
-
warning: '⚠️',
|
|
52
|
-
wrench: '🔧',
|
|
53
|
-
lightning: '🚀',
|
|
54
|
-
clock: '⏱️',
|
|
55
|
-
target: '🎯',
|
|
56
|
-
magnifying: '🔍',
|
|
57
|
-
chart: '📊',
|
|
58
|
-
fire: '💥',
|
|
59
|
-
trophy: '🏆',
|
|
60
|
-
seedling: '🌱',
|
|
61
|
-
leaf: '🍃',
|
|
62
|
-
books: '📚',
|
|
63
|
-
gear: '🔧',
|
|
64
|
-
construction: '🏗️',
|
|
65
|
-
movie: '🎬',
|
|
66
|
-
lightbulb: '💡',
|
|
67
|
-
sparkles: '✨',
|
|
68
|
-
party: '🎉',
|
|
69
|
-
boom: '💥',
|
|
70
|
-
building: '🏢',
|
|
71
|
-
arrow: '🔗',
|
|
72
|
-
dot: '🔸',
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const ASCII_SYMBOLS: OutputSymbols = {
|
|
76
|
-
rocket: '>',
|
|
77
|
-
folder: '[DIR]',
|
|
78
|
-
package: '[PKG]',
|
|
79
|
-
checkmark: '[OK]',
|
|
80
|
-
crossmark: '[ERR]',
|
|
81
|
-
warning: '[WARN]',
|
|
82
|
-
wrench: '[TOOL]',
|
|
83
|
-
lightning: '[FAST]',
|
|
84
|
-
clock: '[TIME]',
|
|
85
|
-
target: '[TARGET]',
|
|
86
|
-
magnifying: '[FIND]',
|
|
87
|
-
chart: '[CHART]',
|
|
88
|
-
fire: '[BOOM]',
|
|
89
|
-
trophy: '[WIN]',
|
|
90
|
-
seedling: '[ROOT]',
|
|
91
|
-
leaf: '[LEAF]',
|
|
92
|
-
books: '[DOCS]',
|
|
93
|
-
gear: '[GEAR]',
|
|
94
|
-
construction: '[BUILD]',
|
|
95
|
-
movie: '[START]',
|
|
96
|
-
lightbulb: '[TIP]',
|
|
97
|
-
sparkles: '[DONE]',
|
|
98
|
-
party: '[SUCCESS]',
|
|
99
|
-
boom: '[ERROR]',
|
|
100
|
-
building: '[CORP]',
|
|
101
|
-
arrow: '->',
|
|
102
|
-
dot: '*',
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// Select appropriate symbol set
|
|
106
|
-
const symbols: OutputSymbols = supportsUnicode() ? UNICODE_SYMBOLS : ASCII_SYMBOLS;
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Output utility class for consistent, terminal-compatible logging
|
|
110
|
-
*/
|
|
111
|
-
export class Output {
|
|
112
|
-
private static readonly symbols = symbols;
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Log a message with appropriate formatting and symbols
|
|
116
|
-
*/
|
|
117
|
-
static log(
|
|
118
|
-
message: string,
|
|
119
|
-
symbol?: keyof OutputSymbols,
|
|
120
|
-
color?: 'blue' | 'green' | 'red' | 'yellow' | 'dim'
|
|
121
|
-
): void {
|
|
122
|
-
const prefix = symbol ? `${this.symbols[symbol]} ` : '';
|
|
123
|
-
const formattedMessage = `${prefix}${message}`;
|
|
124
|
-
|
|
125
|
-
if (color) {
|
|
126
|
-
console.log(pc[color](formattedMessage));
|
|
127
|
-
} else {
|
|
128
|
-
console.log(formattedMessage);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Log an info message (blue with rocket)
|
|
134
|
-
*/
|
|
135
|
-
static info(message: string): void {
|
|
136
|
-
this.log(message, 'rocket', 'blue');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Log a success message (green with checkmark)
|
|
141
|
-
*/
|
|
142
|
-
static success(message: string): void {
|
|
143
|
-
this.log(message, 'checkmark', 'green');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Log an error message (red with crossmark)
|
|
148
|
-
*/
|
|
149
|
-
static error(message: string): void {
|
|
150
|
-
this.log(message, 'crossmark', 'red');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Log a warning message (yellow with warning)
|
|
155
|
-
*/
|
|
156
|
-
static warning(message: string): void {
|
|
157
|
-
this.log(message, 'warning', 'yellow');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Log a dim/muted message
|
|
162
|
-
*/
|
|
163
|
-
static dim(message: string, symbol?: keyof OutputSymbols): void {
|
|
164
|
-
this.log(message, symbol, 'dim');
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Log a build-related message
|
|
169
|
-
*/
|
|
170
|
-
static build(message: string): void {
|
|
171
|
-
this.log(message, 'construction', 'blue');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Log a development-related message
|
|
176
|
-
*/
|
|
177
|
-
static dev(message: string): void {
|
|
178
|
-
this.log(message, 'movie', 'blue');
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Log a package-related message
|
|
183
|
-
*/
|
|
184
|
-
static package(message: string): void {
|
|
185
|
-
this.log(message, 'package', 'blue');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Log a timing message
|
|
190
|
-
*/
|
|
191
|
-
static timing(message: string): void {
|
|
192
|
-
this.log(message, 'clock', 'blue');
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Log a target/summary message
|
|
197
|
-
*/
|
|
198
|
-
static summary(message: string): void {
|
|
199
|
-
this.log(message, 'target', 'blue');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Log a celebration message
|
|
204
|
-
*/
|
|
205
|
-
static celebrate(message: string): void {
|
|
206
|
-
this.log(message, 'party', 'green');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Log a tip message
|
|
211
|
-
*/
|
|
212
|
-
static tip(message: string): void {
|
|
213
|
-
this.log(message, 'lightbulb', 'yellow');
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Create a formatted list item
|
|
218
|
-
*/
|
|
219
|
-
static listItem(item: string, indent: number = 2): void {
|
|
220
|
-
const spaces = ' '.repeat(indent);
|
|
221
|
-
this.log(`${spaces}${item}`, 'dot', 'dim');
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Create a separator line
|
|
226
|
-
*/
|
|
227
|
-
static separator(): void {
|
|
228
|
-
const line = supportsUnicode()
|
|
229
|
-
? '────────────────────────────────────────────────────────'
|
|
230
|
-
: '--------------------------------------------------------';
|
|
231
|
-
console.log(pc.dim(line));
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Get a symbol without logging
|
|
236
|
-
*/
|
|
237
|
-
static getSymbol(symbol: keyof OutputSymbols): string {
|
|
238
|
-
return this.symbols[symbol];
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Format a package name with brackets
|
|
243
|
-
*/
|
|
244
|
-
static formatPackageName(name: string, color?: string): string {
|
|
245
|
-
const formatted = `[${name}]`;
|
|
246
|
-
return color ? pc[color as keyof typeof pc](formatted) : formatted;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Format duration in a human-readable way
|
|
251
|
-
*/
|
|
252
|
-
static formatDuration(ms: number): string {
|
|
253
|
-
if (ms < 1000) {
|
|
254
|
-
return `${ms}ms`;
|
|
255
|
-
} else if (ms < 60000) {
|
|
256
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
257
|
-
} else {
|
|
258
|
-
const minutes = Math.floor(ms / 60000);
|
|
259
|
-
const seconds = ((ms % 60000) / 1000).toFixed(0);
|
|
260
|
-
return `${minutes}m ${seconds}s`;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Log execution summary with consistent formatting
|
|
266
|
-
*/
|
|
267
|
-
static executionSummary(successful: number, failed: number, totalDuration: number): void {
|
|
268
|
-
console.log(pc.bold(`\n${this.symbols.chart} Execution Summary:`));
|
|
269
|
-
this.success(`Successful: ${successful}`);
|
|
270
|
-
|
|
271
|
-
if (failed > 0) {
|
|
272
|
-
this.error(`Failed: ${failed}`);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
this.timing(`Total duration: ${this.formatDuration(totalDuration)}`);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Log build summary with consistent formatting
|
|
280
|
-
*/
|
|
281
|
-
static buildSummary(successful: number, failed: number, totalDuration: number): void {
|
|
282
|
-
console.log(pc.bold(`\n${this.symbols.target} Build Summary:`));
|
|
283
|
-
this.success(`Successfully built: ${successful} packages`);
|
|
284
|
-
|
|
285
|
-
if (failed > 0) {
|
|
286
|
-
this.error(`Failed to build: ${failed} packages`);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
this.timing(`Total build time: ${this.formatDuration(totalDuration)}`);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Check if Unicode is supported in current environment
|
|
294
|
-
*/
|
|
295
|
-
static get supportsUnicode(): boolean {
|
|
296
|
-
return supportsUnicode();
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Re-export symbols for direct access if needed
|
|
301
|
-
export { symbols };
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import type { PackageInfo, WorkspaceInfo } from '../core/workspace.ts';
|
|
2
|
-
import { DependencyGraph } from '../core/dependency-graph.ts';
|
|
3
|
-
import type { ProcessResult } from '../core/process-runner.ts';
|
|
4
|
-
import type { PackageManager } from '../package-managers/index.ts';
|
|
5
|
-
|
|
6
|
-
export interface BuildContext {
|
|
7
|
-
workspace: WorkspaceInfo;
|
|
8
|
-
packages: PackageInfo[];
|
|
9
|
-
dependencyGraph: DependencyGraph;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Build a dependency graph from workspace packages
|
|
14
|
-
*/
|
|
15
|
-
export function buildDependencyGraph(packages: PackageInfo[]): DependencyGraph {
|
|
16
|
-
const graph = new DependencyGraph();
|
|
17
|
-
const packageNames = new Set(packages.map(pkg => pkg.name));
|
|
18
|
-
|
|
19
|
-
// Add all packages to the graph
|
|
20
|
-
packages.forEach(pkg => {
|
|
21
|
-
graph.addPackage(pkg.name);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// Add dependency relationships
|
|
25
|
-
packages.forEach(pkg => {
|
|
26
|
-
// Check both dependencies and devDependencies
|
|
27
|
-
const allDeps = [...pkg.dependencies, ...pkg.devDependencies];
|
|
28
|
-
|
|
29
|
-
allDeps.forEach(depName => {
|
|
30
|
-
// Only add dependency if it's also a workspace package
|
|
31
|
-
if (packageNames.has(depName)) {
|
|
32
|
-
graph.addDependency(pkg.name, depName);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
return graph;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Filter packages by script availability
|
|
42
|
-
*/
|
|
43
|
-
export function filterPackagesByScript(packages: PackageInfo[], scriptName: string): PackageInfo[] {
|
|
44
|
-
return packages.filter(pkg => pkg.scripts[scriptName]);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Build command arguments for running a script with the specified package manager
|
|
49
|
-
*/
|
|
50
|
-
export function buildScriptCommand(
|
|
51
|
-
scriptName: string,
|
|
52
|
-
packageManager: PackageManager
|
|
53
|
-
): { command: string; args: string[] } {
|
|
54
|
-
return packageManager.getRunCommand(scriptName);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Validate that packages have the required script
|
|
59
|
-
*/
|
|
60
|
-
export function validatePackagesHaveScript(
|
|
61
|
-
packages: PackageInfo[],
|
|
62
|
-
scriptName: string
|
|
63
|
-
): {
|
|
64
|
-
valid: PackageInfo[];
|
|
65
|
-
invalid: PackageInfo[];
|
|
66
|
-
} {
|
|
67
|
-
const valid: PackageInfo[] = [];
|
|
68
|
-
const invalid: PackageInfo[] = [];
|
|
69
|
-
|
|
70
|
-
packages.forEach(pkg => {
|
|
71
|
-
if (pkg.scripts && typeof pkg.scripts === 'object' && pkg.scripts[scriptName]) {
|
|
72
|
-
valid.push(pkg);
|
|
73
|
-
} else {
|
|
74
|
-
invalid.push(pkg);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
return { valid, invalid };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Prepare command execution data for process runner
|
|
83
|
-
*/
|
|
84
|
-
export function prepareCommandExecution(
|
|
85
|
-
packages: PackageInfo[],
|
|
86
|
-
scriptName: string,
|
|
87
|
-
packageManager: PackageManager
|
|
88
|
-
) {
|
|
89
|
-
const { command, args } = buildScriptCommand(scriptName, packageManager);
|
|
90
|
-
|
|
91
|
-
return packages.map(pkg => {
|
|
92
|
-
if (!isValidPackagePath(pkg.path)) {
|
|
93
|
-
throw new Error(`Invalid package path: ${pkg.path}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
command,
|
|
98
|
-
args,
|
|
99
|
-
options: {
|
|
100
|
-
cwd: pkg.path,
|
|
101
|
-
env: {
|
|
102
|
-
// Ensure consistent environment
|
|
103
|
-
FORCE_COLOR: '1',
|
|
104
|
-
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
logOptions: {
|
|
108
|
-
prefix: pkg.name,
|
|
109
|
-
color: getPackageColor(pkg.name),
|
|
110
|
-
},
|
|
111
|
-
packageInfo: pkg,
|
|
112
|
-
};
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Simple color assignment for packages
|
|
118
|
-
*/
|
|
119
|
-
const colorPalette = [
|
|
120
|
-
'red',
|
|
121
|
-
'green',
|
|
122
|
-
'yellow',
|
|
123
|
-
'blue',
|
|
124
|
-
'magenta',
|
|
125
|
-
'cyan',
|
|
126
|
-
'gray',
|
|
127
|
-
'redBright',
|
|
128
|
-
'greenBright',
|
|
129
|
-
'yellowBright',
|
|
130
|
-
'blueBright',
|
|
131
|
-
'magentaBright',
|
|
132
|
-
'cyanBright',
|
|
133
|
-
];
|
|
134
|
-
|
|
135
|
-
const packageColors = new Map<string, string>();
|
|
136
|
-
let colorIndex = 0;
|
|
137
|
-
|
|
138
|
-
function getPackageColor(packageName: string): string {
|
|
139
|
-
if (!packageColors.has(packageName)) {
|
|
140
|
-
const color = colorPalette[colorIndex % colorPalette.length];
|
|
141
|
-
if (color) {
|
|
142
|
-
packageColors.set(packageName, color);
|
|
143
|
-
}
|
|
144
|
-
colorIndex++;
|
|
145
|
-
}
|
|
146
|
-
return packageColors.get(packageName) || 'white';
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Format package names for display
|
|
151
|
-
*/
|
|
152
|
-
export function formatPackageName(packageName: string, maxLength = 20): string {
|
|
153
|
-
if (packageName.length <= maxLength) {
|
|
154
|
-
return packageName.padEnd(maxLength);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Truncate and add ellipsis
|
|
158
|
-
return packageName.substring(0, maxLength - 3) + '...';
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Group packages by their scope (for @scope/package naming)
|
|
163
|
-
*/
|
|
164
|
-
export function groupPackagesByScope(packages: PackageInfo[]): Map<string, PackageInfo[]> {
|
|
165
|
-
const groups = new Map<string, PackageInfo[]>();
|
|
166
|
-
|
|
167
|
-
packages.forEach(pkg => {
|
|
168
|
-
const scope = pkg.name.startsWith('@') ? pkg.name.split('/')[0] || 'unscoped' : 'unscoped';
|
|
169
|
-
|
|
170
|
-
if (!groups.has(scope)) {
|
|
171
|
-
groups.set(scope, []);
|
|
172
|
-
}
|
|
173
|
-
const group = groups.get(scope);
|
|
174
|
-
if (group) {
|
|
175
|
-
group.push(pkg);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
return groups;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Calculate execution statistics
|
|
184
|
-
*/
|
|
185
|
-
export interface ExecutionStats {
|
|
186
|
-
totalPackages: number;
|
|
187
|
-
successfulPackages: number;
|
|
188
|
-
failedPackages: number;
|
|
189
|
-
totalDuration: number;
|
|
190
|
-
averageDuration: number;
|
|
191
|
-
longestDuration: number;
|
|
192
|
-
shortestDuration: number;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function calculateExecutionStats(results: ProcessResult[]): ExecutionStats {
|
|
196
|
-
const successful = results.filter(r => r.success);
|
|
197
|
-
const failed = results.filter(r => !r.success);
|
|
198
|
-
const durations = results.map(r => r.duration);
|
|
199
|
-
const totalDuration = durations.reduce((sum, d) => sum + d, 0);
|
|
200
|
-
const avgDuration = durations.length > 0 ? Math.round(totalDuration / durations.length) : 0;
|
|
201
|
-
const maxDuration = durations.length > 0 ? Math.max(...durations) : 0;
|
|
202
|
-
const minDuration = durations.length > 0 ? Math.min(...durations) : 0;
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
totalPackages: results.length,
|
|
206
|
-
successfulPackages: successful.length,
|
|
207
|
-
failedPackages: failed.length,
|
|
208
|
-
totalDuration,
|
|
209
|
-
averageDuration: avgDuration,
|
|
210
|
-
longestDuration: maxDuration,
|
|
211
|
-
shortestDuration: minDuration,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Check if a package path exists and is valid
|
|
217
|
-
*/
|
|
218
|
-
export function isValidPackagePath(packagePath: string): boolean {
|
|
219
|
-
const fs = require('fs');
|
|
220
|
-
const path = require('path');
|
|
221
|
-
|
|
222
|
-
try {
|
|
223
|
-
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
224
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Try to parse the package.json to ensure it's valid
|
|
229
|
-
const content = fs.readFileSync(packageJsonPath, 'utf8');
|
|
230
|
-
const pkg = JSON.parse(content);
|
|
231
|
-
return typeof pkg === 'object' && pkg !== null && typeof pkg.name === 'string';
|
|
232
|
-
} catch {
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Extract package name from path (for display purposes)
|
|
239
|
-
*/
|
|
240
|
-
export function extractPackageNameFromPath(packagePath: string): string {
|
|
241
|
-
const parts = packagePath.split('/');
|
|
242
|
-
return parts[parts.length - 1] || '';
|
|
243
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@test/web-app",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Web application",
|
|
5
|
-
"main": "src/index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"build": "echo 'Building web-app with Bun...' && sleep 4 && echo 'web-app build complete!'",
|
|
8
|
-
"dev": "echo 'Starting web-app dev server with Bun...' && while true; do echo '[web-app] Dev server running on port 3000...'; sleep 3; done",
|
|
9
|
-
"test": "echo 'Running web-app tests with Bun...' && sleep 2 && echo 'web-app tests passed!'",
|
|
10
|
-
"lint": "echo 'Linting web-app with Bun...' && sleep 1 && echo 'web-app linting complete!'",
|
|
11
|
-
"start": "echo 'Starting web-app production server with Bun...' && echo 'web-app running on port 3000'"
|
|
12
|
-
},
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"@test/shared-utils": "workspace:*",
|
|
15
|
-
"@test/ui-components": "workspace:*"
|
|
16
|
-
},
|
|
17
|
-
"devDependencies": {}
|
|
18
|
-
}
|
|
File without changes
|