@heroiclands/package-build 23.12.6 → 23.12.8
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 +18 -0
- package/assets/styles/content-base.css +25 -0
- package/bin/content-commands.mjs +264 -3
- package/bin/package-build.mjs +25 -0
- package/content-config.mjs +72 -0
- package/docs/api.md +10 -9
- package/docs/authoring/assets.md +1 -1
- package/docs/authoring/links-and-markup.md +439 -47
- package/docs/commands.md +137 -3
- package/docs/configuration.md +73 -3
- package/docs/content-format.md +3 -1
- package/docs/guides/book.md +11 -0
- package/docs/guides/module-project.md +16 -0
- package/docs/guides/site.md +19 -0
- package/docs/reference/format-details.md +378 -6
- package/docs/reference/note-types.md +36 -11
- package/e2e.mjs +1 -1
- package/engine/asset-shipping.mjs +22 -8
- package/engine/base-compiler.mjs +2 -0
- package/engine/bundles.mjs +7 -2
- package/engine/compile-corpus.mjs +3 -1
- package/engine/content-charset.mjs +21 -2
- package/engine/content-format.yaml +1 -0
- package/engine/content-index.mjs +12 -0
- package/engine/content-poetry.mjs +22 -4
- package/engine/content-scripts.mjs +1219 -0
- package/engine/content-session.mjs +2 -0
- package/engine/data-keys.mjs +5 -1
- package/engine/dictionary-layout.mjs +18 -12
- package/engine/dictionary-rows.mjs +11 -2
- package/engine/helpers.mjs +8 -2
- package/engine/index-records.mjs +1 -0
- package/engine/journals.mjs +14 -3
- package/engine/language-data.mjs +137 -1
- package/engine/language-tag.mjs +103 -0
- package/engine/language-terms.mjs +16 -4
- package/engine/map-lexicon.mjs +762 -0
- package/engine/markup-validation.mjs +17 -3
- package/engine/names.mjs +1 -1
- package/engine/note-vocabulary.mjs +113 -0
- package/engine/pdf-build.mjs +28 -10
- package/engine/pdf-render.mjs +116 -11
- package/engine/run-rows.mjs +119 -0
- package/engine/run-words.mjs +266 -0
- package/engine/scenes.mjs +1 -0
- package/engine/script-font-delivery.mjs +321 -0
- package/engine/script-fonts.mjs +172 -0
- package/engine/site-build.mjs +10 -3
- package/engine/site-config.mjs +5 -0
- package/engine/site-root.mjs +19 -5
- package/engine/sql-tables.mjs +111 -1
- package/engine/text-maps.mjs +607 -0
- package/engine/verb-pattern.mjs +99 -0
- package/engine/word-pattern.mjs +27 -0
- package/engine/word-rows.mjs +8 -2
- package/engine/word-terms.mjs +44 -0
- package/hugo-theme/layouts/_default/baseof.html +3 -0
- package/hugo-theme/static/css/style.css +10 -0
- package/manifest.mjs +9 -1
- package/package.json +2 -2
- package/types/content-config.d.mts +26 -0
- package/types/e2e.d.mts +2 -2
- package/types/engine/address-fields.d.mts +1 -1
- package/types/engine/address-values.d.mts +1 -1
- package/types/engine/asset-shipping.d.mts +5 -0
- package/types/engine/compile-corpus.d.mts +2 -1
- package/types/engine/content-format-check.d.mts +1 -1
- package/types/engine/content-index.d.mts +11 -3
- package/types/engine/content-poetry.d.mts +7 -0
- package/types/engine/content-scripts.d.mts +226 -0
- package/types/engine/data-keys.d.mts +2 -2
- package/types/engine/dictionary-rows.d.mts +15 -0
- package/types/engine/helpers.d.mts +5 -1
- package/types/engine/journals.d.mts +7 -2
- package/types/engine/language-data.d.mts +2 -0
- package/types/engine/language-tag.d.mts +8 -0
- package/types/engine/language-terms.d.mts +2 -1
- package/types/engine/map-lexicon.d.mts +274 -0
- package/types/engine/markup-validation.d.mts +7 -2
- package/types/engine/note-vocabulary.d.mts +1 -1
- package/types/engine/pdf-build.d.mts +26 -3
- package/types/engine/pdf-coverage.d.mts +1 -1
- package/types/engine/pdf-render.d.mts +17 -2
- package/types/engine/run-rows.d.mts +89 -0
- package/types/engine/run-words.d.mts +86 -0
- package/types/engine/script-font-delivery.d.mts +134 -0
- package/types/engine/script-fonts.d.mts +168 -0
- package/types/engine/site-build.d.mts +2 -1
- package/types/engine/site-root.d.mts +11 -3
- package/types/engine/text-maps.d.mts +175 -0
- package/types/engine/verb-pattern.d.mts +65 -0
- package/types/engine/word-pattern.d.mts +13 -0
- package/types/engine/word-terms.d.mts +6 -0
- package/types/release.d.mts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 23.12.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
A `packageBuild.assets` row that copies files into `icons/` or `images/` now ships them beside the staged pictures instead of having them erased. A shipped picture whose path matches a file a row already staged is an error, and the row's file is left as it was.
|
|
8
|
+
|
|
9
|
+
## 23.12.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
**Writing in the setting's languages and scripts**
|
|
14
|
+
|
|
15
|
+
- Text can be marked with its language and set in one of the HeroicLands fonts, or in plain serif, sans or monospace, on the site, in Foundry and in books.
|
|
16
|
+
- Roman spelling converts into a script through a mapping note, which takes a word from the language's dictionary when the dictionary spells it differently.
|
|
17
|
+
- `package-build transliterate` and `package-build lexicon` show a passage or a word exactly as a build sets it.
|
|
18
|
+
- Converted text is checked against its language, and an unknown word or unregistered name is reported where it is written.
|
|
19
|
+
- A language's present endings can replace its infinitive ending, as Vylari's do.
|
|
20
|
+
|
|
3
21
|
## 23.12.6
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
|
@@ -164,6 +164,31 @@
|
|
|
164
164
|
--poetry-indent: 10em;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/* Text set in one of the faces. The font family and features come from the
|
|
168
|
+
* face's own class; a right-to-left block or verse reads from the right. */
|
|
169
|
+
.script-block {
|
|
170
|
+
margin: 1rem 0;
|
|
171
|
+
line-height: 1.7;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.script-block[dir="rtl"],
|
|
175
|
+
.poetry[dir="rtl"] {
|
|
176
|
+
text-align: start;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Text set in the reader's generic families, `font=serif`, `sans` and `mono`. */
|
|
180
|
+
.font-serif {
|
|
181
|
+
font-family: serif;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.font-sans {
|
|
185
|
+
font-family: sans-serif;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.font-mono {
|
|
189
|
+
font-family: monospace;
|
|
190
|
+
}
|
|
191
|
+
|
|
167
192
|
/*
|
|
168
193
|
* Historical info/warn section classes remain available for styled prose.
|
|
169
194
|
* New alerts use the separate alert classes below.
|
package/bin/content-commands.mjs
CHANGED
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
* npx package-build site [--out <dir>]
|
|
31
31
|
* npx package-build map [--tree] [--from <shortcode>] [--chart <shortcode>] [--travel] [--out <dir>]
|
|
32
32
|
* npx package-build reachability <dir> [file] [--index <shortcode>]
|
|
33
|
+
* npx package-build transliterate <map> [--lang <language>] [--font <slug>] [--hex] [text..]
|
|
34
|
+
* npx package-build lexicon <language> --map <map> [--font <slug>] [--json]
|
|
33
35
|
* npx package-build addresses diff --from <zip|dir> [--strict]
|
|
34
36
|
* npx package-build npc new (--spec <json> | --specfile <path>) [--seed <text>] [--out <path>]
|
|
35
37
|
*
|
|
@@ -62,7 +64,19 @@ import {
|
|
|
62
64
|
unaddressableForeignPackages,
|
|
63
65
|
formatUnaddressableFinding,
|
|
64
66
|
} from "../engine/metadata-index.mjs";
|
|
65
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
fetchNavigation,
|
|
69
|
+
generateHugoConfig,
|
|
70
|
+
HUGO_SOURCE,
|
|
71
|
+
writeHugoConfig,
|
|
72
|
+
} from "../engine/site-config.mjs";
|
|
73
|
+
import {
|
|
74
|
+
checkScriptFonts,
|
|
75
|
+
copyScriptFonts,
|
|
76
|
+
scriptFontFiles,
|
|
77
|
+
SITE_FONTS_DIR,
|
|
78
|
+
siteFontsDestination,
|
|
79
|
+
} from "../engine/script-font-delivery.mjs";
|
|
66
80
|
import { renderItemFieldReference, renderItemFieldsPage } from "../engine/field-reference.mjs";
|
|
67
81
|
import { checkDocLinks, checkDocIndex } from "../engine/docs-checks.mjs";
|
|
68
82
|
import { checkArtSlotSizes } from "../engine/art-fields.mjs";
|
|
@@ -102,6 +116,14 @@ import { ENGINE_NOTE_SCHEMAS } from "../engine/note-schemas.mjs";
|
|
|
102
116
|
import { schemaSubtypeOf } from "../engine/subtype-registry.mjs";
|
|
103
117
|
import { NOTE_VOCABULARY } from "../engine/note-vocabulary.mjs";
|
|
104
118
|
import { markupFindings } from "../engine/markup-validation.mjs";
|
|
119
|
+
import { runContext } from "../engine/content-scripts.mjs";
|
|
120
|
+
import {
|
|
121
|
+
checkLexiconNatives,
|
|
122
|
+
checkMapFonts,
|
|
123
|
+
languageNoteFindings,
|
|
124
|
+
lexiconEntries,
|
|
125
|
+
transliterate,
|
|
126
|
+
} from "../engine/map-lexicon.mjs";
|
|
105
127
|
import { checkFormatting, checkPrettierConventions, lintMarkdown } from "../engine/prose-lint.mjs";
|
|
106
128
|
import { lintProse, scoreProseTree } from "../engine/readability-lint.mjs";
|
|
107
129
|
import { loadPackageBuildConfig } from "../config.mjs";
|
|
@@ -300,6 +322,8 @@ export function registerContentCommands(cli) {
|
|
|
300
322
|
)
|
|
301
323
|
.command(withIndexPreflight(mapCommand()))
|
|
302
324
|
.command(withIndexPreflight(reachabilityCommand()))
|
|
325
|
+
.command(transliterateCommand())
|
|
326
|
+
.command(lexiconCommand())
|
|
303
327
|
.command(addressesCommand())
|
|
304
328
|
.command(withIndexPreflight(npcCommand()));
|
|
305
329
|
}
|
|
@@ -461,7 +485,7 @@ function withBodyPreflight(
|
|
|
461
485
|
: undefined;
|
|
462
486
|
const records = session?.records ?? indexRecordsFor({ config, contentBase });
|
|
463
487
|
if (session?.problems.length) {
|
|
464
|
-
|
|
488
|
+
for (const problem of session.problems) emitDiagnostic(problem);
|
|
465
489
|
process.exitCode = 1;
|
|
466
490
|
return;
|
|
467
491
|
}
|
|
@@ -1257,10 +1281,19 @@ function lintCommand() {
|
|
|
1257
1281
|
assetsBase: config.paths.assets,
|
|
1258
1282
|
});
|
|
1259
1283
|
|
|
1284
|
+
// Text marked with a language, a font or a map, checked against
|
|
1285
|
+
// the languages this package declares, the faces it installed
|
|
1286
|
+
// and the maps its notes declare.
|
|
1287
|
+
const runs = runContext(config, records);
|
|
1260
1288
|
const findings = [
|
|
1261
1289
|
...index.notes.flatMap((note) =>
|
|
1262
|
-
markupFindings(note.raw, note.file, session.syntax),
|
|
1290
|
+
markupFindings(note.raw, note.file, session.syntax, runs),
|
|
1263
1291
|
),
|
|
1292
|
+
// Each mapping note's faces, each language's native forms
|
|
1293
|
+
// and each language's `note`, against the faces and maps.
|
|
1294
|
+
...index.notes.flatMap((note) => checkMapFonts(note, runs)),
|
|
1295
|
+
...index.notes.flatMap((note) => checkLexiconNatives(note, runs)),
|
|
1296
|
+
...languageNoteFindings({ ...config, configFile: packConfigPath() }, records),
|
|
1264
1297
|
...index.notes.flatMap(firstUseChecker),
|
|
1265
1298
|
...(names ? [names] : []),
|
|
1266
1299
|
...addresses.findings,
|
|
@@ -1891,6 +1924,38 @@ function pdfCommand() {
|
|
|
1891
1924
|
};
|
|
1892
1925
|
}
|
|
1893
1926
|
|
|
1927
|
+
/**
|
|
1928
|
+
* Copy the installed script fonts into the Hugo static tree, under a path that
|
|
1929
|
+
* names their version, and drop any other version's copy.
|
|
1930
|
+
*
|
|
1931
|
+
* A package that has not installed `@heroiclands/fonts`, or sets no text in
|
|
1932
|
+
* one of its faces, gets no copy; a package missing a file its fonts depend on
|
|
1933
|
+
* gets a located error and a non-zero exit.
|
|
1934
|
+
*
|
|
1935
|
+
* @param {object} config - The resolved content configuration.
|
|
1936
|
+
* @returns {void}
|
|
1937
|
+
*/
|
|
1938
|
+
function stageSiteFonts(config) {
|
|
1939
|
+
const root = path.join(config.rootDir, HUGO_SOURCE, "static", SITE_FONTS_DIR);
|
|
1940
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
1941
|
+
const found = scriptFontFiles(config, ["woff2"]);
|
|
1942
|
+
if (!found) return;
|
|
1943
|
+
const findings = checkScriptFonts(config);
|
|
1944
|
+
for (const finding of findings) emitDiagnostic(finding);
|
|
1945
|
+
if (findings.length) {
|
|
1946
|
+
process.exitCode = 1;
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
const dest = path.join(
|
|
1950
|
+
config.rootDir,
|
|
1951
|
+
HUGO_SOURCE,
|
|
1952
|
+
"static",
|
|
1953
|
+
siteFontsDestination(found.version),
|
|
1954
|
+
);
|
|
1955
|
+
const count = copyScriptFonts(config, dest, ["woff2"]);
|
|
1956
|
+
log.info(`wrote ${count} script font file(s) to ${path.relative(process.cwd(), dest)}`);
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1894
1959
|
/**
|
|
1895
1960
|
* `package-build site` — publish the content tree as a website.
|
|
1896
1961
|
*
|
|
@@ -2077,6 +2142,7 @@ function siteCommand() {
|
|
|
2077
2142
|
);
|
|
2078
2143
|
const { file } = writeHugoConfig(config, hugo);
|
|
2079
2144
|
log.info(`wrote ${path.relative(process.cwd(), file)}`);
|
|
2145
|
+
stageSiteFonts(config);
|
|
2080
2146
|
} catch (err) {
|
|
2081
2147
|
reportFailure(err);
|
|
2082
2148
|
process.exitCode = 1;
|
|
@@ -2241,6 +2307,201 @@ function mapCommand() {
|
|
|
2241
2307
|
};
|
|
2242
2308
|
}
|
|
2243
2309
|
|
|
2310
|
+
/**
|
|
2311
|
+
* The run context a mapping command reads: the content index's maps and
|
|
2312
|
+
* lexicons, and the configured languages and installed faces, exactly as the
|
|
2313
|
+
* build has them.
|
|
2314
|
+
*
|
|
2315
|
+
* @returns {{config: object, ctx: object}|undefined} The context, or
|
|
2316
|
+
* `undefined` when the content tree could not be read whole, which is
|
|
2317
|
+
* reported.
|
|
2318
|
+
*/
|
|
2319
|
+
function mappingContext() {
|
|
2320
|
+
const config = loadPackConfig();
|
|
2321
|
+
const problems = [];
|
|
2322
|
+
const records = indexRecordsFor({
|
|
2323
|
+
contentBase: config.paths.content,
|
|
2324
|
+
config,
|
|
2325
|
+
skipDirectories: config.skipDirectories,
|
|
2326
|
+
problems,
|
|
2327
|
+
});
|
|
2328
|
+
for (const problem of problems) emitDiagnostic(problem);
|
|
2329
|
+
if (problems.length) return undefined;
|
|
2330
|
+
return { config, ctx: runContext(config, records) };
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
/**
|
|
2334
|
+
* Report what a mapping command was given that names nothing: an unknown
|
|
2335
|
+
* language at the configuration's `languages`, and an unknown map or face at
|
|
2336
|
+
* the content tree, where mapping notes are. A word the rows cannot
|
|
2337
|
+
* capitalise is in the passage given, so it has no file.
|
|
2338
|
+
*
|
|
2339
|
+
* @param {object} config - The resolved configuration.
|
|
2340
|
+
* @param {{problem: string, about: string}} found - What is wrong.
|
|
2341
|
+
* @param {string} [lang] - The language asked for.
|
|
2342
|
+
*/
|
|
2343
|
+
function reportMapping(config, found) {
|
|
2344
|
+
if (found.about === "lang") {
|
|
2345
|
+
const file = packConfigPath();
|
|
2346
|
+
let text;
|
|
2347
|
+
try {
|
|
2348
|
+
text = fs.readFileSync(file, "utf8");
|
|
2349
|
+
} catch {
|
|
2350
|
+
text = undefined;
|
|
2351
|
+
}
|
|
2352
|
+
emitDiagnostic({
|
|
2353
|
+
file,
|
|
2354
|
+
...(text ? positionOfYamlPath(text, ["languages"], { key: true }) : {}),
|
|
2355
|
+
severity: "error",
|
|
2356
|
+
message: found.problem,
|
|
2357
|
+
});
|
|
2358
|
+
} else if (found.about === "word")
|
|
2359
|
+
// A word of the passage given, which sits in no file.
|
|
2360
|
+
emitDiagnostic({ severity: "error", message: found.problem });
|
|
2361
|
+
else emitDiagnostic({ file: config.paths.content, severity: "error", message: found.problem });
|
|
2362
|
+
process.exitCode = 1;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
/**
|
|
2366
|
+
* `package-build transliterate <map> [text..]` — map a passage exactly as the
|
|
2367
|
+
* build maps a run marked with the same `map=`, `lang=` and `font=`.
|
|
2368
|
+
*
|
|
2369
|
+
* @returns {object} The yargs command module.
|
|
2370
|
+
*/
|
|
2371
|
+
// eslint-disable-next-line
|
|
2372
|
+
function transliterateCommand() {
|
|
2373
|
+
return {
|
|
2374
|
+
command: "transliterate <map> [text..]",
|
|
2375
|
+
describe: "Map a passage into a script exactly as the build maps a marked run",
|
|
2376
|
+
builder: (yargs) =>
|
|
2377
|
+
yargs
|
|
2378
|
+
.positional("map", {
|
|
2379
|
+
type: "string",
|
|
2380
|
+
describe: "The `data.map.name` of a mapping note",
|
|
2381
|
+
})
|
|
2382
|
+
.positional("text", {
|
|
2383
|
+
type: "string",
|
|
2384
|
+
array: true,
|
|
2385
|
+
describe: "The passage; `-` or nothing reads it from stdin",
|
|
2386
|
+
})
|
|
2387
|
+
.option("lang", {
|
|
2388
|
+
type: "string",
|
|
2389
|
+
describe: "A declared language, whose lexicon is read first",
|
|
2390
|
+
})
|
|
2391
|
+
.option("font", {
|
|
2392
|
+
type: "string",
|
|
2393
|
+
describe: "The face; required when the map lists more than one",
|
|
2394
|
+
})
|
|
2395
|
+
.option("hex", {
|
|
2396
|
+
type: "boolean",
|
|
2397
|
+
default: false,
|
|
2398
|
+
describe: "Print the code points, as `U+XXXX` separated by spaces",
|
|
2399
|
+
}),
|
|
2400
|
+
handler: (argv) => {
|
|
2401
|
+
try {
|
|
2402
|
+
const read = mappingContext();
|
|
2403
|
+
if (!read) {
|
|
2404
|
+
process.exitCode = 1;
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
const words = (argv.text ?? []).map(String);
|
|
2408
|
+
const text =
|
|
2409
|
+
!words.length || (words.length === 1 && words[0] === "-") ?
|
|
2410
|
+
fs.readFileSync(0, "utf8").replace(/\r?\n$/, "")
|
|
2411
|
+
: words.join(" ");
|
|
2412
|
+
const mapped = transliterate(
|
|
2413
|
+
read.ctx,
|
|
2414
|
+
{ map: String(argv.map), lang: argv.lang, font: argv.font },
|
|
2415
|
+
text,
|
|
2416
|
+
);
|
|
2417
|
+
if ("problem" in mapped) {
|
|
2418
|
+
reportMapping(read.config, mapped);
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
console.log(
|
|
2422
|
+
argv.hex ?
|
|
2423
|
+
Array.from(
|
|
2424
|
+
mapped.text,
|
|
2425
|
+
(char) =>
|
|
2426
|
+
`U+${char.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`,
|
|
2427
|
+
).join(" ")
|
|
2428
|
+
: mapped.text,
|
|
2429
|
+
);
|
|
2430
|
+
} catch (err) {
|
|
2431
|
+
reportFailure(err);
|
|
2432
|
+
process.exitCode = 1;
|
|
2433
|
+
}
|
|
2434
|
+
},
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
/**
|
|
2439
|
+
* `package-build lexicon <language> --map <map>` — every word of a language's
|
|
2440
|
+
* lexicon with its native form under a map, as the build writes it.
|
|
2441
|
+
*
|
|
2442
|
+
* @returns {object} The yargs command module.
|
|
2443
|
+
*/
|
|
2444
|
+
// eslint-disable-next-line
|
|
2445
|
+
function lexiconCommand() {
|
|
2446
|
+
return {
|
|
2447
|
+
command: "lexicon <language>",
|
|
2448
|
+
describe: "List a language's words with their native forms under a map",
|
|
2449
|
+
builder: (yargs) =>
|
|
2450
|
+
yargs
|
|
2451
|
+
.positional("language", {
|
|
2452
|
+
type: "string",
|
|
2453
|
+
describe: "A declared language whose `note` names its language note",
|
|
2454
|
+
})
|
|
2455
|
+
.option("map", {
|
|
2456
|
+
type: "string",
|
|
2457
|
+
demandOption: true,
|
|
2458
|
+
describe: "The `data.map.name` of a mapping note",
|
|
2459
|
+
})
|
|
2460
|
+
.option("font", {
|
|
2461
|
+
type: "string",
|
|
2462
|
+
describe: "The face; required when the map lists more than one",
|
|
2463
|
+
})
|
|
2464
|
+
.option("json", {
|
|
2465
|
+
type: "boolean",
|
|
2466
|
+
default: false,
|
|
2467
|
+
describe: "Print a JSON array rather than tab-separated lines",
|
|
2468
|
+
}),
|
|
2469
|
+
handler: (argv) => {
|
|
2470
|
+
try {
|
|
2471
|
+
const read = mappingContext();
|
|
2472
|
+
if (!read) {
|
|
2473
|
+
process.exitCode = 1;
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
const listed = lexiconEntries(read.ctx, {
|
|
2477
|
+
lang: String(argv.language),
|
|
2478
|
+
map: String(argv.map),
|
|
2479
|
+
font: argv.font,
|
|
2480
|
+
});
|
|
2481
|
+
if ("problem" in listed) {
|
|
2482
|
+
reportMapping(read.config, listed);
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
if (argv.json) console.log(JSON.stringify(listed.entries, null, 2));
|
|
2486
|
+
else
|
|
2487
|
+
for (const entry of listed.entries)
|
|
2488
|
+
console.log(
|
|
2489
|
+
[
|
|
2490
|
+
entry.form,
|
|
2491
|
+
entry.native,
|
|
2492
|
+
entry.exception ? "exception" : "rows",
|
|
2493
|
+
entry.class ?? "",
|
|
2494
|
+
entry.gloss ?? "",
|
|
2495
|
+
].join("\t"),
|
|
2496
|
+
);
|
|
2497
|
+
} catch (err) {
|
|
2498
|
+
reportFailure(err);
|
|
2499
|
+
process.exitCode = 1;
|
|
2500
|
+
}
|
|
2501
|
+
},
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2244
2505
|
/**
|
|
2245
2506
|
* `package-build reachability <dir> [file]` — check that a corpus reads through.
|
|
2246
2507
|
*
|
package/bin/package-build.mjs
CHANGED
|
@@ -96,6 +96,11 @@ import { loadPackConfig, packConfigPath, resolveConfigFile } from "../engine/pac
|
|
|
96
96
|
import { cleanBuildArtifacts, resetStage, stageAssets } from "../stage.mjs";
|
|
97
97
|
import { rowsStagingPictureRoots, stagePackageAssets } from "../engine/asset-shipping.mjs";
|
|
98
98
|
import { baseStyleStageEntry } from "../engine/base-styles.mjs";
|
|
99
|
+
import {
|
|
100
|
+
checkScriptFonts,
|
|
101
|
+
copyScriptFonts,
|
|
102
|
+
STAGED_FONTS_DIR,
|
|
103
|
+
} from "../engine/script-font-delivery.mjs";
|
|
99
104
|
import {
|
|
100
105
|
CDN_ENV,
|
|
101
106
|
cdnCredentials,
|
|
@@ -576,6 +581,26 @@ function assetsCommand() {
|
|
|
576
581
|
count += staged.entries;
|
|
577
582
|
files += staged.files;
|
|
578
583
|
}
|
|
584
|
+
|
|
585
|
+
// The installed fonts, when the package has installed the fonts
|
|
586
|
+
// package and a note sets text in one of its faces: the
|
|
587
|
+
// stylesheet, the WOFF2 files and the licence, which the
|
|
588
|
+
// manifest's `styles` names.
|
|
589
|
+
const fontFindings = checkScriptFonts(packConfig);
|
|
590
|
+
if (fontFindings.length) {
|
|
591
|
+
const error = new Error(fontFindings.map(formatDiagnostic).join("\n"));
|
|
592
|
+
error.located = true;
|
|
593
|
+
die(error);
|
|
594
|
+
}
|
|
595
|
+
const fonts = copyScriptFonts(
|
|
596
|
+
packConfig,
|
|
597
|
+
path.resolve(config.rootDir, config.stageDir, STAGED_FONTS_DIR),
|
|
598
|
+
["woff2"],
|
|
599
|
+
);
|
|
600
|
+
if (fonts) {
|
|
601
|
+
count += 1;
|
|
602
|
+
files += fonts;
|
|
603
|
+
}
|
|
579
604
|
console.log(`✅ Static assets staged (${count} entries, ${files} files).`);
|
|
580
605
|
}),
|
|
581
606
|
};
|
package/content-config.mjs
CHANGED
|
@@ -70,6 +70,7 @@ import { normalizeCurrentDate } from "./engine/current-date.mjs";
|
|
|
70
70
|
// Leaves with no local imports of their own, so naming them here cannot close
|
|
71
71
|
// a cycle around a consumer's config file (see `engine/pack-config.mjs`).
|
|
72
72
|
import { ADDRESS_SEGMENT_PATTERN, isAddressSegment } from "./engine/address-charset.mjs";
|
|
73
|
+
import { isLanguageTag } from "./engine/language-tag.mjs";
|
|
73
74
|
import { ASSET_TYPE_NAMES, shippingLicensesProblem } from "./engine/asset-types.mjs";
|
|
74
75
|
import { isReservedPackage } from "./engine/packages.mjs";
|
|
75
76
|
import { EMPTY_ICON_REGISTRY, checkIconRegistry } from "./engine/content-icons.mjs";
|
|
@@ -614,6 +615,9 @@ export function publishesContentPages(config) {
|
|
|
614
615
|
* `{ system, builders }` registries
|
|
615
616
|
* instead, and the accepted type
|
|
616
617
|
* vocabulary is their union.
|
|
618
|
+
* @property {Record<string, LanguageSpec>} [languages] The languages a
|
|
619
|
+
* note may tag text with, by short
|
|
620
|
+
* name. Default `{}`.
|
|
617
621
|
* @property {PackSpec[]} packs Packs to compile. More than one entry
|
|
618
622
|
* may share a `type`: a note then names
|
|
619
623
|
* the pack it belongs in with its
|
|
@@ -719,6 +723,8 @@ export function publishesContentPages(config) {
|
|
|
719
723
|
* fonts this package ships and the names it
|
|
720
724
|
* draws from them; empty when it declares
|
|
721
725
|
* none.
|
|
726
|
+
* @property {Readonly<Record<string, Readonly<LanguageSpec>>>} languages
|
|
727
|
+
* Frozen; `{}` when absent.
|
|
722
728
|
* @property {readonly Readonly<ResolvedPackSpec>[]} packs
|
|
723
729
|
* @property {readonly string[]} packDirectories Derived: every pack directory
|
|
724
730
|
* the build produces, in compile order —
|
|
@@ -747,6 +753,7 @@ const CONFIG_KEYS = [
|
|
|
747
753
|
"skipDirectories",
|
|
748
754
|
"forbidGeneratedArt",
|
|
749
755
|
"icons",
|
|
756
|
+
"languages",
|
|
750
757
|
"packs",
|
|
751
758
|
"docs",
|
|
752
759
|
"site",
|
|
@@ -937,6 +944,70 @@ function normalizeDefaultCalendar(value) {
|
|
|
937
944
|
return /** @type {string} */ (value);
|
|
938
945
|
}
|
|
939
946
|
|
|
947
|
+
/**
|
|
948
|
+
* One entry of `languages`.
|
|
949
|
+
*
|
|
950
|
+
* @typedef {object} LanguageSpec
|
|
951
|
+
* @property {string} tag Its BCP 47 tag. A constructed language takes
|
|
952
|
+
* `art-x-<name>`.
|
|
953
|
+
* @property {string} [note] The Address of its language note, whose
|
|
954
|
+
* `data.words` is the lexicon a mapped run in the
|
|
955
|
+
* language reads first.
|
|
956
|
+
*/
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* What a language name may be: lowercase letters, digits and single hyphens,
|
|
960
|
+
* starting with a letter. Lowercase keeps two names from differing only in
|
|
961
|
+
* case, which an index keyed by them cannot tell apart.
|
|
962
|
+
*/
|
|
963
|
+
const LANGUAGE_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Check `languages` and freeze it.
|
|
967
|
+
*
|
|
968
|
+
* @param {unknown} value - The configured mapping.
|
|
969
|
+
* @returns {Readonly<Record<string, Readonly<LanguageSpec>>>} The frozen
|
|
970
|
+
* mapping; `{}` when absent.
|
|
971
|
+
*/
|
|
972
|
+
function normalizeLanguages(value) {
|
|
973
|
+
if (value === undefined || value === null) return Object.freeze({});
|
|
974
|
+
if (!isPlainObject(value)) fail("languages", "must be a mapping of language name to `{tag}`");
|
|
975
|
+
const out = {};
|
|
976
|
+
for (const [name, entry] of Object.entries(/** @type {object} */ (value))) {
|
|
977
|
+
const where = `languages.${name}`;
|
|
978
|
+
if (!LANGUAGE_NAME.test(name))
|
|
979
|
+
fail(
|
|
980
|
+
where,
|
|
981
|
+
"is not a name: a name is lowercase letters, digits and single hyphens, " +
|
|
982
|
+
"starting with a letter",
|
|
983
|
+
);
|
|
984
|
+
if (!isPlainObject(entry)) fail(where, "must be a mapping with `tag`");
|
|
985
|
+
rejectUnknownKeys(entry, ["tag", "note"], `${where}.`);
|
|
986
|
+
const { tag, note } = /** @type {Record<string, unknown>} */ (entry);
|
|
987
|
+
if (tag === undefined || tag === null || tag === "")
|
|
988
|
+
fail(
|
|
989
|
+
`${where}.tag`,
|
|
990
|
+
"must be a BCP 47 language tag, such as `art-x-vyl` for a constructed language",
|
|
991
|
+
);
|
|
992
|
+
if (!isLanguageTag(tag))
|
|
993
|
+
fail(
|
|
994
|
+
`${where}.tag`,
|
|
995
|
+
`${String(tag)} is not a valid BCP 47 language tag; a constructed ` +
|
|
996
|
+
"language takes `art-x-<name>`",
|
|
997
|
+
);
|
|
998
|
+
if (note !== undefined && (typeof note !== "string" || note.trim() === ""))
|
|
999
|
+
fail(
|
|
1000
|
+
`${where}.note`,
|
|
1001
|
+
"must be the Address of a language note, such as `skill-vylarilng`",
|
|
1002
|
+
);
|
|
1003
|
+
out[name] = Object.freeze({
|
|
1004
|
+
tag: /** @type {string} */ (tag),
|
|
1005
|
+
...(note === undefined ? {} : { note: /** @type {string} */ (note) }),
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
return Object.freeze(out);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
940
1011
|
/**
|
|
941
1012
|
* @param {object} object
|
|
942
1013
|
* @param {readonly string[]} allowed
|
|
@@ -3075,6 +3146,7 @@ export function defineConfig(config) {
|
|
|
3075
3146
|
skipDirectories: Object.freeze(skipDirectories),
|
|
3076
3147
|
forbidGeneratedArt,
|
|
3077
3148
|
icons: normalizeIcons(input.icons, rootDir),
|
|
3149
|
+
languages: normalizeLanguages(input.languages),
|
|
3078
3150
|
packs: Object.freeze(packs),
|
|
3079
3151
|
packDirectories: Object.freeze(packDirectories),
|
|
3080
3152
|
docs: normalizeDocs(input.docs),
|
package/docs/api.md
CHANGED
|
@@ -2535,15 +2535,16 @@ The wildcard engine subpath exposes
|
|
|
2535
2535
|
`@heroiclands/package-build/engine/dictionary-rows` for deriving the
|
|
2536
2536
|
`dictionary` and `terms` SQL relations from content-index records:
|
|
2537
2537
|
|
|
2538
|
-
| Export | Signature | Returns | Use it when
|
|
2539
|
-
| ----------------------- | ----------------------------------------------- | ------------------------------------------- |
|
|
2540
|
-
| `DICTIONARY_COLUMNS` | `const DICTIONARY_COLUMNS` | A readonly list of column names | Declaring the schema of a language's dictionary.
|
|
2541
|
-
| `TERM_COLUMNS` | `const TERM_COLUMNS` | A readonly list of column names | Declaring the schema of owner-specific glossary terms.
|
|
2542
|
-
| `ETYMOLOGY_KEYS` | `const ETYMOLOGY_KEYS` | A readonly list of keys | Reading the structure of a dictionary word's etymology.
|
|
2543
|
-
| `dictionaryLetter` | `dictionaryLetter(text)` | `string` | Deriving a heading letter from a sort key.
|
|
2544
|
-
| `dictionaryEntryAnchor` | `dictionaryEntryAnchor(language, name)` | `string` | Deriving the stable entry anchor for a word in a language's dictionary.
|
|
2545
|
-
| `dictionaryAndTermRows` | `dictionaryAndTermRows(records, all = records)` | `{ dictionary: object[], terms: object[] }` | Deriving dictionary words and glossary terms for the selected records.
|
|
2546
|
-
| `serializeRows` | `serializeRows(rows)` | `string` | Writing deterministic JSON Lines with keys sorted at every depth.
|
|
2538
|
+
| Export | Signature | Returns | Use it when |
|
|
2539
|
+
| ----------------------- | ----------------------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
2540
|
+
| `DICTIONARY_COLUMNS` | `const DICTIONARY_COLUMNS` | A readonly list of column names | Declaring the schema of a language's dictionary. |
|
|
2541
|
+
| `TERM_COLUMNS` | `const TERM_COLUMNS` | A readonly list of column names | Declaring the schema of owner-specific glossary terms. |
|
|
2542
|
+
| `ETYMOLOGY_KEYS` | `const ETYMOLOGY_KEYS` | A readonly list of keys | Reading the structure of a dictionary word's etymology. |
|
|
2543
|
+
| `dictionaryLetter` | `dictionaryLetter(text)` | `string` | Deriving a heading letter from a sort key. |
|
|
2544
|
+
| `dictionaryEntryAnchor` | `dictionaryEntryAnchor(language, name)` | `string` | Deriving the stable entry anchor for a word in a language's dictionary. |
|
|
2545
|
+
| `dictionaryAndTermRows` | `dictionaryAndTermRows(records, all = records)` | `{ dictionary: object[], terms: object[] }` | Deriving dictionary words and glossary terms for the selected records. |
|
|
2546
|
+
| `serializeRows` | `serializeRows(rows)` | `string` | Writing deterministic JSON Lines with keys sorted at every depth. |
|
|
2547
|
+
| `pluralOf` | `pluralOf(word, grammar)` | `string \| null` | Deriving a noun's plural: its own `plural`, else the first `grammar.plural` row whose ending fits. |
|
|
2547
2548
|
|
|
2548
2549
|
`dictionaryEntryAnchor` takes the language's canonical Address and the
|
|
2549
2550
|
word's form. Its `word-` anchor is derived from the full SHA-256 digest of
|
package/docs/authoring/assets.md
CHANGED
|
@@ -47,7 +47,7 @@ role: portrait
|
|
|
47
47
|
|
|
48
48
|
## What the Foundry package ships
|
|
49
49
|
|
|
50
|
-
The repository keeps every picture; the Foundry package carries a filtered copy, laid out flat. A picture ships when its record says `ai: false` and its licence is one of `LicenseRef-PublicDomain`, `CC0-1.0`, `CC0`, `CC-BY-3.0`, `CC-BY-4.0`, `CC-BY-SA-4.0` or `LicenseRef-NounProject-Pro`, together with any licence the package adds under `packageBuild.shippingLicenses`. A shipped picture sits at `icons/<basename>` or `images/<basename>` at the package root, whatever directories it is kept in here, and the package's asset set, `asset-sets/default.json`, lists what it ships.
|
|
50
|
+
The repository keeps every picture; the Foundry package carries a filtered copy, laid out flat. A picture ships when its record says `ai: false` and its licence is one of `LicenseRef-PublicDomain`, `CC0-1.0`, `CC0`, `CC-BY-3.0`, `CC-BY-4.0`, `CC-BY-SA-4.0` or `LicenseRef-NounProject-Pro`, together with any licence the package adds under `packageBuild.shippingLicenses`. A shipped picture sits at `icons/<basename>` or `images/<basename>` at the package root, whatever directories it is kept in here, and the package's asset set, `asset-sets/default.json`, lists what it ships. A `packageBuild.assets` row may copy other files under `icons/` or `images/`; the shipped pictures are written beside them, and a row's file at the same path as a shipped picture is an error.
|
|
51
51
|
|
|
52
52
|
A note may still name a picture the package does not ship. Its compiled document draws the package's stand-in instead — `icons/missing.<ext>` or `images/missing.<ext>`, which you provide as an ordinary human-made picture licensed to ship, addressed `<package>-none-icon-missing` and `<package>-none-image-missing` — and keeps the real picture's address, so a world that holds the real file can point back at it. A package that leaves out a picture a note names, and ships no stand-in of that type, fails the build at the note. See [what a package ships](../reference/format-details.md#what-a-package-ships).
|
|
53
53
|
|