fullnative 0.1.0

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE +21 -0
  3. package/README.md +161 -0
  4. package/dist/core/env/env.d.ts +34 -0
  5. package/dist/core/env/env.d.ts.map +1 -0
  6. package/dist/core/env/env.js +134 -0
  7. package/dist/core/env/env.js.map +1 -0
  8. package/dist/core/env/index.d.ts +10 -0
  9. package/dist/core/env/index.d.ts.map +1 -0
  10. package/dist/core/env/index.js +10 -0
  11. package/dist/core/env/index.js.map +1 -0
  12. package/dist/core/file/File.d.ts +246 -0
  13. package/dist/core/file/File.d.ts.map +1 -0
  14. package/dist/core/file/File.js +397 -0
  15. package/dist/core/file/File.js.map +1 -0
  16. package/dist/core/file/Folder.d.ts +235 -0
  17. package/dist/core/file/Folder.d.ts.map +1 -0
  18. package/dist/core/file/Folder.js +427 -0
  19. package/dist/core/file/Folder.js.map +1 -0
  20. package/dist/core/index.d.ts +6 -0
  21. package/dist/core/index.d.ts.map +1 -0
  22. package/dist/core/index.js +6 -0
  23. package/dist/core/index.js.map +1 -0
  24. package/dist/core/process/Command.d.ts +94 -0
  25. package/dist/core/process/Command.d.ts.map +1 -0
  26. package/dist/core/process/Command.js +143 -0
  27. package/dist/core/process/Command.js.map +1 -0
  28. package/dist/core/process/LiveProcess.d.ts +134 -0
  29. package/dist/core/process/LiveProcess.d.ts.map +1 -0
  30. package/dist/core/process/LiveProcess.js +214 -0
  31. package/dist/core/process/LiveProcess.js.map +1 -0
  32. package/dist/core/process/Process.d.ts +108 -0
  33. package/dist/core/process/Process.d.ts.map +1 -0
  34. package/dist/core/process/Process.js +138 -0
  35. package/dist/core/process/Process.js.map +1 -0
  36. package/dist/core/process/ProcessError.d.ts +50 -0
  37. package/dist/core/process/ProcessError.d.ts.map +1 -0
  38. package/dist/core/process/ProcessError.js +62 -0
  39. package/dist/core/process/ProcessError.js.map +1 -0
  40. package/dist/core/process/Result.d.ts +61 -0
  41. package/dist/core/process/Result.d.ts.map +1 -0
  42. package/dist/core/process/Result.js +89 -0
  43. package/dist/core/process/Result.js.map +1 -0
  44. package/dist/core/process/index.d.ts +7 -0
  45. package/dist/core/process/index.d.ts.map +1 -0
  46. package/dist/core/process/index.js +6 -0
  47. package/dist/core/process/index.js.map +1 -0
  48. package/dist/core/process/types.d.ts +17 -0
  49. package/dist/core/process/types.d.ts.map +1 -0
  50. package/dist/core/process/types.js +2 -0
  51. package/dist/core/process/types.js.map +1 -0
  52. package/dist/core/shell/Job.d.ts +149 -0
  53. package/dist/core/shell/Job.d.ts.map +1 -0
  54. package/dist/core/shell/Job.js +218 -0
  55. package/dist/core/shell/Job.js.map +1 -0
  56. package/dist/core/shell/Shell.d.ts +275 -0
  57. package/dist/core/shell/Shell.d.ts.map +1 -0
  58. package/dist/core/shell/Shell.js +461 -0
  59. package/dist/core/shell/Shell.js.map +1 -0
  60. package/dist/core/shell/index.d.ts +4 -0
  61. package/dist/core/shell/index.d.ts.map +1 -0
  62. package/dist/core/shell/index.js +3 -0
  63. package/dist/core/shell/index.js.map +1 -0
  64. package/dist/core/shell/types.d.ts +30 -0
  65. package/dist/core/shell/types.d.ts.map +1 -0
  66. package/dist/core/shell/types.js +2 -0
  67. package/dist/core/shell/types.js.map +1 -0
  68. package/dist/index.d.ts +2 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +2 -0
  71. package/dist/index.js.map +1 -0
  72. package/package.json +64 -0
@@ -0,0 +1,461 @@
1
+ import { Process, } from "../process/index.js";
2
+ import { Job } from "./Job.js";
3
+ import * as crypto from "node:crypto";
4
+ /**
5
+ * Facade que representa una sesión de shell para ejecutar comandos
6
+ * de forma cómoda. Mantiene estado (cwd, env, aliases, historial) y
7
+ * delega la ejecución a `Process` por debajo.
8
+ *
9
+ * @example
10
+ * const sh = new Shell();
11
+ *
12
+ * // Ejecución simple (shell-interpreted)
13
+ * const result = await sh.run("echo hola && ls -la");
14
+ * console.log(result.stdout);
15
+ *
16
+ * // Tagged template con interpolación segura
17
+ * const name = "mundo";
18
+ * await sh.$`echo hola ${name}`;
19
+ *
20
+ * // Pipeline
21
+ * const r = await sh.pipe("cat file.txt", "grep foo", "wc -l");
22
+ *
23
+ * // Cadena secuencial (stop on error)
24
+ * const results = await sh.chain("npm install", "npm run build", "npm test");
25
+ *
26
+ * // Segundo plano
27
+ * const job = sh.bg("npm run dev");
28
+ * job.onStdout(chunk => process.stdout.write(chunk));
29
+ * const res = await job.result();
30
+ */
31
+ export class Shell {
32
+ _proc;
33
+ _cwd;
34
+ _env;
35
+ _aliases = new Map();
36
+ _history = [];
37
+ _jobs = new Map();
38
+ _cleanupRegistered = false;
39
+ /**
40
+ * Crea una nueva sesión de shell.
41
+ *
42
+ * @param options - Configuración de la sesión (cwd, env, shell).
43
+ */
44
+ constructor(options = {}) {
45
+ this._proc = new Process();
46
+ this._cwd = options.cwd ?? process.cwd();
47
+ this._env = options.env ?? {};
48
+ }
49
+ // ─── Getters ─────────────────────────────────────────────────────
50
+ /** Directorio de trabajo actual de la sesión. */
51
+ get cwd() {
52
+ return this._cwd;
53
+ }
54
+ /** Copia de las variables de entorno de la sesión. */
55
+ get env() {
56
+ return { ...this._env };
57
+ }
58
+ /** Copia de los alias definidos como objeto plano. */
59
+ get aliases() {
60
+ return Object.fromEntries(this._aliases);
61
+ }
62
+ /** Historial de comandos ejecutados (solo lectura). */
63
+ get history() {
64
+ return this._history;
65
+ }
66
+ // ─── State management ────────────────────────────────────────────
67
+ /**
68
+ * Cambia el directorio de trabajo de la sesión.
69
+ *
70
+ * @param path - Ruta del nuevo directorio de trabajo.
71
+ * @returns `this` para encadenamiento.
72
+ */
73
+ cd(path) {
74
+ this._cwd = path;
75
+ return this;
76
+ }
77
+ /**
78
+ * Establece una variable de entorno para los próximos comandos.
79
+ *
80
+ * @param key - Nombre de la variable.
81
+ * @param value - Valor de la variable.
82
+ * @returns `this` para encadenamiento.
83
+ */
84
+ set(key, value) {
85
+ this._env[key] = value;
86
+ return this;
87
+ }
88
+ /**
89
+ * Elimina una variable de entorno de la sesión.
90
+ *
91
+ * @param key - Nombre de la variable a eliminar.
92
+ * @returns `this` para encadenamiento.
93
+ */
94
+ unset(key) {
95
+ delete this._env[key];
96
+ return this;
97
+ }
98
+ /**
99
+ * Define un alias para un comando.
100
+ *
101
+ * @param name - Nombre del alias.
102
+ * @param command - Comando al que se expande el alias.
103
+ * @returns `this` para encadenamiento.
104
+ */
105
+ alias(name, command) {
106
+ this._aliases.set(name, command);
107
+ return this;
108
+ }
109
+ /**
110
+ * Elimina un alias.
111
+ *
112
+ * @param name - Nombre del alias a eliminar.
113
+ * @returns `this` para encadenamiento.
114
+ */
115
+ unalias(name) {
116
+ this._aliases.delete(name);
117
+ return this;
118
+ }
119
+ // ─── Execution (delegates to Process) ─────────────────────────────
120
+ /**
121
+ * Tagged template que ejecuta un comando interpolando valores
122
+ * de forma segura (con quoting automático). Lanza `TypeError` si algún
123
+ * valor interpolado no es de un tipo soportado (string, number, boolean,
124
+ * array, null/undefined).
125
+ *
126
+ * @example
127
+ * const name = "world";
128
+ * await sh.$`echo hello ${name}`;
129
+ * // ejecuta: echo hello 'world'
130
+ *
131
+ * const files = ["a.txt", "b.txt"];
132
+ * await sh.$`cat ${files}`;
133
+ * // ejecuta: cat 'a.txt' 'b.txt'
134
+ *
135
+ * @param strings - Partes literales del template.
136
+ * @param values - Valores interpolados (se quotean automáticamente).
137
+ * @returns El resultado de la ejecución.
138
+ * @throws {TypeError} Cuando un valor interpolado no se puede quotear.
139
+ */
140
+ $(strings, ...values) {
141
+ let script = "";
142
+ for (let i = 0; i < strings.length; i++) {
143
+ script += strings[i];
144
+ if (i < values.length) {
145
+ script += this.quote(values[i]);
146
+ }
147
+ }
148
+ return this.run(script);
149
+ }
150
+ /**
151
+ * Ejecuta un script shell (interpretado), espera a que termine y devuelve el resultado.
152
+ *
153
+ * @param script - Script a ejecutar.
154
+ * @param options - Opciones adicionales de proceso (sobrescriben cwd/env de la sesión).
155
+ * @returns El resultado de la ejecución.
156
+ */
157
+ async run(script, options) {
158
+ const resolved = this.resolveAliases(script);
159
+ const start = Date.now();
160
+ const result = await this._proc.shell(resolved, this.buildOptions(options));
161
+ this.recordHistory(script, start, result);
162
+ return result;
163
+ }
164
+ // ─── Process management (delegates to Process) ────────────────────
165
+ /**
166
+ * Inicia un script shell (interpretado) y devuelve un `LiveProcess`
167
+ * para interactuar en vivo con su stdin/stdout/stderr.
168
+ *
169
+ * @param script - Script a iniciar.
170
+ * @returns El `LiveProcess` del script lanzado.
171
+ */
172
+ spawnScript(script) {
173
+ return this._proc.spawnScript(this.resolveAliases(script), this.buildOptions());
174
+ }
175
+ // ─── Composition ─────────────────────────────────────────────────
176
+ /**
177
+ * Ejecuta una serie de comandos en pipeline (stdout de uno al stdin del siguiente).
178
+ * Devuelve el resultado del último comando. Lanza `TypeError` si no se pasa
179
+ * ningún comando.
180
+ *
181
+ * @example
182
+ * const r = await sh.pipe("cat file.txt", "grep foo", "wc -l");
183
+ *
184
+ * @param scripts - Comandos a ejecutar en pipeline.
185
+ * @returns El resultado del último comando del pipeline.
186
+ * @throws {TypeError} Cuando `scripts` está vacío.
187
+ */
188
+ async pipe(...scripts) {
189
+ if (scripts.length === 0) {
190
+ throw new TypeError("pipe requires at least one command");
191
+ }
192
+ if (scripts.length === 1)
193
+ return this.run(scripts[0]);
194
+ const handles = scripts.map((s) => this._proc.spawnScript(this.resolveAliases(s), this.buildOptions()));
195
+ for (let i = 0; i < handles.length - 1; i++) {
196
+ handles[i].stdout?.pipe(handles[i + 1].stdin);
197
+ }
198
+ const results = await Promise.all(handles.map((h) => h.wait()));
199
+ return results[results.length - 1];
200
+ }
201
+ /**
202
+ * Ejecuta comandos secuencialmente. Se detiene al primer fallo
203
+ * y devuelve todos los resultados hasta ese punto.
204
+ *
205
+ * @example
206
+ * const results = await sh.chain("npm install", "npm run build", "npm test");
207
+ *
208
+ * @param scripts - Comandos a ejecutar en secuencia.
209
+ * @returns Resultados de cada comando ejecutado (hasta el primer fallo).
210
+ */
211
+ async chain(...scripts) {
212
+ const results = [];
213
+ for (const script of scripts) {
214
+ const result = await this.run(script);
215
+ results.push(result);
216
+ if (result.failed)
217
+ break;
218
+ }
219
+ return results;
220
+ }
221
+ /**
222
+ * Ejecuta `condition`; si termina OK, ejecuta `then`.
223
+ *
224
+ * @param condition - Comando condicional a evaluar.
225
+ * @param then - Comando a ejecutar si `condition` termina OK.
226
+ * @returns El resultado del segundo comando o `null` si no se ejecutó.
227
+ */
228
+ async ifOk(condition, then) {
229
+ const result = await this.run(condition);
230
+ if (result.ok)
231
+ return this.run(then);
232
+ return null;
233
+ }
234
+ /**
235
+ * Ejecuta `condition`; si termina con error, ejecuta `then`.
236
+ *
237
+ * @param condition - Comando condicional a evaluar.
238
+ * @param then - Comando a ejecutar si `condition` falla.
239
+ * @returns El resultado del segundo comando o `null` si no se ejecutó.
240
+ */
241
+ async ifFail(condition, then) {
242
+ const result = await this.run(condition);
243
+ if (result.failed)
244
+ return this.run(then);
245
+ return null;
246
+ }
247
+ // ─── Background jobs ─────────────────────────────────────────────
248
+ /**
249
+ * Inicia un script en segundo plano y devuelve un `Job` registrado
250
+ * en la sesión con el `name` dado (o uno aleatorio) y la opción `autoRestart`.
251
+ * El job se elimina del registro automáticamente al terminar (si no se reinicia).
252
+ * Lanza `TypeError` si ya existe un job con el mismo nombre.
253
+ *
254
+ * @example
255
+ * const dev = sh.bg("npm run dev", { name: "dev" });
256
+ * dev.onOutput(chunk => process.stdout.write(chunk));
257
+ *
258
+ * // Más tarde
259
+ * sh.killAll();
260
+ *
261
+ * @example autoRestart
262
+ * const server = sh.bg("npm run serve", { name: "server", autoRestart: true });
263
+ * server.onRestart(job => console.log(`Restarted (${job.restartCount})`));
264
+ *
265
+ * @param script - Script a ejecutar en segundo plano.
266
+ * @param options - Opciones del job (`name`, `autoRestart`).
267
+ * @returns El `Job` registrado en la sesión.
268
+ * @throws {TypeError} Cuando ya existe un job con el `name` dado.
269
+ */
270
+ bg(script, options) {
271
+ const name = options?.name ?? crypto.randomUUID();
272
+ if (this._jobs.has(name)) {
273
+ throw new TypeError(`Job "${name}" already exists`);
274
+ }
275
+ const live = this.spawnScript(script);
276
+ const job = new Job(live, this.resolveAliases(script), { name, ...options });
277
+ this._jobs.set(name, job);
278
+ this.ensureCleanup();
279
+ job.onExit(() => {
280
+ if (job.ended && !job._disposed_) {
281
+ this._jobs.delete(name);
282
+ }
283
+ });
284
+ if (job.autoRestart && !job.ended) {
285
+ job.onExit(() => {
286
+ if (job._disposed_)
287
+ return;
288
+ const replacement = this.spawnScript(script);
289
+ job._replace(replacement);
290
+ this.watchAutoRestart(job, script);
291
+ });
292
+ }
293
+ return job;
294
+ }
295
+ /** Mapa de jobs activos en la sesión, indexados por nombre (solo lectura). */
296
+ get jobs() {
297
+ return this._jobs;
298
+ }
299
+ /** Lista de jobs actualmente en ejecución. */
300
+ get activeJobs() {
301
+ return [...this._jobs.values()].filter((j) => j.running);
302
+ }
303
+ /**
304
+ * Mata todos los jobs activos con la señal dada (por defecto `SIGTERM`).
305
+ *
306
+ * @param signal - Señal a enviar a cada job activo.
307
+ */
308
+ killAll(signal = "SIGTERM") {
309
+ for (const job of this._jobs.values()) {
310
+ if (job.running)
311
+ job.kill(signal);
312
+ }
313
+ }
314
+ /**
315
+ * Devuelve un job por nombre.
316
+ *
317
+ * @param name - Nombre del job a buscar.
318
+ * @returns El `Job` correspondiente, o `undefined` si no existe.
319
+ */
320
+ job(name) {
321
+ return this._jobs.get(name);
322
+ }
323
+ // ─── Utilities (delegates to Process) ────────────────────────────
324
+ /**
325
+ * Comprueba si un comando existe en el PATH.
326
+ *
327
+ * @param command - Comando a buscar.
328
+ * @returns `true` si el comando existe.
329
+ */
330
+ async exists(command) {
331
+ return this._proc.exists(command);
332
+ }
333
+ /**
334
+ * Devuelve la ruta completa del comando o `null` si no existe.
335
+ *
336
+ * @param command - Comando a resolver.
337
+ * @returns La ruta absoluta del comando, o `null` si no se encuentra.
338
+ */
339
+ async which(command) {
340
+ return this._proc.which(command);
341
+ }
342
+ // ─── History ──────────────────────────────────────────────────────
343
+ /** Limpia el historial de comandos. */
344
+ clearHistory() {
345
+ this._history = [];
346
+ }
347
+ /**
348
+ * Devuelve la última entrada del historial.
349
+ *
350
+ * @returns La última `HistoryItem`, o `undefined` si el historial está vacío.
351
+ */
352
+ lastCommand() {
353
+ return this._history[this._history.length - 1];
354
+ }
355
+ // ─── Private helpers ─────────────────────────────────────────────
356
+ /**
357
+ * Construye las `ProcessOptions` base incluyendo `cwd` y `env` de la sesión.
358
+ *
359
+ * @param extra - Opciones adicionales que sobrescriben las de la sesión.
360
+ * @returns Las opciones de proceso combinadas.
361
+ */
362
+ buildOptions(extra) {
363
+ return {
364
+ cwd: this._cwd,
365
+ env: { ...process.env, ...this._env },
366
+ ...extra,
367
+ };
368
+ }
369
+ /**
370
+ * Resuelve aliases en la primera palabra del script.
371
+ *
372
+ * @param script - Script original.
373
+ * @returns El script con el alias expandido (si aplica).
374
+ */
375
+ resolveAliases(script) {
376
+ const trimmed = script.trimStart();
377
+ const firstSpace = trimmed.indexOf(" ");
378
+ const firstWord = firstSpace === -1 ? trimmed : trimmed.slice(0, firstSpace);
379
+ if (this._aliases.has(firstWord)) {
380
+ const replacement = this._aliases.get(firstWord);
381
+ if (firstSpace === -1)
382
+ return replacement;
383
+ return replacement + trimmed.slice(firstSpace);
384
+ }
385
+ return script;
386
+ }
387
+ /**
388
+ * Escapa un valor para interpolación segura en shell. Lanza `TypeError`
389
+ * si el valor es de un tipo no soportado (p. ej. objetos).
390
+ *
391
+ * @param value - Valor a quotear (string, number, boolean, array, null/undefined).
392
+ * @returns El valor quoteado como string seguro para shell.
393
+ * @throws {TypeError} Cuando el valor es de un tipo no soportado (p. ej. object).
394
+ */
395
+ quote(value) {
396
+ if (value === null || value === undefined)
397
+ return "''";
398
+ if (typeof value === "number" || typeof value === "boolean") {
399
+ return String(value);
400
+ }
401
+ if (Array.isArray(value)) {
402
+ return value.map((v) => this.quote(v)).join(" ");
403
+ }
404
+ if (typeof value === "string") {
405
+ return `'${value.replace(/'/g, "'\\''")}'`;
406
+ }
407
+ throw new TypeError(`Cannot quote value of type ${typeof value}`);
408
+ }
409
+ /**
410
+ * Registra una entrada en el historial.
411
+ *
412
+ * @param command - Comando ejecutado.
413
+ * @param startMs - Timestamp de inicio (ms desde epoch).
414
+ * @param result - Resultado de la ejecución.
415
+ */
416
+ recordHistory(command, startMs, result) {
417
+ this._history.push({
418
+ command,
419
+ startedAt: new Date(startMs),
420
+ durationMs: result.durationMs,
421
+ exitCode: result.exitCode,
422
+ ok: result.ok,
423
+ });
424
+ }
425
+ /**
426
+ * Configura el watcher de `autoRestart` sobre un job: cuando el proceso
427
+ * termina (sin ser dispuesto), se lanza un reemplazo y se registra
428
+ * recursivamente para el siguiente reinicio.
429
+ *
430
+ * @param job - Job a vigilar.
431
+ * @param script - Script a re-ejecutar al reiniciar.
432
+ */
433
+ watchAutoRestart(job, script) {
434
+ job.onExit(() => {
435
+ if (job._disposed_)
436
+ return;
437
+ const replacement = this.spawnScript(script);
438
+ job._replace(replacement);
439
+ this.watchAutoRestart(job, script);
440
+ });
441
+ }
442
+ /**
443
+ * Registra handlers de proceso (`exit`, `SIGINT`, `SIGTERM`) para matar
444
+ * los jobs huérfanos al salir. Solo se registra una vez por sesión.
445
+ */
446
+ ensureCleanup() {
447
+ if (this._cleanupRegistered)
448
+ return;
449
+ this._cleanupRegistered = true;
450
+ const handler = () => {
451
+ this.killAll("SIGTERM");
452
+ };
453
+ process.once("exit", handler);
454
+ process.once("SIGINT", () => {
455
+ handler();
456
+ process.exit(130);
457
+ });
458
+ process.once("SIGTERM", handler);
459
+ }
460
+ }
461
+ //# sourceMappingURL=Shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Shell.js","sourceRoot":"","sources":["../../../src/core/shell/Shell.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { Shell } from "./Shell.js";
2
+ export { Job } from "./Job.js";
3
+ export type { ShellConfig, HistoryItem, JobOptions } from "./types.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/shell/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { Shell } from "./Shell.js";
2
+ export { Job } from "./Job.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/shell/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,30 @@
1
+ /** Opciones de configuración para una sesión de Shell. */
2
+ export interface ShellConfig {
3
+ /** Directorio de trabajo inicial. Por defecto, `process.cwd()`. */
4
+ cwd?: string;
5
+ /** Variables de entorno adicionales. */
6
+ env?: Record<string, string>;
7
+ /** Shell a utilizar. Por defecto, `/bin/sh` o `cmd.exe`. */
8
+ shell?: string;
9
+ }
10
+ /** Entrada del historial de comandos ejecutados. */
11
+ export interface HistoryItem {
12
+ /** Comando ejecutado (script original antes de resolver aliases). */
13
+ command: string;
14
+ /** Fecha y hora en la que comenzó la ejecución del comando. */
15
+ startedAt: Date;
16
+ /** Duración total de la ejecución en milisegundos. */
17
+ durationMs: number;
18
+ /** Código de salida del proceso (`null` si fue terminado por señal). */
19
+ exitCode: number | null;
20
+ /** `true` si el proceso terminó con código de salida 0. */
21
+ ok: boolean;
22
+ }
23
+ /** Opciones para lanzar un job en segundo plano. */
24
+ export interface JobOptions {
25
+ /** Nombre identificador del job. Si no se provee, se genera uno automático. */
26
+ name?: string;
27
+ /** Si el proceso muere, se reinicia automáticamente. */
28
+ autoRestart?: boolean;
29
+ }
30
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/shell/types.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,MAAM,WAAW,WAAW;IAC1B,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,oDAAoD;AACpD,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,SAAS,EAAE,IAAI,CAAC;IAChB,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,2DAA2D;IAC3D,EAAE,EAAE,OAAO,CAAC;CACb;AAED,oDAAoD;AACpD,MAAM,WAAW,UAAU;IACzB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/shell/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export * from "./core/index.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./core/index.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "fullnative",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript toolchain for Node.js — files, folders, processes, shell sessions, and env loading with a clean, object-oriented API.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE",
20
+ "CHANGELOG.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "dev": "tsx src/index.ts",
25
+ "start": "node dist/index.js",
26
+ "typecheck": "tsc --noEmit",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest",
29
+ "test:coverage": "vitest run --coverage",
30
+ "clean": "rm -rf dist",
31
+ "prepublishOnly": "npm run clean && npm run build && npm run typecheck && npm test"
32
+ },
33
+ "keywords": [
34
+ "fullnative",
35
+ "nodejs",
36
+ "typescript",
37
+ "filesystem",
38
+ "process",
39
+ "shell",
40
+ "cli",
41
+ "scripting",
42
+ "dotenv",
43
+ "env"
44
+ ],
45
+ "author": "Zeltri",
46
+ "license": "MIT",
47
+ "homepage": "https://github.com/zeltri/full-native#readme",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/zeltri/full-native.git"
51
+ },
52
+ "bugs": {
53
+ "url": "https://github.com/zeltri/full-native/issues"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^22.0.0",
57
+ "tsx": "^4.19.0",
58
+ "typescript": "^5.7.0",
59
+ "vitest": "^4.1.10"
60
+ },
61
+ "engines": {
62
+ "node": ">=20.12.0"
63
+ }
64
+ }