@orxataguy/tyr 1.0.11-beta.4 → 1.0.11-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/tyr.js CHANGED
@@ -1,46 +1,28 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * bin/tyr.js Tyr CLI entry point.
4
- *
5
- * Spawns Node.js with a custom ESM loader (bin/loader.mjs) that transpiles
6
- * TypeScript files in-memory using esbuild. This replaces the previous tsx
7
- * dependency, eliminating all disk-write overhead from TypeScript compilation
8
- * caches (~/.cache/tsx).
9
- *
10
- * Compatibility: Node.js 18+
11
- * The --loader flag is experimental in Node 22 but fully functional;
12
- * NODE_NO_WARNINGS suppresses the deprecation notice.
13
- */
14
-
15
- import { fileURLToPath, pathToFileURL } from 'url';
16
- import { dirname, join } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, resolve, join } from 'path';
17
4
  import { spawn } from 'child_process';
5
+ import { readFileSync } from 'fs';
18
6
 
19
7
  const __filename = fileURLToPath(import.meta.url);
20
- const __dirname = dirname(__filename);
8
+ const __dirname = dirname(__filename);
9
+ const packageRoot = resolve(__dirname, '..');
21
10
 
22
- const loaderPath = join(__dirname, 'loader.mjs');
23
- const loaderUrl = pathToFileURL(loaderPath).href; // file:// URL required on Windows
24
- const entry = join(__dirname, 'tyr.ts');
11
+ // Locate tsx's CLI entry directly from its package.json — no shell, no .cmd wrappers
12
+ const tsxPkg = JSON.parse(readFileSync(join(packageRoot, 'node_modules', 'tsx', 'package.json'), 'utf-8'));
13
+ const tsxBinField = tsxPkg.bin;
14
+ const tsxBinRelative = typeof tsxBinField === 'string' ? tsxBinField : (tsxBinField.tsx ?? tsxBinField['tsx']);
15
+ const tsxEntry = join(packageRoot, 'node_modules', 'tsx', tsxBinRelative);
16
+ const entry = join(__dirname, 'tyr.ts');
25
17
 
26
- const child = spawn(
27
- process.execPath,
28
- [
29
- '--loader', loaderUrl,
30
- '--no-warnings', // suppress ExperimentalWarning for --loader in Node 22
31
- entry,
32
- ...process.argv.slice(2),
33
- ],
34
- {
35
- stdio: 'inherit',
36
- env: { ...process.env },
37
- }
38
- );
18
+ const child = spawn(process.execPath, [tsxEntry, entry, ...process.argv.slice(2)], {
19
+ stdio: 'inherit'
20
+ });
39
21
 
40
- child.on('exit', (code) => process.exit(code ?? 0));
41
- child.on('error', (err) => {
42
- process.stderr.write(`[tyr] Error: Could not start tyr.\n${err.message}\n`);
43
- process.stderr.write(`loader path: ${loaderPath}\n`);
44
- process.stderr.write('Try reinstalling: npm install -g @orxataguy/tyr\n');
22
+ child.on('exit', (code) => process.exit(code ?? 0));
23
+ child.on('error', (err) => {
24
+ console.error(`Error: Could not start tyr. ${err.message}`);
25
+ console.error(`tsx not found at: ${tsxEntry}`);
26
+ console.error('Try reinstalling: npm install -g @orxataguy/tyr');
45
27
  process.exit(1);
46
28
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orxataguy/tyr",
3
- "version": "1.0.11-beta.4",
3
+ "version": "1.0.11-beta.9",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tyr": "./bin/tyr.js"
@@ -48,13 +48,13 @@
48
48
  "chalk": "^5.6.2",
49
49
  "cheerio": "^1.1.2",
50
50
  "dotenv": "^17.2.3",
51
- "esbuild": "^0.25.0",
52
51
  "execa": "^6.1.0",
53
52
  "find-config": "^1.0.0",
54
53
  "inquirer": "^13.2.1",
55
54
  "js-yaml": "^4.1.1",
56
55
  "mongodb": "^7.2.0",
57
- "mssql": "^12.2.0"
56
+ "mssql": "^12.2.0",
57
+ "tsx": "^4.21.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/js-yaml": "^4.0.9",
@@ -62,7 +62,6 @@
62
62
  "@vitest/coverage-v8": "^3.2.4",
63
63
  "@vitest/ui": "^3.2.4",
64
64
  "husky": "^9.1.7",
65
- "tsx": "^4.21.0",
66
65
  "typescript": "^5.9.3",
67
66
  "vite": "^7.3.1",
68
67
  "vitest": "^3.2.4"
@@ -4,17 +4,16 @@ import yaml from 'js-yaml';
4
4
  import dotenv from 'dotenv';
5
5
  import { fileURLToPath, pathToFileURL } from 'url';
6
6
  import { homedir } from 'os';
7
- import { Container } from './Container.ts';
7
+ import { Container } from './Container';
8
8
 
9
- import gen from './sys/gen.ts';
10
- import rem from './sys/rem.ts';
11
- import doc from './sys/doc.ts';
12
- import ai from './sys/ai.ts';
13
- import build from './sys/build.ts';
14
- import config from './sys/config.ts';
15
- import help from './sys/help.ts';
9
+ import gen from './sys/gen';
10
+ import rem from './sys/rem';
11
+ import doc from './sys/doc';
12
+ import ai from './sys/ai';
13
+ import config from './sys/config';
14
+ import help from './sys/help';
16
15
 
17
- import { TyrError } from './TyrError.ts';
16
+ import { TyrError } from './TyrError';
18
17
 
19
18
  interface TyrConfig {
20
19
  commands: Record<string, string>;
@@ -46,13 +45,6 @@ export class Kernel {
46
45
  private frameworkRoot: string;
47
46
  private userRoot: string;
48
47
 
49
- /**
50
- * In-process cache of CommandFactory functions keyed by command name.
51
- * Avoids re-importing (and re-transpiling via the ESM loader) the same
52
- * module on every run() call within the same process.
53
- */
54
- private readonly _commandCache = new Map<string, CommandFactory>();
55
-
56
48
  constructor() {
57
49
  this.container = new Container();
58
50
  this.config = null;
@@ -188,7 +180,6 @@ export class Kernel {
188
180
  rem,
189
181
  doc,
190
182
  ai,
191
- build,
192
183
  };
193
184
 
194
185
  if (systemCommands[commandName]) {
@@ -213,37 +204,20 @@ export class Kernel {
213
204
  }
214
205
 
215
206
  try {
216
- let commandFactory: CommandFactory;
217
-
218
- if (this._commandCache.has(commandName)) {
219
- // Fast path: factory already imported in this process — no I/O at all
220
- commandFactory = this._commandCache.get(commandName)!;
221
- } else {
222
- // Absolute paths (user commands) are used directly; relative paths resolve from frameworkRoot
223
- const absolutePath = path.isAbsolute(scriptPath)
224
- ? scriptPath
225
- : path.resolve(this.frameworkRoot, scriptPath);
226
-
227
- // Prefer a pre-compiled JS file produced by `tyr build` (AOT path).
228
- // Convention: ~/.tyr/dist/<basename>.js where basename = original filename with .ts → .js
229
- const distFile = path.basename(absolutePath).replace(/\.ts$/, '.js');
230
- const distPath = path.join(this.userRoot, 'dist', distFile);
231
- const resolvedPath = fs.existsSync(distPath) ? distPath : absolutePath;
232
-
233
- // Convert to file:// URL — required by ESM on Windows for absolute paths
234
- const moduleUrl = pathToFileURL(resolvedPath).href;
235
- const module = await import(moduleUrl);
236
-
237
- if (typeof module.default !== 'function') {
238
- throw new Error(`File ${scriptPath} does not export a default function.`);
239
- }
207
+ // Absolute paths (user commands) are used directly; relative paths resolve from frameworkRoot
208
+ const absolutePath = path.isAbsolute(scriptPath)
209
+ ? scriptPath
210
+ : path.resolve(this.frameworkRoot, scriptPath);
211
+
212
+ // Convert to file:// URL — required by ESM on Windows for absolute paths
213
+ const moduleUrl = pathToFileURL(absolutePath).href;
214
+ const module = await import(moduleUrl);
240
215
 
241
- commandFactory = module.default as CommandFactory;
242
- this._commandCache.set(commandName, commandFactory);
216
+ if (typeof module.default !== 'function') {
217
+ throw new Error(`File ${scriptPath} does not export a default function.`);
243
218
  }
244
219
 
245
- // Always build a fresh command instance so the context (logger, run, fail…)
246
- // reflects the current invocation — important for nested run() calls.
220
+ const commandFactory: CommandFactory = module.default;
247
221
  const command = commandFactory(context);
248
222
  await command(args.slice(1));
249
223
 
@@ -82,7 +82,6 @@ export default function help({ userRoot }: TyrContext) {
82
82
  { name: '--update', description: 'Actualiza ~/.tyr desde el repositorio git.', usage: 'tyr --update' },
83
83
  { name: '--upgrade', description: 'Actualiza el paquete npm de tyr.', usage: 'tyr --upgrade' },
84
84
  { name: 'gen', description: 'Genera un nuevo comando a partir de una descripción con IA.', usage: 'tyr gen <nombre> "<descripción>"' },
85
- { name: 'build', description: 'Compila AOT todos los comandos de map.yml a JS puro (elimina overhead de transpilación en ejecución).', usage: 'tyr build [--clean]' },
86
85
  { name: 'doc', description: 'Levanta la documentación del framework en el navegador.', usage: 'tyr doc' },
87
86
  ];
88
87
 
package/bin/loader.mjs DELETED
@@ -1,98 +0,0 @@
1
- /**
2
- * bin/loader.mjs
3
- *
4
- * Custom Node.js ESM loader for Tyr.
5
- * Transpiles TypeScript files on-the-fly using esbuild.transformSync().
6
- *
7
- * Key properties:
8
- * - Zero disk writes: all transpilation happens in memory — no ~/.cache/tsx or similar
9
- * - Fast: esbuild native binary is 50-100× faster than the full TypeScript compiler
10
- * - No type-checking: types are stripped, not verified (run tsc --noEmit separately)
11
- *
12
- * Registered via --loader flag in bin/tyr.js.
13
- */
14
-
15
- import { transformSync } from 'esbuild';
16
- import { existsSync, readFileSync } from 'fs';
17
- import { fileURLToPath, pathToFileURL } from 'url';
18
- import { resolve as resolvePath, dirname } from 'path';
19
-
20
- const TS_RE = /\.m?[ct]?ts$/;
21
- const NO_EXT_RE = /\/[^./]+$/; // path segment with no extension at the end
22
- const EXT_MAP = { '.js': '.ts', '.mjs': '.mts', '.cjs': '.cts' };
23
- const BARE_EXTS = ['.ts', '.mts', '.cts', '.js', '.mjs', '/index.ts', '/index.js'];
24
-
25
- /**
26
- * resolve() — handle two TypeScript-in-ESM conventions that Node cannot resolve natively:
27
- *
28
- * 1. Extensionless imports: './Container' → './Container.ts'
29
- * 2. JS-extension imports: './Foo.js' → './Foo.ts' (TypeScript ESM idiom)
30
- *
31
- * Only relative imports are intercepted; bare specifiers (npm packages) go straight
32
- * to the default resolver.
33
- */
34
- export function resolve(specifier, context, next) {
35
- if (!specifier.startsWith('.') || !context.parentURL) {
36
- return next(specifier, context);
37
- }
38
-
39
- const parentDir = dirname(fileURLToPath(context.parentURL));
40
- const fullPath = resolvePath(parentDir, specifier);
41
-
42
- // Case 1: specifier ends with a JS extension — try the equivalent TS extension
43
- for (const [jsExt, tsExt] of Object.entries(EXT_MAP)) {
44
- if (specifier.endsWith(jsExt)) {
45
- const tsPath = fullPath.slice(0, -jsExt.length) + tsExt;
46
- if (existsSync(tsPath)) {
47
- return { url: pathToFileURL(tsPath).href, shortCircuit: true };
48
- }
49
- }
50
- }
51
-
52
- // Case 2: extensionless — probe TS then JS extensions
53
- if (NO_EXT_RE.test(fullPath)) {
54
- for (const ext of BARE_EXTS) {
55
- const candidate = fullPath + ext;
56
- if (existsSync(candidate)) {
57
- return { url: pathToFileURL(candidate).href, shortCircuit: true };
58
- }
59
- }
60
- }
61
-
62
- return next(specifier, context);
63
- }
64
-
65
- /**
66
- * load() — intercept every ESM import whose URL ends in a TypeScript extension.
67
- *
68
- * We do NOT exclude node_modules: the tyr package itself ships as .ts source
69
- * (bin/tyr.ts, src/core/Kernel.ts, etc.) and those files ARE under node_modules
70
- * when installed globally. Node.js 24's built-in strip-types refuses to handle
71
- * .ts files under node_modules, so our loader must cover them.
72
- *
73
- * The TS_RE regex already limits interception to .ts/.mts/.cts files — the
74
- * compiled .js files that third-party packages ship will never match it.
75
- */
76
- export function load(url, context, next) {
77
- const isTs = TS_RE.test(url);
78
- if (!isTs) return next(url, context);
79
-
80
- const filePath = fileURLToPath(url);
81
- const source = readFileSync(filePath, 'utf8');
82
-
83
- const { code, warnings } = transformSync(source, {
84
- loader: 'ts',
85
- format: 'esm',
86
- target: 'node18',
87
- sourcemap: 'inline', // inline maps so stack traces point to .ts lines
88
- sourcefile: filePath,
89
- // Do not inject helpers — keep output minimal
90
- treeShaking: false,
91
- });
92
-
93
- for (const w of warnings) {
94
- process.stderr.write(`[tyr:loader] warning: ${w.text}\n`);
95
- }
96
-
97
- return { format: 'module', source: code, shortCircuit: true };
98
- }
@@ -1,121 +0,0 @@
1
- /**
2
- * build.ts — sistema: tyr build
3
- *
4
- * Compila AOT (Ahead-of-Time) todos los comandos registrados en ~/.tyr/map.yml
5
- * desde TypeScript a JavaScript puro usando esbuild.
6
- *
7
- * El resultado se escribe en ~/.tyr/dist/<basename>.js.
8
- * Una vez compilado, el Kernel detecta automáticamente los ficheros .js en dist/
9
- * y los usa en lugar de los .ts originales, eliminando por completo el coste
10
- * de transpilación en tiempo de ejecución.
11
- *
12
- * Uso:
13
- * tyr build — compila todos los comandos de map.yml
14
- * tyr build --clean — elimina ~/.tyr/dist/ antes de compilar
15
- */
16
-
17
- import path from 'path';
18
- import fs from 'fs';
19
- import yaml from 'js-yaml';
20
- import type { TyrContext } from '../Kernel.js';
21
-
22
- interface MapYml {
23
- commands: Record<string, string>;
24
- aliases?: Record<string, string>;
25
- }
26
-
27
- export default function build({ logger, userRoot }: TyrContext) {
28
- return async (args: string[]) => {
29
- const clean = args.includes('--clean');
30
- const mapPath = path.join(userRoot, 'map.yml');
31
- const distDir = path.join(userRoot, 'dist');
32
-
33
- // ── Leer map.yml ──────────────────────────────────────────────────────
34
- if (!fs.existsSync(mapPath)) {
35
- logger.error(`No se encontró map.yml en ${userRoot}`);
36
- return;
37
- }
38
-
39
- const raw = yaml.load(fs.readFileSync(mapPath, 'utf8')) as MapYml;
40
- const commands = raw?.commands ?? {};
41
- const entries = Object.entries(commands);
42
-
43
- if (entries.length === 0) {
44
- logger.warn('map.yml no contiene comandos. Nada que compilar.');
45
- return;
46
- }
47
-
48
- // ── Limpiar dist/ si se pide ──────────────────────────────────────────
49
- if (clean && fs.existsSync(distDir)) {
50
- fs.rmSync(distDir, { recursive: true, force: true });
51
- logger.info('dist/ eliminado.');
52
- }
53
-
54
- if (!fs.existsSync(distDir)) {
55
- fs.mkdirSync(distDir, { recursive: true });
56
- }
57
-
58
- // ── Importar esbuild dinámicamente (dep de producción en @orxataguy/tyr) ──
59
- // La importación dinámica garantiza que si esbuild no estuviese disponible
60
- // se obtiene un error claro en lugar de un crash al arrancar el framework.
61
- let esbuild: typeof import('esbuild');
62
- try {
63
- esbuild = await import('esbuild');
64
- } catch {
65
- logger.error('esbuild no está disponible. Reinstala: npm install -g @orxataguy/tyr');
66
- return;
67
- }
68
-
69
- // ── Compilar cada comando ─────────────────────────────────────────────
70
- let compiled = 0;
71
- let failed = 0;
72
-
73
- for (const [name, relPath] of entries) {
74
- const srcPath = path.isAbsolute(relPath)
75
- ? relPath
76
- : path.resolve(userRoot, relPath);
77
-
78
- if (!fs.existsSync(srcPath)) {
79
- logger.warn(`[${name}] Fichero no encontrado: ${srcPath} — omitido.`);
80
- failed++;
81
- continue;
82
- }
83
-
84
- const outFile = path.join(
85
- distDir,
86
- path.basename(srcPath).replace(/\.ts$/, '.js'),
87
- );
88
-
89
- try {
90
- await esbuild.build({
91
- entryPoints: [srcPath],
92
- outfile: outFile,
93
- bundle: false, // solo transpila, no empaqueta deps
94
- platform: 'node',
95
- format: 'esm',
96
- target: 'node18',
97
- sourcemap: false, // sin sourcemaps en producción
98
- packages: 'external', // mantiene imports de npm tal cual
99
- logLevel: 'silent',
100
- });
101
- logger.success(`[${name}] → ${path.relative(userRoot, outFile)}`);
102
- compiled++;
103
- } catch (err: any) {
104
- logger.error(`[${name}] Error de compilación: ${err?.message ?? err}`);
105
- failed++;
106
- }
107
- }
108
-
109
- // ── Resumen ────────────────────────────────────────────────────────────
110
- console.log('');
111
- if (compiled > 0) {
112
- logger.success(
113
- `Build completado: ${compiled} comando${compiled !== 1 ? 's' : ''} compilado${compiled !== 1 ? 's' : ''} en dist/`,
114
- );
115
- logger.info('El Kernel usará automáticamente los ficheros compilados en la próxima ejecución.');
116
- }
117
- if (failed > 0) {
118
- logger.warn(`${failed} comando${failed !== 1 ? 's' : ''} no pudieron compilarse.`);
119
- }
120
- };
121
- }