@grimoire-rs/indexer 0.3.1 → 0.3.3

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/dist/config.d.ts CHANGED
@@ -66,6 +66,20 @@ export interface SiteConfig {
66
66
  * link alone.
67
67
  */
68
68
  footerNote?: string | null;
69
+ /**
70
+ * Extra footer links, rendered after the raw-data line as a plain row.
71
+ *
72
+ * Exists because `footerNote` is text, and the thing an index most often
73
+ * has to put in a footer is not a sentence but a link: a privacy notice, an
74
+ * imprint, a code of conduct. Whether any of those are legally required
75
+ * depends on where the index runner lives and what the index is for, so the
76
+ * renderer takes no position and ships none by default — it just stops the
77
+ * answer being "fork the layout".
78
+ */
79
+ footerLinks?: Array<{
80
+ label: string;
81
+ href: string;
82
+ }>;
69
83
  /**
70
84
  * "Built with ♥ using Grimoire" in the footer, linking to grimoire.rs.
71
85
  * `false` removes it — an index runner is not obliged to advertise the
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAUA,+DAA+D;AAC/D,eAAO,MAAM,WAAW,sBAAsB,CAAC;AAE/C,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,+EAA+E;AAC/E,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;AAEtD,eAAO,MAAM,cAAc,EAAE,kBAuC5B,CAAC;AAEF,2EAA2E;AAC3E,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AA+ED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAelE;AAED,wDAAwD;AACxD,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,kBAAkB,CAWpE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAUA,+DAA+D;AAC/D,eAAO,MAAM,WAAW,sBAAsB,CAAC;AAE/C,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,+EAA+E;AAC/E,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;AAEtD,eAAO,MAAM,cAAc,EAAE,kBAwC5B,CAAC;AAEF,2EAA2E;AAC3E,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAoGD;;;GAGG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAelE;AAED,wDAAwD;AACxD,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,kBAAkB,CAWpE"}
package/dist/config.js CHANGED
@@ -43,6 +43,7 @@ export const DEFAULT_CONFIG = {
43
43
  // old default: "pointers, not payloads", which explains the architecture
44
44
  // to someone who did not ask, and the em dash holding it on.
45
45
  footerNote: "index metadata is CC0 · packages live on OCI registries",
46
+ footerLinks: [],
46
47
  attribution: true,
47
48
  customCss: null,
48
49
  };
@@ -86,6 +87,27 @@ function validate(raw) {
86
87
  if (cfg.attribution !== undefined && typeof cfg.attribution !== "boolean") {
87
88
  fail("attribution must be a boolean");
88
89
  }
90
+ if (cfg.footerLinks !== undefined) {
91
+ if (!Array.isArray(cfg.footerLinks))
92
+ fail("footerLinks must be an array");
93
+ for (const [i, entry] of cfg.footerLinks.entries()) {
94
+ const at = `footerLinks[${i}]`;
95
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
96
+ fail(`${at} must be an object with label and href`);
97
+ }
98
+ const { label, href } = entry;
99
+ if (typeof label !== "string" || label.trim() === "") {
100
+ fail(`${at}.label must be a non-empty string`);
101
+ }
102
+ // Same rule the other URL keys get. A relative href would resolve
103
+ // against whichever page the footer happens to be on — `/p/<ns>/<pkg>/`
104
+ // is two levels deep — so it is refused rather than silently 404ing
105
+ // from half the site.
106
+ if (typeof href !== "string" || !/^https?:\/\//i.test(href)) {
107
+ fail(`${at}.href must be an http(s) URL`);
108
+ }
109
+ }
110
+ }
89
111
  // Lands in `src`. A site-root path is the normal case; an absolute URL is
90
112
  // allowed for a logo already hosted elsewhere. Anything else — a bare
91
113
  // relative path that would break on a subpath deployment, or a `javascript:`
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -120,12 +120,21 @@ function CopyButton({ command, variant = "default", name, }) {
120
120
  // hydrates in the browser, so importing the build-time payload here would
121
121
  // ship the whole catalog twice.
122
122
  export default function Catalog({ packages, vscodeExtension, }) {
123
- // Seeded from `?q=…` on the very first render, not from an effect: a
124
- // keyword chip on a package page links here, and filtering one render late
125
- // means painting the full catalog and then collapsing it. The server has
126
- // no `location`, so it renders the unfiltered list — which is what a
127
- // crawler and a `?q=`-less visitor should both get.
128
- const [query, setQuery] = useState(() => typeof location === "undefined" ? "" : (new URLSearchParams(location.search).get("q") ?? ""));
123
+ // Empty on the first render, ALWAYS `?q=…` is applied a beat later, in
124
+ // the layout effect below. This is not a style preference, it is the one
125
+ // rule this island has to obey.
126
+ //
127
+ // Preact does not diff props while hydrating; its own source says so, and
128
+ // only re-applies props whose value is a function. Text children *are*
129
+ // diffed. So when the first client render disagrees with the server's, you
130
+ // get cards whose text is right and whose every attribute belongs to
131
+ // whichever package the server put at that position: one package's logo
132
+ // over another's name, and a link that opens the wrong page. Nothing
133
+ // throws. This used to seed from `location` here, and did exactly that.
134
+ const [query, setQuery] = useState("");
135
+ // Whether the URL's query has been applied. Gates the reveal below, so the
136
+ // catalog is never unhidden while it still shows the unfiltered list.
137
+ const [seeded, setSeeded] = useState(false);
129
138
  const [kind, setKind] = useState(null);
130
139
  const [sort, setSort] = useState("name");
131
140
  // Deprecated packages are hidden until asked for: a retired package is
@@ -168,19 +177,29 @@ export default function Catalog({ packages, vscodeExtension, }) {
168
177
  behavior: matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth",
169
178
  });
170
179
  };
180
+ // Apply `?q=…`, now that hydration has matched the server's markup and
181
+ // Preact owns the tree. A layout effect rather than a plain one: the
182
+ // resulting render must land before the browser paints, or a `?q=` visitor
183
+ // sees the whole catalog flash past on the way to their results.
184
+ useLayoutEffect(() => {
185
+ setQuery(new URLSearchParams(location.search).get("q") ?? "");
186
+ setSeeded(true);
187
+ }, []);
171
188
  // Base.astro hides the catalog before first paint when the URL carries a
172
- // query. Reveal it once this render the filtered one has hit the DOM.
173
- // A layout effect, so the reveal lands in the same frame as the content.
189
+ // query. Reveal it only once the filtered render is in the DOMkeyed on
190
+ // `seeded`, so the unfiltered first render is never the one revealed.
174
191
  useLayoutEffect(() => {
175
- // Hydration attaches handlers but does not diff props against the server
176
- // markup, so the box the server rendered empty stays empty even though
177
- // this render filtered on `query`. Written straight to the DOM, which is
178
- // what the vnode already claims.
192
+ if (!seeded)
193
+ return;
194
+ // The input's `value` prop was skipped during hydration for the same
195
+ // reason every other prop was, and the render that applied the query is
196
+ // a normal diff — but write it anyway: this effect is also what runs on
197
+ // a `?q=`-less load, where no second render is queued at all.
179
198
  const input = searchRef.current;
180
199
  if (input && input.value !== query)
181
200
  input.value = query;
182
201
  delete document.documentElement.dataset.query;
183
- }, []);
202
+ }, [seeded]);
184
203
  // `/` jumps to the search box, the convention every package registry
185
204
  // shares. Bound on the document so it works wherever the reader is.
186
205
  useEffect(() => {
@@ -187,14 +187,21 @@ export default function Catalog({
187
187
  packages: CatalogPackage[];
188
188
  vscodeExtension: string | null;
189
189
  }) {
190
- // Seeded from `?q=…` on the very first render, not from an effect: a
191
- // keyword chip on a package page links here, and filtering one render late
192
- // means painting the full catalog and then collapsing it. The server has
193
- // no `location`, so it renders the unfiltered list — which is what a
194
- // crawler and a `?q=`-less visitor should both get.
195
- const [query, setQuery] = useState(() =>
196
- typeof location === "undefined" ? "" : (new URLSearchParams(location.search).get("q") ?? ""),
197
- );
190
+ // Empty on the first render, ALWAYS `?q=…` is applied a beat later, in
191
+ // the layout effect below. This is not a style preference, it is the one
192
+ // rule this island has to obey.
193
+ //
194
+ // Preact does not diff props while hydrating; its own source says so, and
195
+ // only re-applies props whose value is a function. Text children *are*
196
+ // diffed. So when the first client render disagrees with the server's, you
197
+ // get cards whose text is right and whose every attribute belongs to
198
+ // whichever package the server put at that position: one package's logo
199
+ // over another's name, and a link that opens the wrong page. Nothing
200
+ // throws. This used to seed from `location` here, and did exactly that.
201
+ const [query, setQuery] = useState("");
202
+ // Whether the URL's query has been applied. Gates the reveal below, so the
203
+ // catalog is never unhidden while it still shows the unfiltered list.
204
+ const [seeded, setSeeded] = useState(false);
198
205
  const [kind, setKind] = useState<string | null>(null);
199
206
  const [sort, setSort] = useState<Sort>("name");
200
207
  // Deprecated packages are hidden until asked for: a retired package is
@@ -240,18 +247,28 @@ export default function Catalog({
240
247
  });
241
248
  };
242
249
 
250
+ // Apply `?q=…`, now that hydration has matched the server's markup and
251
+ // Preact owns the tree. A layout effect rather than a plain one: the
252
+ // resulting render must land before the browser paints, or a `?q=` visitor
253
+ // sees the whole catalog flash past on the way to their results.
254
+ useLayoutEffect(() => {
255
+ setQuery(new URLSearchParams(location.search).get("q") ?? "");
256
+ setSeeded(true);
257
+ }, []);
258
+
243
259
  // Base.astro hides the catalog before first paint when the URL carries a
244
- // query. Reveal it once this render the filtered one has hit the DOM.
245
- // A layout effect, so the reveal lands in the same frame as the content.
260
+ // query. Reveal it only once the filtered render is in the DOMkeyed on
261
+ // `seeded`, so the unfiltered first render is never the one revealed.
246
262
  useLayoutEffect(() => {
247
- // Hydration attaches handlers but does not diff props against the server
248
- // markup, so the box the server rendered empty stays empty even though
249
- // this render filtered on `query`. Written straight to the DOM, which is
250
- // what the vnode already claims.
263
+ if (!seeded) return;
264
+ // The input's `value` prop was skipped during hydration for the same
265
+ // reason every other prop was, and the render that applied the query is
266
+ // a normal diff but write it anyway: this effect is also what runs on
267
+ // a `?q=`-less load, where no second render is queued at all.
251
268
  const input = searchRef.current;
252
269
  if (input && input.value !== query) input.value = query;
253
270
  delete document.documentElement.dataset.query;
254
- }, []);
271
+ }, [seeded]);
255
272
 
256
273
  // `/` jumps to the search box, the convention every package registry
257
274
  // shares. Bound on the document so it works wherever the reader is.
@@ -68,9 +68,10 @@ function repoLabel(url: string): string {
68
68
  // package page) means the catalog the server rendered — every package,
69
69
  // empty search box — is the wrong one. Flag it so CSS holds the
70
70
  // catalog back rather than showing the full list and collapsing it a
71
- // moment later. The island drops the flag on its first render, which
72
- // is already the filtered one; the timeout is the failsafe for an
73
- // island that never hydrates, where a flash beats a blank page.
71
+ // moment later. The island drops the flag once it has applied the
72
+ // query — which is the render after hydration, never the hydrating one
73
+ // itself; the timeout is the failsafe for an island that never
74
+ // hydrates, where a flash beats a blank page.
74
75
  if (new URLSearchParams(location.search).get("q")) {
75
76
  document.documentElement.dataset.query = "";
76
77
  setTimeout(() => delete document.documentElement.dataset.query, 3000);
@@ -167,6 +168,18 @@ function repoLabel(url: string): string {
167
168
  config.footerNote ? ` · ${config.footerNote}` : ""
168
169
  }
169
170
  </p>
171
+ {/* Whatever this index has to link — a privacy notice, an imprint, a
172
+ code of conduct. Absent by default, so a footer without them looks
173
+ exactly as it did before. */}
174
+ {
175
+ config.footerLinks.length > 0 && (
176
+ <p class="footer-links">
177
+ {config.footerLinks.map((link) => (
178
+ <a href={link.href}>{link.label}</a>
179
+ ))}
180
+ </p>
181
+ )
182
+ }
170
183
  {/* Opt-out, not opt-in: an index runner who does not want to say
171
184
  where the software came from sets `attribution: false`, and this
172
185
  disappears. Nothing else on the page changes.
@@ -788,6 +801,14 @@ function repoLabel(url: string): string {
788
801
  .attribution span[aria-hidden] {
789
802
  color: var(--accent);
790
803
  }
804
+ /* Sits between the raw-data line and the attribution, which keeps its
805
+ `margin-inline-start: auto` and so stays pinned to the end. */
806
+ .footer-links {
807
+ margin: 0;
808
+ display: flex;
809
+ flex-wrap: wrap;
810
+ gap: 0.35rem 1rem;
811
+ }
791
812
 
792
813
  /* Copy toast. Fixed and centred at the bottom: a card's three copy
793
814
  buttons sit a few pixels apart and all flash the same check, so
@@ -844,8 +865,10 @@ function repoLabel(url: string): string {
844
865
  search box, so painting it and then collapsing to the match is a
845
866
  flash of the wrong answer. `visibility` rather than `display` —
846
867
  the space stays reserved, so the footer does not jump either. The
847
- attribute is set before first paint and dropped by the island's
848
- first render, which is already the filtered one. */
868
+ attribute is set before first paint and dropped once the island
869
+ has applied the query the render after hydration, because
870
+ hydrating against markup that disagrees with the server silently
871
+ mismatches every attribute (see Catalog.tsx). */
849
872
  :root[data-query] .catalog {
850
873
  visibility: hidden;
851
874
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grimoire-rs/indexer",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "CLI + Astro integration for running your own grim package index",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -38,6 +38,7 @@
38
38
  "@vitest/coverage-v8": "^4.1.10",
39
39
  "eslint": "^10.8.0",
40
40
  "js-yaml": "^4.1.0",
41
+ "jsdom": "^29.1.1",
41
42
  "typescript": "^6.0.3",
42
43
  "typescript-eslint": "^8.65.0",
43
44
  "vitest": "^4.1.10"