@avi2dg/checks 0.54.1 → 0.55.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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Every release of `@avi2dg/checks`, newest first, written by the release from its conventional commits.
|
|
4
4
|
|
|
5
|
+
## 0.55.0
|
|
6
|
+
|
|
7
|
+
Released 2026-10-27.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **dependencies:** defer the advisory scan to the scheduled run [#10](https://github.com/avi2d/checks/pull/10)
|
|
12
|
+
|
|
5
13
|
## 0.54.1
|
|
6
14
|
|
|
7
15
|
Released 2026-10-27.
|
|
@@ -14,6 +14,7 @@ It ignores aliases, because two live advisories can list each other as aliases.
|
|
|
14
14
|
An advisory published against a package the base already held shows at both ends, so it fails no range, and `--all` reports it instead.
|
|
15
15
|
Each failure names the package, its version, the advisory id, its severity and its summary.
|
|
16
16
|
A range that leaves `bun.lock` unchanged runs no scan.
|
|
17
|
+
A run `checks-lint` starts defers the scan to the scheduled `--all` run instead, so the desk and the pull request neither fetch the database nor scan.
|
|
17
18
|
It also holds `advisory-acks.json` to the rules in [The acknowledgement file](#the-acknowledgement-file) on every run.
|
|
18
19
|
|
|
19
20
|
## What it reads
|
|
@@ -62,6 +63,7 @@ An entry whose `until` falls more than 30 days after that moment fails the run a
|
|
|
62
63
|
Once that moment passes `until`, the entry fails every run until it is deleted or renewed with a later `until`, whether or not the range touches `bun.lock`.
|
|
63
64
|
Delete it once the package is upgraded.
|
|
64
65
|
When a scan runs, an entry that matches no advisory at the head fails, so the file holds only live entries.
|
|
66
|
+
A deferred run still holds each entry to its day, while an entry that matches nothing waits for the scheduled run.
|
|
65
67
|
A file that does not decode as that list exits 2.
|
|
66
68
|
|
|
67
69
|
## Arguments
|
|
@@ -80,9 +82,9 @@ With `--all` it fails on every advisory in `bun.lock` at `HEAD` that no entry co
|
|
|
80
82
|
|
|
81
83
|
| Code | When |
|
|
82
84
|
| --- | --- |
|
|
83
|
-
| 0 | the range adds no advisory
|
|
84
|
-
| 1 |
|
|
85
|
-
| 2 | a ref does not resolve, `advisory-acks.json` does not decode, or no verified scanner or usable database
|
|
85
|
+
| 0 | the range adds no advisory on a direct scan or an unchanged range and every acknowledgement holds, or a deferred run holds every acknowledgement without a scan |
|
|
86
|
+
| 1 | a direct scan adds an advisory no acknowledgement covers, or an acknowledgement does not hold on any run |
|
|
87
|
+
| 2 | a ref does not resolve, `advisory-acks.json` does not decode, or a direct scan has no verified scanner or usable database at hand |
|
|
86
88
|
|
|
87
89
|
## Sample output
|
|
88
90
|
|
|
@@ -97,7 +99,8 @@ advisories: 1 acknowledgement(s) in advisory-acks.json do not hold:
|
|
|
97
99
|
## When it runs
|
|
98
100
|
|
|
99
101
|
`checks-lint` runs it over each pull request's range in a repository that tracks `bun.lock`, as [checks-lint](checks-lint.md) says.
|
|
100
|
-
|
|
102
|
+
That run defers the scan to the scheduled run, so it fetches no database and reports the deferral instead of a verdict.
|
|
103
|
+
A range that leaves `bun.lock` unchanged runs in under a second, and a direct run over a range that changes `bun.lock` scans for about 10 seconds on a warm cache.
|
|
101
104
|
|
|
102
105
|
## Running it on a schedule
|
|
103
106
|
|
|
@@ -12,6 +12,7 @@ It runs the gates under [What runs](../bins.md#what-runs), each in its own proce
|
|
|
12
12
|
Gates requiring tracked TypeScript files begin running when the repository tracks TypeScript.
|
|
13
13
|
`checks-lint-coverage` and `checks-unused` also begin running when the repository tracks an `.astro` file.
|
|
14
14
|
`checks-advisories` begins running when the repository tracks `bun.lock`.
|
|
15
|
+
`checks-lint` defers its scan to the scheduled run, so the desk and the pull request fetch no database.
|
|
15
16
|
`checks-frontend-syntax` begins running when the repository tracks `frontend-syntax.json` at its root.
|
|
16
17
|
`checks-copy` begins running when the repository tracks `copy.json` at its root.
|
|
17
18
|
`checks-css-size` begins running when the repository tracks a `*.css` file.
|
package/package.json
CHANGED
package/src/core/gates.ts
CHANGED
|
@@ -8,6 +8,9 @@ export type TrackedContent = {
|
|
|
8
8
|
readonly content: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// checks-lint sets this when it spawns the advisories gate, so the desk and pull request runs leave the database fetch and the scan to the scheduled --all run.
|
|
12
|
+
export const ADVISORIES_DEFER_SCAN = "CHECKS_ADVISORIES_DEFER_SCAN";
|
|
13
|
+
|
|
11
14
|
export const EVERY_REPOSITORY = "every repository";
|
|
12
15
|
|
|
13
16
|
export const VECTORS = ["complexity", "quality", "testing", "docs", "delivery", "dependencies"] as const;
|
|
@@ -21,6 +24,7 @@ export type KitGate = {
|
|
|
21
24
|
readonly reads: "tree" | "range";
|
|
22
25
|
readonly alsoReads?: string;
|
|
23
26
|
readonly args?: readonly string[];
|
|
27
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
24
28
|
readonly appliesTo: typeof EVERY_REPOSITORY | TrackedContent;
|
|
25
29
|
};
|
|
26
30
|
|
|
@@ -63,5 +67,12 @@ export const KIT_GATES = [
|
|
|
63
67
|
{ bin: "checks-exports", vector: "complexity", file: "exports.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE },
|
|
64
68
|
{ bin: "checks-quarantine-clock", vector: "testing", file: "quarantine-clock.ts", reads: "range", appliesTo: EVERY_REPOSITORY },
|
|
65
69
|
{ bin: "checks-imports", vector: "dependencies", file: "imports.ts", reads: "tree", appliesTo: TYPESCRIPT_SOURCE },
|
|
66
|
-
{
|
|
70
|
+
{
|
|
71
|
+
bin: "checks-advisories",
|
|
72
|
+
vector: "dependencies",
|
|
73
|
+
file: "advisories.ts",
|
|
74
|
+
reads: "range",
|
|
75
|
+
env: { [ADVISORIES_DEFER_SCAN]: "1" },
|
|
76
|
+
appliesTo: BUN_LOCKFILE,
|
|
77
|
+
},
|
|
67
78
|
] as const satisfies readonly KitGate[];
|
package/src/core/lint.ts
CHANGED
|
@@ -154,8 +154,9 @@ const runGate = Effect.fn("runGate")(function* (gate: KitGate, range: Range) {
|
|
|
154
154
|
const script = (yield* Path.Path).join(import.meta.dir, "..", gate.vector, gate.file);
|
|
155
155
|
const program = gate.file.endsWith(".sh") ? "sh" : process.execPath;
|
|
156
156
|
const args = gate.reads === "range" ? [script, ...range.refs] : [script, ...(gate.args ?? [])];
|
|
157
|
+
const extra = gate.env === undefined ? {} : { env: gate.env, extendEnv: true };
|
|
157
158
|
const exitCode = yield* spawner
|
|
158
|
-
.exitCode(ChildProcess.make(program, args, { stdin: "ignore", stdout: "inherit", stderr: "inherit" }))
|
|
159
|
+
.exitCode(ChildProcess.make(program, args, { stdin: "ignore", stdout: "inherit", stderr: "inherit", ...extra }))
|
|
159
160
|
.pipe(
|
|
160
161
|
Effect.catch((cause) =>
|
|
161
162
|
Console.error(`${NAME}: cannot run ${gate.bin}: ${cause.message}`).pipe(Effect.as(ChildProcessSpawner.ExitCode(2))),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { Clock, Config, Console, Effect, FileSystem, type Layer, Option, Path, Schema } from "effect";
|
|
3
|
+
import { ADVISORIES_DEFER_SCAN } from "../core/gates.ts";
|
|
3
4
|
import { changedPaths, commitOf, git, pathsAt, rangeFromArgs, writtenAt } from "../core/git.ts";
|
|
4
5
|
import type { BunServices } from "@effect/platform-bun";
|
|
5
6
|
import { runMain, Usage } from "../core/main.ts";
|
|
@@ -80,10 +81,17 @@ const findingsOf = Effect.fn("findingsOf")(function* (lockfiles: Lockfiles) {
|
|
|
80
81
|
return { base: findingsIn(scanned, at("base")), head: findingsIn(scanned, at("head")) };
|
|
81
82
|
}, Effect.scoped);
|
|
82
83
|
|
|
83
|
-
const
|
|
84
|
+
const defersScan = Effect.fn("defersScan")(function* () {
|
|
85
|
+
const value = yield* Config.option(Config.String(ADVISORIES_DEFER_SCAN));
|
|
86
|
+
return Option.isSome(value) && value.value.trim() !== "";
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const outcomeOf = Effect.fn("outcomeOf")(function* (request: Request, root: string, clock: AcknowledgementClock, deferred: boolean) {
|
|
84
90
|
if (request.kind === "range" && (yield* changedPaths(request.base, request.head, [LOCKFILE], root)).length === 0) {
|
|
85
91
|
return { kind: "unchanged", problems: clock.problems } satisfies Outcome;
|
|
86
92
|
}
|
|
93
|
+
// The lint path leaves the verdict to the scheduled run, so a changed lockfile reports the deferral instead of scanning.
|
|
94
|
+
if (deferred && request.kind === "range") return { kind: "deferred", problems: clock.problems } satisfies Outcome;
|
|
87
95
|
const head = yield* fileAt(request.head, LOCKFILE, root);
|
|
88
96
|
const base = request.kind === "range" ? yield* fileAt(request.base, LOCKFILE, root) : Option.none<string>();
|
|
89
97
|
const findings = Option.isNone(head) ? { base: [], head: [] } : yield* findingsOf({ base, head });
|
|
@@ -101,7 +109,7 @@ const advisories = Effect.gen(function* () {
|
|
|
101
109
|
const root = (yield* git(["rev-parse", "--show-toplevel"])).trim();
|
|
102
110
|
const dates = { head: (yield* writtenAt(request.head, root)) * 1000, now: yield* Clock.currentTimeMillis };
|
|
103
111
|
const clock = clockOf(yield* acknowledgementsAt(request.head, root), { kind: request.kind }, dates);
|
|
104
|
-
const outcome = yield* outcomeOf(request, root, clock);
|
|
112
|
+
const outcome = yield* outcomeOf(request, root, clock, yield* defersScan());
|
|
105
113
|
const text = report(outcome);
|
|
106
114
|
yield* Console.log(text);
|
|
107
115
|
if (request.kind === "head") yield* toStepSummary(text);
|
|
@@ -151,10 +151,11 @@ export function judge(base: readonly Finding[], head: readonly Finding[], clock:
|
|
|
151
151
|
|
|
152
152
|
export type Outcome =
|
|
153
153
|
| { readonly kind: "unchanged"; readonly problems: readonly AcknowledgementProblem[] }
|
|
154
|
+
| { readonly kind: "deferred"; readonly problems: readonly AcknowledgementProblem[] }
|
|
154
155
|
| { readonly kind: "scanned"; readonly scope: Scope; readonly judged: Judged };
|
|
155
156
|
|
|
156
157
|
export function passes(outcome: Outcome): boolean {
|
|
157
|
-
if (outcome.kind === "unchanged") return outcome.problems.length === 0;
|
|
158
|
+
if (outcome.kind === "unchanged" || outcome.kind === "deferred") return outcome.problems.length === 0;
|
|
158
159
|
return outcome.judged.failing.length === 0 && outcome.judged.problems.length === 0;
|
|
159
160
|
}
|
|
160
161
|
|
|
@@ -194,6 +195,12 @@ export function report(outcome: Outcome): string {
|
|
|
194
195
|
if (outcome.kind === "unchanged") {
|
|
195
196
|
return [`advisories: ${LOCKFILE} is unchanged in the range, so nothing was scanned`, ...problemLines(outcome.problems)].join("\n");
|
|
196
197
|
}
|
|
198
|
+
if (outcome.kind === "deferred") {
|
|
199
|
+
return [
|
|
200
|
+
`advisories: ${LOCKFILE} changed in the range, so nothing was scanned; the scheduled --all run owns the verdict`,
|
|
201
|
+
...problemLines(outcome.problems),
|
|
202
|
+
].join("\n");
|
|
203
|
+
}
|
|
197
204
|
return [...scannedLines(outcome.scope, outcome.judged), ...problemLines(outcome.judged.problems)].join("\n");
|
|
198
205
|
}
|
|
199
206
|
|