@avi2dg/checks 0.8.0 → 0.10.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
@@ -2,13 +2,16 @@
2
2
 
3
3
  Deterministic checks shared across my TypeScript repos. One package,
4
4
  `@avi2dg/checks`: the `checks-lint` entry point that runs every lint gate
5
- below over a range it resolves itself, the oxlint base config, the
6
- tsconfig fragment with the Effect language-service block, the shared
7
- commitlint config, the shared dependency-cruiser base, the test-layout
8
- check with its bunfig preset, the commit-identity check, the comment
9
- gate with its backtest, the oxlint suppressions ratchet, the Stryker
10
- mutation-testing preset with its no-regression comparator, the CI-wiring
11
- check, and the Effect error-channel plugin compiled to JavaScript.
5
+ below over a range it resolves itself, the `checks-test` entry point
6
+ that runs the suite and refuses an undeclared skip, the `checks-flake`
7
+ run that records the seeds a failing test fails with, the oxlint base
8
+ config, the tsconfig fragment with the Effect language-service block,
9
+ the shared commitlint config, the shared dependency-cruiser base, the
10
+ test-layout check with its bunfig preset, the commit-identity check, the
11
+ comment gate with its backtest, the oxlint suppressions ratchet, the
12
+ Stryker mutation-testing preset with its no-regression comparator, the
13
+ CI-wiring check, and the Effect error-channel plugin compiled to
14
+ JavaScript.
12
15
 
13
16
  Published as `@avi2dg/checks` on the public npm registry.
14
17
 
@@ -54,15 +57,20 @@ cp node_modules/@avi2dg/checks/bunfig.toml bunfig.toml
54
57
  ```json
55
58
  "lint": "oxlint --type-aware && checks-lint",
56
59
  "typecheck": "tsc --noEmit && effect-tsgo diagnostics --project tsconfig.json --format text --strict",
57
- "test": "bun test --randomize"
60
+ "test": "checks-test"
58
61
  ```
59
62
 
63
+ `checks-test` runs `bun test --randomize` and fails on a skip the
64
+ repository has not declared; see "Test entry point" below.
65
+
60
66
  `checks-lint` runs every kit gate a lint needs; see "Lint entry point"
61
67
  below. Three of them:
62
68
 
63
69
  `lint-coverage.sh` fails when oxlint silently skips a tracked `.ts` or
64
70
  `.tsx` file, for example through a stray `.gitignore` entry. It compares
65
71
  `git ls-files` against oxlint's own file walk and names the missing files.
72
+ It exits 2 when oxlint cannot walk the tree, as when it is not on `PATH`
73
+ or its config does not parse.
66
74
 
67
75
  `test-layout.ts` decides the test layout described below.
68
76
 
@@ -87,7 +95,9 @@ The registry version is pinned by the consumer's lockfile; bump
87
95
  ## Lint entry point
88
96
 
89
97
  `checks-lint` runs each of the kit's lint gates in turn and names every
90
- one that fails, rather than stopping at the first:
98
+ one that fails, rather than stopping at the first. A repository whose
99
+ tracked files give a gate nothing to check can leave it out through
100
+ `ciWiring.lintGates`; see "Gate selection" under "CI wiring".
91
101
 
92
102
  | Gate | Reads |
93
103
  | --- | --- |
@@ -122,13 +132,29 @@ tip: commits the base branch gained after the head branched off would
122
132
  otherwise count against the head. When the head is the merge base, as
123
133
  on a push to the default branch or a local run on it, the range would be
124
134
  empty, so each range gate is handed that tip commit alone and checks it
125
- against its parent:
135
+ against its parent, or against the empty tree when it is a repository's
136
+ first commit:
126
137
 
127
138
  ```
128
139
  checks-lint: tip 10ba7d8935b73ed72624120a1542e51bd21ca7c7 from HEAD against origin/main
129
140
  ```
130
141
 
131
- It prints the range, each gate's own report, then its verdict:
142
+ A clone with no remote-tracking refs at all, such as a freshly
143
+ initialised repository with no remote or one whose remote was never
144
+ fetched, has no default branch to start from, so each range gate is
145
+ handed `HEAD` alone the same way:
146
+
147
+ ```
148
+ checks-lint: tip 10ba7d8935b73ed72624120a1542e51bd21ca7c7 from HEAD alone, as the clone has no remote-tracking refs
149
+ ```
150
+
151
+ Once any ref sits under `refs/remotes/`, a missing
152
+ `origin/<default branch>` exits 2 instead, since that is the shape of a
153
+ shallow CI checkout, where `HEAD` alone would leave the commits before
154
+ it unchecked.
155
+
156
+ It prints the range, the declared selection if there is one, each
157
+ gate's own report, then its verdict:
132
158
 
133
159
  ```
134
160
  checks-lint: range 2504acf098d120e73a8ece3c96f22b934f35c6a8..10ba7d8935b73ed72624120a1542e51bd21ca7c7 from HEAD against origin/main
@@ -136,10 +162,11 @@ checks-lint: range 2504acf098d120e73a8ece3c96f22b934f35c6a8..10ba7d8935b73ed7262
136
162
  checks-lint: 3 of 6 gate(s) failed: checks-commit-identity, checks-comment-gate, checks-suppressions-ratchet
137
163
  ```
138
164
 
139
- It exits 1 when any gate found a violation, and 2 when the range does
140
- not resolve or no failing gate could decide. Every gate runs, so a
141
- repository on `checks-lint` declares `ciWiring.gates` (see "CI wiring")
142
- and holds the test layout.
165
+ It exits 1 when any gate found a violation, and 2 when the range or the
166
+ selection does not resolve, or no failing gate could decide. ci-wiring
167
+ always runs, so a repository on `checks-lint` declares `ciWiring.gates`
168
+ (see "CI wiring"), and it holds the test layout unless its selection
169
+ leaves out `checks-test-layout`.
143
170
 
144
171
  CI runs it through `lint`. The checkout fetches the whole history, which
145
172
  the merge base needs:
@@ -207,7 +234,9 @@ which need the `node`, `promise` and `unicorn` plugins in the override's
207
234
  `plugins`. `unicorn/no-process-exit` passes over any file that opens with
208
235
  a shebang, so a bin also needs `no-restricted-properties` on
209
236
  `process.exit`. Sites standing when the override lands go in oxlint's own
210
- baseline, `oxlint --suppress-all`, so their count can only fall.
237
+ baseline, `oxlint --suppress-all`, so their count can only fall. A later
238
+ override lifts `effect-channel/no-throw` from `scripts/comment-matchers.ts`,
239
+ the one file there a host loads without `node_modules`.
211
240
 
212
241
  The language service holds the same paths to Effect-native IO through
213
242
  `overrides` in the plugin block of `tsconfig.json`. effect-tsgo keeps the
@@ -262,8 +291,9 @@ file and the path to move it to when it does not:
262
291
  them; `tests/fixtures/**` is data and is not parsed. Detection parses
263
292
  with swc and reads import specifiers and identifier use, so a test that
264
293
  only carries `"node:child_process"` as a string is not a violation.
265
- - `scripts.test` is exactly `bun test --randomize` and `scripts.lint` runs
266
- this check, itself or through `checks-lint` called by its bare bin name.
294
+ - `scripts.test` is exactly `checks-test`, which runs `bun test --randomize`
295
+ (see "Test entry point"), and `scripts.lint` runs this check, itself or
296
+ through `checks-lint` called by its bare bin name.
267
297
  - `bunfig.toml` carries every `[test]` key of the shipped preset with the
268
298
  same value, and `[test].pathIgnorePatterns` is always
269
299
  `["**/tests/quarantine/**"]`: the check pins it itself, so this repo,
@@ -282,6 +312,109 @@ still run on demand:
282
312
  bun test --path-ignore-patterns='' tests/quarantine
283
313
  ```
284
314
 
315
+ ## Test entry point
316
+
317
+ `checks-test` runs the whole suite with `bun test --randomize`, passes
318
+ bun's output through, and then reads bun's JUnit report of the same run.
319
+ bun exits 0 with tests skipped, so a green run says nothing about the
320
+ tests that never ran. `checks-test` fails when a test was skipped, by
321
+ `test.skip`, `test.skipIf`, `test.if`, `describe.skip` or `test.todo`,
322
+ without a declaration in `package.json`:
323
+
324
+ ```json
325
+ "testSkips": [
326
+ {
327
+ "file": "tests/e2e/docker.test.ts",
328
+ "test": "images > builds the release image",
329
+ "reason": "the runner has no docker daemon",
330
+ "when": "ci"
331
+ }
332
+ ]
333
+ ```
334
+
335
+ `file` is the path bun reports, relative to the package root, and `test`
336
+ is the name bun's console prints: the describe blocks and the test name
337
+ joined by ` > `. `reason` is required. `when` is `ci` or `local` for a
338
+ test skipped only there, and a declaration without it holds in both;
339
+ `checks-test` counts a run as `ci` when `CI` is set true, as GitHub
340
+ Actions sets it. A declaration that holds for the run but matches no
341
+ skipped test fails a ci run too, so a fixed or renamed test takes its
342
+ declaration with it. A local run only warns about it, because whether a
343
+ test skips there can hang on the machine, such as a docker daemon being
344
+ up:
345
+
346
+ ```
347
+ checks-test: 1 skipped test(s) undeclared and 1 declaration(s) matching no skipped test in this ci run:
348
+ tests/pricing.test.ts:12 pricing > rounds half to even: skipped with no declaration; run it, or declare it in package.json testSkips with its reason
349
+ tests/e2e/docker.test.ts > images > builds the release image: declared, but no such test skipped; delete the declaration
350
+ ```
351
+
352
+ It exits 1 when a test failed or a skip is undeclared or, in a ci run,
353
+ a declaration stale, and 2 when `testSkips` does not parse or bun
354
+ passed without writing its report. It takes no arguments: a `-t`
355
+ filter reports every test it leaves out as skipped and a path filter
356
+ drops files a declaration names, so a narrowed run is plain
357
+ `bun test --randomize` with the arguments. Files under
358
+ `tests/quarantine/` are never run and so never reported; see "Test
359
+ layout".
360
+
361
+ ## Flake run
362
+
363
+ A green run proves nothing failed in that run, not that no test is
364
+ flaky. `checks-flake` runs the whole suite several times, each with its
365
+ own `--seed`, and records per failing test the seeds it failed with:
366
+
367
+ ```sh
368
+ checks-flake [--runs <count> | --seed <seed>...] [--report <file>]
369
+ ```
370
+
371
+ `--runs` defaults to 10 runs on random seeds, and `--seed`, given once
372
+ per run, replays chosen seeds, such as the ones a report recorded.
373
+ `bun test --randomize --seed=<seed>` puts the suite in the same order,
374
+ so a seed reproduces a failure that hangs on order. `--report` writes the
375
+ record as JSON, every run's seed and failing tests and every failing
376
+ test's seeds, and under GitHub Actions the summary below is appended to
377
+ the job summary:
378
+
379
+ ```
380
+ checks-flake: 3 of 10 run(s) failed, 1 test(s) failing in them
381
+
382
+ | Test | Failed | Seeds |
383
+ | --- | --- | --- |
384
+ | tests/cache.test.ts:6 reads the cache | 3 of 10 runs | 2170533150, 4046124386, 180394251 |
385
+
386
+ Reproduce a failing run with bun test --randomize --seed=<seed>.
387
+ ```
388
+
389
+ A run that fails with no failing test, such as a test file that throws
390
+ while loading, is listed with its seed on its own line. It exits 1 when
391
+ any run failed and 2 when bun passed without writing its report.
392
+
393
+ A consumer runs it on a schedule and keeps the record as an artifact:
394
+
395
+ ```yaml
396
+ on:
397
+ schedule:
398
+ - cron: "17 5 * * *"
399
+ workflow_dispatch:
400
+ jobs:
401
+ flake:
402
+ runs-on: ubuntu-latest
403
+ steps:
404
+ - uses: actions/checkout@v5
405
+ - uses: oven-sh/setup-bun@v2
406
+ - run: bun install --frozen-lockfile
407
+ - run: bunx checks-flake --runs 10 --report flake-report.json
408
+ - uses: actions/upload-artifact@v4
409
+ if: always()
410
+ with:
411
+ name: flake-report
412
+ path: flake-report.json
413
+ ```
414
+
415
+ and declares the step in `ciWiring.scheduled`, so `checks-ci-wiring`
416
+ fails once the schedule stops running it; see "CI wiring".
417
+
285
418
  ## Dependency rules
286
419
 
287
420
  `.dependency-cruiser.cjs` extends the shared base, which carries
@@ -408,15 +541,23 @@ bun run checks-comment-gate <ref>
408
541
  ```
409
542
 
410
543
  With two arguments it diffs the base against the head. With one it diffs
411
- that commit against its parent, and exits 2 when that parent is not in
412
- the clone rather than widening to the whole tree, so a shallow checkout
413
- running the one-argument form needs `fetch-depth: 2`. Only added lines
544
+ that commit against its parent, or against the empty tree for a
545
+ repository's first commit, which has none. It exits 2 when the parent
546
+ exists but is not in the clone rather than widening to the whole tree,
547
+ so a shallow checkout running the one-argument form needs
548
+ `fetch-depth: 2`. Only added lines
414
549
  are checked, so a violation in a file the diff never touches stays
415
550
  silent, and a refusal counts when any line of the comment carrying it was
416
551
  added. The check refuses a machine-read directive, a record or ticket
417
552
  pointer, a doc block, and a file opening with a rationale block over
418
- three lines, licence headers excepted; `scripts/comments.ts` holds the
419
- scanner the gate and the backtest share.
553
+ three lines, licence headers excepted.
554
+
555
+ `scripts/comment-matchers.ts` holds the scanner, the comment syntaxes
556
+ and a synchronous `refused()`, and imports nothing, so a host such as a
557
+ hook bundle can copy it alone into a directory with no `node_modules`
558
+ and import it as `@avi2dg/checks/scripts/comment-matchers.ts`. The repo's
559
+ cruise fails when it gains an import. `scripts/comments.ts` wraps the
560
+ same matchers in Effect for the gate and the backtest.
420
561
 
421
562
  `checks-lint` runs it over each pull request's range; see "Lint entry point".
422
563
 
@@ -434,8 +575,9 @@ bun run checks-suppressions-ratchet <base-ref> <head-ref>
434
575
  bun run checks-suppressions-ratchet <ref>
435
576
  ```
436
577
 
437
- With one argument it compares that commit with its parent, and exits 2
438
- when the parent is not in the clone. With two it reads the base where
578
+ With one argument it compares that commit with its parent, or with the
579
+ empty tree for a repository's first commit, and exits 2 when the parent
580
+ exists but is not in the clone. With two it reads the base where
439
581
  the head branched off, at their merge-base, so a count the base branch
440
582
  lowered since does not read as a rise at the head. A count that fell and
441
583
  an entry that went both pass. A commit without the file counts as empty,
@@ -523,9 +665,80 @@ ci-wiring: 1 of 8 gate(s) do not run on pull requests to main:
523
665
  .github/workflows/release.yml job publish step 7: .github/workflows/release.yml does not trigger on pull_request
524
666
  ```
525
667
 
526
- It exits 2 when `package.json` declares no gates, a gate is not one
527
- plain command, or a workflow does not parse. Whether a workflow is
528
- well formed is actionlint's question, not this one's.
668
+ It exits 2 when `package.json` declares no gates, `scheduled` is not an
669
+ array, a gate or scheduled command is not one plain command, or a
670
+ workflow does not parse. Whether a workflow is well formed is
671
+ actionlint's question, not this one's.
672
+
673
+ A command a schedule must run, such as the flake run, goes in
674
+ `"scheduled"` beside `"gates"`:
675
+
676
+ ```json
677
+ "ciWiring": {
678
+ "gates": ["bun run lint", "bun run typecheck", "bun run test"],
679
+ "scheduled": ["bunx checks-flake --runs 10 --report flake-report.json"]
680
+ }
681
+ ```
682
+
683
+ Each counts only as a step of the same plain shape in a workflow whose
684
+ `on` carries `schedule` with at least one `cron`, under the same
685
+ `if: false`, `continue-on-error: true` and `needs` rules as a gate. It
686
+ exits 1 naming each one no schedule runs:
687
+
688
+ ```
689
+ ci-wiring: 1 of 1 scheduled command(s) do not run on a schedule:
690
+ bunx checks-flake --runs 10 --report flake-report.json
691
+ .github/workflows/ci.yml job checks step 5: .github/workflows/ci.yml does not trigger on a schedule
692
+ ```
693
+
694
+ ### Gate selection
695
+
696
+ A repository with no TypeScript source gives `checks-lint-coverage` and
697
+ `checks-test-layout` nothing to check, and test-layout still refuses its
698
+ missing `bun test` script and `bunfig.toml`. It declares the gates
699
+ `checks-lint` runs as `lintGates`, beside `gates`:
700
+
701
+ ```json
702
+ "ciWiring": {
703
+ "gates": ["bun run lint"],
704
+ "lintGates": [
705
+ "checks-commit-identity",
706
+ "checks-comment-gate",
707
+ "checks-suppressions-ratchet",
708
+ "checks-ci-wiring"
709
+ ]
710
+ }
711
+ ```
712
+
713
+ `checks-lint` runs exactly those, in the "Lint entry point" table's
714
+ order, and all six when `lintGates` is absent. A step running
715
+ `checks-lint` then counts only for a declared gate that `lintGates`
716
+ keeps.
717
+
718
+ A selection may leave out only a gate that does not apply:
719
+
720
+ | Gate | Applies when the repository |
721
+ | --- | --- |
722
+ | `checks-lint-coverage` | tracks a `.ts` or `.tsx` file |
723
+ | `checks-test-layout` | tracks a `.ts` or `.tsx` file |
724
+ | `checks-commit-identity` | always |
725
+ | `checks-comment-gate` | always |
726
+ | `checks-suppressions-ratchet` | always |
727
+ | `checks-ci-wiring` | always |
728
+
729
+ Both bins exit 2 on a `lintGates` that names an unknown gate or leaves
730
+ out one that always applies. ci-wiring exits 1 when the
731
+ selection leaves out a gate the repository's tracked files make
732
+ applicable, and names the gate and the files:
733
+
734
+ ```
735
+ ci-wiring: ciWiring.lintGates leaves out 2 gate(s) this repository's contents make applicable:
736
+ checks-lint-coverage: the repository tracks TypeScript source (src/widget.ts)
737
+ checks-test-layout: the repository tracks TypeScript source (src/widget.ts)
738
+ ```
739
+
740
+ It reads the files tracked at the checkout, so the pull request that
741
+ adds the first TypeScript file is the one refused.
529
742
 
530
743
  ### Limits
531
744
 
@@ -655,6 +868,16 @@ The shared Stryker preset's `json` reporter writes
655
868
  the entry point, and `lint-coverage.sh` stays a shell script. The
656
869
  gates run one at a time with their output passed straight through, so
657
870
  each report reads whole and in the table's order.
871
+ - A gate selection is checked against the repository's contents rather
872
+ than trusted, so it cannot skip a gate that applies. ci-wiring does
873
+ that check, which is why a selection without it, or without another
874
+ gate that applies everywhere, is refused as `checks-lint` reads it:
875
+ nothing would check the selection otherwise.
876
+ - `checks-test` runs bun itself rather than reading a report some other
877
+ run left: a skip taken only on CI is visible only in CI's own run, and
878
+ an earlier run's report may be stale or narrowed. It reads the JUnit
879
+ report bun writes to a temporary directory, since bun has no other
880
+ per-test output meant for a program.
658
881
  - `checks-ci-wiring` runs inside `lint`, not in a workflow of its own:
659
882
  deleting the step that runs a check is the violation it catches, so the
660
883
  local `lint` is where it has to fail.
@@ -695,7 +918,8 @@ a tag off `main` and reruns the build, `dist/` check, lint, typecheck
695
918
  and tests before it publishes:
696
919
 
697
920
  ```sh
698
- git tag v0.8.0 && git push origin v0.8.0
921
+ tag="v$(bun -p 'require("./package.json").version')"
922
+ git tag "$tag" && git push origin "$tag"
699
923
  ```
700
924
 
701
925
  The `release` workflow publishes the tagged version through npm
package/oxlintrc.json CHANGED
@@ -14,8 +14,8 @@
14
14
  "typescript/no-unnecessary-type-parameters": "error",
15
15
  "typescript/use-unknown-in-catch-callback-variable": "error",
16
16
  "eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", "ignoreRestSiblings": true }],
17
- "typescript/no-unsafe-type-assertion": "off",
18
- "typescript/no-non-null-assertion": "off",
17
+ "typescript/no-unsafe-type-assertion": "error",
18
+ "typescript/no-non-null-assertion": "error",
19
19
  "typescript/consistent-return": "off"
20
20
  }
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avi2dg/checks",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Deterministic checks shared across the captain's TypeScript repos",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,9 +21,13 @@
21
21
  "scripts/gates.ts",
22
22
  "scripts/lint.ts",
23
23
  "scripts/test-layout.ts",
24
+ "scripts/test.ts",
25
+ "scripts/test-report.ts",
26
+ "scripts/flake.ts",
24
27
  "scripts/commit-identity.ts",
25
28
  "scripts/mutation-compare.ts",
26
29
  "scripts/ci-wiring.ts",
30
+ "scripts/comment-matchers.ts",
27
31
  "scripts/comments.ts",
28
32
  "scripts/comment-gate.ts",
29
33
  "scripts/suppressions-ratchet.ts",
@@ -40,9 +44,12 @@
40
44
  "./scripts/lint-coverage.sh": "./scripts/lint-coverage.sh",
41
45
  "./scripts/lint.ts": "./scripts/lint.ts",
42
46
  "./scripts/test-layout.ts": "./scripts/test-layout.ts",
47
+ "./scripts/test.ts": "./scripts/test.ts",
48
+ "./scripts/flake.ts": "./scripts/flake.ts",
43
49
  "./scripts/commit-identity.ts": "./scripts/commit-identity.ts",
44
50
  "./scripts/mutation-compare.ts": "./scripts/mutation-compare.ts",
45
51
  "./scripts/ci-wiring.ts": "./scripts/ci-wiring.ts",
52
+ "./scripts/comment-matchers.ts": "./scripts/comment-matchers.ts",
46
53
  "./scripts/comments.ts": "./scripts/comments.ts",
47
54
  "./scripts/comment-gate.ts": "./scripts/comment-gate.ts",
48
55
  "./scripts/suppressions-ratchet.ts": "./scripts/suppressions-ratchet.ts",
@@ -56,6 +63,8 @@
56
63
  "checks-lint": "scripts/lint.ts",
57
64
  "checks-lint-coverage": "scripts/lint-coverage.sh",
58
65
  "checks-test-layout": "scripts/test-layout.ts",
66
+ "checks-test": "scripts/test.ts",
67
+ "checks-flake": "scripts/flake.ts",
59
68
  "checks-commit-identity": "scripts/commit-identity.ts",
60
69
  "checks-mutation-compare": "scripts/mutation-compare.ts",
61
70
  "checks-ci-wiring": "scripts/ci-wiring.ts",
@@ -65,9 +74,9 @@
65
74
  },
66
75
  "scripts": {
67
76
  "build": "bun build effect-channel/index.ts --outdir dist --target node --format esm",
68
- "lint": "oxlint --type-aware && bun scripts/lint.ts && depcruise --config .dependency-cruiser.cjs effect-channel scripts tests commitlint.config.js dependency-cruiser.config.js stryker.preset.js .dependency-cruiser.cjs",
77
+ "lint": "oxlint --type-aware && bun scripts/lint.ts && depcruise --config .dependency-cruiser.cjs .",
69
78
  "typecheck": "tsc --noEmit && effect-tsgo diagnostics --project tsconfig.json --format text --strict",
70
- "test": "bun test --randomize"
79
+ "test": "bun scripts/test.ts"
71
80
  },
72
81
  "ciWiring": {
73
82
  "gates": [
@@ -77,6 +86,9 @@
77
86
  "bun run typecheck",
78
87
  "bun run test",
79
88
  "./node_modules/.bin/commitlint"
89
+ ],
90
+ "scheduled": [
91
+ "bun scripts/flake.ts --runs 10 --report flake-report.json"
80
92
  ]
81
93
  },
82
94
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import { Console, Effect, Option } from "effect";
3
- import { comments, refused, SYNTAXES } from "./comments.ts";
3
+ import { comments, refused, syntaxOf } from "./comments.ts";
4
4
  import { git } from "./git.ts";
5
5
  import { runMain, Usage } from "./main.ts";
6
6
 
@@ -18,8 +18,7 @@ const EXCLUDED = ["generated/", "vendor/", "repos/", "node_modules/", "dist/"];
18
18
 
19
19
  function readable(path: string): boolean {
20
20
  if (EXCLUDED.some((prefix) => path.startsWith(prefix))) return false;
21
- const extension = path.slice(path.lastIndexOf(".") + 1);
22
- return extension in SYNTAXES;
21
+ return syntaxOf(path) !== undefined;
23
22
  }
24
23
 
25
24
  const at = (sha: string, path: string) => git(["show", `${sha}:${path}`]).pipe(Effect.option);