@hublo/sentinel 1.4.0-alpha.40 → 1.4.0-alpha.42

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.
@@ -15,10 +15,10 @@ import {
15
15
  palette,
16
16
  registerAdapters,
17
17
  resolve
18
- } from "../chunk-USVNSL76.js";
18
+ } from "../chunk-BF5SF6HQ.js";
19
19
  import {
20
20
  resolveContext
21
- } from "../chunk-TDQGNNCV.js";
21
+ } from "../chunk-YHNQONE2.js";
22
22
  import "../chunk-O7REVMOC.js";
23
23
  import "../chunk-QXFCZON7.js";
24
24
  import {
@@ -145,7 +145,7 @@ async function runValidate(ctx) {
145
145
  );
146
146
  }
147
147
  if (!ctx.targets.includes("test")) return 1;
148
- const { validateModuleTests } = await import("../validate-JUMFX4ZF.js");
148
+ const { validateModuleTests } = await import("../validate-AUKAA6EG.js");
149
149
  const outcomes = modules.flatMap(
150
150
  (module) => validateModuleTests(module.name, module.root)
151
151
  );
@@ -53,7 +53,7 @@ import {
53
53
  selfCommand,
54
54
  splitToolArgs,
55
55
  walk
56
- } from "./chunk-TDQGNNCV.js";
56
+ } from "./chunk-YHNQONE2.js";
57
57
  import {
58
58
  parseJsonc
59
59
  } from "./chunk-QXFCZON7.js";
@@ -747,7 +747,8 @@ function resolveVitest(cwd) {
747
747
 
748
748
  // src/roles/test/adapters/vitest/vitest.adapter.ts
749
749
  import { spawnSync } from "node:child_process";
750
- import { isAbsolute as isAbsolute2, resolve as resolve9 } from "node:path";
750
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync2 } from "node:fs";
751
+ import { isAbsolute as isAbsolute2, join as join30, resolve as resolve9 } from "node:path";
751
752
 
752
753
  // src/core/config/tool-args.ts
753
754
  import { existsSync as existsSync6 } from "node:fs";
@@ -767,6 +768,25 @@ function splitToolArgs(cwd, toolArgs = [], { valueFlags = [] } = {}) {
767
768
  return { options, paths };
768
769
  }
769
770
 
771
+ // src/roles/test/ordering-is-a-permutation.ts
772
+ function lineCounts(source) {
773
+ const counts = /* @__PURE__ */ new Map();
774
+ for (const line of source.split("\n")) {
775
+ const trimmed = line.trim();
776
+ if (trimmed === "") continue;
777
+ counts.set(trimmed, (counts.get(trimmed) ?? 0) + 1);
778
+ }
779
+ return counts;
780
+ }
781
+ function lineLostByTheFixer(before, after) {
782
+ const had = lineCounts(before);
783
+ const has = lineCounts(after);
784
+ for (const [line, count] of had) {
785
+ if ((has.get(line) ?? 0) < count) return line;
786
+ }
787
+ return void 0;
788
+ }
789
+
770
790
  // src/core/config/nx-target.ts
771
791
  import { existsSync as existsSync7 } from "node:fs";
772
792
  import { join as join7 } from "node:path";
@@ -1963,6 +1983,25 @@ function rewriteHookReturnValue(source, program) {
1963
1983
  return { source: result, rewritten: edits.length };
1964
1984
  }
1965
1985
 
1986
+ // src/roles/test/codemod/asyncify.ts
1987
+ function alreadyAPromise(text) {
1988
+ return /^(?:Promise|PromiseLike)\s*</.test(text.trim());
1989
+ }
1990
+ function asyncifyEdits(source, fn) {
1991
+ const edits = [{ start: fn.start, end: fn.start, text: "async " }];
1992
+ const annotation = fn.returnType;
1993
+ const declared = annotation?.typeAnnotation;
1994
+ if (declared?.start === void 0 || declared.end === void 0) return edits;
1995
+ const text = source.slice(declared.start, declared.end);
1996
+ if (alreadyAPromise(text)) return edits;
1997
+ edits.push({
1998
+ start: declared.start,
1999
+ end: declared.end,
2000
+ text: `Promise<${text}>`
2001
+ });
2002
+ return edits;
2003
+ }
2004
+
1966
2005
  // src/roles/test/codemod/named-rules.ts
1967
2006
  import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
1968
2007
  import { createRequire as createRequire2 } from "node:module";
@@ -2474,7 +2513,7 @@ function applyNamedRules(source, program, fileName = "") {
2474
2513
  }
2475
2514
  });
2476
2515
  for (const fn of asyncify) {
2477
- edits.push({ start: fn.start, end: fn.start, text: "async " });
2516
+ edits.push(...asyncifyEdits(source, fn));
2478
2517
  }
2479
2518
  const namespaceLines = namespaces.planned().map(
2480
2519
  ({ specifier, binding }) => `import * as ${binding} from '${specifier}'${fileUsesSemicolons(source) ? ";" : ""}`
@@ -2545,6 +2584,12 @@ function rewriteIsolateModules(source, program) {
2545
2584
  const refusals = [];
2546
2585
  const asyncify = /* @__PURE__ */ new Set();
2547
2586
  let rewritten = 0;
2587
+ const awaitedCall = /* @__PURE__ */ new Map();
2588
+ walkWithEnclosingFunction(program, void 0, (node) => {
2589
+ if (node.type !== "AwaitExpression") return;
2590
+ const argument = node.argument;
2591
+ if (argument !== void 0) awaitedCall.set(argument, node);
2592
+ });
2548
2593
  walkWithEnclosingFunction(program, void 0, (node, enclosing) => {
2549
2594
  const unusable = unusableIsolatingCall(node);
2550
2595
  if (unusable !== void 0) {
@@ -2558,10 +2603,11 @@ function rewriteIsolateModules(source, program) {
2558
2603
  if (found === void 0) return;
2559
2604
  const callbackText = source.slice(found.callback.start, found.callback.end);
2560
2605
  const callback = found.callback.async === true ? callbackText : `async ${callbackText}`;
2561
- const indent = indentationAt(source, found.call.start);
2606
+ const replaced = awaitedCall.get(found.call) ?? found.call;
2607
+ const indent = indentationAt(source, replaced.start);
2562
2608
  edits.push({
2563
- start: found.call.start,
2564
- end: found.call.end,
2609
+ start: replaced.start,
2610
+ end: replaced.end,
2565
2611
  text: `vi.resetModules()
2566
2612
  ${indent}await (${callback})()`
2567
2613
  });
@@ -2569,7 +2615,7 @@ ${indent}await (${callback})()`
2569
2615
  if (enclosing !== void 0 && enclosing.async !== true) asyncify.add(enclosing);
2570
2616
  });
2571
2617
  for (const fn of asyncify) {
2572
- edits.push({ start: fn.start, end: fn.start, text: "async " });
2618
+ edits.push(...asyncifyEdits(source, fn));
2573
2619
  }
2574
2620
  const result = applyEdits(source, edits);
2575
2621
  return { source: result, rewritten, refusals };
@@ -5329,6 +5375,13 @@ function writtenFileLinter(cwd) {
5329
5375
  // src/roles/test/adapters/vitest/vitest.adapter.ts
5330
5376
  var FORMATTABLE = /\.(m|c)?(j|t)sx?$|\.json$/;
5331
5377
  var FORMAT_BATCH = 25;
5378
+ function readIfPossible(path) {
5379
+ try {
5380
+ return readFileSync24(path, "utf8");
5381
+ } catch {
5382
+ return void 0;
5383
+ }
5384
+ }
5332
5385
  var VitestAdapter = class extends BaseAdapter {
5333
5386
  target = "test";
5334
5387
  runner = "vitest";
@@ -5450,13 +5503,30 @@ var VitestAdapter = class extends BaseAdapter {
5450
5503
  const linter = writtenFileLinter(ctx.cwd);
5451
5504
  if (linter === void 0) return;
5452
5505
  let unsettled = 0;
5506
+ const reverted = [];
5453
5507
  for (let at = 0; at < written.length; at += FORMAT_BATCH) {
5454
5508
  const batch = written.slice(at, at + FORMAT_BATCH);
5509
+ const before = new Map(batch.map((file) => [file, readIfPossible(join30(ctx.cwd, file))]));
5455
5510
  const pass = spawnSync(linter.bin, [...linter.args, ...batch], {
5456
5511
  cwd: ctx.cwd,
5457
5512
  encoding: "utf8"
5458
5513
  });
5459
5514
  if (pass.status !== 0) unsettled += 1;
5515
+ for (const file of batch) {
5516
+ const was = before.get(file);
5517
+ if (was === void 0) continue;
5518
+ const now = readIfPossible(join30(ctx.cwd, file));
5519
+ if (now === void 0 || now === was) continue;
5520
+ const lost = lineLostByTheFixer(was, now);
5521
+ if (lost === void 0) continue;
5522
+ writeFileSync2(join30(ctx.cwd, file), was);
5523
+ reverted.push(`${file}: \`${lost.slice(0, 80)}\``);
5524
+ }
5525
+ }
5526
+ if (reverted.length > 0) {
5527
+ this.say(
5528
+ `${linter.origin} did not only reorder: it removed ${reverted.length} line(s) it was given, so those file(s) were restored to what this migration wrote and their imports are left for your own lint to order. Reordering imports moves lines, it does not delete them. Measured on this repository, the rules that do this are \`prefer-arrow-callback\`, which un-does the function expression a mocked CLASS needs to be constructible, and unused-directive removal, which deletes a suppression for a rule your real lint target does run. First: ${reverted.slice(0, 3).join("; ")}`
5529
+ );
5460
5530
  }
5461
5531
  this.say(
5462
5532
  unsettled === 0 ? `ran ${linter.origin} with its fixer over the ${written.length} file(s) this wrote, so the import this migration adds is ordered the way your rules ask. Without it, \`import/order\` alone put 19 modules of this repository in the red.` : `ran ${linter.origin} with its fixer over the ${written.length} file(s) this wrote, and it still reports something it cannot fix. Adoption itself is applied. Run your own lint to see what: it may be rules this module already failed, which are not this migration's to settle.`
package/dist/index.js CHANGED
@@ -6,10 +6,10 @@ import {
6
6
  registerAdapters,
7
7
  resolve,
8
8
  setDefaultRunner
9
- } from "./chunk-USVNSL76.js";
9
+ } from "./chunk-BF5SF6HQ.js";
10
10
  import {
11
11
  BaseAdapter
12
- } from "./chunk-TDQGNNCV.js";
12
+ } from "./chunk-YHNQONE2.js";
13
13
  import "./chunk-O7REVMOC.js";
14
14
  import "./chunk-QXFCZON7.js";
15
15
  import "./chunk-NW6UHNYX.js";
@@ -7,7 +7,7 @@ import {
7
7
  readTestAdoption,
8
8
  resolveVitest,
9
9
  suiteOf
10
- } from "./chunk-TDQGNNCV.js";
10
+ } from "./chunk-YHNQONE2.js";
11
11
  import "./chunk-O7REVMOC.js";
12
12
  import "./chunk-QXFCZON7.js";
13
13
  import "./chunk-NW6UHNYX.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hublo/sentinel",
3
- "version": "1.4.0-alpha.40",
3
+ "version": "1.4.0-alpha.42",
4
4
  "description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
5
5
  "type": "module",
6
6
  "license": "MIT",