@geonosis/oxlint-plugin-biological-architecture 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -40
- package/corpus/.oxlintrc.json +33 -0
- package/corpus/layers/core/bad-reaches-up.ts +3 -0
- package/corpus/layers/core/thing.ts +1 -0
- package/corpus/layers/feature/bad-relative.ts +3 -0
- package/corpus/layers/feature/good-downward.ts +3 -0
- package/corpus/layers/feature/good-same-layer.ts +3 -0
- package/corpus/layers/feature/good-type-only.ts +3 -0
- package/corpus/layers/feature/sibling.ts +1 -0
- package/corpus/layers/shell/boot.ts +3 -0
- package/corpus/manifest.json +60 -0
- package/dist/{chunk-74VWOEPL.js → chunk-HXBZSOIU.js} +3 -1
- package/dist/discover-atoms.d.ts +39 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +164 -25
- package/dist/presets.d.ts +36 -0
- package/dist/presets.js +1 -1
- package/package.json +5 -8
- package/bin/geonosis-lint-parity.mjs +0 -4
- package/dist/chunk-2VSZAP5M.js +0 -240
- package/dist/chunk-BGX2XYEL.js +0 -153
- package/dist/chunk-FGUOGKA5.js +0 -228
- package/dist/chunk-FJ3J7B5J.js +0 -155
- package/dist/chunk-KZ5XEK6F.js +0 -83
- package/dist/chunk-RNY5NDH5.js +0 -155
- package/dist/chunk-UM6UIUH3.js +0 -250
- package/dist/parity-cli.js +0 -76
- package/dist/parity.js +0 -24
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# @geonosis/oxlint-plugin-biological-architecture
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
55 architecture rules as an oxlint JS plugin: which tier a file belongs to, what it may import,
|
|
4
4
|
where state is allowed to live, and the backend invariants that go with them. Nothing is hardcoded
|
|
5
|
-
to a repo — every list of brands, features, tables or paths is an option
|
|
6
|
-
|
|
5
|
+
to a repo — every list of brands, features, tables or paths is an option. A rule enabled without the
|
|
6
|
+
options it needs refuses the run rather than passing silently.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
@@ -63,6 +63,7 @@ everything below the organelle is inert. A cell never composes another cell.
|
|
|
63
63
|
| `backend-d1` | 2 | Cloudflare D1 facts — false on an engine with transactions |
|
|
64
64
|
| `backend-medusa` | 1 | the storefront route surface |
|
|
65
65
|
| `backend-sagaflow-cf` | 1 | durable runs on Cloudflare Workflows — false of an inline saga |
|
|
66
|
+
| `tooling` | 1 | `layer-walls` — a repo's OWN layers, declared as data and walled |
|
|
66
67
|
| `comment-ceiling` | 1 | the comment posture, opt-in (+ oxlint's `capitalized-comments`) |
|
|
67
68
|
|
|
68
69
|
A rule that is false on another stack gets its own preset rather than riding along in a generic
|
|
@@ -72,78 +73,76 @@ one: a preset carrying a repo fact is a repo fact wearing a preset's name.
|
|
|
72
73
|
|
|
73
74
|
A repo that already has its own copy of these rules does not delete it because the version numbers
|
|
74
75
|
match. It deletes it when the **findings** match: same tree, same rule ids, two configs, and a diff.
|
|
75
|
-
`geonosis
|
|
76
|
+
[`@geonosis/lint-parity`](https://www.npmjs.com/package/@geonosis/lint-parity) is that diff — a
|
|
77
|
+
separate package, because a parity tool welded to one rule plugin can only ever check that plugin.
|
|
78
|
+
What this package ships towards it is the **evidence**: `corpus/`, the fixture tree its own gates
|
|
79
|
+
run against, one deliberately broken file per rule, plus a `manifest.json` generated from the
|
|
80
|
+
plugin's own rules export naming exactly the rules that corpus speaks for.
|
|
76
81
|
|
|
77
82
|
```bash
|
|
78
|
-
|
|
83
|
+
pnpm add -D @geonosis/lint-parity
|
|
79
84
|
```
|
|
80
85
|
|
|
81
86
|
### Step 0 — reach, before parity
|
|
82
87
|
|
|
83
88
|
Parity between two configs that both do nothing is perfect parity. In one source repo 34 of 51
|
|
84
89
|
configured rules fired nowhere in any corpus it had: 34 rules a consumer would inherit as claims
|
|
85
|
-
nothing could check.
|
|
86
|
-
against, one deliberately broken file per rule — and answers the question directly:
|
|
90
|
+
nothing could check. Point the tool at the corpus this package installed:
|
|
87
91
|
|
|
88
92
|
```bash
|
|
89
|
-
geonosis-lint-parity
|
|
93
|
+
npx geonosis-lint-parity \
|
|
94
|
+
--corpus node_modules/@geonosis/oxlint-plugin-biological-architecture/corpus \
|
|
95
|
+
--a oxlintrc.fork.json --b oxlintrc.published.json --out proofs/reach
|
|
90
96
|
```
|
|
91
97
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
neither**. `--out` writes `CORPUS.md`.
|
|
98
|
+
It prints a fired-under-A / fired-under-B row per in-scope rule and **exits 1 when one fires under
|
|
99
|
+
neither**. A configured `eqeqeq`, `unicorn/*` or `typescript/*` is listed under **outside this
|
|
100
|
+
corpus** and gets no verdict: this tree is no evidence either way about somebody else's rule.
|
|
96
101
|
|
|
97
102
|
Every rule this package exports has at least one corpus file that fires it, and a test fails if that
|
|
98
103
|
stops being true — so a rule cannot be added without evidence it reaches anything.
|
|
99
104
|
|
|
100
105
|
### Then parity
|
|
101
106
|
|
|
102
|
-
It runs oxlint twice with `--format=unix` over the same paths, strips the working-directory prefix,
|
|
103
|
-
sorts, and prints the two counts, `only in A`, `only in B` and a per-rule tally. **It exits 1 when
|
|
104
|
-
`only in A` is non-empty** — a finding the old config caught and the new one does not. New findings
|
|
105
|
-
are what an upgrade is for; lost ones stop the adoption. `--out` writes `a.txt`, `b.txt`,
|
|
106
|
-
`only-in-a.txt`, `only-in-b.txt` and `SUMMARY.md` for the proof file.
|
|
107
|
-
|
|
108
107
|
**Fork vs published**, before deleting a vendored copy — two configs identical but for `jsPlugins`:
|
|
109
108
|
|
|
110
109
|
```bash
|
|
111
110
|
# oxlintrc.fork.json → "jsPlugins": ["./packages/eslint-plugin-biological-architecture"]
|
|
112
111
|
# oxlintrc.published.json → "jsPlugins": ["@geonosis/oxlint-plugin-biological-architecture"]
|
|
113
|
-
geonosis-lint-parity \
|
|
112
|
+
npx geonosis-lint-parity \
|
|
114
113
|
--a oxlintrc.fork.json --b oxlintrc.published.json \
|
|
115
114
|
--out proofs/adoption-parity \
|
|
116
115
|
-- apps packages
|
|
117
116
|
```
|
|
118
117
|
|
|
119
|
-
**
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
geonosis-lint-parity \
|
|
124
|
-
--a oxlintrc.0.1.0.json --b oxlintrc.0.1.1.json \
|
|
125
|
-
--out proofs/parity-0.1.0-vs-0.1.1 \
|
|
126
|
-
-- apps packages
|
|
127
|
-
```
|
|
118
|
+
**Write both configs inside the workspace being linted** — oxlint resolves a `jsPlugins` specifier
|
|
119
|
+
relative to the **config file's** directory, not the working directory. It exits 1 when a finding
|
|
120
|
+
config A reported is missing under config B; a reworded message is reported as **changed** and never
|
|
121
|
+
reaches the exit code. The tool's README has the rest.
|
|
128
122
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
`npx` might fetch. Every difference the run reports has to trace to a decision written down in
|
|
132
|
-
`RULES.md`; one that does not is a defect, not a difference.
|
|
123
|
+
Every difference a run reports has to trace to a decision written down in `RULES.md`; one that does
|
|
124
|
+
not is a defect, not a difference.
|
|
133
125
|
|
|
134
126
|
### What the two configs must say for the findings to match
|
|
135
127
|
|
|
136
|
-
A fork usually had its repo's vocabulary compiled in. Here it is configuration
|
|
137
|
-
|
|
128
|
+
A fork usually had its repo's vocabulary compiled in. Here it is configuration — and **a rule
|
|
129
|
+
enabled without the options it needs refuses the run**, naming itself and the missing option, rather
|
|
130
|
+
than quietly finding nothing. That is deliberate: a rule enabled at `"error"` with nothing to
|
|
131
|
+
compare against is indistinguishable from a clean tree, and one consumer shipped exactly that for
|
|
132
|
+
months. Carry these across, or turn the rule off:
|
|
138
133
|
|
|
139
|
-
| Rule | Option |
|
|
134
|
+
| Rule | Option | Enabled without it |
|
|
140
135
|
| --- | --- | --- |
|
|
141
|
-
| `no-brand-names` | `{ brands, allowedSubstrings }` |
|
|
136
|
+
| `no-brand-names` | `{ brands, allowedSubstrings }` | refuses the run |
|
|
142
137
|
| `organelle-dependency` | `{ sharedFeatures }` | only `shared` crosses a feature boundary |
|
|
143
|
-
| `documents-share-one-table` | `{ perKindTables }` |
|
|
144
|
-
| `document-sagas-are-generic` | `{ perKindPrefixes }` |
|
|
145
|
-
| `step-opens-its-own-cell` | `{ engine, within, wrapper }` |
|
|
146
|
-
| `no-raw-html-atoms` | the positional element→atom map |
|
|
138
|
+
| `documents-share-one-table` | `{ perKindTables }` | refuses the run |
|
|
139
|
+
| `document-sagas-are-generic` | `{ perKindPrefixes }` | refuses the run |
|
|
140
|
+
| `step-opens-its-own-cell` | `{ engine, within, wrapper }` | refuses the run |
|
|
141
|
+
| `no-raw-html-atoms` | the positional element→atom map | refuses the run |
|
|
142
|
+
| `layer-walls` | `{ layers: [{ name, paths, mayImport }], externals? }` | refuses the run |
|
|
143
|
+
|
|
144
|
+
An empty list counts as unconfigured. A rule set to `"off"`, or never named, is untouched — "does
|
|
145
|
+
nothing until configured" survives for a rule nobody enabled, which is oxlint's job, not ours.
|
|
147
146
|
|
|
148
147
|
Two more facts a fork's config will trip on:
|
|
149
148
|
|
package/corpus/.oxlintrc.json
CHANGED
|
@@ -35,6 +35,39 @@
|
|
|
35
35
|
}
|
|
36
36
|
],
|
|
37
37
|
"biological-architecture/effect-hook-naming": "error",
|
|
38
|
+
"biological-architecture/layer-walls": [
|
|
39
|
+
"error",
|
|
40
|
+
{
|
|
41
|
+
"layers": [
|
|
42
|
+
{
|
|
43
|
+
"name": "core",
|
|
44
|
+
"paths": [
|
|
45
|
+
"(?:^|/)layers/core/"
|
|
46
|
+
],
|
|
47
|
+
"mayImport": []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "feature",
|
|
51
|
+
"paths": [
|
|
52
|
+
"(?:^|/)layers/feature/"
|
|
53
|
+
],
|
|
54
|
+
"mayImport": [
|
|
55
|
+
"core"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "shell",
|
|
60
|
+
"paths": [
|
|
61
|
+
"(?:^|/)layers/shell/"
|
|
62
|
+
],
|
|
63
|
+
"mayImport": [
|
|
64
|
+
"core",
|
|
65
|
+
"feature"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
],
|
|
38
71
|
"biological-architecture/max-comment-density": "error",
|
|
39
72
|
"biological-architecture/molecule-atoms-only": "error",
|
|
40
73
|
"biological-architecture/molecule-must-compose": "error",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const thing = () => 'core'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const helper = () => 'feature'
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugin": "biological-architecture",
|
|
3
|
+
"rules": [
|
|
4
|
+
"biological-architecture/atom-no-deps",
|
|
5
|
+
"biological-architecture/cell-must-be-stateful",
|
|
6
|
+
"biological-architecture/cell-must-not-compose-cell",
|
|
7
|
+
"biological-architecture/cell-no-tissues",
|
|
8
|
+
"biological-architecture/cells-folder-index-is-barrel",
|
|
9
|
+
"biological-architecture/compound-must-be-stateless",
|
|
10
|
+
"biological-architecture/compound-no-stores",
|
|
11
|
+
"biological-architecture/constants-in-constants-file",
|
|
12
|
+
"biological-architecture/dialect-through-the-seam",
|
|
13
|
+
"biological-architecture/document-sagas-are-generic",
|
|
14
|
+
"biological-architecture/documents-share-one-table",
|
|
15
|
+
"biological-architecture/effect-hook-naming",
|
|
16
|
+
"biological-architecture/layer-walls",
|
|
17
|
+
"biological-architecture/max-comment-density",
|
|
18
|
+
"biological-architecture/molecule-atoms-only",
|
|
19
|
+
"biological-architecture/molecule-must-compose",
|
|
20
|
+
"biological-architecture/next-route-segment-is-thin-delegate",
|
|
21
|
+
"biological-architecture/no-brand-names",
|
|
22
|
+
"biological-architecture/no-card-shaped-div",
|
|
23
|
+
"biological-architecture/no-cross-feature-stores",
|
|
24
|
+
"biological-architecture/no-d1-transaction",
|
|
25
|
+
"biological-architecture/no-duplicate-jsx-patterns",
|
|
26
|
+
"biological-architecture/no-hook-in-component-disguise",
|
|
27
|
+
"biological-architecture/no-inert-hidden-jsx",
|
|
28
|
+
"biological-architecture/no-inline-data-in-jsx",
|
|
29
|
+
"biological-architecture/no-invalid-feature-folders",
|
|
30
|
+
"biological-architecture/no-logic-in-component-files",
|
|
31
|
+
"biological-architecture/no-orm-outside-db",
|
|
32
|
+
"biological-architecture/no-raw-html-atoms",
|
|
33
|
+
"biological-architecture/no-raw-sql-outside-allowed",
|
|
34
|
+
"biological-architecture/no-react-namespace",
|
|
35
|
+
"biological-architecture/no-renamed-html-props",
|
|
36
|
+
"biological-architecture/no-render-prop-reader",
|
|
37
|
+
"biological-architecture/no-trivial-wrapper-component",
|
|
38
|
+
"biological-architecture/no-ts-in-bio-folders",
|
|
39
|
+
"biological-architecture/no-type-definitions-in-components",
|
|
40
|
+
"biological-architecture/no-void-port",
|
|
41
|
+
"biological-architecture/organelle-dependency",
|
|
42
|
+
"biological-architecture/organelle-single-source",
|
|
43
|
+
"biological-architecture/queries-require-org-scope",
|
|
44
|
+
"biological-architecture/queue-loop-is-the-library",
|
|
45
|
+
"biological-architecture/ssot-no-inline-facts",
|
|
46
|
+
"biological-architecture/ssot-no-process-env",
|
|
47
|
+
"biological-architecture/step-opens-its-own-cell",
|
|
48
|
+
"biological-architecture/store-route-scopes-tenant-data",
|
|
49
|
+
"biological-architecture/tables-declare-their-plane",
|
|
50
|
+
"biological-architecture/tenant-tables-carry-org-id",
|
|
51
|
+
"biological-architecture/time-through-the-door",
|
|
52
|
+
"biological-architecture/tissue-must-compose",
|
|
53
|
+
"biological-architecture/tissue-no-data-props",
|
|
54
|
+
"biological-architecture/tissue-no-hooks",
|
|
55
|
+
"biological-architecture/tissue-no-organelles",
|
|
56
|
+
"biological-architecture/tissue-no-stores",
|
|
57
|
+
"biological-architecture/worker-handles-are-scoped",
|
|
58
|
+
"biological-architecture/zustand-v5-best-practices"
|
|
59
|
+
]
|
|
60
|
+
}
|
|
@@ -59,6 +59,7 @@ var TENANT_SCOPING = [
|
|
|
59
59
|
"tables-declare-their-plane",
|
|
60
60
|
"tenant-tables-carry-org-id"
|
|
61
61
|
];
|
|
62
|
+
var TOOLING = ["layer-walls"];
|
|
62
63
|
var RULE_PREFIX = "biological-architecture";
|
|
63
64
|
var asRules = (names) => Object.fromEntries(names.map((name) => [`${RULE_PREFIX}/${name}`, "error"]));
|
|
64
65
|
var COMMENT_CEILING = {
|
|
@@ -75,7 +76,8 @@ var PRESETS = {
|
|
|
75
76
|
"comment-ceiling": COMMENT_CEILING,
|
|
76
77
|
frontend: { rules: asRules(FRONTEND) },
|
|
77
78
|
recommended: { rules: asRules(FRONTEND) },
|
|
78
|
-
"tenant-scoping": { rules: asRules(TENANT_SCOPING) }
|
|
79
|
+
"tenant-scoping": { rules: asRules(TENANT_SCOPING) },
|
|
80
|
+
tooling: { rules: asRules(TOOLING) }
|
|
79
81
|
};
|
|
80
82
|
var rulesOfPreset = (preset) => Object.keys(PRESETS[preset].rules).filter((id) => id.startsWith(`${RULE_PREFIX}/`)).map((id) => id.slice(RULE_PREFIX.length + 1));
|
|
81
83
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
interface AtomMapping {
|
|
2
|
+
atomFile: string;
|
|
3
|
+
atomName: string;
|
|
4
|
+
nativeElement: string;
|
|
5
|
+
}
|
|
6
|
+
interface AtomRestriction {
|
|
7
|
+
atom: string;
|
|
8
|
+
element: string;
|
|
9
|
+
importPath: string;
|
|
10
|
+
}
|
|
11
|
+
interface RestrictedSyntaxEntry {
|
|
12
|
+
message: string;
|
|
13
|
+
selector: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build no-restricted-syntax entries that forbid raw native HTML usage
|
|
17
|
+
* for every native element wrapped by an atom. Apply these entries to
|
|
18
|
+
* molecules, compounds, cells, and tissues (NOT atoms themselves).
|
|
19
|
+
*/
|
|
20
|
+
declare function buildAtomRestrictions(atomsDir: string, importPathPrefix?: string): RestrictedSyntaxEntry[];
|
|
21
|
+
/**
|
|
22
|
+
* Build atom restrictions in the format expected by the no-raw-html-atoms rule.
|
|
23
|
+
* This replaces no-restricted-syntax for linters that don't support it (e.g. oxlint).
|
|
24
|
+
*/
|
|
25
|
+
declare function buildAtomRuleRestrictions(atomsDir: string, importPathPrefix?: string): AtomRestriction[];
|
|
26
|
+
/**
|
|
27
|
+
* Scan the atoms directory and build a mapping from native HTML element
|
|
28
|
+
* to the atom that wraps it. This is used to generate no-restricted-syntax
|
|
29
|
+
* entries that forbid raw native HTML usage in higher-level components.
|
|
30
|
+
*
|
|
31
|
+
* When multiple atoms wrap the same element (e.g. `input.tsx` and
|
|
32
|
+
* `inline-input.tsx` both wrap `<input>`), the atom whose filename
|
|
33
|
+
* exactly matches the element name is preferred as the canonical one.
|
|
34
|
+
* Variants are kept as alternatives in the error message.
|
|
35
|
+
*/
|
|
36
|
+
declare function scanAtomMappings(atomsDir: string): AtomMapping[];
|
|
37
|
+
declare function detectFirstNativeJsxElement(source: string): null | string;
|
|
38
|
+
|
|
39
|
+
export { buildAtomRestrictions, buildAtomRuleRestrictions, detectFirstNativeJsxElement, scanAtomMappings };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Preset } from './presets.js';
|
|
2
|
+
export { PRESETS, PresetName, rulesOfPreset } from './presets.js';
|
|
3
|
+
|
|
4
|
+
type Rule = {
|
|
5
|
+
create: (context: never) => Record<string, unknown>;
|
|
6
|
+
fixShape: string;
|
|
7
|
+
meta: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
messages: Record<string, string>;
|
|
12
|
+
schema: unknown[];
|
|
13
|
+
type: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
declare const rules: Record<string, Rule>;
|
|
17
|
+
type RuleName = string;
|
|
18
|
+
|
|
19
|
+
declare const plugin: {
|
|
20
|
+
configs: {
|
|
21
|
+
'backend-d1': {
|
|
22
|
+
rules: Record<string, "error">;
|
|
23
|
+
};
|
|
24
|
+
'backend-medusa': {
|
|
25
|
+
rules: Record<string, "error">;
|
|
26
|
+
};
|
|
27
|
+
'backend-sagaflow-cf': {
|
|
28
|
+
rules: Record<string, "error">;
|
|
29
|
+
};
|
|
30
|
+
'backend-workflows': {
|
|
31
|
+
rules: Record<string, "error">;
|
|
32
|
+
};
|
|
33
|
+
'comment-ceiling': Preset;
|
|
34
|
+
frontend: {
|
|
35
|
+
rules: Record<string, "error">;
|
|
36
|
+
};
|
|
37
|
+
recommended: {
|
|
38
|
+
rules: Record<string, "error">;
|
|
39
|
+
};
|
|
40
|
+
'tenant-scoping': {
|
|
41
|
+
rules: Record<string, "error">;
|
|
42
|
+
};
|
|
43
|
+
tooling: {
|
|
44
|
+
rules: Record<string, "error">;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
meta: {
|
|
48
|
+
name: string;
|
|
49
|
+
version: string;
|
|
50
|
+
};
|
|
51
|
+
rules: Record<string, Rule>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export { Preset, type Rule, type RuleName, plugin as default, rules };
|