Skip to content

Commit 90217f3

Browse files
committed
refactor(@angular/cli): add custom parser for bun pm ls
Bun does not support JSON output for the `pm ls` command. This commit introduces a custom parser to interpret Bun's tree-like output format, allowing the package manager abstraction to correctly discover installed dependencies when using Bun.
1 parent bbd7cf2 commit 90217f3

File tree

3 files changed

+95
-3
lines changed

3 files changed

+95
-3
lines changed

packages/angular/cli/src/package-managers/package-manager-descriptor.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { Logger } from './logger';
1717
import { PackageManifest, PackageMetadata } from './package-metadata';
1818
import { InstalledPackage } from './package-tree';
1919
import {
20+
parseBunDependencies,
2021
parseNpmLikeDependencies,
2122
parseNpmLikeError,
2223
parseNpmLikeManifest,
@@ -241,10 +242,10 @@ export const SUPPORTED_PACKAGE_MANAGERS = {
241242
ignoreScriptsFlag: '--ignore-scripts',
242243
getRegistryOptions: (registry: string) => ({ args: ['--registry', registry] }),
243244
versionCommand: ['--version'],
244-
listDependenciesCommand: ['pm', 'ls', '--json'],
245+
listDependenciesCommand: ['pm', 'ls'],
245246
getManifestCommand: ['pm', 'view', '--json'],
246247
outputParsers: {
247-
listDependencies: parseNpmLikeDependencies,
248+
listDependencies: parseBunDependencies,
248249
getRegistryManifest: parseNpmLikeManifest,
249250
getRegistryMetadata: parseNpmLikeMetadata,
250251
getError: parseNpmLikeError,

packages/angular/cli/src/package-managers/parsers.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,3 +519,57 @@ export function parseYarnClassicError(output: string, logger?: Logger): ErrorInf
519519

520520
return null;
521521
}
522+
523+
/**
524+
* Parses the output of `bun pm ls`.
525+
*
526+
* Bun does not support JSON output for `pm ls`. The output is a tree structure:
527+
* ```
528+
* /path/to/project node_modules (1084)
529+
* ├── @angular/[email protected]
530+
* ├── rxjs @7.8.2
531+
* └── zone.js @0.15.1
532+
* ```
533+
*
534+
* @param stdout The standard output of the command.
535+
* @param logger An optional logger instance.
536+
* @returns A map of package names to their installed package details.
537+
*/
538+
export function parseBunDependencies(
539+
stdout: string,
540+
logger?: Logger,
541+
): Map<string, InstalledPackage> {
542+
logger?.debug('Parsing Bun dependency list...');
543+
logStdout(stdout, logger);
544+
545+
const dependencies = new Map<string, InstalledPackage>();
546+
if (!stdout) {
547+
return dependencies;
548+
}
549+
550+
const lines = stdout.split('\n');
551+
// Skip the first line (project info)
552+
for (let i = 1; i < lines.length; i++) {
553+
const line = lines[i].trim();
554+
if (!line) {
555+
continue;
556+
}
557+
558+
// Remove tree structure characters
559+
const cleanLine = line.replace(/^[]\s*/, '');
560+
561+
// Parse name and version
562+
// Scoped: @angular/[email protected]
563+
// Unscoped: rxjs @7.8.2
564+
const match = cleanLine.match(/^(.+?)\s?@([^@\s]+)$/);
565+
if (match) {
566+
const name = match[1];
567+
const version = match[2];
568+
dependencies.set(name, { name, version });
569+
}
570+
}
571+
572+
logger?.debug(` Found ${dependencies.size} dependencies.`);
573+
574+
return dependencies;
575+
}

packages/angular/cli/src/package-managers/parsers_spec.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import { parseNpmLikeError, parseYarnClassicError } from './parsers';
9+
import { parseBunDependencies, parseNpmLikeError, parseYarnClassicError } from './parsers';
1010

1111
describe('parsers', () => {
1212
describe('parseNpmLikeError', () => {
@@ -109,4 +109,41 @@ describe('parsers', () => {
109109
expect(error).toBeNull();
110110
});
111111
});
112+
113+
describe('parseBunDependencies', () => {
114+
it('should parse bun pm ls output', () => {
115+
const stdout = `
116+
/tmp/angular-cli-e2e-PiL5n3/e2e-test/assets/19.0-project-1767113081927 node_modules (1084)
117+
├── @angular-devkit/[email protected]
118+
├── @angular/[email protected]
119+
├── jasmine-core @5.6.0
120+
├── rxjs @7.8.2
121+
└── zone.js @0.15.1
122+
`.trim();
123+
124+
const deps = parseBunDependencies(stdout);
125+
expect(deps.size).toBe(5);
126+
expect(deps.get('@angular-devkit/build-angular')).toEqual({
127+
name: '@angular-devkit/build-angular',
128+
version: '20.3.13',
129+
});
130+
expect(deps.get('@angular/cli')).toEqual({ name: '@angular/cli', version: '20.3.13' });
131+
expect(deps.get('jasmine-core')).toEqual({ name: 'jasmine-core', version: '5.6.0' });
132+
expect(deps.get('rxjs')).toEqual({ name: 'rxjs', version: '7.8.2' });
133+
expect(deps.get('zone.js')).toEqual({ name: 'zone.js', version: '0.15.1' });
134+
});
135+
136+
it('should return empty map for empty stdout', () => {
137+
expect(parseBunDependencies('').size).toBe(0);
138+
});
139+
140+
it('should skip lines that do not match the pattern', () => {
141+
const stdout = `
142+
project node_modules
143+
├── invalid-line
144+
└── another-invalid
145+
`.trim();
146+
expect(parseBunDependencies(stdout).size).toBe(0);
147+
});
148+
});
112149
});

0 commit comments

Comments
 (0)