@heroiclands/package-build 22.12.3 → 22.13.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.
@@ -12,74 +12,17 @@
12
12
  */
13
13
 
14
14
  /**
15
- * Wikilink resolution for the pack compilers.
15
+ * Resolve body wikilinks for Foundry packs.
16
16
  *
17
- * Content notes link to one another with wikilinks rather than file paths:
17
+ * `[[type-shortcode|Text]]` defaults to the readable `note` Address. A stated
18
+ * game system names an Actor or Item; `none` names a systemless document.
19
+ * Journal links can carry a heading anchor. Actor, Item and Macro links open
20
+ * their sheets, so their anchors do not select a page.
18
21
  *
19
- * `[[type-shortcode|Text]]` a document of that type
20
- * `[[type-shortcode|]]` the same, showing the target's current name
21
- * `[[type-shortcode#slug|T]]` a section (see below)
22
- * `[[#slug|Text]]` a section of the source note itself
23
- * `[[doctype-shortcode|T]]` an item's *documentation* (see below)
22
+ * `doc<type>` is accepted as an input alias for the readable note Address.
23
+ * A link with an empty label displays the target's current name.
24
24
  *
25
- * **Every link is an address, and every address carries a label**. A
26
- * link written without one addresses nothing and is reported — see
27
- * {@link unlabelledLinkMessage}, which states the rule for both builds. The
28
- * bare `[[Alias]]` form and the index it was looked up in are retired.
29
- *
30
- * The qualifier is the note's **type**, which with its shortcode is the system's
31
- * logical identity: `(type, shortcode)` is unique by rule (see the Shortcode
32
- * Integrity doc). It is deliberately not the note's directory — shortcodes are
33
- * unique per type, not per directory, so a directory qualifier would add nothing
34
- * to the address while breaking every inbound link the moment a note is refiled.
35
- *
36
- * Nothing narrower than `(type, shortcode)` is consulted — a note's directory
37
- * and its `category` play no part in resolution — and nothing wider: a note's
38
- * *name* is not an address, so two notes of a type may share a display name
39
- * ("Gear" as a rules page and as a user guide page) with nothing to disambiguate.
40
- *
41
- * At compile time each becomes a Foundry UUID enricher, routed to the pack that
42
- * the target's type compiles into (see {@link packForType}):
43
- *
44
- * `@UUID[Compendium.sohl.items.Item.<id>]{Text}`
45
- * `@UUID[Compendium.sohl.journals.JournalEntry.<id>.JournalEntryPage.<anchorId>]{Text}`
46
- *
47
- * **Every address is computed once, when the target is indexed** — see
48
- * {@link buildWikilinkIndex} — and a link is resolved by looking that value up.
49
- * Nothing here concatenates a prefix at the point of use.
50
- *
51
- * Section links address a **JournalEntryPage**, because Foundry UUIDs cannot
52
- * target a position inside a page. A heading carrying `{#slug}` therefore starts
53
- * its own page, whose id is {@link anchorPageId} — derived from the note id and
54
- * the slug so that the link and the page agree without any shared state.
55
- *
56
- * **An anchor on an Item, an Actor or a Macro is a no-op** and is dropped. What
57
- * such a link does is open that document's **sheet** — not its documentation —
58
- * and a sheet has no sections to address. Only a JournalEntry link opens a
59
- * journal, at its first page or at the page an anchor names. An item's pages are
60
- * reached through its `doc<type>` counterpart, below.
61
- *
62
- * **A document and its documentation are two documents.** An item note
63
- * compiles into an item — and, separately, its prose compiles into a
64
- * JournalEntry in the journals pack (see
65
- * {@link sohl.utils.packs.itemDocEntryId}); a macro note works the same way.
66
- * `skill/wpnc` addresses the skill; the **virtual qualifier**
67
- * `docskill/wpnc` addresses that skill's documentation, and
68
- * `docskill/wpnc#crafting` a page within it. `docmacro/autoattack#script`
69
- * reaches a macro's source. Every doc-carrying type has a `doc<type>`
70
- * counterpart, formed by prefix and never enumerated; see
71
- * {@link resolveItemDocType}. Without it a section link to an item note
72
- * produced a UUID against the *items* pack, which cannot hold a
73
- * JournalEntryPage, and dead-ended.
74
- *
75
- * **The two builds read the qualifier differently, by design.** In Foundry the
76
- * item and its documentation are separate documents in separate packs, so the
77
- * two qualifiers resolve to two different UUIDs. On the knowledgebase the item
78
- * note renders as a single page which *is* its documentation, so `doc<type>` and
79
- * `<type>` are aliases for the same URL and an anchor on either is an ordinary
80
- * in-page anchor. One authored link, correct in both.
81
- *
82
- * Plain ESM with no Foundry and no filesystem access, so it is unit-testable.
25
+ * @module
83
26
  */
84
27
 
85
28
  import { resolveShortcodeReference } from "./shortcode-references.mjs";
@@ -100,12 +43,13 @@ import {
100
43
  renderAddress,
101
44
  isAddressTuple,
102
45
  completeAddress,
46
+ ownDocumentSystem,
103
47
  } from "./address.mjs";
104
48
  import { ASSET_TYPE_NAMES } from "./asset-types.mjs";
105
- import { NO_SYSTEM } from "./systems.mjs";
49
+ import { NOTE_SYSTEM } from "./systems.mjs";
106
50
  import { systemOf } from "./document-subtypes.mjs";
107
51
  import { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./subtype-registry.mjs";
108
- import { itemDocEntryId } from "./item-docs.mjs";
52
+ import { hasDocEntry, itemDocEntryId } from "./item-docs.mjs";
109
53
  import { replaceOutsideCode } from "./code-fences.mjs";
110
54
  // The syntax lives in `./wikilink-syntax.mjs`, so the web resolver and this
111
55
  // one cannot disagree about what counts as a link.
@@ -228,10 +172,8 @@ export function buildWikilinkIndex(
228
172
  // type and a UUID carries the pack name, so the address
229
173
  // cannot be derived from the type alone.
230
174
  uuid: compendiumUuid(packageId, d.type, d.id, d.pack),
231
- // An item's prose compiles into a separate JournalEntry,
232
- // addressed by the virtual `doc<type>` qualifier. Its id
233
- // is derived from the item's, so its address is knowable
234
- // here too.
175
+ // The readable note has a separate JournalEntry whose id
176
+ // is derived from the item's id.
235
177
  docUuid: compendiumUuid(packageId, "doc", itemDocEntryId(d.id), d.docPack),
236
178
  },
237
179
  );
@@ -249,19 +191,8 @@ export function buildWikilinkIndex(
249
191
  for (const v of foreignByKey.values()) {
250
192
  if (v.type) foreignTypes.push(norm(v.type));
251
193
  }
252
- // A manifest publishes `doc<type>` addresses, but `doc<type>` is a
253
- // *virtual* qualifier formed by prefix — never a real type. Admitting it
254
- // here would make it one, and a real type owns its own name, so the
255
- // virtual reading would stop firing and every `[[docskill-wpnc]]` would
256
- // resolve nowhere. The virtual form still reaches a foreign documentation
257
- // entry: it reads as `skill` + `itemDoc`, which the manifest lookup then
258
- // asks for as `docskill`.
259
- //
260
- // Real types are admitted **first**, in a pass of their own, so the test
261
- // below sees the complete set. Done in one pass it would depend on
262
- // manifest iteration order wherever the base type is published only by a
263
- // foreign package — `docmacro` admitted or not according to whether
264
- // `macro` happened to come first.
194
+ // A legacy index may contain `doc<type>` records. Keep the authored type
195
+ // vocabulary separate so the input alias can resolve to its base type.
265
196
  for (const t of foreignTypes) {
266
197
  if (!t.startsWith(ITEM_DOC_PREFIX)) types.add(t);
267
198
  }
@@ -334,7 +265,8 @@ function findForeign(index, read) {
334
265
  */
335
266
  function foreignHits(index, read) {
336
267
  if (!read || read.reason || !index.foreign?.size) return [];
337
- const wanted = norm(read.itemDoc ? `doc${read.type}` : read.type);
268
+ const tuple = completeAddress(read, { package: index.contentPackage, system: NOTE_SYSTEM });
269
+ const wanted = norm(tuple.type);
338
270
  const shortcode = norm(read.shortcode);
339
271
 
340
272
  // **An omitted package means this package**, so a short form
@@ -348,7 +280,7 @@ function foreignHits(index, read) {
348
280
  // than a filter. That is what makes a cross-package `ambiguous` impossible:
349
281
  // one key, one entry.
350
282
  if (!read.package) return [];
351
- const wantedSystem = norm(read.system ?? NO_SYSTEM);
283
+ const wantedSystem = norm(tuple.system);
352
284
  const hits = [];
353
285
  for (const [key, entry] of index.foreign) {
354
286
  const parts = readCanonicalKey(key);
@@ -460,7 +392,7 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index }) {
460
392
  const out = replaceOutsideCode(markdown, WIKILINK, (all, rawInner, offset) => {
461
393
  const parsed = readWikilink(rawInner, {
462
394
  package: index.contentPackage,
463
- system: "none",
395
+ system: "note",
464
396
  types: index.types,
465
397
  packages: index.packages,
466
398
  noIndexPackages: index.noIndexPackages,
@@ -516,20 +448,13 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index }) {
516
448
  });
517
449
  return unresolvedLink(text || target, target);
518
450
  }
519
- // An omitted system defaults from where the link is written,
520
- // and a body is under no system block, so it is `none`. Under
521
- // `none` a system-bearing type addresses its *documentation* — a
522
- // note's `none` address IS its `doc<type>` entry — which is what a
523
- // prose link almost always means. Stating the system is how prose
524
- // reaches the Item instead.
525
- //
526
- // Only a type whose *own* document carries a system is redirected.
527
- // A `macro` and the map types have documentation journals too, but
528
- // their own documents are core ones already at `none`, so
529
- // `macro-autoattack` names the Macro and `docmacro-autoattack` its
530
- // journal — two live addresses the redirect would collapse.
451
+ // Ordinary wikilinks default to readable note content.
531
452
  const baseType = resolveItemDocType(qualified.type, index.types);
532
- itemDoc = Boolean(baseType);
453
+ const address = completeAddress(qualified, {
454
+ package: index.contentPackage,
455
+ system: NOTE_SYSTEM,
456
+ });
457
+ itemDoc = address.system === NOTE_SYSTEM && hasDocEntry(baseType ?? qualified.type);
533
458
  if (qualified.package === index.contentPackage)
534
459
  doc = index.byShortcode.get(`${baseType ?? qualified.type}/${qualified.shortcode}`);
535
460
  }
@@ -615,7 +540,7 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index }) {
615
540
  // *sheet*, which has no sections, so the anchor has nothing to address
616
541
  // and is dropped. Forging a JournalEntryPage id onto a document that
617
542
  // can never hold one is what made such links dead-end; an
618
- // item's pages are addressed through its `doc<type>` counterpart.
543
+ // an item's pages are addressed through its `note` Address.
619
544
  // A `#section` the target declares no heading for. Checked here, and
620
545
  // not only by `content-build links`, because this is the build that
621
546
  // *emits* the link: `anchorPageId` will hash any slug into a page id,
@@ -672,7 +597,7 @@ export function resolveReference(index, ref, hint) {
672
597
  );
673
598
  const tuple = parseAddress(ref, {
674
599
  package: index.contentPackage,
675
- system: "none",
600
+ system: "note",
676
601
  types: index.types,
677
602
  packages: index.packages,
678
603
  noIndexPackages: index.noIndexPackages,
@@ -699,15 +624,15 @@ export function resolveReference(index, ref, hint) {
699
624
  if (foreign) return { ...foreign, address: tuple };
700
625
  return undefined;
701
626
  }
702
- const base = resolveItemDocType(tuple.type, index.types);
703
- const type = base ?? tuple.type;
704
- if (tuple.system !== (base ? NO_SYSTEM : systemOf(type, KNOWN_DOCUMENT_SUBTYPE_MAPS)))
627
+ const type = tuple.type;
628
+ const documentation = tuple.system === NOTE_SYSTEM && hasDocEntry(type);
629
+ if (tuple.system !== (documentation ? NOTE_SYSTEM : ownDocumentSystem(type)))
705
630
  return undefined;
706
631
  const local = index.byShortcode?.get(`${type}/${tuple.shortcode}`);
707
632
  if (!local) return undefined;
708
633
  const addresses = index.uuidByDoc?.get(local);
709
634
  const uuid =
710
- base ?
635
+ documentation ?
711
636
  Object.hasOwn(local, "documentationUuid") ?
712
637
  local.documentationUuid
713
638
  : addresses?.docUuid
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "22.12.3",
3
+ "version": "22.13.0",
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",
@@ -96,8 +96,7 @@
96
96
  },
97
97
  "bin": {
98
98
  "package-build": "./bin/package-build.mjs",
99
- "content-build": "./bin/content-build.mjs",
100
- "heroiclands-content-language-server": "./bin/heroiclands-content-language-server.mjs"
99
+ "content-build": "./bin/content-build.mjs"
101
100
  },
102
101
  "files": [
103
102
  "CHANGELOG-content-build.md",
package/sohl/infobox.mjs CHANGED
@@ -270,7 +270,7 @@ function itemPresentation(item, resolve, block) {
270
270
  if (!item.shortcode) return undefined;
271
271
  if (!item.address)
272
272
  return resolve?.(item.shortcode, { kind: "shortcode", type: item.type, system: block });
273
- const documentation = completeAddress({ ...item.address, system: "none" });
273
+ const documentation = completeAddress({ ...item.address, system: "note" });
274
274
  const found = resolve?.(documentation, { type: documentation.type });
275
275
  const native =
276
276
  !found?.name || !found?.subType ? resolve?.(item.address, { type: item.type }) : undefined;
@@ -556,7 +556,7 @@ export function affiliationSections(fm, ctx) {
556
556
  const entries = value.map((skill) => {
557
557
  const native = ctx.resolve?.(skill, { type: "skill", system: "sohl" });
558
558
  const docAddress =
559
- isAddressTuple(skill) ? completeAddress({ ...skill, system: "none" }) : undefined;
559
+ isAddressTuple(skill) ? completeAddress({ ...skill, system: "note" }) : undefined;
560
560
  const doc = docAddress ? ctx.resolve?.(docAddress, { type: docAddress.type }) : undefined;
561
561
  return {
562
562
  text: doc?.name || native?.name || humanizeValue(skill),
@@ -1,14 +1,9 @@
1
1
  /**
2
- * A complete Address: four segments, and nothing else.
3
- *
4
- * `type` is the segment the canonical string carries, after the `doc<type>`
5
- * redirect. There is no fifth field recording the written form, because the
6
- * written form is a rendering rather than part of the value.
2
+ * A complete Address: four segments, with no memory of the authored form.
7
3
  *
8
4
  * @typedef {object} AddressTuple
9
5
  * @property {string} package - The owning **content** package.
10
- * @property {string} system - The system whose document this addresses, or
11
- * `none`.
6
+ * @property {string} system - `note`, `none`, or a game system id.
12
7
  * @property {string} type - The type segment of the canonical Address.
13
8
  * @property {string} shortcode - The note's `shortcode`.
14
9
  */
@@ -33,29 +28,8 @@
33
28
  * @typedef {{reason: string, package?: string}} AddressProblem
34
29
  */
35
30
  /**
36
- * Every content type whose **prose compiles into a JournalEntry of its own**,
37
- * addressed by the virtual `doc<type>` qualifier.
38
- *
39
- * Every item type, every actor type, every map type, plus `macro` — a macro
40
- * note's body documents the script the note also compiles into a Macro, which
41
- * is the same shape as an item and its description: one note, two documents,
42
- * the prose living in the journals pack.
43
- *
44
- * **One set, read by the compilers and by the grammar alike.** The journals
45
- * pass decides what to compile from it, the link manifest decides what to
46
- * publish a `doc<type>` entry for, and the parser decides whether `doc<type>`
47
- * is a legal type segment. Held apart, the three drift into a manifest that
48
- * asserts documentation nothing compiled, a compiled entry no consumer can
49
- * address, or an authored `doc<type>` link that stops parsing. It is composed
50
- * exactly once, in `defineConfig`, and read from there — never recomposed at a
51
- * call site.
52
- *
53
- * `doc` notes are absent, for the reason that applies to them alone: a `doc`
54
- * note's single document *is* its prose, so there is no second document for a
55
- * `doc<type>` address to name.
56
- *
57
- * An accessor rather than a hoisted constant, so that importing this module
58
- * needs no configuration.
31
+ * The configured types whose prose compiles into a separate JournalEntry.
32
+ * The journal has a `note` Address with the authored type segment.
59
33
  *
60
34
  * @returns {ReadonlySet<string>} The configured doc-carrying types.
61
35
  */
@@ -69,30 +43,15 @@ export function docEntryTypes(): ReadonlySet<string>;
69
43
  * `macro`; false for `doc`.
70
44
  */
71
45
  export function hasDocEntry(type: string): boolean;
46
+ /** The segment of a note's own document. */
47
+ export function ownDocumentSystem(type: any): string;
72
48
  /**
73
- * Reads a qualifier as the **virtual `doc<type>`** form, or reports that it is
74
- * not one.
75
- *
76
- * A document and its documentation are two documents in two packs, so they need
77
- * two addresses. `skill-wpnc` is the item; `docskill-wpnc` is the JournalEntry
78
- * its prose compiled into, and `docmacro-autoattack` is the same arrangement
79
- * for a macro.
80
- *
81
- * The virtual form exists for a type that carries separate documentation
82
- * ({@link docEntryTypes} — the set the journals compiler and the link manifest
83
- * read too), **or** for one that routes to the items pack. The second clause
84
- * covers the open, unenumerated set: types that compile into items are not
85
- * enumerated anywhere, and a foreign package may publish an item type this
86
- * build has never heard of. Dropping it would silently unlink every `doc<type>`
87
- * address into such a package.
88
- *
89
- * A **real** type of the same name always wins: the virtual reading is only
90
- * consulted for a qualifier no authored note claims.
91
- *
92
- * @param {string} qualifier - The already-normalised type segment.
93
- * @param {Set<string>} types - Every type the content tree contains.
94
- * @returns {string|null} The underlying document type, or `null` when the
95
- * qualifier is not a virtual one.
49
+ * Read a `doc<type>` input qualifier as the underlying authored type.
50
+ * A real type with that spelling takes precedence.
51
+ *
52
+ * @param {string} qualifier - The type segment from the input.
53
+ * @param {Set<string>} types - Types in the content tree.
54
+ * @returns {string|null} The underlying type, or `null`.
96
55
  */
97
56
  export function resolveItemDocType(qualifier: string, types: Set<string>): string | null;
98
57
  /**
@@ -164,29 +123,17 @@ export function isAddressTuple(value: unknown): boolean;
164
123
  */
165
124
  export function renderAddress(tuple: AddressTuple): string;
166
125
  /**
167
- * Whether the type an Address names is one this position accepts.
126
+ * Whether the canonical type an Address names is accepted at this position.
168
127
  *
169
- * The second of the two questions, and separate from the first because the
170
- * accepted **set** is a property of the position while the grammar is not.
171
- *
172
- * **An accepted type carries its documentation form with it.** A position
173
- * accepting `weapongear` accepts `docweapongear`, because the journal that
174
- * documents a weapon is the same thing to a position that wanted a weapon — and
175
- * a prose address of one *is* the journal, so refusing it would refuse the form
176
- * an author most often writes. It is the widening `defineConfig` applies to the
177
- * types a package may claim, applied here to the types a position may take.
178
- *
179
- * @param {AddressTuple} tuple - A parsed Address.
180
- * @param {Iterable<string>} allowed - The types this position accepts.
181
- * @returns {boolean} True when the Address names one of them.
128
+ * @param {AddressTuple} tuple - A complete Address.
129
+ * @param {Iterable<string>} allowed - Accepted types.
130
+ * @returns {boolean} Whether the type is accepted.
182
131
  */
183
132
  export function acceptsType(tuple: AddressTuple, allowed: Iterable<string>): boolean;
184
133
  /**
185
134
  * Apply the defaults to a partial reading, giving a complete Address.
186
135
  *
187
- * The three rules of the module note, applied in the order they depend on each
188
- * other: the package, then the system the type may fix, then the redirect the
189
- * system may force.
136
+ * Complete a parsed Address using the citing position's package and system.
190
137
  *
191
138
  * @param {{type: string, shortcode: string, package?: string, system?: string,
192
139
  * itemDoc?: boolean}} read - A partial reading, as {@link readQualifier}
@@ -351,23 +298,10 @@ export const ADDRESS_SEPARATOR: string;
351
298
  * @type {number}
352
299
  */
353
300
  export const CANONICAL_KEY_SEGMENTS: number;
301
+ /** The accepted `doc<type>` input prefix for readable note links. */
302
+ export const ITEM_DOC_PREFIX: "doc";
354
303
  /**
355
- * The qualifier prefix that addresses a note's **documentation** rather than
356
- * the note's own document: `docskill-wpnc` is the JournalEntry that
357
- * `skill-wpnc`'s prose compiled into.
358
- *
359
- * Every doc-carrying type has a `doc<type>` counterpart, formed by prefix and
360
- * never enumerated, so the prefix itself is the whole of that vocabulary.
361
- *
362
- * @type {string}
363
- */
364
- export const ITEM_DOC_PREFIX: string;
365
- /**
366
- * A complete Address: four segments, and nothing else.
367
- *
368
- * `type` is the segment the canonical string carries, after the `doc<type>`
369
- * redirect. There is no fifth field recording the written form, because the
370
- * written form is a rendering rather than part of the value.
304
+ * A complete Address: four segments, with no memory of the authored form.
371
305
  */
372
306
  export type AddressTuple = {
373
307
  /**
@@ -375,8 +309,7 @@ export type AddressTuple = {
375
309
  */
376
310
  package: string;
377
311
  /**
378
- * - The system whose document this addresses, or
379
- * `none`.
312
+ * - `note`, `none`, or a game system id.
380
313
  */
381
314
  system: string;
382
315
  /**
@@ -69,12 +69,9 @@ export function packageAddress(fm: object): string;
69
69
  *
70
70
  * The **enclosing system block** decides, at any depth within it, and nothing
71
71
  * else does: `sohl.items[3].model` and `sohl.system.body.structure` are both
72
- * `sohl` because both sit under `sohl:`. Everywhere else is {@link NO_SYSTEM} —
73
- * top-level frontmatter, the shared `data:` container, and body prose, which has
74
- * no key path at all and passes `undefined`.
75
- *
76
- * It is the block rather than the field, so an `Address` field needs no opinion
77
- * about systems and no per-field table has to be kept in step with the schema.
72
+ * `sohl` because both sit under `sohl:`. Everywhere else takes
73
+ * {@link NOTE_SYSTEM}; fields declared for assets or folders supply their own
74
+ * `none` default at the field parser.
78
75
  *
79
76
  * The first segment must **be** a declared system, not merely look like one:
80
77
  * `sohlish.items` is a key called `sohlish`, and `notes.sohl.thing` names no
@@ -39,12 +39,8 @@ export function anchorsOf(body: string): Set<string>;
39
39
  * before, since the walk read the file and it then read it again for the raw
40
40
  * text.
41
41
  *
42
- * The index mirrors what both builds construct, including the two addresses a
43
- * doc-carrying note answers to: `type/shortcode` for the document, and
44
- * `doc<type>/shortcode` for the JournalEntry its prose compiles into. Once a
45
- * manifest publishes `doc<type>` entries that prefix is a *known type*, and the
46
- * virtual reading that used to answer for it no longer fires — a real type owns
47
- * its own name — so the note is indexed under both.
42
+ * The index holds each compiled document under its complete Address. Readable
43
+ * content uses `note`, and system documents use their declared system.
48
44
  *
49
45
  * @param {string} contentBase - Root of the content tree.
50
46
  * @param {object} [opts]
@@ -30,6 +30,7 @@ export const STRUCTURED_ADDRESSES: readonly ({
30
30
  shape: string;
31
31
  type: string;
32
32
  accepts: string[];
33
+ system: string;
33
34
  noteType?: undefined;
34
35
  } | {
35
36
  noteType: string;
@@ -37,5 +38,6 @@ export const STRUCTURED_ADDRESSES: readonly ({
37
38
  shape: string;
38
39
  type: string;
39
40
  accepts: string[];
41
+ system?: undefined;
40
42
  })[];
41
43
  export { AddressEntries, encodeAddresses, cloneAddressState } from "./address-values.mjs";
@@ -80,6 +80,8 @@ export function assertSystemCharset(segments: Iterable<string>, where?: string):
80
80
  * @type {string}
81
81
  */
82
82
  export const NO_SYSTEM: string;
83
+ /** The Address segment for a note's readable content. */
84
+ export const NOTE_SYSTEM: "note";
83
85
  /**
84
86
  * Every game system this toolchain compiles for.
85
87
  *
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env node
2
- /*
3
- * SPDX-License-Identifier: GPL-3.0-or-later
4
- */
5
-
6
- import { runLanguageServer } from "../engine/content-language-server.mjs";
7
-
8
- runLanguageServer();
@@ -1,25 +0,0 @@
1
- # Content language server
2
-
3
- `heroiclands-content-language-server` provides editor navigation for Markdown notes in a HeroicLands content project. It is a stdio Language Server Protocol process; an editor starts it from the project root. The command uses the same `package-build.config.yaml` and Address rules as `content-build`.
4
-
5
- Build the content index in the project before starting navigation:
6
-
7
- ```sh
8
- npx content-build content-index
9
- ```
10
-
11
- The generated JSONL file under the configured `paths.contentIndex` is the source for note names, aliases, tags, shortcodes, Addresses, and destinations. Search covers the current project. Search and destinations reflect the saved index, so run the index command after changing note metadata. The language server reads a completed replacement index on its next request. An editor's unsaved buffer text is used to identify the Address at the cursor, but it does not add search results.
12
-
13
- | LSP request | Behavior |
14
- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
15
- | `textDocument/definition` | Follows an Address or wikilink to the indexed note. An anchor lands on its indexed line. |
16
- | `workspace/symbol` | Finds notes by name, alias, ASCII name, shortcode, or Address. `tag:myth` searches tags. One result appears per source note. |
17
- | `textDocument/references` | Finds authored wikilinks, embeds, and declared frontmatter Address values or keys. Ordinary prose is excluded. |
18
-
19
- The server reports a missing index with the `content-build content-index` command. Reference search scans Markdown notes in the configured content tree. The server writes only LSP messages to stdout, and it uses UTF-16 positions as required by the default LSP position encoding.
20
-
21
- ## Editor integration
22
-
23
- The [HeroicLands Emacs package](https://github.com/HeroicLands/heroiclands-emacs) connects this command through Eglot in content Markdown buffers. Eglot maps `M-.` to definition, `C-M-.` to workspace search, `M-?` to references, and `M-,` to the Xref location stack.
24
-
25
- An editor other than Emacs starts the executable from the package root and associates it with Markdown notes under the configured content directory. The process handles `initialize`, `shutdown`, `exit`, full and incremental document synchronization, definition, references, and workspace symbols. It does not advertise completion, diagnostics, rename, or document symbols.