diff --git a/packages/api/exec.mts b/packages/api/exec.mts index ba87e58b..73b39fba 100644 --- a/packages/api/exec.mts +++ b/packages/api/exec.mts @@ -1,3 +1,4 @@ +/* eslint-disable turbo/no-undeclared-env-vars */ import Path from 'node:path'; import { spawn } from 'node:child_process'; @@ -25,6 +26,7 @@ export type NPMInstallRequestType = BaseExecRequestType & { type NpxRequestType = BaseExecRequestType & { args: Array; + env?: NodeJS.ProcessEnv; }; type SpawnCallRequestType = { @@ -38,25 +40,54 @@ type SpawnCallRequestType = { onError?: (err: NodeError) => void; }; +/** + * Main spawnCall function that routes to platform-specific implementations. + */ export function spawnCall(options: SpawnCallRequestType) { + if (process.platform === 'win32') { + return spawnCallWindows(options); + } else { + return spawnCallUnix(options); + } +} + +/** + * Unix-specific implementation of spawnCall. + */ +function spawnCallUnix(options: SpawnCallRequestType) { const { cwd, env, command, args, stdout, stderr, onExit, onError } = options; - const child = spawn(command, args, { cwd: cwd, env: env }); + + const child = spawn(command, args, { + cwd, + env, + }); child.stdout.on('data', stdout); child.stderr.on('data', stderr); + child.on('error', onError || console.error); + child.on('exit', onExit); - child.on('error', (err) => { - if (onError) { - onError(err); - } else { - console.error(err); - } - }); + return child; +} + +/** + * Windows-specific implementation of spawnCall. + */ +function spawnCallWindows(options: SpawnCallRequestType) { + const { cwd, env, command, args, stdout, stderr, onExit, onError } = options; - child.on('exit', (code, signal) => { - onExit(code, signal); + const child = spawn(command, args, { + cwd, + env, + windowsVerbatimArguments: true, + shell: true, }); + child.stdout.on('data', stdout); + child.stderr.on('data', stderr); + child.on('error', onError || console.error); + child.on('exit', onExit); + return child; } @@ -66,28 +97,22 @@ export function spawnCall(options: SpawnCallRequestType) { * Example: * * node({ - * cwd: '/Users/ben/.srcbook/30v2av4eee17m59dg2c29758to', - * env: {FOO_ENV_VAR: 'foooooooo'}, - * entry: '/Users/ben/.srcbook/30v2av4eee17m59dg2c29758to/src/foo.js', + * cwd: '/path/to/project', + * env: {FOO_ENV_VAR: 'value'}, + * entry: '/path/to/file.js', * stdout(data) {console.log(data.toString('utf8'))}, * stderr(data) {console.error(data.toString('utf8'))}, * onExit(code) {console.log(`Exit code: ${code}`)} * }); - * */ -export function node(options: NodeRequestType) { - const { cwd, env, entry, stdout, stderr, onExit } = options; - - return spawnCall({ +export const node = ({ cwd, env, entry, ...rest }: NodeRequestType) => + spawnCall({ command: 'node', cwd, args: [entry], - stdout, - stderr, - onExit, env: { ...process.env, ...env }, + ...rest, }); -} /** * Execute a TypeScript file using tsx. @@ -95,30 +120,25 @@ export function node(options: NodeRequestType) { * Example: * * tsx({ - * cwd: '/Users/ben/.srcbook/30v2av4eee17m59dg2c29758to', - * env: {FOO_ENV_VAR: 'foooooooo'}, - * entry: '/Users/ben/.srcbook/30v2av4eee17m59dg2c29758to/src/foo.ts', + * cwd: '/path/to/project', + * env: {FOO_ENV_VAR: 'value'}, + * entry: '/path/to/file.ts', * stdout(data) {console.log(data.toString('utf8'))}, * stderr(data) {console.error(data.toString('utf8'))}, * onExit(code) {console.log(`Exit code: ${code}`)} * }); - * */ -export function tsx(options: NodeRequestType) { - const { cwd, env, entry, stdout, stderr, onExit } = options; - - // We are making an assumption about `tsx` being the tool of choice - // for running TypeScript, as well as where it's located on the file system. - return spawnCall({ - command: Path.join(cwd, 'node_modules', '.bin', 'tsx'), +export const tsx = ({ cwd, env, entry, ...rest }: NodeRequestType) => + spawnCall({ + command: + process.platform === 'win32' + ? Path.join(cwd, 'node_modules', '.bin', 'tsx.cmd') + : Path.join(cwd, 'node_modules', '.bin', 'tsx'), cwd, args: [entry], - stdout, - stderr, - onExit, env: { ...process.env, ...env }, + ...rest, }); -} /** * Run npm install. @@ -126,7 +146,7 @@ export function tsx(options: NodeRequestType) { * Install all packages: * * npmInstall({ - * cwd: '/Users/ben/.srcbook/foo', + * cwd: '/path/to/project', * stdout(data) {console.log(data.toString('utf8'))}, * stderr(data) {console.error(data.toString('utf8'))}, * onExit(code) {console.log(`Exit code: ${code}`)} @@ -135,38 +155,34 @@ export function tsx(options: NodeRequestType) { * Install a specific package: * * npmInstall({ - * cwd: '/Users/ben/.srcbook/foo', - * package: 'marked', + * cwd: '/path/to/project', + * packages: ['lodash'], * stdout(data) {console.log(data.toString('utf8'))}, * stderr(data) {console.error(data.toString('utf8'))}, * onExit(code) {console.log(`Exit code: ${code}`)} * }); - * */ -export function npmInstall(options: NPMInstallRequestType) { - const { cwd, stdout, stderr, onExit } = options; - const args = options.packages - ? ['install', '--include=dev', ...(options.args || []), ...options.packages] - : ['install', '--include=dev', ...(options.args || [])]; - - return spawnCall({ +export const npmInstall = ({ cwd, packages = [], args = [], ...rest }: NPMInstallRequestType) => + spawnCall({ command: 'npm', cwd, - args, - stdout, - stderr, - onExit, + args: ['install', '--include=dev', ...args, ...packages], env: process.env, + ...rest, }); -} /** * Run vite. */ -export function vite(options: NpxRequestType) { - return spawnCall({ - ...options, - command: Path.join(options.cwd, 'node_modules', '.bin', 'vite'), - env: process.env, +export const vite = ({ cwd, args = [], env = process.env, ...rest }: NpxRequestType) => + spawnCall({ + command: + process.platform === 'win32' ? 'npx.cmd' : Path.join(cwd, 'node_modules', '.bin', 'vite'), + cwd, + args: process.platform === 'win32' ? ['vite', ...args] : args, + env: { + ...env, + FORCE_COLOR: '1', + }, + ...rest, }); -} diff --git a/packages/api/server/channels/app.mts b/packages/api/server/channels/app.mts index 1d860270..0287d7df 100644 --- a/packages/api/server/channels/app.mts +++ b/packages/api/server/channels/app.mts @@ -113,7 +113,6 @@ async function previewStart( }, onExit: (code) => { deleteAppProcess(app.externalId, 'vite:server'); - wss.broadcast(`app:${app.externalId}`, 'preview:status', { url: null, status: 'stopped', diff --git a/packages/api/tsserver/tsservers.mts b/packages/api/tsserver/tsservers.mts index 9e86625d..dcc51041 100644 --- a/packages/api/tsserver/tsservers.mts +++ b/packages/api/tsserver/tsservers.mts @@ -44,8 +44,12 @@ export class TsServers { // created, the dependencies are not installed and thus this will // shut down immediately. Make sure that we handle this case after // package.json has finished installing its deps. - const child = spawn('npx', ['tsserver'], { + + const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx'; + + const child = spawn(npxCmd, ['tsserver'], { cwd: options.cwd, + shell: process.platform === 'win32', }); const server = new TsServer(child); diff --git a/packages/web/tailwind.config.js b/packages/web/tailwind.config.js index ceaaa635..7504b465 100644 --- a/packages/web/tailwind.config.js +++ b/packages/web/tailwind.config.js @@ -1,6 +1,6 @@ /** @type {import('tailwindcss').Config} */ -const plugin = require('tailwindcss/plugin'); -const path = require('path'); +import plugin from 'tailwindcss/plugin'; +import path from 'path'; module.exports = { darkMode: ['class'],