@avi2dg/checks 0.25.0 → 0.26.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 +9 -0
- package/README.md +3 -4
- package/docs/configs/effect-rules.md +2 -2
- package/docs/configs/native-settings.md +2 -1
- package/docs/design.md +2 -2
- package/docs/gates/checks-docs.md +11 -9
- package/docs/gates/checks-exports.md +104 -0
- package/package.json +8 -5
- package/src/complexity/exports.ts +196 -0
- package/src/complexity/knip.ts +70 -0
- package/src/complexity/unused.ts +7 -56
- package/src/core/gates.ts +2 -1
- package/src/core/swc.ts +1 -1
- package/src/docs/doc-references.ts +1 -1
- package/src/docs/doc-snapshot.ts +1 -1
- package/src/docs/doc-templates.ts +3 -3
- package/src/quality/comments.ts +2 -2
- package/src/testing/test-report.ts +1 -1
- /package/{templates → dist/templates}/adr.md +0 -0
- /package/{templates → dist/templates}/agents.md +0 -0
- /package/{templates → dist/templates}/changelog.md +0 -0
- /package/{templates → dist/templates}/claude.md +0 -0
- /package/{templates → dist/templates}/explanation.md +0 -0
- /package/{templates → dist/templates}/how-to.md +0 -0
- /package/{templates → dist/templates}/readme.md +0 -0
- /package/{templates → dist/templates}/reference.md +0 -0
- /package/{templates → dist/templates}/tutorial.md +0 -0
- /package/{presets → src/quality/presets}/effect.language-service.json +0 -0
- /package/{presets → src/quality/presets}/effect.oxlint.json +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Every release of `@avi2dg/checks`, newest first, written by the release from its conventional commits.
|
|
4
4
|
|
|
5
|
+
## 0.26.0
|
|
6
|
+
|
|
7
|
+
Released 2026-09-27.
|
|
8
|
+
|
|
9
|
+
### Breaking changes
|
|
10
|
+
|
|
11
|
+
- **complexity:** add checks-exports gate holding unused exports to a shrinking baseline [#89](https://github.com/avi2d/checks/pull/89)
|
|
12
|
+
- move doc templates into dist/templates and presets into src/quality [#88](https://github.com/avi2d/checks/pull/88)
|
|
13
|
+
|
|
5
14
|
## 0.25.0
|
|
6
15
|
|
|
7
16
|
Released 2026-09-27.
|
package/README.md
CHANGED
|
@@ -119,6 +119,7 @@ Each gate belongs to the vector it judges a repository on, and the table groups
|
|
|
119
119
|
| complexity | [`checks-suppressions-ratchet`](docs/gates/checks-suppressions-ratchet.md) | the range | every repository |
|
|
120
120
|
| complexity | [`checks-repetition`](docs/gates/checks-repetition.md) | the range | a repository tracking `*.ts` or `*.tsx` |
|
|
121
121
|
| complexity | [`checks-unused`](docs/gates/checks-unused.md) | the working tree | a repository tracking `*.ts` or `*.tsx` |
|
|
122
|
+
| complexity | [`checks-exports`](docs/gates/checks-exports.md) | the range | a repository tracking `*.ts` or `*.tsx` |
|
|
122
123
|
| quality | [`checks-lint-coverage`](docs/gates/checks-lint-coverage.md) | the working tree | a repository tracking `*.ts` or `*.tsx` |
|
|
123
124
|
| quality | [`checks-comment-gate`](docs/gates/checks-comment-gate.md) | the range | every repository |
|
|
124
125
|
| testing | [`checks-test-layout`](docs/gates/checks-test-layout.md) | the working tree | a repository tracking `*.ts` or `*.tsx` |
|
|
@@ -170,13 +171,11 @@ Every path is relative to the installed package, `node_modules/@avi2dg/checks/`.
|
|
|
170
171
|
| `commitlint.config.js` | the shared commitlint config |
|
|
171
172
|
| `dependency-cruiser.config.js` | the shared dependency-cruiser base |
|
|
172
173
|
| `knip-base.json` | the Knip base a repository's configuration imports |
|
|
173
|
-
| `src/` | every bin, which a package script calls by its `checks-` name,
|
|
174
|
-
| `
|
|
175
|
-
| `presets/` | the Effect rule blocks a repository copies into its native config |
|
|
174
|
+
| `src/` | every bin, which a package script calls by its `checks-` name, the modules the bins import, and the Effect rule blocks under `src/quality/presets/` |
|
|
175
|
+
| `dist/` | the compiled oxlint plugins and the doc templates, one template per kind of doc file |
|
|
176
176
|
| `oxlintrc.json` | the oxlint base config `.oxlintrc.json` extends |
|
|
177
177
|
| `stryker.preset.js` | the Stryker mutation-testing preset |
|
|
178
178
|
| `tsconfig.effect.json` | the tsconfig fragment with the Effect language-service block |
|
|
179
|
-
| `dist/` | the compiled oxlint plugins, one per purpose |
|
|
180
179
|
|
|
181
180
|
<!-- end generated shipped -->
|
|
182
181
|
|
|
@@ -31,7 +31,7 @@ The repo's `.oxlintrc.json` owns its Effect paths and exemptions in an override:
|
|
|
31
31
|
}
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
The kit ships the rule block in `presets/effect.oxlint.json` for copying into the override.
|
|
34
|
+
The kit ships the rule block in `src/quality/presets/effect.oxlint.json` for copying into the override.
|
|
35
35
|
Each config in an oxlint `extends` chain sets `plugins` explicitly, because an omitted list enables defaults across the chain.
|
|
36
36
|
`unicorn/no-process-exit` does not check a shebang script, so a bin can use `eslint/no-restricted-properties` for `process.exit`.
|
|
37
37
|
|
|
@@ -39,7 +39,7 @@ Each config in an oxlint `extends` chain sets `plugins` explicitly, because an o
|
|
|
39
39
|
|
|
40
40
|
The repository's `tsconfig.json` holds its Effect override under `compilerOptions.plugins`.
|
|
41
41
|
The override includes the same source paths and excludes the same exempt paths as oxlint.
|
|
42
|
-
The kit ships severity values in `presets/effect.language-service.json` for the override's `options`.
|
|
42
|
+
The kit ships severity values in `src/quality/presets/effect.language-service.json` for the override's `options`.
|
|
43
43
|
The kit's `tsconfig.effect.json` keeps the shared language service diagnostics.
|
|
44
44
|
|
|
45
45
|
## Related topics
|
|
@@ -13,8 +13,9 @@ A consuming repository puts each setting in the file its tool reads.
|
|
|
13
13
|
| `.github/workflows/*.yml` | Pull request commands, scheduled commands, runner labels | GitHub Actions and `checks-ci-wiring` |
|
|
14
14
|
| `.oxlintrc.json` | Effect paths, exemptions, file size, function size, statements, cognitive complexity and depth | oxlint |
|
|
15
15
|
| `oxlint-suppressions.json` | The existing violations oxlint suppresses, per file and rule | oxlint and `checks-suppressions-ratchet` |
|
|
16
|
+
| `exports-baseline.json` | The existing unused exports and types, per file, kind and name | `checks-exports` |
|
|
16
17
|
| `.jscpd.json` | The `path` and `ignore` globs of the files repetition is measured in | jscpd and `checks-repetition` |
|
|
17
|
-
| `knip.config.ts` | The `entry` globs Knip traces unreferenced files from, spread over the kit's `knip-base.json` | Knip and `checks-
|
|
18
|
+
| `knip.config.ts` | The `entry` globs Knip traces unreferenced files from, spread over the kit's `knip-base.json` | Knip, `checks-unused` and `checks-exports` |
|
|
18
19
|
| `tsconfig.json` | Effect language service scope and severity | TypeScript and Effect language service |
|
|
19
20
|
| `package.json` | `scripts` with the `checks-vendor` arguments in `prepare`, `author` and `contributors` | Bun, `checks-commit-identity` and `checks-vendor` |
|
|
20
21
|
| `bunfig.toml` | Test discovery and quarantine exclusion | Bun and `checks-test-layout` |
|
package/docs/design.md
CHANGED
|
@@ -30,7 +30,7 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
|
|
|
30
30
|
- `dist/` is committed.
|
|
31
31
|
No `prepack` or `prepublishOnly` builds it, so a publish ships whatever bundle the publishing worktree holds.
|
|
32
32
|
Rebuild it after pulling with `bun run build`.
|
|
33
|
-
`bun run build` also emits `templates/` and `CHANGELOG.md`, which are committed the same way.
|
|
33
|
+
`bun run build` also emits `dist/templates/` and `CHANGELOG.md`, which are committed the same way.
|
|
34
34
|
CI runs `git diff --exit-code` over the whole tree after the build, because a test that compares a generated file with its source passes on the copy the build just rewrote.
|
|
35
35
|
- `CHANGELOG.md` is generated, so a release commit carries it and the tarball ships it.
|
|
36
36
|
The commit that bumps package.json `version` closes its release, and the `v*` tag goes on that commit, so commits merged after it wait for the next release.
|
|
@@ -78,7 +78,7 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
|
|
|
78
78
|
Setting your own `options.enhancedResolveOptions` replaces the base's, so restate `exportsFields` and `conditionNames` if you do.
|
|
79
79
|
- dependency-cruiser `extends` merges same-name `forbidden` rules with the child's fields winning.
|
|
80
80
|
That is the entry-point and layer recipe under Boundaries in [The dependency rules](configs/dependency-rules.md).
|
|
81
|
-
- The templates in `templates/` are rendered from `src/docs/doc-templates.ts`, the spec `checks-docs` reads.
|
|
81
|
+
- The templates in `dist/templates/` are rendered from `src/docs/doc-templates.ts`, the spec `checks-docs` reads.
|
|
82
82
|
A template written by hand beside the check agrees with it only until someone edits one of them.
|
|
83
83
|
- A page's Diátaxis mode comes from `kind` front matter on the page, whatever directory holds it.
|
|
84
84
|
The repository makes the judgment beside the page, and the check holds it to that template.
|
|
@@ -11,23 +11,25 @@ audience: consumers
|
|
|
11
11
|
It holds each doc file a change touches to the template for its kind, and lists every other doc file that does not conform yet without failing.
|
|
12
12
|
It holds each line a change adds or edits in a living doc or an agent file to the prose rules, as [The prose rules](#the-prose-rules) says.
|
|
13
13
|
It fails when a living doc names a path, link or command that does not resolve, and the range added it or broke it, as [Paths, links and commands](#paths-links-and-commands) says.
|
|
14
|
-
The package ships one template per kind under `templates/`, and a repository starts a new doc file by copying one:
|
|
14
|
+
The package ships one template per kind under `dist/templates/`, and a repository starts a new doc file by copying one:
|
|
15
15
|
|
|
16
16
|
```sh
|
|
17
|
-
cp node_modules/@avi2dg/checks/templates/how-to.md docs/add-a-supplier.md
|
|
17
|
+
cp node_modules/@avi2dg/checks/dist/templates/how-to.md docs/add-a-supplier.md
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
A host resolves a template as `@avi2dg/checks/templates/how-to.md`, which keeps pointing at it wherever the package holds the templates.
|
|
21
|
+
|
|
20
22
|
<!-- generated doc-kinds: bun run build writes it from src/docs/doc-rules.ts, src/docs/doc-templates.ts and scripts/doc-blocks.ts -->
|
|
21
23
|
|
|
22
24
|
| File | Kind | Template |
|
|
23
25
|
| --- | --- | --- |
|
|
24
|
-
| `README.md` | readme | `templates/readme.md` |
|
|
25
|
-
| `CHANGELOG.md` | changelog | `templates/changelog.md` |
|
|
26
|
-
| `AGENTS.md` | agents | `templates/agents.md` |
|
|
27
|
-
| `CLAUDE.md` | claude | `templates/claude.md` |
|
|
28
|
-
| `CONTRIBUTING.md` | how-to | `templates/how-to.md` |
|
|
29
|
-
| each file in `docs/adr/` but its generated index, `README.md` | adr | `templates/adr.md` |
|
|
30
|
-
| a page with `kind` in front matter | tutorial, how-to, reference or explanation | `templates/<mode>.md` |
|
|
26
|
+
| `README.md` | readme | `dist/templates/readme.md` |
|
|
27
|
+
| `CHANGELOG.md` | changelog | `dist/templates/changelog.md` |
|
|
28
|
+
| `AGENTS.md` | agents | `dist/templates/agents.md` |
|
|
29
|
+
| `CLAUDE.md` | claude | `dist/templates/claude.md` |
|
|
30
|
+
| `CONTRIBUTING.md` | how-to | `dist/templates/how-to.md` |
|
|
31
|
+
| each file in `docs/adr/` but its generated index, `README.md` | adr | `dist/templates/adr.md` |
|
|
32
|
+
| a page with `kind` in front matter | tutorial, how-to, reference or explanation | `dist/templates/<mode>.md` |
|
|
31
33
|
|
|
32
34
|
<!-- end generated doc-kinds -->
|
|
33
35
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: reference
|
|
3
|
+
audience: consumers
|
|
4
|
+
---
|
|
5
|
+
# checks-exports
|
|
6
|
+
|
|
7
|
+
`checks-exports` is the gate that refuses an exported value or type nothing imports, and a reader looks it up when Knip names a symbol no file reaches.
|
|
8
|
+
|
|
9
|
+
## What it checks
|
|
10
|
+
|
|
11
|
+
It runs Knip with the repository's own configuration and names each exported value and type no file imports.
|
|
12
|
+
It reads only the exports and types issue types, so an unreferenced file or an unused dependency never fails it.
|
|
13
|
+
It asks Knip for those issue types itself, so a configuration that narrows `include` or excludes them still has its exports judged.
|
|
14
|
+
A configuration that turns the exports or types rules off silences those symbols, so keep both rules on.
|
|
15
|
+
It holds each reported symbol against `exports-baseline.json`, and fails on any symbol the baseline does not hold.
|
|
16
|
+
It fails on any baseline entry the range adds unless Knip reports that same symbol unused at the base, so the baseline records debt that already existed and never debt the range creates.
|
|
17
|
+
It fails on any baseline entry Knip no longer reports, so the baseline only shrinks.
|
|
18
|
+
It fails when the repository tracks no TypeScript source, since an empty scan would pass without judging anything.
|
|
19
|
+
It fails when the repository holds no Knip configuration, since Knip's default entries cannot tell a dead export from a public one.
|
|
20
|
+
|
|
21
|
+
## What it reads
|
|
22
|
+
|
|
23
|
+
It runs Knip over the working tree, so an uncommitted file is judged like a committed one.
|
|
24
|
+
It reads the repository's Knip configuration, which names the entries.
|
|
25
|
+
It reads `exports-baseline.json` in the repository root, which holds each accepted unused export as a file, kind and name triple.
|
|
26
|
+
It reads `exports-baseline.json` again at the base of the range, where a commit without the file counts as empty.
|
|
27
|
+
When the range adds baseline entries, it runs Knip again on a temporary checkout of the base and removes the checkout when it ends.
|
|
28
|
+
That checkout links the repository's `node_modules`, so a configuration importing the kit's base loads there too.
|
|
29
|
+
Knip configurations do not extend a package file, so the consumer configuration imports the kit's base and spreads it:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import base from "@avi2dg/checks/knip-base.json";
|
|
33
|
+
|
|
34
|
+
export default { ...base, entry: ["src/index.ts", "tests/**/*.test.ts"] };
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The base in `knip-base.json` reports only unreferenced files.
|
|
38
|
+
The gate resolves the Knip binary from the installed kit, so a consumer installs nothing beyond the kit.
|
|
39
|
+
|
|
40
|
+
## Arguments
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
checks-exports <base-ref> <head-ref>
|
|
44
|
+
checks-exports <ref>
|
|
45
|
+
checks-exports --write
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
With two arguments the base is where the head branched off, at their merge-base.
|
|
49
|
+
When the checked-out commit is a merge of the head ref and one other commit, as a pull request's default checkout is, the base is that other commit instead.
|
|
50
|
+
So under `checks-lint` a pull request's merge checkout is judged against the base branch tip it merges, and a symbol that branch already baselined does not count as added.
|
|
51
|
+
With one the base is that commit's parent, or the empty tree for a repository's first commit, where nothing counts as already unused.
|
|
52
|
+
With `--write` it records every unused export and type Knip reports into `exports-baseline.json`, the way `oxlint --suppress-all` seeds `oxlint-suppressions.json`.
|
|
53
|
+
|
|
54
|
+
## Seeding a baseline
|
|
55
|
+
|
|
56
|
+
A repository adopting the gate runs `checks-exports --write` on a branch that changes no source and commits the file it writes.
|
|
57
|
+
The gate passes that range, since Knip reports every seeded symbol unused at the base too.
|
|
58
|
+
A seed written after the range adds a dead export, or after it removes the last import of one, fails on that symbol.
|
|
59
|
+
|
|
60
|
+
## Exit codes
|
|
61
|
+
|
|
62
|
+
| Code | When |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| 0 | no reported symbol is unlisted, no baseline entry is added or stale, or `--write` recorded the baseline |
|
|
65
|
+
| 1 | a reported symbol is unlisted, a baseline entry is added or stale or the repository holds no Knip configuration |
|
|
66
|
+
| 2 | a ref does not resolve, the repository tracks no TypeScript source, Knip cannot run, or a report or baseline does not parse |
|
|
67
|
+
|
|
68
|
+
## Sample output
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
exports: 2 unused export(s) not in exports-baseline.json:
|
|
72
|
+
src/used.ts: unusedExport (export)
|
|
73
|
+
src/used.ts: UnusedOptions (type)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
A baseline entry the range adds fails when Knip does not report its symbol unused at the base:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
exports: 1 exports-baseline.json export(s) the range adds that its base did not leave unused, remove the export instead:
|
|
80
|
+
src/used.ts: unusedExport (export)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A passing run with a held baseline counts what it holds:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
exports: 2 unused export(s) in exports-baseline.json, and no new ones
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
A passing run with an empty baseline says so:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
exports: no unused exports or types
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Opting out
|
|
96
|
+
|
|
97
|
+
`checks-lint` runs this gate only when the repository tracks `.ts` or `.tsx` files.
|
|
98
|
+
A repository that tracks one names its entries in a Knip configuration.
|
|
99
|
+
|
|
100
|
+
## Related topics
|
|
101
|
+
|
|
102
|
+
- [checks-lint](checks-lint.md)
|
|
103
|
+
- [checks-suppressions-ratchet](checks-suppressions-ratchet.md)
|
|
104
|
+
- [checks-unused](checks-unused.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avi2dg/checks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Deterministic checks shared across the captain's TypeScript repos",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
"src/complexity/suppressions-ratchet.ts",
|
|
48
48
|
"src/complexity/repetition.ts",
|
|
49
49
|
"src/complexity/unused.ts",
|
|
50
|
+
"src/complexity/exports.ts",
|
|
51
|
+
"src/complexity/knip.ts",
|
|
50
52
|
"src/testing/quarantine-clock.ts",
|
|
51
53
|
"src/docs/docs.ts",
|
|
52
54
|
"src/dependencies/vendor.ts",
|
|
@@ -56,9 +58,9 @@
|
|
|
56
58
|
"src/docs/doc-references.ts",
|
|
57
59
|
"src/docs/doc-snapshot.ts",
|
|
58
60
|
"src/docs/doc-templates.ts",
|
|
59
|
-
"templates/",
|
|
60
|
-
"presets/effect.oxlint.json",
|
|
61
|
-
"presets/effect.language-service.json",
|
|
61
|
+
"dist/templates/",
|
|
62
|
+
"src/quality/presets/effect.oxlint.json",
|
|
63
|
+
"src/quality/presets/effect.language-service.json",
|
|
62
64
|
"oxlintrc.json",
|
|
63
65
|
"stryker.preset.js",
|
|
64
66
|
"tsconfig.effect.json",
|
|
@@ -72,6 +74,7 @@
|
|
|
72
74
|
"./scripts/comment-matchers.ts": "./src/quality/comment-matchers.ts",
|
|
73
75
|
"./scripts/prose-matchers.ts": "./src/docs/prose-matchers.ts",
|
|
74
76
|
"./stryker.preset.js": "./stryker.preset.js",
|
|
77
|
+
"./templates/*": "./dist/templates/*",
|
|
75
78
|
"./tsconfig.effect.json": "./tsconfig.effect.json"
|
|
76
79
|
},
|
|
77
80
|
"bin": {
|
|
@@ -91,6 +94,7 @@
|
|
|
91
94
|
"checks-suppressions-ratchet": "src/complexity/suppressions-ratchet.ts",
|
|
92
95
|
"checks-repetition": "src/complexity/repetition.ts",
|
|
93
96
|
"checks-unused": "src/complexity/unused.ts",
|
|
97
|
+
"checks-exports": "src/complexity/exports.ts",
|
|
94
98
|
"checks-quarantine-clock": "src/testing/quarantine-clock.ts",
|
|
95
99
|
"checks-docs": "src/docs/docs.ts",
|
|
96
100
|
"checks-vendor": "src/dependencies/vendor.ts"
|
|
@@ -116,7 +120,6 @@
|
|
|
116
120
|
"@oxlint/plugins": "1.83.0",
|
|
117
121
|
"@swc/core": "1.16.2",
|
|
118
122
|
"@types/bun": "^1.3.0",
|
|
119
|
-
"ajv": "8.20.0",
|
|
120
123
|
"dependency-cruiser": "18.4.0",
|
|
121
124
|
"effect": "4.0.0-rc.115",
|
|
122
125
|
"jscpd": "5.3.2",
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { Console, Effect, FileSystem, Path, Schema } from "effect";
|
|
3
|
+
import { checkoutFiles, commitOf, git, pathsAt, rangeEnds, refArgs } from "../core/git.ts";
|
|
4
|
+
import { runMain } from "../core/main.ts";
|
|
5
|
+
import { knipReport, scanTree } from "./knip.ts";
|
|
6
|
+
|
|
7
|
+
export type UnusedSymbol = {
|
|
8
|
+
readonly file: string;
|
|
9
|
+
readonly kind: "export" | "type";
|
|
10
|
+
readonly name: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type Baseline = readonly UnusedSymbol[];
|
|
14
|
+
|
|
15
|
+
export type Held = {
|
|
16
|
+
readonly reported: readonly UnusedSymbol[];
|
|
17
|
+
readonly base: Baseline;
|
|
18
|
+
readonly head: Baseline;
|
|
19
|
+
readonly unusedAtBase: readonly UnusedSymbol[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type Drift = {
|
|
23
|
+
readonly unlisted: readonly UnusedSymbol[];
|
|
24
|
+
readonly added: readonly UnusedSymbol[];
|
|
25
|
+
readonly stale: readonly UnusedSymbol[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const NAME = "exports";
|
|
29
|
+
export const BASELINE_FILE = "exports-baseline.json";
|
|
30
|
+
const INCLUDED = ["--include", "exports,types"];
|
|
31
|
+
const WRITE = "--write";
|
|
32
|
+
const USAGE = `usage: exports.ts <ref> | <base-ref> <head-ref> | ${WRITE}`;
|
|
33
|
+
|
|
34
|
+
class ExportsError extends Schema.TaggedError<ExportsError>()("ExportsError", {
|
|
35
|
+
message: Schema.String,
|
|
36
|
+
}) {}
|
|
37
|
+
|
|
38
|
+
const KnipSymbol = Schema.Struct({ name: Schema.String });
|
|
39
|
+
|
|
40
|
+
const decodeKnipReport = Schema.decodeUnknownEffect(
|
|
41
|
+
Schema.fromJsonString(
|
|
42
|
+
Schema.Struct({
|
|
43
|
+
issues: Schema.Array(
|
|
44
|
+
Schema.Struct({
|
|
45
|
+
file: Schema.String,
|
|
46
|
+
exports: Schema.optional(Schema.Array(KnipSymbol)),
|
|
47
|
+
types: Schema.optional(Schema.Array(KnipSymbol)),
|
|
48
|
+
}),
|
|
49
|
+
),
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const decodeBaseline = Schema.decodeUnknownEffect(
|
|
55
|
+
Schema.fromJsonString(
|
|
56
|
+
Schema.Array(
|
|
57
|
+
Schema.Struct({
|
|
58
|
+
file: Schema.String,
|
|
59
|
+
kind: Schema.Literals(["export", "type"]),
|
|
60
|
+
name: Schema.String,
|
|
61
|
+
}),
|
|
62
|
+
),
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
function byPosition(a: UnusedSymbol, b: UnusedSymbol): number {
|
|
67
|
+
return a.file.localeCompare(b.file) || a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const symbolsOf = Effect.fn("symbolsOf")(function* (stdout: string) {
|
|
71
|
+
const { issues } = yield* decodeKnipReport(stdout).pipe(
|
|
72
|
+
Effect.mapError((cause) => new ExportsError({ message: `knip's JSON report does not decode: ${cause.message}` })),
|
|
73
|
+
);
|
|
74
|
+
return issues
|
|
75
|
+
.flatMap(({ file, exports = [], types = [] }): readonly UnusedSymbol[] => [
|
|
76
|
+
...exports.map(({ name }): UnusedSymbol => ({ file, kind: "export", name })),
|
|
77
|
+
...types.map(({ name }): UnusedSymbol => ({ file, kind: "type", name })),
|
|
78
|
+
])
|
|
79
|
+
.toSorted(byPosition);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export const baselineOf = Effect.fn("baselineOf")(function* (text: string, where: string) {
|
|
83
|
+
return yield* decodeBaseline(text).pipe(
|
|
84
|
+
Effect.mapError((cause) => new ExportsError({ message: `${where} does not decode: ${cause.message}` })),
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const baselinePath = Effect.fn("baselinePath")(function* (root: string) {
|
|
89
|
+
return (yield* Path.Path).join(root, BASELINE_FILE);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const baselineInTree = Effect.fn("baselineInTree")(function* (root: string) {
|
|
93
|
+
const fs = yield* FileSystem.FileSystem;
|
|
94
|
+
const at = yield* baselinePath(root);
|
|
95
|
+
if (!(yield* fs.exists(at))) return [] satisfies Baseline;
|
|
96
|
+
return yield* fs.readFileString(at).pipe(Effect.flatMap((text) => baselineOf(text, BASELINE_FILE)));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const baselineAt = Effect.fn("baselineAt")(function* (treeish: string, root: string) {
|
|
100
|
+
const blob = (yield* git(["ls-tree", "--object-only", treeish, "--", BASELINE_FILE], root)).trim();
|
|
101
|
+
if (blob === "") return [] satisfies Baseline;
|
|
102
|
+
return yield* baselineOf(yield* git(["cat-file", "blob", blob], root), `${BASELINE_FILE} at ${treeish}`);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const keyOf = ({ file, kind, name }: UnusedSymbol): string => `${kind}\0${file}\0${name}`;
|
|
106
|
+
|
|
107
|
+
function outside(symbols: readonly UnusedSymbol[], others: readonly UnusedSymbol[]): readonly UnusedSymbol[] {
|
|
108
|
+
const known = new Set(others.map(keyOf));
|
|
109
|
+
return symbols.filter((symbol) => !known.has(keyOf(symbol))).toSorted(byPosition);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function driftOf({ reported, base, head, unusedAtBase }: Held): Drift {
|
|
113
|
+
return {
|
|
114
|
+
unlisted: outside(reported, head),
|
|
115
|
+
added: outside(head, [...base, ...unusedAtBase]),
|
|
116
|
+
stale: outside(head, reported),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function describe(symbol: UnusedSymbol): string {
|
|
121
|
+
return ` ${symbol.file}: ${symbol.name} (${symbol.kind})`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function report(head: Baseline, { unlisted, added, stale }: Drift): string {
|
|
125
|
+
const sections = [
|
|
126
|
+
{ symbols: unlisted, what: `unused export(s) not in ${BASELINE_FILE}:` },
|
|
127
|
+
{ symbols: added, what: `${BASELINE_FILE} export(s) the range adds that its base did not leave unused, remove the export instead:` },
|
|
128
|
+
{ symbols: stale, what: `${BASELINE_FILE} export(s) no longer reported, remove them:` },
|
|
129
|
+
].filter(({ symbols }) => symbols.length > 0);
|
|
130
|
+
if (sections.length > 0) {
|
|
131
|
+
return sections.flatMap(({ symbols, what }) => [`${NAME}: ${symbols.length} ${what}`, ...symbols.map(describe)]).join("\n");
|
|
132
|
+
}
|
|
133
|
+
return head.length === 0
|
|
134
|
+
? `${NAME}: no unused exports or types`
|
|
135
|
+
: `${NAME}: ${head.length} unused export(s) in ${BASELINE_FILE}, and no new ones`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const scan = scanTree(NAME, INCLUDED).pipe(Effect.mapError((cause) => new ExportsError({ message: cause.message })));
|
|
139
|
+
|
|
140
|
+
// Knip loads the configuration's imports, such as the kit's knip-base.json, from a node_modules the checkout lacks.
|
|
141
|
+
const unusedAt = Effect.fn("unusedAt")(
|
|
142
|
+
function* (rev: string, root: string) {
|
|
143
|
+
const files = yield* pathsAt(rev, [], root);
|
|
144
|
+
if (files.length === 0) return [] satisfies readonly UnusedSymbol[];
|
|
145
|
+
const fs = yield* FileSystem.FileSystem;
|
|
146
|
+
const path = yield* Path.Path;
|
|
147
|
+
const tree = yield* checkoutFiles(rev, files, yield* fs.makeTempDirectoryScoped({ prefix: "checks-exports-" }), root);
|
|
148
|
+
const modules = path.join(root, "node_modules");
|
|
149
|
+
if (yield* fs.exists(modules)) yield* fs.symlink(modules, path.join(tree, "node_modules"));
|
|
150
|
+
const reported = yield* knipReport(tree, INCLUDED).pipe(
|
|
151
|
+
Effect.mapError((cause) => new ExportsError({ message: `at ${rev}: ${cause.message}` })),
|
|
152
|
+
);
|
|
153
|
+
return reported.kind === "unconfigured" ? [] : yield* symbolsOf(reported.stdout);
|
|
154
|
+
},
|
|
155
|
+
Effect.scoped,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Knip scans the checked-out tree, and a pull request's merge checkout holds the base branch tip beside the range head.
|
|
159
|
+
const judgedBase = Effect.fn("judgedBase")(function* (first: string, second: string | undefined, root: string) {
|
|
160
|
+
const { base } = yield* rangeEnds(first, second, root);
|
|
161
|
+
if (second === undefined) return base;
|
|
162
|
+
const head = yield* commitOf(second, root);
|
|
163
|
+
const parents = (yield* git(["rev-parse", "HEAD^@"], root)).split("\n").filter((line) => line !== "");
|
|
164
|
+
const [other, ...rest] = parents.filter((parent) => parent !== head);
|
|
165
|
+
return parents.includes(head) && other !== undefined && rest.length === 0 ? other : base;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const check = Effect.fn("check")(function* (first: string, second: string | undefined) {
|
|
169
|
+
const { root, reported } = yield* scan;
|
|
170
|
+
if (reported.kind === "unconfigured") return false;
|
|
171
|
+
const baseRev = yield* judgedBase(first, second, root);
|
|
172
|
+
const base = yield* baselineAt(baseRev, root);
|
|
173
|
+
const head = yield* baselineInTree(root);
|
|
174
|
+
const unusedAtBase = outside(head, base).length === 0 ? [] : yield* unusedAt(baseRev, root);
|
|
175
|
+
const drift = driftOf({ reported: yield* symbolsOf(reported.stdout), base, head, unusedAtBase });
|
|
176
|
+
yield* Console.log(report(head, drift));
|
|
177
|
+
return drift.unlisted.length === 0 && drift.added.length === 0 && drift.stale.length === 0;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const seed = Effect.gen(function* () {
|
|
181
|
+
const { root, reported } = yield* scan;
|
|
182
|
+
if (reported.kind === "unconfigured") return false;
|
|
183
|
+
const symbols = yield* symbolsOf(reported.stdout);
|
|
184
|
+
yield* (yield* FileSystem.FileSystem).writeFileString(yield* baselinePath(root), `${JSON.stringify(symbols, null, 2)}\n`);
|
|
185
|
+
yield* Console.log(`${NAME}: wrote ${symbols.length} unused export(s) to ${BASELINE_FILE}`);
|
|
186
|
+
return true;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const gate = Effect.gen(function* () {
|
|
190
|
+
const args = process.argv.slice(2);
|
|
191
|
+
if (args.length === 1 && args[0] === WRITE) return yield* seed;
|
|
192
|
+
const { first, second } = yield* refArgs(args, USAGE);
|
|
193
|
+
return yield* check(first, second);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (import.meta.main) runMain(NAME, gate);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Console, Effect, FileSystem, Path, Schema } from "effect";
|
|
2
|
+
import { collect, git } from "../core/git.ts";
|
|
3
|
+
|
|
4
|
+
class KnipError extends Schema.TaggedError<KnipError>()("KnipError", {
|
|
5
|
+
message: Schema.String,
|
|
6
|
+
}) {}
|
|
7
|
+
|
|
8
|
+
const CONFIGS = [
|
|
9
|
+
".knip.json",
|
|
10
|
+
".knip.jsonc",
|
|
11
|
+
"knip.json",
|
|
12
|
+
"knip.jsonc",
|
|
13
|
+
"knip.js",
|
|
14
|
+
"knip.ts",
|
|
15
|
+
"knip.config.js",
|
|
16
|
+
"knip.config.ts",
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
const TYPESCRIPT = ["*.ts", "*.tsx"];
|
|
20
|
+
|
|
21
|
+
const hasKnipConfig = Effect.fn("hasKnipConfig")(function* (root: string) {
|
|
22
|
+
const fs = yield* FileSystem.FileSystem;
|
|
23
|
+
const path = yield* Path.Path;
|
|
24
|
+
for (const name of CONFIGS) {
|
|
25
|
+
if (yield* fs.exists(path.join(root, name))) return true;
|
|
26
|
+
}
|
|
27
|
+
const manifest: unknown = yield* fs.readFileString(path.join(root, "package.json")).pipe(
|
|
28
|
+
Effect.flatMap((text) =>
|
|
29
|
+
Effect.try({
|
|
30
|
+
try: () => JSON.parse(text) as unknown,
|
|
31
|
+
catch: () => new KnipError({ message: "package.json does not parse as JSON" }),
|
|
32
|
+
})
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
return typeof manifest === "object" && manifest !== null && "knip" in manifest;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const knipBinary = Effect.fn("knipBinary")(function* () {
|
|
39
|
+
const path = yield* Path.Path;
|
|
40
|
+
const main = yield* Effect.try({
|
|
41
|
+
try: () => new URL("../bin/knip.js", import.meta.resolve("knip")),
|
|
42
|
+
catch: () => new KnipError({ message: "cannot resolve knip from the installed kit" }),
|
|
43
|
+
});
|
|
44
|
+
return yield* path.fromFileUrl(main);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const knipReport = Effect.fn("knipReport")(function* (root: string, args: readonly string[]) {
|
|
48
|
+
if (!(yield* hasKnipConfig(root))) return { kind: "unconfigured" } as const;
|
|
49
|
+
const run = yield* collect(process.execPath, [yield* knipBinary(), ...args, "--reporter", "json"], root).pipe(
|
|
50
|
+
Effect.mapError((cause) => new KnipError({ message: `cannot run knip: ${cause.message}` })),
|
|
51
|
+
);
|
|
52
|
+
if (run.exitCode !== 0 && run.exitCode !== 1) {
|
|
53
|
+
return yield* new KnipError({ message: `knip exits ${run.exitCode}: ${(run.stdout + run.stderr).trim()}` });
|
|
54
|
+
}
|
|
55
|
+
return { kind: "reported", stdout: run.stdout } as const;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const scanTree = Effect.fn("scanTree")(function* (name: string, args: readonly string[]) {
|
|
59
|
+
const root = (yield* git(["rev-parse", "--show-toplevel"])).trim();
|
|
60
|
+
const tracked = (yield* git(["ls-files", "--", ...TYPESCRIPT], root))
|
|
61
|
+
.split("\n")
|
|
62
|
+
.map((line) => line.trim())
|
|
63
|
+
.filter((line) => line !== "");
|
|
64
|
+
if (tracked.length === 0) return yield* new KnipError({ message: "no tracked .ts or .tsx files to scan" });
|
|
65
|
+
const reported = yield* knipReport(root, args);
|
|
66
|
+
if (reported.kind === "unconfigured") {
|
|
67
|
+
yield* Console.log(`${name}: no knip configuration names entry files, so add one extending the kit's knip-base.json`);
|
|
68
|
+
}
|
|
69
|
+
return { root, tracked: tracked.length, reported };
|
|
70
|
+
});
|
package/src/complexity/unused.ts
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { Console, Effect,
|
|
3
|
-
import { collect, git } from "../core/git.ts";
|
|
2
|
+
import { Console, Effect, Schema } from "effect";
|
|
4
3
|
import { runMain } from "../core/main.ts";
|
|
4
|
+
import { scanTree } from "./knip.ts";
|
|
5
5
|
|
|
6
6
|
export type Scan = { readonly tracked: number; readonly files: readonly string[] };
|
|
7
7
|
|
|
8
8
|
const NAME = "unused";
|
|
9
|
-
const CONFIGS = [
|
|
10
|
-
".knip.json",
|
|
11
|
-
".knip.jsonc",
|
|
12
|
-
"knip.json",
|
|
13
|
-
"knip.jsonc",
|
|
14
|
-
"knip.js",
|
|
15
|
-
"knip.ts",
|
|
16
|
-
"knip.config.js",
|
|
17
|
-
"knip.config.ts",
|
|
18
|
-
] as const;
|
|
19
|
-
const TYPESCRIPT = ["*.ts", "*.tsx"];
|
|
20
9
|
const JUDGED = /\.tsx?$/;
|
|
21
10
|
|
|
22
11
|
class UnusedError extends Schema.TaggedError<UnusedError>()("UnusedError", {
|
|
@@ -46,51 +35,13 @@ export function report({ tracked, files }: Scan): string {
|
|
|
46
35
|
return [`${NAME}: ${files.length} unreferenced file(s):`, ...files.map((file) => ` ${file}`)].join("\n");
|
|
47
36
|
}
|
|
48
37
|
|
|
49
|
-
const hasConfig = Effect.fn("hasConfig")(function* (root: string) {
|
|
50
|
-
const fs = yield* FileSystem.FileSystem;
|
|
51
|
-
const path = yield* Path.Path;
|
|
52
|
-
for (const name of CONFIGS) {
|
|
53
|
-
if (yield* fs.exists(path.join(root, name))) return true;
|
|
54
|
-
}
|
|
55
|
-
const manifest: unknown = yield* fs.readFileString(path.join(root, "package.json")).pipe(
|
|
56
|
-
Effect.flatMap((text) =>
|
|
57
|
-
Effect.try({
|
|
58
|
-
try: () => JSON.parse(text) as unknown,
|
|
59
|
-
catch: () => new UnusedError({ message: "package.json does not parse as JSON" }),
|
|
60
|
-
})
|
|
61
|
-
),
|
|
62
|
-
);
|
|
63
|
-
return typeof manifest === "object" && manifest !== null && "knip" in manifest;
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const knip = Effect.fn("knip")(function* () {
|
|
67
|
-
const path = yield* Path.Path;
|
|
68
|
-
const main = yield* Effect.try({
|
|
69
|
-
try: () => new URL("../bin/knip.js", import.meta.resolve("knip")),
|
|
70
|
-
catch: () => new UnusedError({ message: "cannot resolve knip from the installed kit" }),
|
|
71
|
-
});
|
|
72
|
-
return yield* path.fromFileUrl(main);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
38
|
const unused = Effect.gen(function* () {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
.split("\n")
|
|
79
|
-
.map((line) => line.trim())
|
|
80
|
-
.filter((line) => line !== "");
|
|
81
|
-
if (tracked.length === 0) return yield* new UnusedError({ message: "no tracked .ts or .tsx files to scan" });
|
|
82
|
-
if (!(yield* hasConfig(root))) {
|
|
83
|
-
yield* Console.log(`${NAME}: no knip configuration names entry files, so add one extending the kit's knip-base.json`);
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
const run = yield* collect(process.execPath, [yield* knip(), "--files", "--reporter", "json"], root).pipe(
|
|
87
|
-
Effect.mapError((cause) => new UnusedError({ message: `cannot run knip: ${cause.message}` })),
|
|
39
|
+
const { tracked, reported } = yield* scanTree(NAME, ["--files"]).pipe(
|
|
40
|
+
Effect.mapError((cause) => new UnusedError({ message: cause.message })),
|
|
88
41
|
);
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
const files = yield* filesOf(run.stdout);
|
|
93
|
-
yield* Console.log(report({ tracked: tracked.length, files }));
|
|
42
|
+
if (reported.kind === "unconfigured") return false;
|
|
43
|
+
const files = yield* filesOf(reported.stdout);
|
|
44
|
+
yield* Console.log(report({ tracked, files }));
|
|
94
45
|
return files.length === 0;
|
|
95
46
|
});
|
|
96
47
|
|
package/src/core/gates.ts
CHANGED
|
@@ -12,7 +12,7 @@ export const EVERY_REPOSITORY = "every repository";
|
|
|
12
12
|
|
|
13
13
|
export const VECTORS = ["complexity", "quality", "testing", "docs", "delivery", "dependencies"] as const;
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
type Vector = (typeof VECTORS)[number];
|
|
16
16
|
|
|
17
17
|
export type KitGate = {
|
|
18
18
|
readonly bin: string;
|
|
@@ -41,5 +41,6 @@ export const KIT_GATES = [
|
|
|
41
41
|
{ bin: "checks-docs", vector: "docs", file: "docs.ts", reads: "range", appliesTo: EVERY_REPOSITORY },
|
|
42
42
|
{ bin: "checks-repetition", vector: "complexity", file: "repetition.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE },
|
|
43
43
|
{ bin: "checks-unused", vector: "complexity", file: "unused.ts", reads: "tree", appliesTo: TYPESCRIPT_SOURCE },
|
|
44
|
+
{ bin: "checks-exports", vector: "complexity", file: "exports.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE },
|
|
44
45
|
{ bin: "checks-quarantine-clock", vector: "testing", file: "quarantine-clock.ts", reads: "range", appliesTo: EVERY_REPOSITORY },
|
|
45
46
|
] as const satisfies readonly KitGate[];
|
package/src/core/swc.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parse } from "@swc/core";
|
|
2
2
|
import { Effect, Schema } from "effect";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
class TypeScriptParseError extends Schema.TaggedError<TypeScriptParseError>()("TypeScriptParseError", {
|
|
5
5
|
message: Schema.String,
|
|
6
6
|
}) {}
|
|
7
7
|
|
package/src/docs/doc-snapshot.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Effect, FileSystem, Path, Schema } from "effect";
|
|
|
2
2
|
import { anchoredTargets, anchorsOf, PACKAGE_MANIFEST, packagesOf, snapshotOf, type Unresolved } from "./doc-references.ts";
|
|
3
3
|
import { git, pathsAt } from "../core/git.ts";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
class ManifestUnreadable extends Schema.TaggedError<ManifestUnreadable>()("ManifestUnreadable", {
|
|
6
6
|
message: Schema.String,
|
|
7
7
|
}) {}
|
|
8
8
|
|
|
@@ -9,21 +9,21 @@ export type Title =
|
|
|
9
9
|
| { readonly type: "fixed"; readonly text: string }
|
|
10
10
|
| { readonly type: "open"; readonly placeholder: string; readonly rule: HeadingRule; readonly prefix?: string };
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
type Outlined = {
|
|
13
13
|
readonly shape: "outline";
|
|
14
14
|
readonly title: Title;
|
|
15
15
|
readonly lead: readonly string[];
|
|
16
16
|
readonly sections: readonly Slot[];
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
type Exact = {
|
|
20
20
|
readonly shape: "exact";
|
|
21
21
|
readonly text: string;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
export type Template = Outlined | Exact;
|
|
25
25
|
|
|
26
|
-
export const TEMPLATE_DIRECTORY = "templates";
|
|
26
|
+
export const TEMPLATE_DIRECTORY = "dist/templates";
|
|
27
27
|
|
|
28
28
|
export const ADR_STATUSES = ["Proposed", "Accepted", "Rejected", "Deprecated", "Superseded", "Retired"] as const;
|
|
29
29
|
|
package/src/quality/comments.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import { commentsIn, refusalsIn, syntaxOf, unreadable, type Syntax } from "./comment-matchers.ts";
|
|
3
3
|
|
|
4
|
-
export {
|
|
4
|
+
export { syntaxOf } from "./comment-matchers.ts";
|
|
5
5
|
|
|
6
6
|
export class UnreadableCode extends Schema.TaggedError<UnreadableCode>()("UnreadableCode", {
|
|
7
7
|
message: Schema.String,
|
|
8
8
|
}) {}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
function syntaxFor(path: string): Effect.Effect<Syntax, UnreadableCode> {
|
|
11
11
|
const syntax = syntaxOf(path);
|
|
12
12
|
return syntax ? Effect.succeed(syntax) : Effect.fail(new UnreadableCode({ message: unreadable(path) }));
|
|
13
13
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|