Skip to content

Commit de98975

Browse files
jheddingsclaude
andauthored
fix(cli): resolve dependency issues for npx usage (#52)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c2982af commit de98975

6 files changed

Lines changed: 94 additions & 14 deletions

File tree

cli/package-lock.json

Lines changed: 0 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
"dependencies": {
3434
"@oclif/core": "4.10.4",
3535
"chalk": "^5.6.2",
36-
"jiti": "^2.6.1",
3736
"jszip": "^3.10.1"
3837
},
3938
"devDependencies": {

cli/src/commands/everywhere/init.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import chalk from 'chalk';
2+
import { spawn } from 'node:child_process';
23
import * as fs from 'node:fs';
34
import * as path from 'node:path';
45
import { fileURLToPath } from 'node:url';
@@ -16,6 +17,26 @@ function getSdkVersion(): string {
1617
return pkg.version;
1718
}
1819

20+
export function runNpmInstall(cwd: string): Promise<void> {
21+
return new Promise((resolve, reject) => {
22+
const child = spawn('npm', ['install'], {
23+
cwd,
24+
stdio: 'inherit',
25+
shell: true,
26+
});
27+
child.on('error', (err) => {
28+
reject(new Error(`Failed to start npm install: ${err.message}`));
29+
});
30+
child.on('close', (code) => {
31+
if (code === 0) {
32+
resolve();
33+
} else {
34+
reject(new Error(`npm install failed with exit code ${code}`));
35+
}
36+
});
37+
});
38+
}
39+
1940
export default class InitCommand extends EverywhereBaseCommand {
2041
static description = 'Scaffold a stub Workday Everywhere plugin in an existing npm project.';
2142

@@ -110,7 +131,8 @@ export default class InitCommand extends EverywhereBaseCommand {
110131
fs.writeFileSync(tsxPath, renderStub(pkg.name));
111132
this.log(chalk.green('Created plugin.tsx'));
112133

113-
// Next-steps hint
114-
this.log('Run `npm install` to install dependencies.');
134+
// Run npm install
135+
this.log('Installing dependencies...');
136+
await runNpmInstall(pluginDir);
115137
}
116138
}

cli/tests/commands/everywhere/init.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, expect } from 'vitest';
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
22
import InitCommand from '../../../src/commands/everywhere/init.js';
33
import EverywhereBaseCommand from '../../../src/commands/everywhere/base.js';
44

@@ -25,3 +25,58 @@ describe('everywhere init', () => {
2525
});
2626
});
2727
});
28+
29+
describe('runNpmInstall', () => {
30+
beforeEach(() => {
31+
vi.resetModules();
32+
});
33+
34+
describe('when npm install succeeds', () => {
35+
it('resolves the promise', async () => {
36+
const mockOn = vi.fn().mockImplementation((event, cb) => {
37+
if (event === 'close') cb(0);
38+
});
39+
const mockSpawn = vi.fn().mockReturnValue({ on: mockOn });
40+
41+
vi.doMock('node:child_process', () => ({ spawn: mockSpawn }));
42+
43+
const { runNpmInstall } = await import('../../../src/commands/everywhere/init.js');
44+
45+
await expect(runNpmInstall('/fake/dir')).resolves.toBeUndefined();
46+
});
47+
});
48+
49+
describe('when npm install fails', () => {
50+
it('rejects with the exit code in the error message', async () => {
51+
const mockOn = vi.fn().mockImplementation((event, cb) => {
52+
if (event === 'close') cb(1);
53+
});
54+
const mockSpawn = vi.fn().mockReturnValue({ on: mockOn });
55+
56+
vi.doMock('node:child_process', () => ({ spawn: mockSpawn }));
57+
58+
const { runNpmInstall } = await import('../../../src/commands/everywhere/init.js');
59+
60+
await expect(runNpmInstall('/fake/dir')).rejects.toThrow(
61+
'npm install failed with exit code 1'
62+
);
63+
});
64+
});
65+
66+
describe('when spawn emits an error', () => {
67+
it('rejects with the error message', async () => {
68+
const mockOn = vi.fn().mockImplementation((event, cb) => {
69+
if (event === 'error') cb(new Error('spawn ENOENT'));
70+
});
71+
const mockSpawn = vi.fn().mockReturnValue({ on: mockOn });
72+
73+
vi.doMock('node:child_process', () => ({ spawn: mockSpawn }));
74+
75+
const { runNpmInstall } = await import('../../../src/commands/everywhere/init.js');
76+
77+
await expect(runNpmInstall('/fake/dir')).rejects.toThrow(
78+
'Failed to start npm install: spawn ENOENT'
79+
);
80+
});
81+
});
82+
});

package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
},
5757
"dependencies": {
5858
"@oclif/core": "4.10.5",
59+
"chalk": "^5.6.2",
5960
"esbuild": "^0.28",
6061
"jszip": "^3.10.1",
6162
"vite": "^8"

0 commit comments

Comments
 (0)