@grimoire-rs/indexer 0.4.3 → 0.5.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 +190 -0
- package/NOTICE +30 -0
- package/README.md +76 -331
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +35 -4
- package/dist/cli/init.js.map +1 -1
- package/dist/config.d.ts +107 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +181 -36
- package/dist/config.js.map +1 -1
- package/dist/renderer/astro/components/CardLogo.d.ts +5 -0
- package/dist/renderer/astro/components/CardLogo.js +58 -0
- package/dist/renderer/astro/components/CardLogo.tsx +96 -0
- package/dist/renderer/astro/components/Catalog.d.ts +14 -1
- package/dist/renderer/astro/components/Catalog.js +467 -108
- package/dist/renderer/astro/components/Catalog.tsx +756 -349
- package/dist/renderer/astro/components/CommandBar.astro +66 -0
- package/dist/renderer/astro/components/CopyButton.d.ts +7 -0
- package/dist/renderer/astro/components/CopyButton.js +28 -0
- package/dist/renderer/astro/components/CopyButton.tsx +56 -0
- package/dist/renderer/astro/components/KindMark.d.ts +69 -0
- package/dist/renderer/astro/components/KindMark.js +66 -0
- package/dist/renderer/astro/components/KindMark.tsx +141 -0
- package/dist/renderer/astro/components/PackageCard.d.ts +18 -0
- package/dist/renderer/astro/components/PackageCard.js +50 -0
- package/dist/renderer/astro/components/PackageCard.tsx +273 -0
- package/dist/renderer/astro/components/PackageRow.d.ts +10 -0
- package/dist/renderer/astro/components/PackageRow.js +32 -0
- package/dist/renderer/astro/components/PackageRow.tsx +126 -0
- package/dist/renderer/astro/components/PickerMenu.astro +5 -14
- package/dist/renderer/astro/components/SiteFooter.astro +64 -0
- package/dist/renderer/astro/components/SiteHeader.astro +74 -0
- package/dist/renderer/astro/components/VersionMenu.astro +2 -2
- package/dist/renderer/astro/layouts/Base.astro +860 -206
- package/dist/renderer/astro/lib/base.d.ts +25 -0
- package/dist/renderer/astro/lib/base.js +23 -0
- package/dist/renderer/astro/lib/base.ts +27 -0
- package/dist/renderer/astro/lib/catalog.d.ts +24 -0
- package/dist/renderer/astro/lib/catalog.js +36 -0
- package/dist/renderer/astro/lib/catalog.ts +37 -0
- package/dist/renderer/astro/lib/commands.d.ts +58 -0
- package/dist/renderer/astro/lib/commands.js +86 -0
- package/dist/renderer/astro/lib/commands.ts +117 -0
- package/dist/renderer/astro/lib/keywordRail.d.ts +44 -0
- package/dist/renderer/astro/lib/keywordRail.js +99 -0
- package/dist/renderer/astro/lib/keywordRail.ts +110 -0
- package/dist/renderer/astro/pages/index.astro +40 -87
- package/dist/renderer/astro/pages/p/[...slug].astro +340 -195
- package/dist/renderer/astro/styles/tokens.css +40 -5
- package/dist/renderer/index.d.ts +58 -0
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +547 -5
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/types.d.ts +9 -0
- package/dist/renderer/types.d.ts.map +1 -1
- package/package.json +9 -4
- package/templates/README.md +6 -0
- package/templates/ci/github-ratings.yml +5 -0
- package/templates/gitignore +4 -1
- package/templates/theme/README.md +38 -0
- package/templates/tsconfig.json +47 -0
package/README.md
CHANGED
|
@@ -4,39 +4,8 @@ CLI + Astro integration for running your own [Grimoire](https://github.com/grimo
|
|
|
4
4
|
package index — a static site that lists the skills, rules, agents, mcp
|
|
5
5
|
servers, and bundles available in one or more OCI registries.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- `grim-indexer init` — scaffold a new index repo: the `index/**` content
|
|
10
|
-
tree, site config, a `package.json` that pins this package, and the CI
|
|
11
|
-
its forge runs.
|
|
12
|
-
- `grim-indexer dev` — serve the index locally, through the same renderer
|
|
13
|
-
`build` uses. The review loop for an entry or a branding change.
|
|
14
|
-
- `grim-indexer enrich` — refresh `enrich/**` from the registry: READMEs,
|
|
15
|
-
changelogs, logos, versions, tag lists, and the curated annotations grim
|
|
16
|
-
reports (`revision`, `authors`, `vendor`, `url`, `documentation`,
|
|
17
|
-
`compatibility` and the repository's `support` channels). The only step
|
|
18
|
-
that goes online, and the only one that needs `grim` on `PATH`.
|
|
19
|
-
`--seed` restores the sidecars from `<site>/enrich.json` first, so a
|
|
20
|
-
pipeline that commits nothing still only downloads what moved.
|
|
21
|
-
- `grim-indexer build` — render `index/**` into a static site.
|
|
22
|
-
- `grim-indexer validate` — CI gate for contribution PRs/MRs against an
|
|
23
|
-
index repo.
|
|
24
|
-
- `grim-indexer ci` — render the index repo's workflows from the `ci`
|
|
25
|
-
block of its `index.config.json`; `--check` verifies the committed ones
|
|
26
|
-
still match and exits 65 on drift.
|
|
27
|
-
|
|
28
|
-
A scaffolded index owns its CI: the workflow files are committed in that
|
|
29
|
-
repository and run `npm ci` against its own lockfile, so nothing is
|
|
30
|
-
fetched from here at run time and the version that builds an index is the
|
|
31
|
-
one that repo has locked. The generated `verify-ci` job re-renders and
|
|
32
|
-
diffs on every push, which is what keeps a hand-edit from silently
|
|
33
|
-
forking the pipeline.
|
|
34
|
-
|
|
35
|
-
An index stores nothing but pointers — a ref and who owns it. Everything a
|
|
36
|
-
reader looks at lives in the registry, so an index that never runs `enrich`
|
|
37
|
-
renders a catalogue of names with *No README available* on every page. The
|
|
38
|
-
scaffolded CI runs it before each build; set `"enrich": false` in the `ci`
|
|
39
|
-
block for a pointers-only site.
|
|
7
|
+
**Documentation: <https://grimoire-rs.github.io/indexer/>** — source in
|
|
8
|
+
[`docs/`](./docs/index.md).
|
|
40
9
|
|
|
41
10
|
## Install
|
|
42
11
|
|
|
@@ -44,16 +13,14 @@ block for a pointers-only site.
|
|
|
44
13
|
npm install --save-dev @grimoire-rs/indexer
|
|
45
14
|
```
|
|
46
15
|
|
|
47
|
-
##
|
|
16
|
+
## Quickstart
|
|
48
17
|
|
|
49
18
|
```sh
|
|
50
19
|
npx @grimoire-rs/indexer init # scaffold; writes package.json + lockfile
|
|
51
20
|
```
|
|
52
21
|
|
|
53
22
|
Or start from [`grimoire-rs/index-template`](https://github.com/grimoire-rs/index-template):
|
|
54
|
-
"Use this template", clone, then `npm install && npm run setup
|
|
55
|
-
`init` run in place, so it reads your `origin` remote and already knows the
|
|
56
|
-
forge and the Pages URL.
|
|
23
|
+
"Use this template", clone, then `npm install && npm run setup`.
|
|
57
24
|
|
|
58
25
|
Everything after that runs through the scaffolded repo's own scripts, so it
|
|
59
26
|
uses the version that repo locked:
|
|
@@ -63,317 +30,91 @@ npm run dev # local preview
|
|
|
63
30
|
npm run build # index/** -> dist/
|
|
64
31
|
npm run enrich # needs `grim` on PATH
|
|
65
32
|
npm run validate # the contribution gate
|
|
66
|
-
npm run ci
|
|
67
|
-
npm run ci:check # fail on drift (what the verify-ci job runs)
|
|
33
|
+
npm run ci:check # fail on CI drift
|
|
68
34
|
```
|
|
69
35
|
|
|
70
|
-
|
|
36
|
+
Full walkthrough: [Quickstart](./docs/how-to/quickstart.md).
|
|
71
37
|
|
|
72
|
-
|
|
73
|
-
// astro.config.mjs
|
|
74
|
-
import { defineConfig } from "astro/config";
|
|
75
|
-
import grimoireIndexer from "@grimoire-rs/indexer/integration";
|
|
38
|
+
## Subcommands
|
|
76
39
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
80
|
-
```
|
|
40
|
+
`init` · `dev` · `enrich` · `build` · `validate` · `ci` · `ratings` — each one
|
|
41
|
+
and its flags in the [CLI reference](./docs/reference/cli.md).
|
|
81
42
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
the upvote count on the forge thread that owns that artifact, and `updated`,
|
|
87
|
-
when that artifact last moved. It is the read contract every client shares:
|
|
88
|
-
`grim`, this renderer, and the VS Code extension all read the same file, and
|
|
89
|
-
none of them writes it.
|
|
90
|
-
|
|
91
|
-
```json
|
|
92
|
-
{
|
|
93
|
-
"schema_version": 1,
|
|
94
|
-
"generated_at": "2026-08-18T09:30:00Z",
|
|
95
|
-
"providers": { "rating": "github", "updated": "indexer" },
|
|
96
|
-
"entries": {
|
|
97
|
-
"ghcr.io/acme/code-review": {
|
|
98
|
-
"rating": {
|
|
99
|
-
"up": 12,
|
|
100
|
-
"target": "DIC_kwDOAbc123",
|
|
101
|
-
"url": "https://github.com/acme/index/discussions/42"
|
|
102
|
-
},
|
|
103
|
-
"updated": { "at": "2026-07-01T10:00:00+00:00" }
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
```
|
|
43
|
+
An index stores nothing but pointers — a ref and who owns it. Everything a
|
|
44
|
+
reader looks at lives in the registry, so an index that never runs `enrich`
|
|
45
|
+
renders a catalogue of names. See
|
|
46
|
+
[Index vs. site](./docs/explanation/index-vs-site.md).
|
|
108
47
|
|
|
109
|
-
|
|
110
|
-
|---|---|---|
|
|
111
|
-
| `schema_version` | int | Monotonic. Currently `1`. |
|
|
112
|
-
| `generated_at` | string | RFC 3339, UTC. |
|
|
113
|
-
| `providers` | object | Which backend produced each signal, keyed by stat name. `providers.rating` is `"github"` or `"gitlab"`. |
|
|
114
|
-
| `entries` | object | Keyed by artifact ref, **exactly as that ref appears in `all.json`**. |
|
|
115
|
-
| `entries[ref]` | object | One key per signal. |
|
|
116
|
-
| `entries[ref].rating.up` | int | Upvotes, `0` included. A thread that exists but has no votes is published as `0`, so its `url` is there to vote at. |
|
|
117
|
-
| `entries[ref].rating.target` | string | The forge's own id for the thread. **Opaque.** |
|
|
118
|
-
| `entries[ref].rating.url` | string | Where a human goes to vote. **Opaque.** |
|
|
119
|
-
| `entries[ref].updated.at` | string | RFC 3339. When the artifact last moved. |
|
|
120
|
-
|
|
121
|
-
`target` and `url` are opaque: no client parses one and no client constructs
|
|
122
|
-
one. They differ per forge and may change shape without a `schema_version`
|
|
123
|
-
bump, which is exactly what "opaque" buys.
|
|
124
|
-
|
|
125
|
-
**`entries[ref]` is a bag of stats, not a record.** A ref may carry `updated`
|
|
126
|
-
and no `rating`, or the reverse. A further signal arrives as a sibling key
|
|
127
|
-
with a sibling entry in `providers`; that is additive and needs no version
|
|
128
|
-
bump.
|
|
129
|
-
|
|
130
|
-
Some fixtures under `test/ratings/fixtures/` carry a `downloads` key. It is
|
|
131
|
-
there as an *unknown* key — the thing a reader must carry forward without
|
|
132
|
-
understanding — and is **not a specification**. No producer writes it, its
|
|
133
|
-
shape is not fixed, and a client must not code against it.
|
|
134
|
-
|
|
135
|
-
### Absent is first-class
|
|
136
|
-
|
|
137
|
-
Five distinct levels of absence. None of them is an error, a warning above
|
|
138
|
-
`debug`, or a failed build:
|
|
139
|
-
|
|
140
|
-
| Absent | Means |
|
|
141
|
-
|---|---|
|
|
142
|
-
| The file (404) | This index publishes no stats |
|
|
143
|
-
| `entries` | Nothing is rated yet |
|
|
144
|
-
| A ref within `entries` | That artifact has no stats at all |
|
|
145
|
-
| `rating` on a ref that is present | No rating thread exists for it — not the same as a thread with `up: 0`. Any other stat on that ref is unaffected |
|
|
146
|
-
| `rating` on a rendered catalog entry | Unrated. No consumer may assume the field is there |
|
|
147
|
-
|
|
148
|
-
### Reading a document you do not fully understand
|
|
149
|
-
|
|
150
|
-
A client that understands version *N* accepts any document declaring `≤ N`,
|
|
151
|
-
ignoring fields it does not know. A document declaring `> N` may degrade to
|
|
152
|
-
"no rating", but must never be a parse error. So fields are added and never
|
|
153
|
-
repurposed, and `schema_version` rises only when an existing field changes
|
|
154
|
-
meaning.
|
|
155
|
-
|
|
156
|
-
`test/ratings/fixtures/` holds one document per rule above — the minimal valid
|
|
157
|
-
v1, unknown fields at two levels, an unrecognised `providers.rating` value, the
|
|
158
|
-
absence levels, and a document from the future. They are the reference
|
|
159
|
-
documents for every client's parser tests, in this repository and outside it.
|
|
160
|
-
|
|
161
|
-
### Producing it
|
|
162
|
-
|
|
163
|
-
`updated` needs no configuration and no forge. `enrich` already runs `describe`
|
|
164
|
-
per package, so it writes the date into the sidecar — the artifact's own
|
|
165
|
-
`created` (a commit date, so a re-publish of the same commit keeps the same
|
|
166
|
-
answer) or, for an artifact published outside a repository, the first build
|
|
167
|
-
that saw its current digest. `build` joins that onto the document it
|
|
168
|
-
publishes. An index that runs `enrich` gets it whether or not it wants
|
|
169
|
-
ratings.
|
|
170
|
-
|
|
171
|
-
`rating` is opt-in. Add a `ratings` block to `index.config.json` and re-render
|
|
172
|
-
CI (`npm run ci`). The block is optional; without it nothing is tallied.
|
|
173
|
-
|
|
174
|
-
```jsonc
|
|
175
|
-
"ratings": {
|
|
176
|
-
"provider": "github", // "github" | "gitlab"
|
|
177
|
-
"container": "Ratings", // GitHub: Discussions category. GitLab: work item type.
|
|
178
|
-
"createBudget": 400, // threads created per run; default 400
|
|
179
|
-
"lockThreads": false // default FALSE - a locked thread cannot be voted on
|
|
180
|
-
}
|
|
181
|
-
```
|
|
48
|
+
## Making it yours
|
|
182
49
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
`lockThreads` defaults to `false`, and did not always. A lock looks like the
|
|
190
|
-
low-moderation default — a rating signal without a comment forum to moderate —
|
|
191
|
-
but on GitLab it also stops the voting. The work-item UI draws the thumbs-up
|
|
192
|
-
control on a locked item and ignores the click, guarding on `discussionLocked`
|
|
193
|
-
without sending the mutation, while REST and GraphQL both accept a reaction on
|
|
194
|
-
that same item and read it back. So nothing warns you: the tally runs, the
|
|
195
|
-
threads look healthy, and every one of them is unvotable by the only means most
|
|
196
|
-
people have.
|
|
197
|
-
|
|
198
|
-
Setting it costs nothing in marker authority — R-1 reads the thread body and
|
|
199
|
-
never a comment, so a reply cannot forge a marker either way. Turn it on if you
|
|
200
|
-
would rather moderate nothing and have checked that your forge still lets a
|
|
201
|
-
human react; GitHub Discussions are untested here.
|
|
202
|
-
|
|
203
|
-
Re-rendering with the block present adds one job to the generated pipeline
|
|
204
|
-
(`ratings` on GitHub, `grim-indexer:ratings` on GitLab), an hourly schedule, and
|
|
205
|
-
a seed step in the deploy. The seed step is what keeps a failed tally from
|
|
206
|
-
emptying a published rating set: it reads the currently published `stats.json`
|
|
207
|
-
and carries it forward per stat key, and it fails the job rather than treating
|
|
208
|
-
an unreadable seed as an empty one.
|
|
209
|
-
|
|
210
|
-
### Turning it off
|
|
211
|
-
|
|
212
|
-
Two steps, and the first alone is not enough:
|
|
213
|
-
|
|
214
|
-
1. Remove the `ratings` block and re-render CI. That stops the tally.
|
|
215
|
-
2. **Delete the published `stats.json` from the deploy.** Until it is gone the
|
|
216
|
-
last tally keeps being served, frozen, forever.
|
|
217
|
-
|
|
218
|
-
After both, clients read a 404 and every artifact shows as unrated on its next
|
|
219
|
-
refresh — unless the index still runs `enrich`, in which case the next build
|
|
220
|
-
republishes a `stats.json` carrying `updated` and nothing else. The sidecar is
|
|
221
|
-
never committed — it is a build input the deploy publishes — so there is no
|
|
222
|
-
history to unwind either way.
|
|
223
|
-
|
|
224
|
-
## The enrichment checkpoint
|
|
225
|
-
|
|
226
|
-
`enrich` skips work by digest, and every one of those comparisons reads
|
|
227
|
-
`enrich/<namespace>/<name>/data.json` off disk. The scaffolded CI never has
|
|
228
|
-
that file — the sidecars live only in the deploy job's workspace and are
|
|
229
|
-
committed nowhere — so without help every deploy re-downloads every README,
|
|
230
|
-
changelog, logo and payload, and re-dates every artifact that carries no
|
|
231
|
-
`created` of its own.
|
|
232
|
-
|
|
233
|
-
So `build` publishes `enrich.json` beside `all.json`, and
|
|
234
|
-
`grim-indexer enrich --seed` reads it back from `<site>/enrich.json` before
|
|
235
|
-
refreshing. The live site is the checkpoint, the same arrangement the ratings
|
|
236
|
-
sidecar already uses. The generated CI passes `--seed`; re-render with
|
|
237
|
-
`npm run ci` to pick it up.
|
|
238
|
-
|
|
239
|
-
Unlike `stats.json`, **this is not a read contract.** Nothing outside this
|
|
240
|
-
package reads it, its shape may change without notice, and no client should
|
|
241
|
-
code against it.
|
|
242
|
-
|
|
243
|
-
Failure is never fatal: an unreachable, oversized, unparseable or
|
|
244
|
-
unrecognised checkpoint warns and seeds nothing, and the run does the full
|
|
245
|
-
download it would have done anyway. A checkpoint that disagrees with itself —
|
|
246
|
-
claiming a README it does not carry — has the digest that guards that file
|
|
247
|
-
dropped, so the next run fetches it rather than trusting a stale flag.
|
|
248
|
-
|
|
249
|
-
`describe` still runs once per package and is never skipped, so CI still
|
|
250
|
-
installs `grim` and still makes one round trip each. The checkpoint saves the
|
|
251
|
-
downloads, not the probe.
|
|
50
|
+
- **Branding, colour, shape** — `index.config.json` plus one CSS file:
|
|
51
|
+
[Brand the site](./docs/how-to/customize-branding.md).
|
|
52
|
+
- **Your own pages** — a setup guide, a publish guide, anything else, in
|
|
53
|
+
`theme/pages/`: [Add your own pages](./docs/how-to/add-pages.md).
|
|
54
|
+
- **Your own components** — replace the header, a card, a row:
|
|
55
|
+
[Theme overlay](./docs/reference/theme-overlay.md).
|
|
252
56
|
|
|
253
57
|
## Status
|
|
254
58
|
|
|
255
|
-
Pre-1.0. The end-to-end loop was proven against live GitHub repositories
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
hostile variants (author not the namespace owner, path outside the
|
|
260
|
-
pointer layout, registry host outside the committed allowlist,
|
|
261
|
-
unreachable OCI ref, unowned namespace).
|
|
262
|
-
|
|
263
|
-
Two things that trial settled, both worth knowing before you scaffold:
|
|
264
|
-
|
|
265
|
-
- **Require the check named `validate`.** It was `validate / validate`
|
|
266
|
-
while the scaffold emitted thin callers of reusable workflows; the
|
|
267
|
-
workflow is now committed in the index repo, so the context is just the
|
|
268
|
-
job key. Requiring a context that never reports blocks every PR forever
|
|
269
|
-
and looks exactly like the gate rejecting your contribution.
|
|
270
|
-
- **In the combined (`--with-skills`) layout the gate does not cover your
|
|
271
|
-
own CI's announce.** GitHub runs no workflows on a PR opened with
|
|
272
|
-
`secrets.GITHUB_TOKEN`, so that PR arrives ungated — review it by hand.
|
|
273
|
-
It also needs "Allow GitHub Actions to create and approve pull
|
|
274
|
-
requests" enabled, which is off by default and which also lets
|
|
275
|
-
workflows approve PRs.
|
|
276
|
-
|
|
277
|
-
Not yet proven live: the GitLab leg (hermetic unit tests only - no live
|
|
278
|
-
GitLab pipeline has run the rendered CI), and the cross-repository
|
|
279
|
-
announce, which needs a credential beyond the CI token.
|
|
280
|
-
|
|
281
|
-
Two things are frozen and safe to build on: the published URL layout
|
|
282
|
-
(`/p/<namespace>/<name>/` and `/all.json`) and the per-record `schema`
|
|
283
|
-
field. Everything else may still move — including `/enrich.json`, which is
|
|
284
|
-
this package's own checkpoint and not a read contract.
|
|
285
|
-
|
|
286
|
-
Theming is CSS custom properties, all named `--grim-<category>-<role>` and
|
|
287
|
-
all declared in one file — `src/renderer/astro/styles/tokens.css`, whose
|
|
288
|
-
contract comment is the only reference. There is no second table here to go
|
|
289
|
-
stale.
|
|
290
|
-
|
|
291
|
-
| Family | Tokens |
|
|
292
|
-
|---|---|
|
|
293
|
-
| Colour | `--grim-color-*` — surface, accent, package kind, state |
|
|
294
|
-
| Space | `--grim-space-1` … `-9`, a sparse scale whose steps grow apart |
|
|
295
|
-
| Type | `--grim-text-2xs` … `-2xl` |
|
|
296
|
-
| Radius | `--grim-radius-sm` … `-xl`, `-pill` |
|
|
297
|
-
| Border, motion, elevation | `--grim-border-width`, `--grim-duration-*`, `--grim-shadow-*` |
|
|
298
|
-
|
|
299
|
-
**Colour is the only family that differs per scheme**, so it is the only one
|
|
300
|
-
you override twice — under `:root` *and* `[data-theme="dark"]`. `:root` is
|
|
301
|
-
scheme-agnostic, so a `:root`-only colour override silently takes dark mode
|
|
302
|
-
with it. Everything else is a measurement: declare it once.
|
|
303
|
-
|
|
304
|
-
Everything the renderer ships sits in `@layer grimoire`, and your file is
|
|
305
|
-
emitted unlayered and last. Unlayered CSS beats a layered rule outright, at
|
|
306
|
-
any specificity, so an ordinary selector wins with no `!important` and no
|
|
307
|
-
knowledge of where Astro injected its bundle.
|
|
308
|
-
|
|
309
|
-
To target one element rather than retheme globally, use its `data-slot`:
|
|
310
|
-
|
|
311
|
-
```css
|
|
312
|
-
[data-slot="package-card"] { border-radius: 2px; }
|
|
313
|
-
```
|
|
59
|
+
Pre-1.0. The end-to-end loop was proven against live GitHub repositories on
|
|
60
|
+
2026-07-28. Two things are frozen and safe to build on: the published URL
|
|
61
|
+
layout (`/p/<namespace>/<name>/` and `/all.json`) and the per-record `schema`
|
|
62
|
+
field.
|
|
314
63
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
are internal and unversioned, so `@layer` will make a rule targeting one
|
|
321
|
-
win, but nothing promises the class is still there next release.
|
|
322
|
-
|
|
323
|
-
There is deliberately no component-override API (`--grim-card-radius` and
|
|
324
|
-
friends). With the layer and the slots, it would reach nothing the CSS above
|
|
325
|
-
cannot already reach, and it would freeze a per-slot prop contract that is
|
|
326
|
-
not worth promising this early.
|
|
327
|
-
|
|
328
|
-
> **If you wrote a `theme.css` against `0.4.0` or earlier**, it no longer
|
|
329
|
-
> applies — silently, without an error. The tokens were unnamespaced
|
|
330
|
-
> (`--accent`, `--bg`, `--fg`), which collides with any other stylesheet on
|
|
331
|
-
> the page using those names. They are now `--grim-color-accent`,
|
|
332
|
-
> `--grim-color-bg`, `--grim-color-fg` and so on: the same role names behind
|
|
333
|
-
> a `--grim-color-` prefix.
|
|
334
|
-
|
|
335
|
-
> **Use `0.1.4` or later.** `0.1.0` installs without an executable - npm
|
|
336
|
-
> silently stripped its `bin` entry at publish time. `0.1.1` and `0.1.2`
|
|
337
|
-
> scaffold CI that points at reusable workflows those tags do not contain,
|
|
338
|
-
> so the first push to a scaffolded index fails before any job runs.
|
|
339
|
-
>
|
|
340
|
-
> An index already scaffolded against a reusable-workflow version keeps
|
|
341
|
-
> working as long as its pinned `uses:`/`include:` refs stay on an existing
|
|
342
|
-
> tag - old tags are not deleted, so that resolution does not break on its
|
|
343
|
-
> own. It breaks the moment something bumps the pinned ref, because `main`
|
|
344
|
-
> no longer defines any reusable workflow or remote include for it to
|
|
345
|
-
> resolve to: a Renovate update of an `@grimoire-rs/indexer` action ref
|
|
346
|
-
> will now fail hard. Fix it before that happens by adding a `package.json`
|
|
347
|
-
> pinning this package (if the old scaffold has none) and running
|
|
348
|
-
> `npm run ci`, which re-renders the workflow files and nothing else.
|
|
349
|
-
> **Not `init --force`**: that rewrites the whole scaffold from its
|
|
350
|
-
> templates, discarding the added scripts in `package.json`, your
|
|
351
|
-
> `.gitignore` rules, your `README.md`, and any `uses:` pin Renovate has
|
|
352
|
-
> bumped since - `npm run ci` keeps all four. `publish.toml` and
|
|
353
|
-
> `index-policy.json` are never rewritten, by `--force` or anything else.
|
|
354
|
-
>
|
|
355
|
-
> Through `0.1.3`, `init --with-skills` wrote a `publish.toml` with no
|
|
356
|
-
> `[announce]` table, so `grim publish --announce` in a combined-layout
|
|
357
|
-
> repo proposed its packages into the **public** first-party index rather
|
|
358
|
-
> than the one beside them. If you scaffolded that layout on `0.1.3` or
|
|
359
|
-
> earlier, add an `[announce]` table naming your own repository before
|
|
360
|
-
> announcing.
|
|
64
|
+
What is not yet proven, and what is known to be awkward:
|
|
65
|
+
[Known limitations](./docs/ops/known-limitations.md). What moved in each
|
|
66
|
+
release — including the component paths and props the theme overlay's Unstable
|
|
67
|
+
tier does not freeze — is [`CHANGELOG.md`](./CHANGELOG.md), which ships inside
|
|
68
|
+
the npm package as well.
|
|
361
69
|
|
|
362
70
|
## Developing the renderer
|
|
363
71
|
|
|
72
|
+
The toolchain — `task`, `node`, `grim` — is pinned in `ocx.toml`. Once:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
ocx shell allow # per-prompt activation for this project, once
|
|
76
|
+
task install
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Node is deliberately not in `ocx.toml`'s default `[tools]` table. `engines`
|
|
80
|
+
claims node 22 *and* 24, so both are groups and every invocation names one:
|
|
81
|
+
`ocx run -g default,node22 -- task check` is the other half of what CI runs.
|
|
82
|
+
|
|
364
83
|
Changing how the site *looks* needs a way to see it that does not cost an
|
|
365
|
-
npm release. `
|
|
84
|
+
npm release. `task dev` serves the catalog with hot reload:
|
|
366
85
|
|
|
367
86
|
```sh
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
87
|
+
task dev # the bundled dev index
|
|
88
|
+
task dev -- --port 4400
|
|
89
|
+
task dev FIXTURE=/path/to/an/index # your own index, or a checkout
|
|
371
90
|
# of github.com/grimoire-rs/index
|
|
372
|
-
|
|
91
|
+
task dev -- --config ./variant.json # try an index.config.json without
|
|
373
92
|
# editing the index it renders
|
|
374
|
-
|
|
93
|
+
task dev -- --help
|
|
375
94
|
```
|
|
376
95
|
|
|
96
|
+
`task --list` has the rest; `task check` is the whole gate CI runs.
|
|
97
|
+
|
|
98
|
+
The docs site is a separate toolchain and deliberately not part of `check` —
|
|
99
|
+
`check` must stay runnable with no Python present. `task docs:serve` previews
|
|
100
|
+
it, `task docs:build` is the strict build the `pages` workflow gates PRs with.
|
|
101
|
+
|
|
102
|
+
### The dev index
|
|
103
|
+
|
|
104
|
+
`test/fixtures/dev/` is what `task dev` renders, and it holds **one artifact
|
|
105
|
+
per rendering state** — rated, zero-vote and unrated; deprecated with and
|
|
106
|
+
without a replacement; a logo present, absent, and declared-but-not-shipped;
|
|
107
|
+
every kind; enriched and pointer-only; a second namespace on a second forge.
|
|
108
|
+
Its `README.md` is the table of which artifact exists for which state.
|
|
109
|
+
|
|
110
|
+
It ships an `index/` tree, so `task dev` runs the real `compileIndex` — which
|
|
111
|
+
is the only way the three logo states differ, since publishing
|
|
112
|
+
`enrich/<ns>/<name>/logo.<ext>` is something only the compile path does.
|
|
113
|
+
|
|
114
|
+
Add a state by adding an artifact and a row in that table. It is not a test
|
|
115
|
+
fixture: `test/renderer/fixture/` is, and `test/renderer/build.test.ts`
|
|
116
|
+
counts its contents, so the two are kept apart on purpose.
|
|
117
|
+
|
|
377
118
|
Every part of the hero is config, so `--config` is how you review the site
|
|
378
119
|
with a piece switched off — `{"install": []}` drops the installer buttons,
|
|
379
120
|
`{"registry": null}` the add-this-index ones, `{"vscodeExtension": null}`
|
|
@@ -383,13 +124,14 @@ It renders through the same `inlineConfig` as `grim-indexer build`, so the
|
|
|
383
124
|
preview is the release output, not an approximation. Edits under
|
|
384
125
|
`src/renderer/astro/` (templates, components, the tokens in
|
|
385
126
|
`styles/tokens.css`) reload in place; changing the renderer's own
|
|
386
|
-
TypeScript needs a restart, because `
|
|
127
|
+
TypeScript needs a restart, because `task dev` builds `dist/` on start.
|
|
387
128
|
|
|
388
129
|
The scratch index root lives in the gitignored `.dev/`, rebuilt on every
|
|
389
130
|
run — the repo you point `--root` at is copied, never rendered in place.
|
|
390
131
|
|
|
391
|
-
`npm run dev:smoke` boots the server, asserts the landing and
|
|
392
|
-
render, and checks the staged directory is cleaned up on
|
|
132
|
+
`task smoke` (`npm run dev:smoke`) boots the server, asserts the landing and
|
|
133
|
+
detail pages render, and checks the staged directory is cleaned up on
|
|
134
|
+
shutdown. It is part of `task check`, so CI runs it too. That
|
|
393
135
|
check lives here rather than in the vitest suite because Astro's dev server
|
|
394
136
|
does not route correctly when nested inside vitest's own Vite; the build
|
|
395
137
|
path is covered by `test/renderer/build.test.ts`.
|
|
@@ -399,4 +141,7 @@ install the resulting tarball into a scratch index repo.
|
|
|
399
141
|
|
|
400
142
|
## License
|
|
401
143
|
|
|
402
|
-
Apache-2.0
|
|
144
|
+
Apache-2.0. See `NOTICE` for the third-party assets a built index carries:
|
|
145
|
+
the artifact-kind marks are Microsoft's codicons under CC BY 4.0 — the same
|
|
146
|
+
glyphs the VS Code extension uses, so one catalogue reads the same in both —
|
|
147
|
+
alongside Lucide (ISC) and Material Design Icons (Apache-2.0).
|
package/dist/cli/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAYA,OAAO,EAQL,KAAK,KAAK,EACV,KAAK,cAAc,EACpB,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAoE1D,YAAY,EAAE,KAAK,EAAE,CAAC;AAEtB,gGAAgG;AAChG,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,OAAO,EAAE,cAAc,CAAC;IACxB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,gDAAgD;AAChD,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,CAAC;AAE5F,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;IACrD,cAAc,EAAE,OAAO,CAAC;IACxB,kDAAkD;IAClD,SAAS,EAAE,OAAO,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAYA,OAAO,EAQL,KAAK,KAAK,EACV,KAAK,cAAc,EACpB,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAoE1D,YAAY,EAAE,KAAK,EAAE,CAAC;AAEtB,gGAAgG;AAChG,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,OAAO,EAAE,cAAc,CAAC;IACxB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,gDAAgD;AAChD,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,CAAC;AAE5F,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;IACrD,cAAc,EAAE,OAAO,CAAC;IACxB,kDAAkD;IAClD,SAAS,EAAE,OAAO,CAAC;CACpB;AAy5BD,wBAAsB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CA+G5F"}
|
package/dist/cli/init.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import * as prompts from "@clack/prompts";
|
|
10
10
|
import { FORGES, loadCiConfig, PUBLISH_TRIGGERS, renderCi, resolveCi, staleCi, } from "../ci.js";
|
|
11
|
-
import { CONFIG_FILE, validateSite } from "../config.js";
|
|
11
|
+
import { CONFIG_FILE, validateSite, validateUrlShape } from "../config.js";
|
|
12
12
|
import { fromTemplate } from "../templates.js";
|
|
13
13
|
import { CliError, EXIT } from "./exit.js";
|
|
14
14
|
const NAME_RE = /^[a-z0-9][a-z0-9._-]*$/;
|
|
@@ -137,11 +137,31 @@ function badName(value) {
|
|
|
137
137
|
}
|
|
138
138
|
return null;
|
|
139
139
|
}
|
|
140
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* What `SiteConfig.logo` accepts — borrowed from `config.ts` for the reason
|
|
142
|
+
* `badSiteUrl` gives above, and checked here so a typo fails now rather than
|
|
143
|
+
* at build time.
|
|
144
|
+
*
|
|
145
|
+
* The copy this replaces read `/^(\/|https?:\/\/)/i`, which accepted
|
|
146
|
+
* `//evil.test/x.svg`, `/\evil.test/x.svg`, a tab-smuggled `/\t/evil.test/x`
|
|
147
|
+
* and `https://good.test@evil.test/x` — every one of them off-origin. The
|
|
148
|
+
* owner authors `index.config.json`, so this is a typo guard rather than a
|
|
149
|
+
* trust boundary; the reason to share one guard is that three copies of the
|
|
150
|
+
* rule had stopped agreeing about what a site-root path is, and `config.ts`
|
|
151
|
+
* documents each shape it refuses.
|
|
152
|
+
*/
|
|
141
153
|
function badLogo(value) {
|
|
142
|
-
|
|
154
|
+
// Blank is "no logo": the answer is omitted from `index.config.json`
|
|
155
|
+
// entirely, so the shared guard never sees an empty string.
|
|
156
|
+
if (value === "")
|
|
157
|
+
return null;
|
|
158
|
+
try {
|
|
159
|
+
validateUrlShape(value, "logo", true);
|
|
143
160
|
return null;
|
|
144
|
-
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
return err.message;
|
|
164
|
+
}
|
|
145
165
|
}
|
|
146
166
|
/**
|
|
147
167
|
* The target dir's `origin` remote as an https URL — set when the repo was
|
|
@@ -710,11 +730,22 @@ function plan(answers, version, ci) {
|
|
|
710
730
|
const from = (rel, dest) => ({ path: dest, content: fromTemplate(rel, vars) });
|
|
711
731
|
const files = [
|
|
712
732
|
{ path: "index/.gitkeep", content: "" },
|
|
733
|
+
// The overlay has to be scaffolded or nobody finds the feature, and this
|
|
734
|
+
// is the only thing that says where a page goes. A README rather than the
|
|
735
|
+
// `.gitkeep` it replaces, because what someone needs before they overwrite
|
|
736
|
+
// a component — which parts of the overlay are promised — is not
|
|
737
|
+
// discoverable from a directory listing, and stating it only in the
|
|
738
|
+
// tsconfig comment put it where nobody reads it in time. Both files
|
|
739
|
+
// reserve `theme/` for git equally well; only one of them answers that.
|
|
740
|
+
from("theme/README.md", "theme/README.md"),
|
|
713
741
|
{ path: "index.config.json", content: siteConfig(answers) },
|
|
714
742
|
{ path: POLICY_FILE, content: indexPolicy(answers) },
|
|
715
743
|
from("gitignore", ".gitignore"),
|
|
716
744
|
from("gitattributes", ".gitattributes"),
|
|
717
745
|
from("package.json", "package.json"),
|
|
746
|
+
// Editor-only: it is what makes `@grim/*` resolve while a theme page is
|
|
747
|
+
// being written. The build never reads it.
|
|
748
|
+
from("tsconfig.json", "tsconfig.json"),
|
|
718
749
|
from("README.md", "README.md"),
|
|
719
750
|
];
|
|
720
751
|
// The same renderer `grim-indexer ci` uses, driven by the `ci` block that
|