@avi2dg/checks 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 avi2d
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,363 @@
1
+ # checks
2
+
3
+ Deterministic checks shared across my TypeScript repos. One package,
4
+ `@avi2dg/checks`: the oxlint base config, the tsconfig fragment with the
5
+ Effect language-service block, the shared commitlint config, the shared
6
+ dependency-cruiser base, the test-layout check with its bunfig preset,
7
+ the commit-identity check with its workflow, the comment gate with its
8
+ workflow and backtest, and the Effect error-channel plugin compiled to
9
+ JavaScript.
10
+
11
+ Published as `@avi2dg/checks` on the public npm registry.
12
+
13
+ ## Consume it
14
+
15
+ From the consuming repo:
16
+
17
+ ```sh
18
+ bun add -d @avi2dg/checks oxlint@1.83.0 oxlint-tsgolint@7.0.2002 @effect/tsgo@0.45.0 typescript@7.0.2 dependency-cruiser@18.4.0 @swc/core@1.16.2
19
+ ```
20
+
21
+ `.oxlintrc.json`:
22
+
23
+ ```json
24
+ {
25
+ "extends": ["./node_modules/@avi2dg/checks/oxlintrc.json"],
26
+ "plugins": ["typescript", "oxc", "eslint", "import"]
27
+ }
28
+ ```
29
+
30
+ `.gitignore` keeps oxlint out of `node_modules/`:
31
+
32
+ ```
33
+ node_modules/
34
+ ```
35
+
36
+ `tsconfig.json` gains one line:
37
+
38
+ ```json
39
+ {
40
+ "extends": "@avi2dg/checks/tsconfig.effect.json"
41
+ }
42
+ ```
43
+
44
+ `bunfig.toml` is a copy of the shipped preset:
45
+
46
+ ```sh
47
+ cp node_modules/@avi2dg/checks/bunfig.toml bunfig.toml
48
+ ```
49
+
50
+ `package.json` gains three scripts:
51
+
52
+ ```json
53
+ "lint": "oxlint --type-aware && ./node_modules/@avi2dg/checks/scripts/lint-coverage.sh && bun ./node_modules/@avi2dg/checks/scripts/test-layout.ts && bun ./node_modules/@avi2dg/checks/scripts/commit-identity.ts HEAD",
54
+ "typecheck": "tsc --noEmit && effect-tsgo diagnostics --project tsconfig.json --format text --strict",
55
+ "test": "bun test --randomize"
56
+ ```
57
+
58
+ `lint-coverage.sh` fails when oxlint silently skips a tracked `.ts` or
59
+ `.tsx` file, for example through a stray `.gitignore` entry. It compares
60
+ `git ls-files` against oxlint's own file walk and names the missing files.
61
+
62
+ `test-layout.ts` decides the test layout described below.
63
+
64
+ `commit-identity.ts` refuses a commit with an author other than the
65
+ repository owner; see "Commit identity" below.
66
+
67
+ The registry version is pinned by the consumer's lockfile; bump
68
+ `@avi2dg/checks` to adopt a new release.
69
+
70
+ ## Test layout
71
+
72
+ `bun ./node_modules/@avi2dg/checks/scripts/test-layout.ts` fails unless the
73
+ repo holds this shape, and names the file and the path to move it to when
74
+ it does not:
75
+
76
+ - Every test file is `tests/**/*.test.ts` or `.tsx`. A `*.test.ts`, `*.spec.ts` or
77
+ `*_test.ts` under `src/`, `test/`, `__tests__/` or the repo root fails.
78
+ Tracked and untracked files that `git ls-files --exclude-standard`
79
+ reports are scanned, so `node_modules/` and every gitignored tree are
80
+ out of reach, and a local run agrees with CI before `git add`.
81
+ - `tests/lib/**` holds helpers and `tests/fixtures/**` holds data; neither
82
+ may hold a test file. Every other directory directly under `tests/` is a
83
+ test group and may nest as deep as it likes.
84
+ - Two levels. A test outside `tests/e2e/` runs in-process, so it may not
85
+ import `node:child_process`, `net`, `http`, `https`, `http2`, `tls` or
86
+ `dgram`, may not import `$`, `spawn`, `spawnSync`, `connect`, `serve` or
87
+ `listen` from `bun`, may not touch `Bun.$` or `Bun.spawn`, and may not
88
+ call `fetch`. A test inside `tests/e2e/` may do all of it. Helpers in
89
+ `tests/lib/**` answer to the same rule, since an in-process test reaches
90
+ them; `tests/fixtures/**` is data and is not parsed. Detection parses
91
+ with swc and reads import specifiers and identifier use, so a test that
92
+ only carries `"node:child_process"` as a string is not a violation.
93
+ - `scripts.test` is exactly `bun test --randomize` and `scripts.lint` runs
94
+ this check.
95
+ - `bunfig.toml` carries every `[test]` key of the shipped preset with the
96
+ same value. Other tables, and extra `[test]` keys, are the repo's own.
97
+
98
+ The in-process half is what a mutation run can mutate; `tests/e2e/**` is
99
+ excluded from a mutate scope by construction, because a subprocess kills
100
+ both the speed and the coverage signal a mutant needs.
101
+
102
+ The preset also skips `tests/quarantine/**` on a default run. A test that
103
+ turns flaky moves there, so the suite stays trustworthy, and the flake is
104
+ still run on demand:
105
+
106
+ ```sh
107
+ bun test --path-ignore-patterns='' tests/quarantine
108
+ ```
109
+
110
+ ## Dependency rules
111
+
112
+ `.dependency-cruiser.cjs` extends the shared base, which carries
113
+ `no-circular`, `no-orphans`, `not-to-dev-dep` (shipped source importing
114
+ a dev-only package, which a package listed in `peerDependencies` too is
115
+ not), `not-to-unresolvable` (nothing installed answers the
116
+ specifier), and `no-deep-imports` (a subpath the package's exports map
117
+ does not publish):
118
+
119
+ ```js
120
+ module.exports = {
121
+ extends: "./node_modules/@avi2dg/checks/dependency-cruiser.config.js",
122
+ forbidden: [
123
+ {
124
+ name: "ui-cannot-reach-server",
125
+ severity: "error",
126
+ from: { path: "^src/ui" },
127
+ to: { path: "^src/server" },
128
+ },
129
+ ],
130
+ };
131
+ ```
132
+
133
+ That last block is the layer-boundary recipe: append a named rule per
134
+ boundary you own. A rule that restates a base name overrides it field
135
+ by field, which is how an entry point stops being an orphan:
136
+ redeclare `no-orphans` with your entry added to its `pathNot`.
137
+ This repo's own `.dependency-cruiser.cjs` does that for the plugin
138
+ entry.
139
+
140
+ `package.json` gains the script:
141
+
142
+ ```json
143
+ "lint:deps": "depcruise --config .dependency-cruiser.cjs src"
144
+ ```
145
+
146
+ Enforcement runs in CI beside the other checks:
147
+
148
+ ```yaml
149
+ jobs:
150
+ lint:
151
+ steps:
152
+ - uses: actions/checkout@v5
153
+ - uses: oven-sh/setup-bun@v2
154
+ - run: bun install --frozen-lockfile
155
+ - run: bun run lint:deps
156
+ ```
157
+
158
+ ## Commit lint
159
+
160
+ Commits follow `@commitlint/config-conventional` plus the house
161
+ prefixes listed in `commitlint.config.js`, shared from
162
+ `@avi2dg/checks/commitlint.config.js`. It arrives with the registry
163
+ dependency above, since `@commitlint/cli` and
164
+ `@commitlint/config-conventional` are dependencies, not peers.
165
+ Enforcement runs in CI on pull requests, because `jj` never fires a
166
+ git hook. Add this workflow to the consuming repo:
167
+
168
+ ```yaml
169
+ on:
170
+ pull_request:
171
+ types: [opened, edited, synchronize, reopened]
172
+ jobs:
173
+ commitlint:
174
+ uses: avi2d/checks/.github/workflows/commitlint.yml@main
175
+ ```
176
+
177
+ It lints the pull request title and nothing else. The title is the
178
+ enforced subject because squash merges use the PR title as the main
179
+ commit subject; per-commit messages are not linted. GitHub appends
180
+ ` (#N)` to the squashed subject, so the workflow lints the title with
181
+ that suffix attached and the header length limit applies to the landed
182
+ subject, not the bare title.
183
+
184
+ It never sees a commit's author or committer fields, nor the
185
+ `Co-authored-by` trailer GitHub writes from a foreign author when it
186
+ squashes, so it cannot enforce who a commit belongs to. The
187
+ commit-identity check below is the enforcement.
188
+
189
+ ## Commit identity
190
+
191
+ `scripts/commit-identity.ts` walks every commit in a range and fails when
192
+ one carries an identity other than the repository owner's:
193
+
194
+ ```sh
195
+ bun ./node_modules/@avi2dg/checks/scripts/commit-identity.ts <base-ref> <head-ref>
196
+ bun ./node_modules/@avi2dg/checks/scripts/commit-identity.ts <ref>
197
+ ```
198
+
199
+ With one argument it checks that commit alone, which is the form the
200
+ `lint` script above runs on `HEAD`. It refuses a commit whose author or
201
+ committer is outside the allowlist, and one whose trailer block carries
202
+ a `Co-authored-by:` trailer as git parses it, and it names the offending
203
+ commit and reason. Other trailers and prose mentioning an address in the
204
+ body are left alone. `GitHub <noreply@github.com>` is allowed as
205
+ committer only, since that is who writes a squash merge.
206
+
207
+ The allowlist defaults to `avi2d <avi2dg@gmail.com>`. A repo with other
208
+ owners restates it in `package.json`:
209
+
210
+ ```json
211
+ "commitIdentity": {
212
+ "authors": [{ "name": "avi2d", "email": "avi2dg@gmail.com" }]
213
+ }
214
+ ```
215
+
216
+ Enforcement runs on pull requests, where the range from the base branch's
217
+ current tip to the head is visible:
218
+
219
+ ```yaml
220
+ on:
221
+ pull_request:
222
+ types: [opened, edited, synchronize, reopened]
223
+ jobs:
224
+ commit-identity:
225
+ uses: avi2d/checks/.github/workflows/commit-identity.yml@main
226
+ ```
227
+
228
+ The workflow fetches the consumer's full history and ranges from the
229
+ fetched base branch, not the event's recorded base sha, which GitHub
230
+ leaves stale once the base branch advances after the pull request opens.
231
+
232
+ ## Comment gate
233
+
234
+ `scripts/comment-gate.ts` runs the comment check over a diff and fails
235
+ when an added line carries a banned comment:
236
+
237
+ ```sh
238
+ bun ./node_modules/@avi2dg/checks/scripts/comment-gate.ts <base-ref> <head-ref>
239
+ bun ./node_modules/@avi2dg/checks/scripts/comment-gate.ts <ref>
240
+ ```
241
+
242
+ With two arguments it diffs the base against the head. With one it diffs
243
+ that commit against its parent, and exits 2 when that parent is not in
244
+ the clone rather than widening to the whole tree, so a shallow checkout
245
+ running the one-argument form needs `fetch-depth: 2`. Only added lines
246
+ are checked, so a violation in a file the diff never touches stays
247
+ silent, and a refusal counts when any line of the comment carrying it was
248
+ added. The check refuses a machine-read directive, a record or ticket
249
+ pointer, a doc block, and a file opening with a rationale block over
250
+ three lines, licence headers excepted; `scripts/comments.ts` holds the
251
+ scanner the gate and the backtest share.
252
+
253
+ Enforcement runs on pull requests, where the range from the base branch's
254
+ current tip to the head is visible:
255
+
256
+ ```yaml
257
+ on:
258
+ pull_request:
259
+ types: [opened, edited, synchronize, reopened]
260
+ jobs:
261
+ comment-gate:
262
+ uses: avi2d/checks/.github/workflows/comment-gate.yml@main
263
+ ```
264
+
265
+ ## Backtest
266
+
267
+ `scripts/backtest.ts` reports what the comment check would have refused
268
+ at each recent commit, so a repository can measure its own history:
269
+
270
+ ```sh
271
+ bun ./node_modules/@avi2dg/checks/scripts/backtest.ts [commit-count]
272
+ ```
273
+
274
+ It walks the last `commit-count` first-parent commits, 60 by default,
275
+ prints a row per commit that touches code and then the totals,
276
+ attributes only the refusals each commit introduced, and counts new
277
+ comment text as a share of added lines.
278
+ `generated/`, `vendor/`, `repos/`, `node_modules/` and `dist/` are out
279
+ of reach, so the figures are authored code.
280
+
281
+ ## Why it is shaped this way
282
+
283
+ - `plugins` does not inherit through oxlint `extends`. `rules`,
284
+ `categories` and `jsPlugins` do. That is why the consumer snippet
285
+ restates `plugins` and nothing else.
286
+ - `node_modules/` is excluded through the consumer's `.gitignore`, not
287
+ `ignorePatterns`: oxlint still walks the installed package when only
288
+ `ignorePatterns` names it.
289
+ - `files` in package.json is the published surface: `tests/`, `AGENTS.md`
290
+ and the `.ts` plugin source never reach an install. npm adds
291
+ `package.json`, `README` and `LICENSE` to the tarball whatever `files`
292
+ says. `bun pm pack` builds the same tarball the registry serves, which
293
+ is what the packed-tarball consumer e2e test installs.
294
+ - The plugin ships compiled as `dist/index.js`, built with
295
+ `bun build effect-channel/index.ts --outdir dist --target node --format esm`.
296
+ Node refuses to type-strip a `.ts` plugin under `node_modules`, so the
297
+ `.ts` source would fail to load from an installed package.
298
+ - `dist/` is committed. No `prepack` or `prepublishOnly` builds it, so a
299
+ publish ships whatever bundle the publishing worktree holds. Rebuild it
300
+ after pulling with `bun run build`; CI fails when the committed bundle
301
+ is stale.
302
+ - The base parses with swc because typescript 7 (tsgo) has no compiler
303
+ API for dependency-cruiser to use. Without `@swc/core` installed the
304
+ cruise silently skips every `.ts` file, so this repo's test asserts its
305
+ own TypeScript is cruised.
306
+ - `bunfig.toml` has no `extends` and no include: bun ignores an unknown
307
+ top-level key in silence, so a preset cannot be inherited and the
308
+ consumer's copy is compared key by key against the installed one
309
+ instead. `[test] pathIgnorePatterns` is a real bunfig key, and an empty
310
+ `--path-ignore-patterns` flag overrides the file's own list.
311
+ - The layout check ships as `.ts` and is invoked with `bun`, which needs
312
+ no build step and no `dist/` entry, unlike the oxlint plugin that node
313
+ loads.
314
+ - `bun` counts as a built-in module. Nothing installed resolves it except
315
+ `@types/bun`, which would otherwise make every runtime `bun` import look
316
+ like a dev-only dependency.
317
+ - The pull request merge commit GitHub builds is authored by `GitHub
318
+ <noreply@github.com>`, which commit-identity refuses as an author. A
319
+ consumer that runs the check inside `lint` checks out
320
+ `github.event.pull_request.head.sha` instead of the default merge ref,
321
+ as this repo's `ci.yml` does.
322
+ - `no-deep-imports` judges the import specifier, never the resolved file.
323
+ The base honours `exports` maps, so a subpath the map publishes resolves
324
+ and passes, one it omits fails to resolve and is reported, and a package
325
+ without an `exports` map publishes every file. A bare import always
326
+ passes whatever file its entry lives in. Setting your own
327
+ `options.enhancedResolveOptions` replaces the base's, so restate
328
+ `exportsFields` and `conditionNames` if you do.
329
+ - dependency-cruiser `extends` merges same-name `forbidden` rules with the
330
+ child's fields winning. That is the entry-point and layer recipe above.
331
+
332
+ ## Develop
333
+
334
+ ```sh
335
+ bun install
336
+ bun run build
337
+ bun run lint
338
+ bun run typecheck
339
+ bun run test
340
+ ```
341
+
342
+ Release by tagging a commit on `main`. The `release` workflow refuses
343
+ a tag off `main` and reruns the build, `dist/` check, lint, typecheck
344
+ and tests before it publishes:
345
+
346
+ ```sh
347
+ git tag v0.2.0 && git push origin v0.2.0
348
+ ```
349
+
350
+ The `release` workflow publishes the tagged version through npm
351
+ trusted publishing, so the tag and `package.json` must agree. No
352
+ token is stored anywhere: GitHub mints the publish credential for
353
+ each run.
354
+
355
+ npm attaches a trusted publisher only to a package that already
356
+ exists, so the first version goes out by hand: `npm publish` from the
357
+ tagged commit as `avi2dg`, then add the trusted publisher (repository
358
+ `avi2d/checks`, workflow `release.yml`) in the package's npm settings.
359
+ That first tag's `release` run fails on the already-published version;
360
+ every later tag publishes through the workflow.
361
+
362
+ `publishConfig.access` in package.json is what makes the scoped package
363
+ public.
package/bunfig.toml ADDED
@@ -0,0 +1,2 @@
1
+ [test]
2
+ pathIgnorePatterns = ["**/tests/quarantine/**"]
@@ -0,0 +1,22 @@
1
+ import configConventional from "@commitlint/config-conventional";
2
+
3
+ const houseTypes = [
4
+ "theme",
5
+ "herdr",
6
+ "renamer",
7
+ "shell",
8
+ "pi",
9
+ "home",
10
+ "audit",
11
+ "comments",
12
+ "unslop",
13
+ ];
14
+
15
+ const conventionalEnum = configConventional.rules["type-enum"];
16
+
17
+ export default {
18
+ extends: ["@commitlint/config-conventional"],
19
+ rules: {
20
+ "type-enum": [conventionalEnum[0], conventionalEnum[1], [...conventionalEnum[2], ...houseTypes]],
21
+ },
22
+ };
@@ -0,0 +1,72 @@
1
+ export default {
2
+ forbidden: [
3
+ {
4
+ name: "no-circular",
5
+ severity: "error",
6
+ comment: "A circular relationship never has a single entry point, so invert one side.",
7
+ from: {},
8
+ to: { circular: true },
9
+ },
10
+ {
11
+ name: "no-orphans",
12
+ severity: "error",
13
+ comment:
14
+ "Nothing reaches this module. Use it, remove it, or exempt the entry point by name in your own config.",
15
+ from: {
16
+ orphan: true,
17
+ pathNot: [
18
+ "(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$",
19
+ "[.]d[.]ts$",
20
+ "(^|/)tsconfig[.]json$",
21
+ "(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$",
22
+ "(^|/)[^/]*[.]config[.](?:js|cjs|mjs|ts|cts|mts)$",
23
+ "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$",
24
+ ],
25
+ },
26
+ to: {},
27
+ },
28
+ {
29
+ name: "not-to-dev-dep",
30
+ severity: "error",
31
+ comment:
32
+ "Shipped source cannot rely on a package that is absent in production. Move it to dependencies or peerDependencies, or keep the import in a test or config file.",
33
+ from: {
34
+ pathNot: [
35
+ "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$",
36
+ "(^|/)[^/]*[.]config[.](?:js|cjs|mjs|ts|cts|mts)$",
37
+ ],
38
+ },
39
+ to: {
40
+ dependencyTypes: ["npm-dev"],
41
+ dependencyTypesNot: ["type-only", "npm-peer"],
42
+ },
43
+ },
44
+ {
45
+ name: "not-to-unresolvable",
46
+ severity: "error",
47
+ comment: "Nothing installed answers to this specifier. Install the package or fix the path.",
48
+ from: {},
49
+ to: { couldNotResolve: true },
50
+ },
51
+ {
52
+ name: "no-deep-imports",
53
+ severity: "error",
54
+ comment:
55
+ "The specifier reaches past the package name into a subpath its exports map does not publish. Depend on a published entry instead.",
56
+ from: {},
57
+ to: {
58
+ couldNotResolve: true,
59
+ path: "^(@[^/]+/[^/]+|[^@./#][^/]*)/",
60
+ },
61
+ },
62
+ ],
63
+ options: {
64
+ parser: "swc",
65
+ builtInModules: { add: ["bun"] },
66
+ doNotFollow: { path: ["node_modules"] },
67
+ enhancedResolveOptions: {
68
+ exportsFields: ["exports"],
69
+ conditionNames: ["types", "import", "require", "node", "default"],
70
+ },
71
+ },
72
+ };
package/dist/index.js ADDED
@@ -0,0 +1,80 @@
1
+ // effect-channel/no-error-channel-escape.ts
2
+ var EFFECT_SOURCES = new Set(["effect", "effect/Effect"]);
3
+ var INSTEAD = {
4
+ drops: "it drops the failure and the success together, so no caller can tell one from the other. Handle the error by tag, or keep it as a value with Effect.result or Effect.exit",
5
+ swallows: "a Cause is the typed error plus defects plus interruption, so this swallows the bugs and the cancellations along with it. Catch the errors you named, with Effect.catchTag or Effect.catchTags",
6
+ blind: "the handler cannot see what it is recovering from, so every error in the channel collapses into one fallback. Name the errors with Effect.catchTag, or take the error and use it"
7
+ };
8
+ var REFUSED = new Map([
9
+ ["ignore", INSTEAD.drops],
10
+ ["ignoreCause", INSTEAD.drops],
11
+ ["catchCause", INSTEAD.swallows],
12
+ ["catchCauseIf", INSTEAD.swallows],
13
+ ["catchCauseFilter", INSTEAD.swallows]
14
+ ]);
15
+ var blindToTheError = (handler) => {
16
+ if (!handler)
17
+ return false;
18
+ if (handler.type !== "ArrowFunctionExpression" && handler.type !== "FunctionExpression")
19
+ return false;
20
+ return handler.params.every((param) => param.type === "Identifier" && /^_+$/.test(param.name));
21
+ };
22
+ var rule = {
23
+ meta: {
24
+ type: "problem",
25
+ docs: { description: "Disallow the combinators that erase Effect's error channel" }
26
+ },
27
+ create(context) {
28
+ const effect = new Set;
29
+ const named = (node) => {
30
+ if (!node || node.type !== "MemberExpression" || node.computed)
31
+ return null;
32
+ if (node.object.type !== "Identifier" || !effect.has(node.object.name))
33
+ return null;
34
+ return node.property.type === "Identifier" ? node.property.name : null;
35
+ };
36
+ const refuse = (node, combinator, instead) => {
37
+ context.report({ node, message: `Effect.${combinator} erases the error channel: ${instead}` });
38
+ };
39
+ return {
40
+ ImportDeclaration(node) {
41
+ if (!EFFECT_SOURCES.has(node.source.value))
42
+ return;
43
+ const module = node.source.value === "effect/Effect";
44
+ for (const specifier of node.specifiers) {
45
+ if (specifier.type === "ImportSpecifier") {
46
+ if (specifier.imported.type === "Identifier" && specifier.imported.name === "Effect")
47
+ effect.add(specifier.local.name);
48
+ } else if (module)
49
+ effect.add(specifier.local.name);
50
+ }
51
+ },
52
+ MemberExpression(node) {
53
+ const combinator = named(node);
54
+ if (combinator === null)
55
+ return;
56
+ const instead = REFUSED.get(combinator);
57
+ if (instead !== undefined)
58
+ refuse(node, combinator, instead);
59
+ },
60
+ CallExpression(node) {
61
+ if (named(node.callee) !== "catch")
62
+ return;
63
+ if (!blindToTheError(node.arguments[node.arguments.length - 1]))
64
+ return;
65
+ refuse(node, "catch", INSTEAD.blind);
66
+ }
67
+ };
68
+ }
69
+ };
70
+ var no_error_channel_escape_default = rule;
71
+
72
+ // effect-channel/index.ts
73
+ var plugin = {
74
+ meta: { name: "effect-channel" },
75
+ rules: { "no-error-channel-escape": no_error_channel_escape_default }
76
+ };
77
+ var effect_channel_default = plugin;
78
+ export {
79
+ effect_channel_default as default
80
+ };
package/oxlintrc.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "plugins": ["typescript", "oxc", "eslint", "import"],
3
+ "jsPlugins": ["./dist/index.js"],
4
+ "categories": { "correctness": "error", "suspicious": "error" },
5
+ "rules": {
6
+ "effect-channel/no-error-channel-escape": "error",
7
+ "typescript/no-explicit-any": "error",
8
+ "typescript/ban-ts-comment": ["error", { "ts-expect-error": true }],
9
+ "typescript/no-inferrable-types": "error",
10
+ "typescript/explicit-module-boundary-types": "error",
11
+ "typescript/switch-exhaustiveness-check": "error",
12
+ "typescript/prefer-readonly": "error",
13
+ "typescript/no-unnecessary-condition": ["error", { "allowConstantLoopConditions": true }],
14
+ "typescript/no-unnecessary-type-parameters": "error",
15
+ "typescript/use-unknown-in-catch-callback-variable": "error",
16
+ "eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", "ignoreRestSiblings": true }],
17
+ "typescript/no-unsafe-type-assertion": "off",
18
+ "typescript/no-non-null-assertion": "off",
19
+ "typescript/consistent-return": "off"
20
+ }
21
+ }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@avi2dg/checks",
3
+ "version": "0.1.0",
4
+ "description": "Deterministic checks shared across the captain's TypeScript repos",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/avi2d/checks.git"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "type": "module",
14
+ "files": [
15
+ "bunfig.toml",
16
+ "commitlint.config.js",
17
+ "dependency-cruiser.config.js",
18
+ "scripts/lint-coverage.sh",
19
+ "scripts/test-layout.ts",
20
+ "scripts/commit-identity.ts",
21
+ "scripts/comments.ts",
22
+ "scripts/comment-gate.ts",
23
+ "scripts/backtest.ts",
24
+ "oxlintrc.json",
25
+ "tsconfig.effect.json",
26
+ "dist/index.js"
27
+ ],
28
+ "exports": {
29
+ "./bunfig.toml": "./bunfig.toml",
30
+ "./commitlint.config.js": "./commitlint.config.js",
31
+ "./dependency-cruiser.config.js": "./dependency-cruiser.config.js",
32
+ "./scripts/lint-coverage.sh": "./scripts/lint-coverage.sh",
33
+ "./scripts/test-layout.ts": "./scripts/test-layout.ts",
34
+ "./scripts/commit-identity.ts": "./scripts/commit-identity.ts",
35
+ "./scripts/comments.ts": "./scripts/comments.ts",
36
+ "./scripts/comment-gate.ts": "./scripts/comment-gate.ts",
37
+ "./scripts/backtest.ts": "./scripts/backtest.ts",
38
+ "./oxlintrc.json": "./oxlintrc.json",
39
+ "./tsconfig.effect.json": "./tsconfig.effect.json",
40
+ "./dist/index.js": "./dist/index.js"
41
+ },
42
+ "scripts": {
43
+ "build": "bun build effect-channel/index.ts --outdir dist --target node --format esm",
44
+ "lint": "oxlint --type-aware && ./scripts/lint-coverage.sh && bun scripts/test-layout.ts && bun scripts/commit-identity.ts HEAD && bun scripts/comment-gate.ts HEAD && depcruise --config .dependency-cruiser.cjs effect-channel scripts tests commitlint.config.js dependency-cruiser.config.js .dependency-cruiser.cjs",
45
+ "typecheck": "tsc --noEmit && effect-tsgo diagnostics --project tsconfig.json --format text --strict",
46
+ "test": "bun test --randomize"
47
+ },
48
+ "peerDependencies": {
49
+ "@swc/core": "1.16.2",
50
+ "dependency-cruiser": "18.4.0",
51
+ "@effect/tsgo": "0.45.0",
52
+ "oxlint": "1.83.0",
53
+ "oxlint-tsgolint": "7.0.2002"
54
+ },
55
+ "devDependencies": {
56
+ "@effect/tsgo": "0.45.0",
57
+ "@oxlint/plugins": "1.83.0",
58
+ "@swc/core": "1.16.2",
59
+ "@types/bun": "^1.3.0",
60
+ "dependency-cruiser": "18.4.0",
61
+ "effect": "4.0.0-rc.115",
62
+ "oxlint": "1.83.0",
63
+ "oxlint-tsgolint": "7.0.2002",
64
+ "typescript": "7.0.2"
65
+ },
66
+ "dependencies": {
67
+ "@commitlint/cli": "21.2.3",
68
+ "@commitlint/config-conventional": "21.2.3"
69
+ }
70
+ }