Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions packages/angular/cli/src/package-managers/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,25 +393,39 @@ export class PackageManager {
* Gets the name of the package in the current project.
*/
async getCurrentPackageName(): Promise<string | undefined> {
try {
const content = await this.host.readFile(join(this.cwd, 'package.json'));
const { name } = JSON.parse(content) as { name?: unknown };
if (typeof name === 'string') {
return name;
}
} catch {
// Fall back to package manager command if reading file fails
}

if (this.descriptor.getPackageNameCommand) {
try {
const { stdout } = await this.#run(this.descriptor.getPackageNameCommand);
if (stdout) {
return JSON.parse(stdout);
const name = JSON.parse(stdout);
if (typeof name === 'string') {
return name;
}

// In a workspace member npm prints an object keyed by workspace name instead of a string.
if (name && typeof name === 'object') {
const values = Object.values(name);
if (values.length === 1 && typeof values[0] === 'string') {
return values[0];
}
}
}
} catch {
// Fall back to reading file if command fails
// Ignore command failure
}
}

try {
const content = await this.host.readFile(join(this.cwd, 'package.json'));
const pkgJson = JSON.parse(content);

return pkgJson.name;
} catch {
return undefined;
}
return undefined;
}

/**
Expand Down
69 changes: 69 additions & 0 deletions packages/angular/cli/src/package-managers/package-manager_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,75 @@ describe('PackageManager', () => {
});
});

describe('getCurrentPackageName', () => {
it('should return the name from package.json without running the package manager command', async () => {
const pm = new PackageManager(host, '/repo/app', descriptor);
const readFileSpy = spyOn(host, 'readFile').and.resolveTo('{"name": "app"}');

expect(await pm.getCurrentPackageName()).toBe('app');
expect(readFileSpy).toHaveBeenCalledWith(jasmine.stringMatching(/package\.json$/));
expect(runCommandSpy).not.toHaveBeenCalled();
});

it('should fall back to the package manager command when reading package.json fails', async () => {
const pm = new PackageManager(host, '/repo/app/src', descriptor);
spyOn(host, 'readFile').and.rejectWith(new Error('ENOENT'));
runCommandSpy.and.resolveTo({ stdout: '"app"', stderr: '' });

expect(await pm.getCurrentPackageName()).toBe('app');
expect(runCommandSpy).toHaveBeenCalledWith(
descriptor.binary,
descriptor.getPackageNameCommand,
jasmine.objectContaining({ cwd: '/repo/app/src' }),
);
});

it('should extract the package name when npm prints a single-entry workspace object', async () => {
const pm = new PackageManager(host, '/repo/apps/app/src', descriptor);
spyOn(host, 'readFile').and.rejectWith(new Error('ENOENT'));
// Inside a workspace member, `npm pkg get name` prints `{ "app": "app" }` instead of `"app"`.
runCommandSpy.and.resolveTo({ stdout: '{\n "app": "app"\n}', stderr: '' });

expect(await pm.getCurrentPackageName()).toBe('app');
});

it('should return undefined when npm prints multiple workspace entries and package.json is unavailable', async () => {
const pm = new PackageManager(host, '/repo', descriptor);
spyOn(host, 'readFile').and.rejectWith(new Error('ENOENT'));
runCommandSpy.and.resolveTo({
stdout: '{\n "app-a": "app-a",\n "app-b": "app-b"\n}',
stderr: '',
});

expect(await pm.getCurrentPackageName()).toBeUndefined();
});
});

describe('getProjectDependencies', () => {
it('should list the dependencies of an npm workspace member', async () => {
const pm = new PackageManager(host, '/repo/apps/app', descriptor);
runCommandSpy.and.resolveTo({
stdout: JSON.stringify({
name: 'monorepo',
dependencies: {
app: {
version: '0.0.0',
resolved: 'file:../apps/app',
dependencies: { '@angular/core': { version: '22.0.0' } },
},
},
}),
stderr: '',
});
spyOn(host, 'readFile').and.resolveTo('{"name": "app"}');

const dependencies = await pm.getProjectDependencies();

expect([...dependencies.keys()]).toEqual(['@angular/core']);
expect(dependencies.get('@angular/core')?.version).toBe('22.0.0');
});
});

describe('initializationError', () => {
it('should throw initializationError when running commands', async () => {
const error = new Error('Not installed');
Expand Down
Loading