workspace-utils 1.0.0 → 1.0.1

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 +15460 -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,404 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
2
- import { mkdirSync, writeFileSync, rmSync, existsSync } from 'fs';
3
- import { join } from 'path';
4
- import { WorkspaceParser } from './workspace.ts';
5
-
6
- describe('WorkspaceParser', () => {
7
- const testDir = join(process.cwd(), 'test-temp-workspace');
8
-
9
- beforeEach(() => {
10
- // Clean up test directory if it exists
11
- if (existsSync(testDir)) {
12
- rmSync(testDir, { recursive: true, force: true });
13
- }
14
- mkdirSync(testDir, { recursive: true });
15
- });
16
-
17
- afterEach(() => {
18
- // Clean up test directory
19
- if (existsSync(testDir)) {
20
- rmSync(testDir, { recursive: true, force: true });
21
- }
22
- });
23
-
24
- describe('parseWorkspace', () => {
25
- it('should parse Bun workspace with package.json workspaces', async () => {
26
- // Create workspace root
27
- writeFileSync(join(testDir, 'bun.lockb'), '');
28
- writeFileSync(
29
- join(testDir, 'package.json'),
30
- JSON.stringify({
31
- name: 'test-workspace',
32
- workspaces: ['packages/*'],
33
- })
34
- );
35
-
36
- // Create a package
37
- mkdirSync(join(testDir, 'packages', 'test-pkg'), { recursive: true });
38
- writeFileSync(
39
- join(testDir, 'packages', 'test-pkg', 'package.json'),
40
- JSON.stringify({
41
- name: '@test/pkg',
42
- version: '1.0.0',
43
- scripts: {
44
- build: 'echo building',
45
- test: 'echo testing',
46
- },
47
- dependencies: {},
48
- })
49
- );
50
-
51
- const parser = new WorkspaceParser(testDir);
52
- const workspace = await parser.parseWorkspace();
53
-
54
- expect(workspace.root).toBe(testDir);
55
- expect(workspace.packages).toHaveLength(1);
56
- expect(workspace.packages[0]?.name).toBe('@test/pkg');
57
- expect(workspace.packages[0]?.scripts).toEqual({
58
- build: 'echo building',
59
- test: 'echo testing',
60
- });
61
- expect(workspace.packageManager.name).toBe('bun');
62
- });
63
-
64
- it('should parse pnpm workspace with pnpm-workspace.yaml', async () => {
65
- // Create workspace root
66
- writeFileSync(join(testDir, 'pnpm-lock.yaml'), 'lockfileVersion: 6.0');
67
- writeFileSync(join(testDir, 'pnpm-workspace.yaml'), 'packages:\n - packages/*\n - apps/*');
68
-
69
- // Create packages
70
- mkdirSync(join(testDir, 'packages', 'utils'), { recursive: true });
71
- writeFileSync(
72
- join(testDir, 'packages', 'utils', 'package.json'),
73
- JSON.stringify({
74
- name: '@test/utils',
75
- version: '1.0.0',
76
- scripts: {
77
- build: 'tsc',
78
- },
79
- })
80
- );
81
-
82
- mkdirSync(join(testDir, 'apps', 'web'), { recursive: true });
83
- writeFileSync(
84
- join(testDir, 'apps', 'web', 'package.json'),
85
- JSON.stringify({
86
- name: '@test/web',
87
- version: '1.0.0',
88
- dependencies: {
89
- '@test/utils': 'workspace:*',
90
- },
91
- scripts: {
92
- dev: 'vite',
93
- build: 'vite build',
94
- },
95
- })
96
- );
97
-
98
- const parser = new WorkspaceParser(testDir);
99
- const workspace = await parser.parseWorkspace();
100
-
101
- expect(workspace.root).toBe(testDir);
102
- expect(workspace.packages).toHaveLength(2);
103
- expect(workspace.packageManager.name).toBe('pnpm');
104
-
105
- const utilsPkg = workspace.packages.find(p => p.name === '@test/utils');
106
- const webPkg = workspace.packages.find(p => p.name === '@test/web');
107
-
108
- expect(utilsPkg).toBeDefined();
109
- expect(webPkg).toBeDefined();
110
- expect(webPkg?.dependencies).toContain('@test/utils');
111
- });
112
-
113
- it('should parse npm workspace with package.json workspaces', async () => {
114
- // Create workspace root
115
- writeFileSync(join(testDir, 'package-lock.json'), '{}');
116
- writeFileSync(
117
- join(testDir, 'package.json'),
118
- JSON.stringify({
119
- name: 'test-workspace',
120
- workspaces: ['libs/*'],
121
- })
122
- );
123
-
124
- // Create a package
125
- mkdirSync(join(testDir, 'libs', 'shared'), { recursive: true });
126
- writeFileSync(
127
- join(testDir, 'libs', 'shared', 'package.json'),
128
- JSON.stringify({
129
- name: 'shared',
130
- version: '1.0.0',
131
- scripts: {
132
- test: 'jest',
133
- },
134
- })
135
- );
136
-
137
- const parser = new WorkspaceParser(testDir);
138
- const workspace = await parser.parseWorkspace();
139
-
140
- expect(workspace.root).toBe(testDir);
141
- expect(workspace.packages).toHaveLength(1);
142
- expect(workspace.packages[0]?.name).toBe('shared');
143
- expect(workspace.packageManager.name).toBe('npm');
144
- });
145
-
146
- it('should handle packages with dependencies and devDependencies', async () => {
147
- writeFileSync(join(testDir, 'bun.lockb'), '');
148
- writeFileSync(
149
- join(testDir, 'package.json'),
150
- JSON.stringify({
151
- name: 'test-workspace',
152
- workspaces: ['packages/*'],
153
- })
154
- );
155
-
156
- mkdirSync(join(testDir, 'packages', 'core'), { recursive: true });
157
- writeFileSync(
158
- join(testDir, 'packages', 'core', 'package.json'),
159
- JSON.stringify({
160
- name: '@test/core',
161
- version: '1.0.0',
162
- dependencies: {
163
- lodash: '^4.0.0',
164
- '@test/utils': 'workspace:*',
165
- },
166
- devDependencies: {
167
- typescript: '^5.0.0',
168
- '@types/lodash': '^4.0.0',
169
- },
170
- scripts: {
171
- build: 'tsc',
172
- test: 'vitest',
173
- },
174
- })
175
- );
176
-
177
- mkdirSync(join(testDir, 'packages', 'utils'), { recursive: true });
178
- writeFileSync(
179
- join(testDir, 'packages', 'utils', 'package.json'),
180
- JSON.stringify({
181
- name: '@test/utils',
182
- version: '1.0.0',
183
- scripts: {
184
- build: 'tsc',
185
- },
186
- })
187
- );
188
-
189
- const parser = new WorkspaceParser(testDir);
190
- const workspace = await parser.parseWorkspace();
191
-
192
- const corePkg = workspace.packages.find(p => p.name === '@test/core');
193
- expect(corePkg).toBeDefined();
194
- expect(corePkg!.dependencies).toContain('@test/utils');
195
- expect(corePkg!.dependencies).toContain('lodash');
196
- expect(corePkg!.devDependencies).toContain('typescript');
197
- expect(corePkg!.devDependencies).toContain('@types/lodash');
198
- });
199
- });
200
-
201
- describe('findWorkspaceRoot', () => {
202
- it('should find workspace root when running from subdirectory', async () => {
203
- // Create nested directory structure
204
- writeFileSync(join(testDir, 'bun.lockb'), '');
205
- writeFileSync(
206
- join(testDir, 'package.json'),
207
- JSON.stringify({
208
- name: 'test-workspace',
209
- workspaces: ['packages/*'],
210
- })
211
- );
212
-
213
- mkdirSync(join(testDir, 'packages', 'deep', 'nested'), { recursive: true });
214
- writeFileSync(
215
- join(testDir, 'packages', 'deep', 'package.json'),
216
- JSON.stringify({
217
- name: '@test/deep',
218
- version: '1.0.0',
219
- })
220
- );
221
-
222
- // Start parser from nested directory
223
- const nestedDir = join(testDir, 'packages', 'deep', 'nested');
224
- const parser = new WorkspaceParser(nestedDir);
225
- const workspace = await parser.parseWorkspace();
226
-
227
- expect(workspace.root).toBe(testDir);
228
- });
229
-
230
- it('should find pnpm workspace root from subdirectory', async () => {
231
- writeFileSync(join(testDir, 'pnpm-workspace.yaml'), 'packages:\n - packages/*');
232
- mkdirSync(join(testDir, 'packages', 'sub'), { recursive: true });
233
-
234
- const parser = new WorkspaceParser(join(testDir, 'packages', 'sub'));
235
- const workspace = await parser.parseWorkspace();
236
-
237
- expect(workspace.root).toBe(testDir);
238
- });
239
- });
240
-
241
- describe('filterPackages', () => {
242
- let parser: WorkspaceParser;
243
- let workspace: any;
244
-
245
- beforeEach(async () => {
246
- writeFileSync(join(testDir, 'bun.lockb'), '');
247
- writeFileSync(
248
- join(testDir, 'package.json'),
249
- JSON.stringify({
250
- name: 'test-workspace',
251
- workspaces: ['packages/*', 'apps/*'],
252
- })
253
- );
254
-
255
- // Create multiple packages
256
- const packages = [
257
- { dir: 'packages/utils', name: '@scope/utils' },
258
- { dir: 'packages/core', name: '@scope/core' },
259
- { dir: 'apps/web', name: '@scope/web-app' },
260
- { dir: 'apps/mobile', name: '@scope/mobile-app' },
261
- ];
262
-
263
- for (const pkg of packages) {
264
- mkdirSync(join(testDir, pkg.dir), { recursive: true });
265
- writeFileSync(
266
- join(testDir, pkg.dir, 'package.json'),
267
- JSON.stringify({
268
- name: pkg.name,
269
- version: '1.0.0',
270
- scripts: { build: 'echo building' },
271
- })
272
- );
273
- }
274
-
275
- parser = new WorkspaceParser(testDir);
276
- workspace = await parser.parseWorkspace();
277
- });
278
-
279
- it('should filter packages by scope pattern', () => {
280
- const filtered = parser.filterPackages(workspace.packages, '@scope/*');
281
- expect(filtered).toHaveLength(4);
282
- });
283
-
284
- it('should filter packages by specific pattern', () => {
285
- const filtered = parser.filterPackages(workspace.packages, '*utils*');
286
- expect(filtered).toHaveLength(1);
287
- expect(filtered[0]?.name).toBe('@scope/utils');
288
- });
289
-
290
- it('should filter packages by app pattern', () => {
291
- const filtered = parser.filterPackages(workspace.packages, '*app*');
292
- expect(filtered).toHaveLength(2);
293
- });
294
-
295
- it('should return all packages when no pattern provided', () => {
296
- const filtered = parser.filterPackages(workspace.packages);
297
- expect(filtered).toHaveLength(4);
298
- });
299
-
300
- it('should return empty array for non-matching pattern', () => {
301
- const filtered = parser.filterPackages(workspace.packages, 'nonexistent');
302
- expect(filtered).toHaveLength(0);
303
- });
304
- });
305
-
306
- describe('getPackagesWithScript', () => {
307
- let parser: WorkspaceParser;
308
- let workspace: any;
309
-
310
- beforeEach(async () => {
311
- writeFileSync(join(testDir, 'bun.lockb'), '');
312
- writeFileSync(
313
- join(testDir, 'package.json'),
314
- JSON.stringify({
315
- name: 'test-workspace',
316
- workspaces: ['packages/*'],
317
- })
318
- );
319
-
320
- // Create packages with different scripts
321
- mkdirSync(join(testDir, 'packages', 'with-test'), { recursive: true });
322
- writeFileSync(
323
- join(testDir, 'packages', 'with-test', 'package.json'),
324
- JSON.stringify({
325
- name: 'with-test',
326
- scripts: { test: 'vitest', build: 'tsc' },
327
- })
328
- );
329
-
330
- mkdirSync(join(testDir, 'packages', 'without-test'), { recursive: true });
331
- writeFileSync(
332
- join(testDir, 'packages', 'without-test', 'package.json'),
333
- JSON.stringify({
334
- name: 'without-test',
335
- scripts: { build: 'tsc' },
336
- })
337
- );
338
-
339
- parser = new WorkspaceParser(testDir);
340
- workspace = await parser.parseWorkspace();
341
- });
342
-
343
- it('should return packages with specific script', () => {
344
- const withTest = parser.getPackagesWithScript(workspace.packages, 'test');
345
- expect(withTest).toHaveLength(1);
346
- expect(withTest[0]?.name).toBe('with-test');
347
- });
348
-
349
- it('should return packages with build script', () => {
350
- const withBuild = parser.getPackagesWithScript(workspace.packages, 'build');
351
- expect(withBuild).toHaveLength(2);
352
- });
353
-
354
- it('should return empty array for non-existent script', () => {
355
- const withLint = parser.getPackagesWithScript(workspace.packages, 'lint');
356
- expect(withLint).toHaveLength(0);
357
- });
358
- });
359
-
360
- describe('error handling', () => {
361
- it('should throw error when no workspace configuration found', () => {
362
- expect(() => new WorkspaceParser(testDir)).toThrow(
363
- 'No package manager detected. Please ensure you have a lock file (bun.lockb, pnpm-lock.yaml, or package-lock.json) or workspace configuration in your project.'
364
- );
365
- });
366
-
367
- it('should handle invalid package.json files gracefully', async () => {
368
- writeFileSync(join(testDir, 'bun.lockb'), '');
369
- writeFileSync(
370
- join(testDir, 'package.json'),
371
- JSON.stringify({
372
- name: 'test-workspace',
373
- workspaces: ['packages/*'],
374
- })
375
- );
376
-
377
- mkdirSync(join(testDir, 'packages', 'invalid'), { recursive: true });
378
- writeFileSync(join(testDir, 'packages', 'invalid', 'package.json'), 'invalid json');
379
-
380
- const parser = new WorkspaceParser(testDir);
381
-
382
- await expect(parser.parseWorkspace()).rejects.toThrow();
383
- });
384
-
385
- it('should handle missing package.json in package directory', async () => {
386
- writeFileSync(join(testDir, 'bun.lockb'), '');
387
- writeFileSync(
388
- join(testDir, 'package.json'),
389
- JSON.stringify({
390
- name: 'test-workspace',
391
- workspaces: ['packages/*'],
392
- })
393
- );
394
-
395
- mkdirSync(join(testDir, 'packages', 'no-package-json'), { recursive: true });
396
-
397
- const parser = new WorkspaceParser(testDir);
398
- const workspace = await parser.parseWorkspace();
399
-
400
- // Should skip directories without package.json
401
- expect(workspace.packages).toHaveLength(0);
402
- });
403
- });
404
- });
@@ -1,228 +0,0 @@
1
- import { readFileSync, existsSync } from 'fs';
2
- import { join, resolve, dirname } from 'path';
3
- import fg from 'fast-glob';
4
- import { PackageManagerDetector } from '../package-managers/index.ts';
5
- import type { PackageManager } from '../package-managers/index.ts';
6
-
7
- export interface PackageInfo {
8
- name: string;
9
- path: string;
10
- packageJson: Record<string, unknown>;
11
- dependencies: string[];
12
- devDependencies: string[];
13
- scripts: Record<string, string>;
14
- }
15
-
16
- export interface WorkspaceInfo {
17
- root: string;
18
- packages: PackageInfo[];
19
- packageMap: Map<string, PackageInfo>;
20
- packageManager: PackageManager;
21
- }
22
-
23
- export class WorkspaceParser {
24
- private workspaceRoot: string;
25
- private packageManager: PackageManager;
26
-
27
- constructor(workspaceRoot: string = process.cwd()) {
28
- this.workspaceRoot = this.findWorkspaceRoot(resolve(workspaceRoot));
29
- // Detect package manager
30
- const detection = PackageManagerDetector.detect(this.workspaceRoot);
31
- this.packageManager = detection.packageManager;
32
- }
33
-
34
- /**
35
- * Parse the workspace and discover all packages
36
- */
37
- async parseWorkspace(): Promise<WorkspaceInfo> {
38
- const workspaceConfig = this.readWorkspaceConfig();
39
- const packagePaths = await this.resolvePackagePaths(workspaceConfig.packages || []);
40
- const packages = await Promise.all(packagePaths.map(path => this.loadPackageInfo(path)));
41
-
42
- const packageMap = new Map<string, PackageInfo>();
43
- packages.forEach(pkg => {
44
- packageMap.set(pkg.name, pkg);
45
- });
46
-
47
- return {
48
- root: this.workspaceRoot,
49
- packages,
50
- packageMap,
51
- packageManager: this.packageManager,
52
- };
53
- }
54
-
55
- /**
56
- * Find the workspace root by traversing up directories
57
- */
58
- private findWorkspaceRoot(startDir: string): string {
59
- let currentDir = startDir;
60
- while (currentDir !== dirname(currentDir)) {
61
- const packageJsonPath = join(currentDir, 'package.json');
62
-
63
- if (existsSync(packageJsonPath)) {
64
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as Record<
65
- string,
66
- unknown
67
- >;
68
- if (packageJson.workspaces) {
69
- return currentDir;
70
- }
71
- }
72
-
73
- // Check for package manager specific workspace files
74
- const pnpmWorkspace = join(currentDir, 'pnpm-workspace.yaml');
75
- if (existsSync(pnpmWorkspace)) {
76
- return currentDir;
77
- }
78
-
79
- currentDir = dirname(currentDir);
80
- }
81
-
82
- // If no workspace found, return the original directory
83
- return startDir;
84
- }
85
-
86
- /**
87
- * Read and parse workspace configuration using the detected package manager
88
- */
89
- private readWorkspaceConfig(): { packages: string[] } {
90
- try {
91
- const config = this.packageManager.parseWorkspaceConfig(this.workspaceRoot);
92
- return config;
93
- } catch (error) {
94
- throw new Error(
95
- `Failed to parse workspace configuration with ${this.packageManager.name}: ${
96
- error instanceof Error ? error.message : String(error)
97
- }`
98
- );
99
- }
100
- }
101
-
102
- /**
103
- * Resolve package paths from glob patterns
104
- */
105
- private async resolvePackagePaths(patterns: string[]): Promise<string[]> {
106
- const packagePaths: string[] = [];
107
-
108
- for (const pattern of patterns) {
109
- // Handle negation patterns
110
- if (pattern.startsWith('!')) {
111
- continue; // Skip for now, we'll handle exclusions later
112
- }
113
-
114
- const paths = await fg(pattern, {
115
- cwd: this.workspaceRoot,
116
- onlyDirectories: true,
117
- absolute: false,
118
- });
119
-
120
- for (const path of paths) {
121
- const packageJsonPath = join(this.workspaceRoot, path, 'package.json');
122
- if (existsSync(packageJsonPath)) {
123
- packagePaths.push(resolve(this.workspaceRoot, path));
124
- }
125
- }
126
- }
127
-
128
- // Handle exclusion patterns
129
- const exclusionPatterns = patterns.filter(p => p.startsWith('!'));
130
- if (exclusionPatterns.length > 0) {
131
- const excludedPaths = new Set<string>();
132
-
133
- for (const pattern of exclusionPatterns) {
134
- const cleanPattern = pattern.slice(1); // Remove '!'
135
- const paths = await fg(cleanPattern, {
136
- cwd: this.workspaceRoot,
137
- onlyDirectories: true,
138
- absolute: false,
139
- });
140
-
141
- paths.forEach(path => {
142
- excludedPaths.add(resolve(this.workspaceRoot, path));
143
- });
144
- }
145
-
146
- return packagePaths.filter(path => !excludedPaths.has(path));
147
- }
148
-
149
- return Array.from(new Set(packagePaths)); // Remove duplicates
150
- }
151
-
152
- /**
153
- * Load package.json and extract relevant information
154
- */
155
- private async loadPackageInfo(packagePath: string): Promise<PackageInfo> {
156
- const packageJsonPath = join(packagePath, 'package.json');
157
-
158
- if (!existsSync(packageJsonPath)) {
159
- throw new Error(`package.json not found in ${packagePath}`);
160
- }
161
-
162
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as Record<
163
- string,
164
- unknown
165
- >;
166
-
167
- if (!packageJson.name || typeof packageJson.name !== 'string') {
168
- throw new Error(`Package name not found in ${packageJsonPath}`);
169
- }
170
-
171
- const dependenciesObj = packageJson.dependencies as Record<string, string> | undefined;
172
- const devDependenciesObj = packageJson.devDependencies as Record<string, string> | undefined;
173
- const scriptsObj = packageJson.scripts as Record<string, string> | undefined;
174
-
175
- const dependencies = Object.keys(dependenciesObj || {});
176
- const devDependencies = Object.keys(devDependenciesObj || {});
177
- const scripts = scriptsObj || {};
178
-
179
- return {
180
- name: packageJson.name,
181
- path: packagePath,
182
- packageJson,
183
- dependencies,
184
- devDependencies,
185
- scripts,
186
- };
187
- }
188
-
189
- /**
190
- * Filter packages by pattern (e.g., @scope/*, package-*)
191
- */
192
- filterPackages(packages: PackageInfo[], pattern?: string): PackageInfo[] {
193
- if (!pattern) {
194
- return packages;
195
- }
196
-
197
- // Convert glob pattern to regex
198
- const regexPattern = pattern
199
- .replace(/\*/g, '.*')
200
- .replace(/\?/g, '.')
201
- .replace(/\[([^\]]+)\]/g, '[$1]');
202
-
203
- const regex = new RegExp(`^${regexPattern}$`);
204
-
205
- return packages.filter(pkg => regex.test(pkg.name));
206
- }
207
-
208
- /**
209
- * Check if a package has a specific script
210
- */
211
- hasScript(packageInfo: PackageInfo, scriptName: string): boolean {
212
- return scriptName in packageInfo.scripts;
213
- }
214
-
215
- /**
216
- * Get packages that have a specific script
217
- */
218
- getPackagesWithScript(packages: PackageInfo[], scriptName: string): PackageInfo[] {
219
- return packages.filter(pkg => this.hasScript(pkg, scriptName));
220
- }
221
-
222
- /**
223
- * Get the detected package manager
224
- */
225
- getPackageManager(): PackageManager {
226
- return this.packageManager;
227
- }
228
- }