@fynjs/run 1.0.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.
@@ -0,0 +1,192 @@
1
+ import { pathToFileURL } from "node:url";
2
+ import Path from "path";
3
+ import env from "./env.js";
4
+ import xsh from "xsh";
5
+ import logger from "../lib/logger.js";
6
+ import config from "./config.js";
7
+ import ck from "./ck.js";
8
+ import { searchUpTaskFile } from "./search-up-task-file.js";
9
+ import WrapProcess from "./wrap-process.js";
10
+ import npmLoader from "./npm-loader.js";
11
+ import requireAt from "require-at";
12
+ import instance from "../lib/xrun-instance.js";
13
+ import TsRunner from "./ts-runner.js";
14
+
15
+ /**
16
+ * Update the current working directory
17
+ * @param {string} [dir] - Directory to change to
18
+ * @returns {string} New working directory
19
+ */
20
+ function updateCwd(dir) {
21
+ dir = dir || WrapProcess.cwd();
22
+ const newCwd = Path.isAbsolute(dir) ? dir : Path.resolve(dir);
23
+
24
+ try {
25
+ const cwd = WrapProcess.cwd();
26
+ if (newCwd !== cwd) {
27
+ WrapProcess.chdir(newCwd);
28
+ logger.log(ck`CWD changed to <magenta>${newCwd}</>`);
29
+ } else if (env.get(env.xrunCwd) !== cwd) {
30
+ logger.log(ck`CWD is <magenta>${cwd}</>`);
31
+ }
32
+ env.set(env.xrunCwd, newCwd);
33
+
34
+ return newCwd;
35
+ } catch (err) {
36
+ logger.log(ck`chdir <magenta>${newCwd}</> <red>failed</>`);
37
+ WrapProcess.exit(1);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Search for task files in the given directory
43
+ * @param {boolean} search - Whether to search up directories
44
+ * @param {ParseOptions} opts - Search options
45
+ * @returns {SearchResult} Search result
46
+ */
47
+ function searchTaskFile(search, opts) {
48
+ const xrunDir = Path.join(opts.cwd, opts.dir || "");
49
+
50
+ const loadResult = searchUpTaskFile(xrunDir, search);
51
+
52
+ if (!loadResult.found) {
53
+ if (env.get(env.xrunTaskFile) !== "not found") {
54
+ const x = xsh.pathCwd.replace(xrunDir, "./");
55
+ logger.log(ck`No <green>${config.taskFile}</> found in <magenta>${x}</>`);
56
+ }
57
+ // set env to let subsequent xrun calls know that the task file was not found
58
+ // and avoid logging the same message again
59
+ env.set(env.xrunTaskFile, "not found");
60
+ } else if (opts.updateCwd !== false) {
61
+ // force CWD to where xrun task file was found
62
+ loadResult.cwd = updateCwd(loadResult.dir);
63
+ }
64
+
65
+ return loadResult;
66
+ }
67
+
68
+ /**
69
+ * Load a task file.
70
+ *
71
+ * Loading goes through `import()` rather than `require`, which is what lets a task file use
72
+ * top-level await - node's `require(esm)` refuses that graph outright and always will. Every
73
+ * other format keeps working: `import()` of a CommonJS file yields a namespace with
74
+ * `module.exports` on `.default`, which processTasks already unwraps.
75
+ *
76
+ * @param {string} name - Path to the task file
77
+ * @returns {Promise<Object|Function|undefined>} Loaded task module, or undefined if it failed
78
+ */
79
+ async function loadTaskFile(name) {
80
+ const ext = Path.extname(name);
81
+ if (ext === ".ts" || ext === ".tsx" || ext === ".mts" || ext === ".cts") {
82
+ TsRunner.startRunner();
83
+ }
84
+
85
+ try {
86
+ return await import(pathToFileURL(Path.resolve(name)).href);
87
+ } catch (e) {
88
+ const file = xsh.pathCwd.replace(name, ".");
89
+ const errMsg = ck`<red>Unable to load ${file}</>`;
90
+
91
+ //
92
+ // node strips TypeScript types natively, which covers ordinary task files. It cannot
93
+ // handle syntax that has to be transformed rather than erased - enums, namespaces,
94
+ // parameter properties - and says so with its own code. Name the limit rather than
95
+ // dumping a stack that does not explain it.
96
+ //
97
+ /* istanbul ignore next */
98
+ if (
99
+ e.code === "ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX" ||
100
+ e.code === "ERR_UNKNOWN_FILE_EXTENSION" ||
101
+ e.code === "ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING"
102
+ ) {
103
+ logger.error(
104
+ ck`${errMsg}: node cannot load this TypeScript on its own.
105
+ It strips types, but enums, namespaces and parameter properties need a real transform.
106
+ Rewrite them, or run node with <yellow>--experimental-transform-types</>.
107
+ ${e.message}`
108
+ );
109
+ return undefined;
110
+ }
111
+
112
+ logger.error(`${errMsg}: ${xsh.pathCwd.replace(e.stack, ".", "g")}`);
113
+ return undefined;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Process loaded tasks and register them with xrun
119
+ * @param {Object|Function} tasks - Tasks to process
120
+ * @param {string} loadMsg - Message to display when tasks are loaded
121
+ * @param {string} [ns="xrun"] - Namespace to load tasks into
122
+ */
123
+ function processTasks(tasks, loadMsg, ns = "xrun") {
124
+ if (typeof tasks === "function") {
125
+ tasks(instance.xrun);
126
+ if (loadMsg) {
127
+ logger.log(`Loaded tasks by calling export function from ${loadMsg}`);
128
+ }
129
+ } else if (typeof tasks === "object") {
130
+ if (tasks.default) {
131
+ processTasks(tasks.default, `${loadMsg} default export`, ns);
132
+ } else if (Object.keys(tasks).length > 0) {
133
+ instance.xrun.load(ns, tasks);
134
+ logger.log(ck`Loaded tasks from ${loadMsg} into namespace <magenta>${ns}</>`);
135
+ } else if (loadMsg) {
136
+ logger.log(`Loaded ${loadMsg}`);
137
+ }
138
+ } else {
139
+ logger.log(ck`Unknown export type <yellow>${typeof tasks}</> from ${loadMsg}`);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Load tasks from files or required modules
145
+ * @param {ParseOptions} opts - Options for loading tasks
146
+ * @param {SearchResult} searchResult - Result from searching for task files
147
+ * @returns {boolean} Whether any tasks were loaded
148
+ */
149
+ async function loadTasks(opts, searchResult) {
150
+ let loaded = false;
151
+ npmLoader(instance.xrun, opts);
152
+ if (opts.require) {
153
+ // a for-of rather than forEach: each file has to finish loading before the next starts,
154
+ // so tasks land in the order the user listed their modules
155
+ for (const xmod of opts.require) {
156
+ let file;
157
+ try {
158
+ file = requireAt(WrapProcess.cwd()).resolve(xmod);
159
+ } catch (err) {
160
+ logger.log(
161
+ ck`<red>ERROR:</> <yellow>Unable to require module</> <cyan>'${xmod}'</> - <red>${err.message}</>`
162
+ );
163
+ continue;
164
+ }
165
+ const tasks = await loadTaskFile(file);
166
+ /* istanbul ignore else */
167
+ if (tasks) {
168
+ const loadMsg = ck`<green>${xmod}</>`;
169
+ processTasks(tasks, loadMsg);
170
+ loaded = true;
171
+ }
172
+ }
173
+ } else if (searchResult.xrunFile) {
174
+ const tasks = await loadTaskFile(searchResult.xrunFile);
175
+ /* istanbul ignore else */
176
+ if (tasks) {
177
+ processTasks(
178
+ tasks,
179
+ env.get(env.xrunTaskFile) !== searchResult.xrunFile
180
+ ? ck`<green>${xsh.pathCwd.replace(searchResult.xrunFile, ".")}</>`
181
+ : ""
182
+ );
183
+ env.set(env.xrunTaskFile, searchResult.xrunFile);
184
+
185
+ return (loaded = true);
186
+ }
187
+ }
188
+
189
+ return loaded;
190
+ }
191
+
192
+ export { updateCwd, searchTaskFile, loadTaskFile, processTasks, loadTasks };
@@ -0,0 +1,51 @@
1
+ import { createRequire } from "node:module";
2
+ import { makeOptionalRequire } from "optional-require";
3
+
4
+ //
5
+ // A require hook is the point here - TsRunner installs tsx / ts-node into require - so this
6
+ // deliberately stays on require rather than moving to optional-import.
7
+ //
8
+ const optionalRequire = makeOptionalRequire(createRequire(import.meta.url));
9
+ import env from "./env.js";
10
+ import logger from "../lib/logger.js";
11
+ import path from "path";
12
+ import WrapProcess from "./wrap-process.js";
13
+
14
+ const TsRunner = {
15
+ "runner-tsx": "tsx",
16
+ "runner-ts-node": "ts-node/register/transpile-only",
17
+ loaded: undefined,
18
+ runner: undefined,
19
+ _require: optionalRequire,
20
+ load(name) {
21
+ const runner = TsRunner._require(TsRunner[`runner-${name}`], {
22
+ fail: e => (TsRunner[`error-${name}`] = e)
23
+ });
24
+ if (runner) {
25
+ TsRunner.loaded = name;
26
+ TsRunner.runner = runner;
27
+ const resolve = TsRunner._require.resolve;
28
+ TsRunner.path =
29
+ (resolve && ": " + path.relative(WrapProcess.cwd(), resolve(TsRunner[`runner-${name}`]))) ||
30
+ "";
31
+ }
32
+ return runner;
33
+ },
34
+ startRunner() {
35
+ const runners = ["tsx", "ts-node"];
36
+ for (const runner of runners) {
37
+ if (TsRunner.load(runner)) {
38
+ break;
39
+ }
40
+ }
41
+ if (!TsRunner.loaded) {
42
+ const errMsg = runners.map(r => r + ": " + TsRunner[`error-${r}`]).join("\n ");
43
+ logger.log(`Unable to load a typescript runner:\n ${errMsg}`);
44
+ } else if (!env.get(env.xrunId)) {
45
+ /* if xrunId exist then we are already running as invocation from another xrun */
46
+ logger.log(`Loaded ${TsRunner.loaded} for TypeScript files${TsRunner.path}`);
47
+ }
48
+ }
49
+ };
50
+
51
+ export default TsRunner;
package/cli/usage.js ADDED
@@ -0,0 +1,6 @@
1
+ import chalk from "../lib/chalk.js";
2
+ const t1 = chalk.cyan("task1");
3
+ const t2 = chalk.cyan("task2");
4
+ const o = chalk.gray("[task options]");
5
+ const usage = "xrun " + chalk.blue("[options] [--]") + ` [${t1} ${o} ${t2} ${o} ...]`;
6
+ export default usage;
@@ -0,0 +1,27 @@
1
+ /** @type {WrapProcess} */
2
+ const WrapProcess = {
3
+ _process: process,
4
+ exit(code) {
5
+ this._process.exit(code);
6
+ },
7
+ cwd() {
8
+ return this._process.cwd();
9
+ },
10
+ chdir(dir) {
11
+ this._process.chdir(dir);
12
+ },
13
+ get argv() {
14
+ return this._process.argv;
15
+ },
16
+ set argv(value) {
17
+ this._process.argv = value;
18
+ },
19
+ get env() {
20
+ return this._process.env;
21
+ },
22
+ set env(value) {
23
+ this._process.env = value;
24
+ }
25
+ };
26
+
27
+ export default WrapProcess;
@@ -0,0 +1,425 @@
1
+ import ownInstance from "../lib/xrun-instance.js";
2
+ import Path from "path";
3
+ import parseCmdArgs from "./parse-cmd-args.js";
4
+ import chalk from "../lib/chalk.js";
5
+ import logger from "../lib/logger.js";
6
+ import usage from "./usage.js";
7
+ import { envPath as envPath } from "xsh";
8
+ import Fs from "fs";
9
+ import xsh from "xsh";
10
+ import cliOptions from "./cli-options.js";
11
+ import parseArray from "../lib/util/parse-array.js";
12
+ import { makeOptionalRequire } from "optional-require";
13
+ import { createRequire } from "node:module";
14
+
15
+ const require = createRequire(import.meta.url);
16
+ const optionalRequire = makeOptionalRequire(require);
17
+ import env from "./env.js";
18
+ import WrapProcess from "./wrap-process.js";
19
+ import { CliContext } from "../lib/cli-context.js";
20
+
21
+ /**
22
+ * Flush logger based on options
23
+ * @param {Object} opts - Options
24
+ */
25
+ function flushLogger(opts) {
26
+ // only opts carries a quiet preference - without one, leave the current setting alone
27
+ // rather than silently un-quieting the logger
28
+ if (opts) {
29
+ logger.quiet(opts.quiet);
30
+ }
31
+ logger.resetBuffer(true, false);
32
+ }
33
+
34
+ /**
35
+ * Handle process exit or callback
36
+ * @param {number} code - Exit code
37
+ * @param {Function} done - Optional callback
38
+ */
39
+ function handleExitOrDone(code, done) {
40
+ if (done) {
41
+ const err = new Error(`exit code: ${code}`);
42
+ err.exitCode = code;
43
+ done(err);
44
+ } else {
45
+ WrapProcess.exit(code);
46
+ }
47
+ return true;
48
+ }
49
+
50
+ /**
51
+ * Process environment options from command line
52
+ * @param {Object} opts - Options object containing env property
53
+ */
54
+ function processEnvOptions(opts) {
55
+ const envs = [].concat(opts.env).filter(Boolean);
56
+
57
+ for (const envStr of envs) {
58
+ const [key, val] = envStr.split("=");
59
+ if (key) {
60
+ env.set(key, val);
61
+ }
62
+ }
63
+ }
64
+
65
+ /**
66
+ * List CLI options for shell auto completion
67
+ * @param {Array} argv - Command line arguments
68
+ * @param {number} offset - Argument offset
69
+ * @param {Function} done - Optional callback
70
+ * @returns {void}
71
+ */
72
+ function handleCliOptions(argv, offset, done) {
73
+ if (argv.length === 3 && argv[offset] === "--options") {
74
+ Object.keys(cliOptions).forEach(k => {
75
+ const x = cliOptions[k];
76
+ console.log(`--${k}`);
77
+ console.log(`-${x.alias}`);
78
+ });
79
+ return handleExitOrDone(0, done);
80
+ }
81
+ return false;
82
+ }
83
+
84
+ /**
85
+ * Find and load the runner module
86
+ * @param {string} xrunPath - Path to xrun
87
+ * @returns {Object} Runner module and its path
88
+ */
89
+ function findRunnerModule(xrunPath) {
90
+ let runner;
91
+
92
+ //
93
+ // The runner may resolve to this package's own ESM entry, in which case require(esm) hands
94
+ // back the module namespace rather than the instance - the runner sits on `.default`. A copy
95
+ // that is still CJS has no `.default` and is used as-is.
96
+ //
97
+ /* istanbul ignore next: the .default arm needs a real require of this package - see below */
98
+ const loadRunner = p => {
99
+ const mod = optionalRequire(p);
100
+ return mod && (mod.default || mod);
101
+ };
102
+
103
+ const foundReq = [
104
+ xrunPath, // first look for it in path passed from cli
105
+ "@fynjs/run" // let node.js resolve by package name
106
+ ].find(p => p && (runner = loadRunner(p)));
107
+
108
+ //
109
+ // Not covered on purpose. Exercising this means letting `optionalRequire` load a real copy
110
+ // of this package through node's own registry, which is a second, uninstrumented copy of
111
+ // every module in it - the rest of the run then uses that copy and coverage collapses for
112
+ // files that have nothing to do with this branch. A test here costs ~20 statements elsewhere.
113
+ //
114
+ /* istanbul ignore next */
115
+ if (runner) {
116
+ return { runner, foundPath: Path.dirname(require.resolve(foundReq)) };
117
+ }
118
+
119
+ //
120
+ // Definitive known location. This used to be `optionalRequire("..")`, but resolving our own
121
+ // package at runtime can load a second copy of it - a distinct xrun instance, with distinct
122
+ // Symbols - which is exactly what the two earlier entries are for. A static import is the
123
+ // same module by construction.
124
+ //
125
+ return { runner: ownInstance.xrun, foundPath: Path.dirname(import.meta.dirname) };
126
+ }
127
+
128
+ /**
129
+ * Handle case when no tasks are found
130
+ * @param {CliContext} cliContext - Command context
131
+ * @param {string} cwd - Current working directory
132
+ * @param {Function} done - Optional callback
133
+ */
134
+ function handleNoTasks(cliContext, cwd, done, opts) {
135
+ //
136
+ // Flush first, like handleTaskListing does. Without this the whole diagnostic below is
137
+ // written into the logger's buffer and then thrown away by the exit, so a missing, broken,
138
+ // or unloadable task file made xrun exit 1 with no output at all.
139
+ //
140
+ flushLogger(opts);
141
+
142
+ const fromCwd = optionalRequire.resolve("@fynjs/run") || "not found - probably not installed";
143
+ const fromMyDir = Path.dirname(require.resolve(".."));
144
+ const searchResult = cliContext.getSearchResult();
145
+ const info = searchResult.xrunFile
146
+ ? `
147
+ This could be due to a few reasons:
148
+
149
+ 1. your task file ${searchResult.xrunFile} didn't load any tasks or contains errors.
150
+ 2. there are multiple copies of this package (@fynjs/run) installed in "node_modules".
151
+ `
152
+ : `
153
+ You do not have a "xrun-tasks.js|ts" file, so the only tasks may come from your
154
+ 'package.json' npm scripts, and you probably don't have any defined there either.
155
+ `;
156
+
157
+ logger.error(`${chalk.red("*** No tasks found ***")}
158
+ ${info}
159
+ For reference, some paths used to search for tasks:
160
+ - my current import.meta.dirname: '${import.meta.dirname}'
161
+ - dir used to search for tasks:
162
+ '${cwd}'
163
+
164
+ Some paths used to resolve @fynjs/run:
165
+ - resolved from CWD: '${fromCwd}'
166
+ - resolved from my dir: '${fromMyDir}'
167
+ `);
168
+ return handleExitOrDone(1, done);
169
+ }
170
+
171
+ /**
172
+ * Handle task listing
173
+ * @param {Object} runner - Runner instance
174
+ * @param {Object} opts - Options
175
+ * @param {Function} done - Optional callback
176
+ * @returns {void}
177
+ */
178
+ function handleTaskListing(runner, opts, done) {
179
+ flushLogger(opts);
180
+ const ns = opts.list && opts.list.split(",").map(x => x.trim());
181
+ try {
182
+ if (opts.full) {
183
+ let fn = runner._tasks.fullNames(ns);
184
+ if (opts.full > 1) fn = fn.map(x => (x.startsWith("/") ? x : `/${x}`));
185
+ console.log(fn.join("\n"));
186
+ } else {
187
+ console.log(runner._tasks.names(ns).join("\n"));
188
+ }
189
+ } catch (err) {
190
+ console.log(err.message);
191
+ }
192
+ return handleExitOrDone(0, done);
193
+ }
194
+
195
+ /**
196
+ * Handle namespace listing
197
+ * @param {Object} runner - Runner instance
198
+ * @param {Object} opts - Options
199
+ * @param {Function} done - Optional callback
200
+ * @returns {void}
201
+ */
202
+ function handleNamespaceListing(runner, opts, done) {
203
+ flushLogger(opts);
204
+ console.log(runner._tasks._namespaces.join("\n"));
205
+ return handleExitOrDone(0, done);
206
+ }
207
+
208
+ /**
209
+ * Handle help display
210
+ * @param {Object} runner - Runner instance
211
+ * @param {CliContext} cliContext - Command context
212
+ * @param {Object} opts - Options
213
+ * @param {string} cmdName - Command name
214
+ * @param {Function} done - Optional callback
215
+ * @returns {void}
216
+ */
217
+ function handleHelp(runner, cliContext, opts, cmdName, done) {
218
+ flushLogger(opts);
219
+ runner.printTasks();
220
+ /* istanbul ignore if */
221
+ if (!opts.quiet) {
222
+ console.log(`${usage}`);
223
+ console.log(
224
+ chalk.bold(" Help:"),
225
+ `${cmdName} -h`,
226
+ chalk.bold(" Example:"),
227
+ `${cmdName} build\n`
228
+ );
229
+ }
230
+ return handleExitOrDone(1, done);
231
+ }
232
+
233
+ /**
234
+ * Setup node_modules bin in PATH
235
+ * @param {Object} opts - Options
236
+ */
237
+ function setupNodeModulesBin(opts) {
238
+ if (opts.nmbin) {
239
+ const nmBin = Path.join(opts.cwd, "node_modules", ".bin");
240
+ if (Fs.existsSync(nmBin)) {
241
+ const x = chalk.magenta(`${xsh.pathCwd.replace(nmBin, ".")}`);
242
+ const pathStr = env.get(envPath.envKey) || "";
243
+ const updated = envPath.addToFront(nmBin);
244
+ if (updated !== pathStr) {
245
+ logger.log(`Added ${x} to front of PATH`);
246
+ } else if (!env.get(env.xrunId)) {
247
+ logger.log(`PATH already contains ${x}`, pathStr);
248
+ }
249
+ }
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Setup environment variables
255
+ */
256
+ function setupEnvironment() {
257
+ if (!env.get(env.xrunId)) {
258
+ env.set(env.xrunId, "1");
259
+ } else {
260
+ env.set(env.xrunId, parseInt(env.get(env.xrunId)) + 1);
261
+ }
262
+
263
+ if (!env.has(env.forceColor)) {
264
+ env.set(env.forceColor, "1");
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Process task arguments
270
+ * @param {Array} tasks - Task arguments
271
+ * @param {Object} opts - Options
272
+ * @returns {Array} Processed tasks
273
+ */
274
+ function processTasks(tasks, opts) {
275
+ tasks = tasks.map(x => {
276
+ if (x.startsWith("/") && x.indexOf("/", 1) > 1) {
277
+ return x.substring(1);
278
+ }
279
+ return x;
280
+ });
281
+
282
+ if (tasks[0].startsWith("[")) {
283
+ let arrayStr;
284
+ try {
285
+ arrayStr = tasks.join(" ");
286
+ tasks = parseArray(arrayStr);
287
+ } catch (e) {
288
+ console.log(
289
+ "Parsing array of tasks failed:",
290
+ chalk.red(`${e.message}:`),
291
+ chalk.cyan(arrayStr)
292
+ );
293
+ return null;
294
+ }
295
+ }
296
+
297
+ if (tasks.length > 1 && tasks[0] !== "." && opts && opts.serial) {
298
+ tasks = ["."].concat(tasks);
299
+ }
300
+
301
+ return tasks;
302
+ }
303
+
304
+ /**
305
+ * Handle quiet flag setting in environment
306
+ * @param {Object} jsonMeta - Command metadata
307
+ * @param {Object} opts - Command options
308
+ * @returns {boolean} - Whether quiet mode is enabled
309
+ */
310
+ function handleQuietFlag(jsonMeta, opts) {
311
+ if (jsonMeta.source.quiet === "default") {
312
+ opts.quiet = env.get(env.xrunQuiet) === "1";
313
+ jsonMeta.source.quiet = "env";
314
+ } else if (opts.quiet) {
315
+ env.set(env.xrunQuiet, "1");
316
+ }
317
+ return opts.quiet;
318
+ }
319
+
320
+ /**
321
+ * Main entry point for xrun
322
+ * @param {Array} argv - Command line arguments
323
+ * @param {number} offset - Argument offset
324
+ * @param {string} xrunPath - Path to xrun
325
+ * @param {Function} done - Optional callback
326
+ * @returns {*} Runner result or void
327
+ */
328
+ async function xrunMain(argv, offset, xrunPath = "", done = null) {
329
+ let cmdName = "xrun";
330
+ const cwd = WrapProcess.cwd();
331
+
332
+ if (!argv) {
333
+ cmdName = Path.basename(WrapProcess.argv[1]);
334
+ argv = WrapProcess.argv;
335
+ offset = 2;
336
+ } else {
337
+ cmdName = "xrun";
338
+ }
339
+
340
+ // Handle CLI options listing
341
+ if (handleCliOptions(argv, offset, done)) return;
342
+
343
+ // Find and load runner module
344
+ const { runner, foundPath } = findRunnerModule(xrunPath);
345
+ const rawCmdArgs = await parseCmdArgs.parseArgs(argv, offset, foundPath);
346
+
347
+ // Create CliContext as the primary interface
348
+ const cliContext = new CliContext(rawCmdArgs);
349
+
350
+ const numTasks = runner.countTasks();
351
+ const jsonMeta = cliContext.getMetadata();
352
+ const opts = cliContext.getGlobalOptions();
353
+
354
+ // Handle quiet flag
355
+ handleQuietFlag(jsonMeta, opts);
356
+
357
+ // Handle no tasks case
358
+ if (numTasks === 0) {
359
+ return handleNoTasks(cliContext, cwd, done, opts);
360
+ }
361
+ // Handle task listing
362
+ else if (jsonMeta.source.list !== "default") {
363
+ return handleTaskListing(runner, opts, done);
364
+ }
365
+ // Handle namespace listing
366
+ else if (opts.ns) {
367
+ return handleNamespaceListing(runner, opts, done);
368
+ }
369
+
370
+ // Handle help display
371
+ /* istanbul ignore if */
372
+ if (cliContext.getTasks().length === 0) {
373
+ /* istanbul ignore next */
374
+ return handleHelp(runner, cliContext, opts, cmdName, done);
375
+ }
376
+
377
+ flushLogger(opts);
378
+
379
+ // Setup environment
380
+ setupNodeModulesBin(opts);
381
+ setupEnvironment();
382
+
383
+ // Configure runner with CliContext
384
+ if (runner.stopOnError === undefined || jsonMeta.source.soe !== "default") {
385
+ runner.stopOnError = cliContext.getStopOnError();
386
+ }
387
+
388
+ // Set CliContext on runner
389
+ runner.setCliContext(cliContext);
390
+
391
+ // Process tasks using CliContext
392
+ const processedTasks = processTasks(cliContext.getTasks(), opts);
393
+ /* istanbul ignore next */
394
+ if (processedTasks === null) {
395
+ /* istanbul ignore next */
396
+ return handleExitOrDone(1, done);
397
+ }
398
+
399
+ processEnvOptions(opts);
400
+
401
+ // Run tasks with CliContext already set on runner
402
+ return runner.run(processedTasks.length === 1 ? processedTasks[0] : processedTasks, done);
403
+ }
404
+
405
+ import { INTERNALS } from "../lib/defaults.js";
406
+ export { xrunMain };
407
+
408
+ export default {
409
+ xrunMain,
410
+ [INTERNALS]: {
411
+ flushLogger,
412
+ handleExitOrDone,
413
+ handleCliOptions,
414
+ findRunnerModule,
415
+ handleNoTasks,
416
+ handleTaskListing,
417
+ handleNamespaceListing,
418
+ handleHelp,
419
+ setupNodeModulesBin,
420
+ setupEnvironment,
421
+ processTasks,
422
+ handleQuietFlag,
423
+ processEnvOptions
424
+ }
425
+ };