Skip to content

Commit 53c0a90

Browse files
authored
fix(all): shell option usage on child_process.spawn calls (#2708)
1 parent 8e8ab4a commit 53c0a90

File tree

8 files changed

+38
-96
lines changed

8 files changed

+38
-96
lines changed

packages/create-email/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"create-email": "src/index.js"
3737
},
3838
"devDependencies": {
39+
"nypm": "0.6.0",
3940
"react": "19.0.0",
4041
"tsconfig": "workspace:*",
4142
"typescript": "5.8.3"

packages/create-email/src/index.spec.ts

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import { spawnSync } from 'node:child_process';
22
import { existsSync, promises as fs } from 'node:fs';
33
import path from 'node:path';
4+
import { installDependencies, runScript } from 'nypm';
45

56
describe('automatic setup', () => {
6-
const starterPath = path.resolve(__dirname, '../.test');
7+
const starterPath = path.resolve(import.meta.dirname, '../.test');
78
test.sequential('creation', async () => {
89
if (existsSync(starterPath)) {
910
await fs.rm(starterPath, { recursive: true });
1011
}
1112

1213
const createEmailProcess = spawnSync(
13-
'node',
14-
[path.resolve(__dirname, './index.js'), '.test'],
14+
process.execPath,
15+
[path.resolve(import.meta.dirname, './index.js'), '.test'],
1516
{
16-
shell: true,
17-
cwd: path.resolve(__dirname, '../'),
17+
cwd: path.resolve(import.meta.dirname, '../'),
1818
stdio: 'pipe',
1919
},
2020
);
@@ -26,36 +26,24 @@ describe('automatic setup', () => {
2626
);
2727
});
2828

29-
test.sequential('install', { timeout: 40_000 }, () => {
30-
const installProcess = spawnSync('npm', ['install'], {
31-
shell: true,
32-
cwd: path.resolve(starterPath),
33-
stdio: 'pipe',
29+
test.sequential('install', { timeout: 40_000 }, async () => {
30+
await installDependencies({
31+
cwd: starterPath,
32+
packageManager: 'npm',
3433
});
35-
if (installProcess.stderr) {
36-
console.log(installProcess.stderr.toString());
37-
}
38-
expect(installProcess.status, 'starter npm install should return 0').toBe(
39-
0,
40-
);
4134
});
4235

43-
test.sequential('export', () => {
44-
const exportProcess = spawnSync('npm', ['run export'], {
45-
shell: true,
36+
test.sequential('export', async () => {
37+
await runScript('export', {
4638
cwd: starterPath,
47-
stdio: 'pipe',
39+
packageManager: 'npm',
4840
});
49-
if (exportProcess.stderr) {
50-
console.log(exportProcess.stderr.toString());
51-
}
52-
expect(exportProcess.status, 'export should return status code 0').toBe(0);
5341
});
5442

55-
test.sequential('type checking', { timeout: 10_000 }, () => {
56-
const typecheckingProcess = spawnSync('npx', ['tsc'], {
57-
shell: true,
43+
test.sequential('type checking', { timeout: 10_000 }, async () => {
44+
const typecheckingProcess = spawnSync('npx tsc', {
5845
cwd: starterPath,
46+
shell: true,
5947
stdio: 'pipe',
6048
});
6149
if (typecheckingProcess.stderr) {

packages/create-email/tsconfig.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"include": ["**/*.ts", "**/*.tsx"],
44
"exclude": ["dist", "build", "node_modules", ".test"],
55
"compilerOptions": {
6+
"moduleResolution": "nodenext",
7+
"module": "nodenext",
68
"noEmit": true,
79
"types": ["vitest/globals"]
810
}
Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
import { spawn } from 'node:child_process';
22
import fs from 'node:fs';
33
import path from 'node:path';
4-
import url from 'node:url';
54

6-
const filename = url.fileURLToPath(import.meta.url);
7-
const dirname = path.dirname(filename);
8-
9-
const nextBuildProcess = spawn('pnpm', ['next', 'build'], {
5+
const nextBuildProcess = spawn('pnpm next build', {
106
detached: true,
117
shell: true,
128
stdio: 'inherit',
13-
cwd: path.resolve(dirname, '../'),
9+
cwd: path.resolve(import.meta.dirname, '../'),
1410
});
1511

1612
process.on('SIGINT', () => {
@@ -23,7 +19,7 @@ nextBuildProcess.on('exit', (code) => {
2319
process.exit(code);
2420
}
2521

26-
fs.rmSync(path.resolve(dirname, '../.next/cache'), {
22+
fs.rmSync(path.resolve(import.meta.dirname, '../.next/cache'), {
2723
recursive: true,
2824
});
2925
});

packages/preview-server/scripts/dev.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT=true`,
3030
'utf8',
3131
);
3232

33-
const webServerProcess = child_process.spawn('next', ['dev'], {
33+
const webServerProcess = child_process.spawn('pnpm next dev', {
3434
cwd: previewServerRoot,
3535
shell: true,
3636
stdio: 'inherit',

packages/preview-server/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"noEmit": true,
2929
"strict": false,
3030
"target": "ESNext",
31-
"module": "CommonJS",
31+
"module": "esnext",
3232
"noUncheckedIndexedAccess": true,
3333
"resolveJsonModule": true,
3434
"types": ["vitest/globals"],
@@ -39,6 +39,7 @@
3939
"tailwind-internals.d.ts",
4040
"**/*.ts",
4141
"**/*.tsx",
42+
"**/*.mts",
4243
".next/types/**/*.ts",
4344
".next/dev/types/**/*.ts",
4445
"next.config.mjs"

packages/react-email/src/commands/build.ts

Lines changed: 11 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { spawn } from 'node:child_process';
21
import fs from 'node:fs';
32
import path from 'node:path';
43
import logSymbols from 'log-symbols';
4+
import { installDependencies, type PackageManagerName, runScript } from 'nypm';
55
import ora from 'ora';
66
import {
77
type EmailsDirectory,
@@ -12,65 +12,9 @@ import { registerSpinnerAutostopping } from '../utils/register-spinner-autostopp
1212

1313
interface Args {
1414
dir: string;
15-
packageManager: string;
15+
packageManager: PackageManagerName;
1616
}
1717

18-
const buildPreviewApp = (absoluteDirectory: string) => {
19-
return new Promise<void>((resolve, reject) => {
20-
const nextBuild = spawn('npm', ['run', 'build'], {
21-
cwd: absoluteDirectory,
22-
shell: true,
23-
});
24-
nextBuild.stdout.pipe(process.stdout);
25-
nextBuild.stderr.pipe(process.stderr);
26-
27-
nextBuild.on('close', (code) => {
28-
if (code === 0) {
29-
resolve();
30-
} else {
31-
reject(
32-
new Error(
33-
`Unable to build the Next app and it exited with code: ${code}`,
34-
),
35-
);
36-
}
37-
});
38-
});
39-
};
40-
41-
const npmInstall = async (
42-
builtPreviewAppPath: string,
43-
packageManager: string,
44-
) => {
45-
return new Promise<void>((resolve, reject) => {
46-
const childProc = spawn(
47-
packageManager,
48-
[
49-
'install',
50-
packageManager === 'deno' ? '' : '--include=dev',
51-
packageManager === 'deno' ? '--quiet' : '--silent',
52-
],
53-
{
54-
cwd: builtPreviewAppPath,
55-
shell: true,
56-
},
57-
);
58-
childProc.stdout.pipe(process.stdout);
59-
childProc.stderr.pipe(process.stderr);
60-
childProc.on('close', (code) => {
61-
if (code === 0) {
62-
resolve();
63-
} else {
64-
reject(
65-
new Error(
66-
`Unable to install the dependencies and it exited with code: ${code}`,
67-
),
68-
);
69-
}
70-
});
71-
});
72-
};
73-
7418
const setNextEnvironmentVariablesForBuild = async (
7519
emailsDirRelativePath: string,
7620
builtPreviewAppPath: string,
@@ -283,14 +227,21 @@ export const build = async ({
283227
await updatePackageJson(builtPreviewAppPath);
284228

285229
spinner.text = 'Installing dependencies on `.react-email`';
286-
await npmInstall(builtPreviewAppPath, packageManager);
230+
await installDependencies({
231+
cwd: builtPreviewAppPath,
232+
silent: true,
233+
packageManager,
234+
});
287235

288236
spinner.stopAndPersist({
289237
text: 'Successfully prepared `.react-email` for `next build`',
290238
symbol: logSymbols.success,
291239
});
292240

293-
await buildPreviewApp(builtPreviewAppPath);
241+
await runScript('build', {
242+
packageManager,
243+
cwd: builtPreviewAppPath,
244+
});
294245
} catch (error) {
295246
console.log(error);
296247
process.exit(1);

pnpm-lock.yaml

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

0 commit comments

Comments
 (0)