@heroiclands/package-build 23.12.3 → 23.12.4
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 +10 -0
- package/ci/ci-steps.mjs +5 -5
- package/content-config.mjs +19 -0
- package/docs/commands.md +5 -4
- package/docs/configuration.md +27 -12
- package/docs/guides/book.md +8 -6
- package/docs/guides/release-and-deploy.md +6 -9
- package/docs/project-setup.md +11 -11
- package/engine/pdf-build.mjs +1 -0
- package/engine/pdf-index.mjs +22 -8
- package/engine/pdf-pages.mjs +11 -2
- package/engine/pdf-render.mjs +20 -23
- package/engine/project-init.mjs +2 -2
- package/githooks/pre-push +5 -5
- package/package.json +1 -1
- package/types/content-config.d.mts +5 -0
- package/types/engine/pdf-index.d.mts +7 -3
- package/types/engine/pdf-pages.d.mts +2 -1
- package/types/engine/pdf-render.d.mts +8 -5
- package/ci/confirm-npm-version.mjs +0 -135
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 23.12.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
A PDF book may declare a short `abbreviation`, so references into it read `(Aelw. p307)` instead of naming the full title, and the other books' indexes list its notes the same way.
|
|
8
|
+
|
|
9
|
+
In PDF books, a definition list sets its term in bold at the start of the line with the description running on after it, indented on later lines, and no space appears between a linked name and the punctuation that follows it.
|
|
10
|
+
|
|
11
|
+
`project init` scaffolds the build workflow at `.gitea/workflows/build.yml`, and `project check` and `package-build ci` read it from there.
|
|
12
|
+
|
|
3
13
|
## 23.12.3
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/ci/ci-steps.mjs
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* of the command list is a second statement of one thing, and the copy is the
|
|
19
19
|
* one that goes stale — silently, because a pre-push check that runs four of
|
|
20
20
|
* five steps still exits 0. So the steps are parsed out of
|
|
21
|
-
* `.
|
|
21
|
+
* `.gitea/workflows/build.yml` at run time and there is nothing to keep in
|
|
22
22
|
* step.
|
|
23
23
|
*
|
|
24
24
|
* **It refuses rather than assumes.** If the workflow's shape changes so that
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
*
|
|
29
29
|
* **What it cannot run**, and says so: a `uses:` step is a published action, not
|
|
30
30
|
* a command — the forbidden-marker check, the coverage upload. Those stay
|
|
31
|
-
*
|
|
31
|
+
* CI's to run, and the summary names them so the gap is visible rather than
|
|
32
32
|
* assumed away.
|
|
33
33
|
*
|
|
34
34
|
* **It parses the workflow itself rather than importing a YAML library**, for a
|
|
@@ -64,7 +64,7 @@ const ROOT = process.cwd();
|
|
|
64
64
|
* failed*. Mirrors the same constant in `ci-docker.mjs`.
|
|
65
65
|
*/
|
|
66
66
|
export const UNAVAILABLE = 2;
|
|
67
|
-
const WORKFLOW_DIR = path.join(ROOT, ".
|
|
67
|
+
const WORKFLOW_DIR = path.join(ROOT, ".gitea/workflows");
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* The workflow's steps, split into what can be run here and what cannot.
|
|
@@ -77,7 +77,7 @@ export function ciSteps() {
|
|
|
77
77
|
const files = prTriggeredWorkflows();
|
|
78
78
|
if (!files.length) {
|
|
79
79
|
throw new NoWorkflow(
|
|
80
|
-
`no workflow under .
|
|
80
|
+
`no workflow under .gitea/workflows is triggered by \`pull_request\`, ` +
|
|
81
81
|
`so there is nothing here that a pull request would run.`,
|
|
82
82
|
);
|
|
83
83
|
}
|
|
@@ -236,7 +236,7 @@ function main() {
|
|
|
236
236
|
});
|
|
237
237
|
if (result.status !== 0) {
|
|
238
238
|
console.error(
|
|
239
|
-
`\nci-steps: FAILED at "${step.name}" — this is what
|
|
239
|
+
`\nci-steps: FAILED at "${step.name}" — this is what CI would report.\n` +
|
|
240
240
|
` Fix it, or push with --no-verify if you mean to.`,
|
|
241
241
|
);
|
|
242
242
|
return result.status ?? 1;
|
package/content-config.mjs
CHANGED
|
@@ -787,6 +787,7 @@ const PDF_KEYS = [
|
|
|
787
787
|
const PDF_BOOK_KEYS = [
|
|
788
788
|
"id",
|
|
789
789
|
"title",
|
|
790
|
+
"abbreviation",
|
|
790
791
|
"subtitle",
|
|
791
792
|
"coverImage",
|
|
792
793
|
"front",
|
|
@@ -1813,6 +1814,8 @@ function normalizeSite(value) {
|
|
|
1813
1814
|
* @typedef {object} ResolvedPdfBook
|
|
1814
1815
|
* @property {string} id Its name on the command line, `--book <id>`.
|
|
1815
1816
|
* @property {string} title
|
|
1817
|
+
* @property {string} abbreviation Names the book in another book's
|
|
1818
|
+
* references; `""` when none is declared.
|
|
1816
1819
|
* @property {string} subtitle `""` when none is declared.
|
|
1817
1820
|
* @property {string} coverImage Absolute; `""` when none is declared.
|
|
1818
1821
|
* Whether the file exists is checked when the
|
|
@@ -1984,6 +1987,8 @@ function normalizePdfBooks(value, rootDir, contentPackage) {
|
|
|
1984
1987
|
const ids = new Set();
|
|
1985
1988
|
/** @type {Map<string, number>} Stem → the book that took it. */
|
|
1986
1989
|
const stems = new Map();
|
|
1990
|
+
/** @type {Map<string, string>} Abbreviation → the id of the book that took it. */
|
|
1991
|
+
const abbreviations = new Map();
|
|
1987
1992
|
return Object.freeze(
|
|
1988
1993
|
value.map((entry, i) => {
|
|
1989
1994
|
const where = `pdf.books[${i}]`;
|
|
@@ -2003,6 +2008,19 @@ function normalizePdfBooks(value, rootDir, contentPackage) {
|
|
|
2003
2008
|
if (ids.has(id)) fail(`${where}.id`, `is \`${id}\`, which names a book more than once`);
|
|
2004
2009
|
ids.add(id);
|
|
2005
2010
|
const title = requireNonEmptyString(book.title, `${where}.title`);
|
|
2011
|
+
let abbreviation = "";
|
|
2012
|
+
if (book.abbreviation !== undefined) {
|
|
2013
|
+
abbreviation = requireNonEmptyString(book.abbreviation, `${where}.abbreviation`);
|
|
2014
|
+
const holder = abbreviations.get(abbreviation);
|
|
2015
|
+
if (holder !== undefined) {
|
|
2016
|
+
fail(
|
|
2017
|
+
`${where}.abbreviation`,
|
|
2018
|
+
`is \`${abbreviation}\`, which books \`${holder}\` and \`${id}\` both ` +
|
|
2019
|
+
"declare — a reader could not tell which book to open",
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
abbreviations.set(abbreviation, id);
|
|
2023
|
+
}
|
|
2006
2024
|
|
|
2007
2025
|
// One tree, from exactly one place: two would leave the reader to
|
|
2008
2026
|
// guess which is printed, and none prints nothing.
|
|
@@ -2059,6 +2077,7 @@ function normalizePdfBooks(value, rootDir, contentPackage) {
|
|
|
2059
2077
|
return Object.freeze({
|
|
2060
2078
|
id,
|
|
2061
2079
|
title,
|
|
2080
|
+
abbreviation,
|
|
2062
2081
|
subtitle:
|
|
2063
2082
|
book.subtitle === undefined ?
|
|
2064
2083
|
""
|
package/docs/commands.md
CHANGED
|
@@ -128,16 +128,16 @@ package-build ci [--native]
|
|
|
128
128
|
|
|
129
129
|
The default command runs in Docker on Linux against a clean export of the
|
|
130
130
|
current commit. It reads the repository's pull-request workflows under
|
|
131
|
-
`.
|
|
131
|
+
`.gitea/workflows/` and runs their `run:` steps in workflow order. A failed
|
|
132
132
|
step stops the replay. Uncommitted changes are absent from the clean export.
|
|
133
133
|
|
|
134
134
|
`--native` runs the same steps on the host in the current working tree. It
|
|
135
135
|
needs no Docker and includes uncommitted changes, but its operating system and
|
|
136
|
-
existing files can differ from a clean
|
|
136
|
+
existing files can differ from a clean CI runner. The steps themselves
|
|
137
137
|
still come from the workflow files.
|
|
138
138
|
|
|
139
139
|
The command lists `uses:` steps that it cannot run locally. A successful replay
|
|
140
|
-
covers the listed `run:` steps;
|
|
140
|
+
covers the listed `run:` steps; the forge runs the published actions separately.
|
|
141
141
|
Workflow steps may install dependencies, generate files, or perform other
|
|
142
142
|
declared build actions.
|
|
143
143
|
|
|
@@ -2632,7 +2632,8 @@ Typst compiler; `--no-compile` emits the Typst source and stops there,
|
|
|
2632
2632
|
which is what lets the source be inspected or compiled by hand.
|
|
2633
2633
|
|
|
2634
2634
|
A reference to a note that another book is home to prints that book and page,
|
|
2635
|
-
`Name (_Book title_, p. 12)
|
|
2635
|
+
`Name (_Book title_, p. 12)`, or `Name (Aelw. p12)` when that book declares an
|
|
2636
|
+
`abbreviation`. A compile renders and lays out every book, writes
|
|
2636
2637
|
`pages.json` (each note's home book and the page its entry starts on) into the
|
|
2637
2638
|
output directory, and renders again with those pages; `--no-compile` runs
|
|
2638
2639
|
neither pass and prints `p. ?`. See the
|
package/docs/configuration.md
CHANGED
|
@@ -1156,6 +1156,7 @@ pdf:
|
|
|
1156
1156
|
books:
|
|
1157
1157
|
- id: core
|
|
1158
1158
|
title: The World of Thalorna
|
|
1159
|
+
abbreviation: Thal.
|
|
1159
1160
|
subtitle: A Song of Heroic Lands Sourcebook
|
|
1160
1161
|
coverImage: assets/images/covers/core.webp
|
|
1161
1162
|
document: book.yaml
|
|
@@ -1209,7 +1210,8 @@ The keys of `pdf` itself are shared by every book:
|
|
|
1209
1210
|
|
|
1210
1211
|
- `pdf.indexSeeAlso` adds a line to a book's index for each note that another
|
|
1211
1212
|
book prints and this book mentions in authored prose, written
|
|
1212
|
-
`Name: _Book title_, p. N`
|
|
1213
|
+
`Name: _Book title_, p. N` (or `Name: Aelw. p307` when that book declares an
|
|
1214
|
+
`abbreviation`) with the page the note's entry starts on in its
|
|
1213
1215
|
home book. It is `false` by default, and the index then lists only the notes
|
|
1214
1216
|
the book is home to. See [The index](guides/book.md#the-index).
|
|
1215
1217
|
- `pdf.indexHeads` lists leading words that open many names, such as
|
|
@@ -1236,20 +1238,29 @@ Targud` is also listed as `Targud, Kingdom of`, with the same pages as the
|
|
|
1236
1238
|
|
|
1237
1239
|
Each entry of `pdf.books` is one PDF:
|
|
1238
1240
|
|
|
1239
|
-
| Key
|
|
1240
|
-
|
|
|
1241
|
-
| `pdf.books[].id`
|
|
1242
|
-
| `pdf.books[].title`
|
|
1243
|
-
| `pdf.books[].
|
|
1244
|
-
| `pdf.books[].
|
|
1245
|
-
| `pdf.books[].
|
|
1246
|
-
| `pdf.books[].
|
|
1247
|
-
| `pdf.books[].
|
|
1248
|
-
| `pdf.books[].
|
|
1241
|
+
| Key | Type | Required | Default |
|
|
1242
|
+
| -------------------------- | ------------------------- | -------------- | ------------------------- |
|
|
1243
|
+
| `pdf.books[].id` | string | yes | — |
|
|
1244
|
+
| `pdf.books[].title` | string | yes | — |
|
|
1245
|
+
| `pdf.books[].abbreviation` | string | no | `""` — the title names it |
|
|
1246
|
+
| `pdf.books[].subtitle` | string | no | `""` |
|
|
1247
|
+
| `pdf.books[].coverImage` | string (path) | no | `""` — no cover |
|
|
1248
|
+
| `pdf.books[].front` | string[] (markdown files) | no | `[]` |
|
|
1249
|
+
| `pdf.books[].out` | string (file stem) | no | `<contentPackage>-<id>` |
|
|
1250
|
+
| `pdf.books[].contents` | list (the tree, inline) | one of the two | — |
|
|
1251
|
+
| `pdf.books[].document` | string (path to the tree) | one of the two | — |
|
|
1249
1252
|
|
|
1250
1253
|
- `id` is lowercase letters, digits and hyphens, starting with a letter or
|
|
1251
1254
|
digit. It is what `--book <id>` names and the tail of the default file stem.
|
|
1252
1255
|
Each `id` is used once.
|
|
1256
|
+
- `abbreviation` is a short name for the book, such as `Aelw.`. It is used only
|
|
1257
|
+
where another book refers to this one: a reference to a note this book
|
|
1258
|
+
prints reads `Name (Aelw. p307)`, with a non-breaking space between the
|
|
1259
|
+
abbreviation and the page, and the other book's index, when
|
|
1260
|
+
`pdf.indexSeeAlso` is on, lists the note as `Name: Aelw. p307`. A book
|
|
1261
|
+
with no abbreviation is named by its full title in italics, `Name (_Book
|
|
1262
|
+
title_, p. 307)`. Each abbreviation is used once, since a reader could not
|
|
1263
|
+
tell which book to open from one shared by two.
|
|
1253
1264
|
- `contents` is the tree written inline — the same `sectionName` /
|
|
1254
1265
|
`contents` / `filter` / `file` shape a tree file holds under its own
|
|
1255
1266
|
`contents:` key — and `document` names the file holding it. A book declares
|
|
@@ -1296,6 +1307,10 @@ file, line and column the key is written at:
|
|
|
1296
1307
|
|
|
1297
1308
|
> ``package-build config: `pdf.books[0].title` must be a non-empty string.``
|
|
1298
1309
|
|
|
1310
|
+
> ``package-build config: `pdf.books[0].abbreviation` must be a non-empty string.``
|
|
1311
|
+
|
|
1312
|
+
> ``package-build config: `pdf.books[1].abbreviation` is `Thal.`, which books `core` and `vedyara` both declare — a reader could not tell which book to open.``
|
|
1313
|
+
|
|
1299
1314
|
> ``package-build config: `pdf.books[0]` must declare exactly one of `contents` and `document` — the tree written inline, or the file holding it.``
|
|
1300
1315
|
|
|
1301
1316
|
> ``package-build config: `pdf.books[0].out` is `dist/core`, which is not a file stem — letters, digits, `.`, `_` and `-`, with no directory; the directory is `pdf.out`.``
|
|
@@ -1304,7 +1319,7 @@ file, line and column the key is written at:
|
|
|
1304
1319
|
|
|
1305
1320
|
> ``package-build config: `pdf.books[0].front` must be a list of markdown files.``
|
|
1306
1321
|
|
|
1307
|
-
> ``package-build config: `pdf.books[0].<key>` is not a recognized option (expected one of: id, title, subtitle, coverImage, front, out, contents, document).``
|
|
1322
|
+
> ``package-build config: `pdf.books[0].<key>` is not a recognized option (expected one of: id, title, abbreviation, subtitle, coverImage, front, out, contents, document).``
|
|
1308
1323
|
|
|
1309
1324
|
> ``package-build config: `pdf.coverage` must be one of: warning, error, off.``
|
|
1310
1325
|
|
package/docs/guides/book.md
CHANGED
|
@@ -174,11 +174,11 @@ the Lyrebird Inn (The Tenebrae Volume, p. 214)
|
|
|
174
174
|
|
|
175
175
|
Which form a wikilink takes depends on where its target is printed:
|
|
176
176
|
|
|
177
|
-
| The target is | The link prints
|
|
178
|
-
| ------------------------------------------------------------- |
|
|
179
|
-
| An entry of the same book, including a note the book reprints | An internal destination
|
|
180
|
-
| An entry of another book, and not of this one | `Name (_Book title_, p. N)`, with no link |
|
|
181
|
-
| A note in no book | The web address of the note's page
|
|
177
|
+
| The target is | The link prints |
|
|
178
|
+
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
179
|
+
| An entry of the same book, including a note the book reprints | An internal destination |
|
|
180
|
+
| An entry of another book, and not of this one | `Name (_Book title_, p. N)`, or `Name (Aelw. pN)` with an abbreviation, with no link |
|
|
181
|
+
| A note in no book | The web address of the note's page |
|
|
182
182
|
|
|
183
183
|
The page is the page the PDF itself numbers, which is the number printed in the
|
|
184
184
|
footer and used by the contents and the index, so the printed `p. N` is the
|
|
@@ -438,7 +438,7 @@ first book in `pdf.books` that selects it. In a package of several books:
|
|
|
438
438
|
as the cross-book references (`pages.json`), so the page a reference prints
|
|
439
439
|
for a note is the page its home book's index lists for the note's own entry.
|
|
440
440
|
- A mention of a note another book prints is not a term of the mentioning
|
|
441
|
-
book. The mention reads `Name (_Book title_, p. N)` in the text, and the note's
|
|
441
|
+
book. The mention reads `Name (_Book title_, p. N)` in the text (`Name (Aelw. pN)` when the home book declares an `abbreviation`), and the note's
|
|
442
442
|
entry and its page are in the other book's index.
|
|
443
443
|
|
|
444
444
|
Set [`pdf.indexSeeAlso`](../configuration.md) to `true` to list those mentions
|
|
@@ -447,6 +447,8 @@ then has a line, filed under its name:
|
|
|
447
447
|
|
|
448
448
|
> Sword: _Book A_, p. 12
|
|
449
449
|
|
|
450
|
+
A home book with an `abbreviation` files the line as `Sword: BkA. p12`.
|
|
451
|
+
|
|
450
452
|
Two kinds of link add nothing:
|
|
451
453
|
|
|
452
454
|
- **Links in generated sections** such as "Within" or "Governed by". They name
|
|
@@ -182,17 +182,14 @@ release runs in two halves, one on each:
|
|
|
182
182
|
pushes the tag `v<version>` at the merged version commit. Releasing from
|
|
183
183
|
Gitea is switched on by the repository variable `HL_RELEASE_ENABLED`.
|
|
184
184
|
2. **Publish, on GitHub.** The push mirror carries the tag to GitHub, where it
|
|
185
|
-
starts the **Publish to npm** workflow, `.github/workflows/release.yml
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
changelog section, unless one exists.
|
|
185
|
+
starts the **Publish to npm** workflow, `.github/workflows/release.yml`, the
|
|
186
|
+
only workflow GitHub runs. Every check has already run on Gitea before the
|
|
187
|
+
tag exists, so the workflow installs, publishes through npm Trusted
|
|
188
|
+
Publishing with provenance, and creates the GitHub Release from the
|
|
189
|
+
version's changelog section.
|
|
191
190
|
|
|
192
191
|
Nothing on Gitea publishes to npm, and nothing on GitHub runs for a branch
|
|
193
|
-
push.
|
|
194
|
-
dispatching it with the tag as its ref, finishes a release whose publish or
|
|
195
|
-
Release step failed and changes nothing for one that completed. The
|
|
192
|
+
push. The
|
|
196
193
|
[npm package page](https://www.npmjs.com/package/@heroiclands/package-build)
|
|
197
194
|
shows the published version and README.
|
|
198
195
|
|
package/docs/project-setup.md
CHANGED
|
@@ -513,7 +513,7 @@ tool:
|
|
|
513
513
|
organisation-level and defined in `HeroicLands/.github`, so the repository
|
|
514
514
|
carries no label registry of its own.
|
|
515
515
|
|
|
516
|
-
`.
|
|
516
|
+
`.gitea/workflows/` carries the repository's own CI. The org-wide
|
|
517
517
|
`HeroicLands/.github` repository supplies the shared composite actions those
|
|
518
518
|
workflows `uses:` — `labels`, `no-attribution` and `todos` — plus the reusable
|
|
519
519
|
site-deploy workflow, so a fix to any of them reaches every repository at once.
|
|
@@ -522,16 +522,16 @@ site-deploy workflow, so a fix to any of them reaches every repository at once.
|
|
|
522
522
|
|
|
523
523
|
A summary, because "where does this come from" is the question that recurs.
|
|
524
524
|
|
|
525
|
-
| Read
|
|
526
|
-
|
|
|
527
|
-
| `package.json`
|
|
528
|
-
| `package-build.config.yaml`
|
|
529
|
-
| `assets/content/**/*.md`
|
|
530
|
-
| `.gitignore`
|
|
531
|
-
| `.prettierignore`
|
|
532
|
-
| `lang/*.json`
|
|
533
|
-
| `.env.local`
|
|
534
|
-
| `.
|
|
525
|
+
| Read | By |
|
|
526
|
+
| ---------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
527
|
+
| `package.json` | The Foundry package id, the version, the release addresses; the site's `baseURL`, description and author. |
|
|
528
|
+
| `package-build.config.yaml` | Everything else about the build. |
|
|
529
|
+
| `assets/content/**/*.md` | Every content command. |
|
|
530
|
+
| `.gitignore` | `package-build format`, `package-build markdown`. |
|
|
531
|
+
| `.prettierignore` | `package-build format`. |
|
|
532
|
+
| `lang/*.json` | `package-build lang`. |
|
|
533
|
+
| `.env.local` | `package-build deploy`. |
|
|
534
|
+
| `.gitea/workflows/build.yml` | The `pre-push` hook, for its step list. |
|
|
535
535
|
|
|
536
536
|
Everything written goes under `build/`. Nothing the toolchain generates is
|
|
537
537
|
committed.
|
package/engine/pdf-build.mjs
CHANGED
package/engine/pdf-index.mjs
CHANGED
|
@@ -102,11 +102,13 @@ function letterOf(key) {
|
|
|
102
102
|
* @param {string[]} [opts.heads] - Leading words (`pdf.indexHeads`) matched
|
|
103
103
|
* literally against the start of a name; a name that opens with one is also
|
|
104
104
|
* listed as `<rest>, <head>`, the head without its trailing space or colon. None when absent.
|
|
105
|
-
* @param {Array<{slug: string, name: string, title: string,
|
|
105
|
+
* @param {Array<{slug: string, name: string, title: string, abbreviation?: string,
|
|
106
|
+
* page: string|number}>} [opts.foreign] -
|
|
106
107
|
* Notes another book prints, listed as `Name: Book title, p. N` once this
|
|
107
|
-
* book mentions them.
|
|
108
|
+
* book mentions them, or `Name: Aelw. p12` when the home book has an
|
|
109
|
+
* abbreviation.
|
|
108
110
|
* @returns {Array<{letter: string, name: string, slug: string, see?: string,
|
|
109
|
-
* foreign?: {title: string, page: string}}>} Terms with `see` set for a
|
|
111
|
+
* foreign?: {title: string, abbreviation?: string, page: string}}>} Terms with `see` set for a
|
|
110
112
|
* cross-reference to the main name it points at.
|
|
111
113
|
*/
|
|
112
114
|
export function indexTerms(entries, { owns = () => true, foreign = [], heads = [] } = {}) {
|
|
@@ -198,7 +200,11 @@ export function indexTerms(entries, { owns = () => true, foreign = [], heads = [
|
|
|
198
200
|
keyed.push({
|
|
199
201
|
name,
|
|
200
202
|
slug: other.slug,
|
|
201
|
-
foreign: {
|
|
203
|
+
foreign: {
|
|
204
|
+
title: String(other.title),
|
|
205
|
+
...(other.abbreviation ? { abbreviation: String(other.abbreviation) } : {}),
|
|
206
|
+
page: String(other.page),
|
|
207
|
+
},
|
|
202
208
|
key: sortKey(name),
|
|
203
209
|
});
|
|
204
210
|
}
|
|
@@ -234,7 +240,7 @@ export function indexTerms(entries, { owns = () => true, foreign = [], heads = [
|
|
|
234
240
|
export function indexPreamble({ seeAlso = false } = {}) {
|
|
235
241
|
return [
|
|
236
242
|
// Zero-size and invisible; the label is what the chapter queries.
|
|
237
|
-
"#let book-ix(key, main: false) = [#metadata((key: key, main: main))
|
|
243
|
+
"#let book-ix(key, main: false) = [#metadata((key: key, main: main))<book-index>]",
|
|
238
244
|
"#let book-index(terms) = context {",
|
|
239
245
|
" set par(justify: false, first-line-indent: 0em, hanging-indent: 1em, spacing: 0.45em)",
|
|
240
246
|
" set text(size: 8.8pt)",
|
|
@@ -271,7 +277,12 @@ export function indexPreamble({ seeAlso = false } = {}) {
|
|
|
271
277
|
...(seeAlso ?
|
|
272
278
|
[
|
|
273
279
|
" if away != none {",
|
|
274
|
-
|
|
280
|
+
' let short = away.at("abbreviation", default: none)',
|
|
281
|
+
" if short != none {",
|
|
282
|
+
" par[#term.name: #short~p#away.page]",
|
|
283
|
+
" } else {",
|
|
284
|
+
" par[#term.name: #emph[#away.title], p. #away.page]",
|
|
285
|
+
" }",
|
|
275
286
|
" continue",
|
|
276
287
|
" }",
|
|
277
288
|
]
|
|
@@ -309,8 +320,11 @@ export function indexChapter(entries, title, opts = {}) {
|
|
|
309
320
|
}${
|
|
310
321
|
term.foreign === undefined ?
|
|
311
322
|
""
|
|
312
|
-
: `, foreign: (title: "${escapeString(term.foreign.title)}",
|
|
313
|
-
|
|
323
|
+
: `, foreign: (title: "${escapeString(term.foreign.title)}", ${
|
|
324
|
+
term.foreign.abbreviation === undefined ?
|
|
325
|
+
""
|
|
326
|
+
: `abbreviation: "${escapeString(term.foreign.abbreviation)}", `
|
|
327
|
+
}` + `page: "${escapeString(term.foreign.page)}")`
|
|
314
328
|
})`,
|
|
315
329
|
);
|
|
316
330
|
return [
|
package/engine/pdf-pages.mjs
CHANGED
|
@@ -142,7 +142,7 @@ export function readEntryPages(typPath, fontArgs, binary = "typst") {
|
|
|
142
142
|
* The books that render, in configuration order.
|
|
143
143
|
* @param {Map<string, string>} input.homes - Each note's key → its home book's
|
|
144
144
|
* id, from {@link module:engine/pdf-coverage.coverage}.
|
|
145
|
-
* @returns {Array<{key: string, book: string, title: string, anchor: string,
|
|
145
|
+
* @returns {Array<{key: string, book: string, title: string, abbreviation?: string, anchor: string,
|
|
146
146
|
* record: object, slug: string, aliases: string[]}>} One per homed note, in book order and
|
|
147
147
|
* then entry order.
|
|
148
148
|
*/
|
|
@@ -160,6 +160,7 @@ export function homeEntries({ books, homes }) {
|
|
|
160
160
|
key,
|
|
161
161
|
book: book.id,
|
|
162
162
|
title: book.title,
|
|
163
|
+
abbreviation: book.abbreviation,
|
|
163
164
|
anchor: entry.anchor,
|
|
164
165
|
record,
|
|
165
166
|
slug: String(record?.address?.slug ?? record?.shortcode ?? ""),
|
|
@@ -246,13 +247,21 @@ export function elsewhereFor({ entries, book, printed, table, seeAlso = false })
|
|
|
246
247
|
const keys = [entry.slug, ...entry.aliases].filter((key) => key && !printed.has(key));
|
|
247
248
|
const page = table?.[entry.key]?.page ?? UNMEASURED_PAGE;
|
|
248
249
|
for (const key of keys) {
|
|
249
|
-
if (!map.has(key))
|
|
250
|
+
if (!map.has(key)) {
|
|
251
|
+
map.set(key, {
|
|
252
|
+
slug: entry.slug,
|
|
253
|
+
title: entry.title,
|
|
254
|
+
abbreviation: entry.abbreviation,
|
|
255
|
+
page,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
250
258
|
}
|
|
251
259
|
if (seeAlso && entry.slug && !printed.has(entry.slug)) {
|
|
252
260
|
listed.push({
|
|
253
261
|
slug: entry.slug,
|
|
254
262
|
name: String(entry.record?.name?.full ?? "").trim(),
|
|
255
263
|
title: entry.title,
|
|
264
|
+
abbreviation: entry.abbreviation,
|
|
256
265
|
page,
|
|
257
266
|
});
|
|
258
267
|
}
|
package/engine/pdf-render.mjs
CHANGED
|
@@ -841,12 +841,10 @@ function renderBlock(tokens, i, out, ctx) {
|
|
|
841
841
|
}
|
|
842
842
|
|
|
843
843
|
/**
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
* subsequent paragraphs, lists and blocks retain the definition's inset. A long
|
|
849
|
-
* term enlarges its slot without changing the continuation margin.
|
|
844
|
+
* Definition rows set as a book sets them: the term in bold, run in at the
|
|
845
|
+
* margin, and the definition following on the same line, with its continuation
|
|
846
|
+
* lines and any further paragraphs, lists and blocks indented 1em. A long term
|
|
847
|
+
* takes no more room than a short one. Rows are spaced as paragraphs are.
|
|
850
848
|
*
|
|
851
849
|
* Scope the helper to this list so exported Markdown fragments also work without
|
|
852
850
|
* a book preamble, including nested definition lists.
|
|
@@ -854,18 +852,12 @@ function renderBlock(tokens, i, out, ctx) {
|
|
|
854
852
|
* @returns {string} Typst markup.
|
|
855
853
|
*/
|
|
856
854
|
function renderDefinitionList(entries) {
|
|
857
|
-
return `#
|
|
858
|
-
let book-definition(term, body) =
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
block(width: 100%, breakable: true, inset: (left: indent), above: 0pt, below: 0.5em)[
|
|
864
|
-
#set par(first-line-indent: 0pt, hanging-indent: 0pt)
|
|
865
|
-
#set text(weight: "regular")
|
|
866
|
-
#h(-indent)#box(width: slot, label)#h(gap)#body
|
|
867
|
-
]
|
|
868
|
-
}
|
|
855
|
+
return `#{
|
|
856
|
+
let book-definition(term, body) = block(width: 100%, breakable: true, inset: (left: 1em))[
|
|
857
|
+
#set par(first-line-indent: 0pt, hanging-indent: 0pt)
|
|
858
|
+
#set text(weight: "regular")
|
|
859
|
+
#h(-1em)#text(weight: "bold", term)#h(0.5em)#body
|
|
860
|
+
]
|
|
869
861
|
${entries.join("\n ")}
|
|
870
862
|
}`;
|
|
871
863
|
}
|
|
@@ -1457,17 +1449,22 @@ function renderLink(href, inner, ctx) {
|
|
|
1457
1449
|
|
|
1458
1450
|
/**
|
|
1459
1451
|
* A reference to a note another book prints, as Typst: the words the link
|
|
1460
|
-
* carried, then the book and the page
|
|
1452
|
+
* carried, then the book and the page. A book with an abbreviation reads
|
|
1453
|
+
* `Name (Aelw. p12)`; one without reads `Name (_Book title_, p. 12)`.
|
|
1461
1454
|
*
|
|
1462
|
-
* The space before the page number
|
|
1463
|
-
* line of its own.
|
|
1455
|
+
* The space before the page number, or between the abbreviation and the page,
|
|
1456
|
+
* does not break, so a number never starts a line of its own.
|
|
1464
1457
|
*
|
|
1465
1458
|
* @param {string} inner - The already-rendered link text.
|
|
1466
|
-
* @param {{title: string, page: string|number}} away -
|
|
1467
|
-
*
|
|
1459
|
+
* @param {{title: string, abbreviation?: string, page: string|number}} away -
|
|
1460
|
+
* The home book's title, its abbreviation when it declares one, and the page
|
|
1461
|
+
* the entry starts on.
|
|
1468
1462
|
* @returns {string} Typst markup.
|
|
1469
1463
|
*/
|
|
1470
1464
|
export function crossBookReference(inner, away) {
|
|
1465
|
+
if (away.abbreviation) {
|
|
1466
|
+
return `${inner} (${escapeTypst(away.abbreviation)}~p${away.page})`;
|
|
1467
|
+
}
|
|
1471
1468
|
return `${inner} (#emph[${escapeTypst(away.title)}], p.~${away.page})`;
|
|
1472
1469
|
}
|
|
1473
1470
|
|
package/engine/project-init.mjs
CHANGED
|
@@ -308,7 +308,7 @@ function projectText(answers) {
|
|
|
308
308
|
"Labels are organization-level, defined in `HeroicLands/.github`.",
|
|
309
309
|
"",
|
|
310
310
|
].join("\n"),
|
|
311
|
-
".
|
|
311
|
+
".gitea/workflows/build.yml": [
|
|
312
312
|
"# SPDX-License-Identifier: GPL-3.0-or-later",
|
|
313
313
|
"name: Build and Test",
|
|
314
314
|
"on:",
|
|
@@ -424,7 +424,7 @@ export function checkProject(rootDir) {
|
|
|
424
424
|
"LICENSE.md",
|
|
425
425
|
".changeset/config.json",
|
|
426
426
|
".github/ISSUE_REPORTING.md",
|
|
427
|
-
".
|
|
427
|
+
".gitea/workflows/build.yml",
|
|
428
428
|
];
|
|
429
429
|
for (const file of required) {
|
|
430
430
|
if (!fs.existsSync(path.join(root, file))) add(file, "required project file is missing");
|
package/githooks/pre-push
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env sh
|
|
2
2
|
#
|
|
3
3
|
# Before pushing, run exactly what the Build & Test workflow runs — in a
|
|
4
|
-
# container, over a clean export of HEAD — so a red
|
|
4
|
+
# container, over a clean export of HEAD — so a red CI check is something
|
|
5
5
|
# you chose rather than something you discover.
|
|
6
6
|
#
|
|
7
7
|
# The steps are not listed here. `ci/ci-steps.mjs` reads them out of
|
|
8
|
-
# `.
|
|
8
|
+
# `.gitea/workflows/build.yml`, because a hook holding its own copy of the
|
|
9
9
|
# command list is a second statement of one thing, and the copy is the one that
|
|
10
10
|
# goes stale — silently, since running four of five steps still exits 0.
|
|
11
11
|
#
|
|
12
12
|
# **On push rather than at `gh pr create`**, because a push is where this
|
|
13
|
-
# repository's work reaches
|
|
13
|
+
# repository's work reaches the forge: normally one, just before the pull request.
|
|
14
14
|
# Nothing to remember, no wrapper command, and no entry in package.json — the
|
|
15
15
|
# hook is the whole mechanism.
|
|
16
16
|
#
|
|
@@ -92,7 +92,7 @@ status=$?
|
|
|
92
92
|
# 2 means the check could not run — no Docker — which is not the same as a
|
|
93
93
|
# failure and must not stop the push. Whoever is pushing may not be the
|
|
94
94
|
# maintainer, may be on a machine without Docker, and is entitled to open a
|
|
95
|
-
# pull request;
|
|
95
|
+
# pull request; CI is what enforces the workflow. Said loudly, though: a
|
|
96
96
|
# skipped check that reads like a passed one is how a guard loses its meaning.
|
|
97
97
|
# 127 is "could not execute it at all" — no node on PATH, package not
|
|
98
98
|
# installed. Infrastructure, not a verdict, so it is handled the same way.
|
|
@@ -104,6 +104,6 @@ fi
|
|
|
104
104
|
|
|
105
105
|
if [ "$status" -ne 0 ]; then
|
|
106
106
|
echo ""
|
|
107
|
-
echo "pre-push: refusing to push —
|
|
107
|
+
echo "pre-push: refusing to push — CI would report the same failure."
|
|
108
108
|
exit 1
|
|
109
109
|
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "23.12.
|
|
3
|
+
"version": "23.12.4",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -159,6 +159,11 @@ export type ResolvedPdfBook = {
|
|
|
159
159
|
*/
|
|
160
160
|
id: string;
|
|
161
161
|
title: string;
|
|
162
|
+
/**
|
|
163
|
+
* Names the book in another book's
|
|
164
|
+
* references; `""` when none is declared.
|
|
165
|
+
*/
|
|
166
|
+
abbreviation: string;
|
|
162
167
|
/**
|
|
163
168
|
* `""` when none is declared.
|
|
164
169
|
*/
|
|
@@ -20,11 +20,13 @@ export function indexMarker(slug: string, main?: boolean): string;
|
|
|
20
20
|
* @param {string[]} [opts.heads] - Leading words (`pdf.indexHeads`) matched
|
|
21
21
|
* literally against the start of a name; a name that opens with one is also
|
|
22
22
|
* listed as `<rest>, <head>`, the head without its trailing space or colon. None when absent.
|
|
23
|
-
* @param {Array<{slug: string, name: string, title: string,
|
|
23
|
+
* @param {Array<{slug: string, name: string, title: string, abbreviation?: string,
|
|
24
|
+
* page: string|number}>} [opts.foreign] -
|
|
24
25
|
* Notes another book prints, listed as `Name: Book title, p. N` once this
|
|
25
|
-
* book mentions them.
|
|
26
|
+
* book mentions them, or `Name: Aelw. p12` when the home book has an
|
|
27
|
+
* abbreviation.
|
|
26
28
|
* @returns {Array<{letter: string, name: string, slug: string, see?: string,
|
|
27
|
-
* foreign?: {title: string, page: string}}>} Terms with `see` set for a
|
|
29
|
+
* foreign?: {title: string, abbreviation?: string, page: string}}>} Terms with `see` set for a
|
|
28
30
|
* cross-reference to the main name it points at.
|
|
29
31
|
*/
|
|
30
32
|
export function indexTerms(entries: object[], { owns, foreign, heads }?: {
|
|
@@ -34,6 +36,7 @@ export function indexTerms(entries: object[], { owns, foreign, heads }?: {
|
|
|
34
36
|
slug: string;
|
|
35
37
|
name: string;
|
|
36
38
|
title: string;
|
|
39
|
+
abbreviation?: string;
|
|
37
40
|
page: string | number;
|
|
38
41
|
}[] | undefined;
|
|
39
42
|
}): Array<{
|
|
@@ -43,6 +46,7 @@ export function indexTerms(entries: object[], { owns, foreign, heads }?: {
|
|
|
43
46
|
see?: string;
|
|
44
47
|
foreign?: {
|
|
45
48
|
title: string;
|
|
49
|
+
abbreviation?: string;
|
|
46
50
|
page: string;
|
|
47
51
|
};
|
|
48
52
|
}>;
|
|
@@ -57,7 +57,7 @@ export function readEntryPages(typPath: string, fontArgs: string[], binary?: str
|
|
|
57
57
|
* The books that render, in configuration order.
|
|
58
58
|
* @param {Map<string, string>} input.homes - Each note's key → its home book's
|
|
59
59
|
* id, from {@link module:engine/pdf-coverage.coverage}.
|
|
60
|
-
* @returns {Array<{key: string, book: string, title: string, anchor: string,
|
|
60
|
+
* @returns {Array<{key: string, book: string, title: string, abbreviation?: string, anchor: string,
|
|
61
61
|
* record: object, slug: string, aliases: string[]}>} One per homed note, in book order and
|
|
62
62
|
* then entry order.
|
|
63
63
|
*/
|
|
@@ -74,6 +74,7 @@ export function homeEntries({ books, homes }: {
|
|
|
74
74
|
key: string;
|
|
75
75
|
book: string;
|
|
76
76
|
title: string;
|
|
77
|
+
abbreviation?: string;
|
|
77
78
|
anchor: string;
|
|
78
79
|
record: object;
|
|
79
80
|
slug: string;
|
|
@@ -166,18 +166,21 @@ export function markdownToTypst(markdown: string, opts?: {
|
|
|
166
166
|
export function absolutePageUrl(url: string, site?: string): string;
|
|
167
167
|
/**
|
|
168
168
|
* A reference to a note another book prints, as Typst: the words the link
|
|
169
|
-
* carried, then the book and the page
|
|
169
|
+
* carried, then the book and the page. A book with an abbreviation reads
|
|
170
|
+
* `Name (Aelw. p12)`; one without reads `Name (_Book title_, p. 12)`.
|
|
170
171
|
*
|
|
171
|
-
* The space before the page number
|
|
172
|
-
* line of its own.
|
|
172
|
+
* The space before the page number, or between the abbreviation and the page,
|
|
173
|
+
* does not break, so a number never starts a line of its own.
|
|
173
174
|
*
|
|
174
175
|
* @param {string} inner - The already-rendered link text.
|
|
175
|
-
* @param {{title: string, page: string|number}} away -
|
|
176
|
-
*
|
|
176
|
+
* @param {{title: string, abbreviation?: string, page: string|number}} away -
|
|
177
|
+
* The home book's title, its abbreviation when it declares one, and the page
|
|
178
|
+
* the entry starts on.
|
|
177
179
|
* @returns {string} Typst markup.
|
|
178
180
|
*/
|
|
179
181
|
export function crossBookReference(inner: string, away: {
|
|
180
182
|
title: string;
|
|
183
|
+
abbreviation?: string;
|
|
181
184
|
page: string | number;
|
|
182
185
|
}): string;
|
|
183
186
|
/**
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
-
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
-
*
|
|
5
|
-
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
-
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
-
*
|
|
8
|
-
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
-
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
-
*
|
|
11
|
-
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Asserts that npm serves an exact package version.
|
|
16
|
-
*
|
|
17
|
-
* `npm publish` returns before the registry serves the new version, and a
|
|
18
|
-
* version that is accepted but never served is indistinguishable from success
|
|
19
|
-
* by the publish alone. This polls `npm view <name>@<version> version` until
|
|
20
|
-
* it prints the version, then exits 0. When the time budget runs out it exits 1
|
|
21
|
-
* with an `::error::` annotation naming the package, the version and the last
|
|
22
|
-
* registry answer.
|
|
23
|
-
*
|
|
24
|
-
* Usage: `node ci/confirm-npm-version.mjs <name> <version> [--interval <s>]
|
|
25
|
-
* [--timeout <s>]`. The defaults are 15 seconds between queries and 600 seconds
|
|
26
|
-
* overall.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import { spawnSync } from "node:child_process";
|
|
30
|
-
import { fileURLToPath } from "node:url";
|
|
31
|
-
|
|
32
|
-
/** Seconds between registry queries. */
|
|
33
|
-
export const DEFAULT_INTERVAL_SECONDS = 15;
|
|
34
|
-
/** Seconds the registry has to serve the version. */
|
|
35
|
-
export const DEFAULT_TIMEOUT_SECONDS = 600;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Ask the registry which version `name@version` resolves to.
|
|
39
|
-
*
|
|
40
|
-
* @param {string} spec - `<name>@<version>`.
|
|
41
|
-
* @returns {string} The version npm printed, or its error text when the query
|
|
42
|
-
* fails (an unpublished version answers E404).
|
|
43
|
-
*/
|
|
44
|
-
export function queryNpm(spec) {
|
|
45
|
-
const result = spawnSync("npm", ["view", spec, "version"], { encoding: "utf8" });
|
|
46
|
-
if (result.error) return result.error.message;
|
|
47
|
-
const out = (result.stdout ?? "").trim();
|
|
48
|
-
if (result.status === 0) return out;
|
|
49
|
-
const err = (result.stderr ?? "").trim().replace(/\s*\n\s*/g, " | ");
|
|
50
|
-
return err || out || `npm exited ${result.status}`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Poll until the registry serves `version` of `name`.
|
|
55
|
-
*
|
|
56
|
-
* @param {object} options
|
|
57
|
-
* @param {string} options.name - Package name.
|
|
58
|
-
* @param {string} options.version - Exact version expected.
|
|
59
|
-
* @param {(spec: string) => string} [options.query] - Registry query; returns
|
|
60
|
-
* the registry's answer for `<name>@<version>`.
|
|
61
|
-
* @param {(ms: number) => Promise<void>} [options.sleep] - Waits between queries.
|
|
62
|
-
* @param {number} [options.intervalSeconds] - Seconds between queries.
|
|
63
|
-
* @param {number} [options.timeoutSeconds] - Seconds before giving up.
|
|
64
|
-
* @param {(line: string) => void} [options.log] - Progress output.
|
|
65
|
-
* @returns {Promise<{ok: true, attempts: number} | {ok: false, attempts: number, last: string}>}
|
|
66
|
-
*/
|
|
67
|
-
export async function confirmNpmVersion({
|
|
68
|
-
name,
|
|
69
|
-
version,
|
|
70
|
-
query = queryNpm,
|
|
71
|
-
sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
72
|
-
intervalSeconds = DEFAULT_INTERVAL_SECONDS,
|
|
73
|
-
timeoutSeconds = DEFAULT_TIMEOUT_SECONDS,
|
|
74
|
-
log = () => {},
|
|
75
|
-
}) {
|
|
76
|
-
const spec = `${name}@${version}`;
|
|
77
|
-
const attemptLimit = Math.floor(timeoutSeconds / intervalSeconds) + 1;
|
|
78
|
-
let last = "";
|
|
79
|
-
for (let attempts = 1; attempts <= attemptLimit; attempts++) {
|
|
80
|
-
last = query(spec);
|
|
81
|
-
if (last === version) {
|
|
82
|
-
log(`${spec} is served by npm.`);
|
|
83
|
-
return { ok: true, attempts };
|
|
84
|
-
}
|
|
85
|
-
log(`${spec} is not served yet (${last || "empty answer"}).`);
|
|
86
|
-
if (attempts < attemptLimit) await sleep(intervalSeconds * 1000);
|
|
87
|
-
}
|
|
88
|
-
return { ok: false, attempts: attemptLimit, last };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function parseArguments(argv) {
|
|
92
|
-
const positional = [];
|
|
93
|
-
const options = {};
|
|
94
|
-
for (let i = 0; i < argv.length; i++) {
|
|
95
|
-
const arg = argv[i];
|
|
96
|
-
if (arg === "--interval" || arg === "--timeout") {
|
|
97
|
-
const value = Number(argv[++i]);
|
|
98
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
99
|
-
throw new Error(`${arg} takes a positive number of seconds.`);
|
|
100
|
-
}
|
|
101
|
-
options[arg === "--interval" ? "intervalSeconds" : "timeoutSeconds"] = value;
|
|
102
|
-
} else if (arg.startsWith("--")) {
|
|
103
|
-
throw new Error(`Unknown option ${arg}.`);
|
|
104
|
-
} else {
|
|
105
|
-
positional.push(arg);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (positional.length !== 2) {
|
|
109
|
-
throw new Error(
|
|
110
|
-
"Usage: confirm-npm-version.mjs <name> <version> [--interval <s>] [--timeout <s>]",
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
return { name: positional[0], version: positional[1], ...options };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function main(argv) {
|
|
117
|
-
let options;
|
|
118
|
-
try {
|
|
119
|
-
options = parseArguments(argv);
|
|
120
|
-
} catch (error) {
|
|
121
|
-
console.error(`::error::${error.message}`);
|
|
122
|
-
return 2;
|
|
123
|
-
}
|
|
124
|
-
const outcome = await confirmNpmVersion({ ...options, log: (line) => console.log(line) });
|
|
125
|
-
if (outcome.ok) return 0;
|
|
126
|
-
const timeout = options.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
|
|
127
|
-
console.error(
|
|
128
|
-
`::error::npm does not serve ${options.name}@${options.version} after ${timeout}s and ${outcome.attempts} queries. Last registry answer: ${outcome.last || "(empty)"}`,
|
|
129
|
-
);
|
|
130
|
-
return 1;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
134
|
-
process.exitCode = await main(process.argv.slice(2));
|
|
135
|
-
}
|