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..6282c76f74 100644 --- a/packages/create-email/src/index.spec.ts +++ b/packages/create-email/src/index.spec.ts @@ -1,20 +1,20 @@ 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'], + process.execPath, + [path.resolve(import.meta.dirname, './index.js'), '.test'], { - shell: true, - cwd: path.resolve(__dirname, '../'), + cwd: path.resolve(import.meta.dirname, '../'), stdio: 'pipe', }, ); @@ -26,36 +26,24 @@ 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 typecheckingProcess = spawnSync('npx tsc', { cwd: starterPath, + shell: true, stdio: 'pipe', }); if (typecheckingProcess.stderr) { diff --git a/packages/create-email/tsconfig.json b/packages/create-email/tsconfig.json index 8ee1333b50..8c1c2ea235 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": "nodenext", "noEmit": true, "types": ["vitest/globals"] } diff --git a/packages/preview-server/scripts/build-preview-server.mts b/packages/preview-server/scripts/build-preview-server.mts index 36c0236651..fde679aa76 100644 --- a/packages/preview-server/scripts/build-preview-server.mts +++ b/packages/preview-server/scripts/build-preview-server.mts @@ -1,16 +1,12 @@ 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 nextBuildProcess = spawn('pnpm', ['next', 'build'], { +const nextBuildProcess = spawn('pnpm next build', { detached: true, shell: true, stdio: 'inherit', - cwd: path.resolve(dirname, '../'), + cwd: path.resolve(import.meta.dirname, '../'), }); process.on('SIGINT', () => { @@ -23,7 +19,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, }); }); diff --git a/packages/preview-server/scripts/dev.mts b/packages/preview-server/scripts/dev.mts index 07fcda8c35..dbb1bc095c 100644 --- a/packages/preview-server/scripts/dev.mts +++ b/packages/preview-server/scripts/dev.mts @@ -30,7 +30,7 @@ NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT=true`, 'utf8', ); -const webServerProcess = child_process.spawn('next', ['dev'], { +const webServerProcess = child_process.spawn('pnpm next dev', { cwd: previewServerRoot, shell: true, stdio: 'inherit', 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" 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); 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