ts-builds 4.2.0 → 4.3.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/README.md +22 -4
- package/dist/cli.js +80 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -221,6 +221,23 @@ npx ts-builds validate:core
|
|
|
221
221
|
npx ts-builds validate:landing
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
+
A `commands` entry named after a built-in step (`format`, `format:check`, `lint`, `lint:check`, `typecheck`,
|
|
225
|
+
`ts-types`, `test`, `test:watch`, `test:coverage`, `test:ui`, `build`, `build:watch`, `dev`, `preview`) replaces
|
|
226
|
+
that step everywhere: inside a chain, and when you run it directly (`ts-builds lint:check`, or
|
|
227
|
+
`ts-builds lint check`). A chain with one of those names takes precedence over both, as it does inside a chain.
|
|
228
|
+
`compile` can be overridden too, but only runs inside a chain. An override runs your command instead of
|
|
229
|
+
ts-builds' lint, so it sends no lint telemetry, and an override of `lint` or `lint:check` removes any old
|
|
230
|
+
`.ts-builds/lint-report.json`, so `lint:summary` can't read stale counts.
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"commands": {
|
|
235
|
+
"lint": "eslint ./src ./test --fix --max-warnings=0",
|
|
236
|
+
"lint:check": "eslint ./src ./test --max-warnings=0"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
224
241
|
### Aggregating lint results across a monorepo
|
|
225
242
|
|
|
226
243
|
Under a task runner, `ts-builds` runs per-package, so each package's ESLint output
|
|
@@ -299,8 +316,9 @@ Each run sends one JSON object:
|
|
|
299
316
|
|
|
300
317
|
- **Never sent:** file paths, source code or message text.
|
|
301
318
|
- **Sent as-is:** the `package.json` name, left out when there isn't one, and rule ids as your config
|
|
302
|
-
names them.
|
|
303
|
-
|
|
319
|
+
names them. A plugin's declared name is used only when it looks like an npm package name, and
|
|
320
|
+
otherwise is replaced by the name derived from its rule prefix, such as `eslint-plugin-<prefix>`. Its
|
|
321
|
+
declared version is used only when it's semver.
|
|
304
322
|
- **`project`:** a salted hash of the git remote, so one repo's packages group together. It's a
|
|
305
323
|
stable pseudonym, not anonymity: anyone who knows the remote URL can compute it. It's left out
|
|
306
324
|
when there's no remote.
|
|
@@ -311,8 +329,8 @@ Each run sends one JSON object:
|
|
|
311
329
|
- **`enabled`:** the plugin rules that are on, with their severity, read from the first linted file's
|
|
312
330
|
config. It tells a rule with no findings apart from a rule that's off. Core rules are left out.
|
|
313
331
|
|
|
314
|
-
It never changes a run's result. It sends after lint finishes and its output is printed,
|
|
315
|
-
most 2 seconds, and ignores every error, so the exit code and output stay the same. A run that
|
|
332
|
+
It never changes a run's result. It sends after lint finishes and its output is printed, the send
|
|
333
|
+
waits at most 2 seconds, and it ignores every error, so the exit code and output stay the same. A run that
|
|
316
334
|
fails before linting, such as one with a broken config, sends nothing.
|
|
317
335
|
|
|
318
336
|
It doesn't cover `lint.useProjectEslint`, which spawns your own ESLint so ts-builds never sees the
|
package/dist/cli.js
CHANGED
|
@@ -343,6 +343,13 @@ const LINT_REPORT_VERSION = 1;
|
|
|
343
343
|
function lintReportPath(dir = targetDir) {
|
|
344
344
|
return join(dir, ".ts-builds", "lint-report.json");
|
|
345
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Remove the sidecar. When a `commands` override replaces `lint` or `lint:check`, ts-builds' own lint
|
|
348
|
+
* doesn't run and writes no report, so an old one would leave `lint:summary` reading stale counts.
|
|
349
|
+
*/
|
|
350
|
+
function forgetLintReport(dir = targetDir) {
|
|
351
|
+
rmSync(lintReportPath(dir), { force: true });
|
|
352
|
+
}
|
|
346
353
|
/** The `name` in `<dir>/package.json`, when the file exists, parses and has a non-empty name. */
|
|
347
354
|
function packageJsonName(dir = targetDir) {
|
|
348
355
|
const pkgPath = join(dir, "package.json");
|
|
@@ -426,9 +433,10 @@ function resolveEnv(env) {
|
|
|
426
433
|
...env
|
|
427
434
|
} : process.env;
|
|
428
435
|
}
|
|
429
|
-
|
|
436
|
+
/** Spawn one shell command line; resolves with its exit code, or 1 if it can't start */
|
|
437
|
+
function spawnShell(commandLine, label, options) {
|
|
430
438
|
return new Promise((resolve) => {
|
|
431
|
-
const child = spawn(
|
|
439
|
+
const child = spawn(commandLine, {
|
|
432
440
|
cwd: resolveCwd(options.cwd),
|
|
433
441
|
env: resolveEnv(options.env),
|
|
434
442
|
stdio: "inherit",
|
|
@@ -438,27 +446,24 @@ function runCommand(command, args, options = {}) {
|
|
|
438
446
|
resolve(code ?? 1);
|
|
439
447
|
});
|
|
440
448
|
child.on("error", (err) => {
|
|
441
|
-
console.error(`Failed to run
|
|
449
|
+
console.error(`Failed to run${label}: ${err.message}`);
|
|
442
450
|
resolve(1);
|
|
443
451
|
});
|
|
444
452
|
});
|
|
445
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Run `command` with `args` through the shell, so `.bin` shims (including
|
|
456
|
+
* Windows `.cmd` files) and commands such as `npx eslint` resolve. `args` are
|
|
457
|
+
* joined into the command line as given, not escaped: quote any argument that
|
|
458
|
+
* holds spaces or shell characters, as `prettierFormatArgs` does. Joining here
|
|
459
|
+
* rather than passing `args` to `spawn` gives the same command line Node built
|
|
460
|
+
* before, without its DEP0190 warning, which flags exactly that unescaped join.
|
|
461
|
+
*/
|
|
462
|
+
function runCommand(command, args, options = {}) {
|
|
463
|
+
return spawnShell([command, ...args].join(" "), ` ${command}`, options);
|
|
464
|
+
}
|
|
446
465
|
function runShellCommand(shellCmd, options = {}) {
|
|
447
|
-
return
|
|
448
|
-
const child = spawn(shellCmd, {
|
|
449
|
-
cwd: resolveCwd(options.cwd),
|
|
450
|
-
env: resolveEnv(options.env),
|
|
451
|
-
stdio: "inherit",
|
|
452
|
-
shell: true
|
|
453
|
-
});
|
|
454
|
-
child.on("close", (code) => {
|
|
455
|
-
resolve(code ?? 1);
|
|
456
|
-
});
|
|
457
|
-
child.on("error", (err) => {
|
|
458
|
-
console.error(`Failed to run: ${err.message}`);
|
|
459
|
-
resolve(1);
|
|
460
|
-
});
|
|
461
|
-
});
|
|
466
|
+
return spawnShell(shellCmd, "", options);
|
|
462
467
|
}
|
|
463
468
|
|
|
464
469
|
// src/cli/commands/build.ts
|
|
@@ -606,7 +611,7 @@ function prettierConfigArg() {
|
|
|
606
611
|
* no prettier config of their own (see `prettierConfigArg`), so we fill the gap without ever
|
|
607
612
|
* overriding a config they wrote.
|
|
608
613
|
*
|
|
609
|
-
* Bundled paths are quoted because `runCommand`
|
|
614
|
+
* Bundled paths are quoted because `runCommand` runs one shell command line (the shell strips the
|
|
610
615
|
* quotes), keeping them correct when the install path contains spaces.
|
|
611
616
|
*/
|
|
612
617
|
function prettierFormatArgs(check, bundledIgnore = bundledPrettierIgnorePath(), bundledConfig = prettierConfigArg()) {
|
|
@@ -2405,6 +2410,53 @@ function runSize(args) {
|
|
|
2405
2410
|
function isRunFnCommand(cmd) {
|
|
2406
2411
|
return "runFn" in cmd;
|
|
2407
2412
|
}
|
|
2413
|
+
/** The built-in chain steps. A `commands` entry with one of these names replaces it, in a chain or run directly. */
|
|
2414
|
+
const BUILTIN_STEP_NAMES = [
|
|
2415
|
+
"format",
|
|
2416
|
+
"format:check",
|
|
2417
|
+
"lint",
|
|
2418
|
+
"lint:check",
|
|
2419
|
+
"typecheck",
|
|
2420
|
+
"ts-types",
|
|
2421
|
+
"test",
|
|
2422
|
+
"test:watch",
|
|
2423
|
+
"test:coverage",
|
|
2424
|
+
"test:ui",
|
|
2425
|
+
"build",
|
|
2426
|
+
"build:watch",
|
|
2427
|
+
"dev",
|
|
2428
|
+
"preview",
|
|
2429
|
+
"compile"
|
|
2430
|
+
];
|
|
2431
|
+
const isBuiltinStepName = (name) => BUILTIN_STEP_NAMES.includes(name);
|
|
2432
|
+
/** The step a top-level command runs: `lint check` is `lint:check`, `test watch` is `test:watch`, `test run` is `test` */
|
|
2433
|
+
function stepName(command, subCommand) {
|
|
2434
|
+
if ((command === "lint" || command === "format") && subCommand === "check") return `${command}:check`;
|
|
2435
|
+
if (command === "build" && subCommand === "watch") return "build:watch";
|
|
2436
|
+
if (command === "test" && (subCommand === "watch" || subCommand === "coverage" || subCommand === "ui")) return `test:${subCommand}`;
|
|
2437
|
+
return command;
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* The user's replacement for a built-in step, if their config has one. Run directly, a built-in
|
|
2441
|
+
* resolves the same way it does inside a chain (`runStep`): `config.commands[step] ?? builtin`.
|
|
2442
|
+
*/
|
|
2443
|
+
function builtinOverride(step, config) {
|
|
2444
|
+
return Option(config.commands[step]).filter(() => isBuiltinStepName(step));
|
|
2445
|
+
}
|
|
2446
|
+
/** An override of `lint` or `lint:check` replaces ts-builds' lint, so its old report must go */
|
|
2447
|
+
function forgetLintReportIfLint(step) {
|
|
2448
|
+
if (step === "lint" || step === "lint:check") forgetLintReport();
|
|
2449
|
+
}
|
|
2450
|
+
/** Run a `commands` entry on its own: check its cwd, then run it through the shell */
|
|
2451
|
+
async function runConfiguredCommand(name, cmdDef) {
|
|
2452
|
+
const cwdError = escapingValidateCwdError(name, cmdDef);
|
|
2453
|
+
if (cwdError) {
|
|
2454
|
+
console.error(cwdError);
|
|
2455
|
+
return 1;
|
|
2456
|
+
}
|
|
2457
|
+
forgetLintReportIfLint(name);
|
|
2458
|
+
return runShellCommand(cmdDef.run, { cwd: cmdDef.cwd });
|
|
2459
|
+
}
|
|
2408
2460
|
function getBuiltinCommands(config) {
|
|
2409
2461
|
return {
|
|
2410
2462
|
format: { runFn: () => runFormat(false) },
|
|
@@ -2455,6 +2507,7 @@ async function runStep(step, config, builtins, path) {
|
|
|
2455
2507
|
console.error(cwdError);
|
|
2456
2508
|
return 1;
|
|
2457
2509
|
}
|
|
2510
|
+
if (Object.hasOwn(config.commands, step)) forgetLintReportIfLint(step);
|
|
2458
2511
|
const cwdLabel = "cwd" in cmdDef && cmdDef.cwd ? ` (in ${cmdDef.cwd})` : "";
|
|
2459
2512
|
console.log(`\n▶ Running ${step}...${cwdLabel}`);
|
|
2460
2513
|
const code = isRunFnCommand(cmdDef) ? await cmdDef.runFn() : await runShellCommand(cmdDef.run, { cwd: cmdDef.cwd });
|
|
@@ -2472,6 +2525,12 @@ async function runValidate(chainName = "validate") {
|
|
|
2472
2525
|
// src/cli.ts
|
|
2473
2526
|
const command = process.argv[2] || "init";
|
|
2474
2527
|
const subCommand = process.argv[3];
|
|
2528
|
+
const step = stepName(command, subCommand);
|
|
2529
|
+
if (isBuiltinStepName(step)) {
|
|
2530
|
+
const config = loadConfig();
|
|
2531
|
+
if (Object.hasOwn(config.chains, step)) process.exit(await runValidate(step));
|
|
2532
|
+
await builtinOverride(step, config).fold(() => Promise.resolve(), async (cmd) => process.exit(await runConfiguredCommand(step, cmd)));
|
|
2533
|
+
}
|
|
2475
2534
|
switch (command) {
|
|
2476
2535
|
case "help":
|
|
2477
2536
|
case "--help":
|
|
@@ -2550,16 +2609,8 @@ switch (command) {
|
|
|
2550
2609
|
default: {
|
|
2551
2610
|
const config = loadConfig();
|
|
2552
2611
|
if (config.chains[command]) process.exit(await runValidate(command));
|
|
2553
|
-
else if (config.commands[command])
|
|
2554
|
-
|
|
2555
|
-
const cwdError = escapingValidateCwdError(command, cmdDef);
|
|
2556
|
-
if (cwdError) {
|
|
2557
|
-
console.error(cwdError);
|
|
2558
|
-
process.exit(1);
|
|
2559
|
-
}
|
|
2560
|
-
const code = await runShellCommand(cmdDef.run, { cwd: cmdDef.cwd });
|
|
2561
|
-
process.exit(code);
|
|
2562
|
-
} else {
|
|
2612
|
+
else if (config.commands[command]) process.exit(await runConfiguredCommand(command, config.commands[command]));
|
|
2613
|
+
else {
|
|
2563
2614
|
console.error(`Unknown command: ${command}`);
|
|
2564
2615
|
console.log("Run 'ts-builds help' for usage information.");
|
|
2565
2616
|
process.exit(1);
|
package/package.json
CHANGED