no-mistakes 0.52.2 → 0.53.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.
package/README.md CHANGED
@@ -15,8 +15,13 @@ npx no-mistakes dependents src/utils.mts --json
15
15
  npx no-mistakes symbols src/utils.mts --json
16
16
  npx no-mistakes import-usages --root . --filter 'src/**' --json
17
17
  npx no-mistakes check --json
18
+ npx no-mistakes planning-impact --changed-files /private/run/changed-files.txt --output-dir /private/run
18
19
  ```
19
20
 
21
+ `planning-impact` is supplied by the npm package, not the Cargo-installed
22
+ binary. It writes the private four-report artifact contract from one prepared
23
+ analysis. See the [CLI reference](../../docs/cli/planning-impact.md).
24
+
20
25
  Programmatic Node usage loads the same Rust analysis through N-API:
21
26
 
22
27
  ```js
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { spawn } = require("node:child_process");
5
+ const { join } = require("node:path");
6
+ const { boundedDiagnostic } = require("../planning-impact-cli");
7
+
8
+ const VALUE_OPTIONS = new Set(["--timeout", "--lock-timeout", "--jobs", "--profile", "-j"]);
9
+ const FLAG_OPTIONS = new Set(["--fail-on-lock"]);
10
+ const INLINE_VALUE_OPTION = /^(?:--(?:timeout|lock-timeout|jobs|profile)=|-j)/u;
11
+
12
+ function firstSubcommand(argv) {
13
+ const index = firstSubcommandIndex(argv);
14
+ return index === undefined ? undefined : argv[index];
15
+ }
16
+
17
+ function firstSubcommandIndex(argv) {
18
+ for (let index = 0; index < argv.length; index += 1) {
19
+ const value = argv[index];
20
+ if (value === "--") return undefined;
21
+ if (VALUE_OPTIONS.has(value)) {
22
+ index += 1;
23
+ continue;
24
+ }
25
+ if (FLAG_OPTIONS.has(value) || INLINE_VALUE_OPTION.test(value)) {
26
+ continue;
27
+ }
28
+ return index;
29
+ }
30
+ return undefined;
31
+ }
32
+
33
+ function planningImpactArgs(argv) {
34
+ const index = firstSubcommandIndex(argv);
35
+ return [...argv.slice(0, index), ...argv.slice(index + 1)];
36
+ }
37
+
38
+ function launchNative(argv, spawnFn = spawn, io = process, kill = process.kill) {
39
+ const child = spawnFn(join(__dirname, "no-mistakes"), argv, { stdio: "inherit" });
40
+ child.on("error", (error) => {
41
+ io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
42
+ io.exitCode = 1;
43
+ });
44
+ child.on("exit", (code, signal) => {
45
+ if (signal) kill(process.pid, signal);
46
+ else io.exitCode = code === null ? 1 : code;
47
+ });
48
+ return child;
49
+ }
50
+
51
+ async function main(
52
+ argv = process.argv.slice(2),
53
+ {
54
+ io = process,
55
+ launchNativeFn = launchNative,
56
+ runPlanning = (args, planningIo) =>
57
+ require("../planning-impact-cli").main(args, process.cwd(), planningIo),
58
+ } = {},
59
+ ) {
60
+ if (firstSubcommand(argv) === "planning-impact") {
61
+ try {
62
+ await runPlanning(planningImpactArgs(argv), io);
63
+ } catch (error) {
64
+ io.stderr.write(boundedDiagnostic(error));
65
+ io.exitCode = 1;
66
+ }
67
+ } else {
68
+ launchNativeFn(argv);
69
+ }
70
+ }
71
+
72
+ if (require.main === module) void main();
73
+
74
+ module.exports = { firstSubcommand, launchNative, main, planningImpactArgs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "no-mistakes",
3
- "version": "0.52.2",
3
+ "version": "0.53.1",
4
4
  "description": "Static codebase analysis tools for TS/JS dependencies, dependents, and symbols",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -9,7 +9,7 @@
9
9
  "directory": "packages/no-mistakes"
10
10
  },
11
11
  "bin": {
12
- "no-mistakes": "bin/no-mistakes"
12
+ "no-mistakes": "bin/no-mistakes.js"
13
13
  },
14
14
  "files": [
15
15
  "bin/",
@@ -22,6 +22,7 @@
22
22
  "planning-impact-artifacts-inputs.js",
23
23
  "planning-impact-artifacts-lock.js",
24
24
  "planning-impact-artifacts-privacy.js",
25
+ "planning-impact-cli.js",
25
26
  "planning.js",
26
27
  "workflow-topology-index.js",
27
28
  "workflow-topology-index-helpers.js",
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+
3
+ const { parseArgs } = require("node:util");
4
+
5
+ const USAGE = [
6
+ "usage: no-mistakes planning-impact --changed-files <manifest>",
7
+ "--output-dir <directory> [--broad]",
8
+ ].join(" ");
9
+
10
+ function parseNonNegativeInteger(name, value) {
11
+ if (!/^(?:0|[1-9][0-9]*)$/u.test(value)) {
12
+ throw new Error(`${name} must be a non-negative integer`);
13
+ }
14
+ const parsed = Number(value);
15
+ if (!Number.isSafeInteger(parsed)) throw new Error(`${name} must be a safe integer`);
16
+ return parsed;
17
+ }
18
+
19
+ function parsePlanningImpactArgs(argv, root) {
20
+ const { values } = parseArgs({
21
+ args: argv,
22
+ options: {
23
+ "changed-files": { type: "string" },
24
+ "output-dir": { type: "string" },
25
+ broad: { type: "boolean" },
26
+ timeout: { type: "string" },
27
+ "lock-timeout": { type: "string" },
28
+ "fail-on-lock": { type: "boolean" },
29
+ jobs: { type: "string", short: "j" },
30
+ profile: { type: "string" },
31
+ help: { type: "boolean", short: "h" },
32
+ },
33
+ strict: true,
34
+ });
35
+ if (values.help) return null;
36
+ if (!values["changed-files"] || !values["output-dir"]) throw new Error(USAGE);
37
+ if (values.profile !== undefined && values.profile !== "ci") {
38
+ throw new Error('profile must be "ci" when set');
39
+ }
40
+ const options = {
41
+ root,
42
+ changedFilesManifest: values["changed-files"],
43
+ outputDirectory: values["output-dir"],
44
+ broad: values.broad === true,
45
+ };
46
+ for (const [flag, key] of [
47
+ ["timeout", "timeout"],
48
+ ["lock-timeout", "lockTimeout"],
49
+ ["jobs", "jobs"],
50
+ ]) {
51
+ if (values[flag] !== undefined) options[key] = parseNonNegativeInteger(flag, values[flag]);
52
+ }
53
+ if (values["fail-on-lock"]) options.failOnLock = true;
54
+ if (values.profile) options.profile = values.profile;
55
+ return options;
56
+ }
57
+
58
+ async function runPlanningImpact(argv, root, writer) {
59
+ const options = parsePlanningImpactArgs(argv, root);
60
+ if (options) await writer(options);
61
+ return options;
62
+ }
63
+
64
+ function boundedDiagnostic(error) {
65
+ const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
66
+ return Buffer.from(`${detail}\n`)
67
+ .subarray(0, 4096)
68
+ .toString("utf8")
69
+ .replace(/\ufffd+$/u, "");
70
+ }
71
+
72
+ async function main(argv = process.argv.slice(2), root = process.cwd(), io = process, writer) {
73
+ const options = parsePlanningImpactArgs(argv, root);
74
+ if (!options) {
75
+ io.stdout.write(`${USAGE}\n`);
76
+ return;
77
+ }
78
+ await (writer || require("./index").writePlanningImpactArtifacts)(options);
79
+ }
80
+
81
+ module.exports = { USAGE, boundedDiagnostic, main, parsePlanningImpactArgs, runPlanningImpact };
@@ -41,6 +41,8 @@ export interface ResolvedTrigger {
41
41
  name: string;
42
42
  paths: string[];
43
43
  targets: string[];
44
+ /** Effective changed-test expansion policy; present only for structured triggers. */
45
+ includeChangedTests?: boolean;
44
46
  source: "triggers" | "projects";
45
47
  }
46
48