diff --git a/changelog.md b/changelog.md index 7bc3cb5..54cc6e1 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2026-03-15 - 1.1.0 - feat(cli) +add npmextra-based compile target configuration for deno builds + +- adds support for reading compileTargets from npmextra.json when tsdeno compile is run without explicit arguments +- exports new configuration interfaces for typed compile target definitions +- replaces direct filesystem utilities with smartfs for package.json isolation during compilation + ## 2026-03-15 - 1.0.1 - fix(repo) no changes to commit diff --git a/package.json b/package.json index fc2be17..5acc687 100644 --- a/package.json +++ b/package.json @@ -25,17 +25,15 @@ ], "dependencies": { "@push.rocks/early": "^4.0.4", + "@push.rocks/npmextra": "^5.3.3", "@push.rocks/smartcli": "^4.0.20", - "@push.rocks/smartfile": "^11.2.0", - "@push.rocks/smartlog": "^3.2.1", - "@push.rocks/smartlog-destination-local": "^9.0.2", - "@push.rocks/smartpath": "^6.0.0", - "@push.rocks/smartshell": "^3.3.0" + "@push.rocks/smartfs": "^1.5.0", + "@push.rocks/smartshell": "^3.3.7" }, "devDependencies": { - "@git.zone/tsbuild": "^4.1.2", + "@git.zone/tsbuild": "^4.3.0", "@git.zone/tsrun": "^2.0.1", - "@git.zone/tstest": "^3.1.8", - "@types/node": "^22.0.0" + "@git.zone/tstest": "^3.3.2", + "@types/node": "^25.5.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2982ff1..42236e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,37 +11,31 @@ importers: '@push.rocks/early': specifier: ^4.0.4 version: 4.0.4 + '@push.rocks/npmextra': + specifier: ^5.3.3 + version: 5.3.3 '@push.rocks/smartcli': specifier: ^4.0.20 version: 4.0.20 - '@push.rocks/smartfile': - specifier: ^11.2.0 - version: 11.2.7 - '@push.rocks/smartlog': - specifier: ^3.2.1 - version: 3.2.1 - '@push.rocks/smartlog-destination-local': - specifier: ^9.0.2 - version: 9.0.2 - '@push.rocks/smartpath': - specifier: ^6.0.0 - version: 6.0.0 + '@push.rocks/smartfs': + specifier: ^1.5.0 + version: 1.5.0 '@push.rocks/smartshell': - specifier: ^3.3.0 + specifier: ^3.3.7 version: 3.3.7 devDependencies: '@git.zone/tsbuild': - specifier: ^4.1.2 + specifier: ^4.3.0 version: 4.3.0 '@git.zone/tsrun': specifier: ^2.0.1 version: 2.0.1 '@git.zone/tstest': - specifier: ^3.1.8 + specifier: ^3.3.2 version: 3.3.2(socks@2.8.7)(typescript@5.9.3) '@types/node': - specifier: ^22.0.0 - version: 22.19.15 + specifier: ^25.5.0 + version: 25.5.0 packages: @@ -1559,6 +1553,9 @@ packages: '@types/node@22.19.15': resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} + '@types/node@25.5.0': + resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + '@types/ping@0.4.4': resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==} @@ -3184,6 +3181,9 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -5823,7 +5823,7 @@ snapshots: '@types/clean-css@4.2.11': dependencies: - '@types/node': 22.19.15 + '@types/node': 25.5.0 source-map: 0.6.1 '@types/debug@4.1.12': @@ -5833,7 +5833,7 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 22.19.15 + '@types/node': 25.5.0 '@types/hast@3.0.4': dependencies: @@ -5853,7 +5853,7 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 22.19.15 + '@types/node': 25.5.0 '@types/mdast@4.0.4': dependencies: @@ -5867,11 +5867,11 @@ snapshots: '@types/mute-stream@0.0.4': dependencies: - '@types/node': 22.19.15 + '@types/node': 25.5.0 '@types/node-forge@1.3.14': dependencies: - '@types/node': 22.19.15 + '@types/node': 25.5.0 '@types/node@16.9.1': {} @@ -5879,6 +5879,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@25.5.0': + dependencies: + undici-types: 7.18.2 + '@types/ping@0.4.4': {} '@types/relateurl@0.2.33': {} @@ -5889,11 +5893,11 @@ snapshots: '@types/tar-stream@3.1.4': dependencies: - '@types/node': 22.19.15 + '@types/node': 25.5.0 '@types/through2@2.0.41': dependencies: - '@types/node': 22.19.15 + '@types/node': 25.5.0 '@types/trusted-types@2.0.7': {} @@ -5919,11 +5923,11 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 22.19.15 + '@types/node': 25.5.0 '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.19.15 + '@types/node': 25.5.0 optional: true '@ungap/structured-clone@1.3.0': {} @@ -7835,6 +7839,8 @@ snapshots: undici-types@6.21.0: {} + undici-types@7.18.2: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3 diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index db07071..3eb9838 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@git.zone/tsdeno', - version: '1.0.1', + version: '1.1.0', description: 'A helper tool for deno compile that strips package.json to prevent devDependency bloat in compiled binaries.' } diff --git a/ts/index.ts b/ts/index.ts index af131dc..81d15eb 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -2,6 +2,7 @@ import * as early from '@push.rocks/early'; early.start('tsdeno'); export * from './tsdeno.classes.tsdeno.js'; +export * from './tsdeno.interfaces.js'; export { runCli } from './tsdeno.cli.js'; early.stop(); diff --git a/ts/plugins.ts b/ts/plugins.ts index f873c84..363c314 100644 --- a/ts/plugins.ts +++ b/ts/plugins.ts @@ -1,21 +1,17 @@ // node native import * as path from 'path'; -import * as fs from 'fs/promises'; -export { path, fs }; +export { path }; // @push.rocks scope +import * as npmextra from '@push.rocks/npmextra'; import * as smartcli from '@push.rocks/smartcli'; -import * as smartfile from '@push.rocks/smartfile'; -import * as smartlog from '@push.rocks/smartlog'; -import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local'; -import * as smartpath from '@push.rocks/smartpath'; +import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs'; import * as smartshell from '@push.rocks/smartshell'; export { + npmextra, smartcli, - smartfile, - smartlog, - smartlogDestinationLocal, - smartpath, + SmartFs, + SmartFsProviderNode, smartshell, }; diff --git a/ts/tsdeno.classes.tsdeno.ts b/ts/tsdeno.classes.tsdeno.ts index 91830ce..0423b17 100644 --- a/ts/tsdeno.classes.tsdeno.ts +++ b/ts/tsdeno.classes.tsdeno.ts @@ -1,9 +1,12 @@ 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; @@ -20,13 +23,7 @@ export class TsDeno { const packageJsonPath = plugins.path.join(this.cwd, 'package.json'); const backupPath = plugins.path.join(this.cwd, 'package.json.bak'); - let hasPackageJson = false; - try { - await plugins.fs.access(packageJsonPath); - hasPackageJson = true; - } catch { - hasPackageJson = false; - } + const hasPackageJson = await smartfs.file(packageJsonPath).exists(); // Ensure --node-modules-dir=none is present if (!args.some((arg) => arg.startsWith('--node-modules-dir'))) { @@ -36,7 +33,7 @@ export class TsDeno { // Temporarily hide package.json from Deno if (hasPackageJson) { console.log('tsdeno: temporarily hiding package.json to exclude devDependencies from bundle'); - await plugins.fs.rename(packageJsonPath, backupPath); + await smartfs.file(packageJsonPath).move(backupPath); } try { @@ -51,9 +48,81 @@ export class TsDeno { } finally { // Always restore package.json if (hasPackageJson) { - await plugins.fs.rename(backupPath, packageJsonPath); + 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; + } } diff --git a/ts/tsdeno.cli.ts b/ts/tsdeno.cli.ts index 7643b5a..7e1d6de 100644 --- a/ts/tsdeno.cli.ts +++ b/ts/tsdeno.cli.ts @@ -10,8 +10,10 @@ tsdenoCli.standardCommand().subscribe(async (argvArg) => { console.log(`@git.zone/tsdeno v${commitinfo.version}`); console.log(''); console.log('Usage:'); - console.log(' tsdeno compile [deno compile args...] Compile with package.json isolation'); + console.log(' tsdeno compile Compile all targets from npmextra.json'); + console.log(' tsdeno compile [deno compile args...] Compile with explicit args (passthrough)'); console.log(''); + console.log('When no args are given, tsdeno reads compileTargets from npmextra.json.'); console.log('The compile command temporarily removes package.json before running'); console.log('deno compile, preventing devDependencies from bloating the binary.'); console.log('--node-modules-dir=none is added automatically.'); @@ -19,11 +21,15 @@ tsdenoCli.standardCommand().subscribe(async (argvArg) => { tsdenoCli.addCommand('compile').subscribe(async (argvArg) => { const tsDeno = new TsDeno(); - - // Pass through all args after "compile" to deno compile const rawArgs = process.argv.slice(3); - await tsDeno.compile(rawArgs); + if (rawArgs.length === 0) { + // No args — read targets from npmextra.json + await tsDeno.compileFromConfig(); + } else { + // Args provided — passthrough to deno compile + await tsDeno.compile(rawArgs); + } }); export { tsdenoCli }; diff --git a/ts/tsdeno.interfaces.ts b/ts/tsdeno.interfaces.ts new file mode 100644 index 0000000..a4d66ab --- /dev/null +++ b/ts/tsdeno.interfaces.ts @@ -0,0 +1,12 @@ +export interface ITsdenoConfig { + compileTargets: ICompileTarget[]; +} + +export interface ICompileTarget { + name: string; + entryPoint: string; + outDir: string; + target: string; + permissions?: string[]; + noCheck?: boolean; +}