@glossarist/concept-browser 0.7.113 → 0.7.115

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.
Files changed (86) hide show
  1. package/README.md +87 -2
  2. package/cli/index.mjs +15 -15
  3. package/cli/index.ts +9 -11
  4. package/data/concept-model/shapes/glossarist.concept.yaml.schema.json +255 -0
  5. package/package.json +14 -12
  6. package/scripts/__tests__/{bibliography.test.mjs → bibliography.test.ts} +1 -1
  7. package/scripts/__tests__/{cli-script-contract.test.mjs → cli-script-contract.test.ts} +3 -3
  8. package/scripts/__tests__/{concept-groups.test.mjs → concept-groups.test.ts} +1 -1
  9. package/scripts/__tests__/{doctor.test.mjs → doctor.test.ts} +1 -1
  10. package/scripts/__tests__/{fetch-datasets.test.mjs → fetch-datasets.test.ts} +1 -1
  11. package/scripts/__tests__/{first-non-empty.test.mjs → first-non-empty.test.ts} +1 -1
  12. package/scripts/__tests__/{process-about-pages.test.mjs → process-about-pages.test.ts} +1 -1
  13. package/scripts/{bridge-to-astro.mjs → bridge-to-astro.ts} +2 -4
  14. package/scripts/{build-edges.js → build-edges.ts} +11 -13
  15. package/scripts/{check-syntax.mjs → check-syntax.ts} +20 -13
  16. package/scripts/{fetch-datasets.mjs → fetch-datasets.ts} +2 -2
  17. package/scripts/{generate-data.mjs → generate-data.ts} +73 -460
  18. package/scripts/{generate-ontology-schema.mjs → generate-ontology-schema.ts} +2 -2
  19. package/scripts/lib/{build-cache.mjs → build-cache.ts} +3 -2
  20. package/scripts/lib/concept-formats.ts +206 -0
  21. package/scripts/lib/concept-uri.ts +14 -0
  22. package/scripts/lib/{dataset-turtle.mjs → dataset-turtle.ts} +4 -3
  23. package/scripts/lib/harmonize-concept.ts +86 -0
  24. package/scripts/lib/jsonld-emitter.ts +169 -0
  25. package/scripts/lib/{local-path-safety.mjs → local-path-safety.ts} +6 -2
  26. package/scripts/lib/yaml-types.ts +262 -0
  27. package/scripts/{process-about-pages.mjs → process-about-pages.ts} +1 -1
  28. package/scripts/{smoke.mjs → smoke.ts} +4 -3
  29. package/scripts/{validate-shacl.mjs → validate-shacl.ts} +5 -5
  30. package/scripts/validate-yaml.ts +144 -0
  31. package/src/__tests__/extract-source-refs.test.ts +2 -3
  32. package/src/__tests__/perf/serialization-perf.test.ts +5 -5
  33. package/src/__tests__/scripts/agents-turtle.test.ts +1 -1
  34. package/src/__tests__/scripts/bibliography-turtle.test.ts +1 -1
  35. package/src/__tests__/scripts/build-activity-turtle.test.ts +1 -1
  36. package/src/__tests__/scripts/build-cache.test.ts +1 -1
  37. package/src/__tests__/scripts/build-integration.test.ts +19 -19
  38. package/src/__tests__/scripts/dataset-turtle.test.ts +1 -1
  39. package/src/__tests__/scripts/migrate-v1-to-v3.test.ts +1 -1
  40. package/src/__tests__/scripts/normalize-yaml.test.ts +1 -1
  41. package/src/__tests__/scripts/turtle-escape.test.ts +1 -1
  42. package/src/__tests__/scripts/validate-yaml.test.ts +76 -0
  43. package/src/__tests__/scripts/version-turtle.test.ts +1 -1
  44. package/src/__tests__/scripts/vocab-turtle.test.ts +1 -1
  45. package/src/__tests__/scripts-syntax.test.ts +2 -2
  46. package/src/__tests__/use-rdf-document.test.ts +2 -3
  47. package/src/__tests__/utils/content-renderer-xss.test.ts +57 -0
  48. package/src/__tests__/utils/inline-content-kinds.test.ts +139 -0
  49. package/src/__tests__/validate-shacl.test.ts +3 -3
  50. package/src/adapters/wire/index.ts +69 -0
  51. package/src/pages/[...path].astro +1 -0
  52. package/src/router/index.ts +5 -0
  53. package/src/utils/content-renderer.ts +106 -23
  54. package/src/utils/url-safety.ts +1 -5
  55. package/src/views/learn/LearnInlineContentView.vue +128 -0
  56. package/tsconfig.json +2 -2
  57. package/vite.config.ts +1 -1
  58. package/src/__tests__/composables/use-concept-edges-v2.test.ts +0 -167
  59. package/src/__tests__/composables/use-concept-edges-v2.test.ts.bak +0 -166
  60. package/src/types/agents-version-turtle.d.ts +0 -27
  61. package/src/types/bibliography-turtle.d.ts +0 -12
  62. package/src/types/build-activity-turtle.d.ts +0 -16
  63. package/src/types/build-cache.d.ts +0 -20
  64. package/src/types/dataset-turtle.d.ts +0 -32
  65. package/src/types/normalize-yaml.d.ts +0 -16
  66. package/src/types/turtle-escape.d.ts +0 -6
  67. package/src/types/vocab-turtle.d.ts +0 -13
  68. /package/scripts/{doctor.mjs → doctor.ts} +0 -0
  69. /package/scripts/{extract-source-refs.js → extract-source-refs.ts} +0 -0
  70. /package/scripts/{generate-404.js → generate-404.ts} +0 -0
  71. /package/scripts/{generate-ontology-data.mjs → generate-ontology-data.ts} +0 -0
  72. /package/scripts/lib/{agents-turtle.mjs → agents-turtle.ts} +0 -0
  73. /package/scripts/lib/{bibliography-turtle.mjs → bibliography-turtle.ts} +0 -0
  74. /package/scripts/lib/{bibliography.mjs → bibliography.ts} +0 -0
  75. /package/scripts/lib/build/{image-assets.mjs → image-assets.ts} +0 -0
  76. /package/scripts/lib/build/{non-verbal-consumer.mjs → non-verbal-consumer.ts} +0 -0
  77. /package/scripts/lib/{build-activity-turtle.mjs → build-activity-turtle.ts} +0 -0
  78. /package/scripts/lib/{concept-groups.mjs → concept-groups.ts} +0 -0
  79. /package/scripts/lib/{first-non-empty.mjs → first-non-empty.ts} +0 -0
  80. /package/scripts/lib/{turtle-escape.mjs → turtle-escape.ts} +0 -0
  81. /package/scripts/lib/{version-turtle.mjs → version-turtle.ts} +0 -0
  82. /package/scripts/lib/{vocab-turtle.mjs → vocab-turtle.ts} +0 -0
  83. /package/scripts/{load-site-config.mjs → load-site-config.ts} +0 -0
  84. /package/scripts/{migrate-v1-to-v3.mjs → migrate-v1-to-v3.ts} +0 -0
  85. /package/scripts/{normalize-yaml.mjs → normalize-yaml.ts} +0 -0
  86. /package/scripts/{sync-concept-model.mjs → sync-concept-model.ts} +0 -0
package/README.md CHANGED
@@ -39,14 +39,57 @@ Your project (CWD) Package (read-only)
39
39
  │ └── my-vocab/ ─┤ ┌──────────────────────────┐
40
40
  │ └── concepts/*.yaml ─┤ │ @glossarist/concept-browser │
41
41
  ├── public/ ─┤ ──> │ │
42
- │ └── (logos, favicons) ─┤ │ generate-data.mjs │
43
- ├── .cb-content/ ← generated │ │ bridge-to-astro.mjs │
42
+ │ └── (logos, favicons) ─┤ │ generate-data.ts │
43
+ ├── .cb-content/ ← generated │ │ bridge-to-astro.ts │
44
44
  └── dist/ ← output │ │ astro build │
45
45
  ──┘ └──────────────────────────┘
46
46
  ```
47
47
 
48
48
  The package reads your config and data, generates `.cb-content/` (content collections) and `dist/` (static site) in your project. The package itself (`node_modules/`) is never modified.
49
49
 
50
+ ## Data / Deployment Boundary
51
+
52
+ There is a strict separation between **dataset authors** and **deployers**:
53
+
54
+ | Role | Owns | Does NOT touch |
55
+ |------|------|----------------|
56
+ | **Dataset author** | YAML concept files, bibliography, figures/tables | site-config.yml, deployment URLs, other datasets |
57
+ | **Deployer** | site-config.yml, URI patterns, branding, fonts | dataset content, inline references, concept IDs |
58
+ | **concept-browser** | Resolves every citation at runtime | — |
59
+
60
+ The same dataset YAML works in any deployment. A citation like `{{cite:iso704}}` resolves differently depending on which datasets are co-deployed — the deployer's `site-config.yml:uriPatterns` decides.
61
+
62
+ ## Inline Content Syntax
63
+
64
+ All inline references in concept text use the unified `{{kind:target}}` notation:
65
+
66
+ | Kind | Syntax | Example | Description |
67
+ |------|--------|---------|-------------|
68
+ | cite | `{{cite:sourceId}}` | `{{cite:iso7301}}` | Cite a ConceptSource from this concept's `sources[]` |
69
+ | cite+label | `{{cite:id, label}}` | `{{cite:iso7301, rice}}` | Same, with explicit display label |
70
+ | urn | `{{urn:URN}}` | `{{urn:iso:std:iso:704}}` | Reference via URN routing |
71
+ | fig | `{{fig:id}}` | `{{fig:diagram_3}}` | Reference a figure entity |
72
+ | table | `{{table:id}}` | `{{table:unit_list}}` | Reference a table entity |
73
+ | formula | `{{formula:id}}` | `{{formula:ohm_law}}` | Reference a formula entity |
74
+ | bib | `{{bib:id}}` | `{{bib:ref_1}}` | Bibliography entry (no underlying concept) |
75
+ | link | `{{link:URL}}` | `{{link:https://example.com}}` | External URL |
76
+ | link+label | `{{link:URL, label}}` | `{{link:https://example.com, click here}}` | External URL with label |
77
+ | image | `{{image:src}}` | `{{image:diagram.png}}` | Inline image embed |
78
+ | image+alt | `{{image:src, alt}}` | `{{image:diagram.png, The diagram}}` | Image with alt text |
79
+ | designation | `{{designation}}` | `{{measurement unit}}` | Reference by designation text |
80
+ | numeric | `{{numeric_id}}` | `{{112-01-10}}` | Reference by numeric ID |
81
+
82
+ The deprecated `<<ref,title>>` AsciiDoc xref syntax still works but emits a console warning. Migrate to the unified syntax.
83
+
84
+ ### Citation Resolution Cascade
85
+
86
+ When concept-browser encounters `{{cite:sourceId}}`, it walks this cascade:
87
+
88
+ 1. **uriPatterns** — Is there a co-deployed dataset matching this URI? → internal link
89
+ 2. **routing[]** — Is there a routing entry in site-config.yml? → external link
90
+ 3. **citation.link** — Does the source have a canonical link? → flat bibliography record
91
+ 4. **unresolved** — No match → plain text
92
+
50
93
  ## Configuration
51
94
 
52
95
  Everything is in `site-config.yml`. See [`site-config.example.yml`](site-config.example.yml) for all options.
@@ -293,6 +336,48 @@ The page appears in the sidebar navigation automatically.
293
336
 
294
337
  See [`site-config.example.yml`](site-config.example.yml) for all options including UI languages, fonts, features, and dataset groups.
295
338
 
339
+ ## Data/Deployment Boundary
340
+
341
+ Concept-browser enforces a strict separation between **data** (authored by dataset authors) and **deployment** (configured by deployers):
342
+
343
+ - **Dataset authors** write concepts, sources, bibliography, and inline mentions. They don't know where their dataset will be deployed or what other datasets will be co-deployed.
344
+ - **Deployers** write `site-config.yml` to register datasets, declare URI patterns, and optionally add routing entries for external datasets. They never edit dataset content.
345
+ - **Concept-browser** resolves every cross-reference at runtime via a fixed cascade, making multiple datasets behave as one coherent whole.
346
+
347
+ ### The resolution cascade
348
+
349
+ Every `{{cite:...}}` and `{{urn:...}}` mention walks this cascade at render time:
350
+
351
+ 1. **`uriPatterns`** — is there a co-deployed dataset that matches? → internal link (case 1)
352
+ 2. **`routing[]`** — is there a routing entry for the URI? → external link (case 2)
353
+ 3. **`citation.link`** — does the source have a canonical link? → flat bib record (case 3)
354
+ 4. **Unresolved** — plain text
355
+
356
+ The same YAML renders differently in different deployments. The data never changes.
357
+
358
+ ## Inline Content Syntax
359
+
360
+ All inline references in concept text use the unified `{{kind:target}}` notation:
361
+
362
+ | Kind | Example | What it does |
363
+ |---|---|---|
364
+ | `cite` | `{{cite:sourceId}}` | Cite a `ConceptSource` from this concept's `sources[]` — walks the full resolution cascade |
365
+ | `urn` | `{{urn:iso:std:iso:704}}` | Reference a concept via URN routing |
366
+ | `fig` | `{{fig:diagram_3}}` | Reference a figure entity in the same dataset |
367
+ | `table` | `{{table:units}}` | Reference a table entity |
368
+ | `formula` | `{{formula:ohm_law}}` | Reference a formula entity |
369
+ | `bib` | `{{bib:ref_1}}` | Reference a bibliography entry (case-3-only, no underlying concept) |
370
+ | `link` | `{{link:https://example.com}}` | External URL (canonical, deployment-independent) |
371
+ | `image` | `{{image:src, alt}}` | Inline image embed |
372
+ | *(none)* | `{{measurement unit}}` | Designation match in same dataset |
373
+ | *(none)* | `{{112-01-10}}` | Numeric ID match in same dataset |
374
+
375
+ Each kind accepts an optional label: `{{kind:target, label}}`.
376
+
377
+ **Deprecated:** `<<ref,title>>` (AsciiDoc xref syntax) emits a deprecation warning. Migrate to `{{kind:target}}`.
378
+
379
+ See `/learn/inline-content` on any deployed site for a full interactive reference.
380
+
296
381
  ## CLI Commands
297
382
 
298
383
  ```
package/cli/index.mjs CHANGED
@@ -3,15 +3,15 @@
3
3
  /**
4
4
  * Glossarist Concept Browser CLI — runtime entrypoint.
5
5
  *
6
- * This is the published bin entry. It stays as plain JS (no tsx dependency)
7
- * so consumers who install with --omit=dev can use it.
8
- *
9
- * The TypeScript version lives at cli/index.ts for type-checking during
10
- * development. The two files should be kept in sync manually until
11
- * TODO.typescript/03 adds a compile step.
6
+ * Registers tsx as the ESM loader so dynamic imports of .ts scripts
7
+ * work at runtime. tsx is a regular dependency (not devDep), so it
8
+ * survives --omit=dev.
12
9
  */
13
10
 
14
- import { loadSiteConfig } from '../scripts/load-site-config.mjs';
11
+ import { register } from 'node:module';
12
+ register('tsx/esm', import.meta.url);
13
+
14
+ import { loadSiteConfig } from '../scripts/load-site-config.ts';
15
15
  import { existsSync } from 'fs';
16
16
  import { resolve, dirname } from 'path';
17
17
  import { fileURLToPath } from 'url';
@@ -20,10 +20,10 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
20
20
  const pkgRoot = resolve(__dirname, '..');
21
21
 
22
22
  const commands = {
23
- fetch: async () => (await import('../scripts/fetch-datasets.mjs')).main(),
24
- generate: async () => { await import('../scripts/generate-data.mjs'); },
25
- edges: async () => (await import('../scripts/build-edges.js')).main(),
26
- about: async () => (await import('../scripts/process-about-pages.mjs')).main(),
23
+ fetch: async () => (await import('../scripts/fetch-datasets.ts')).main(),
24
+ generate: async () => { await import('../scripts/generate-data.ts'); },
25
+ edges: async () => (await import('../scripts/build-edges.ts')).main(),
26
+ about: async () => (await import('../scripts/process-about-pages.ts')).main(),
27
27
  };
28
28
 
29
29
  function parseArgs(argv) {
@@ -294,7 +294,7 @@ Environment:
294
294
  }
295
295
 
296
296
  console.log(`\n=== BRIDGE DATA ===\n`);
297
- const bridge = resolve(pkgRoot, 'scripts', 'bridge-to-astro.mjs');
297
+ const bridge = resolve(pkgRoot, 'scripts', 'bridge-to-astro.ts');
298
298
  if (existsSync(bridge)) {
299
299
  await import(`file://${bridge}`);
300
300
  }
@@ -319,7 +319,7 @@ Environment:
319
319
  await viteBuild({ configFile: viteConfig, root: pkgRoot, mode: 'production' });
320
320
  }
321
321
 
322
- const postbuild = resolve(pkgRoot, 'scripts', 'generate-404.js');
322
+ const postbuild = resolve(pkgRoot, 'scripts', 'generate-404.ts');
323
323
  if (existsSync(postbuild)) {
324
324
  await import(`file://${postbuild}`);
325
325
  }
@@ -334,7 +334,7 @@ Environment:
334
334
  }
335
335
 
336
336
  if (cmd === 'normalize') {
337
- const { normalizeYaml } = await import('../scripts/normalize-yaml.mjs');
337
+ const { normalizeYaml } = await import('../scripts/normalize-yaml.ts');
338
338
  const check = process.argv.includes('--check');
339
339
  const paths = process.argv.slice(2).filter(a => !a.startsWith('-') && a !== 'normalize');
340
340
  const { checked, nonNfc, fixed } = normalizeYaml({ check, paths });
@@ -357,7 +357,7 @@ Environment:
357
357
  }
358
358
 
359
359
  if (cmd === 'doctor') {
360
- const { main: doctorMain } = await import('../scripts/doctor.mjs');
360
+ const { main: doctorMain } = await import('../scripts/doctor.ts');
361
361
  await doctorMain();
362
362
  return;
363
363
  }
package/cli/index.ts CHANGED
@@ -21,10 +21,7 @@
21
21
  * GITHUB_TOKEN GitHub token for private repos
22
22
  */
23
23
 
24
- // Scripts are still .mjs (TODO.typescript/02). These imports will be fully
25
- // typed once the scripts convert to .ts.
26
- // @ts-expect-error — .mjs module without declaration
27
- import { loadSiteConfig } from '../scripts/load-site-config.mjs';
24
+ import { loadSiteConfig } from '../scripts/load-site-config';
28
25
  import { existsSync } from 'fs';
29
26
  import { resolve, dirname } from 'path';
30
27
  import { fileURLToPath } from 'url';
@@ -40,10 +37,11 @@ interface ParsedArgs {
40
37
  type CommandRunner = () => Promise<void>;
41
38
 
42
39
  const commands: Record<string, CommandRunner> = {
43
- fetch: async () => (await import('../scripts/fetch-datasets.mjs' as string)).main(),
44
- generate: async () => { await import('../scripts/generate-data.mjs' as string); },
45
- edges: async () => (await import('../scripts/build-edges.js' as string)).main(),
46
- about: async () => (await import('../scripts/process-about-pages.mjs' as string)).main(),
40
+ fetch: async () => (await import('../scripts/fetch-datasets')).main(),
41
+ generate: async () => { await import('../scripts/generate-data'); },
42
+ edges: async () => (await import('../scripts/build-edges')).main(),
43
+ about: async () => (await import('../scripts/process-about-pages')).main(),
44
+ 'validate-yaml': async () => { await import('../scripts/validate-yaml'); },
47
45
  };
48
46
 
49
47
  function parseArgs(argv: readonly string[]): ParsedArgs {
@@ -308,7 +306,7 @@ async function runBuildPipeline(config: SiteConfig | null): Promise<void> {
308
306
  }
309
307
 
310
308
  console.log(`\n=== BRIDGE DATA ===\n`);
311
- const bridge: string = resolve(pkgRoot, 'scripts', 'bridge-to-astro.mjs');
309
+ const bridge: string = resolve(pkgRoot, 'scripts', 'bridge-to-astro.ts');
312
310
  if (existsSync(bridge)) {
313
311
  await import(`file://${bridge}`);
314
312
  }
@@ -341,7 +339,7 @@ async function runBuildPipeline(config: SiteConfig | null): Promise<void> {
341
339
  }
342
340
 
343
341
  async function runNormalize(): Promise<void> {
344
- const mod = await import('../scripts/normalize-yaml.mjs' as string);
342
+ const mod = await import('../scripts/normalize-yaml');
345
343
  const normalizeYaml = mod.normalizeYaml;
346
344
  const check: boolean = process.argv.includes('--check');
347
345
  const paths: string[] = process.argv.slice(2).filter(a => !a.startsWith('-') && a !== 'normalize');
@@ -414,7 +412,7 @@ Environment:
414
412
  }
415
413
 
416
414
  if (cmd === 'doctor') {
417
- const mod = await import('../scripts/doctor.mjs' as string);
415
+ const mod = await import('../scripts/doctor');
418
416
  await mod.main();
419
417
  return;
420
418
  }
@@ -0,0 +1,255 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://glossarist.org/concept-model/shapes/glossarist.concept.yaml.schema.json",
4
+ "title": "Glossarist Concept YAML",
5
+ "description": "Schema for dataset author YAML files. Derived from scripts/lib/yaml-types.ts — the TypeScript interfaces are the source of truth.",
6
+ "type": "object",
7
+ "required": ["termid"],
8
+ "properties": {
9
+ "termid": {
10
+ "type": "string",
11
+ "description": "Unique concept identifier within the dataset"
12
+ },
13
+ "id": { "type": "string" },
14
+ "uri": { "type": "string", "format": "uri" },
15
+ "status": {
16
+ "type": "string",
17
+ "enum": ["valid", "not_valid", "withdrawn", "draft", "superseded"]
18
+ },
19
+ "_related": {
20
+ "type": "array",
21
+ "items": { "$ref": "#/$defs/relation" }
22
+ },
23
+ "_partitiveRelations": {
24
+ "type": "array",
25
+ "items": { "$ref": "#/$defs/partitiveRelation" }
26
+ },
27
+ "_genericRelations": {
28
+ "type": "array",
29
+ "items": { "$ref": "#/$defs/genericRelation" }
30
+ },
31
+ "_domains": {
32
+ "type": "array",
33
+ "items": { "type": "string" }
34
+ },
35
+ "_dates": {
36
+ "type": "array",
37
+ "items": { "$ref": "#/$defs/date" }
38
+ },
39
+ "_sources": {
40
+ "type": "array",
41
+ "items": { "$ref": "#/$defs/source" }
42
+ },
43
+ "_status": { "type": "string" },
44
+ "_schemaVersion": { "type": "string" },
45
+ "_dateAccepted": { "type": "string", "format": "date" }
46
+ },
47
+ "patternProperties": {
48
+ "^[a-z]{3}$": { "$ref": "#/$defs/localization" }
49
+ },
50
+ "additionalProperties": true,
51
+
52
+ "$defs": {
53
+ "localization": {
54
+ "type": "object",
55
+ "properties": {
56
+ "language_code": { "type": "string", "pattern": "^[a-z]{3}$" },
57
+ "terms": { "type": "array", "items": { "$ref": "#/$defs/term" } },
58
+ "definition": { "type": "array", "items": { "$ref": "#/$defs/content" } },
59
+ "notes": { "type": "array", "items": { "$ref": "#/$defs/content" } },
60
+ "examples": { "type": "array", "items": { "$ref": "#/$defs/content" } },
61
+ "annotations": { "type": "array", "items": { "$ref": "#/$defs/content" } },
62
+ "sources": { "type": "array", "items": { "$ref": "#/$defs/source" } },
63
+ "dates": { "type": "array", "items": { "$ref": "#/$defs/date" } },
64
+ "domain": { "type": "array", "items": { "type": "string" } },
65
+ "entry_status": {
66
+ "type": "string",
67
+ "enum": ["valid", "not_valid", "withdrawn", "draft", "superseded"]
68
+ },
69
+ "classification": { "type": "string" },
70
+ "release": { "type": "string" }
71
+ }
72
+ },
73
+
74
+ "term": {
75
+ "type": "object",
76
+ "required": ["designation"],
77
+ "properties": {
78
+ "type": {
79
+ "type": "string",
80
+ "enum": ["expression", "abbreviation", "symbol", "letter_symbol", "graphical symbol"]
81
+ },
82
+ "designation": { "type": "string" },
83
+ "normative_status": {
84
+ "type": "string",
85
+ "enum": ["preferred", "admitted", "deprecated", ""]
86
+ },
87
+ "origin": { "$ref": "#/$defs/origin" },
88
+ "grammar_info": {
89
+ "type": "array",
90
+ "items": { "$ref": "#/$defs/grammarInfo" }
91
+ },
92
+ "international": { "type": "boolean" },
93
+ "absent": { "type": "boolean" },
94
+ "geographical_area": { "type": "string" },
95
+ "term_type": { "type": "string" },
96
+ "prefix": { "type": "string" },
97
+ "usage_info": { "type": "string" },
98
+ "field_of_application": { "type": "string" }
99
+ }
100
+ },
101
+
102
+ "content": {
103
+ "type": "object",
104
+ "properties": {
105
+ "content": { "type": "string" }
106
+ }
107
+ },
108
+
109
+ "source": {
110
+ "type": "object",
111
+ "properties": {
112
+ "id": { "type": "string" },
113
+ "type": {
114
+ "type": "string",
115
+ "enum": ["authoritative", "lineage", "adapted"]
116
+ },
117
+ "status": {
118
+ "type": "string",
119
+ "enum": ["identical", "modified", "restyled", "context-added", "extracted", "expanded-coverage"]
120
+ },
121
+ "origin": { "$ref": "#/$defs/origin" }
122
+ }
123
+ },
124
+
125
+ "origin": {
126
+ "type": "object",
127
+ "properties": {
128
+ "ref": {
129
+ "type": "object",
130
+ "properties": {
131
+ "source": { "type": "string" },
132
+ "id": { "type": "string" },
133
+ "version": { "type": "string" },
134
+ "text": { "type": "string" }
135
+ }
136
+ },
137
+ "locality": {
138
+ "type": "object",
139
+ "properties": {
140
+ "type": { "type": "string" },
141
+ "reference_from": { "type": "string" },
142
+ "reference_to": { "type": "string" }
143
+ }
144
+ },
145
+ "link": { "type": "string", "format": "uri" }
146
+ }
147
+ },
148
+
149
+ "grammarInfo": {
150
+ "type": "object",
151
+ "properties": {
152
+ "gender": { "type": "string" },
153
+ "number": { "type": "string" },
154
+ "partOfSpeech": { "type": "string" },
155
+ "noun": { "type": "boolean" },
156
+ "verb": { "type": "boolean" },
157
+ "adj": { "type": "boolean" },
158
+ "adverb": { "type": "boolean" },
159
+ "preposition": { "type": "boolean" },
160
+ "participle": { "type": "boolean" }
161
+ }
162
+ },
163
+
164
+ "date": {
165
+ "type": "object",
166
+ "properties": {
167
+ "type": { "type": "string" },
168
+ "date": { "type": "string", "format": "date" }
169
+ }
170
+ },
171
+
172
+ "relation": {
173
+ "type": "object",
174
+ "properties": {
175
+ "type": { "type": "string" },
176
+ "target": {
177
+ "oneOf": [
178
+ { "type": "string" },
179
+ {
180
+ "type": "object",
181
+ "properties": {
182
+ "ref": { "type": "string" },
183
+ "uri": { "type": "string", "format": "uri" }
184
+ }
185
+ }
186
+ ]
187
+ },
188
+ "content": { "type": "string" },
189
+ "label": { "type": "string" }
190
+ }
191
+ },
192
+
193
+ "partitiveRelation": {
194
+ "type": "object",
195
+ "properties": {
196
+ "type": { "type": "string" },
197
+ "target": { "type": "string" },
198
+ "members": {
199
+ "type": "array",
200
+ "items": { "$ref": "#/$defs/partitiveMember" }
201
+ },
202
+ "criterion": { "type": "string" },
203
+ "multiplicity": {
204
+ "type": "string",
205
+ "enum": ["compulsory", "optional", "compulsory_multiple", "optional_multiple", "compulsory_at_least_one"]
206
+ }
207
+ }
208
+ },
209
+
210
+ "partitiveMember": {
211
+ "type": "object",
212
+ "properties": {
213
+ "ref": { "type": "string" },
214
+ "uri": { "type": "string", "format": "uri" },
215
+ "presence": {
216
+ "type": "string",
217
+ "enum": ["required", "optional"]
218
+ },
219
+ "count": {
220
+ "type": "string",
221
+ "enum": ["exactly_one", "at_least_one", "multiple"]
222
+ },
223
+ "delimiting": { "type": "boolean" },
224
+ "multiplicity": {
225
+ "type": "string",
226
+ "enum": ["compulsory", "optional", "compulsory_multiple", "optional_multiple", "compulsory_at_least_one"]
227
+ }
228
+ }
229
+ },
230
+
231
+ "genericRelation": {
232
+ "type": "object",
233
+ "properties": {
234
+ "type": { "type": "string" },
235
+ "target": { "type": "string" },
236
+ "members": {
237
+ "type": "array",
238
+ "items": {
239
+ "oneOf": [
240
+ { "type": "string" },
241
+ {
242
+ "type": "object",
243
+ "properties": {
244
+ "ref": { "type": "string" },
245
+ "uri": { "type": "string", "format": "uri" }
246
+ }
247
+ }
248
+ ]
249
+ }
250
+ },
251
+ "criterion": { "type": "string" }
252
+ }
253
+ }
254
+ }
255
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glossarist/concept-browser",
3
- "version": "0.7.113",
3
+ "version": "0.7.115",
4
4
  "description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,22 +9,23 @@
9
9
  "scripts": {
10
10
  "dev": "vite",
11
11
  "build": "vue-tsc --noEmit && vite build",
12
- "postbuild": "node scripts/generate-404.js",
12
+ "postbuild": "tsx scripts/generate-404.ts",
13
13
  "preview": "vite preview",
14
- "check:scripts": "node scripts/check-syntax.mjs",
15
- "pretest": "node scripts/check-syntax.mjs",
16
- "prebuild": "node scripts/check-syntax.mjs",
17
- "fetch-datasets": "node scripts/fetch-datasets.mjs",
18
- "generate-data": "node scripts/generate-data.mjs",
19
- "generate-ontology": "node scripts/generate-ontology-data.mjs && node scripts/generate-ontology-schema.mjs",
20
- "sync:model": "node scripts/sync-concept-model.mjs",
21
- "build:full": "npm run generate-ontology && npm run fetch-datasets && npm run generate-data && node scripts/build-edges.js && npm run build",
14
+ "check:scripts": "tsx scripts/check-syntax.ts",
15
+ "validate:yaml": "tsx scripts/validate-yaml.ts",
16
+ "validate:shacl": "tsx scripts/validate-shacl.ts",
17
+ "pretest": "tsx scripts/check-syntax.ts",
18
+ "prebuild": "tsx scripts/check-syntax.ts",
19
+ "fetch-datasets": "tsx scripts/fetch-datasets.ts",
20
+ "generate-data": "tsx scripts/generate-data.ts",
21
+ "generate-ontology": "tsx scripts/generate-ontology-data.ts && tsx scripts/generate-ontology-schema.ts",
22
+ "sync:model": "tsx scripts/sync-concept-model.ts",
23
+ "build:full": "npm run generate-ontology && npm run fetch-datasets && npm run generate-data && tsx scripts/build-edges.ts && npm run build",
22
24
  "build:site": "concept-browser --site build",
23
- "smoke": "node scripts/smoke.mjs",
25
+ "smoke": "tsx scripts/smoke.ts",
24
26
  "test": "vitest run",
25
27
  "test:watch": "vitest",
26
28
  "mutation:test": "stryker run stryker.config.mjs",
27
- "validate:shacl": "node scripts/validate-shacl.mjs",
28
29
  "dev:astro": "astro dev",
29
30
  "build:astro": "astro build",
30
31
  "preview:astro": "astro preview"
@@ -44,6 +45,7 @@
44
45
  "pinia": "^2.3.1",
45
46
  "sharp": "^0.34.5",
46
47
  "tailwindcss": "^4.3.2",
48
+ "tsx": "^4.23.4",
47
49
  "vite": "^8.1.4",
48
50
  "vue": "^3.5.13",
49
51
  "vue-router": "^4.5.1"
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { normalizeBibliography } from '../lib/bibliography.mjs';
2
+ import { normalizeBibliography } from '../lib/bibliography';
3
3
 
4
4
  describe('normalizeBibliography', () => {
5
5
  it('wraps a bare array into { bibliography: [...] }', () => {
@@ -13,9 +13,9 @@ import { resolve, dirname } from 'node:path';
13
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
14
 
15
15
  const SCRIPT_PATHS = [
16
- '../../scripts/fetch-datasets.mjs',
17
- '../../scripts/build-edges.js',
18
- '../../scripts/process-about-pages.mjs',
16
+ '../../scripts/fetch-datasets.ts',
17
+ '../../scripts/build-edges.ts',
18
+ '../../scripts/process-about-pages.ts',
19
19
  ];
20
20
 
21
21
  describe('CLI script contract: every script exports main()', () => {
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { getGroups } from '../lib/concept-groups.mjs';
2
+ import { getGroups } from '../lib/concept-groups';
3
3
 
4
4
  describe('getGroups', () => {
5
5
  it('returns explicit eng.groups when present', () => {
@@ -3,7 +3,7 @@ import { writeFileSync, mkdirSync, rmSync, existsSync } from 'node:fs';
3
3
  import { resolve, join } from 'node:path';
4
4
  import { mkdtempSync } from 'node:fs';
5
5
  import { tmpdir } from 'node:os';
6
- import { runDoctor, formatResults, CHECKS, NODE_MIN_MAJOR } from '../doctor.mjs';
6
+ import { runDoctor, formatResults, CHECKS, NODE_MIN_MAJOR } from '../doctor';
7
7
 
8
8
  function makeTmpProject() {
9
9
  const dir = mkdtempSync(join(tmpdir(), 'doctor-'));
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import os from 'os';
5
- import { assertLocalPathSafe } from '../lib/local-path-safety.mjs';
5
+ import { assertLocalPathSafe } from '../lib/local-path-safety';
6
6
 
7
7
  function makeTmpTree() {
8
8
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cb-fetch-'));
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { firstNonEmpty } from '../lib/first-non-empty.mjs';
2
+ import { firstNonEmpty } from '../lib/first-non-empty';
3
3
 
4
4
  describe('firstNonEmpty — register-wins resolution', () => {
5
5
  it('returns the first value when both are present (register wins)', () => {
@@ -4,7 +4,7 @@ import { join, dirname } from 'node:path';
4
4
  import { mkdtempSync } from 'node:fs';
5
5
  import { tmpdir } from 'node:os';
6
6
 
7
- const scriptPath = join(dirname(new URL(import.meta.url).pathname), '..', 'process-about-pages.mjs');
7
+ const scriptPath = join(dirname(new URL(import.meta.url).pathname), '..', 'process-about-pages');
8
8
 
9
9
  // Process-about-pages discovers per-dataset about content from either
10
10
  // .datasets/<id>/about/ (fetched) OR site-config localPath overrides.
@@ -1,6 +1,4 @@
1
- function buildConceptUri(uriBase, registerId, conceptId) {
2
- return `${uriBase}/${registerId}/concept/${conceptId}`;
3
- }
1
+ import { buildConceptUri } from './lib/concept-uri';
4
2
 
5
3
 
6
4
  /**
@@ -79,7 +77,7 @@ for (const reg of registry) {
79
77
  if (index && index.concepts) {
80
78
  for (const concept of index.concepts) {
81
79
  if (!concept) continue;
82
- const conceptEntry = {
80
+ const conceptEntry: Record<string, any> = {
83
81
  registerId: id,
84
82
  conceptId: concept.id,
85
83
  uri: buildConceptUri(entry.uriBase || '', id, concept.id),