@drkmttr/adhere 0.2.1 → 0.2.2
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/{adhere.config.ts → .adhere/config.ts} +1 -1
- package/.github/workflows/ci.yaml +9 -1
- package/.github/workflows/publish.yaml +37 -6
- package/.github/workflows/release.yaml +10 -1
- package/README.md +48 -13
- package/bin/adhere.js +43 -0
- package/bun.lock +1 -1
- package/package.json +12 -4
- package/scripts/npm-packages.ts +68 -0
- package/skills/adhere/SKILL.md +9 -7
- package/src/cli.ts +3 -3
- package/src/config.ts +6 -0
- package/src/init.ts +18 -1
- package/src/services/AdhereConfig.ts +1 -3
- package/test/audit.test.ts +15 -3
- package/tsconfig.json +1 -1
- /package/.adhere/{adhere → rules}/refusals-name-the-file.md +0 -0
|
@@ -16,4 +16,12 @@ jobs:
|
|
|
16
16
|
- run: bun install --frozen-lockfile
|
|
17
17
|
- run: bun run typecheck
|
|
18
18
|
- run: bun run test
|
|
19
|
-
- run: bun run build
|
|
19
|
+
- run: bun run build:npm
|
|
20
|
+
- name: Run the launcher against the linux-x64 package
|
|
21
|
+
run: |
|
|
22
|
+
# Outside the checkout, where the launcher has no source to fall back to.
|
|
23
|
+
launcher="$RUNNER_TEMP/launcher"
|
|
24
|
+
mkdir -p "$launcher/node_modules/@drkmttr"
|
|
25
|
+
cp -r bin "$launcher/bin"
|
|
26
|
+
ln -s "$PWD/dist/npm/linux-x64" "$launcher/node_modules/@drkmttr/adhere-linux-x64"
|
|
27
|
+
node "$launcher/bin/adhere.js" --version
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
name: Publish
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
workflow_call:
|
|
5
|
+
inputs:
|
|
6
|
+
tag_name:
|
|
7
|
+
description: "Release tag to publish, for example v0.3.0."
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
inputs:
|
|
12
|
+
tag_name:
|
|
13
|
+
description: "Release tag to publish, for example v0.3.0."
|
|
14
|
+
required: true
|
|
15
|
+
type: string
|
|
6
16
|
|
|
7
17
|
permissions:
|
|
8
18
|
id-token: write
|
|
@@ -23,15 +33,36 @@ jobs:
|
|
|
23
33
|
node-version: "24"
|
|
24
34
|
registry-url: "https://registry.npmjs.org"
|
|
25
35
|
- name: Verify release tag matches package version
|
|
36
|
+
env:
|
|
37
|
+
TAG_NAME: ${{ inputs.tag_name }}
|
|
26
38
|
run: |
|
|
27
|
-
version="${
|
|
39
|
+
version="${TAG_NAME#v}"
|
|
40
|
+
if [[ "$version" == "$TAG_NAME" || ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.+][0-9A-Za-z.-]+)?$ ]]; then
|
|
41
|
+
echo "Release tags must look like vX.Y.Z, optionally with a prerelease/build suffix." >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
28
44
|
package_version="$(node -p "require('./package.json').version")"
|
|
29
45
|
if [ "$version" != "$package_version" ]; then
|
|
30
|
-
echo "Release tag $
|
|
46
|
+
echo "Release tag $TAG_NAME does not match package.json version $package_version." >&2
|
|
31
47
|
exit 1
|
|
32
48
|
fi
|
|
33
49
|
- run: bun install --frozen-lockfile
|
|
34
50
|
- run: bun run typecheck
|
|
35
51
|
- run: bun run test
|
|
36
|
-
- run: bun run build
|
|
37
|
-
-
|
|
52
|
+
- run: bun run build:npm
|
|
53
|
+
- name: Publish the platform packages
|
|
54
|
+
run: |
|
|
55
|
+
for directory in dist/npm/*/; do
|
|
56
|
+
name="$(node -p "require('./${directory}package.json').name")"
|
|
57
|
+
version="$(node -p "require('./${directory}package.json').version")"
|
|
58
|
+
# A rerun after a partial publish skips the versions already on npm.
|
|
59
|
+
if npm view "$name@$version" version >/dev/null 2>&1; then
|
|
60
|
+
echo "$name@$version is already published."
|
|
61
|
+
else
|
|
62
|
+
npm publish "$directory" --access public --provenance
|
|
63
|
+
fi
|
|
64
|
+
done
|
|
65
|
+
- name: Publish @drkmttr/adhere with the platform packages as optional dependencies
|
|
66
|
+
run: |
|
|
67
|
+
bun scripts/npm-packages.ts pin
|
|
68
|
+
npm publish --access public --provenance
|
|
@@ -55,8 +55,17 @@ jobs:
|
|
|
55
55
|
- run: bun install --frozen-lockfile
|
|
56
56
|
- run: bun run typecheck
|
|
57
57
|
- run: bun run test
|
|
58
|
-
- run: bun run build
|
|
58
|
+
- run: bun run build:npm
|
|
59
59
|
- name: Create GitHub release
|
|
60
60
|
run: bun run release -- --ci "${{ steps.release.outputs.version }}"
|
|
61
61
|
env:
|
|
62
62
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
63
|
+
|
|
64
|
+
publish:
|
|
65
|
+
needs: release
|
|
66
|
+
uses: ./.github/workflows/publish.yaml
|
|
67
|
+
permissions:
|
|
68
|
+
contents: read
|
|
69
|
+
id-token: write
|
|
70
|
+
with:
|
|
71
|
+
tag_name: ${{ github.ref_name }}
|
package/README.md
CHANGED
|
@@ -14,8 +14,8 @@ Deterministic rules (substring matches, type checks) belong in a normal linter.
|
|
|
14
14
|
A config file in the working directory of the repo being audited, at one of
|
|
15
15
|
these paths (keep one):
|
|
16
16
|
|
|
17
|
+
- `.adhere/config.ts`, the default, next to the rules
|
|
17
18
|
- `adhere.config.ts`
|
|
18
|
-
- `.adhere/config.ts`
|
|
19
19
|
- `.adhere.config.ts`
|
|
20
20
|
|
|
21
21
|
```ts
|
|
@@ -46,17 +46,26 @@ package installed, `defineConfig({...})` does the same thing.
|
|
|
46
46
|
|
|
47
47
|
### Rules as Markdown files
|
|
48
48
|
|
|
49
|
-
A repo's own rules live in `.adhere/`, one `*.md` file per rule
|
|
50
|
-
|
|
51
|
-
`data/brand-ports`. When that
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
A repo's own rules live in `.adhere/`, one `*.md` file per rule, next to the
|
|
50
|
+
config and the cache (neither is read as a rule). The path without `.md` is the
|
|
51
|
+
rule id, so `.adhere/data/brand-ports.md` is `data/brand-ports`. When that
|
|
52
|
+
directory exists, it is read without any config.
|
|
53
|
+
|
|
54
|
+
`.adhere/` is the default rather than `docs/adhere/` because it keeps
|
|
55
|
+
everything adhere owns in one directory: the config and the cache are tool
|
|
56
|
+
state, not documentation, and would stay in `.adhere/` anyway. A nested
|
|
57
|
+
`.adhere/` also scopes its rules to the directory that contains it (below). The
|
|
58
|
+
cost is visibility: a dot directory is hidden from `ls`, and from `rg` without
|
|
59
|
+
`--hidden`. A repo that would rather keep its rules with the rest of its
|
|
60
|
+
documentation can point `rules` at `docs/adhere/`:
|
|
54
61
|
|
|
55
62
|
```ts
|
|
56
63
|
export default { presets: ["effect"], rules: "./docs/adhere" } satisfies Config;
|
|
57
64
|
```
|
|
58
65
|
|
|
59
|
-
|
|
66
|
+
Rules read through `rules` apply project-wide; nested `.adhere/` directories
|
|
67
|
+
are not read then. A config is otherwise needed only to name presets or set
|
|
68
|
+
thresholds.
|
|
60
69
|
|
|
61
70
|
Nested `.adhere/` directories are also discovered. A rule in
|
|
62
71
|
`packages/api/.adhere/data/brand-ports.md` has the same id,
|
|
@@ -100,7 +109,7 @@ A preset has the shape of a config without `presets`: `rules`, and optionally
|
|
|
100
109
|
[effect-solutions](https://github.com/kitlangton/effect-solutions) docs and the
|
|
101
110
|
[effect/platform](https://effect.website/docs/platform/introduction/) docs.
|
|
102
111
|
Name it in the config, or on the command line with `--preset effect`, in which
|
|
103
|
-
case
|
|
112
|
+
case the config file is optional.
|
|
104
113
|
|
|
105
114
|
### Precedence
|
|
106
115
|
|
|
@@ -109,6 +118,19 @@ order (a later preset wins), then the defaults `jev-latest` and `0.7`. A rule
|
|
|
109
118
|
in `rules` replaces a preset rule with the same id. A rule's own `threshold`
|
|
110
119
|
beats all of the above for that rule.
|
|
111
120
|
|
|
121
|
+
## Install
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
npm install --global @drkmttr/adhere # or: bun add --global @drkmttr/adhere
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`adhere` is the native executable (below), compiled for macOS and Linux on
|
|
128
|
+
arm64 and x64 and for Windows on x64. Each platform's is its own package,
|
|
129
|
+
`@drkmttr/adhere-<platform>-<arch>`, and an optional dependency of
|
|
130
|
+
`@drkmttr/adhere` that the install fetches only on that platform. The package's
|
|
131
|
+
`bin/adhere.js` runs it with Node, so neither Bun nor adhere's runtime
|
|
132
|
+
dependencies are needed.
|
|
133
|
+
|
|
112
134
|
## Run
|
|
113
135
|
|
|
114
136
|
From a checkout, with Bun:
|
|
@@ -119,6 +141,9 @@ bun link # puts `adhere` on PATH
|
|
|
119
141
|
adhere --preset effect
|
|
120
142
|
```
|
|
121
143
|
|
|
144
|
+
In a checkout no platform package is installed, so `bin/adhere.js` runs
|
|
145
|
+
`src/main.ts` with Bun instead.
|
|
146
|
+
|
|
122
147
|
`adhere` audits the working directory. When that directory contains `agents/`,
|
|
123
148
|
`apps/`, or `packages/`, only those trees are read. Exit code 1 when there is
|
|
124
149
|
at least one finding.
|
|
@@ -151,7 +176,9 @@ are treated as intentional shadowing rather than contradictions.
|
|
|
151
176
|
[`--compile`](https://bun.sh/docs/bundler/executables) with
|
|
152
177
|
`--asset ./presets`. It runs without Bun or `node_modules` on the target
|
|
153
178
|
machine and still loads the repo's `config.ts` and Markdown rules from disk.
|
|
154
|
-
|
|
179
|
+
`bun run build:npm` compiles it for every published platform instead, each
|
|
180
|
+
into its package under `dist/npm/`
|
|
181
|
+
([`scripts/npm-packages.ts`](./scripts/npm-packages.ts)).
|
|
155
182
|
|
|
156
183
|
## Release
|
|
157
184
|
|
|
@@ -168,10 +195,18 @@ derives `0.3.0` from `v0.3.0`, and runs `bun run release -- --ci 0.3.0`.
|
|
|
168
195
|
Release-it bumps `package.json`, commits `chore: release v0.3.0` back to
|
|
169
196
|
`main`, skips npm publish, and creates the GitHub Release for the existing tag.
|
|
170
197
|
|
|
171
|
-
Publishing stays in `.github/workflows/publish.yaml`:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
198
|
+
Publishing stays in `.github/workflows/publish.yaml`: after release-it
|
|
199
|
+
finishes, `.github/workflows/release.yaml` calls the reusable publish workflow
|
|
200
|
+
directly, avoiding a chained GitHub Release event created by `GITHUB_TOKEN`.
|
|
201
|
+
The publish workflow verifies that `package.json` matches the release tag,
|
|
202
|
+
builds the platform packages, and publishes each of them before
|
|
203
|
+
`@drkmttr/adhere`, which it first lists them in as `optionalDependencies` at
|
|
204
|
+
the same version. Every publish is `npm publish --access public --provenance`.
|
|
205
|
+
No npm token is used, so a new platform package has to be published once by
|
|
206
|
+
hand and given the trusted publisher on npmjs.com before the job can publish
|
|
207
|
+
it. The publish workflow can also be run manually for an already-created
|
|
208
|
+
release tag if a publish needs to be retried; it skips platform packages whose
|
|
209
|
+
version is already on npm.
|
|
175
210
|
|
|
176
211
|
## How a file is judged
|
|
177
212
|
|
package/bin/adhere.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `adhere` on PATH. Runs the executable compiled for this machine, which the
|
|
4
|
+
* install fetched as the optional dependency `@drkmttr/adhere-<platform>-<arch>`
|
|
5
|
+
* (see scripts/npm-packages.ts). A checkout has none installed, so there it
|
|
6
|
+
* runs src/main.ts with Bun instead.
|
|
7
|
+
*/
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { createRequire } from "node:module";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
14
|
+
const platformPackage = `@drkmttr/adhere-${platform}`;
|
|
15
|
+
const executable = process.platform === "win32" ? "adhere.exe" : "adhere";
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
|
|
18
|
+
const resolveExecutable = () => {
|
|
19
|
+
try {
|
|
20
|
+
return createRequire(import.meta.url).resolve(`${platformPackage}/bin/${executable}`);
|
|
21
|
+
} catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const run = (command, commandArgs) => {
|
|
27
|
+
const result = spawnSync(command, commandArgs, { stdio: "inherit" });
|
|
28
|
+
if (result.error) throw result.error;
|
|
29
|
+
process.exitCode = result.status ?? 1;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const prebuilt = resolveExecutable();
|
|
33
|
+
if (prebuilt) run(prebuilt, args);
|
|
34
|
+
else if (existsSync(new URL("../.git", import.meta.url)))
|
|
35
|
+
run("bun", [fileURLToPath(new URL("../src/main.ts", import.meta.url)), ...args]);
|
|
36
|
+
else {
|
|
37
|
+
console.error(
|
|
38
|
+
`adhere: ${platformPackage} is not installed, so there is no executable for ${platform}.` +
|
|
39
|
+
" It is an optional dependency of @drkmttr/adhere: reinstall without --omit=optional or --no-optional." +
|
|
40
|
+
" If it is still missing, adhere does not ship an executable for this platform.",
|
|
41
|
+
);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
}
|
package/bun.lock
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"": {
|
|
6
6
|
"name": "@drkmttr/adhere",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@effect/platform-bun": "4.0.0-rc.112",
|
|
9
8
|
"effect": "4.0.0-rc.112",
|
|
10
9
|
},
|
|
11
10
|
"devDependencies": {
|
|
11
|
+
"@effect/platform-bun": "4.0.0-rc.112",
|
|
12
12
|
"@types/bun": "^1.4.2",
|
|
13
13
|
"@types/node": "^24.13.6",
|
|
14
14
|
"release-it": "^21.1.0",
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drkmttr/adhere",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "Lints a repository against reference code in adhere
|
|
5
|
+
"description": "Lints a repository against reference code in .adhere/, judged by Jev.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/darkmatter/adhere.git"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
|
-
"adhere": "
|
|
11
|
+
"adhere": "bin/adhere.js"
|
|
12
12
|
},
|
|
13
13
|
"type": "module",
|
|
14
14
|
"imports": {
|
|
@@ -26,13 +26,14 @@
|
|
|
26
26
|
"test": "vp test",
|
|
27
27
|
"typecheck": "tsc --noEmit && bun run lint",
|
|
28
28
|
"build": "bun build --compile --minify --sourcemap src/main.ts --asset ./presets --outfile dist/adhere",
|
|
29
|
+
"build:npm": "bun scripts/npm-packages.ts build",
|
|
29
30
|
"release": "release-it"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@effect/platform-bun": "4.0.0-rc.112",
|
|
33
33
|
"effect": "4.0.0-rc.112"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
+
"@effect/platform-bun": "4.0.0-rc.112",
|
|
36
37
|
"@types/bun": "^1.4.2",
|
|
37
38
|
"@types/node": "^24.13.6",
|
|
38
39
|
"release-it": "^21.1.0",
|
|
@@ -58,5 +59,12 @@
|
|
|
58
59
|
"allowSameVersion": true,
|
|
59
60
|
"publish": false
|
|
60
61
|
}
|
|
62
|
+
},
|
|
63
|
+
"optionalDependencies": {
|
|
64
|
+
"@drkmttr/adhere-darwin-arm64": "0.2.2",
|
|
65
|
+
"@drkmttr/adhere-darwin-x64": "0.2.2",
|
|
66
|
+
"@drkmttr/adhere-linux-arm64": "0.2.2",
|
|
67
|
+
"@drkmttr/adhere-linux-x64": "0.2.2",
|
|
68
|
+
"@drkmttr/adhere-win32-x64": "0.2.2"
|
|
61
69
|
}
|
|
62
70
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The npm packages that carry the compiled executable, one per platform. Each
|
|
3
|
+
* is an optional dependency of `@drkmttr/adhere` limited by `os` and `cpu`, so
|
|
4
|
+
* an install fetches only its own machine's, and `bin/adhere.js` runs it.
|
|
5
|
+
*
|
|
6
|
+
* bun scripts/npm-packages.ts build compile every platform into dist/npm/
|
|
7
|
+
* bun scripts/npm-packages.ts pin list them in package.json at its version
|
|
8
|
+
*
|
|
9
|
+
* Only the publish job runs `pin`. The committed package.json leaves them out
|
|
10
|
+
* because a version is not on npm until that job has published it.
|
|
11
|
+
*/
|
|
12
|
+
import packageJson from "../package.json";
|
|
13
|
+
import { $ } from "bun";
|
|
14
|
+
import { rm } from "node:fs/promises";
|
|
15
|
+
|
|
16
|
+
process.chdir(new URL("../", import.meta.url).pathname);
|
|
17
|
+
|
|
18
|
+
/** `os` and `cpu` are Node's `process.platform` and `process.arch`, which the launcher names the package from. */
|
|
19
|
+
const platforms = [
|
|
20
|
+
{ os: "darwin", cpu: "arm64", target: "bun-darwin-arm64" },
|
|
21
|
+
{ os: "darwin", cpu: "x64", target: "bun-darwin-x64" },
|
|
22
|
+
{ os: "linux", cpu: "arm64", target: "bun-linux-arm64" },
|
|
23
|
+
{ os: "linux", cpu: "x64", target: "bun-linux-x64" },
|
|
24
|
+
{ os: "win32", cpu: "x64", target: "bun-windows-x64" },
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
type Platform = (typeof platforms)[number];
|
|
28
|
+
|
|
29
|
+
const packageName = ({ os, cpu }: Platform) => `${packageJson.name}-${os}-${cpu}`;
|
|
30
|
+
|
|
31
|
+
const writeJson = (path: string, value: unknown) =>
|
|
32
|
+
Bun.write(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
33
|
+
|
|
34
|
+
const build = async () => {
|
|
35
|
+
for (const platform of platforms) {
|
|
36
|
+
const directory = `dist/npm/${platform.os}-${platform.cpu}`;
|
|
37
|
+
const executable = platform.os === "win32" ? "adhere.exe" : "adhere";
|
|
38
|
+
await rm(directory, { recursive: true, force: true });
|
|
39
|
+
await $`bun build --compile --minify --sourcemap --target=${platform.target} src/main.ts --asset ./presets --outfile ${directory}/bin/${executable}`;
|
|
40
|
+
await writeJson(`${directory}/package.json`, {
|
|
41
|
+
name: packageName(platform),
|
|
42
|
+
version: packageJson.version,
|
|
43
|
+
description: `The adhere executable for ${platform.os}-${platform.cpu}. Install ${packageJson.name}, which depends on it.`,
|
|
44
|
+
repository: packageJson.repository,
|
|
45
|
+
os: [platform.os],
|
|
46
|
+
cpu: [platform.cpu],
|
|
47
|
+
files: [`bin/${executable}`],
|
|
48
|
+
preferUnplugged: true,
|
|
49
|
+
publishConfig: packageJson.publishConfig,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const pin = () =>
|
|
55
|
+
writeJson("package.json", {
|
|
56
|
+
...packageJson,
|
|
57
|
+
optionalDependencies: Object.fromEntries(
|
|
58
|
+
platforms.map((platform) => [packageName(platform), packageJson.version]),
|
|
59
|
+
),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const command = Bun.argv[2];
|
|
63
|
+
if (command === "build") await build();
|
|
64
|
+
else if (command === "pin") await pin();
|
|
65
|
+
else {
|
|
66
|
+
console.error("usage: bun scripts/npm-packages.ts build|pin");
|
|
67
|
+
process.exit(2);
|
|
68
|
+
}
|
package/skills/adhere/SKILL.md
CHANGED
|
@@ -44,9 +44,11 @@ Skip rules a preset already covers. Run `adhere --help` and read
|
|
|
44
44
|
|
|
45
45
|
## 3. Write each rule as a file
|
|
46
46
|
|
|
47
|
-
Default location `.adhere/<topic>/<slug>.md
|
|
48
|
-
|
|
49
|
-
config
|
|
47
|
+
Default location `.adhere/<topic>/<slug>.md`, next to the config and the
|
|
48
|
+
cache. Use `docs/adhere/<topic>/<slug>.md` with `rules: "./docs/adhere"` in the
|
|
49
|
+
config only when the user wants rules kept with the repo's docs; rules read
|
|
50
|
+
that way apply project-wide, and nested `.adhere/` directories are not read.
|
|
51
|
+
The path without `.md` is the rule id.
|
|
50
52
|
|
|
51
53
|
````md
|
|
52
54
|
---
|
|
@@ -73,10 +75,10 @@ Rules for the rule:
|
|
|
73
75
|
|
|
74
76
|
## 4. Configure
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
`.adhere.config.ts
|
|
78
|
-
passed on the command line. Use a type-only import so
|
|
79
|
-
under the native binary:
|
|
78
|
+
Put the config at `.adhere/config.ts`. `adhere.config.ts` and
|
|
79
|
+
`.adhere.config.ts` also load; keep one. A config is optional when `.adhere/`
|
|
80
|
+
exists or a preset is passed on the command line. Use a type-only import so
|
|
81
|
+
the file also loads under the native binary:
|
|
80
82
|
|
|
81
83
|
```ts
|
|
82
84
|
import type { Config } from "@drkmttr/adhere";
|
package/src/cli.ts
CHANGED
|
@@ -48,7 +48,7 @@ class UsageReported extends Schema.TaggedError<UsageReported>()(
|
|
|
48
48
|
|
|
49
49
|
const preset = Flag.choice("preset", presetNames).pipe(
|
|
50
50
|
Flag.optional,
|
|
51
|
-
Flag.withDescription("Add a built-in rule set. With a preset, adhere
|
|
51
|
+
Flag.withDescription("Add a built-in rule set. With a preset, .adhere/config.ts is optional."),
|
|
52
52
|
);
|
|
53
53
|
|
|
54
54
|
const threshold = Flag.float("threshold").pipe(
|
|
@@ -86,7 +86,7 @@ const audit = Command.make("adhere", { preset, threshold }, () =>
|
|
|
86
86
|
}),
|
|
87
87
|
).pipe(
|
|
88
88
|
Command.withDescription(
|
|
89
|
-
"Audit the working directory against reference code from adhere
|
|
89
|
+
"Audit the working directory against reference code from .adhere/config.ts or a preset, judged by Jev.",
|
|
90
90
|
),
|
|
91
91
|
Command.provide((input) =>
|
|
92
92
|
auditLayer({
|
|
@@ -98,7 +98,7 @@ const audit = Command.make("adhere", { preset, threshold }, () =>
|
|
|
98
98
|
|
|
99
99
|
const INIT_HELP = `Usage: adhere init [--force]
|
|
100
100
|
|
|
101
|
-
Scaffold adhere
|
|
101
|
+
Scaffold .adhere/config.ts and example Markdown rules.
|
|
102
102
|
|
|
103
103
|
Options:
|
|
104
104
|
--force Overwrite existing scaffold files
|
package/src/config.ts
CHANGED
|
@@ -32,6 +32,12 @@ export const AdhereConfig = Schema.Struct({
|
|
|
32
32
|
/** Where a repo keeps its rule files by default. The cache lives under it. */
|
|
33
33
|
export const ADHERE_DIRECTORY = ".adhere";
|
|
34
34
|
export const CACHE_DIRECTORY = `${ADHERE_DIRECTORY}/cache`;
|
|
35
|
+
/** Where a config may live, relative to the working directory. Exactly one may exist. */
|
|
36
|
+
export const CONFIG_FILES = [
|
|
37
|
+
`${ADHERE_DIRECTORY}/config.ts`,
|
|
38
|
+
"adhere.config.ts",
|
|
39
|
+
".adhere.config.ts",
|
|
40
|
+
] as const;
|
|
35
41
|
export interface AdhereConfig extends Schema.Schema.Type<typeof AdhereConfig> {}
|
|
36
42
|
|
|
37
43
|
/** The shape a config file default-exports. */
|
package/src/init.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, relative } from "node:path";
|
|
3
|
+
import { CONFIG_FILES } from "#config.ts";
|
|
3
4
|
import { Effect } from "effect";
|
|
4
5
|
|
|
5
6
|
export interface InitResult {
|
|
@@ -44,8 +45,10 @@ export const loadCustomerProfile = (customerId: CustomerId) =>
|
|
|
44
45
|
\`\`\`
|
|
45
46
|
`;
|
|
46
47
|
|
|
48
|
+
const CONFIG_PATH = CONFIG_FILES[0];
|
|
49
|
+
|
|
47
50
|
const files = [
|
|
48
|
-
[
|
|
51
|
+
[CONFIG_PATH, CONFIG],
|
|
49
52
|
[".adhere/style/prefer-small-files.md", SMALL_FILES],
|
|
50
53
|
[".adhere/style/name-domain-actions.md", NAME_EFFECTS],
|
|
51
54
|
] as const;
|
|
@@ -56,6 +59,15 @@ const exists = (path: string): Promise<boolean> =>
|
|
|
56
59
|
() => false,
|
|
57
60
|
);
|
|
58
61
|
|
|
62
|
+
/** A config at another accepted path. Scaffolding a second one would refuse every run. */
|
|
63
|
+
const otherConfig = (root: string) =>
|
|
64
|
+
Effect.promise(async () => {
|
|
65
|
+
for (const file of CONFIG_FILES) {
|
|
66
|
+
if (file !== CONFIG_PATH && (await exists(join(root, file)))) return file;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
});
|
|
70
|
+
|
|
59
71
|
const writeScaffoldFile = (root: string, path: string, contents: string, options: InitOptions) =>
|
|
60
72
|
Effect.tryPromise({
|
|
61
73
|
try: async () => {
|
|
@@ -79,7 +91,12 @@ export const initProject = (
|
|
|
79
91
|
Effect.gen(function* () {
|
|
80
92
|
const created: Array<string> = [];
|
|
81
93
|
const skipped: Array<string> = [];
|
|
94
|
+
const existing = yield* otherConfig(root);
|
|
82
95
|
for (const [path, contents] of files) {
|
|
96
|
+
if (path === CONFIG_PATH && existing !== undefined) {
|
|
97
|
+
skipped.push(existing);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
83
100
|
const status = yield* writeScaffoldFile(root, path, contents, options);
|
|
84
101
|
if (status === "created") {
|
|
85
102
|
created.push(relative(root, join(root, path)));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ADHERE_DIRECTORY,
|
|
3
3
|
type AdhereConfig as Decoded,
|
|
4
|
+
CONFIG_FILES,
|
|
4
5
|
ConfigUnavailable,
|
|
5
6
|
decodeConfig,
|
|
6
7
|
type Loaded,
|
|
@@ -20,9 +21,6 @@ import {
|
|
|
20
21
|
} from "#rules.ts";
|
|
21
22
|
import { Context, Effect, FileSystem, Layer, Path, Record } from "effect";
|
|
22
23
|
|
|
23
|
-
/** Where a config may live, relative to the working directory. Exactly one may exist. */
|
|
24
|
-
export const CONFIG_FILES = ["adhere.config.ts", ".adhere/config.ts", ".adhere.config.ts"] as const;
|
|
25
|
-
|
|
26
24
|
export class AdhereConfig extends Context.Service<
|
|
27
25
|
AdhereConfig,
|
|
28
26
|
ResolvedConfig
|
package/test/audit.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { Crypto, Effect, FileSystem, Layer, Path, Record } from "effect";
|
|
@@ -433,7 +433,7 @@ describe("init", () => {
|
|
|
433
433
|
it("scaffolds config and example rules without clobbering existing files", async () => {
|
|
434
434
|
const root = join(tmpdir(), `adhere-init-${Date.now()}`);
|
|
435
435
|
await Effect.runPromise(initProject(root));
|
|
436
|
-
const config = await readFile(join(root, "adhere
|
|
436
|
+
const config = await readFile(join(root, ".adhere", "config.ts"), "utf8");
|
|
437
437
|
const rule = await readFile(join(root, ".adhere", "style", "prefer-small-files.md"), "utf8");
|
|
438
438
|
|
|
439
439
|
expect(config).toContain("satisfies Config");
|
|
@@ -441,9 +441,21 @@ describe("init", () => {
|
|
|
441
441
|
|
|
442
442
|
const second = await Effect.runPromise(initProject(root));
|
|
443
443
|
expect(second.created).toEqual([]);
|
|
444
|
-
expect(second.skipped).toContain("adhere
|
|
444
|
+
expect(second.skipped).toContain(".adhere/config.ts");
|
|
445
445
|
expect(second.skipped).toContain(".adhere/style/prefer-small-files.md");
|
|
446
446
|
});
|
|
447
|
+
|
|
448
|
+
it("keeps a config at another accepted path instead of adding a second one", async () => {
|
|
449
|
+
const root = join(tmpdir(), `adhere-init-existing-${Date.now()}`);
|
|
450
|
+
await mkdir(root, { recursive: true });
|
|
451
|
+
await writeFile(join(root, "adhere.config.ts"), "export default {};\n", "utf8");
|
|
452
|
+
|
|
453
|
+
const result = await Effect.runPromise(initProject(root, { force: true }));
|
|
454
|
+
|
|
455
|
+
expect(result.skipped).toContain("adhere.config.ts");
|
|
456
|
+
expect(result.created).not.toContain(".adhere/config.ts");
|
|
457
|
+
await expect(access(join(root, ".adhere", "config.ts"))).rejects.toThrow();
|
|
458
|
+
});
|
|
447
459
|
});
|
|
448
460
|
|
|
449
461
|
describe("pipeline", () => {
|
package/tsconfig.json
CHANGED
|
File without changes
|