Skip to content

Commit 1c6b646

Browse files
Merge pull request #210 from beakerandjake/feature/209-support-for-windows
Feature/209 support for windows
2 parents 55f9a84 + 551f685 commit 1c6b646

File tree

5 files changed

+127
-5
lines changed

5 files changed

+127
-5
lines changed

src/errors/solutionWorkerErrors.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { pathToFileURL } from 'url';
1+
import { pathToFileURL } from 'node:url';
22

33
/**
44
* Error raised if the Solution Worker thread exits without posting an answer message.

src/initialize/installPackages.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1+
import { platform } from 'node:process';
12
import { spawn } from 'node:child_process';
23
import { getConfigValue } from '../config.js';
34
import { logger } from '../logger.js';
45

6+
/**
7+
* Returns the npm command to execute for the specific platform.
8+
* Fixes ENOENT errors on windows.
9+
* @param {String} platform
10+
* @private
11+
*/
12+
export const getNpmCommand = (platformName) =>
13+
/^win/.test(platformName) ? 'npm.cmd' : 'npm';
14+
515
/**
616
* Install packages for the user.
717
*/
@@ -11,7 +21,7 @@ export const installPackages = async () => {
1121
logger.debug('installing npm packages');
1222
await new Promise((resolve, reject) => {
1323
// run npm install command
14-
const childProcess = spawn('npm', ['i', '--omit=dev'], {
24+
const childProcess = spawn(getNpmCommand(platform), ['i', '--omit=dev'], {
1525
cwd: getConfigValue('cwd'),
1626
stdio: ['ignore', 'ignore', 'pipe'],
1727
detached: false,

src/solutions/importUserSolutionModule.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* istanbul ignore file */
1+
import { pathToFileURL } from 'node:url';
22
import {
33
UserSolutionFileNotFoundError,
44
UserSolutionSyntaxError,
@@ -12,7 +12,9 @@ import {
1212
*/
1313
export const importUserSolutionModule = async (fileName) => {
1414
try {
15-
const module = await import(fileName);
15+
// fix ERR_UNSUPPORTED_EM_URL_SCHEME error on windows, and ensure path starts with file://
16+
const fileUrl = pathToFileURL(fileName).href;
17+
const module = await import(fileUrl);
1618
return module;
1719
} catch (error) {
1820
// throw nicer error if user file not found.

tests/initialize/installPackages.test.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ jest.unstable_mockModule('node:child_process', () => ({ spawn: jest.fn() }));
88

99
// import after mocks set up
1010
const { spawn } = await import('node:child_process');
11-
const { installPackages } = await import('../../src/initialize/installPackages.js');
11+
const { installPackages, getNpmCommand } = await import(
12+
'../../src/initialize/installPackages.js'
13+
);
1214

1315
describe('initialize', () => {
1416
describe('installPackages()', () => {
@@ -100,4 +102,19 @@ describe('initialize', () => {
100102
await expect(promise).resolves.not.toThrow();
101103
});
102104
});
105+
106+
describe('getNpmCommand()', () => {
107+
test.each([
108+
['npm', 'aix'],
109+
['npm', 'darwin'],
110+
['npm', 'freebsd'],
111+
['npm', 'linux'],
112+
['npm', 'openbsd'],
113+
['npm', 'sunos'],
114+
['npm.cmd', 'windows'],
115+
])('returns "%s" for platform "%s"', (expected, platform) => {
116+
const result = getNpmCommand(platform);
117+
expect(result).toBe(expected);
118+
});
119+
});
103120
});
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { describe, jest, test, afterEach } from '@jest/globals';
2+
3+
// setup mocks
4+
jest.unstable_mockModule('node:url', () => ({
5+
pathToFileURL: (path) => ({ href: path }),
6+
}));
7+
class UserSolutionSyntaxErrorMock extends Error {
8+
constructor() {
9+
super();
10+
this.name = 'UserSolutionSyntaxErrorMock';
11+
}
12+
}
13+
class UserSolutionFileNotFoundErrorMock extends Error {
14+
constructor() {
15+
super();
16+
this.name = 'UserSolutionFileNotFoundErrorMock';
17+
}
18+
}
19+
jest.unstable_mockModule('src/errors/solutionWorkerErrors.js', () => ({
20+
UserSolutionFileNotFoundError: UserSolutionFileNotFoundErrorMock,
21+
UserSolutionSyntaxError: UserSolutionSyntaxErrorMock,
22+
}));
23+
// import after mocks set up.
24+
25+
describe('importUserSolutionModule', () => {
26+
afterEach(() => {
27+
jest.clearAllMocks();
28+
jest.resetModules();
29+
});
30+
31+
test('returns module', async () => {
32+
// mock an module we know exists
33+
const modulePath = 'src/main.js';
34+
const module = { levelOne: () => {} };
35+
jest.unstable_mockModule(modulePath, () => module);
36+
37+
const { importUserSolutionModule } = await import(
38+
'../../src/solutions/importUserSolutionModule.js'
39+
);
40+
const result = await importUserSolutionModule(modulePath);
41+
expect(result).toMatchObject(module);
42+
});
43+
44+
test('throws UserSolutionFileNotFoundError if file not found', async () => {
45+
// mock an module we know exists
46+
const modulePath = 'src/main.js';
47+
const error = new Error();
48+
error.code = 'ERR_MODULE_NOT_FOUND';
49+
jest.unstable_mockModule(modulePath, () => {
50+
throw error;
51+
});
52+
53+
const { importUserSolutionModule } = await import(
54+
'../../src/solutions/importUserSolutionModule.js'
55+
);
56+
57+
await expect(async () => importUserSolutionModule(modulePath)).rejects.toThrow(
58+
UserSolutionFileNotFoundErrorMock
59+
);
60+
});
61+
62+
test('throws UserSolutionSyntaxError if file has syntax error', async () => {
63+
// mock an module we know exists
64+
const modulePath = 'src/main.js';
65+
jest.unstable_mockModule(modulePath, () => {
66+
throw new SyntaxError();
67+
});
68+
69+
const { importUserSolutionModule } = await import(
70+
'../../src/solutions/importUserSolutionModule.js'
71+
);
72+
73+
await expect(async () => importUserSolutionModule(modulePath)).rejects.toThrow(
74+
UserSolutionSyntaxErrorMock
75+
);
76+
});
77+
78+
test('throws on unknown error', async () => {
79+
// mock an module we know exists
80+
const modulePath = 'src/main.js';
81+
jest.unstable_mockModule(modulePath, () => {
82+
throw new RangeError();
83+
});
84+
85+
const { importUserSolutionModule } = await import(
86+
'../../src/solutions/importUserSolutionModule.js'
87+
);
88+
89+
await expect(async () => importUserSolutionModule(modulePath)).rejects.toThrow(
90+
RangeError
91+
);
92+
});
93+
});

0 commit comments

Comments
 (0)