@grimoire-rs/indexer 0.3.2 → 0.4.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 +137 -2
- package/dist/ci.d.ts +6 -1
- package/dist/ci.d.ts.map +1 -1
- package/dist/ci.js +32 -1
- package/dist/ci.js.map +1 -1
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.js +3 -1
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/ci.d.ts.map +1 -1
- package/dist/cli/ci.js +5 -1
- package/dist/cli/ci.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +17 -3
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +8 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/ratings.d.ts +3 -0
- package/dist/cli/ratings.d.ts.map +1 -0
- package/dist/cli/ratings.js +160 -0
- package/dist/cli/ratings.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +47 -1
- package/dist/config.js.map +1 -1
- package/dist/enrich/index.d.ts.map +1 -1
- package/dist/enrich/index.js +74 -1
- package/dist/enrich/index.js.map +1 -1
- package/dist/ratings/budget.d.ts +16 -0
- package/dist/ratings/budget.d.ts.map +1 -0
- package/dist/ratings/budget.js +7 -0
- package/dist/ratings/budget.js.map +1 -0
- package/dist/ratings/config.d.ts +56 -0
- package/dist/ratings/config.d.ts.map +1 -0
- package/dist/ratings/config.js +86 -0
- package/dist/ratings/config.js.map +1 -0
- package/dist/ratings/marker.d.ts +105 -0
- package/dist/ratings/marker.d.ts.map +1 -0
- package/dist/ratings/marker.js +141 -0
- package/dist/ratings/marker.js.map +1 -0
- package/dist/ratings/provider.d.ts +108 -0
- package/dist/ratings/provider.d.ts.map +1 -0
- package/dist/ratings/provider.js +202 -0
- package/dist/ratings/provider.js.map +1 -0
- package/dist/ratings/provider_github.d.ts +3 -0
- package/dist/ratings/provider_github.d.ts.map +1 -0
- package/dist/ratings/provider_github.js +134 -0
- package/dist/ratings/provider_github.js.map +1 -0
- package/dist/ratings/provider_gitlab.d.ts +3 -0
- package/dist/ratings/provider_gitlab.d.ts.map +1 -0
- package/dist/ratings/provider_gitlab.js +152 -0
- package/dist/ratings/provider_gitlab.js.map +1 -0
- package/dist/ratings/provider_memory.d.ts +28 -0
- package/dist/ratings/provider_memory.d.ts.map +1 -0
- package/dist/ratings/provider_memory.js +49 -0
- package/dist/ratings/provider_memory.js.map +1 -0
- package/dist/ratings/reconcile.d.ts +47 -0
- package/dist/ratings/reconcile.d.ts.map +1 -0
- package/dist/ratings/reconcile.js +133 -0
- package/dist/ratings/reconcile.js.map +1 -0
- package/dist/ratings/seed.d.ts +60 -0
- package/dist/ratings/seed.d.ts.map +1 -0
- package/dist/ratings/seed.js +132 -0
- package/dist/ratings/seed.js.map +1 -0
- package/dist/renderer/astro/components/Catalog.d.ts +2 -0
- package/dist/renderer/astro/components/Catalog.js +96 -28
- package/dist/renderer/astro/components/Catalog.tsx +136 -32
- package/dist/renderer/astro/layouts/Base.astro +15 -5
- package/dist/renderer/astro/lib/catalog.d.ts +25 -0
- package/dist/renderer/astro/lib/catalog.js +46 -0
- package/dist/renderer/astro/lib/catalog.ts +49 -0
- package/dist/renderer/astro/pages/p/[...slug].astro +146 -3
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +101 -2
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/types.d.ts +41 -0
- package/dist/renderer/types.d.ts.map +1 -1
- package/dist/validate/adapters/http.d.ts +6 -1
- package/dist/validate/adapters/http.d.ts.map +1 -1
- package/dist/validate/adapters/http.js +5 -1
- package/dist/validate/adapters/http.js.map +1 -1
- package/package.json +2 -1
- package/templates/ci/github-pages.yml +11 -5
- package/templates/ci/github-ratings-seed.yml +88 -0
- package/templates/ci/github-ratings.yml +51 -0
- package/templates/ci/gitlab-ci.yml +3 -3
- package/templates/ci/gitlab-ratings-seed.sh +83 -0
- package/templates/ci/gitlab-ratings.yml +37 -0
- package/templates/gitignore +8 -0
|
@@ -15,7 +15,12 @@ export interface HttpResponse {
|
|
|
15
15
|
body: string;
|
|
16
16
|
header(name: string): string;
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
/** Everything past a plain GET. Omitted leaves `fetch`'s own defaults. */
|
|
19
|
+
export interface RequestOptions {
|
|
20
|
+
method?: string;
|
|
21
|
+
body?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function request(url: string, headers?: Record<string, string>, options?: RequestOptions): Promise<HttpResponse>;
|
|
19
24
|
/** `request` plus a `JSON.parse` that yields `null` instead of throwing. */
|
|
20
25
|
export declare function requestJson(url: string, headers?: Record<string, string>): Promise<{
|
|
21
26
|
status: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/validate/adapters/http.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH,eAAO,MAAM,UAAU,qBAAqB,CAAC;AAC7C,eAAO,MAAM,UAAU,QAAS,CAAC;AACjC,eAAO,MAAM,kBAAkB,QAAc,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B;AA4BD,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/validate/adapters/http.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH,eAAO,MAAM,UAAU,qBAAqB,CAAC;AAC7C,eAAO,MAAM,UAAU,QAAS,CAAC;AACjC,eAAO,MAAM,kBAAkB,QAAc,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B;AA4BD,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACpC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAwBvB;AAED,4EAA4E;AAC5E,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAQ5C"}
|
|
@@ -37,11 +37,15 @@ async function readCapped(response, cap) {
|
|
|
37
37
|
}
|
|
38
38
|
return Buffer.concat(chunks).toString("utf8");
|
|
39
39
|
}
|
|
40
|
-
export async function request(url, headers = {}) {
|
|
40
|
+
export async function request(url, headers = {}, options = {}) {
|
|
41
41
|
const none = { status: 0, body: "", header: () => "" };
|
|
42
42
|
try {
|
|
43
43
|
const response = await fetch(url, {
|
|
44
44
|
headers: { "User-Agent": USER_AGENT, ...headers },
|
|
45
|
+
// Both `undefined` for a two-argument call, which is what `fetch` sees
|
|
46
|
+
// when neither is passed — the existing GET call sites are untouched.
|
|
47
|
+
method: options.method,
|
|
48
|
+
body: options.body,
|
|
45
49
|
// ponytail: redirects are refused, not followed. If a real registry ever
|
|
46
50
|
// needs one, re-run the host policy on the Location and follow one hop.
|
|
47
51
|
redirect: "manual",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/validate/adapters/http.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,sCAAsC;AAEtC;;;;;;;GAOG;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,kBAAkB,CAAC;AAC7C,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC;AACjC,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAAC;AAS9C,KAAK,UAAU,UAAU,CACvB,QAAkB,EAClB,GAAW;IAEX,IAAI,CAAC,QAAQ,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;IACzC,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,CAAC;QACH,SAAS,CAAC;YACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC;YACzB,IAAI,IAAI,GAAG,GAAG,EAAE,CAAC;gBACf,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;gBACtB,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/validate/adapters/http.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grimoire-rs/indexer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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"
|
|
@@ -7,7 +7,7 @@ name: pages
|
|
|
7
7
|
|
|
8
8
|
on:
|
|
9
9
|
push:
|
|
10
|
-
branches: [{{defaultBranch}}]
|
|
10
|
+
branches: [{{defaultBranch}}]{{githubRatingsSchedule}}
|
|
11
11
|
workflow_dispatch:
|
|
12
12
|
|
|
13
13
|
# Default-deny at the top; every job pays for exactly what it needs.
|
|
@@ -19,7 +19,7 @@ concurrency:
|
|
|
19
19
|
|
|
20
20
|
jobs:
|
|
21
21
|
build:
|
|
22
|
-
runs-on: ubuntu-latest
|
|
22
|
+
runs-on: ubuntu-latest{{githubRatingsBuildKeys}}
|
|
23
23
|
permissions:
|
|
24
24
|
contents: read
|
|
25
25
|
steps:
|
|
@@ -40,12 +40,17 @@ jobs:
|
|
|
40
40
|
# here, in the job that publishes the site.
|
|
41
41
|
- name: Install
|
|
42
42
|
run: npm ci --ignore-scripts
|
|
43
|
-
|
|
43
|
+
|
|
44
|
+
# Ahead of the build, not after it: it only resolves this repository's
|
|
45
|
+
# Pages URL and sets outputs, and a step that runs before the build - the
|
|
46
|
+
# ratings seed - cross-checks `site` against that URL before it reads the
|
|
47
|
+
# published sidecar.
|
|
48
|
+
- id: pages
|
|
49
|
+
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
|
50
|
+
{{githubEnrichSteps}}{{githubRatingsSeedSteps}}
|
|
44
51
|
- name: Build index
|
|
45
52
|
run: npm run build
|
|
46
53
|
|
|
47
|
-
- uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
|
48
|
-
|
|
49
54
|
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
|
50
55
|
with:
|
|
51
56
|
path: dist
|
|
@@ -62,3 +67,4 @@ jobs:
|
|
|
62
67
|
steps:
|
|
63
68
|
- id: deployment
|
|
64
69
|
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
|
70
|
+
{{githubRatingsJob}}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
|
|
2
|
+
# Invariant R-2 lives in these two steps and in their order.
|
|
3
|
+
#
|
|
4
|
+
# A completed tally has already merged itself over this same published
|
|
5
|
+
# copy, per stat key, so it wins outright. Only when there is no fresh
|
|
6
|
+
# tally does the published copy get carried forward untouched - which is
|
|
7
|
+
# exactly what must happen when the `ratings` job failed, was skipped, or
|
|
8
|
+
# was never enabled on this run.
|
|
9
|
+
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
|
10
|
+
# A failed or skipped tally produced no artifact. That must not fail
|
|
11
|
+
# the deploy: carrying the published sidecar forward is the whole point.
|
|
12
|
+
continue-on-error: true
|
|
13
|
+
with:
|
|
14
|
+
name: grim-stats
|
|
15
|
+
|
|
16
|
+
# `|| true` is forbidden here. It cannot tell a genuine 404 from a DNS
|
|
17
|
+
# failure, a TLS error, a 5xx or a truncated body - and if the tally also
|
|
18
|
+
# failed, the deploy would then publish a site with no sidecar at all,
|
|
19
|
+
# the exact wipe R-2 exists to prevent. So the status code is captured
|
|
20
|
+
# and branched on: 404 is an empty seed, a 2xx that parses is the merge
|
|
21
|
+
# base, anything else fails the job.
|
|
22
|
+
- name: Seed from the published sidecar
|
|
23
|
+
env:
|
|
24
|
+
PAGES_URL: ${{ steps.pages.outputs.base_url }}
|
|
25
|
+
run: |
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
|
|
28
|
+
# One source. The seed URL is `site` from index.config.json, read
|
|
29
|
+
# out of the checkout here - the same key, the same file and the
|
|
30
|
+
# same run as the tally that produced the artifact above. Not baked
|
|
31
|
+
# in at render time on purpose: a workflow rendered before `site`
|
|
32
|
+
# was last edited would seed from one URL while the tally read the
|
|
33
|
+
# other, which is the divergence this step exists to close.
|
|
34
|
+
SITE_URL=$(node -p 'JSON.parse(require("fs").readFileSync("index.config.json","utf8")).site ?? ""')
|
|
35
|
+
rstrip() { u=$1; while [ "${u%/}" != "$u" ]; do u=${u%/}; done; printf '%s' "$u"; }
|
|
36
|
+
SITE_URL=$(rstrip "$SITE_URL")
|
|
37
|
+
case "$SITE_URL" in
|
|
38
|
+
https://*) ;;
|
|
39
|
+
# Not defaulted: the built-in default names the first-party index,
|
|
40
|
+
# and seeding from someone else's published stats would merge
|
|
41
|
+
# their ratings into this one's sidecar. TLS because the fetched
|
|
42
|
+
# document decides every published rating - `--proto '=https'`
|
|
43
|
+
# below would refuse it anyway, in curl's words instead of these.
|
|
44
|
+
*) echo "::error::index.config.json needs an explicit https \`site\` - the seed is read from <site>/stats.json (got '$SITE_URL')"; exit 1 ;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
# The Pages URL is a cross-check, never a second source: while it was
|
|
48
|
+
# the source, this step fetched one URL and the tally fetched the
|
|
49
|
+
# other, and a 404 there is a legal empty seed - so a disagreement
|
|
50
|
+
# published a sidecar built from nothing, at exit 0.
|
|
51
|
+
#
|
|
52
|
+
# Absent is not a disagreement: a fork, or a deploy that is not
|
|
53
|
+
# Pages, has no such URL, and `site` stands alone. Neither is a
|
|
54
|
+
# trailing slash, the scheme, or host case - hostnames are
|
|
55
|
+
# case-insensitive where the path is not. A real disagreement fails
|
|
56
|
+
# ahead of the freshness check below: the tally read `site` too, so
|
|
57
|
+
# a fresh artifact is no evidence that this URL is the right one.
|
|
58
|
+
lower() { u=${1#*://}; h=${u%%/*}; printf '%s%s' "${h,,}" "${u#"$h"}"; }
|
|
59
|
+
if [ -n "${PAGES_URL:-}" ] && [ "$(lower "$(rstrip "$PAGES_URL")")" != "$(lower "$SITE_URL")" ]; then
|
|
60
|
+
echo "::error::index.config.json publishes to $SITE_URL but this pipeline deploys $PAGES_URL - the seed would be read from one and the sidecar published to the other"
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
if [ -f .stats.json ]; then
|
|
65
|
+
echo "using the tally this run produced"
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
code=$(curl -sS --proto '=https' --tlsv1.2 --max-time 30 \
|
|
69
|
+
-o .stats.json.seed -w '%{http_code}' "$SITE_URL/stats.json") || {
|
|
70
|
+
echo "::error::could not read the published stats.json - refusing to deploy a site that would empty every published rating"
|
|
71
|
+
exit 1
|
|
72
|
+
}
|
|
73
|
+
case "$code" in
|
|
74
|
+
404)
|
|
75
|
+
echo "no stats.json published yet - starting from an empty seed"
|
|
76
|
+
;;
|
|
77
|
+
2??)
|
|
78
|
+
node -e 'JSON.parse(require("fs").readFileSync(".stats.json.seed","utf8"))' || {
|
|
79
|
+
echo "::error::the published stats.json did not parse - refusing to treat an unreadable seed as an empty one"
|
|
80
|
+
exit 1
|
|
81
|
+
}
|
|
82
|
+
mv -f .stats.json.seed .stats.json
|
|
83
|
+
;;
|
|
84
|
+
*)
|
|
85
|
+
echo "::error::the published stats.json returned HTTP $code"
|
|
86
|
+
exit 1
|
|
87
|
+
;;
|
|
88
|
+
esac
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
# Tallies the forge's own upvote counters into `.stats.json` - the sidecar
|
|
3
|
+
# `build` joins onto the catalog and the deploy publishes beside all.json.
|
|
4
|
+
#
|
|
5
|
+
# Its own job, ahead of `build`, because `build` has to run even when this
|
|
6
|
+
# one fails. A tally that cannot reach the forge must never empty a published
|
|
7
|
+
# rating set (R-2), and the only way to keep the previous one is to deploy
|
|
8
|
+
# without a fresh tally - see the seed step in `build`.
|
|
9
|
+
#
|
|
10
|
+
# Turn ratings off by deleting the `ratings` block from index.config.json and
|
|
11
|
+
# re-rendering. That stops the tally, but the last one stays published:
|
|
12
|
+
# deleting `stats.json` from the deploy is the other half of the rollback.
|
|
13
|
+
ratings:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
# The only write anything in this pipeline is granted, and it is what a
|
|
18
|
+
# vote is: threads live on the forge, nothing is committed here.
|
|
19
|
+
discussions: write
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
22
|
+
with:
|
|
23
|
+
persist-credentials: false
|
|
24
|
+
|
|
25
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
26
|
+
with:
|
|
27
|
+
node-version: "{{nodeVersion}}"
|
|
28
|
+
cache: npm
|
|
29
|
+
|
|
30
|
+
- name: Install
|
|
31
|
+
run: npm ci --ignore-scripts
|
|
32
|
+
|
|
33
|
+
# `npx --no` refuses to fetch anything `npm ci` did not install, so the
|
|
34
|
+
# renderer that tallies is the one package-lock.json pins - the same
|
|
35
|
+
# reviewed-fact rule every other job here follows. Spelled as `npx`
|
|
36
|
+
# rather than an npm script so an index scaffolded before ratings existed
|
|
37
|
+
# turns them on by re-rendering CI, with no package.json edit.
|
|
38
|
+
- name: Tally ratings
|
|
39
|
+
env:
|
|
40
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
+
run: npx --no grim-indexer ratings
|
|
42
|
+
|
|
43
|
+
# `error` rather than the default warning: a run that reported success
|
|
44
|
+
# and wrote nothing is a bug, and the failure mode it would otherwise
|
|
45
|
+
# produce - `build` silently falling back to the seed - looks exactly
|
|
46
|
+
# like a healthy carry-forward.
|
|
47
|
+
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
|
48
|
+
with:
|
|
49
|
+
name: grim-stats
|
|
50
|
+
path: .stats.json
|
|
51
|
+
if-no-files-found: error
|
|
@@ -129,13 +129,13 @@ grim-indexer:validate:
|
|
|
129
129
|
pages:
|
|
130
130
|
stage: deploy
|
|
131
131
|
rules:
|
|
132
|
-
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
132
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH{{gitlabRatingsPagesRule}}
|
|
133
133
|
before_script:
|
|
134
134
|
- npm ci --cache .npm --prefer-offline --ignore-scripts
|
|
135
135
|
script:
|
|
136
136
|
- |
|
|
137
137
|
set -eu
|
|
138
|
-
{{gitlabEnrichScript}}
|
|
138
|
+
{{gitlabEnrichScript}}{{gitlabRatingsSeedScript}}
|
|
139
139
|
# Built into `dist/`, then moved. Building straight into `public/` was a
|
|
140
140
|
# data-loss bug: the compile step clears its output directory first, so it
|
|
141
141
|
# deleted this index's own committed asset layer (logo, favicon) before
|
|
@@ -146,4 +146,4 @@ pages:
|
|
|
146
146
|
artifacts:
|
|
147
147
|
paths:
|
|
148
148
|
- public
|
|
149
|
-
{{gitlabPublishJob}}
|
|
149
|
+
{{gitlabRatingsJob}}{{gitlabPublishJob}}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
|
|
2
|
+
# Invariant R-2. GitLab hands this job the ratings artifact before the
|
|
3
|
+
# script runs, so `.stats.json` being present means a tally completed -
|
|
4
|
+
# and a completed tally has already merged itself over this same
|
|
5
|
+
# published copy, per stat key. Only when there is none does the
|
|
6
|
+
# published copy get carried forward untouched, which is what must happen
|
|
7
|
+
# when the tally failed or was skipped.
|
|
8
|
+
#
|
|
9
|
+
# `|| true` is forbidden here. It cannot tell a genuine 404 from a DNS
|
|
10
|
+
# failure, a TLS error, a 5xx or a truncated body - and if the tally also
|
|
11
|
+
# failed, the deploy would then publish a site with no sidecar at all,
|
|
12
|
+
# the exact wipe R-2 exists to prevent. So the status code is captured
|
|
13
|
+
# and branched on: 404 is an empty seed, a 2xx that parses is the merge
|
|
14
|
+
# base, anything else fails the job.
|
|
15
|
+
#
|
|
16
|
+
# One source. The seed URL is `site` from index.config.json, read out of
|
|
17
|
+
# the checkout here - the same key, the same file and the same run as
|
|
18
|
+
# the tally that produced the artifact this job was handed. Not baked in
|
|
19
|
+
# at render time on purpose: a pipeline rendered before `site` was last
|
|
20
|
+
# edited would seed from one URL while the tally read the other, which
|
|
21
|
+
# is the divergence this step exists to close.
|
|
22
|
+
#
|
|
23
|
+
# Deliberately NOT cross-checked against `CI_PAGES_URL`. That variable
|
|
24
|
+
# is always a subdomain of `CI_PAGES_DOMAIN` and never reflects a custom
|
|
25
|
+
# domain, and unique domains (16.7+) and `path_prefix` (17.9+) move it
|
|
26
|
+
# again - so it disagrees with a correct `site` as the normal case, and
|
|
27
|
+
# comparing them would fail every such deploy. The GitHub arm keeps its
|
|
28
|
+
# cross-check because `steps.pages.outputs.base_url` really is a
|
|
29
|
+
# statement about where the site deploys.
|
|
30
|
+
SITE_URL=$(node -p 'JSON.parse(require("fs").readFileSync("index.config.json","utf8")).site ?? ""')
|
|
31
|
+
rstrip() { u=$1; while [ "${u%/}" != "$u" ]; do u=${u%/}; done; printf '%s' "$u"; }
|
|
32
|
+
SITE_URL=$(rstrip "$SITE_URL")
|
|
33
|
+
case "$SITE_URL" in
|
|
34
|
+
https://*) ;;
|
|
35
|
+
# Not defaulted: the built-in default names the first-party index, and
|
|
36
|
+
# seeding from someone else's published stats would merge their
|
|
37
|
+
# ratings into this one's sidecar. TLS because the fetched document
|
|
38
|
+
# decides every published rating - `--proto '=https'` below would
|
|
39
|
+
# refuse it anyway, in curl's words instead of these.
|
|
40
|
+
*)
|
|
41
|
+
echo "grim-indexer: index.config.json needs an explicit https \`site\` - the seed is read from <site>/stats.json (got '$SITE_URL')" >&2
|
|
42
|
+
exit 1
|
|
43
|
+
;;
|
|
44
|
+
esac
|
|
45
|
+
|
|
46
|
+
# `node:*-alpine` ships no curl, and the runner's clone helper is a
|
|
47
|
+
# separate image, so nothing else puts one here. Install it for whichever
|
|
48
|
+
# package manager this image has, then insist it is really there - a
|
|
49
|
+
# missing tool must fail the job, never silently skip the seed.
|
|
50
|
+
if [ ! -f .stats.json ]; then
|
|
51
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
52
|
+
if command -v apk >/dev/null 2>&1; then
|
|
53
|
+
apk add --no-cache curl
|
|
54
|
+
elif command -v apt-get >/dev/null 2>&1; then
|
|
55
|
+
apt-get update -qq && apt-get install -y -qq --no-install-recommends curl
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
command -v curl >/dev/null 2>&1 || {
|
|
59
|
+
echo "grim-indexer: seeding stats.json needs curl, and this image has none" >&2
|
|
60
|
+
exit 1
|
|
61
|
+
}
|
|
62
|
+
code=$(curl -sS --proto '=https' --tlsv1.2 --max-time 30 \
|
|
63
|
+
-o .stats.json.seed -w '%{http_code}' "$SITE_URL/stats.json") || {
|
|
64
|
+
echo "grim-indexer: could not read the published stats.json - refusing to deploy a site that would empty every published rating" >&2
|
|
65
|
+
exit 1
|
|
66
|
+
}
|
|
67
|
+
case "$code" in
|
|
68
|
+
404)
|
|
69
|
+
echo "grim-indexer: no stats.json published yet - starting from an empty seed"
|
|
70
|
+
;;
|
|
71
|
+
2??)
|
|
72
|
+
node -e 'JSON.parse(require("fs").readFileSync(".stats.json.seed","utf8"))' || {
|
|
73
|
+
echo "grim-indexer: the published stats.json did not parse - refusing to treat an unreadable seed as an empty one" >&2
|
|
74
|
+
exit 1
|
|
75
|
+
}
|
|
76
|
+
mv -f .stats.json.seed .stats.json
|
|
77
|
+
;;
|
|
78
|
+
*)
|
|
79
|
+
echo "grim-indexer: the published stats.json returned HTTP $code" >&2
|
|
80
|
+
exit 1
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
83
|
+
fi
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
# Tallies the forge's own emoji reactions into `.stats.json` - the sidecar the
|
|
3
|
+
# `pages` job joins onto the catalog and publishes beside all.json. (GitLab
|
|
4
|
+
# renamed this feature Award Emoji to Emoji Reactions in 16.0; the GraphQL
|
|
5
|
+
# surface is unchanged.)
|
|
6
|
+
#
|
|
7
|
+
# Its own job, ahead of `pages`, because `pages` has to run even when this one
|
|
8
|
+
# fails. A tally that cannot reach GitLab must never empty a published rating
|
|
9
|
+
# set (R-2), and the only way to keep the previous one is to deploy without a
|
|
10
|
+
# fresh tally - hence `allow_failure`, which still reds the pipeline and still
|
|
11
|
+
# lets the deploy through, and hence `optional: true` on the `pages` job's
|
|
12
|
+
# `needs`.
|
|
13
|
+
#
|
|
14
|
+
# `resource_group` is a correctness requirement, not tuning: two overlapping
|
|
15
|
+
# runs can interleave a stale tally over a fresh one. It serializes them. Set
|
|
16
|
+
# the group's process mode to `oldest_first` once in Settings -> CI/CD, so a
|
|
17
|
+
# queued run cannot overtake the one ahead of it - that part has no YAML.
|
|
18
|
+
#
|
|
19
|
+
# The tally needs a token that may write reactions: set GRIM_RATINGS_TOKEN as a
|
|
20
|
+
# masked CI/CD variable (a project access token with `api` scope is enough,
|
|
21
|
+
# since the threads live in this same project). CI_JOB_TOKEN cannot do it.
|
|
22
|
+
grim-indexer:ratings:
|
|
23
|
+
stage: test
|
|
24
|
+
resource_group: grim-ratings
|
|
25
|
+
allow_failure: true
|
|
26
|
+
rules:
|
|
27
|
+
- if: $CI_PIPELINE_SOURCE == "schedule"
|
|
28
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
29
|
+
before_script:
|
|
30
|
+
- npm ci --cache .npm --prefer-offline --ignore-scripts
|
|
31
|
+
script:
|
|
32
|
+
# `npx --no` refuses to fetch anything `npm ci` did not install, so the
|
|
33
|
+
# renderer that tallies is the one package-lock.json pins.
|
|
34
|
+
- npx --no grim-indexer ratings
|
|
35
|
+
artifacts:
|
|
36
|
+
paths:
|
|
37
|
+
- .stats.json
|
package/templates/gitignore
CHANGED
|
@@ -8,6 +8,14 @@ dist/
|
|
|
8
8
|
# Renderer scratch dir - removed on a clean build, left behind by a killed one.
|
|
9
9
|
.index-*/
|
|
10
10
|
|
|
11
|
+
# The ratings sidecar. CI seeds it from the live site, the tally merges over
|
|
12
|
+
# it, and the deploy publishes it as `stats.json` - it is a build input, never
|
|
13
|
+
# a source of truth. Deliberately not committed: it is regenerated from the
|
|
14
|
+
# forge every run, so a committed copy is only ever a stale one, and it would
|
|
15
|
+
# keep being published after the `ratings` block was removed, defeating the
|
|
16
|
+
# rollback.
|
|
17
|
+
.stats.json
|
|
18
|
+
|
|
11
19
|
node_modules/
|
|
12
20
|
.npm/
|
|
13
21
|
|