import * as plugins from './plugins.js'; import type { ITsdenoConfig, ICompileTarget } from './tsdeno.interfaces.js'; const shell = new plugins.smartshell.Smartshell({ executor: 'bash', }); const smartfs = new plugins.SmartFs(new plugins.SmartFsProviderNode()); export class TsDeno { public cwd: string; constructor(cwdArg?: string) { this.cwd = cwdArg || process.cwd(); } /** * Wraps `deno compile` with package.json isolation. * Temporarily removes package.json so Deno doesn't resolve devDependencies * into the compiled binary, then restores it afterwards. */ public async compile(args: string[]): Promise { const packageJsonPath = plugins.path.join(this.cwd, 'package.json'); const backupPath = plugins.path.join(this.cwd, 'package.json.bak'); const hasPackageJson = await smartfs.file(packageJsonPath).exists(); // Ensure --node-modules-dir=none is present if (!args.some((arg) => arg.startsWith('--node-modules-dir'))) { args = ['--node-modules-dir=none', ...args]; } // Temporarily hide package.json from Deno if (hasPackageJson) { console.log('tsdeno: temporarily hiding package.json to exclude devDependencies from bundle'); await smartfs.file(packageJsonPath).move(backupPath); } try { const shellCommand = `cd ${this.cwd} && deno compile ${args.join(' ')}`; console.log(`tsdeno: running deno compile ${args.join(' ')}`); const result = await shell.execPassthrough(shellCommand); if (result.exitCode !== 0) { console.error(`tsdeno: deno compile exited with code ${result.exitCode}`); process.exit(result.exitCode); } } finally { // Always restore package.json if (hasPackageJson) { await smartfs.file(backupPath).move(packageJsonPath); console.log('tsdeno: restored package.json'); } } } /** * Reads compile targets from npmextra.json and compiles each one. * The package.json hide/restore wraps the entire loop. */ public async compileFromConfig(): Promise { const npmextraInstance = new plugins.npmextra.Npmextra(this.cwd); const config = npmextraInstance.dataFor('@git.zone/tsdeno', { compileTargets: [], }); if (config.compileTargets.length === 0) { console.error('tsdeno: no compileTargets found in npmextra.json under "@git.zone/tsdeno"'); console.error('tsdeno: either pass args directly or add config to npmextra.json'); process.exit(1); } console.log(`tsdeno: found ${config.compileTargets.length} compile target(s) in npmextra.json`); const packageJsonPath = plugins.path.join(this.cwd, 'package.json'); const backupPath = plugins.path.join(this.cwd, 'package.json.bak'); const hasPackageJson = await smartfs.file(packageJsonPath).exists(); // Hide package.json once for all targets if (hasPackageJson) { console.log('tsdeno: temporarily hiding package.json to exclude devDependencies from bundle'); await smartfs.file(packageJsonPath).move(backupPath); } try { for (const target of config.compileTargets) { console.log(`\ntsdeno: compiling target "${target.name}"...`); const args = this.buildArgsFromTarget(target); const shellCommand = `cd ${this.cwd} && deno compile ${args.join(' ')}`; console.log(`tsdeno: running deno compile ${args.join(' ')}`); const result = await shell.execPassthrough(shellCommand); if (result.exitCode !== 0) { console.error(`tsdeno: deno compile exited with code ${result.exitCode} for target "${target.name}"`); process.exit(result.exitCode); } } } finally { if (hasPackageJson) { await smartfs.file(backupPath).move(packageJsonPath); console.log('\ntsdeno: restored package.json'); } } console.log(`\ntsdeno: all ${config.compileTargets.length} target(s) compiled successfully`); } private buildArgsFromTarget(target: ICompileTarget): string[] { const args: string[] = ['--node-modules-dir=none']; if (target.noCheck) { args.push('--no-check'); } if (target.permissions) { args.push(...target.permissions); } const outputPath = plugins.path.join(target.outDir, target.name); args.push('--output', outputPath); args.push('--target', target.target); args.push(target.entryPoint); return args; } }