@sheetxl/runner 0.8.0-beta.1 → 0.8.0-beta.10

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/README.md CHANGED
@@ -19,7 +19,19 @@ npm i -D @sheetxl/runner
19
19
  ## Using the runner
20
20
 
21
21
  ``` ts
22
- export type Runner = (workbook: IWorkbook, target: RunTarget | RunTarget[], options?: RunOptions) => Promise<void>;
22
+ export type Runner = (workbook: IWorkbook | null, args: ArgV | undefined, options: RunOptions) => Promise<IWorkbook | any | void>;
23
+ ```
24
+
25
+ The two data inputs are positional — the workbook and the args. Everything else is host wiring and
26
+ lives in `RunOptions`, `target` included.
27
+
28
+ `run` hands back whatever the script returned: a tally, a range, nothing. A returned `IWorkbook` is
29
+ also the workbook the run ended on — the one to write for `--save`/`--out` — so a host that needs it
30
+ reads it off the result:
31
+
32
+ ```ts
33
+ const result = await Runner.run(wb, args, { target: 'scripts/transform.ts' });
34
+ const ended = (result as IWorkbook)?.isIWorkbook ? result as IWorkbook : wb;
23
35
  ```
24
36
 
25
37
  ``` ts
@@ -28,8 +40,8 @@ import { Runner } from '@sheetxl/runner';
28
40
 
29
41
  const wb = new Workbook();
30
42
 
31
- await Runner.run(wb, 'scripts/transform.ts', {
32
- args: ['region=EU']
43
+ await Runner.run(wb, { raw: ['--region', 'EU'], kv: { region: 'EU' }, _: [] }, {
44
+ target: 'scripts/transform.ts'
33
45
  });
34
46
 
35
47
  ```
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license @sheetxl/runner - SheetXL - Programmatic execution for SheetXL scripts - v0.8.0-beta.1
2
+ * @license @sheetxl/runner - SheetXL - Programmatic execution for SheetXL scripts - v0.8.0-beta.10
3
3
  *
4
4
  * (C) 2026-present SheetXL Inc. & Michael T. Ford
5
5
  * License: The license can be found at https://www.sheetxl.com/license.
@@ -7,14 +7,14 @@
7
7
  import t from "node:path";
8
8
  import e from "node:fs";
9
9
  import { pathToFileURL as n } from "node:url";
10
- import { createLogChannels as r } from "@sheetxl/utils";
10
+ import { createLogChannels as r, resolveLogTimeout as s } from "@sheetxl/utils";
11
11
  var i = Object.defineProperty;
12
- function s(e, n = "SHEETXL_IN") {
12
+ function o(e, n = "SHEETXL_IN") {
13
13
  if (t.isAbsolute(e)) return e;
14
14
  const r = n ? process.env[n] : void 0;
15
15
  return t.resolve(r ?? process.cwd(), e);
16
16
  }
17
- var o = ((t, e) => {
17
+ var a = ((t, e) => {
18
18
  let n = {};
19
19
  for (var r in t) i(n, r, {
20
20
  get: t[r],
@@ -22,22 +22,23 @@ var o = ((t, e) => {
22
22
  });
23
23
  return e || i(n, Symbol.toStringTag, { value: "Module" }), n;
24
24
  })({
25
- DEFAULT_DESCRIPTION: () => g,
26
- classify: () => m,
27
- prepare: () => y
28
- }), a = null;
29
- function c() {
30
- return a || (a = (async () => {
25
+ DEFAULT_DESCRIPTION: () => v,
26
+ classify: () => w,
27
+ prepare: () => T,
28
+ toSnippetOffset: () => b
29
+ }), c = null;
30
+ function l() {
31
+ return c || (c = (async () => {
31
32
  try {
32
33
  const t = await import("typescript");
33
34
  return t.default ?? t;
34
35
  } catch {
35
36
  return null;
36
37
  }
37
- })()), a;
38
+ })()), c;
38
39
  }
39
- async function l(t) {
40
- const e = await c();
40
+ async function u(t) {
41
+ const e = await l();
41
42
  if (!e) return null;
42
43
  const n = e.createSourceFile("snippet.ts", t, e.ScriptTarget.Latest, !1, e.ScriptKind.TS);
43
44
  return (n.parseDiagnostics?.length ?? 0) > 0 ? null : {
@@ -45,154 +46,310 @@ async function l(t) {
45
46
  sf: n
46
47
  };
47
48
  }
48
- function u(t, e) {
49
- return t.text.slice(e.getStart(t), e.end);
50
- }
51
- function f(t) {
52
- return t.endsWith(";") ? t : `${t};`;
53
- }
54
49
  function p(t) {
55
50
  let e = 0;
56
51
  for (let n = 0; n < t.length; n++) "\n" === t[n] && e++;
57
52
  return e;
58
53
  }
59
- function d(t, e) {
60
- const n = e.getStart(t);
61
- let r = t.text.slice(n, e.end);
62
- const i = e.declarationList.declarations;
63
- for (let e = i.length - 1; e >= 0; e--) {
64
- const s = i[e].initializer;
65
- if (!s) continue;
66
- const o = s.getStart(t) - n, a = s.end - n;
67
- r = `${r.slice(0, o)}await (${r.slice(o, a)})${r.slice(a)}`;
54
+ function d(t, e, n) {
55
+ let r = n.getStart(e);
56
+ for (const s of n.declarationList.declarations) {
57
+ const n = s.initializer;
58
+ if (!n) continue;
59
+ const i = n.getStart(e);
60
+ t.copy(r, i), t.add("await ("), t.copy(i, n.end), t.add(")"), r = n.end;
61
+ }
62
+ t.copy(r, n.end), t.terminate();
63
+ }
64
+ var f = class {
65
+ constructor(t) {
66
+ this._code = t, this.spans = [], this._parts = [], this._length = 0;
67
+ }
68
+ add(t) {
69
+ this._parts.push(t), this._length += t.length;
70
+ }
71
+ copy(t, e) {
72
+ e <= t || (this.spans.push({
73
+ at: this._length,
74
+ from: t,
75
+ length: e - t
76
+ }), this.add(this._code.slice(t, e)));
77
+ }
78
+ terminate() {
79
+ this._parts[this._parts.length - 1]?.endsWith(";") || this.add(";");
80
+ }
81
+ toString() {
82
+ return this._parts.join("");
83
+ }
84
+ }, m = /* @__PURE__ */ new Set([
85
+ "log",
86
+ "warn",
87
+ "error"
88
+ ]), h = "await wb?.whenCalculationReady?.(); ";
89
+ function g(t, e) {
90
+ const n = e.statements;
91
+ let r = n.length - 1;
92
+ for (; r >= 0 && n[r].kind === t.SyntaxKind.EmptyStatement;) r--;
93
+ if (r < 0) return null;
94
+ const s = n[r];
95
+ if ((t.isExpressionStatement(s) || t.isExportAssignment(s) && !s.isExportEquals) && ((e) => {
96
+ for (; t.isParenthesizedExpression(e);) e = e.expression;
97
+ return t.isArrowFunction(e) || t.isFunctionExpression(e);
98
+ })(s.expression)) return {
99
+ index: r,
100
+ from: s.expression.getStart(e),
101
+ to: s.expression.end
102
+ };
103
+ if (t.isFunctionDeclaration(s) && s.body) {
104
+ const n = t.getModifiers(s) ?? [], i = n.some((e) => e.kind === t.SyntaxKind.ExportKeyword);
105
+ if (n.some((e) => e.kind === t.SyntaxKind.DefaultKeyword) && !s.name || !i && function(t, e) {
106
+ let n = -1;
107
+ for (let r = 0; r < e.statements.length; r++) {
108
+ const s = e.statements[r];
109
+ if (s.kind !== t.SyntaxKind.EmptyStatement && !t.isImportDeclaration(s) && !t.isImportEqualsDeclaration(s)) {
110
+ if (-1 !== n) return -1;
111
+ n = r;
112
+ }
113
+ }
114
+ return n;
115
+ }(t, e) === r) {
116
+ let i = s.getStart(e);
117
+ for (const e of n) e.kind !== t.SyntaxKind.ExportKeyword && e.kind !== t.SyntaxKind.DefaultKeyword || (i = e.end);
118
+ for (; i < s.end && /\s/.test(e.text[i]);) i++;
119
+ return {
120
+ index: r,
121
+ from: i,
122
+ to: s.end
123
+ };
124
+ }
68
125
  }
69
- return f(r);
126
+ return null;
70
127
  }
71
- async function m(t) {
72
- const e = await l(t);
73
- return e ? function(t, e) {
128
+ var y = "const __entry: (ctx: SheetXL.IScript.ScriptContext) => unknown = (", x = (t) => `); ${t} = await __entry(ctx);`;
129
+ async function w(t) {
130
+ const e = await u(t);
131
+ if (!e) return function(t) {
132
+ return /^\s*export\s+default\b/m.test(t) || /^\s*export\s*\{[^}]*\bas\s+default\b/m.test(t);
133
+ }(t) ? "script" : "snippet";
134
+ const { ts: n, sf: r } = e;
135
+ return function(t, e) {
74
136
  for (const n of e.statements) {
75
137
  if (t.isExportAssignment(n) && !n.isExportEquals) return !0;
76
138
  if (t.canHaveModifiers(n) && t.getModifiers(n)?.some((e) => e.kind === t.SyntaxKind.DefaultKeyword)) return !0;
77
139
  if (t.isExportDeclaration(n) && n.exportClause && t.isNamedExports(n.exportClause) && n.exportClause.elements.some((t) => "default" === t.name.text)) return !0;
78
140
  }
79
141
  return !1;
80
- }(e.ts, e.sf) ? "script" : "snippet" : function(t) {
81
- return /^\s*export\s+default\b/m.test(t) || /^\s*export\s*\{[^}]*\bas\s+default\b/m.test(t);
82
- }(t) ? "script" : "snippet";
142
+ }(n, r) && !function(t, e) {
143
+ const n = g(t, e);
144
+ return !!n && e.statements.every((e, r) => r === n.index || !t.isExportAssignment(e) && !t.isExportDeclaration(e) && !(t.canHaveModifiers(e) && t.getModifiers(e)?.some((e) => e.kind === t.SyntaxKind.ExportKeyword)));
145
+ }(n, r) ? "script" : "snippet";
83
146
  }
84
- var w = 3, g = "Run Snippet", h = (t, e, n = [], r) => [
85
- ...n,
86
- ...r ? [r] : [],
87
- "export default async function __snippet(ctx: SheetXL.IScript.ScriptContext): Promise<void> {",
88
- " const { wb, log, warn, error, args } = ctx as any;",
89
- " let __result: any = undefined;",
90
- t,
91
- " ;",
92
- e,
93
- "};"
94
- ].join("\n");
95
- async function y(t, e) {
96
- const n = e?.epilogue ?? " return __result;", r = await l(t);
147
+ var S = 3, v = "Run Snippet", E = (t, e, n = [], r) => {
148
+ const s = [
149
+ ...n,
150
+ ...r ? [r] : [],
151
+ "export default async function __snippet(ctx: SheetXL.IScript.ScriptContext): Promise<void> {",
152
+ " const { wb, log, warn, error, args } = ctx;",
153
+ " let __result: any = undefined;"
154
+ ].join("\n") + "\n";
155
+ return {
156
+ source: s + [
157
+ t,
158
+ " ;",
159
+ e,
160
+ "};"
161
+ ].join("\n"),
162
+ bodyStart: s.length
163
+ };
164
+ };
165
+ async function T(t, e) {
166
+ const n = e?.epilogue ?? " return __result;", r = await u(t);
97
167
  if (!r) {
98
- const e = await c() ? void 0 : "TypeScript is unavailable, so this snippet ran without implicit await or a result value. Install the `typescript` peer dependency to restore them.";
168
+ const e = await l() ? void 0 : "TypeScript is unavailable, so this snippet ran without implicit await or a result value. Install the `typescript` peer dependency to restore them.", { source: r, bodyStart: s } = E(t, n);
99
169
  return {
100
- source: h(t, n),
170
+ source: r,
101
171
  kind: "verbatim",
102
- lineOffset: w,
172
+ lineOffset: S,
173
+ spans: [{
174
+ at: s,
175
+ from: 0,
176
+ length: t.length
177
+ }],
178
+ expectsValue: !1,
103
179
  warning: e
104
180
  };
105
181
  }
106
- const { ts: i, sf: s } = r, o = s.statements, a = function(t, e) {
182
+ const { ts: s, sf: i } = r, o = i.statements, a = function(t, e) {
107
183
  if (!e.statements[0]) return;
108
184
  const n = t.getLeadingCommentRanges(e.text, 0);
109
185
  if (n?.length) for (let r = n.length - 1; r >= 0; r--) {
110
- const i = n[r];
111
- if (i.kind !== t.SyntaxKind.MultiLineCommentTrivia) continue;
112
- const s = e.text.slice(i.pos, i.end);
113
- if (s.startsWith("/**")) return s;
186
+ const s = n[r];
187
+ if (s.kind !== t.SyntaxKind.MultiLineCommentTrivia) continue;
188
+ const i = e.text.slice(s.pos, s.end);
189
+ if (i.startsWith("/**")) return i;
114
190
  }
115
- }(i, s), m = a ? p(a) + 1 : 0;
116
- if (1 === o.length && i.isBlock(o[0])) return {
117
- source: h(` __result = await (\n${t}\n );`, n, [], a),
118
- kind: "expression",
119
- lineOffset: w + m + 1
120
- };
121
- const { body: g, hoisted: y } = function(t, e, n) {
122
- const r = e.text, i = e.statements;
123
- let s = -1;
124
- for (let e = i.length - 1; e >= 0; e--) if (i[e].kind !== t.SyntaxKind.EmptyStatement) {
125
- t.isExpressionStatement(i[e]) && (s = e);
191
+ }(s, i), c = a ? p(a) + 1 : 0;
192
+ if (1 === o.length && s.isBlock(o[0])) {
193
+ const { source: r, bodyStart: s } = E(` __result = await (
194
+ ${t}\n );`, n, [], a);
195
+ return {
196
+ source: r,
197
+ kind: "expression",
198
+ lineOffset: S + c + 1,
199
+ spans: [{
200
+ at: s + 21,
201
+ from: 0,
202
+ length: t.length
203
+ }],
204
+ expectsValue: !0
205
+ };
206
+ }
207
+ const { body: w, spans: v, hoisted: T, expectsValue: b } = function(t, e, n) {
208
+ const r = e.text, s = e.statements;
209
+ let i = -1;
210
+ for (let e = s.length - 1; e >= 0; e--) if (s[e].kind !== t.SyntaxKind.EmptyStatement) {
211
+ t.isExpressionStatement(s[e]) && (i = e);
126
212
  break;
127
213
  }
128
- const o = [], a = [];
129
- let c = 0;
130
- for (let l = 0; l < i.length; l++) {
131
- const m = i[l], w = m.getStart(e);
132
- a.push(r.slice(c, w)), c = m.end;
133
- const g = r.slice(w, m.end);
134
- if (t.isImportDeclaration(m) || t.isImportEqualsDeclaration(m)) o.push(g), a.push("\n".repeat(p(g)));
135
- else if (t.isVariableStatement(m)) a.push(d(e, m));
136
- else {
137
- if (t.isExpressionStatement(m)) {
138
- const t = u(e, m.expression);
139
- a.push(l === s ? `${n} = await (${t});` : `await (${t});`);
140
- continue;
141
- }
142
- a.push(f(g));
214
+ const o = g(t, e);
215
+ let a = !1;
216
+ if (!o && -1 !== i) {
217
+ const e = s[i].expression;
218
+ a = !(t.isCallExpression(e) && t.isIdentifier(e.expression) && m.has(e.expression.text));
219
+ }
220
+ const c = [], l = new f(r);
221
+ let u = 0, w = !1;
222
+ for (let a = 0; a < s.length; a++) {
223
+ const f = s[a], m = f.getStart(e);
224
+ if (l.copy(u, m), u = f.end, t.isImportDeclaration(f) || t.isImportEqualsDeclaration(f)) {
225
+ const t = r.slice(m, f.end);
226
+ c.push({
227
+ text: t,
228
+ from: m
229
+ }), l.add("\n".repeat(p(t)));
230
+ continue;
143
231
  }
232
+ w && l.add(h), w = !0, o?.index !== a ? t.isVariableStatement(f) ? d(l, e, f) : t.isExpressionStatement(f) ? (l.add(a === i ? `${n} = await (` : "await ("), l.copy(f.expression.getStart(e), f.expression.end), l.add(");")) : (l.copy(m, f.end), l.terminate()) : (l.add(y), l.copy(o.from, o.to), l.add(x(n)));
144
233
  }
145
- return a.push(r.slice(c)), {
146
- body: a.join(""),
147
- hoisted: o
234
+ return l.copy(u, r.length), {
235
+ body: l.toString(),
236
+ spans: l.spans,
237
+ hoisted: c,
238
+ expectsValue: a
148
239
  };
149
- }(i, s, "__result"), x = y.length > 0 ? p(y.join("\n")) + y.length : 0;
240
+ }(s, i, "__result"), _ = T.map((t) => t.text), k = T.length > 0 ? p(_.join("\n")) + T.length : 0, { source: D, bodyStart: K } = E(w, n, _, a);
241
+ let R = 0;
242
+ const I = T.map((t) => {
243
+ const e = {
244
+ at: R,
245
+ from: t.from,
246
+ length: t.text.length
247
+ };
248
+ return R += t.text.length + 1, e;
249
+ });
150
250
  return {
151
- source: h(g, n, y, a),
251
+ source: D,
152
252
  kind: "statements",
153
- lineOffset: w + x + m
253
+ lineOffset: S + k + c,
254
+ spans: [...I, ...v.map((t) => ({
255
+ ...t,
256
+ at: t.at + K
257
+ }))],
258
+ expectsValue: b
154
259
  };
155
260
  }
156
- async function x(t, e, n = "Runner timed out") {
261
+ function b(t, e) {
262
+ let n = null;
263
+ for (const r of t.spans) {
264
+ if (r.at > e) return null !== n && e < n.at + n.length ? n.from + (e - n.at) : r.from;
265
+ n = r;
266
+ }
267
+ return n ? n.from + Math.min(e - n.at, n.length) : 0;
268
+ }
269
+ var _ = /* @__PURE__ */ new Set(["TS2339", "TS2551"]), k = /* @__PURE__ */ new Set([
270
+ ..._,
271
+ "TS2304",
272
+ "TS2552",
273
+ "TS2448",
274
+ "TS2588",
275
+ "TS2531",
276
+ "TS2532",
277
+ "TS2533",
278
+ "TS18047",
279
+ "TS18048",
280
+ "TS18049",
281
+ "TS2349",
282
+ "TS2351",
283
+ "TS2721",
284
+ "TS2722",
285
+ "TS2723"
286
+ ]), D = /on type '(never|\{\})'/;
287
+ function K(t) {
288
+ return void 0 === t || "TypeError" === t || "ReferenceError" === t;
289
+ }
290
+ async function R(t, e) {
291
+ try {
292
+ const n = "snippet" === await w(t) ? await T(t) : null;
293
+ if ("undefined" === e && !n?.expectsValue) return [];
294
+ const [{ ScriptCompiler: r }, { getScriptingDeclarations: s }] = await Promise.all([import("@sheetxl/scripting"), import("@sheetxl/sdk")]), { diagnostics: i } = await r.compile({
295
+ source: n?.source ?? t,
296
+ ambientTypes: await s(),
297
+ lenient: !0,
298
+ disableBundle: !0,
299
+ disableSourceMap: !0
300
+ }), o = "threw" === e ? k : _;
301
+ return i.filter((t) => "error" === t.severity && o.has(t.code) && !(_.has(t.code) && D.test(t.message))).map((t) => ({
302
+ ...t,
303
+ start: n ? b(n, t.start) : t.start,
304
+ ..."undefined" === e ? {
305
+ severity: "warning",
306
+ message: `${t.message} It reads as undefined.`
307
+ } : {}
308
+ }));
309
+ } catch {
310
+ return [];
311
+ }
312
+ }
313
+ async function I(t, e, n = "Runner timed out") {
157
314
  if (!e || e <= 0) return t;
158
315
  let r;
159
- const i = new Promise((t, i) => r = setTimeout(() => i(new Error(n)), e));
316
+ const s = new Promise((t, s) => r = setTimeout(() => s(new Error(n)), e));
160
317
  try {
161
- return await Promise.race([t, i]);
318
+ return await Promise.race([t, s]);
162
319
  } finally {
163
320
  clearTimeout(r);
164
321
  }
165
322
  }
166
- var S = null;
167
- function v() {
168
- return S || (S = (async () => {
323
+ var C = null;
324
+ function $() {
325
+ return C || (C = (async () => {
169
326
  const { register: t } = await import("tsx/esm/api");
170
327
  t({ tsconfig: !1 });
171
- })()), S;
328
+ })()), C;
172
329
  }
173
- var b = "data:text/typescript,", $ = "data:text/javascript,", E = null;
174
- function _() {
175
- return E || (E = (async () => {
176
- const { registerHooks: e, stripTypeScriptTypes: r } = await import("node:module"), i = r;
330
+ var L = "data:text/typescript,", M = "data:text/javascript,", W = null;
331
+ function P() {
332
+ return W || (W = (async () => {
333
+ const { registerHooks: e, stripTypeScriptTypes: r } = await import("node:module"), s = r;
177
334
  e({
178
- resolve(e, r, i) {
179
- const s = r.parentURL;
180
- if (s && (s.startsWith(b) || s.startsWith($))) {
181
- const s = n(t.join(process.cwd(), "_inline_.ts")).href;
182
- return i(e, {
335
+ resolve(e, r, s) {
336
+ const i = r.parentURL;
337
+ if (i && (i.startsWith(L) || i.startsWith(M))) {
338
+ const i = n(t.join(process.cwd(), "_inline_.ts")).href;
339
+ return s(e, {
183
340
  ...r,
184
- parentURL: s
341
+ parentURL: i
185
342
  });
186
343
  }
187
- return i(e, r);
344
+ return s(e, r);
188
345
  },
189
346
  load(t, e, n) {
190
- if (t.startsWith(b)) {
347
+ if (t.startsWith(L)) {
191
348
  const e = decodeURIComponent(t.slice(21)), n = process.emitWarning;
192
349
  let r;
193
350
  process.emitWarning = () => {};
194
351
  try {
195
- r = i(e, { mode: "transform" });
352
+ r = s(e, { mode: "transform" });
196
353
  } finally {
197
354
  process.emitWarning = n;
198
355
  }
@@ -205,25 +362,25 @@ function _() {
205
362
  return n(t, e);
206
363
  }
207
364
  });
208
- })()), E;
365
+ })()), W;
209
366
  }
210
- async function R(e) {
211
- const r = "string" == typeof (i = e) ? i.startsWith("file:") ? i : n(t.resolve(process.cwd(), i)).href : i.href;
212
- var i;
213
- const s = t.extname(new URL(r).pathname).toLowerCase();
214
- return ".ts" !== s && ".tsx" !== s || await v(), import(r);
367
+ async function A(e) {
368
+ const r = "string" == typeof (s = e) ? s.startsWith("file:") ? s : n(t.resolve(process.cwd(), s)).href : s.href;
369
+ var s;
370
+ const i = t.extname(new URL(r).pathname).toLowerCase();
371
+ return ".ts" !== i && ".tsx" !== i || await $(), import(r);
215
372
  }
216
- async function k(t, e) {
217
- const n = e.notifier ?? (await import("@sheetxl/sdk")).Notifier, i = r({
218
- debug: (t) => n.debug(t),
219
- log: (t) => n.log(t),
220
- info: (t) => n.log(t),
221
- warn: (t) => n.warn(t),
222
- error: (t) => n.error(t)
223
- }, { render: e.render });
373
+ async function U(t, e, n) {
374
+ const s = n.notifier ?? (await import("@sheetxl/sdk")).Notifier, i = r({
375
+ debug: (t) => s.debug(t),
376
+ log: (t) => s.log(t),
377
+ info: (t) => s.log(t),
378
+ warn: (t) => s.warn(t),
379
+ error: (t) => s.error(t)
380
+ }, { render: n.render });
224
381
  return {
225
382
  ctx: {
226
- args: e.args ?? {
383
+ args: e ?? {
227
384
  raw: [],
228
385
  kv: {},
229
386
  _: []
@@ -234,56 +391,60 @@ async function k(t, e) {
234
391
  error: i.error
235
392
  },
236
393
  channels: i,
237
- notifier: n
394
+ notifier: s
238
395
  };
239
396
  }
240
- var C = { run: async (n, r, i = {}) => {
241
- const s = Array.isArray(r) ? r : [r], o = await Promise.all(s.map((n) => (async (n, r = {}) => {
242
- let i = null;
243
- const s = (t, e) => {
397
+ var j = async (n, r, i) => {
398
+ const o = i.target, a = Array.isArray(o) ? o : [o], c = await Promise.all(a.map((n) => (async (n, r = {}) => {
399
+ let s = null;
400
+ const i = (t, e) => {
244
401
  let n = t?.default;
245
402
  if (n && "function" != typeof n && "function" == typeof n.default && (n = n.default), "function" != typeof n) throw new Error(`Script '${e}' has no runnable entry: export a default function, e.g. \`export default function main(ctx) { … }\`.`);
246
403
  return n;
247
404
  };
248
- if ("function" == typeof n) i = n;
405
+ if ("function" == typeof n) s = n;
249
406
  else if ("string" == typeof n || n instanceof URL) {
250
407
  let o = null;
251
- if ("string" != typeof n || /^[a-z]+:\/\//i.test(n)) o = await R(n);
408
+ if ("string" != typeof n || /^[a-z]+:\/\//i.test(n)) o = await A(n);
252
409
  else {
253
- const i = t.resolve(r.cwd ?? process.cwd(), n);
254
- if (!e.existsSync(i)) throw new Error(`Script not found: ${i}`);
255
- e.accessSync(i, e.constants.R_OK), o = await R(i);
410
+ const s = t.resolve(r.cwd ?? process.cwd(), n);
411
+ if (!e.existsSync(s)) throw new Error(`Script not found: ${s}`);
412
+ e.accessSync(s, e.constants.R_OK), o = await A(s);
256
413
  }
257
- i = s(o, String(n));
414
+ s = i(o, String(n));
258
415
  } else if ("code" in n) {
259
416
  const t = n.filename ?? "inline.ts";
260
417
  let e, r = null;
261
- t.endsWith(".ts") || t.endsWith(".tsx") ? (await v(), await _(), e = `${b}${encodeURIComponent(n.code)}`, r = await import(e)) : (await _(), e = `${$}${encodeURIComponent(n.code)}`, r = await import(e)), i = s(r, t);
418
+ t.endsWith(".ts") || t.endsWith(".tsx") ? (await $(), await P(), e = `${L}${encodeURIComponent(n.code)}`, r = await import(e)) : (await P(), e = `${M}${encodeURIComponent(n.code)}`, r = await import(e)), s = i(r, t);
262
419
  } else {
263
420
  if (!("runner" in n)) throw new Error("Invalid run target specified.");
264
- i = n.runner;
421
+ s = n.runner;
265
422
  }
266
- return i;
267
- })(n, i))), a = Date.now();
268
- let c = n, l = null;
269
- const u = o.length;
270
- for (let t = 0; t < u; t++) {
271
- const e = o[t], { ctx: n, channels: r, notifier: s } = await k(c, i);
272
- l = s;
273
- const a = Date.now();
274
- let f;
423
+ return s;
424
+ })(n, i))), l = Date.now();
425
+ let u, p = n, d = null;
426
+ const f = c.length;
427
+ for (let t = 0; t < f; t++) {
428
+ const e = c[t], { ctx: n, channels: o, notifier: a } = await U(p, r, i);
429
+ d = a;
430
+ const l = Date.now();
431
+ let m;
432
+ const h = p, g = () => Promise.resolve().then(() => e(n));
275
433
  try {
276
- f = await x(Promise.resolve().then(() => e(n)), i.timeoutMs, "Runner timed out");
434
+ m = await I(null !== h ? Promise.resolve(h.doBatch(g, "Run Script")) : g(), i.timeoutMs, "Runner timed out");
277
435
  } finally {
278
- await r.flush();
436
+ await o.flush({ timeoutMs: s(i.logTimeoutMs) });
279
437
  }
280
- const p = Date.now();
281
- null != f && f.isIWorkbook && (c = f), s.debug(`Run ${1 === u ? "" : `${t + 1}/${u} `}completed in ${p - a} ms.`);
438
+ const y = Date.now();
439
+ u = m, null != m && m.isIWorkbook && (p = m), a.debug(`Run ${1 === f ? "" : `${t + 1}/${f} `}completed in ${y - l} ms.`);
282
440
  }
283
- if (u > 1 && l) {
441
+ if (f > 1 && d) {
284
442
  const t = Date.now();
285
- l.debug(`All runs completed in ${t - a} ms.`);
443
+ d.debug(`All runs completed in ${t - l} ms.`);
286
444
  }
287
- return c ?? void 0;
445
+ return u;
446
+ }, O = { run: async (t, e, n) => {
447
+ const { withScriptRuntime: r } = await import("@sheetxl/sdk");
448
+ return r(() => j(t, e, n));
288
449
  } };
289
- export { o as Snippet, C as default, s as resolvePath };
450
+ export { a as Snippet, O as default, R as diagnoseRun, K as isExplainable, o as resolvePath };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sheetxl/runner",
3
3
  "description": "SheetXL - Programmatic execution for SheetXL scripts",
4
- "version": "0.8.0-beta.1",
4
+ "version": "0.8.0-beta.10",
5
5
  "license": "SEE LICENSE IN <LICENSE>",
6
6
  "type": "module",
7
7
  "exports": {
@@ -36,18 +36,18 @@
36
36
  "runner"
37
37
  ],
38
38
  "dependencies": {
39
- "@sheetxl/io": "^0.8.0-beta.1",
40
- "@sheetxl/primitives": "^0.8.0-beta.1",
41
- "@sheetxl/scripting": "^0.8.0-beta.1",
42
- "@sheetxl/sdk": "^0.8.0-beta.1",
43
- "@sheetxl/utils": "^0.8.0-beta.1"
39
+ "@sheetxl/io": "^0.8.0-beta.10",
40
+ "@sheetxl/primitives": "^0.8.0-beta.10",
41
+ "@sheetxl/scripting": "^0.8.0-beta.10",
42
+ "@sheetxl/sdk": "^0.8.0-beta.10",
43
+ "@sheetxl/utils": "^0.8.0-beta.10"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "typescript": "^5.9.0 || ^6.0.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@types/node": "^26.1.2",
50
- "tsx": "^4.23.1",
49
+ "@types/node": "^26.6.4",
50
+ "tsx": "^4.23.15",
51
51
  "typescript": "^5.9.0 || ^6.0.0"
52
52
  }
53
53
  }
@@ -0,0 +1,33 @@
1
+ import { Diagnostic } from '@sheetxl/scripting';
2
+ /**
3
+ * What a run did that a type check may explain: it threw, or a snippet's final expression came
4
+ * back `undefined`.
5
+ *
6
+ * @remarks
7
+ * `undefined` is a symptom because the SheetXL API never returns it - `null` is the empty answer.
8
+ * A read that produced `undefined` read something that is not there.
9
+ */
10
+ export type RunSymptom = 'threw' | 'undefined';
11
+ /**
12
+ * Whether a failure is one a type check can explain, by the error's `name`. A failure with no
13
+ * name (a thrown string, an error that crossed a wire as its message) is checked too.
14
+ */
15
+ export declare function isExplainable(errorName: string | undefined): boolean;
16
+ /**
17
+ * Type-checks the source of a run that has already gone wrong, and returns the findings that can
18
+ * explain it.
19
+ *
20
+ * @remarks
21
+ * A run is never type-checked before it executes: the types are for discovery, not enforcement,
22
+ * and most of what a strict check reports about code written to run once (an unannotated `[]`,
23
+ * `catch (e)`, a field added to `{}`) runs correctly. A failed run leaves nothing behind, so
24
+ * running first costs nothing, and the check is paid only by a run with a symptom.
25
+ *
26
+ * The one implementation behind `sheetxl -e` and the MCP `run` tool, so both answer alike.
27
+ *
28
+ * Positions are in `source` as given - a snippet is checked in its wrapper and mapped back.
29
+ * A `threw` finding is an error; an `undefined` one is a warning, as the run completed.
30
+ * Never throws: a diagnosis that fails must not replace the failure it was diagnosing.
31
+ */
32
+ export declare function diagnoseRun(source: string, symptom: RunSymptom): Promise<Diagnostic[]>;
33
+ //# sourceMappingURL=RunDiagnosis.d.ts.map
@@ -1,14 +1,12 @@
1
1
  import { Runner } from './Types';
2
- /** Core: run targets as a serial pipeline, each receiving the current workbook.
2
+ /**
3
+ * Runs the pipeline with the `SheetXL` global installed.
3
4
  *
4
- * Pipeline semantics:
5
- * - Targets run **serially** in order, never in parallel.
6
- * - Each target receives a fresh `RunContext` built with the **current** workbook.
7
- * - If a target returns an `IWorkbook`, that becomes the workbook for all subsequent targets.
8
- * - `args` are shared across all targets — they represent pipeline-level configuration,
9
- * not per-step input. This follows the standard Unix convention where environment
10
- * variables / flags are visible to every process in a pipeline.
11
- * - Returns the final workbook (which may differ from the one passed in).
5
+ * @remarks
6
+ * The declarations a script is written against promise `SheetXL.ISheet.Visibility.Hidden` is a
7
+ * VALUE. A compiled macro gets it from the macro runtime; source run here is type-stripped and
8
+ * imported, so nothing would define it. The install spans module LOADING too, not only the call —
9
+ * a script may read an enum at its top level — and is removed however the run ends.
12
10
  */
13
11
  export declare const run: Runner;
14
12
  declare const _default: {
@@ -9,7 +9,16 @@
9
9
  * contract promises two things a bare paste into an async function does not give them —
10
10
  * 1. **implicit await**: nothing in a snippet is ever written `await`, yet a promise-returning
11
11
  * statement must have settled before the next one runs;
12
- * 2. **the final expression is the result**: exactly like a REPL, or `node -p`.
12
+ * 2. **the final expression is the result**: exactly like a REPL, or `node -p`;
13
+ * 3. **values are current**: calculation is asynchronous, so a formula set by one statement is
14
+ * settled before the next statement runs — `setFormula('=1+1')` then `getValue()` reads `2`,
15
+ * the way it reads in Excel, rather than the `null` a bare paste would see.
16
+ *
17
+ * **A snippet that is a function is called.** Its value is the function — `async ({ wb }) => …` as
18
+ * the final expression, or a snippet that is only `function main(ctx) { … }` — so it is called with
19
+ * the context, typed as a script's entry would be, and its return is the result. `export default` in
20
+ * front of an arrow or an anonymous function, with nothing else exported, is ceremony for the same
21
+ * thing and runs the same way: the script form is kept for what needs it, a named entry or exports.
13
22
  *
14
23
  * This is the same problem Node's REPL solves in `lib/internal/repl/await.js`, and it is solved the
15
24
  * same way: **parse for real, then rewrite the top-level statements from the AST.** Statement
@@ -32,9 +41,21 @@
32
41
  * context parameter) so the identical source compiles on every path: the CLI's type-stripping
33
42
  * loader ignores the annotations, and the `ScriptCompiler`'s linter requires them.
34
43
  */
44
+ /**
45
+ * A run of the author's own text inside an emitted source: `length` characters that sit at `at`
46
+ * in the emitted text and came from `from` in the snippet.
47
+ */
48
+ export interface Span {
49
+ at: number;
50
+ from: number;
51
+ length: number;
52
+ }
35
53
  /** How a source will run — see the module remarks for the contract. */
36
54
  export type SourceForm = 'script' | 'snippet';
37
- /** `script` when the source has a top-level default export; `snippet` otherwise. */
55
+ /**
56
+ * `script` when the source has a top-level default export; `snippet` otherwise — and when that
57
+ * export is only an entry function (`export default async ({ wb }) => …`), which runs as a snippet.
58
+ */
38
59
  export declare function classify(code: string): Promise<SourceForm>;
39
60
  export interface WrapOptions {
40
61
  /**
@@ -55,11 +76,19 @@ export interface Prepared {
55
76
  */
56
77
  kind: 'expression' | 'statements' | 'verbatim';
57
78
  /**
58
- * Lines of wrapper prologue before the first line of user code — subtract from a compiler
59
- * diagnostic's line to point back into the snippet. Exact for every kind: the rewrite emits the
60
- * original trivia, so no line moves.
79
+ * Lines of wrapper prologue before the first line of user code. Exact for every kind: the
80
+ * rewrite emits the original trivia, so no line moves. It places LINES only — to point a
81
+ * compiler position back into the snippet, line and column, use {@link toSnippetOffset}.
61
82
  */
62
83
  lineOffset: number;
84
+ /** Where the author's text sits within {@link source}, in source order. */
85
+ spans: Span[];
86
+ /**
87
+ * Whether the snippet ends in an expression whose value is its answer — not a statement, not a
88
+ * call to `log`/`warn`/`error`, and not an entry function, which may return nothing as a script
89
+ * may. Such a snippet answering `undefined` read something that is not there.
90
+ */
91
+ expectsValue: boolean;
63
92
  /**
64
93
  * Set only when the contract could not be honored — no implicit await, no result. Callers should
65
94
  * surface it; a snippet quietly returning `undefined` is worse than one that failed.
@@ -86,4 +115,14 @@ export declare const DEFAULT_DESCRIPTION = "Run Snippet";
86
115
  * sidesteps any position mapping — the whole snippet is the expression.
87
116
  */
88
117
  export declare function prepare(code: string, options?: WrapOptions): Promise<Prepared>;
118
+ /**
119
+ * Where an offset into a prepared {@link Prepared.source} sits in the snippet the author wrote.
120
+ *
121
+ * @remarks
122
+ * Exact wherever the offset falls in the author's own text, which is where a compiler puts a
123
+ * diagnostic about it. An offset in text the wrapper added has no place in the snippet, so it
124
+ * answers with the nearest thing that does: the author's next character, or the end of their last
125
+ * text when nothing of theirs follows.
126
+ */
127
+ export declare function toSnippetOffset(prepared: Prepared, offset: number): number;
89
128
  //# sourceMappingURL=Snippet.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { IWorkbook } from '@sheetxl/sdk';
2
- import { LogRenderer } from '@sheetxl/utils';
2
+ import { LogRenderer, MaybePromise } from '@sheetxl/utils';
3
3
  export type Notifier = typeof import('@sheetxl/sdk').Notifier;
4
4
  /**
5
5
  * Arguments passed after `--` on the CLI (`sheetxl -f <script> [workbook] -- [args...]`).
@@ -48,6 +48,9 @@ export type RunContext = {
48
48
  * Variadic and value-taking, like `console.log`: `log('rows:', n)` and `log(range.getStats())`
49
49
  * both work. An SDK object renders as its markdown report when the host supplies a renderer
50
50
  * (the CLI does); otherwise it falls back to JSON.
51
+ *
52
+ * A promise is awaited and logs as its settled value, so `log(sheet.evaluate(…))` needs no
53
+ * `await`. Output order is still call order.
51
54
  */
52
55
  readonly log: (...args: any[]) => void;
53
56
  /** Diagnostics: something surprising but survivable. Travels separately from `log` (stderr). */
@@ -57,8 +60,19 @@ export type RunContext = {
57
60
  };
58
61
  /**
59
62
  * The function signature for a script run by the CLI or the MCP `run` tool.
63
+ *
64
+ * @remarks
65
+ * The return value is the run's RESULT, the same contract a snippet's final expression has, so it
66
+ * is deliberately unconstrained — a tally, an `ICellRange`, a `getStats()` object. Returning
67
+ * nothing is normal: a run whose output is its `log` has no answer to hand back.
68
+ *
69
+ * An `IWorkbook` is the one return that is also *structural*: it becomes the workbook the next
70
+ * stage runs against, on top of being the result. Nothing else is inspected — an array is just an
71
+ * array.
72
+ *
73
+ * Sync or async, both real: `function main(ctx): number[]` is as valid as the `async` spelling.
60
74
  */
61
- export type RunFunction = (ctx: RunContext) => Promise<void | IWorkbook>;
75
+ export type RunFunction = (ctx: RunContext) => MaybePromise<IWorkbook | any | void>;
62
76
  /**
63
77
  * Possible targets for the runner to execute
64
78
  */
@@ -69,7 +83,17 @@ export type RunTarget = string | URL | {
69
83
  runner: RunFunction;
70
84
  };
71
85
  export interface RunOptions {
72
- args?: ArgV;
86
+ /**
87
+ * What to run — a path, a URL, inline code, or a function.
88
+ *
89
+ * @remarks
90
+ * An array runs as a serial pipeline: each stage gets the workbook the one before it left, and
91
+ * the run answers with the LAST stage's return value. That makes a chain lossy in one corner —
92
+ * an early stage that swapped the workbook is only visible if the last stage hands it back — so
93
+ * a host that needs per-stage reporting calls `run` once per target instead, which is what the
94
+ * CLI does (it owns the error report for the target that failed).
95
+ */
96
+ target: RunTarget | RunTarget[];
73
97
  /** The host's output sink (toasts on a UI host, the console on the CLI). Not exposed to scripts. */
74
98
  notifier?: Notifier;
75
99
  /**
@@ -82,6 +106,18 @@ export interface RunOptions {
82
106
  render?: LogRenderer;
83
107
  signal?: AbortSignal;
84
108
  timeoutMs?: number;
109
+ /**
110
+ * How long a finished stage waits for what it logged to settle, in milliseconds. `0` waits
111
+ * indefinitely.
112
+ *
113
+ * @remarks
114
+ * `log(promise)` prints what the promise settles to, so a stage is not done until its log is. A
115
+ * value still unsettled when this elapses prints as `[pending]`; nothing is thrown and the rest
116
+ * of the log is kept. Independent of `timeoutMs`, which bounds the script itself.
117
+ *
118
+ * @defaultValue `DEFAULT_LOG_TIMEOUT_MS` (10 seconds)
119
+ */
120
+ logTimeoutMs?: number;
85
121
  cwd?: string;
86
122
  ts?: {
87
123
  enabled?: boolean;
@@ -90,6 +126,24 @@ export interface RunOptions {
90
126
  }
91
127
  /**
92
128
  * Run any RunFunction with a built in context.
129
+ *
130
+ * @remarks
131
+ * The two data inputs are positional — the workbook the run is against, and the args it was given.
132
+ * Everything else is host wiring and lives in {@link RunOptions}, including `target`, which is why
133
+ * the options are required rather than optional.
134
+ *
135
+ * **Hands back exactly what the entry returned**, undisturbed — see {@link RunFunction}. A host
136
+ * that also needs the workbook the run ended on reads it off that value when it is one:
137
+ *
138
+ * ```ts
139
+ * const result = await run(wb, args, { target: 'transform.ts' });
140
+ * const ended = (result as IWorkbook)?.isIWorkbook ? result as IWorkbook : wb;
141
+ * ```
142
+ *
143
+ * Always a promise, even for a sync entry: resolving a target loads modules.
144
+ *
145
+ * `args` is spelled `ArgV | undefined` rather than `args?` because TypeScript will not let an
146
+ * optional parameter precede a required one — a run with no args passes `undefined` for it.
93
147
  */
94
- export type Runner = (workbook: IWorkbook | null, target: RunTarget | RunTarget[], options?: RunOptions) => Promise<void | IWorkbook>;
148
+ export type Runner = (workbook: IWorkbook | null, args: ArgV | undefined, options: RunOptions) => Promise<IWorkbook | any | void>;
95
149
  //# sourceMappingURL=Types.d.ts.map
@@ -1,5 +1,6 @@
1
1
  export * from './Types';
2
2
  export * from './RunUtils';
3
3
  export * as Snippet from './Snippet';
4
+ export * from './RunDiagnosis';
4
5
  export { default } from './Runner';
5
6
  //# sourceMappingURL=index.d.ts.map