@fynjs/run 1.0.1 → 1.1.1

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 (103) hide show
  1. package/bin/qrun.js +6 -1
  2. package/bin/xrun.js +1 -1
  3. package/dist/cli/check-global.d.ts +1 -0
  4. package/{cli → dist/cli}/check-global.js +3 -3
  5. package/dist/cli/ck.d.ts +1 -0
  6. package/dist/cli/cli-options.d.ts +80 -0
  7. package/dist/cli/cli-options.js +95 -0
  8. package/dist/cli/config.d.ts +7 -0
  9. package/dist/cli/config.js +7 -0
  10. package/dist/cli/env.d.ts +17 -0
  11. package/dist/cli/env.js +31 -0
  12. package/dist/cli/npm-loader.d.ts +2 -0
  13. package/dist/cli/npm-loader.js +43 -0
  14. package/dist/cli/parse-cmd-args.d.ts +34 -0
  15. package/dist/cli/parse-cmd-args.js +199 -0
  16. package/dist/cli/provider-packages.d.ts +28 -0
  17. package/dist/cli/provider-packages.js +62 -0
  18. package/dist/cli/search-up-task-file.d.ts +8 -0
  19. package/dist/cli/search-up-task-file.js +79 -0
  20. package/dist/cli/task-file.d.ts +40 -0
  21. package/dist/cli/task-file.js +177 -0
  22. package/dist/cli/ts-runner.d.ts +2 -0
  23. package/dist/cli/ts-runner.js +60 -0
  24. package/dist/cli/usage.d.ts +2 -0
  25. package/dist/cli/wrap-process.d.ts +10 -0
  26. package/dist/cli/wrap-process.js +26 -0
  27. package/dist/cli/xrun-main.d.ts +119 -0
  28. package/dist/cli/xrun-main.js +417 -0
  29. package/dist/cli/xrun.d.ts +2 -0
  30. package/{cli → dist/cli}/xrun.js +0 -1
  31. package/dist/lib/chalk.d.ts +2 -0
  32. package/{lib → dist/lib}/chalk.js +0 -1
  33. package/dist/lib/cli-context.d.ts +97 -0
  34. package/dist/lib/cli-context.js +129 -0
  35. package/dist/lib/defaults.d.ts +30 -0
  36. package/{lib → dist/lib}/defaults.js +13 -16
  37. package/dist/lib/gen-xqid.d.ts +2 -0
  38. package/dist/lib/gen-xqid.js +8 -0
  39. package/dist/lib/index.d.ts +3 -0
  40. package/{lib → dist/lib}/index.js +0 -3
  41. package/dist/lib/logger.d.ts +2 -0
  42. package/dist/lib/logger.js +95 -0
  43. package/dist/lib/my-pkg.d.ts +4 -0
  44. package/dist/lib/my-pkg.js +42 -0
  45. package/dist/lib/ns-order.d.ts +7 -0
  46. package/dist/lib/ns-order.js +62 -0
  47. package/dist/lib/print-tasks/index.d.ts +2 -0
  48. package/dist/lib/print-tasks/index.js +100 -0
  49. package/dist/lib/reporters/console.d.ts +20 -0
  50. package/dist/lib/reporters/console.js +119 -0
  51. package/dist/lib/stringify.d.ts +2 -0
  52. package/dist/lib/stringify.js +19 -0
  53. package/dist/lib/util/parse-array.d.ts +2 -0
  54. package/dist/lib/util/parse-array.js +5 -0
  55. package/dist/lib/util/task-options.d.ts +37 -0
  56. package/dist/lib/util/task-options.js +115 -0
  57. package/dist/lib/util/update-env.d.ts +1 -0
  58. package/dist/lib/util/update-env.js +18 -0
  59. package/dist/lib/xqitem.d.ts +22 -0
  60. package/dist/lib/xqitem.js +57 -0
  61. package/dist/lib/xqtor.d.ts +57 -0
  62. package/dist/lib/xqtor.js +704 -0
  63. package/dist/lib/xqtree.d.ts +11 -0
  64. package/dist/lib/xqtree.js +33 -0
  65. package/dist/lib/xrun-instance.d.ts +9 -0
  66. package/dist/lib/xrun-instance.js +27 -0
  67. package/dist/lib/xrun.d.ts +55 -0
  68. package/dist/lib/xrun.js +317 -0
  69. package/dist/lib/xtask-spec.d.ts +10 -0
  70. package/dist/lib/xtask-spec.js +57 -0
  71. package/dist/lib/xtasks.d.ts +25 -0
  72. package/dist/lib/xtasks.js +119 -0
  73. package/package.json +39 -15
  74. package/cli/cli-options.js +0 -98
  75. package/cli/config.js +0 -8
  76. package/cli/env.js +0 -33
  77. package/cli/npm-loader.js +0 -56
  78. package/cli/parse-cmd-args.js +0 -156
  79. package/cli/provider-packages.js +0 -73
  80. package/cli/search-up-task-file.js +0 -92
  81. package/cli/task-file.js +0 -192
  82. package/cli/ts-runner.js +0 -51
  83. package/cli/wrap-process.js +0 -27
  84. package/cli/xrun-main.js +0 -425
  85. package/lib/cli-context.js +0 -145
  86. package/lib/gen-xqid.js +0 -10
  87. package/lib/logger.js +0 -105
  88. package/lib/ns-order.js +0 -76
  89. package/lib/print-tasks/index.js +0 -105
  90. package/lib/reporters/console.js +0 -133
  91. package/lib/stringify.js +0 -16
  92. package/lib/util/parse-array.js +0 -5
  93. package/lib/util/update-env.js +0 -16
  94. package/lib/xqitem.js +0 -70
  95. package/lib/xqtor.js +0 -814
  96. package/lib/xqtree.js +0 -41
  97. package/lib/xrun-instance.js +0 -33
  98. package/lib/xrun.d.ts +0 -89
  99. package/lib/xrun.js +0 -344
  100. package/lib/xtask-spec.js +0 -63
  101. package/lib/xtasks.js +0 -137
  102. /package/{cli → dist/cli}/ck.js +0 -0
  103. /package/{cli → dist/cli}/usage.js +0 -0
@@ -0,0 +1,704 @@
1
+ import { createRequire } from "node:module";
2
+ import assert from "assert";
3
+ import defaults from "./defaults.js";
4
+ import Insync from "insync";
5
+ import XQItem from "./xqitem.js";
6
+ import { exec as exec } from "xsh";
7
+ import parseArray from "./util/parse-array.js";
8
+ import childProc from "child_process";
9
+ import updateEnv from "./util/update-env.js";
10
+ import { NixClap } from "@fynjs/cli-args";
11
+ import { getTaskOptionSpec } from "./util/task-options.js";
12
+ //
13
+ // Kept lazy rather than a static import: a static import is hoisted to module load, which is
14
+ // exactly what the call site below avoids so fynpo bootstrap can build unwrap-npm-cmd first.
15
+ //
16
+ const lazyRequire = createRequire(import.meta.url);
17
+ const STAGE_FINALLY = "finally";
18
+ const isSerial = x => defaults.SERIAL_SIG.includes(x[0]);
19
+ const isConcurrent = x => defaults.CONCURRENT_SIG.includes(x[0]);
20
+ const isReadableStream = x => Boolean(x && x.pipe && x.on && x._readableState);
21
+ /*
22
+ * Task executor (XQtor) - After all the fuss, it comes down to this -- the core of xrun
23
+ * that handles the execution of tasks.
24
+ *
25
+ * Tasks are added to a stack using an array.
26
+ *
27
+ * - Each concurrent tasks array is executed with a new XQtor and Insync.parallel.
28
+ * - Serial tasks are all added to the top of the "stack" in reverse order
29
+ * - Some items are actions that cause more items to be created and pushed
30
+ * into the stack. Like looking up the task of a task name.
31
+ * - As each task is completed, the XQtor's next is invoked to pop items
32
+ * from the stack for processing.
33
+ * - A mark item is added to the stack before each task so when a mark
34
+ * is seen, error is checked and done event is emitted.
35
+ *
36
+ */
37
+ class XQtor {
38
+ constructor(options) {
39
+ this._tasks = options.tasks;
40
+ this._done = options.done;
41
+ this._xrun = options.xrun;
42
+ this.xqItems = [];
43
+ }
44
+ next(err, xqId) {
45
+ process.nextTick(() => this._next(err, xqId), 0);
46
+ }
47
+ _next(err, xqId) {
48
+ if (err) {
49
+ const qItem = this._xrun.xqTree.item(xqId);
50
+ qItem.err = err;
51
+ this._xrun.fail(err);
52
+ }
53
+ if (this.xqItems.length > 0) {
54
+ this.execute();
55
+ }
56
+ else {
57
+ this._done(this._xrun.failed);
58
+ }
59
+ }
60
+ execute() {
61
+ assert(this.xqItems.length > 0, "no next task");
62
+ const qItem = this.popItem();
63
+ if (qItem.mark) {
64
+ return this._processMark(qItem);
65
+ }
66
+ if (this._xrun._isStop || (this._xrun.failed && !qItem.isFinally && this._xrun.stopOnError)) {
67
+ return this.next(null, qItem.id);
68
+ }
69
+ const value = qItem.value();
70
+ if (value === defaults.STOP_SYM) {
71
+ this._xrun.actStop();
72
+ return this.next(null, qItem.id);
73
+ }
74
+ const vtype = value.constructor.name;
75
+ if (vtype === "XTaskSpec") {
76
+ if (value.type === "exec") {
77
+ return this._shellXer(qItem, value, true);
78
+ }
79
+ else if (value.type === "env") {
80
+ return this._envXer(qItem, value);
81
+ }
82
+ this._xrun.fail(new Error(`Unable to process XTaskSpec type ${value.type} for task ${qItem.name}`));
83
+ }
84
+ else if (vtype === "String") {
85
+ if (this._isAnonShell(value)) {
86
+ return this._shellXer(qItem, this._parseAnonShell(value), true);
87
+ }
88
+ return this._processLookup(qItem);
89
+ }
90
+ else if (vtype === "Function" || vtype === "AsyncFunction") {
91
+ return this._functionXer(qItem, value);
92
+ }
93
+ else if (vtype === "Array") {
94
+ return this._processArray(qItem, value);
95
+ }
96
+ else if (value.item) {
97
+ const dep = value.item.dep;
98
+ if (dep && !qItem.xqDep) {
99
+ return this._processDep(qItem, dep);
100
+ }
101
+ else {
102
+ return this._processTaskObject(qItem);
103
+ }
104
+ }
105
+ else {
106
+ this._xrun.fail(new Error(`Unable to process task ${qItem.name} \
107
+ because value type ${vtype} is unknown and no value.item`));
108
+ }
109
+ return this.next(null, qItem.id);
110
+ }
111
+ pushMarkItem(qItem, callback) {
112
+ const x = this._makeXqMarkItem(qItem, callback);
113
+ this.pushItem(x);
114
+ return x;
115
+ }
116
+ pushItem(qItem) {
117
+ this.xqItems.push(qItem);
118
+ }
119
+ popItem() {
120
+ return this.xqItems.pop();
121
+ }
122
+ _isAnonShell(value) {
123
+ let sig;
124
+ if ((sig = defaults.ANON_SHELL_OPT_SIG.find(x => value.startsWith(x)))) {
125
+ return { sig, opt: true };
126
+ }
127
+ if ((sig = defaults.ANON_SHELL_SIG.find(x => value.startsWith(x)))) {
128
+ return { sig, opt: false };
129
+ }
130
+ return false;
131
+ }
132
+ _parseShellFlags(flags, value) {
133
+ const unknowns = [];
134
+ flags = flags.reduce((a, o) => {
135
+ const f = o.trim().toLowerCase();
136
+ if (defaults.SHELL_FLAGS.indexOf(f) < 0) {
137
+ unknowns.push(o);
138
+ }
139
+ else {
140
+ a[f] = true;
141
+ }
142
+ return a;
143
+ }, {});
144
+ if (unknowns.length) {
145
+ throw new Error(`Unknown flag ${unknowns.join(",")} in shell task: ${value}`);
146
+ }
147
+ return flags;
148
+ }
149
+ _parseAnonShell(value) {
150
+ if (typeof value !== "string")
151
+ return value;
152
+ let cmd = value;
153
+ let flags = {};
154
+ let error;
155
+ const anon = this._isAnonShell(value);
156
+ if (anon) {
157
+ const { sig, opt } = anon;
158
+ // support options like ~(tty,spawn,sync)$
159
+ if (opt) {
160
+ let ix;
161
+ const closeSig = defaults.ANON_SHELL_OPT_CLOSE_SIG.find(x => {
162
+ ix = value.indexOf(x, sig.length);
163
+ return ix > sig.length;
164
+ });
165
+ if (closeSig) {
166
+ const so = value.substring(sig.length, ix);
167
+ try {
168
+ flags = this._parseShellFlags(so.split(","), value);
169
+ }
170
+ catch (err) {
171
+ error = err;
172
+ }
173
+ cmd = value.substring(ix + closeSig.length);
174
+ }
175
+ else {
176
+ error = new Error(`Missing ${defaults.ANON_SHELL_OPT_CLOSE_SIG[0]} in shell task: ${value}`);
177
+ }
178
+ }
179
+ else {
180
+ cmd = value.substring(sig.length);
181
+ }
182
+ }
183
+ return { flags, cmd, error };
184
+ }
185
+ _isStrArray(value) {
186
+ return value.startsWith(defaults.STR_ARRAY_SIG);
187
+ }
188
+ _parseStrArray(value) {
189
+ if (this._isStrArray(value)) {
190
+ return parseArray(value.substring(defaults.STR_ARRAY_SIG.length - 1));
191
+ }
192
+ return undefined;
193
+ }
194
+ _processLookup(qItem) {
195
+ this._emit("execute", {
196
+ type: "lookup",
197
+ qItem
198
+ });
199
+ // lookup is sync; should not insert mark for it
200
+ try {
201
+ const found = qItem.lookup(this._tasks);
202
+ this.pushItem(qItem);
203
+ this.next(null, qItem.id);
204
+ if (found.search) {
205
+ this._xrun.emit("search", { qItem, found });
206
+ qItem.setNamespace(found.ns);
207
+ }
208
+ }
209
+ catch (error) {
210
+ if (error.optional) {
211
+ this._xrun.emit("not-found", error);
212
+ this.next();
213
+ }
214
+ else {
215
+ this.next(error, qItem.id);
216
+ }
217
+ }
218
+ }
219
+ _processDep(qItem, dep) {
220
+ this._emit("dep", {
221
+ qItem
222
+ });
223
+ qItem.xqDep = true; // item's dep has been processed
224
+ this.pushItem(qItem);
225
+ const di = this._createQItem({
226
+ name: qItem.name,
227
+ value: { top: true, depDee: qItem, item: { task: dep } }
228
+ }, this._xrun.xqTree.parent(qItem));
229
+ this.pushItem(di);
230
+ this.next(null, qItem.id);
231
+ }
232
+ _processTaskObject(qItem) {
233
+ const value = qItem.value();
234
+ const stage = qItem.stage || "task";
235
+ const itemTask = value.item[stage] || value.item;
236
+ const type = itemTask.constructor.name;
237
+ if (type === "Array") {
238
+ return this._processArray(qItem, itemTask, value.top);
239
+ }
240
+ else if (type === "Function" || type === "AsyncFunction") {
241
+ return this._functionXer(qItem, itemTask);
242
+ }
243
+ else if (type === "String") {
244
+ const parsedArray = this._parseStrArray(itemTask);
245
+ if (parsedArray)
246
+ return this._processArray(qItem, parsedArray, value.top);
247
+ return this._shellXer(qItem, itemTask);
248
+ }
249
+ else if (type === "XTaskSpec") {
250
+ if (itemTask.type === "exec") {
251
+ return this._shellXer(qItem, itemTask);
252
+ }
253
+ else if (itemTask.type === "env") {
254
+ return this._envXer(qItem, itemTask);
255
+ }
256
+ this._xrun.fail(new Error(`Unable to process XTaskSpec type ${itemTask.type} for task ${qItem.name}`));
257
+ }
258
+ else {
259
+ this._xrun.fail(new Error(`Task ${qItem.name} has unrecognize task value type ${type}`));
260
+ }
261
+ return this.next(null, qItem.id);
262
+ }
263
+ _processArray(qItem, tasks, top) {
264
+ // check first element for concurrent signature
265
+ // top level task arrays are automatically serial
266
+ // or check first element for serial signature
267
+ if (isConcurrent(tasks) || (!top && !isSerial(tasks))) {
268
+ this._concurrentArrayXer(qItem, tasks);
269
+ }
270
+ else {
271
+ this._serialArrayXer(qItem, tasks);
272
+ }
273
+ }
274
+ _serialArrayXer(qItem, tasks) {
275
+ if (isSerial(tasks)) {
276
+ tasks = tasks.slice(1);
277
+ }
278
+ this._emit("execute", {
279
+ type: "serial-arr",
280
+ qItem,
281
+ array: tasks
282
+ });
283
+ this.pushMarkItem(qItem);
284
+ this.xqItems = this.xqItems.concat(tasks
285
+ .map(value => {
286
+ return this._createQItem(this._resolveValueToQItemOptions(qItem.ns, `${qItem.name}.S`, value, "serial_child"), qItem);
287
+ })
288
+ .reverse());
289
+ this.next(null, qItem.id);
290
+ }
291
+ _resolveValueToQItemOptions(ns, name, value, type) {
292
+ if (typeof value === "string" && !this._isAnonShell(value)) {
293
+ name = value;
294
+ value = undefined;
295
+ }
296
+ const anon = typeof value === "function";
297
+ return {
298
+ ns,
299
+ name,
300
+ value,
301
+ anon,
302
+ type
303
+ };
304
+ }
305
+ _concurrentArrayXer(qItem, tasks) {
306
+ if (isConcurrent(tasks)) {
307
+ tasks = tasks.slice(1);
308
+ }
309
+ this._emit("execute", {
310
+ type: "concurrent-arr",
311
+ qItem,
312
+ array: tasks
313
+ });
314
+ this.pushMarkItem(qItem);
315
+ Insync.parallel(tasks.map(value => cb => {
316
+ this._emit("spawn-async", { name: qItem.name });
317
+ const xqtor = new XQtor({
318
+ tasks: this._tasks,
319
+ xrun: this._xrun,
320
+ done: () => {
321
+ this._emit("done-async");
322
+ process.nextTick(() => cb());
323
+ }
324
+ });
325
+ const options = this._resolveValueToQItemOptions(qItem.ns, `${qItem.name}.C`, value, "concurrent_child");
326
+ const item = this._createQItem(options, qItem);
327
+ if (options.value === undefined) {
328
+ xqtor._processLookup(item);
329
+ }
330
+ else {
331
+ xqtor.pushItem(item);
332
+ xqtor.next();
333
+ }
334
+ }), () => this.next());
335
+ }
336
+ _envXer(qItem, cmdVal) {
337
+ this._emit("execute", {
338
+ type: "env",
339
+ qItem,
340
+ cmdVal
341
+ });
342
+ updateEnv(cmdVal.options.env, process.env, cmdVal.options.override);
343
+ return this.next();
344
+ }
345
+ _shellXer(qItem, cmdVal, anon) {
346
+ cmdVal = this._parseAnonShell(cmdVal);
347
+ if (cmdVal.error) {
348
+ setTimeout(() => this.next(cmdVal.error, qItem.id), 0);
349
+ return;
350
+ }
351
+ const { cmd } = cmdVal;
352
+ const xrunOptions = Object.assign({ delayRunMs: 0 }, cmdVal.xrun || cmdVal.xclap);
353
+ const options = Object.assign({}, cmdVal.options);
354
+ let { flags } = cmdVal;
355
+ if (typeof flags === "string") {
356
+ flags = this._parseShellFlags(flags.split(","), `XTaskSpec "${flags}"`);
357
+ }
358
+ else if (Array.isArray(flags)) {
359
+ flags = this._parseShellFlags(flags);
360
+ }
361
+ const item = qItem.value().item || {};
362
+ Object.assign(flags, item.flags);
363
+ // Access CLI context through runner
364
+ const cliContext = this._xrun.getCliContext();
365
+ const itemArgv = qItem.argv;
366
+ const cliArgv = cliContext.getTaskArgv(qItem.name);
367
+ const remainingArgv = (cliContext.isLastTask(qItem.name) && cliContext.getRemainingArgs()) || [];
368
+ // Build command with task-specific argv
369
+ const cmd2 = []
370
+ .concat(itemArgv.slice(1), cliArgv.slice(1), remainingArgv)
371
+ .map(x => (x && x.includes(" ") ? `"${x}"` : x))
372
+ .join(" ");
373
+ setTimeout(() => {
374
+ const execData = {
375
+ type: "shell",
376
+ anon,
377
+ qItem,
378
+ cmd: cmd + ((cmd2 && " " + cmd2) || ""),
379
+ cmd2,
380
+ flags,
381
+ cmdVal,
382
+ options,
383
+ item
384
+ };
385
+ this._emit("execute", execData);
386
+ this._doShellXer(execData);
387
+ }, xrunOptions.delayRunMs);
388
+ }
389
+ _doShellXer({ /*anon,*/ qItem, cmd, flags, /*cmdVal,*/ options, item }) {
390
+ const itemOptions = Object.assign({}, item.options);
391
+ this.pushMarkItem(qItem);
392
+ const env = Object.assign(flags.noenv ? {} : Object.assign({}, process.env), itemOptions.env, options.env);
393
+ if (qItem.err) {
394
+ env.XCLAP_ERR = env.XRUN_ERR = qItem.err.message || "true";
395
+ }
396
+ if (this._xrun.failed || qItem.err) {
397
+ env.XCLAP_FAILED = env.XRUN_FAILED = "true";
398
+ }
399
+ let child;
400
+ const watch = { finish: false };
401
+ const done = (err) => {
402
+ // even if there's error, but if it's due to child being terminated with
403
+ // SIGTERM, then treat that as a normal exit.
404
+ if (this._isChildSigTerm(err, child) || this._isChildTerminated(child)) {
405
+ err = null;
406
+ }
407
+ //
408
+ // A spawned child can report both `error` and `close`, so `done` can be called twice.
409
+ // Guarding it is the point; reproducing that race on demand in a test is not something
410
+ // this suite can do deterministically.
411
+ //
412
+ /* istanbul ignore next */
413
+ if (watch.finish)
414
+ return 0;
415
+ watch.finish = true;
416
+ return this.next(err, qItem.id);
417
+ };
418
+ let { tty, spawn, sync, npm } = flags;
419
+ if (npm) {
420
+ tty = spawn = true;
421
+ }
422
+ // Lazy require unwrap-npm-cmd to avoid requiring it at module load time
423
+ // This ensures it's built by fynpo bootstrap before it's required
424
+ const { unwrapNpmCmd } = lazyRequire("unwrap-npm-cmd");
425
+ const cmd2 = unwrapNpmCmd(cmd, { path: env.PATH });
426
+ if (tty || spawn) {
427
+ const spawnOpts = { shell: true };
428
+ Object.assign(spawnOpts, itemOptions, options, { env });
429
+ if (tty)
430
+ spawnOpts.stdio = "inherit";
431
+ if (sync) {
432
+ child = childProc.spawnSync(cmd2, spawnOpts);
433
+ if (child.error) {
434
+ done(child.error);
435
+ }
436
+ else if (child.status === 0) {
437
+ done();
438
+ }
439
+ else {
440
+ done(new Error(`cmd "${cmd}" exit code ${child.status}`));
441
+ }
442
+ }
443
+ else {
444
+ child = childProc.spawn(cmd2, spawnOpts);
445
+ child.on("close", (code, signal) => {
446
+ if (code === 0 || signal === "SIGTERM") {
447
+ return done();
448
+ }
449
+ return done(new Error(`cmd "${cmd}" exit code ${code}`));
450
+ });
451
+ }
452
+ }
453
+ else {
454
+ child = exec(Object.assign({ silent: false }, itemOptions, options, { env }), cmd2, done);
455
+ }
456
+ if (!watch.finish) {
457
+ this._handleChildTask(child, cmd);
458
+ watch.cancel = () => {
459
+ this._xrun.killChildProcess(child);
460
+ done();
461
+ };
462
+ this._watchFailure(watch);
463
+ }
464
+ }
465
+ _handleChildTask(child, cmd) {
466
+ if (child && child instanceof childProc.ChildProcess) {
467
+ const sym = Symbol(cmd);
468
+ this._xrun.addTaskChild(child, sym);
469
+ child.on("exit", () => {
470
+ this._xrun.removeTaskChild(child, sym);
471
+ });
472
+ return child;
473
+ }
474
+ }
475
+ _processMoreFromFn(qItem, value, callback) {
476
+ let tof = typeof value;
477
+ if (tof === "string") {
478
+ const parsedArray = this._parseStrArray(value);
479
+ if (parsedArray)
480
+ value = parsedArray;
481
+ tof = typeof value;
482
+ }
483
+ if (!value ||
484
+ (tof !== "string" &&
485
+ tof !== "function" &&
486
+ !Array.isArray(value) &&
487
+ value.constructor.name !== "XTaskSpec" &&
488
+ value !== defaults.STOP_SYM)) {
489
+ return;
490
+ }
491
+ const qi = this._createQItem(this._resolveValueToQItemOptions(qItem.ns, `${qItem.name}.fR`, value, "func_returned_child"), qItem);
492
+ if (callback) {
493
+ assert(typeof callback === "function", `${qItem.name} callback from function calling run is not a function`);
494
+ this.pushMarkItem(qItem, callback);
495
+ }
496
+ this.pushItem(qi);
497
+ }
498
+ _isChildSigTerm(err, child) {
499
+ return err && err.code === null && child && child.signalCode === "SIGTERM";
500
+ }
501
+ _isChildTerminated(child) {
502
+ return child && child[defaults.STOP_SYM];
503
+ }
504
+ _handleStream(val) {
505
+ if (!isReadableStream(val)) {
506
+ return val;
507
+ }
508
+ return new Promise((resolve, reject) => {
509
+ const handle = err => {
510
+ val.removeListener("end", handle);
511
+ val.removeListener("error", handle);
512
+ return err ? reject(err) : resolve(undefined);
513
+ };
514
+ val.once("close", handle);
515
+ val.once("error", handle);
516
+ });
517
+ }
518
+ _functionXer(qItem, fn) {
519
+ this._emit("execute", {
520
+ type: "function",
521
+ anon: qItem.anon,
522
+ qItem
523
+ });
524
+ const watch = { finish: false, qItem };
525
+ const done = (err, value) => {
526
+ if (watch.finish)
527
+ return;
528
+ watch.finish = true;
529
+ if (value) {
530
+ this._processMoreFromFn(qItem, value);
531
+ }
532
+ this.next(err, qItem.id);
533
+ };
534
+ watch.cancel = done;
535
+ try {
536
+ this.pushMarkItem(qItem);
537
+ const run = (value, cb) => this._processMoreFromFn(qItem, value, cb);
538
+ const task = qItem.value();
539
+ // Access CLI context through runner
540
+ const cliContext = this._xrun.getCliContext();
541
+ let cliCmd = cliContext.getTaskCommand(qItem.name);
542
+ const { options, commands, allowUnknownOption } = getTaskOptionSpec(task);
543
+ const itemArgv = qItem.argv;
544
+ const cliArgv = cliCmd.argv || [];
545
+ const hasOptionSpec = Object.keys(options).length > 0 || Object.keys(commands).length > 0;
546
+ if (hasOptionSpec || itemArgv.length > 1 || cliArgv.length > 1) {
547
+ const argv = [qItem.name].concat(itemArgv.slice(1), cliArgv.slice(1));
548
+ const config = {
549
+ name: qItem.name,
550
+ allowUnknownOption: allowUnknownOption !== undefined ? allowUnknownOption : true,
551
+ allowUnknownCommand: false,
552
+ noDefaultHandlers: true
553
+ };
554
+ const argp = new NixClap(config)
555
+ .init(options, commands)
556
+ .parse(argv, 1);
557
+ if (argp.errorNodes.length > 0) {
558
+ const unknownOptions = [];
559
+ const otherErrors = [];
560
+ for (const node of argp.errorNodes) {
561
+ for (const e of node.errors) {
562
+ if (e.message.includes("unknown CLI option")) {
563
+ unknownOptions.push(e.data?.name);
564
+ }
565
+ else {
566
+ otherErrors.push(e.message);
567
+ }
568
+ }
569
+ }
570
+ if (unknownOptions.length > 0) {
571
+ return done(new Error(`Unknown options for task ${qItem.name}: ${unknownOptions.join(", ")}`));
572
+ }
573
+ if (otherErrors.length > 0) {
574
+ return done(new Error(`Error parsing options for task ${qItem.name}: ${otherErrors.join("; ")}`));
575
+ }
576
+ }
577
+ cliCmd = argp.command;
578
+ }
579
+ const argv = cliCmd.argv || [];
580
+ const context = {
581
+ run,
582
+ argv,
583
+ cliCmd,
584
+ argp: cliCmd.jsonMeta,
585
+ args: cliCmd.argsList,
586
+ argOpts: cliCmd.opts,
587
+ err: qItem.err,
588
+ failed: this._xrun.failed
589
+ };
590
+ if (fn.length > 1) {
591
+ // takes two params, pass in context and done callback
592
+ this._watchFailure(watch);
593
+ return fn.call(context, context, done);
594
+ }
595
+ if (fn.constructor.name !== "AsyncFunction" && fn.length > 0) {
596
+ // non async function that takes 1 param, check if it wants the context
597
+ // or a callback.
598
+ const fnStr = fn.toString().replace(/\s/g, "");
599
+ const matchParam = x => {
600
+ return (fnStr.startsWith(`${x}=>`) ||
601
+ fnStr.match(new RegExp(`\\(${x}[^\\)]*\\)=>`)) ||
602
+ fnStr.match(new RegExp(`function[^\\(]*\\(${x}[^\\)]*\\){`)) ||
603
+ fnStr.match(new RegExp(`[^(]*\\(${x}[^\\)]*\\){`)));
604
+ };
605
+ const takeContext = ["ctx", "context"].find(matchParam);
606
+ if (!takeContext) {
607
+ // non async task function takes callback, expect async behavior
608
+ this._watchFailure(watch);
609
+ return fn.call(context, done);
610
+ }
611
+ // function takes context as first param
612
+ return done(null, fn.call(context, context));
613
+ }
614
+ const x = this._handleStream(fn.call(context, context));
615
+ // handling a exec/spawn child from a function
616
+ const cmd = `ChildProcess of function task ${qItem.name}`;
617
+ const child = this._handleChildTask(x && x.child, cmd) || this._handleChildTask(x, cmd);
618
+ if (!x || typeof x.then !== "function") {
619
+ // assume no async behavior in task function
620
+ return done(null, x);
621
+ }
622
+ // x.then is a function, assume task function returned a promise
623
+ this._watchFailure(watch);
624
+ return x.then(v => done(null, v), err => {
625
+ // treat child process that got SIGTERM as exited normally
626
+ if (this._isChildSigTerm(err, child)) {
627
+ done();
628
+ }
629
+ else {
630
+ done(err);
631
+ }
632
+ });
633
+ }
634
+ catch (err) {
635
+ return done(err);
636
+ }
637
+ }
638
+ _watchFailure(watch) {
639
+ if (this._xrun.stopOnError !== "full")
640
+ return undefined;
641
+ return setTimeout(() => {
642
+ if (watch.finish)
643
+ return 0;
644
+ if (this._xrun.failed) {
645
+ this._xrun.emit("fail-cancel", watch);
646
+ return watch.cancel();
647
+ }
648
+ return this._watchFailure(watch);
649
+ }, 50).unref();
650
+ }
651
+ _processMark(qItem) {
652
+ const value = qItem.value();
653
+ const xqItem = this._xrun.xqTree.item(value.xqId);
654
+ const endTime = Date.now();
655
+ const data = {
656
+ qItem,
657
+ xqItem,
658
+ elapse: endTime - value.startTime,
659
+ startTime: value.startTime,
660
+ endTime,
661
+ hrStartTime: value.hrStartTime,
662
+ hrElapse: process.hrtime(value.hrStartTime)
663
+ };
664
+ if (value.callback) {
665
+ value.callback(this._xrun.failed, data);
666
+ }
667
+ else {
668
+ this._emit("done-item", data);
669
+ }
670
+ const xqValue = xqItem.value();
671
+ if (xqValue.item && xqValue.item[STAGE_FINALLY] && !xqItem.isFinally) {
672
+ xqItem.stage = STAGE_FINALLY;
673
+ xqItem.isFinally = true;
674
+ this.pushItem(xqItem);
675
+ }
676
+ this.next();
677
+ }
678
+ _makeXqMarkItem(qItem, callback) {
679
+ // do not add marking item to tree
680
+ const mark = new XQItem({
681
+ name: `mark_${qItem.name}`,
682
+ value: {
683
+ startTime: Date.now(),
684
+ hrStartTime: process.hrtime(),
685
+ callback,
686
+ xqId: qItem.id
687
+ }
688
+ });
689
+ mark.mark = true;
690
+ return mark;
691
+ }
692
+ _emit(event, data) {
693
+ try {
694
+ this._xrun.emit(event, data);
695
+ }
696
+ catch (err) {
697
+ this._xrun.fail(err);
698
+ }
699
+ }
700
+ _createQItem(options, parent) {
701
+ return this._xrun.xqTree.create(options, parent);
702
+ }
703
+ }
704
+ export default XQtor;