@grimoire-rs/indexer 0.2.2 → 0.3.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 +51 -18
- package/dist/ci.d.ts +115 -0
- package/dist/ci.d.ts.map +1 -0
- package/dist/ci.js +286 -0
- package/dist/ci.js.map +1 -0
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.js +3 -9
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/ci.d.ts +6 -0
- package/dist/cli/ci.d.ts.map +1 -0
- package/dist/cli/ci.js +43 -0
- package/dist/cli/ci.js.map +1 -0
- package/dist/cli/dev.d.ts +7 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +57 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/init.d.ts +6 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +294 -74
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +61 -8
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/out_dir.d.ts +11 -0
- package/dist/cli/out_dir.d.ts.map +1 -0
- package/dist/cli/out_dir.js +30 -0
- package/dist/cli/out_dir.js.map +1 -0
- package/dist/cli/validate.d.ts +1 -0
- package/dist/cli/validate.d.ts.map +1 -1
- package/dist/cli/validate.js +47 -1
- package/dist/cli/validate.js.map +1 -1
- package/dist/templates.d.ts +12 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +40 -0
- package/dist/templates.js.map +1 -0
- package/dist/validate/adapters/files.d.ts.map +1 -1
- package/dist/validate/adapters/files.js +13 -3
- package/dist/validate/adapters/files.js.map +1 -1
- package/dist/validate/index.js +17 -2
- package/dist/validate/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/README.md +115 -19
- package/templates/ci/github-automerge.yml +36 -0
- package/templates/ci/github-enrich.yml +27 -0
- package/templates/ci/github-pages.yml +64 -0
- package/templates/ci/github-validate.yml +157 -0
- package/templates/ci/github-verify-ci.yml +47 -0
- package/templates/ci/gitlab-ci.yml +148 -0
- package/templates/ci/gitlab-enrich.sh +26 -0
- package/templates/ci/gitlab-verify.yml +16 -0
- package/templates/ci/header.txt +19 -0
- package/templates/gitattributes +4 -0
- package/templates/gitignore +7 -2
- package/templates/package.json +18 -0
- package/templates/publish.toml +2 -2
- package/templates/github-pages.yml +0 -24
- package/templates/github-validate.yml +0 -51
- package/templates/gitlab-ci.yml +0 -13
- package/templates/reusable/gitlab-index-ci.yml +0 -125
package/README.md
CHANGED
|
@@ -6,20 +6,33 @@ servers, and bundles available in one or more OCI registries.
|
|
|
6
6
|
|
|
7
7
|
## Subcommands
|
|
8
8
|
|
|
9
|
-
- `grim-indexer init` — scaffold a new index repo
|
|
10
|
-
tree,
|
|
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.
|
|
11
14
|
- `grim-indexer enrich` — refresh `enrich/**` from the registry: READMEs,
|
|
12
15
|
changelogs, logos, versions and tag lists. The only step that goes
|
|
13
16
|
online, and the only one that needs `grim` on `PATH`.
|
|
14
17
|
- `grim-indexer build` — render `index/**` into a static site.
|
|
15
18
|
- `grim-indexer validate` — CI gate for contribution PRs/MRs against an
|
|
16
19
|
index repo.
|
|
20
|
+
- `grim-indexer ci` — render the index repo's workflows from the `ci`
|
|
21
|
+
block of its `index.config.json`; `--check` verifies the committed ones
|
|
22
|
+
still match and exits 65 on drift.
|
|
23
|
+
|
|
24
|
+
A scaffolded index owns its CI: the workflow files are committed in that
|
|
25
|
+
repository and run `npm ci` against its own lockfile, so nothing is
|
|
26
|
+
fetched from here at run time and the version that builds an index is the
|
|
27
|
+
one that repo has locked. The generated `verify-ci` job re-renders and
|
|
28
|
+
diffs on every push, which is what keeps a hand-edit from silently
|
|
29
|
+
forking the pipeline.
|
|
17
30
|
|
|
18
31
|
An index stores nothing but pointers — a ref and who owns it. Everything a
|
|
19
32
|
reader looks at lives in the registry, so an index that never runs `enrich`
|
|
20
33
|
renders a catalogue of names with *No README available* on every page. The
|
|
21
|
-
scaffolded CI runs it before each build; set `enrich: false`
|
|
22
|
-
|
|
34
|
+
scaffolded CI runs it before each build; set `"enrich": false` in the `ci`
|
|
35
|
+
block for a pointers-only site.
|
|
23
36
|
|
|
24
37
|
## Install
|
|
25
38
|
|
|
@@ -30,10 +43,19 @@ npm install --save-dev @grimoire-rs/indexer
|
|
|
30
43
|
## Usage
|
|
31
44
|
|
|
32
45
|
```sh
|
|
33
|
-
npx @grimoire-rs/indexer init
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
npx @grimoire-rs/indexer init # scaffold; writes package.json + lockfile
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Everything after that runs through the scaffolded repo's own scripts, so it
|
|
50
|
+
uses the version that repo locked:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npm run dev # local preview
|
|
54
|
+
npm run build # index/** -> dist/
|
|
55
|
+
npm run enrich # needs `grim` on PATH
|
|
56
|
+
npm run validate # the contribution gate
|
|
57
|
+
npm run ci # re-render CI after editing index.config.json
|
|
58
|
+
npm run ci:check # fail on drift (what the verify-ci job runs)
|
|
37
59
|
```
|
|
38
60
|
|
|
39
61
|
As an Astro integration:
|
|
@@ -60,9 +82,11 @@ unreachable OCI ref, unowned namespace).
|
|
|
60
82
|
|
|
61
83
|
Two things that trial settled, both worth knowing before you scaffold:
|
|
62
84
|
|
|
63
|
-
- **
|
|
64
|
-
|
|
65
|
-
|
|
85
|
+
- **Require the check named `validate`.** It was `validate / validate`
|
|
86
|
+
while the scaffold emitted thin callers of reusable workflows; the
|
|
87
|
+
workflow is now committed in the index repo, so the context is just the
|
|
88
|
+
job key. Requiring a context that never reports blocks every PR forever
|
|
89
|
+
and looks exactly like the gate rejecting your contribution.
|
|
66
90
|
- **In the combined (`--with-skills`) layout the gate does not cover your
|
|
67
91
|
own CI's announce.** GitHub runs no workflows on a PR opened with
|
|
68
92
|
`secrets.GITHUB_TOKEN`, so that PR arrives ungated — review it by hand.
|
|
@@ -70,8 +94,8 @@ Two things that trial settled, both worth knowing before you scaffold:
|
|
|
70
94
|
requests" enabled, which is off by default and which also lets
|
|
71
95
|
workflows approve PRs.
|
|
72
96
|
|
|
73
|
-
Not yet proven live: the GitLab leg (hermetic tests only
|
|
74
|
-
|
|
97
|
+
Not yet proven live: the GitLab leg (hermetic unit tests only - no live
|
|
98
|
+
GitLab pipeline has run the rendered CI), and the cross-repository
|
|
75
99
|
announce, which needs a credential beyond the CI token.
|
|
76
100
|
|
|
77
101
|
Two things are frozen and safe to build on: the published URL layout
|
|
@@ -83,13 +107,22 @@ is deliberately no component-override API yet — publishing one would
|
|
|
83
107
|
freeze a prop contract per slot, and that is not a promise worth making
|
|
84
108
|
this early.
|
|
85
109
|
|
|
86
|
-
> **Use `0.1.4` or later.** `0.1.0` installs without an executable
|
|
110
|
+
> **Use `0.1.4` or later.** `0.1.0` installs without an executable - npm
|
|
87
111
|
> silently stripped its `bin` entry at publish time. `0.1.1` and `0.1.2`
|
|
88
112
|
> scaffold CI that points at reusable workflows those tags do not contain,
|
|
89
|
-
> so the first push to a scaffolded index fails before any job runs.
|
|
90
|
-
>
|
|
91
|
-
>
|
|
92
|
-
>
|
|
113
|
+
> so the first push to a scaffolded index fails before any job runs.
|
|
114
|
+
>
|
|
115
|
+
> An index already scaffolded against a reusable-workflow version keeps
|
|
116
|
+
> working as long as its pinned `uses:`/`include:` refs stay on an existing
|
|
117
|
+
> tag - old tags are not deleted, so that resolution does not break on its
|
|
118
|
+
> own. It breaks the moment something bumps the pinned ref, because `main`
|
|
119
|
+
> no longer defines any reusable workflow or remote include for it to
|
|
120
|
+
> resolve to: a Renovate update of an `@grimoire-rs/indexer` action ref
|
|
121
|
+
> will now fail hard. Fix it before that happens by re-scaffolding the CI -
|
|
122
|
+
> `npx @grimoire-rs/indexer init . --force` and commit the result, or, if
|
|
123
|
+
> you would rather not touch anything else `init` writes, add a
|
|
124
|
+
> `package.json` pinning this package (if the old scaffold has none) and
|
|
125
|
+
> run `npm run ci` to re-render just the workflow files.
|
|
93
126
|
>
|
|
94
127
|
> Through `0.1.3`, `init --with-skills` wrote a `publish.toml` with no
|
|
95
128
|
> `[announce]` table, so `grim publish --announce` in a combined-layout
|
package/dist/ci.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { CliError } from "./cli/exit.js";
|
|
2
|
+
/**
|
|
3
|
+
* Forges CI can be rendered for. One repository is hosted on one forge, so
|
|
4
|
+
* this is a choice rather than a set — rendering both left every index with a
|
|
5
|
+
* pipeline it would never run, and a second gate to keep honest for nothing.
|
|
6
|
+
*/
|
|
7
|
+
export type Forge = "github" | "gitlab";
|
|
8
|
+
/**
|
|
9
|
+
* The `ci` block of `index.config.json` — everything that varies the
|
|
10
|
+
* generated pipeline. Every key is optional; [`resolveCi`] fills the gaps.
|
|
11
|
+
*/
|
|
12
|
+
export interface CiConfig {
|
|
13
|
+
/** Which forge's CI to render. Default `github`. */
|
|
14
|
+
forge?: Forge;
|
|
15
|
+
/** Node.js the jobs run on — `setup-node` version, and the GitLab image tag. Default `22`. */
|
|
16
|
+
nodeVersion?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Refresh READMEs, changelogs, logos and version lists from the registry
|
|
19
|
+
* before building. `false` renders the enrichment steps away entirely, for
|
|
20
|
+
* a pointers-only site whose build never leaves the runner. Default `true`.
|
|
21
|
+
*/
|
|
22
|
+
enrich?: boolean;
|
|
23
|
+
/** grim release the enrichment step reads the registry with — `latest` or a tag. Default `latest`. */
|
|
24
|
+
grimVersion?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Squash-merge a pull request the gate passed, then deploy. Default `false`:
|
|
27
|
+
* merging an untrusted contribution unattended is a policy this package will
|
|
28
|
+
* not assume on a repository's behalf.
|
|
29
|
+
*
|
|
30
|
+
* GitHub only, and deliberately. GitHub always runs the base branch's copy
|
|
31
|
+
* of a `pull_request_target` workflow, so the gate a pull request faces is
|
|
32
|
+
* one it cannot edit. GitLab has no equivalent — the merge request supplies
|
|
33
|
+
* its own `.gitlab-ci.yml`, and a fork's pipeline runs in the fork — so an
|
|
34
|
+
* auto-merge there would act on a verdict the contributor could write.
|
|
35
|
+
*/
|
|
36
|
+
autoMerge?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Own these files by hand. The `verify-ci` drift guard is then not emitted,
|
|
39
|
+
* `ci --check` is the only thing left that would notice an edit, and CI
|
|
40
|
+
* fixes stop arriving by re-render. Discouraged, and deliberately not the
|
|
41
|
+
* default — but it is the repository's CI, so it is the repository's call.
|
|
42
|
+
*/
|
|
43
|
+
allowManualEdits?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export type ResolvedCiConfig = Required<CiConfig>;
|
|
46
|
+
/** Fill every gap from the defaults documented on [`CiConfig`]. */
|
|
47
|
+
export declare function resolveCi(ci: CiConfig | undefined): ResolvedCiConfig;
|
|
48
|
+
/** Validate the `ci` block of an already-parsed config object. */
|
|
49
|
+
export declare function validateCi(raw: unknown): CiConfig;
|
|
50
|
+
/** Read the `ci` block out of `index.config.json`. A missing file means defaults. */
|
|
51
|
+
export declare function loadCiConfig(root: string): Promise<CiConfig>;
|
|
52
|
+
/**
|
|
53
|
+
* First line of every file this renderer writes. Stale detection keys on it,
|
|
54
|
+
* so a repository's hand-written workflows are never mistaken for ours.
|
|
55
|
+
*
|
|
56
|
+
* Deliberately carries no version stamp. The version that renders these files
|
|
57
|
+
* is the one in the repository's own `package-lock.json`; stamping it here too
|
|
58
|
+
* would be a second copy that goes stale, and every dependency bump would red
|
|
59
|
+
* the drift guard until somebody re-rendered a file whose content had not
|
|
60
|
+
* changed.
|
|
61
|
+
*/
|
|
62
|
+
export declare const GENERATED_HEADER = "# Generated by @grimoire-rs/indexer";
|
|
63
|
+
/**
|
|
64
|
+
* Render the complete CI file set. Nothing touches disk — the caller writes
|
|
65
|
+
* it or diffs it against what is committed.
|
|
66
|
+
*/
|
|
67
|
+
export declare function renderCi(ci: ResolvedCiConfig): Map<string, string>;
|
|
68
|
+
/**
|
|
69
|
+
* Canonicalize a generated file for drift comparison.
|
|
70
|
+
*
|
|
71
|
+
* The index repository owns the *pin* on every `uses:` action reference —
|
|
72
|
+
* its own Renovate or Dependabot will bump `uses: owner/action@<ref> # vX`,
|
|
73
|
+
* and the guard must not read that as a hand-edit. Only the `@<ref>` suffix
|
|
74
|
+
* and any trailing version comment are stripped; `owner/action` survives, so
|
|
75
|
+
* swapping in a different action still trips drift.
|
|
76
|
+
*/
|
|
77
|
+
export declare function normalizeForDrift(content: string): string;
|
|
78
|
+
/**
|
|
79
|
+
* Files on disk that carry our header but that the current config no longer
|
|
80
|
+
* renders — a `forge` narrowed from `both`, or `allowManualEdits` turned on
|
|
81
|
+
* after a guard was already committed. Reported, never deleted: removing a
|
|
82
|
+
* file from somebody's repository is their call, not the renderer's.
|
|
83
|
+
*/
|
|
84
|
+
export declare function staleCi(root: string, files: Map<string, string>): Promise<string[]>;
|
|
85
|
+
export interface CiOutcome {
|
|
86
|
+
path: string;
|
|
87
|
+
/** `written` on a render; `drift`/`stale`/`ok` on a check. */
|
|
88
|
+
status: "written" | "unchanged" | "ok" | "drift" | "stale";
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Write the rendered set, overwriting whatever is there.
|
|
92
|
+
*
|
|
93
|
+
* "Whatever is there" is judged through [`normalizeForDrift`], the same lens
|
|
94
|
+
* the guard uses — not raw bytes. Otherwise the two disagreed: `ci --check`
|
|
95
|
+
* deliberately tolerates a bumped `uses: owner/action@<ref>`, while a plain
|
|
96
|
+
* `npm run ci` rewrote it back to the pin baked into this package, silently
|
|
97
|
+
* undoing every Renovate bump the repository is told it may make. The repo
|
|
98
|
+
* owns its pins; a render must not take them back.
|
|
99
|
+
*/
|
|
100
|
+
export declare function writeCi(root: string, files: Map<string, string>): Promise<CiOutcome[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Compare the rendered set against what is committed. Missing counts as
|
|
103
|
+
* drift; a generated file the config no longer renders counts as stale.
|
|
104
|
+
* Hints are path-only — the contents of a repository's CI never go to stderr.
|
|
105
|
+
*/
|
|
106
|
+
export declare function checkCi(root: string, files: Map<string, string>): Promise<CiOutcome[]>;
|
|
107
|
+
/**
|
|
108
|
+
* The re-render command a drift message tells the reader to run. It is the
|
|
109
|
+
* repository's own npm script, so it runs the renderer the lock pins rather
|
|
110
|
+
* than whatever `npx` would fetch today.
|
|
111
|
+
*/
|
|
112
|
+
export declare const RE_RENDER_COMMAND = "npm run ci";
|
|
113
|
+
/** Turn a failed check into the error the CLI exits on. */
|
|
114
|
+
export declare function driftError(outcomes: CiOutcome[]): CliError;
|
|
115
|
+
//# sourceMappingURL=ci.d.ts.map
|
package/dist/ci.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci.d.ts","sourceRoot":"","sources":["../src/ci.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,QAAQ,EAAQ,MAAM,eAAe,CAAC;AAI/C;;;;GAIG;AACH,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAIxC;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,oDAAoD;IACpD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,8FAA8F;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sGAAsG;IACtG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAElD,mEAAmE;AACnE,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,SAAS,GAAG,gBAAgB,CASpE;AAMD,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,CAiCjD;AAED,qFAAqF;AACrF,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkBlE;AAID;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,wCAAwC,CAAC;AA2CtE;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,gBAAgB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAuClE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASzD;AAED;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAoBzF;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,IAAI,GAAG,OAAO,GAAG,OAAO,CAAC;CAC5D;AAED;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAc5F;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAW5F;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,eAAe,CAAC;AAE9C,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,QAAQ,CAO1D"}
|
package/dist/ci.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 The Grimoire Authors
|
|
3
|
+
// The CI renderer: `index.config.json`'s `ci` block in, complete workflow
|
|
4
|
+
// files out. The index repository owns and commits what it runs — there is no
|
|
5
|
+
// reusable workflow to call, no remote include to fetch, and nothing about the
|
|
6
|
+
// pipeline that lives anywhere but in the repository being pushed.
|
|
7
|
+
//
|
|
8
|
+
// The trade that buys is the drift guard: because the files are generated,
|
|
9
|
+
// `grim-indexer ci --check` can re-render and diff, and the generated
|
|
10
|
+
// `verify-ci` job runs exactly that on every push and pull request. So a
|
|
11
|
+
// hand-edit is caught rather than silently forked, while the repository keeps
|
|
12
|
+
// full ownership of its own CI — set `allowManualEdits` and the guard is not
|
|
13
|
+
// emitted at all.
|
|
14
|
+
import fs from "node:fs/promises";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { CliError, EXIT } from "./cli/exit.js";
|
|
17
|
+
import { CONFIG_FILE, SiteConfigError } from "./config.js";
|
|
18
|
+
import { fromTemplate } from "./templates.js";
|
|
19
|
+
const FORGES = ["github", "gitlab"];
|
|
20
|
+
/** Fill every gap from the defaults documented on [`CiConfig`]. */
|
|
21
|
+
export function resolveCi(ci) {
|
|
22
|
+
return {
|
|
23
|
+
forge: ci?.forge ?? "github",
|
|
24
|
+
nodeVersion: ci?.nodeVersion ?? "22",
|
|
25
|
+
enrich: ci?.enrich ?? true,
|
|
26
|
+
grimVersion: ci?.grimVersion ?? "latest",
|
|
27
|
+
allowManualEdits: ci?.allowManualEdits ?? false,
|
|
28
|
+
autoMerge: ci?.autoMerge ?? false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function fail(msg) {
|
|
32
|
+
throw new SiteConfigError(`${CONFIG_FILE}: ci.${msg}`);
|
|
33
|
+
}
|
|
34
|
+
/** Validate the `ci` block of an already-parsed config object. */
|
|
35
|
+
export function validateCi(raw) {
|
|
36
|
+
if (raw === undefined || raw === null)
|
|
37
|
+
return {};
|
|
38
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
39
|
+
throw new SiteConfigError(`${CONFIG_FILE}: ci must be an object`);
|
|
40
|
+
}
|
|
41
|
+
const ci = raw;
|
|
42
|
+
if (ci.forge !== undefined && !FORGES.includes(ci.forge)) {
|
|
43
|
+
fail(`forge must be one of ${FORGES.join(", ")}`);
|
|
44
|
+
}
|
|
45
|
+
// These land inside a double-quoted shell word or a docker image tag in the
|
|
46
|
+
// generated YAML. The repository owner writes this file, so it is a paste
|
|
47
|
+
// guard rather than a trust boundary — but a value carrying a quote or a `$`
|
|
48
|
+
// would rewrite the command around it, and no version string needs either.
|
|
49
|
+
for (const key of ["nodeVersion", "grimVersion"]) {
|
|
50
|
+
const value = ci[key];
|
|
51
|
+
if (value !== undefined && typeof value !== "string")
|
|
52
|
+
fail(`${key} must be a string`);
|
|
53
|
+
if (typeof value === "string" && !/^[\w.@^~><=* +-]+$/.test(value)) {
|
|
54
|
+
fail(`${key} must be a plain version or range`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const key of ["enrich", "allowManualEdits", "autoMerge"]) {
|
|
58
|
+
if (ci[key] !== undefined && typeof ci[key] !== "boolean")
|
|
59
|
+
fail(`${key} must be a boolean`);
|
|
60
|
+
}
|
|
61
|
+
// Refused rather than ignored. Rendering GitLab CI while the config asks for
|
|
62
|
+
// auto-merge would leave a repository believing contributions land by
|
|
63
|
+
// themselves, and the honest answer is that they cannot be made to safely
|
|
64
|
+
// there — see the field's doc comment.
|
|
65
|
+
if (ci.autoMerge === true && ci.forge === "gitlab") {
|
|
66
|
+
fail("autoMerge is github-only - the GitLab gate cannot be made tamper-proof");
|
|
67
|
+
}
|
|
68
|
+
return ci;
|
|
69
|
+
}
|
|
70
|
+
/** Read the `ci` block out of `index.config.json`. A missing file means defaults. */
|
|
71
|
+
export async function loadCiConfig(root) {
|
|
72
|
+
let text;
|
|
73
|
+
try {
|
|
74
|
+
text = await fs.readFile(path.join(root, CONFIG_FILE), "utf8");
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (err.code === "ENOENT")
|
|
78
|
+
return {};
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = JSON.parse(text);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
throw new SiteConfigError(`${CONFIG_FILE}: ${err.message}`);
|
|
87
|
+
}
|
|
88
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
89
|
+
throw new SiteConfigError(`${CONFIG_FILE}: must contain a JSON object`);
|
|
90
|
+
}
|
|
91
|
+
return validateCi(parsed.ci);
|
|
92
|
+
}
|
|
93
|
+
// ── Rendering ────────────────────────────────────────────────────────────────
|
|
94
|
+
/**
|
|
95
|
+
* First line of every file this renderer writes. Stale detection keys on it,
|
|
96
|
+
* so a repository's hand-written workflows are never mistaken for ours.
|
|
97
|
+
*
|
|
98
|
+
* Deliberately carries no version stamp. The version that renders these files
|
|
99
|
+
* is the one in the repository's own `package-lock.json`; stamping it here too
|
|
100
|
+
* would be a second copy that goes stale, and every dependency bump would red
|
|
101
|
+
* the drift guard until somebody re-rendered a file whose content had not
|
|
102
|
+
* changed.
|
|
103
|
+
*/
|
|
104
|
+
export const GENERATED_HEADER = "# Generated by @grimoire-rs/indexer";
|
|
105
|
+
/**
|
|
106
|
+
* What CI scaffolded by a pre-header release (<= 0.2.2) looks like: a thin
|
|
107
|
+
* caller pointing back at this package's reusable workflow, or a GitLab
|
|
108
|
+
* remote include of the same. Those files carry no generated header, so
|
|
109
|
+
* without this the reaper was blind to exactly the repositories that most
|
|
110
|
+
* need telling — an index scaffolded with the old `--forge both` resolves to
|
|
111
|
+
* `forge: "github"` today (its config has no `ci` block at all) and would
|
|
112
|
+
* keep serving an orphaned `.gitlab-ci.yml` with nothing to say so.
|
|
113
|
+
*/
|
|
114
|
+
const LEGACY_SIGNATURE = /uses:\s*grimoire-rs\/indexer\/\.github\/workflows\/|raw\.githubusercontent\.com\/grimoire-rs\/indexer\//;
|
|
115
|
+
/** Where each forge's files land. */
|
|
116
|
+
const GITHUB_FILES = {
|
|
117
|
+
pages: ".github/workflows/pages.yml",
|
|
118
|
+
validate: ".github/workflows/validate.yml",
|
|
119
|
+
verify: ".github/workflows/verify-ci.yml",
|
|
120
|
+
};
|
|
121
|
+
const GITLAB_FILE = ".gitlab-ci.yml";
|
|
122
|
+
/**
|
|
123
|
+
* Where the grim release tarball is fetched from. `latest` has its own URL
|
|
124
|
+
* shape on GitHub releases; anything else is a tag. Resolving it here rather
|
|
125
|
+
* than in the generated shell is the point of rendering CI from config: a
|
|
126
|
+
* value that is fixed at render time should not be a branch at run time.
|
|
127
|
+
*/
|
|
128
|
+
function grimReleaseBase(grimVersion) {
|
|
129
|
+
const releases = "https://github.com/grimoire-rs/grimoire/releases";
|
|
130
|
+
return grimVersion === "latest"
|
|
131
|
+
? `${releases}/latest/download`
|
|
132
|
+
: `${releases}/download/${grimVersion}`;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* `paths:` entries that must re-run the drift guard — every file it renders,
|
|
136
|
+
* plus the config they are rendered from. `package-lock.json` is in there
|
|
137
|
+
* because the lock is what decides which renderer produces them.
|
|
138
|
+
*/
|
|
139
|
+
const VERIFY_PATHS = [CONFIG_FILE, "package.json", "package-lock.json", ".github/workflows/**"];
|
|
140
|
+
/**
|
|
141
|
+
* Render the complete CI file set. Nothing touches disk — the caller writes
|
|
142
|
+
* it or diffs it against what is committed.
|
|
143
|
+
*/
|
|
144
|
+
export function renderCi(ci) {
|
|
145
|
+
const base = {
|
|
146
|
+
nodeVersion: ci.nodeVersion,
|
|
147
|
+
grimVersion: ci.grimVersion,
|
|
148
|
+
grimReleaseBase: grimReleaseBase(ci.grimVersion),
|
|
149
|
+
};
|
|
150
|
+
// Rendered first: `render` is single-pass, so a block handed in as a value
|
|
151
|
+
// must already have its own placeholders resolved. The trailing newline is
|
|
152
|
+
// trimmed because the template supplies one on the placeholder's own line —
|
|
153
|
+
// keeping both would leave a stray blank line, or an empty block would not.
|
|
154
|
+
const block = (rel) => fromTemplate(rel, base).replace(/\n$/, "");
|
|
155
|
+
const vars = {
|
|
156
|
+
...base,
|
|
157
|
+
header: fromTemplate("ci/header.txt", base),
|
|
158
|
+
githubEnrichSteps: ci.enrich ? block("ci/github-enrich.yml") : "",
|
|
159
|
+
gitlabEnrichScript: ci.enrich ? block("ci/gitlab-enrich.sh") : "",
|
|
160
|
+
verifyPaths: VERIFY_PATHS.map((entry) => ` - ${entry}`).join("\n"),
|
|
161
|
+
gitlabVerifyJob: ci.allowManualEdits ? "" : block("ci/gitlab-verify.yml"),
|
|
162
|
+
// GitHub only — `validateCi` refuses the GitLab combination outright, so
|
|
163
|
+
// there is no silently-inert case to render around here.
|
|
164
|
+
githubAutoMergeJob: ci.autoMerge ? block("ci/github-automerge.yml") : "",
|
|
165
|
+
};
|
|
166
|
+
// Exactly one trailing newline, whatever the placeholders resolved to. An
|
|
167
|
+
// optional block that renders empty at the end of a file otherwise leaves
|
|
168
|
+
// its own blank line behind, so the same config produced two shapes.
|
|
169
|
+
const file = (rel) => fromTemplate(rel, vars).replace(/\n*$/, "\n");
|
|
170
|
+
const files = new Map();
|
|
171
|
+
if (ci.forge === "github") {
|
|
172
|
+
files.set(GITHUB_FILES.pages, file("ci/github-pages.yml"));
|
|
173
|
+
files.set(GITHUB_FILES.validate, file("ci/github-validate.yml"));
|
|
174
|
+
if (!ci.allowManualEdits) {
|
|
175
|
+
files.set(GITHUB_FILES.verify, file("ci/github-verify-ci.yml"));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
files.set(GITLAB_FILE, file("ci/gitlab-ci.yml"));
|
|
180
|
+
}
|
|
181
|
+
return files;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Canonicalize a generated file for drift comparison.
|
|
185
|
+
*
|
|
186
|
+
* The index repository owns the *pin* on every `uses:` action reference —
|
|
187
|
+
* its own Renovate or Dependabot will bump `uses: owner/action@<ref> # vX`,
|
|
188
|
+
* and the guard must not read that as a hand-edit. Only the `@<ref>` suffix
|
|
189
|
+
* and any trailing version comment are stripped; `owner/action` survives, so
|
|
190
|
+
* swapping in a different action still trips drift.
|
|
191
|
+
*/
|
|
192
|
+
export function normalizeForDrift(content) {
|
|
193
|
+
// Line endings first. Git for Windows installs with `core.autocrlf=true` by
|
|
194
|
+
// default, so an untouched clone has CRLF on disk while the renderer emits
|
|
195
|
+
// LF — every generated file read as drift, and `npm run ci` could not clear
|
|
196
|
+
// it because the rewrite was LF too. The scaffold ships a `.gitattributes`
|
|
197
|
+
// that prevents it; this unsticks the checkouts that already have it.
|
|
198
|
+
return content
|
|
199
|
+
.replace(/\r\n/g, "\n")
|
|
200
|
+
.replace(/^([ \t]*(?:-[ \t]+)?uses:[ \t]*[^@\s]+)@[^\s#]+(?:[ \t]*#[^\n]*)?$/gm, "$1");
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Files on disk that carry our header but that the current config no longer
|
|
204
|
+
* renders — a `forge` narrowed from `both`, or `allowManualEdits` turned on
|
|
205
|
+
* after a guard was already committed. Reported, never deleted: removing a
|
|
206
|
+
* file from somebody's repository is their call, not the renderer's.
|
|
207
|
+
*/
|
|
208
|
+
export async function staleCi(root, files) {
|
|
209
|
+
const candidates = [GITLAB_FILE];
|
|
210
|
+
try {
|
|
211
|
+
for (const entry of await fs.readdir(path.join(root, ".github/workflows"))) {
|
|
212
|
+
if (/\.ya?ml$/.test(entry))
|
|
213
|
+
candidates.push(`.github/workflows/${entry}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
/* no workflows directory — nothing of ours can be stale in it */
|
|
218
|
+
}
|
|
219
|
+
const stale = [];
|
|
220
|
+
for (const relative of candidates) {
|
|
221
|
+
if (files.has(relative))
|
|
222
|
+
continue;
|
|
223
|
+
const content = await fs.readFile(path.join(root, relative), "utf8").catch(() => null);
|
|
224
|
+
if (content === null)
|
|
225
|
+
continue;
|
|
226
|
+
if (content.startsWith(GENERATED_HEADER) || LEGACY_SIGNATURE.test(content)) {
|
|
227
|
+
stale.push(relative);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return stale.sort();
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Write the rendered set, overwriting whatever is there.
|
|
234
|
+
*
|
|
235
|
+
* "Whatever is there" is judged through [`normalizeForDrift`], the same lens
|
|
236
|
+
* the guard uses — not raw bytes. Otherwise the two disagreed: `ci --check`
|
|
237
|
+
* deliberately tolerates a bumped `uses: owner/action@<ref>`, while a plain
|
|
238
|
+
* `npm run ci` rewrote it back to the pin baked into this package, silently
|
|
239
|
+
* undoing every Renovate bump the repository is told it may make. The repo
|
|
240
|
+
* owns its pins; a render must not take them back.
|
|
241
|
+
*/
|
|
242
|
+
export async function writeCi(root, files) {
|
|
243
|
+
const outcomes = [];
|
|
244
|
+
for (const [relative, content] of files) {
|
|
245
|
+
const abs = path.join(root, relative);
|
|
246
|
+
const current = await fs.readFile(abs, "utf8").catch(() => null);
|
|
247
|
+
if (current !== null && normalizeForDrift(current) === normalizeForDrift(content)) {
|
|
248
|
+
outcomes.push({ path: relative, status: "unchanged" });
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
252
|
+
await fs.writeFile(abs, content);
|
|
253
|
+
outcomes.push({ path: relative, status: "written" });
|
|
254
|
+
}
|
|
255
|
+
return outcomes;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Compare the rendered set against what is committed. Missing counts as
|
|
259
|
+
* drift; a generated file the config no longer renders counts as stale.
|
|
260
|
+
* Hints are path-only — the contents of a repository's CI never go to stderr.
|
|
261
|
+
*/
|
|
262
|
+
export async function checkCi(root, files) {
|
|
263
|
+
const outcomes = [];
|
|
264
|
+
for (const [relative, expected] of files) {
|
|
265
|
+
const actual = await fs.readFile(path.join(root, relative), "utf8").catch(() => null);
|
|
266
|
+
const same = actual !== null && normalizeForDrift(actual) === normalizeForDrift(expected);
|
|
267
|
+
outcomes.push({ path: relative, status: same ? "ok" : "drift" });
|
|
268
|
+
}
|
|
269
|
+
for (const relative of await staleCi(root, files)) {
|
|
270
|
+
outcomes.push({ path: relative, status: "stale" });
|
|
271
|
+
}
|
|
272
|
+
return outcomes;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The re-render command a drift message tells the reader to run. It is the
|
|
276
|
+
* repository's own npm script, so it runs the renderer the lock pins rather
|
|
277
|
+
* than whatever `npx` would fetch today.
|
|
278
|
+
*/
|
|
279
|
+
export const RE_RENDER_COMMAND = "npm run ci";
|
|
280
|
+
/** Turn a failed check into the error the CLI exits on. */
|
|
281
|
+
export function driftError(outcomes) {
|
|
282
|
+
const bad = outcomes.filter((outcome) => outcome.status !== "ok");
|
|
283
|
+
return new CliError(`${bad.length} generated CI file(s) do not match ${CONFIG_FILE} — ` +
|
|
284
|
+
`re-run \`${RE_RENDER_COMMAND}\` and commit the result`, EXIT.data);
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=ci.js.map
|
package/dist/ci.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci.js","sourceRoot":"","sources":["../src/ci.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/cli/build.ts"],"names":[],"mappings":"AAQA,OAAO,
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/cli/build.ts"],"names":[],"mappings":"AAQA,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGhD,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkB9E"}
|
package/dist/cli/build.js
CHANGED
|
@@ -4,17 +4,11 @@
|
|
|
4
4
|
// site over it. Order is load-bearing: `compileIndex` clears `outDir` and
|
|
5
5
|
// writes `all.json`, which the renderer then reads.
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import {
|
|
7
|
+
import { EXIT } from "./exit.js";
|
|
8
|
+
import { resolveOutDir } from "./out_dir.js";
|
|
8
9
|
export async function build(root, flags) {
|
|
9
10
|
const rootDir = path.resolve(root);
|
|
10
|
-
const outDir =
|
|
11
|
-
// `compileIndex` starts by removing `outDir` outright, so an out-dir that
|
|
12
|
-
// is the repo root — or an ancestor of it — would delete the index it is
|
|
13
|
-
// about to compile.
|
|
14
|
-
if (outDir === rootDir || rootDir.startsWith(outDir + path.sep)) {
|
|
15
|
-
throw new CliError(`--out-dir ${JSON.stringify(outDir)} contains the index root ${JSON.stringify(rootDir)}; ` +
|
|
16
|
-
`the build would delete it`, EXIT.usage);
|
|
17
|
-
}
|
|
11
|
+
const outDir = resolveOutDir(rootDir, flags.outDir);
|
|
18
12
|
const [{ loadConfig }, { compileIndex }, { buildSite }] = await Promise.all([
|
|
19
13
|
import("../config.js"),
|
|
20
14
|
import("../data/index.js"),
|
package/dist/cli/build.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/cli/build.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,sCAAsC;AAEtC,0EAA0E;AAC1E,0EAA0E;AAC1E,oDAAoD;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/cli/build.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,sCAAsC;AAEtC,0EAA0E;AAC1E,0EAA0E;AAC1E,oDAAoD;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,IAAI,EAAiB,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAM7C,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAY,EAAE,KAAiB;IACzD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAEpD,MAAM,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC1E,MAAM,CAAC,cAAc,CAAC;QACtB,MAAM,CAAC,kBAAkB,CAAC;QAC1B,MAAM,CAAC,sBAAsB,CAAC;KAC/B,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAC5E,MAAM,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAEnD,OAAO,CAAC,GAAG,CACT,GAAG,KAAK,sBAAsB,UAAU,CAAC,MAAM,oBAAoB,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,IAAI,MAAM,EAAE,CACpH,CAAC;IACF,OAAO,IAAI,CAAC,EAAE,CAAC;AACjB,CAAC"}
|
package/dist/cli/ci.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci.d.ts","sourceRoot":"","sources":["../../src/cli/ci.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEhD,MAAM,WAAW,OAAO;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAMD,wBAAsB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA+BxE"}
|
package/dist/cli/ci.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 The Grimoire Authors
|
|
3
|
+
// `grim-indexer ci` — render this repository's CI from `index.config.json`,
|
|
4
|
+
// or (`--check`) verify that what is committed still matches. The generated
|
|
5
|
+
// `verify-ci` job runs the second form, which is what keeps a hand-edit from
|
|
6
|
+
// silently forking the pipeline.
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { checkCi, driftError, loadCiConfig, renderCi, RE_RENDER_COMMAND, resolveCi, staleCi, writeCi, } from "../ci.js";
|
|
9
|
+
import { CONFIG_FILE } from "../config.js";
|
|
10
|
+
import { EXIT } from "./exit.js";
|
|
11
|
+
function report(outcome) {
|
|
12
|
+
console.log(` ${outcome.status.padEnd(12)}${outcome.path}`);
|
|
13
|
+
}
|
|
14
|
+
export async function ci(root, flags) {
|
|
15
|
+
const rootDir = path.resolve(root);
|
|
16
|
+
const resolved = resolveCi(await loadCiConfig(rootDir));
|
|
17
|
+
const files = renderCi(resolved);
|
|
18
|
+
if (flags.check) {
|
|
19
|
+
const outcomes = await checkCi(rootDir, files);
|
|
20
|
+
for (const outcome of outcomes) {
|
|
21
|
+
// Path-only, on stderr: the contents of a repository's CI are not this
|
|
22
|
+
// command's to print, and a log is the wrong place for them.
|
|
23
|
+
if (outcome.status !== "ok")
|
|
24
|
+
console.error(`${outcome.status}: ${outcome.path}`);
|
|
25
|
+
}
|
|
26
|
+
if (outcomes.some((outcome) => outcome.status !== "ok"))
|
|
27
|
+
throw driftError(outcomes);
|
|
28
|
+
console.log(`${files.size} generated CI file(s) match ${CONFIG_FILE}`);
|
|
29
|
+
return EXIT.ok;
|
|
30
|
+
}
|
|
31
|
+
const written = await writeCi(rootDir, files);
|
|
32
|
+
written.forEach(report);
|
|
33
|
+
const stale = await staleCi(rootDir, files);
|
|
34
|
+
for (const relative of stale) {
|
|
35
|
+
report({ path: relative, status: "stale" });
|
|
36
|
+
}
|
|
37
|
+
if (stale.length > 0) {
|
|
38
|
+
console.error(`\n${stale.length} generated file(s) are no longer rendered by ${CONFIG_FILE} — ` +
|
|
39
|
+
`delete them, or \`${RE_RENDER_COMMAND}:check\` keeps failing`);
|
|
40
|
+
}
|
|
41
|
+
return EXIT.ok;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=ci.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci.js","sourceRoot":"","sources":["../../src/cli/ci.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,sCAAsC;AAEtC,4EAA4E;AAC5E,4EAA4E;AAC5E,6EAA6E;AAC7E,iCAAiC;AACjC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EACL,OAAO,EACP,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,OAAO,EACP,OAAO,GAER,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAiB,MAAM,WAAW,CAAC;AAMhD,SAAS,MAAM,CAAC,OAAkB;IAChC,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAY,EAAE,KAAc;IACnD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEjC,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC/C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,uEAAuE;YACvE,6DAA6D;YAC7D,IAAI,OAAO,CAAC,MAAM,KAAK,IAAI;gBAAE,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC;YAAE,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;QACpF,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,+BAA+B,WAAW,EAAE,CAAC,CAAC;QACvE,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC9C,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAExB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC5C,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;QAC7B,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CACX,KAAK,KAAK,CAAC,MAAM,gDAAgD,WAAW,KAAK;YAC/E,qBAAqB,iBAAiB,wBAAwB,CACjE,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC,EAAE,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/cli/dev.ts"],"names":[],"mappings":"AASA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAG1D,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAgBD,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoC1E"}
|