@fuzdev/fuz_app 0.26.0 → 0.27.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.
@@ -1 +1 @@
1
- {"version":3,"file":"deno.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/deno.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,WAAW,EAA4B,MAAM,WAAW,CAAC;AAsCtE;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,KAAG,WAwEhE,CAAC"}
1
+ {"version":3,"file":"deno.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/deno.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,WAAW,EAA4B,MAAM,WAAW,CAAC;AAwDtE;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,aAAa,CAAC,MAAM,CAAC,KAAG,WA4HhE,CAAC"}
@@ -38,6 +38,33 @@ export const create_deno_runtime = (args) => ({
38
38
  mkdir: (path, options) => Deno.mkdir(path, options),
39
39
  read_text_file: (path) => Deno.readTextFile(path),
40
40
  read_file: (path) => Deno.readFile(path),
41
+ read_text_from_offset: async (path, offset) => {
42
+ const s = await Deno.stat(path);
43
+ const file_size = s.size;
44
+ const bytes_to_read = Math.max(0, file_size - offset);
45
+ if (bytes_to_read === 0)
46
+ return { content: '', bytes_read: 0, file_size };
47
+ const handle = await Deno.open(path, { read: true });
48
+ try {
49
+ await handle.seek(offset, Deno.SeekMode.Start);
50
+ const buffer = new Uint8Array(bytes_to_read);
51
+ const bytes_read = (await handle.read(buffer)) ?? 0;
52
+ return {
53
+ content: new TextDecoder().decode(buffer.subarray(0, bytes_read)),
54
+ bytes_read,
55
+ file_size,
56
+ };
57
+ }
58
+ finally {
59
+ handle.close();
60
+ }
61
+ },
62
+ readdir: async (path) => {
63
+ const names = [];
64
+ for await (const entry of Deno.readDir(path))
65
+ names.push(entry.name);
66
+ return names;
67
+ },
41
68
  write_text_file: (path, content) => Deno.writeTextFile(path, content),
42
69
  write_file: (path, data) => Deno.writeFile(path, data),
43
70
  rename: (old_path, new_path) => Deno.rename(old_path, new_path),
@@ -45,20 +72,46 @@ export const create_deno_runtime = (args) => ({
45
72
  // === HTTP ===
46
73
  fetch: globalThis.fetch,
47
74
  // === Local Commands ===
48
- run_command: async (cmd, args) => {
75
+ run_command: async (cmd, args, options) => {
49
76
  try {
50
- const proc = new Deno.Command(cmd, {
51
- args,
52
- stdout: 'piped',
53
- stderr: 'piped',
54
- });
55
- const result = await proc.output();
56
- return {
57
- success: result.code === 0,
58
- code: result.code,
59
- stdout: new TextDecoder().decode(result.stdout),
60
- stderr: new TextDecoder().decode(result.stderr),
61
- };
77
+ const controller = options?.timeout_ms !== undefined ? new AbortController() : null;
78
+ const signal = controller && options?.signal
79
+ ? AbortSignal.any([controller.signal, options.signal])
80
+ : (controller?.signal ?? options?.signal);
81
+ const timer = controller && options?.timeout_ms !== undefined
82
+ ? setTimeout(() => controller.abort(), options.timeout_ms)
83
+ : null;
84
+ let timed_out = false;
85
+ if (controller) {
86
+ controller.signal.addEventListener('abort', () => {
87
+ if (options?.signal?.aborted)
88
+ return;
89
+ timed_out = true;
90
+ }, { once: true });
91
+ }
92
+ try {
93
+ const proc = new Deno.Command(cmd, {
94
+ args,
95
+ cwd: options?.cwd,
96
+ signal,
97
+ stdout: 'piped',
98
+ stderr: 'piped',
99
+ });
100
+ const result = await proc.output();
101
+ const base = {
102
+ success: result.code === 0 && !timed_out,
103
+ code: result.code,
104
+ stdout: new TextDecoder().decode(result.stdout),
105
+ stderr: new TextDecoder().decode(result.stderr),
106
+ };
107
+ if (options?.timeout_ms !== undefined)
108
+ base.timed_out = timed_out;
109
+ return base;
110
+ }
111
+ finally {
112
+ if (timer !== null)
113
+ clearTimeout(timer);
114
+ }
62
115
  }
63
116
  catch (error) {
64
117
  const message = error instanceof Error ? error.message : String(error);
@@ -16,12 +16,28 @@ export interface StatResult {
16
16
  }
17
17
  /**
18
18
  * Result of executing a command.
19
+ *
20
+ * `timed_out` is present only when `timeout_ms` was passed in `RunCommandOptions`
21
+ * and the process was killed after exceeding the timeout. Callers that pass
22
+ * `timeout_ms` should check this flag to distinguish timeout from exit-code failure.
19
23
  */
20
24
  export interface CommandResult {
21
25
  success: boolean;
22
26
  code: number;
23
27
  stdout: string;
24
28
  stderr: string;
29
+ timed_out?: boolean;
30
+ }
31
+ /**
32
+ * Options for `run_command`.
33
+ */
34
+ export interface RunCommandOptions {
35
+ /** Working directory for the child process. */
36
+ cwd?: string;
37
+ /** AbortSignal to terminate the child process. */
38
+ signal?: AbortSignal;
39
+ /** Kill the process and return `timed_out: true` after this many milliseconds. */
40
+ timeout_ms?: number;
25
41
  }
26
42
  /**
27
43
  * Environment variable access.
@@ -32,16 +48,37 @@ export interface EnvDeps {
32
48
  /** Set an environment variable. */
33
49
  env_set: (name: string, value: string) => void;
34
50
  }
51
+ /**
52
+ * Result of reading text from a byte offset.
53
+ */
54
+ export interface ReadTextFromOffsetResult {
55
+ /** Decoded text content read from the offset. */
56
+ content: string;
57
+ /** Number of bytes actually read. */
58
+ bytes_read: number;
59
+ /** Total file size at the time of the read (for truncation detection). */
60
+ file_size: number;
61
+ }
35
62
  /**
36
63
  * File system read operations.
37
64
  */
38
65
  export interface FsReadDeps {
39
66
  /** Get file/directory stats, or null if path doesn't exist. */
40
67
  stat: (path: string) => Promise<StatResult | null>;
41
- /** Read a file as text. */
68
+ /** Read a file as text. Throws if the file does not exist. */
42
69
  read_text_file: (path: string) => Promise<string>;
43
- /** Read a file as bytes. */
70
+ /** Read a file as bytes. Throws if the file does not exist. */
44
71
  read_file: (path: string) => Promise<Uint8Array>;
72
+ /**
73
+ * Read text starting from a byte offset. Throws if the file does not exist.
74
+ *
75
+ * Returns `content`, `bytes_read`, and `file_size` so callers can detect
76
+ * truncation (when `file_size < offset`) and tail incrementally without
77
+ * re-reading the whole file.
78
+ */
79
+ read_text_from_offset: (path: string, offset: number) => Promise<ReadTextFromOffsetResult>;
80
+ /** List directory entries (names, not full paths). Throws if the directory does not exist. */
81
+ readdir: (path: string) => Promise<Array<string>>;
45
82
  }
46
83
  /**
47
84
  * File system write operations.
@@ -71,8 +108,15 @@ export interface FsRemoveDeps {
71
108
  * Command execution.
72
109
  */
73
110
  export interface CommandDeps {
74
- /** Run a command and return the result. */
75
- run_command: (cmd: string, args: Array<string>) => Promise<CommandResult>;
111
+ /**
112
+ * Run a command and return the result. Never throws — failures surface as
113
+ * `success: false`.
114
+ *
115
+ * `options.cwd` sets the child's working directory. `options.signal` aborts
116
+ * the child when the signal fires. `options.timeout_ms` kills the child
117
+ * after the given duration and returns `timed_out: true` on the result.
118
+ */
119
+ run_command: (cmd: string, args: Array<string>, options?: RunCommandOptions) => Promise<CommandResult>;
76
120
  }
77
121
  /**
78
122
  * HTTP fetch capability.
@@ -1 +1 @@
1
- {"version":3,"file":"deps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/deps.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACvB,yCAAyC;IACzC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC9C,mCAAmC;IACnC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,+DAA+D;IAC/D,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,8DAA8D;IAC9D,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,+DAA+D;IAC/D,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACjD;;;;;;OAMG;IACH,qBAAqB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAC3F,8FAA8F;IAC9F,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;CAClD;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,0BAA0B;IAC1B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,4BAA4B;IAC5B,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,6BAA6B;IAC7B,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,4BAA4B;IAC5B,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9D;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,kCAAkC;IAClC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzE;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B;;;;;;;OAOG;IACH,WAAW,EAAE,CACZ,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EACnB,OAAO,CAAC,EAAE,iBAAiB,KACvB,OAAO,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,yDAAyD;IACzD,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACvB,6BAA6B;IAC7B,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,6BAA6B;IAC7B,YAAY,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,6CAA6C;IAC7C,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC3D;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,oCAAoC;IACpC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAChB,SACC,OAAO,EACP,UAAU,EACV,WAAW,EACX,YAAY,EACZ,WAAW,EACX,SAAS,EACT,YAAY,EACZ,WAAW,EACX,OAAO;IACR,qCAAqC;IACrC,OAAO,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC,qCAAqC;IACrC,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,qFAAqF;IACrF,mBAAmB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3E"}
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @module
9
9
  */
10
- import type { RuntimeDeps, CommandResult } from './deps.js';
10
+ import type { RuntimeDeps, CommandResult, RunCommandOptions } from './deps.js';
11
11
  /**
12
12
  * Mock `RuntimeDeps` with observable state for assertions.
13
13
  */
@@ -22,10 +22,11 @@ export interface MockRuntime extends RuntimeDeps {
22
22
  mock_dirs: Set<string>;
23
23
  /** Exit calls recorded (exit codes). */
24
24
  exit_calls: Array<number>;
25
- /** Commands executed. */
25
+ /** Commands executed. Captures `options` when passed so tests can assert cwd/timeout/signal. */
26
26
  command_calls: Array<{
27
27
  cmd: string;
28
28
  args: Array<string>;
29
+ options?: RunCommandOptions;
29
30
  }>;
30
31
  /** Commands executed with inherit. */
31
32
  command_inherit_calls: Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"mock.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/mock.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAc,aAAa,EAAC,MAAM,WAAW,CAAC;AAItE;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC/C,kCAAkC;IAClC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,0CAA0C;IAC1C,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,+CAA+C;IAC/C,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvC,mCAAmC;IACnC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,wCAAwC;IACxC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,yBAAyB;IACzB,aAAa,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;KAAC,CAAC,CAAC;IACzD,sCAAsC;IACtC,qBAAqB,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;KAAC,CAAC,CAAC;IACjE,8BAA8B;IAC9B,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,4CAA4C;IAC5C,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACjD,yCAAyC;IACzC,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,4BAA4B;IAC5B,WAAW,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,WAAW,CAAA;KAAC,CAAC,CAAC;IACxE,wDAAwD;IACxD,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CAC5C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAM,KAAK,CAAC,MAAM,CAAM,KAAG,WA4K9D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,WAAW,KAAG,IAazD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS,WAAW,EAAE,OAAO,MAAM,KAAG,IAEpE,CAAC;AAEF;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM;CAKxB"}
1
+ {"version":3,"file":"mock.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/mock.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAc,aAAa,EAAE,iBAAiB,EAAC,MAAM,WAAW,CAAC;AAIzF;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC/C,kCAAkC;IAClC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,0CAA0C;IAC1C,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,+CAA+C;IAC/C,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvC,mCAAmC;IACnC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,wCAAwC;IACxC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,gGAAgG;IAChG,aAAa,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,iBAAiB,CAAA;KAAC,CAAC,CAAC;IACtF,sCAAsC;IACtC,qBAAqB,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;KAAC,CAAC,CAAC;IACjE,8BAA8B;IAC9B,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,4CAA4C;IAC5C,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACjD,yCAAyC;IACzC,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,4BAA4B;IAC5B,WAAW,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,WAAW,CAAA;KAAC,CAAC,CAAC;IACxE,wDAAwD;IACxD,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CAC5C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAM,KAAK,CAAC,MAAM,CAAM,KAAG,WAkO9D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,WAAW,KAAG,IAazD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS,WAAW,EAAE,OAAO,MAAM,KAAG,IAEpE,CAAC;AAEF;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM;CAKxB"}
@@ -114,6 +114,55 @@ export const create_mock_runtime = (args = []) => {
114
114
  error.code = 'ENOENT';
115
115
  throw error;
116
116
  },
117
+ read_text_from_offset: async (path, offset) => {
118
+ let bytes;
119
+ const stored_bytes = mock_fs_bytes.get(path);
120
+ if (stored_bytes !== undefined) {
121
+ bytes = stored_bytes;
122
+ }
123
+ else {
124
+ const content = mock_fs.get(path);
125
+ if (content === undefined) {
126
+ const error = new Error(`ENOENT: no such file or directory: ${path}`);
127
+ error.code = 'ENOENT';
128
+ throw error;
129
+ }
130
+ bytes = new TextEncoder().encode(content);
131
+ }
132
+ const file_size = bytes.length;
133
+ const bytes_to_read = Math.max(0, file_size - offset);
134
+ if (bytes_to_read === 0)
135
+ return { content: '', bytes_read: 0, file_size };
136
+ const slice = bytes.subarray(offset, offset + bytes_to_read);
137
+ return {
138
+ content: new TextDecoder().decode(slice),
139
+ bytes_read: slice.length,
140
+ file_size,
141
+ };
142
+ },
143
+ readdir: async (path) => {
144
+ const prefix = path.endsWith('/') ? path : path + '/';
145
+ const seen = new Set();
146
+ const collect = (key) => {
147
+ if (!key.startsWith(prefix))
148
+ return;
149
+ const rest = key.slice(prefix.length);
150
+ const slash = rest.indexOf('/');
151
+ seen.add(slash === -1 ? rest : rest.slice(0, slash));
152
+ };
153
+ for (const key of mock_fs.keys())
154
+ collect(key);
155
+ for (const key of mock_fs_bytes.keys())
156
+ collect(key);
157
+ for (const key of mock_dirs)
158
+ collect(key);
159
+ if (seen.size === 0 && !mock_dirs.has(path)) {
160
+ const error = new Error(`ENOENT: no such file or directory: ${path}`);
161
+ error.code = 'ENOENT';
162
+ throw error;
163
+ }
164
+ return Array.from(seen).sort();
165
+ },
117
166
  write_text_file: async (path, content) => {
118
167
  mock_fs.set(path, content);
119
168
  },
@@ -163,13 +212,20 @@ export const create_mock_runtime = (args = []) => {
163
212
  throw new TypeError(`fetch failed (no mock for ${url})`);
164
213
  },
165
214
  // === Local Commands ===
166
- run_command: async (cmd, args) => {
167
- command_calls.push({ cmd, args });
215
+ run_command: async (cmd, args, options) => {
216
+ command_calls.push(options ? { cmd, args, options } : { cmd, args });
168
217
  const key = `${cmd} ${args.join(' ')}`;
169
218
  const mocked = mock_command_results.get(key);
170
- if (mocked)
219
+ if (mocked) {
220
+ if (options?.timeout_ms !== undefined && mocked.timed_out === undefined) {
221
+ return { ...mocked, timed_out: false };
222
+ }
171
223
  return mocked;
172
- return { success: true, code: 0, stdout: '', stderr: '' };
224
+ }
225
+ const result = { success: true, code: 0, stdout: '', stderr: '' };
226
+ if (options?.timeout_ms !== undefined)
227
+ result.timed_out = false;
228
+ return result;
173
229
  },
174
230
  run_command_inherit: async (cmd, args) => {
175
231
  command_inherit_calls.push({ cmd, args });
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/node.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAC,WAAW,EAA4B,MAAM,WAAW,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC/B,OAAM,aAAa,CAAC,MAAM,CAAyB,KACjD,WAkHD,CAAC"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/runtime/node.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAC,WAAW,EAA4B,MAAM,WAAW,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC/B,OAAM,aAAa,CAAC,MAAM,CAAyB,KACjD,WAmKD,CAAC"}
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Buffer } from 'node:buffer';
10
10
  import { spawn } from 'node:child_process';
11
- import { stat, mkdir, readFile, writeFile, rename, rm } from 'node:fs/promises';
11
+ import { stat, mkdir, readFile, readdir, writeFile, rename, rm, open } from 'node:fs/promises';
12
12
  import process from 'node:process';
13
13
  /**
14
14
  * Create a `RuntimeDeps` backed by Node.js APIs.
@@ -42,6 +42,27 @@ export const create_node_runtime = (args = process.argv.slice(2)) => ({
42
42
  },
43
43
  read_text_file: (path) => readFile(path, 'utf-8'),
44
44
  read_file: (path) => readFile(path).then((buf) => new Uint8Array(buf)),
45
+ read_text_from_offset: async (path, offset) => {
46
+ const s = await stat(path);
47
+ const file_size = s.size;
48
+ const bytes_to_read = Math.max(0, file_size - offset);
49
+ if (bytes_to_read === 0)
50
+ return { content: '', bytes_read: 0, file_size };
51
+ const handle = await open(path, 'r');
52
+ try {
53
+ const buffer = Buffer.alloc(bytes_to_read);
54
+ const { bytesRead } = await handle.read(buffer, 0, bytes_to_read, offset);
55
+ return {
56
+ content: buffer.toString('utf-8', 0, bytesRead),
57
+ bytes_read: bytesRead,
58
+ file_size,
59
+ };
60
+ }
61
+ finally {
62
+ await handle.close();
63
+ }
64
+ },
65
+ readdir: (path) => readdir(path),
45
66
  write_text_file: (path, content) => writeFile(path, content, 'utf-8'),
46
67
  write_file: (path, data) => writeFile(path, data),
47
68
  rename: (old_path, new_path) => rename(old_path, new_path),
@@ -49,17 +70,45 @@ export const create_node_runtime = (args = process.argv.slice(2)) => ({
49
70
  // === HTTP ===
50
71
  fetch: globalThis.fetch,
51
72
  // === Local Commands ===
52
- run_command: (cmd, args) => {
73
+ run_command: (cmd, args, options) => {
53
74
  return new Promise((resolve) => {
54
75
  const proc = spawn(cmd, args, {
55
76
  stdio: ['ignore', 'pipe', 'pipe'],
77
+ cwd: options?.cwd,
56
78
  });
57
79
  const stdout_chunks = [];
58
80
  const stderr_chunks = [];
81
+ let timed_out = false;
82
+ let done = false;
83
+ const finish = (result) => {
84
+ if (done)
85
+ return;
86
+ done = true;
87
+ if (timer !== null)
88
+ clearTimeout(timer);
89
+ if (options?.signal)
90
+ options.signal.removeEventListener('abort', on_abort);
91
+ resolve(result);
92
+ };
93
+ const on_abort = () => {
94
+ proc.kill();
95
+ };
96
+ const timer = options?.timeout_ms !== undefined
97
+ ? setTimeout(() => {
98
+ timed_out = true;
99
+ proc.kill();
100
+ }, options.timeout_ms)
101
+ : null;
102
+ if (options?.signal) {
103
+ if (options.signal.aborted)
104
+ proc.kill();
105
+ else
106
+ options.signal.addEventListener('abort', on_abort, { once: true });
107
+ }
59
108
  proc.stdout.on('data', (chunk) => stdout_chunks.push(chunk));
60
109
  proc.stderr.on('data', (chunk) => stderr_chunks.push(chunk));
61
110
  proc.on('error', (error) => {
62
- resolve({
111
+ finish({
63
112
  success: false,
64
113
  code: 1,
65
114
  stdout: '',
@@ -67,12 +116,15 @@ export const create_node_runtime = (args = process.argv.slice(2)) => ({
67
116
  });
68
117
  });
69
118
  proc.on('close', (code) => {
70
- resolve({
71
- success: code === 0,
119
+ const result = {
120
+ success: code === 0 && !timed_out,
72
121
  code: code ?? 1,
73
122
  stdout: Buffer.concat(stdout_chunks).toString('utf-8').trim(),
74
123
  stderr: Buffer.concat(stderr_chunks).toString('utf-8').trim(),
75
- });
124
+ };
125
+ if (options?.timeout_ms !== undefined)
126
+ result.timed_out = timed_out;
127
+ finish(result);
76
128
  });
77
129
  });
78
130
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",