no-mistakes 0.58.0 → 0.59.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 CHANGED
@@ -48,6 +48,10 @@ const report = await analyzeProject({
48
48
  Use `analyzeProject()` when reports share a root and configuration. Dedicated
49
49
  functions remain convenient for a single query:
50
50
 
51
+ `testsWhy` reports that receive `planJson` are materialized in a private
52
+ temporary directory and cleaned up after success, native rejection, or
53
+ preparation failure, including when they are batched through `analyzeProject()`.
54
+
51
55
  ````js
52
56
  const {
53
57
  dependencies,
package/index.js CHANGED
@@ -7,7 +7,6 @@ const planning = require("./planning");
7
7
  const { writePlanningImpactArtifacts: writeArtifacts } = require("./planning-impact-artifacts");
8
8
  const { createPlanningArtifactLock } = require("./planning-impact-artifacts-lock");
9
9
  const { createWorkflowTopologyIndex } = require("./workflow-topology-index");
10
- const fs = require("node:fs");
11
10
  const path = require("node:path");
12
11
 
13
12
  async function callJson(fn, options) {
@@ -64,7 +63,6 @@ const jsonApis = createJsonApis({
64
63
  symbols: "symbolsJson",
65
64
  });
66
65
 
67
- const PLAN_INPUT_REPORTS = new Set(["testsComment", "testsGraph", "testsGraphMermaid"]);
68
66
  const CAMELIZE_REPORTS = new Set(["testsPlan", "testsImpact", "testsTargets", "testsGraph"]);
69
67
  const acquirePlanningArtifactLock = createPlanningArtifactLock(native);
70
68
 
@@ -73,18 +71,7 @@ async function analyzeProject(options = {}) {
73
71
  const generatedDirs = [];
74
72
  try {
75
73
  if (Array.isArray(request.reports)) {
76
- request.reports = await Promise.all(
77
- request.reports.map(async (report) => {
78
- if (report.type === "testsWhy") {
79
- const prepared = await planning.prepareWhyPlan(report);
80
- if (prepared.generatedDir) generatedDirs.push(prepared.generatedDir);
81
- return prepared.request;
82
- }
83
- return PLAN_INPUT_REPORTS.has(report.type)
84
- ? await planning.decamelizePlanOptions(report)
85
- : report;
86
- }),
87
- );
74
+ request.reports = await planning.prepareAnalyzeProjectReports(request.reports, generatedDirs);
88
75
  }
89
76
  const result = await jsonApis.analyzeProject(request);
90
77
  for (const report of result.reports || []) {
@@ -114,40 +101,8 @@ async function writePlanningImpactArtifacts(options) {
114
101
  );
115
102
  }
116
103
 
117
- const topologyMemo = new Map();
118
-
119
- async function ciTopology(options) {
120
- const root = path.resolve((options && options.root) || process.cwd());
121
- const configPath = path.resolve(root, (options && options.config) || ".no-mistakes.yml");
122
- let mtime = 0;
123
- try {
124
- mtime = fs.statSync(configPath).mtimeMs;
125
- } catch {
126
- mtime = 0;
127
- }
128
- const workflows = JSON.stringify(
129
- []
130
- .concat((options && options.workflows) || [])
131
- .map(String)
132
- .sort(),
133
- );
134
- const identity = `${root}\0${configPath}\0`;
135
- const key = `${identity}${mtime}\0${workflows}`;
136
- const stale = [];
137
- for (const memoKey of topologyMemo.keys()) {
138
- if (!memoKey.startsWith(identity)) continue;
139
- const memoMtime = memoKey.slice(identity.length).split("\0")[0];
140
- if (memoMtime !== String(mtime)) stale.push(memoKey);
141
- }
142
- for (const memoKey of stale) topologyMemo.delete(memoKey);
143
- const cached = topologyMemo.get(key);
144
- if (cached) return cached.then((value) => structuredClone(value));
145
- const pending = jsonApis.ciTopology({ ...options, root }).catch((error) => {
146
- topologyMemo.delete(key);
147
- throw error;
148
- });
149
- topologyMemo.set(key, pending);
150
- return pending.then((value) => structuredClone(value));
104
+ async function ciTopology(options = {}) {
105
+ return jsonApis.ciTopology({ ...options, root: path.resolve(options.root || process.cwd()) });
151
106
  }
152
107
 
153
108
  const version = () => native.version();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "no-mistakes",
3
- "version": "0.58.0",
3
+ "version": "0.59.0",
4
4
  "description": "Deterministic impact maps, test plans, Playwright coverage, and repository checks for coding agents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,10 +45,10 @@
45
45
  "access": "public"
46
46
  },
47
47
  "optionalDependencies": {
48
- "no-mistakes-darwin-arm64": "0.58.0",
49
- "no-mistakes-darwin-x64": "0.58.0",
50
- "no-mistakes-linux-arm64-gnu": "0.58.0",
51
- "no-mistakes-linux-x64-gnu": "0.58.0",
52
- "no-mistakes-win32-x64-msvc": "0.58.0"
48
+ "no-mistakes-darwin-arm64": "0.59.0",
49
+ "no-mistakes-darwin-x64": "0.59.0",
50
+ "no-mistakes-linux-arm64-gnu": "0.59.0",
51
+ "no-mistakes-linux-x64-gnu": "0.59.0",
52
+ "no-mistakes-win32-x64-msvc": "0.59.0"
53
53
  }
54
54
  }
package/planning.js CHANGED
@@ -7,6 +7,7 @@ const { resolveNativePackage } = require("./scripts/native-package");
7
7
  const native = require(
8
8
  process.env.NO_MISTAKES_TEST_NAPI_ADDON_PATH || resolveNativePackage().addonPath,
9
9
  );
10
+ const PLAN_INPUT_REPORTS = new Set(["testsComment", "testsGraph", "testsGraphMermaid"]);
10
11
 
11
12
  async function callJson(fn, options) {
12
13
  const input = Buffer.from(JSON.stringify(options || {}));
@@ -94,7 +95,15 @@ async function prepareWhyPlan(options = {}) {
94
95
  }
95
96
  if (document == null) return { request: next };
96
97
  const generatedDir = await fs.mkdtemp(path.join(os.tmpdir(), "no-mistakes-why-"));
97
- await fs.writeFile(path.join(generatedDir, "plan.json"), JSON.stringify(loadPlanJson(document)));
98
+ try {
99
+ await fs.writeFile(
100
+ path.join(generatedDir, "plan.json"),
101
+ JSON.stringify(loadPlanJson(document)),
102
+ );
103
+ } catch (error) {
104
+ await removeGeneratedDir(generatedDir);
105
+ throw error;
106
+ }
98
107
  next.plan = path.join(generatedDir, "plan.json");
99
108
  delete next.planJson;
100
109
  return { request: next, generatedDir };
@@ -109,6 +118,21 @@ async function removeGeneratedDir(generatedDir) {
109
118
  await fs.rm(generatedDir, { recursive: true, force: true }).catch(() => {});
110
119
  }
111
120
 
121
+ async function prepareAnalyzeProjectReports(reports, generatedDirs) {
122
+ const preparations = reports.map(async (report) => {
123
+ if (report.type === "testsWhy") {
124
+ const prepared = await prepareWhyPlan(report);
125
+ if (prepared.generatedDir) generatedDirs.push(prepared.generatedDir);
126
+ return prepared.request;
127
+ }
128
+ return PLAN_INPUT_REPORTS.has(report.type) ? await decamelizePlanOptions(report) : report;
129
+ });
130
+ const settled = await Promise.allSettled(preparations);
131
+ const rejected = settled.find((result) => result.status === "rejected");
132
+ if (rejected) throw rejected.reason;
133
+ return settled.map((result) => result.value);
134
+ }
135
+
112
136
  function camelizeWhy(value) {
113
137
  if (!value || typeof value !== "object" || Array.isArray(value)) {
114
138
  return camelizeValue(value);
@@ -176,6 +200,7 @@ module.exports = {
176
200
  camelizeWhy,
177
201
  decamelizePlanOptions,
178
202
  materializeWhyPlan,
203
+ prepareAnalyzeProjectReports,
179
204
  prepareWhyPlan,
180
205
  removeGeneratedDir,
181
206
  testsComment,