From 2674ac042d30e9bd28f4feeb85353f61498429df Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Tue, 25 Nov 2025 10:15:50 -0300 Subject: [PATCH 01/10] fix shell usage in preview server dev --- packages/preview-server/scripts/dev.mts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/preview-server/scripts/dev.mts b/packages/preview-server/scripts/dev.mts index 07fcda8c35..3dc7a4759e 100644 --- a/packages/preview-server/scripts/dev.mts +++ b/packages/preview-server/scripts/dev.mts @@ -30,9 +30,10 @@ NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT=true`, 'utf8', ); -const webServerProcess = child_process.spawn('next', ['dev'], { +const next = path.resolve(dirname, '../node_modules/.bin/next'); + +const webServerProcess = child_process.spawn(next, ['dev'], { cwd: previewServerRoot, - shell: true, stdio: 'inherit', }); From 6baabeec0a48f71fb616f02147d0e66d242e8b59 Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Tue, 25 Nov 2025 10:21:35 -0300 Subject: [PATCH 02/10] fix shell usage in create-email tests --- packages/create-email/package.json | 1 + packages/create-email/src/index.spec.ts | 43 ++++++++++--------------- packages/create-email/tsconfig.json | 2 ++ pnpm-lock.yaml | 3 ++ 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/packages/create-email/package.json b/packages/create-email/package.json index e1c1609eca..7980147180 100644 --- a/packages/create-email/package.json +++ b/packages/create-email/package.json @@ -36,6 +36,7 @@ "create-email": "src/index.js" }, "devDependencies": { + "nypm": "0.6.0", "react": "19.0.0", "tsconfig": "workspace:*", "typescript": "5.8.3" diff --git a/packages/create-email/src/index.spec.ts b/packages/create-email/src/index.spec.ts index 67b78ee99a..14eea63e52 100644 --- a/packages/create-email/src/index.spec.ts +++ b/packages/create-email/src/index.spec.ts @@ -1,19 +1,19 @@ import { spawnSync } from 'node:child_process'; import { existsSync, promises as fs } from 'node:fs'; import path from 'node:path'; +import { installDependencies, runScript } from 'nypm'; describe('automatic setup', () => { - const starterPath = path.resolve(__dirname, '../.test'); + const starterPath = path.resolve(import.meta.dirname, '../.test'); test.sequential('creation', async () => { if (existsSync(starterPath)) { await fs.rm(starterPath, { recursive: true }); } const createEmailProcess = spawnSync( - 'node', - [path.resolve(__dirname, './index.js'), '.test'], + path.resolve(__dirname, './index.js'), + ['.test'], { - shell: true, cwd: path.resolve(__dirname, '../'), stdio: 'pipe', }, @@ -26,35 +26,26 @@ describe('automatic setup', () => { ); }); - test.sequential('install', { timeout: 40_000 }, () => { - const installProcess = spawnSync('npm', ['install'], { - shell: true, - cwd: path.resolve(starterPath), - stdio: 'pipe', + test.sequential('install', { timeout: 40_000 }, async () => { + await installDependencies({ + cwd: starterPath, + packageManager: 'npm', }); - if (installProcess.stderr) { - console.log(installProcess.stderr.toString()); - } - expect(installProcess.status, 'starter npm install should return 0').toBe( - 0, - ); }); - test.sequential('export', () => { - const exportProcess = spawnSync('npm', ['run export'], { - shell: true, + test.sequential('export', async () => { + await runScript('export', { cwd: starterPath, - stdio: 'pipe', + packageManager: 'npm', }); - if (exportProcess.stderr) { - console.log(exportProcess.stderr.toString()); - } - expect(exportProcess.status, 'export should return status code 0').toBe(0); }); - test.sequential('type checking', { timeout: 10_000 }, () => { - const typecheckingProcess = spawnSync('npx', ['tsc'], { - shell: true, + test.sequential('type checking', { timeout: 10_000 }, async () => { + const tscBinary = path.resolve( + import.meta.dirname, + '../node_modules/.bin/tsc', + ); + const typecheckingProcess = spawnSync(tscBinary, [], { cwd: starterPath, stdio: 'pipe', }); diff --git a/packages/create-email/tsconfig.json b/packages/create-email/tsconfig.json index 8ee1333b50..663a8da103 100644 --- a/packages/create-email/tsconfig.json +++ b/packages/create-email/tsconfig.json @@ -3,6 +3,8 @@ "include": ["**/*.ts", "**/*.tsx"], "exclude": ["dist", "build", "node_modules", ".test"], "compilerOptions": { + "moduleResolution": "nodenext", + "module": "node20", "noEmit": true, "types": ["vitest/globals"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 981ac1d77d..c8c56cded9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -476,6 +476,9 @@ importers: specifier: ^8.0.0 version: 8.2.0 devDependencies: + nypm: + specifier: 0.6.0 + version: 0.6.0 react: specifier: ^19.0.0 version: 19.0.0 From 637cf329d4a9adafa111b202357f82da3289a290 Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Tue, 25 Nov 2025 10:33:08 -0300 Subject: [PATCH 03/10] include preview-server scripts in tsconfig, update module option --- packages/preview-server/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/preview-server/tsconfig.json b/packages/preview-server/tsconfig.json index c33e31b3f8..b3fa612a2c 100644 --- a/packages/preview-server/tsconfig.json +++ b/packages/preview-server/tsconfig.json @@ -28,7 +28,7 @@ "noEmit": true, "strict": false, "target": "ESNext", - "module": "CommonJS", + "module": "esnext", "noUncheckedIndexedAccess": true, "resolveJsonModule": true, "types": ["vitest/globals"], @@ -39,6 +39,7 @@ "tailwind-internals.d.ts", "**/*.ts", "**/*.tsx", + "**/*.mts", ".next/types/**/*.ts", ".next/dev/types/**/*.ts", "next.config.mjs" From 7ab82407d14ed1c2692def18cc01cd89bb9a727a Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Tue, 25 Nov 2025 10:33:18 -0300 Subject: [PATCH 04/10] remove shell f rom preview server's build --- .../scripts/build-preview-server.mts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/preview-server/scripts/build-preview-server.mts b/packages/preview-server/scripts/build-preview-server.mts index 36c0236651..c951f3670c 100644 --- a/packages/preview-server/scripts/build-preview-server.mts +++ b/packages/preview-server/scripts/build-preview-server.mts @@ -1,16 +1,16 @@ import { spawn } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; -import url from 'node:url'; -const filename = url.fileURLToPath(import.meta.url); -const dirname = path.dirname(filename); +const nextBinaryPath = path.resolve( + import.meta.dirname, + '../node_modules/.bin/next', +); -const nextBuildProcess = spawn('pnpm', ['next', 'build'], { +const nextBuildProcess = spawn(nextBinaryPath, ['build'], { detached: true, - shell: true, stdio: 'inherit', - cwd: path.resolve(dirname, '../'), + cwd: path.resolve(import.meta.dirname, '../'), }); process.on('SIGINT', () => { @@ -23,7 +23,7 @@ nextBuildProcess.on('exit', (code) => { process.exit(code); } - fs.rmSync(path.resolve(dirname, '../.next/cache'), { + fs.rmSync(path.resolve(import.meta.dirname, '../.next/cache'), { recursive: true, }); }); From 7b1beca9431d690512741842dd723f48e80abcd4 Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Tue, 25 Nov 2025 10:37:01 -0300 Subject: [PATCH 05/10] use nypm's features for script running and dependency installation CLI's build --- packages/react-email/src/commands/build.ts | 71 ++++------------------ 1 file changed, 11 insertions(+), 60 deletions(-) diff --git a/packages/react-email/src/commands/build.ts b/packages/react-email/src/commands/build.ts index 262d5437b6..11fede1b5e 100644 --- a/packages/react-email/src/commands/build.ts +++ b/packages/react-email/src/commands/build.ts @@ -1,7 +1,7 @@ -import { spawn } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import logSymbols from 'log-symbols'; +import { installDependencies, type PackageManagerName, runScript } from 'nypm'; import ora from 'ora'; import { type EmailsDirectory, @@ -12,65 +12,9 @@ import { registerSpinnerAutostopping } from '../utils/register-spinner-autostopp interface Args { dir: string; - packageManager: string; + packageManager: PackageManagerName; } -const buildPreviewApp = (absoluteDirectory: string) => { - return new Promise((resolve, reject) => { - const nextBuild = spawn('npm', ['run', 'build'], { - cwd: absoluteDirectory, - shell: true, - }); - nextBuild.stdout.pipe(process.stdout); - nextBuild.stderr.pipe(process.stderr); - - nextBuild.on('close', (code) => { - if (code === 0) { - resolve(); - } else { - reject( - new Error( - `Unable to build the Next app and it exited with code: ${code}`, - ), - ); - } - }); - }); -}; - -const npmInstall = async ( - builtPreviewAppPath: string, - packageManager: string, -) => { - return new Promise((resolve, reject) => { - const childProc = spawn( - packageManager, - [ - 'install', - packageManager === 'deno' ? '' : '--include=dev', - packageManager === 'deno' ? '--quiet' : '--silent', - ], - { - cwd: builtPreviewAppPath, - shell: true, - }, - ); - childProc.stdout.pipe(process.stdout); - childProc.stderr.pipe(process.stderr); - childProc.on('close', (code) => { - if (code === 0) { - resolve(); - } else { - reject( - new Error( - `Unable to install the dependencies and it exited with code: ${code}`, - ), - ); - } - }); - }); -}; - const setNextEnvironmentVariablesForBuild = async ( emailsDirRelativePath: string, builtPreviewAppPath: string, @@ -283,14 +227,21 @@ export const build = async ({ await updatePackageJson(builtPreviewAppPath); spinner.text = 'Installing dependencies on `.react-email`'; - await npmInstall(builtPreviewAppPath, packageManager); + await installDependencies({ + cwd: builtPreviewAppPath, + silent: true, + packageManager, + }); spinner.stopAndPersist({ text: 'Successfully prepared `.react-email` for `next build`', symbol: logSymbols.success, }); - await buildPreviewApp(builtPreviewAppPath); + await runScript('build', { + packageManager, + cwd: builtPreviewAppPath, + }); } catch (error) { console.log(error); process.exit(1); From 237ecb75e61aaa89c3eff02087fedcdc1fa13579 Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Wed, 26 Nov 2025 15:25:20 -0300 Subject: [PATCH 06/10] check for Windows and run .cmd with it --- packages/create-email/src/index.spec.ts | 4 +++- packages/preview-server/scripts/build-preview-server.mts | 4 +++- packages/preview-server/scripts/dev.mts | 9 +++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/create-email/src/index.spec.ts b/packages/create-email/src/index.spec.ts index 14eea63e52..b63fe4539a 100644 --- a/packages/create-email/src/index.spec.ts +++ b/packages/create-email/src/index.spec.ts @@ -43,7 +43,9 @@ describe('automatic setup', () => { test.sequential('type checking', { timeout: 10_000 }, async () => { const tscBinary = path.resolve( import.meta.dirname, - '../node_modules/.bin/tsc', + process.platform === 'win32' + ? '../node_modules/.bin/tsc.cmd' + : '../node_modules/.bin/tsc', ); const typecheckingProcess = spawnSync(tscBinary, [], { cwd: starterPath, diff --git a/packages/preview-server/scripts/build-preview-server.mts b/packages/preview-server/scripts/build-preview-server.mts index c951f3670c..d11482903f 100644 --- a/packages/preview-server/scripts/build-preview-server.mts +++ b/packages/preview-server/scripts/build-preview-server.mts @@ -4,7 +4,9 @@ import path from 'node:path'; const nextBinaryPath = path.resolve( import.meta.dirname, - '../node_modules/.bin/next', + process.platform === 'win32' + ? '../node_modules/.bin/next.cmd' + : '../node_modules/.bin/next', ); const nextBuildProcess = spawn(nextBinaryPath, ['build'], { diff --git a/packages/preview-server/scripts/dev.mts b/packages/preview-server/scripts/dev.mts index 3dc7a4759e..fb9942daac 100644 --- a/packages/preview-server/scripts/dev.mts +++ b/packages/preview-server/scripts/dev.mts @@ -30,9 +30,14 @@ NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT=true`, 'utf8', ); -const next = path.resolve(dirname, '../node_modules/.bin/next'); +const nextBinaryPath = path.resolve( + import.meta.dirname, + process.platform === 'win32' + ? '../node_modules/.bin/next.cmd' + : '../node_modules/.bin/next', +); -const webServerProcess = child_process.spawn(next, ['dev'], { +const webServerProcess = child_process.spawn(nextBinaryPath, ['dev'], { cwd: previewServerRoot, stdio: 'inherit', }); From 3fa9cc505a2c8885576763883859bdcd86fcf31b Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Wed, 26 Nov 2025 15:28:12 -0300 Subject: [PATCH 07/10] don't use __dirname --- packages/create-email/src/index.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/create-email/src/index.spec.ts b/packages/create-email/src/index.spec.ts index b63fe4539a..4bd17c65f3 100644 --- a/packages/create-email/src/index.spec.ts +++ b/packages/create-email/src/index.spec.ts @@ -11,10 +11,10 @@ describe('automatic setup', () => { } const createEmailProcess = spawnSync( - path.resolve(__dirname, './index.js'), + path.resolve(import.meta.dirname, './index.js'), ['.test'], { - cwd: path.resolve(__dirname, '../'), + cwd: path.resolve(import.meta.dirname, '../'), stdio: 'pipe', }, ); From c12bbe475443c7da5e7e017b3551320b5ec0b2fa Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Wed, 26 Nov 2025 15:32:37 -0300 Subject: [PATCH 08/10] try using shell without arguments --- packages/create-email/src/index.spec.ts | 9 ++------- .../preview-server/scripts/build-preview-server.mts | 10 ++-------- packages/preview-server/scripts/dev.mts | 10 ++-------- 3 files changed, 6 insertions(+), 23 deletions(-) diff --git a/packages/create-email/src/index.spec.ts b/packages/create-email/src/index.spec.ts index 4bd17c65f3..66df164f21 100644 --- a/packages/create-email/src/index.spec.ts +++ b/packages/create-email/src/index.spec.ts @@ -41,14 +41,9 @@ describe('automatic setup', () => { }); test.sequential('type checking', { timeout: 10_000 }, async () => { - const tscBinary = path.resolve( - import.meta.dirname, - process.platform === 'win32' - ? '../node_modules/.bin/tsc.cmd' - : '../node_modules/.bin/tsc', - ); - const typecheckingProcess = spawnSync(tscBinary, [], { + const typecheckingProcess = spawnSync('npx tsc', { cwd: starterPath, + shell: true, stdio: 'pipe', }); if (typecheckingProcess.stderr) { diff --git a/packages/preview-server/scripts/build-preview-server.mts b/packages/preview-server/scripts/build-preview-server.mts index d11482903f..fde679aa76 100644 --- a/packages/preview-server/scripts/build-preview-server.mts +++ b/packages/preview-server/scripts/build-preview-server.mts @@ -2,15 +2,9 @@ import { spawn } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; -const nextBinaryPath = path.resolve( - import.meta.dirname, - process.platform === 'win32' - ? '../node_modules/.bin/next.cmd' - : '../node_modules/.bin/next', -); - -const nextBuildProcess = spawn(nextBinaryPath, ['build'], { +const nextBuildProcess = spawn('pnpm next build', { detached: true, + shell: true, stdio: 'inherit', cwd: path.resolve(import.meta.dirname, '../'), }); diff --git a/packages/preview-server/scripts/dev.mts b/packages/preview-server/scripts/dev.mts index fb9942daac..dbb1bc095c 100644 --- a/packages/preview-server/scripts/dev.mts +++ b/packages/preview-server/scripts/dev.mts @@ -30,15 +30,9 @@ NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT=true`, 'utf8', ); -const nextBinaryPath = path.resolve( - import.meta.dirname, - process.platform === 'win32' - ? '../node_modules/.bin/next.cmd' - : '../node_modules/.bin/next', -); - -const webServerProcess = child_process.spawn(nextBinaryPath, ['dev'], { +const webServerProcess = child_process.spawn('pnpm next dev', { cwd: previewServerRoot, + shell: true, stdio: 'inherit', }); From 0a4a3c997d7b5bc275404bb4ec2580e0697dfe93 Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Wed, 26 Nov 2025 15:39:17 -0300 Subject: [PATCH 09/10] add execPath for better windows support --- packages/create-email/src/index.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/create-email/src/index.spec.ts b/packages/create-email/src/index.spec.ts index 66df164f21..6282c76f74 100644 --- a/packages/create-email/src/index.spec.ts +++ b/packages/create-email/src/index.spec.ts @@ -11,8 +11,8 @@ describe('automatic setup', () => { } const createEmailProcess = spawnSync( - path.resolve(import.meta.dirname, './index.js'), - ['.test'], + process.execPath, + [path.resolve(import.meta.dirname, './index.js'), '.test'], { cwd: path.resolve(import.meta.dirname, '../'), stdio: 'pipe', From 032309b998669563517db3f40698774e57db7822 Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Wed, 26 Nov 2025 16:55:14 -0300 Subject: [PATCH 10/10] fix tsconfig --- packages/create-email/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-email/tsconfig.json b/packages/create-email/tsconfig.json index 663a8da103..8c1c2ea235 100644 --- a/packages/create-email/tsconfig.json +++ b/packages/create-email/tsconfig.json @@ -4,7 +4,7 @@ "exclude": ["dist", "build", "node_modules", ".test"], "compilerOptions": { "moduleResolution": "nodenext", - "module": "node20", + "module": "nodenext", "noEmit": true, "types": ["vitest/globals"] }