@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.
- package/LICENSE +201 -0
- package/README.md +720 -0
- package/bin/qrun.js +3 -0
- package/bin/xrun.js +42 -0
- package/cli/check-global.js +6 -0
- package/cli/ck.js +6 -0
- package/cli/cli-options.js +98 -0
- package/cli/config.js +8 -0
- package/cli/env.js +33 -0
- package/cli/npm-loader.js +56 -0
- package/cli/parse-cmd-args.js +156 -0
- package/cli/provider-packages.js +73 -0
- package/cli/search-up-task-file.js +92 -0
- package/cli/task-file.js +192 -0
- package/cli/ts-runner.js +51 -0
- package/cli/usage.js +6 -0
- package/cli/wrap-process.js +27 -0
- package/cli/xrun-main.js +425 -0
- package/cli/xrun.js +8 -0
- package/lib/chalk.js +11 -0
- package/lib/cli-context.js +145 -0
- package/lib/defaults.js +32 -0
- package/lib/gen-xqid.js +10 -0
- package/lib/index.js +12 -0
- package/lib/logger.js +105 -0
- package/lib/ns-order.js +76 -0
- package/lib/print-tasks/index.js +105 -0
- package/lib/reporters/console.js +133 -0
- package/lib/stringify.js +16 -0
- package/lib/util/parse-array.js +5 -0
- package/lib/util/update-env.js +16 -0
- package/lib/xqitem.js +70 -0
- package/lib/xqtor.js +814 -0
- package/lib/xqtree.js +41 -0
- package/lib/xrun-instance.js +33 -0
- package/lib/xrun.d.ts +89 -0
- package/lib/xrun.js +344 -0
- package/lib/xtask-spec.js +63 -0
- package/lib/xtasks.js +137 -0
- package/package.json +93 -0
package/cli/task-file.js
ADDED
|
@@ -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 };
|
package/cli/ts-runner.js
ADDED
|
@@ -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,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;
|
package/cli/xrun-main.js
ADDED
|
@@ -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
|
+
};
|