verifika 0.0.1 → 0.0.3
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 +35 -0
- package/README.md +102 -53
- package/dist/cli.js +4 -0
- package/dist/config.js +1 -1
- package/dist/init.js +39 -1
- package/dist/logs.js +58 -17
- package/dist/main.js +104 -31
- package/dist/pager.js +92 -0
- package/dist/planner.js +20 -7
- package/dist/preview.js +66 -7
- package/dist/process.js +9 -2
- package/dist/reporters/agent.js +6 -9
- package/dist/reporters/human.js +71 -21
- package/dist/reporters/live.js +209 -0
- package/dist/reporters/plan.js +5 -3
- package/dist/reporters/reporter.js +25 -1
- package/dist/runner.js +14 -1
- package/dist/runs.js +40 -1
- package/package.json +3 -3
- package/skills/verifika/SKILL.md +29 -14
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses [Semantic Versioning](https://semver.org/). The CLI flags, exit codes, configuration format (`version: 1`) and JSON output (`schemaVersion: 1`) are the public API.
|
|
4
4
|
|
|
5
|
+
## [0.0.3] - 2026-10-13
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Live status block in `human` mode on an interactive terminal: progress and elapsed time, each running check with a spinner, its elapsed time and latest output line, and what queued checks wait on. Finished checks print above it as in line mode. Line output remains the fallback in CI, when piped, with `TERM=dumb`, and with the new `--no-live` flag or `VERIFIKA_LIVE=0`.
|
|
10
|
+
- `human` output: a `verifika · <selection> · <n> checks · <j> parallel` header, aligned durations, the command and a rerun hint (`vfk -c <check>`) on failure lines, a `○ not run: build failed` marker for checks blocked by a failed dependency, and an estimate of the time saved by skipped checks from their last recorded durations.
|
|
11
|
+
- In `human` mode, the first interrupt prints `stopping… (Ctrl-C again to force)`.
|
|
12
|
+
- `vfk logs` on a terminal: local and relative start time (`today 01:12, 3m ago`), clickable OSC 8 log paths (opt out with `VERIFIKA_HYPERLINKS=0`), and paging through `$PAGER` or `less -R -F -X` (`--no-pager` to opt out; never with `--tail`, when piped, or in `agent` mode).
|
|
13
|
+
- `vfk plan` in `human` mode colours `run` and dims skipped checks.
|
|
14
|
+
- `vfk init` adds `"verify": "vfk"` and `"verify:ci": "vfk --all"` to `package.json` when they are absent, keeping its indentation, line endings, key order and trailing newline; existing scripts are never overwritten.
|
|
15
|
+
- JSON (additive, `schemaVersion: 1`): `checks[].blockedBy` on blocked checks in run results, naming the dependency that did not pass; `checks[].requiredBy` in plan output on checks selected only as dependencies.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **`npm run verify` is now selective.** The recommended scripts are `"verify": "vfk"` for local work and `"verify:ci": "vfk --all"` for the full gate; use `npm run verify:ci` (or `vfk --all`) in CI and before delivery. The README setup prompt, docs and agent skill say so, and the prompt only replaces an existing `verify` script when the configured checks cover what it ran. If you followed the 0.0.2 setup, your `"verify": "vfk --all"` script still runs the full gate; to adopt the split, rename it to `verify:ci` and add `"verify": "vfk"` by hand (`vfk init` only adds scripts when it creates the config).
|
|
20
|
+
- A check pulled in by several dependents now names all of them (`required by test, pack`), in plan output and reasons.
|
|
21
|
+
- Failure previews leave out package-manager script banners (`> app@1.0.0 lint`, `> eslint .`, yarn's `$ …`).
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- ESLint failure previews showed only the `✖ N problems` summary; they now keep each `line:col error` line under its file heading, plus the summary.
|
|
26
|
+
- A failed check whose preview was hidden by the three-preview limit looked as if nothing was found; its line now says `(preview omitted: vfk logs <check>)`, in `human` and `agent` output.
|
|
27
|
+
|
|
28
|
+
## [0.0.2] - 2026-10-12
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- `vfk` is now the advertised command: `--help`, run hints (`Run: vfk logs <check>`), error hints, the README, the docs and the agent skill use it. `verifika` keeps working and is identical.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- README: a copy-paste prompt that has a coding agent install Verifika, write and validate a config, and install the skill.
|
|
37
|
+
|
|
5
38
|
## [0.0.1] - 2026-10-12
|
|
6
39
|
|
|
7
40
|
First public release.
|
|
@@ -27,4 +60,6 @@ First public release.
|
|
|
27
60
|
- A process that deliberately detaches from the check (on Linux and macOS: leaves the process group, for example via `setsid`; on Windows: breaks away from the job with an exited intermediate parent) is not tracked unless it keeps the check's output open, in which case the check is reported as `error`.
|
|
28
61
|
- On Windows, confirming cleanup queries the process table with PowerShell after each check, which costs roughly 0.5–3 s per check.
|
|
29
62
|
|
|
63
|
+
[0.0.3]: https://github.com/alexportelli12/verifika/compare/v0.0.2...v0.0.3
|
|
64
|
+
[0.0.2]: https://github.com/alexportelli12/verifika/releases/tag/v0.0.2
|
|
30
65
|
[0.0.1]: https://github.com/alexportelli12/verifika/releases/tag/v0.0.1
|
package/README.md
CHANGED
|
@@ -4,56 +4,101 @@
|
|
|
4
4
|
[](https://github.com/alexportelli12/verifika/releases/latest)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
Run the checks your
|
|
7
|
+
**Run only the checks your change needs. Get one line back when they pass.**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Every repository has a verification gate: format, lint, typecheck, tests, build. Using it well is harder than it looks:
|
|
10
|
+
|
|
11
|
+
- **Running everything is slow.** A one-line fix waits minutes for checks it cannot affect, so people (and agents) skip the gate or batch changes until the end.
|
|
12
|
+
- **Picking checks by hand is unreliable.** It is easy to forget the one that breaks, and "it passed" stops meaning much.
|
|
13
|
+
- **Failures are buried in noise.** A test runner can print tens of thousands of lines to report one failed assertion. A coding agent pays for every line in tokens and context, and often never reaches the line that matters.
|
|
14
|
+
|
|
15
|
+
Verifika fixes this with one config file and one command. You list your checks in `verifika.config.json`, with the files each one covers. Then `vfk` runs only the checks your changes affect, CI runs all of them, and the output says exactly what failed:
|
|
10
16
|
|
|
11
17
|
```text
|
|
12
|
-
$
|
|
18
|
+
$ vfk
|
|
13
19
|
PASS 3/3 (4.1s) selective: 2 files changed since HEAD (a1b2c3d); 2 skipped
|
|
14
20
|
|
|
15
|
-
$
|
|
21
|
+
$ vfk --all
|
|
16
22
|
FAIL 4/5 (12s) full run (--all)
|
|
17
23
|
typecheck: exit 2
|
|
18
24
|
src/cart.ts(14,7): error TS2322: Type 'string' is not assignable to type 'number'.
|
|
19
|
-
Run:
|
|
25
|
+
Run: vfk logs typecheck
|
|
20
26
|
```
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
## Why Verifika
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
- **Small output, especially for agents.** On a real Angular project, one failing test produced 94,900 bytes of output with the project's previous verify script (~23,700 tokens). Verifika reported the same failure in 369 bytes (~90 tokens): the test name, the assertion and the totals. Full logs stay on disk until you ask for them.
|
|
31
|
+
- **Less waiting.** Locally, only affected checks run: in a 25-check monorepo, a typical edit ran 1–13 of them. Checks that share no build outputs can also run in parallel; on that Angular project, four at a time cut the full gate from 95.5 s to 61.7 s. [Measurements](docs/benchmarks.md).
|
|
32
|
+
- **Deterministic and conservative.** Selection comes from your config, file globs, dependencies and git; no heuristics, no LLM. When the scope of a change is unclear, Verifika runs everything or fails loudly. It never quietly skips a check, and in CI it runs the full suite unless you explicitly ask for a selective run.
|
|
33
|
+
- **Trustworthy results.** Timeouts, fail-fast, correct exit codes and signal handling. A check only passes once its whole process tree has exited. Tested on Linux, macOS and Windows.
|
|
34
|
+
- **Built for humans and coding agents alike.** Live progress on a terminal, one line in agent sessions, JSON for scripts. It ships an [agent skill](skills/verifika/SKILL.md) for Claude Code, Codex, OpenCode and others.
|
|
35
|
+
- **Small.** One JSON file, one runtime dependency, no daemon, no cache to invalidate.
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
- **Token-efficient.** Agents get one line on success and a bounded failure preview on failure. Full logs stay on disk until asked for.
|
|
28
|
-
- **Fast.** Selective runs skip unaffected checks, and independent checks can run in parallel once you opt in (`defaults.concurrency`). One small runtime dependency.
|
|
29
|
-
- **Reliable.** Timeouts, fail-fast, process cleanup, correct exit codes and signal handling, tested on Linux, macOS and Windows.
|
|
37
|
+
_Verifika_ is Maltese for "verification". The command is `vfk`, short for `verifika`; both are installed and identical.
|
|
30
38
|
|
|
31
|
-
##
|
|
39
|
+
## Get started
|
|
32
40
|
|
|
33
41
|
Requires Node.js 22.13 or newer.
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
npm install --save-dev verifika
|
|
37
|
-
```
|
|
43
|
+
### With a coding agent
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
Paste this prompt into Claude Code, Codex, OpenCode or another coding agent, from your repository root. It installs Verifika, writes and validates a config for your project, and reports what it found.
|
|
40
46
|
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
```text
|
|
48
|
+
Set up Verifika (https://github.com/alexportelli12/verifika) in this repository.
|
|
49
|
+
|
|
50
|
+
1. Check that Node.js is 22.13 or newer, and install the project's dependencies if
|
|
51
|
+
they are missing. Install Verifika as a dev dependency with this repository's
|
|
52
|
+
package manager (npm install --save-dev verifika, or the pnpm/yarn equivalent).
|
|
53
|
+
It provides the `vfk` command.
|
|
54
|
+
2. Run `npx vfk init` to create verifika.config.json, then refine it by reading
|
|
55
|
+
package.json, the CI workflows and the tool configs:
|
|
56
|
+
- One check per quality gate the project actually has (such as format, lint,
|
|
57
|
+
typecheck, unit tests, build). Do not invent checks for tools it does not use,
|
|
58
|
+
or add a check that another one already fully covers. Every command must be
|
|
59
|
+
non-interactive and exit on its own: no watch mode, no prompts, no dev servers.
|
|
60
|
+
- Give each check `paths` globs for the files that can affect it, so local runs
|
|
61
|
+
select only what a change needs. If unsure, leave `paths` out: the check then
|
|
62
|
+
runs on every change.
|
|
63
|
+
- Add `dependsOn` where one check needs another's output (for example e2e after build).
|
|
64
|
+
- Keep concurrency at 1 unless the checks clearly share no build outputs.
|
|
65
|
+
- Make sure package.json has two scripts: "verify": "vfk" (selective, for local
|
|
66
|
+
work) and "verify:ci": "vfk --all" (the full gate). `vfk init` adds them when
|
|
67
|
+
they are missing. If a "verify" or "verify:ci" script already exists, replace
|
|
68
|
+
it only when the configured checks cover everything it ran; otherwise leave it
|
|
69
|
+
and report the conflict. This is the one exception to the rule below about
|
|
70
|
+
existing scripts.
|
|
71
|
+
- If the CI workflow runs the gate some other way, report that switching it to
|
|
72
|
+
`npm run verify:ci` is recommended. Do not edit the workflow.
|
|
73
|
+
Reference: https://github.com/alexportelli12/verifika/blob/main/docs/configuration.md
|
|
74
|
+
3. Validate the config:
|
|
75
|
+
- Run `npx vfk --all`. If a check fails, decide whether the config is wrong (fix it)
|
|
76
|
+
or the project already fails that check (report it; do not fix it).
|
|
77
|
+
- Run `npx vfk plan`. Until this setup is committed, the changed package.json and
|
|
78
|
+
lockfile select every check; that is expected. Explain which checks a typical
|
|
79
|
+
source edit and a documentation-only edit would select once it is committed.
|
|
80
|
+
4. Install the agent skill: copy node_modules/verifika/skills/verifika into this
|
|
81
|
+
agent's skills directory (see
|
|
82
|
+
https://github.com/alexportelli12/verifika/blob/main/docs/agents.md).
|
|
83
|
+
|
|
84
|
+
Do not change source code or existing scripts to make checks pass (except "verify"
|
|
85
|
+
and "verify:ci", as step 2 describes), and do not commit.
|
|
86
|
+
Finish with: the checks you configured and why, the --all result, the expected
|
|
87
|
+
selection for those two edits, the scripts you added, replaced or left (with any
|
|
88
|
+
conflicts), any CI recommendation, and any existing failures you found.
|
|
44
89
|
```
|
|
45
90
|
|
|
46
|
-
|
|
91
|
+
Review the config it writes before committing it: the commands in it run on every verification.
|
|
47
92
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Generate a starter config from your `package.json` scripts:
|
|
93
|
+
### By hand
|
|
51
94
|
|
|
52
95
|
```sh
|
|
53
|
-
|
|
96
|
+
npm install --save-dev verifika
|
|
97
|
+
npx vfk init # creates verifika.config.json from your package.json scripts,
|
|
98
|
+
# and adds "verify" / "verify:ci" scripts if they are missing
|
|
54
99
|
```
|
|
55
100
|
|
|
56
|
-
|
|
101
|
+
`init` only maps conventional script names (`lint`, `test`, `build`, ...) and leaves every check unfiltered, so open the file and give each check `paths`:
|
|
57
102
|
|
|
58
103
|
```json
|
|
59
104
|
{
|
|
@@ -72,46 +117,50 @@ Or write `verifika.config.json` yourself:
|
|
|
72
117
|
}
|
|
73
118
|
```
|
|
74
119
|
|
|
75
|
-
If your repository checks the formatting of every file (for example `prettier --check .`), format the new `verifika.config.json` too.
|
|
76
|
-
|
|
77
|
-
Then:
|
|
120
|
+
If your repository checks the formatting of every file (for example `prettier --check .`), format the new `verifika.config.json` too. Then:
|
|
78
121
|
|
|
79
122
|
```sh
|
|
80
|
-
npx
|
|
81
|
-
npx
|
|
82
|
-
npx
|
|
123
|
+
npx vfk plan # which checks would run for your changes, and why
|
|
124
|
+
npx vfk # run them
|
|
125
|
+
npx vfk --all # run everything (do this before you push)
|
|
83
126
|
```
|
|
84
127
|
|
|
85
|
-
|
|
128
|
+
`init` adds two scripts unless package.json already has scripts with those names (it never overwrites them):
|
|
86
129
|
|
|
87
130
|
```json
|
|
88
|
-
{ "scripts": { "verify": "
|
|
131
|
+
{ "scripts": { "verify": "vfk", "verify:ci": "vfk --all" } }
|
|
89
132
|
```
|
|
90
133
|
|
|
134
|
+
`npm run verify` runs the checks your changes affect, for quick feedback while you work. `npm run verify:ci` runs every check: it is the full gate, so use it in CI and before you push or open a pull request.
|
|
135
|
+
|
|
136
|
+
Run logs go to `.verifika/`, which ignores itself in git, so you do not need to edit `.gitignore`. To install globally instead, run `npm install --global verifika`.
|
|
137
|
+
|
|
91
138
|
## How checks are selected
|
|
92
139
|
|
|
93
|
-
| Situation
|
|
94
|
-
|
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| A changed file matches `invalidateAll`
|
|
101
|
-
| Git unavailable, no commits, unknown base
|
|
140
|
+
| Situation | What runs |
|
|
141
|
+
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
142
|
+
| `vfk` locally | Checks whose `paths` match files changed since `changes.base` (default `HEAD`: your uncommitted and untracked work) |
|
|
143
|
+
| `vfk` in CI (`CI` or a known CI variable) | Everything; only `--changed`/`--changed-since` on the command line can narrow it |
|
|
144
|
+
| `vfk --all` | Everything |
|
|
145
|
+
| `vfk --check lint` | `lint` and its dependencies |
|
|
146
|
+
| `vfk --changed-since origin/main` | Checks matching files changed since `merge-base(origin/main, HEAD)`, plus uncommitted and untracked files |
|
|
147
|
+
| A changed file matches `invalidateAll` | Everything |
|
|
148
|
+
| Git unavailable, no commits, unknown base | Everything, with the reason printed (or an error, when you asked for `--changed`/`--changed-since` explicitly) |
|
|
102
149
|
|
|
103
|
-
A check without `paths` runs whenever any file changed; `"always": true` runs it even when nothing changed. Selecting a check also selects its `dependsOn` checks. `
|
|
150
|
+
A check without `paths` runs whenever any file changed; `"always": true` runs it even when nothing changed. Selecting a check also selects its `dependsOn` checks. `vfk plan` shows every decision.
|
|
104
151
|
|
|
105
152
|
## Output modes
|
|
106
153
|
|
|
107
154
|
The default is `human` on an interactive terminal and `agent` otherwise. Override with `--mode` or `VERIFIKA_MODE`.
|
|
108
155
|
|
|
109
|
-
| Mode | Success | Failure
|
|
110
|
-
| ------- | ----------------------------- |
|
|
111
|
-
| `human` | Live
|
|
112
|
-
| `agent` | One line: `PASS 4/4 (4.3s) …` | One line per problem, bounded preview, logs command
|
|
113
|
-
| `json` | Versioned result document | Same document, `"status": "failed"`
|
|
114
|
-
| `quiet` | Nothing | One line on stderr
|
|
156
|
+
| Mode | Success | Failure |
|
|
157
|
+
| ------- | ----------------------------- | --------------------------------------------------------------- |
|
|
158
|
+
| `human` | Live status block, summary | Failing checks with command and rerun hint, previews, logs hint |
|
|
159
|
+
| `agent` | One line: `PASS 4/4 (4.3s) …` | One line per problem, bounded preview, logs command |
|
|
160
|
+
| `json` | Versioned result document | Same document, `"status": "failed"` |
|
|
161
|
+
| `quiet` | Nothing | One line on stderr |
|
|
162
|
+
|
|
163
|
+
In `human` mode on an interactive terminal, a live status block shows progress, each running check with its latest output line, and what the queued checks wait on; finished checks print above it. In CI, when piped, with `TERM=dumb`, or with `--no-live` / `VERIFIKA_LIVE=0`, `human` mode prints plain lines instead (one per check start and end, plus a note every 30 seconds for long checks), which read well in logs. `NO_COLOR` turns colour off in both.
|
|
115
164
|
|
|
116
165
|
Exit codes: `0` passed (or nothing to verify), `1` verification failed, `2` usage or configuration error, `128+n` interrupted by a signal (`130` SIGINT, `143` SIGTERM).
|
|
117
166
|
|
|
@@ -120,10 +169,10 @@ Exit codes: `0` passed (or nothing to verify), `1` verification failed, `2` usag
|
|
|
120
169
|
Every run stores each check's combined stdout/stderr under `.verifika/runs/<run-id>/`. The directory ignores itself in git, and only the last 10 runs are kept (`defaults.keepRuns`).
|
|
121
170
|
|
|
122
171
|
```sh
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
172
|
+
vfk logs # latest run: statuses and log paths
|
|
173
|
+
vfk logs typecheck # full log of one check
|
|
174
|
+
vfk logs --failed # logs of every failed check
|
|
175
|
+
vfk logs test --tail 50 # last 50 lines
|
|
127
176
|
```
|
|
128
177
|
|
|
129
178
|
Logs contain whatever your commands print, which can include secrets. They never leave your machine; keep `.verifika/` out of anything you publish.
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { ExitCode } from "./errors.js";
|
|
3
3
|
import { main } from "./main.js";
|
|
4
|
+
import { systemTerminal } from "./reporters/live.js";
|
|
4
5
|
const write = (stream) => (text) => {
|
|
5
6
|
stream.write(text);
|
|
6
7
|
};
|
|
@@ -17,6 +18,9 @@ main(process.argv.slice(2), {
|
|
|
17
18
|
isTTY: Boolean(process.stdout.isTTY),
|
|
18
19
|
out: write(process.stdout),
|
|
19
20
|
err: write(process.stderr),
|
|
21
|
+
...(process.stdout.isTTY
|
|
22
|
+
? { terminal: systemTerminal(write(process.stdout), write(process.stderr), process.stdout) }
|
|
23
|
+
: {}),
|
|
20
24
|
}).then((code) => {
|
|
21
25
|
process.exitCode = code;
|
|
22
26
|
}, (error) => {
|
package/dist/config.js
CHANGED
|
@@ -30,7 +30,7 @@ export function findConfigFile(cwd, explicit) {
|
|
|
30
30
|
return candidate;
|
|
31
31
|
const parent = path.dirname(directory);
|
|
32
32
|
if (parent === directory) {
|
|
33
|
-
throw new VerifikaError(`no ${CONFIG_FILE} found in ${cwd} or any parent directory`, "Create one with:
|
|
33
|
+
throw new VerifikaError(`no ${CONFIG_FILE} found in ${cwd} or any parent directory`, "Create one with: vfk init");
|
|
34
34
|
}
|
|
35
35
|
directory = parent;
|
|
36
36
|
}
|
package/dist/init.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { CONFIG_FILE } from "./config.js";
|
|
5
5
|
import { VerifikaError } from "./errors.js";
|
|
@@ -14,6 +14,44 @@ const CANDIDATES = [
|
|
|
14
14
|
["build", ["build"]],
|
|
15
15
|
];
|
|
16
16
|
const NPM_PLACEHOLDER_TEST = /no test specified/;
|
|
17
|
+
/** npm scripts `vfk init` adds: selective verification while working, the full gate for CI. */
|
|
18
|
+
export const VERIFY_SCRIPTS = {
|
|
19
|
+
verify: "vfk",
|
|
20
|
+
"verify:ci": "vfk --all",
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Adds the `verify` and `verify:ci` scripts to package.json where they are
|
|
24
|
+
* absent; existing scripts are never overwritten. Keeps the file's indentation,
|
|
25
|
+
* line endings, key order and trailing newline.
|
|
26
|
+
*/
|
|
27
|
+
export async function addVerifyScripts(root) {
|
|
28
|
+
const file = path.join(root, "package.json");
|
|
29
|
+
const raw = await readFile(file, "utf8");
|
|
30
|
+
const bom = raw.startsWith("\uFEFF") ? "\uFEFF" : "";
|
|
31
|
+
const text = raw.slice(bom.length);
|
|
32
|
+
const manifest = JSON.parse(text);
|
|
33
|
+
const scripts = (manifest.scripts ??= {});
|
|
34
|
+
const result = { added: [], kept: [] };
|
|
35
|
+
for (const [name, command] of Object.entries(VERIFY_SCRIPTS)) {
|
|
36
|
+
const existing = scripts[name];
|
|
37
|
+
if (existing === undefined) {
|
|
38
|
+
scripts[name] = command;
|
|
39
|
+
result.added.push(name);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
result.kept.push({ name, command: existing });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (result.added.length === 0)
|
|
46
|
+
return result;
|
|
47
|
+
// A one-line manifest stays on one line.
|
|
48
|
+
const indent = text.trim().includes("\n") ? (/^[ \t]+(?=")/m.exec(text)?.[0] ?? " ") : "";
|
|
49
|
+
const newline = text.includes("\r\n") ? "\r\n" : "\n";
|
|
50
|
+
const trailing = /\r?\n$/.test(text) ? newline : "";
|
|
51
|
+
const json = JSON.stringify(manifest, null, indent).replace(/\n/g, newline);
|
|
52
|
+
await writeFile(file, `${bom}${json}${trailing}`);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
17
55
|
/**
|
|
18
56
|
* Writes a starter verifika.config.json from the package.json scripts in `root`.
|
|
19
57
|
* Checks are created without path filters (they run on any change); narrowing
|
package/dist/logs.js
CHANGED
|
@@ -2,7 +2,9 @@ import { createReadStream } from "node:fs";
|
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import { open } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
5
6
|
import { VerifikaError } from "./errors.js";
|
|
7
|
+
import { withPager } from "./pager.js";
|
|
6
8
|
import { duration, isFailureStatus, selectionLabel } from "./reporters/reporter.js";
|
|
7
9
|
import { plain, symbol } from "./reporters/human.js";
|
|
8
10
|
import { stripAnsi } from "./preview.js";
|
|
@@ -15,7 +17,7 @@ import { readRun } from "./runs.js";
|
|
|
15
17
|
export async function showLogs(request, output) {
|
|
16
18
|
const run = await readRun(request.root, request.runId);
|
|
17
19
|
if (request.checks.length === 0 && !request.failed) {
|
|
18
|
-
output.out(renderRunSummary(run));
|
|
20
|
+
output.out(renderRunSummary(run, request.display));
|
|
19
21
|
return 0;
|
|
20
22
|
}
|
|
21
23
|
let targets;
|
|
@@ -35,20 +37,29 @@ export async function showLogs(request, output) {
|
|
|
35
37
|
targets = request.checks.map((name) => byName.get(name));
|
|
36
38
|
}
|
|
37
39
|
const headers = request.failed || targets.length > 1;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
const print = async (write) => {
|
|
41
|
+
for (const check of targets) {
|
|
42
|
+
if (headers)
|
|
43
|
+
await write(`── ${check.name}: ${check.status} (${check.reason}) ──\n`);
|
|
44
|
+
if (!check.log) {
|
|
45
|
+
await write(`${check.name} has no log: ${check.status} (${check.reason})\n`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
await printLog(path.join(request.root, check.log), request, write);
|
|
44
49
|
}
|
|
45
|
-
|
|
50
|
+
};
|
|
51
|
+
if (request.pager !== undefined && request.tail === undefined) {
|
|
52
|
+
await withPager(request.pager, request.env ?? process.env, print);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
await print((text) => output.out(text));
|
|
46
56
|
}
|
|
47
57
|
return 0;
|
|
48
58
|
}
|
|
49
|
-
export function renderRunSummary(run) {
|
|
59
|
+
export function renderRunSummary(run, display) {
|
|
60
|
+
const started = display ? describeTime(new Date(run.startedAt), display.now) : run.startedAt;
|
|
50
61
|
const lines = [
|
|
51
|
-
`Run ${run.runId} · ${run.status} · ${duration(run.durationMs)} · ${
|
|
62
|
+
`Run ${run.runId} · ${run.status} · ${duration(run.durationMs)} · ${started}`,
|
|
52
63
|
selectionLabel(run.selection, run.summary.skipped),
|
|
53
64
|
];
|
|
54
65
|
if (run.status === "running")
|
|
@@ -60,8 +71,9 @@ export function renderRunSummary(run) {
|
|
|
60
71
|
parts.push(duration(check.durationMs).padStart(6));
|
|
61
72
|
if (check.status !== "passed")
|
|
62
73
|
parts.push(check.reason);
|
|
63
|
-
if (check.log)
|
|
64
|
-
parts.push(check.log);
|
|
74
|
+
if (check.log) {
|
|
75
|
+
parts.push(display?.hyperlinks ? hyperlink(check.log, path.join(run.root, check.log)) : check.log);
|
|
76
|
+
}
|
|
65
77
|
lines.push(parts.join(" "));
|
|
66
78
|
}
|
|
67
79
|
// One line for everything not selected; large configs would otherwise list dozens.
|
|
@@ -72,12 +84,41 @@ export function renderRunSummary(run) {
|
|
|
72
84
|
}
|
|
73
85
|
return `${lines.join("\n")}\n`;
|
|
74
86
|
}
|
|
75
|
-
|
|
87
|
+
/**
|
|
88
|
+
* `today 01:12, 3m ago`; `yesterday 23:40, 2h ago`; otherwise the local date.
|
|
89
|
+
* Local time, because that is what the user's clock shows.
|
|
90
|
+
*/
|
|
91
|
+
export function describeTime(time, now) {
|
|
92
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
93
|
+
const clock = `${pad(time.getHours())}:${pad(time.getMinutes())}`;
|
|
94
|
+
const day = (date) => new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
95
|
+
const days = Math.round((day(now) - day(time)) / 86_400_000);
|
|
96
|
+
const date = days === 0
|
|
97
|
+
? "today"
|
|
98
|
+
: days === 1
|
|
99
|
+
? "yesterday"
|
|
100
|
+
: `${time.getFullYear()}-${pad(time.getMonth() + 1)}-${pad(time.getDate())}`;
|
|
101
|
+
return `${date} ${clock}, ${ago(now.getTime() - time.getTime())}`;
|
|
102
|
+
}
|
|
103
|
+
function ago(ms) {
|
|
104
|
+
if (ms < 60_000)
|
|
105
|
+
return ms < 1_000 ? "just now" : `${Math.floor(ms / 1000)}s ago`;
|
|
106
|
+
if (ms < 3_600_000)
|
|
107
|
+
return `${Math.floor(ms / 60_000)}m ago`;
|
|
108
|
+
if (ms < 86_400_000)
|
|
109
|
+
return `${Math.floor(ms / 3_600_000)}h ago`;
|
|
110
|
+
return `${Math.floor(ms / 86_400_000)}d ago`;
|
|
111
|
+
}
|
|
112
|
+
/** An OSC 8 terminal hyperlink to a local file; terminals without support show just the text. */
|
|
113
|
+
export function hyperlink(text, file) {
|
|
114
|
+
return `\u001b]8;;${pathToFileURL(file).href}\u001b\\${text}\u001b]8;;\u001b\\`;
|
|
115
|
+
}
|
|
116
|
+
async function printLog(file, { tail, stripAnsi: strip = false }, write) {
|
|
76
117
|
try {
|
|
77
118
|
if (tail !== undefined) {
|
|
78
119
|
const text = await readTail(file, tail);
|
|
79
120
|
const shown = strip ? stripAnsi(text) : text;
|
|
80
|
-
|
|
121
|
+
await write(shown.endsWith("\n") || shown === "" ? shown : `${shown}\n`);
|
|
81
122
|
return;
|
|
82
123
|
}
|
|
83
124
|
if (strip) {
|
|
@@ -87,16 +128,16 @@ async function printLog(file, { tail, stripAnsi: strip = false }, output) {
|
|
|
87
128
|
crlfDelay: Infinity,
|
|
88
129
|
});
|
|
89
130
|
for await (const line of lines)
|
|
90
|
-
|
|
131
|
+
await write(`${stripAnsi(line)}\n`);
|
|
91
132
|
return;
|
|
92
133
|
}
|
|
93
134
|
let last = "";
|
|
94
135
|
for await (const chunk of createReadStream(file, { encoding: "utf8" })) {
|
|
95
|
-
|
|
136
|
+
await write(chunk);
|
|
96
137
|
last = chunk;
|
|
97
138
|
}
|
|
98
139
|
if (last !== "" && !last.endsWith("\n"))
|
|
99
|
-
|
|
140
|
+
await write("\n");
|
|
100
141
|
}
|
|
101
142
|
catch (error) {
|
|
102
143
|
throw new VerifikaError(`cannot read log ${file}: ${error.message}`);
|