@glossarist/concept-browser 0.7.114 → 0.7.116

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 (103) hide show
  1. package/README.md +45 -2
  2. package/cli/index.mjs +10 -13
  3. package/cli/index.ts +9 -11
  4. package/data/concept-model/shapes/glossarist.concept.yaml.schema.json +255 -0
  5. package/package.json +18 -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.js +132 -0
  14. package/scripts/{bridge-to-astro.mjs → bridge-to-astro.ts} +2 -4
  15. package/scripts/build-edges.js +242 -336
  16. package/scripts/build-edges.ts +572 -0
  17. package/scripts/build-scripts.mjs +22 -0
  18. package/scripts/check-syntax.js +81 -0
  19. package/scripts/{check-syntax.mjs → check-syntax.ts} +20 -13
  20. package/scripts/doctor.js +278 -0
  21. package/scripts/extract-source-refs.js +16 -24
  22. package/scripts/extract-source-refs.ts +32 -0
  23. package/scripts/fetch-datasets.js +278 -0
  24. package/scripts/{fetch-datasets.mjs → fetch-datasets.ts} +2 -2
  25. package/scripts/generate-404.js +10 -11
  26. package/scripts/generate-404.ts +15 -0
  27. package/scripts/generate-data.js +2220 -0
  28. package/scripts/{generate-data.mjs → generate-data.ts} +73 -460
  29. package/scripts/generate-ontology-data.js +139 -0
  30. package/scripts/generate-ontology-schema.js +472 -0
  31. package/scripts/{generate-ontology-schema.mjs → generate-ontology-schema.ts} +2 -2
  32. package/scripts/lib/{build-cache.mjs → build-cache.ts} +3 -2
  33. package/scripts/lib/concept-formats.ts +206 -0
  34. package/scripts/lib/concept-uri.ts +14 -0
  35. package/scripts/lib/{dataset-turtle.mjs → dataset-turtle.ts} +4 -3
  36. package/scripts/lib/harmonize-concept.ts +86 -0
  37. package/scripts/lib/jsonld-emitter.ts +169 -0
  38. package/scripts/lib/{local-path-safety.mjs → local-path-safety.ts} +6 -2
  39. package/scripts/lib/yaml-types.ts +262 -0
  40. package/scripts/load-site-config.js +83 -0
  41. package/scripts/migrate-v1-to-v3.js +113 -0
  42. package/scripts/normalize-yaml.js +96 -0
  43. package/scripts/process-about-pages.js +241 -0
  44. package/scripts/{process-about-pages.mjs → process-about-pages.ts} +1 -1
  45. package/scripts/smoke.js +211 -0
  46. package/scripts/{smoke.mjs → smoke.ts} +4 -3
  47. package/scripts/sync-concept-model.js +63 -0
  48. package/scripts/validate-shacl.js +152 -0
  49. package/scripts/{validate-shacl.mjs → validate-shacl.ts} +5 -5
  50. package/scripts/validate-yaml.js +132 -0
  51. package/scripts/validate-yaml.ts +144 -0
  52. package/src/__tests__/extract-source-refs.test.ts +2 -3
  53. package/src/__tests__/perf/serialization-perf.test.ts +5 -5
  54. package/src/__tests__/published-package-contract.test.ts +84 -0
  55. package/src/__tests__/scripts/agents-turtle.test.ts +1 -1
  56. package/src/__tests__/scripts/bibliography-turtle.test.ts +1 -1
  57. package/src/__tests__/scripts/build-activity-turtle.test.ts +1 -1
  58. package/src/__tests__/scripts/build-cache.test.ts +1 -1
  59. package/src/__tests__/scripts/build-integration.test.ts +19 -19
  60. package/src/__tests__/scripts/dataset-turtle.test.ts +1 -1
  61. package/src/__tests__/scripts/migrate-v1-to-v3.test.ts +1 -1
  62. package/src/__tests__/scripts/normalize-yaml.test.ts +1 -1
  63. package/src/__tests__/scripts/turtle-escape.test.ts +1 -1
  64. package/src/__tests__/scripts/validate-yaml.test.ts +76 -0
  65. package/src/__tests__/scripts/version-turtle.test.ts +1 -1
  66. package/src/__tests__/scripts/vocab-turtle.test.ts +1 -1
  67. package/src/__tests__/scripts-syntax.test.ts +4 -4
  68. package/src/__tests__/use-rdf-document.test.ts +2 -3
  69. package/src/__tests__/utils/content-renderer-xss.test.ts +57 -0
  70. package/src/__tests__/validate-shacl.test.ts +3 -3
  71. package/src/adapters/wire/index.ts +69 -0
  72. package/src/data/ontology-schema.json +1336 -183
  73. package/src/utils/content-renderer.ts +7 -3
  74. package/src/utils/url-safety.ts +1 -5
  75. package/tsconfig.json +2 -2
  76. package/vite.config.ts +1 -1
  77. package/src/__tests__/composables/use-concept-edges-v2.test.ts +0 -167
  78. package/src/__tests__/composables/use-concept-edges-v2.test.ts.bak +0 -166
  79. package/src/types/agents-version-turtle.d.ts +0 -27
  80. package/src/types/bibliography-turtle.d.ts +0 -12
  81. package/src/types/build-activity-turtle.d.ts +0 -16
  82. package/src/types/build-cache.d.ts +0 -20
  83. package/src/types/dataset-turtle.d.ts +0 -32
  84. package/src/types/normalize-yaml.d.ts +0 -16
  85. package/src/types/turtle-escape.d.ts +0 -6
  86. package/src/types/vocab-turtle.d.ts +0 -13
  87. /package/scripts/{doctor.mjs → doctor.ts} +0 -0
  88. /package/scripts/{generate-ontology-data.mjs → generate-ontology-data.ts} +0 -0
  89. /package/scripts/lib/{agents-turtle.mjs → agents-turtle.ts} +0 -0
  90. /package/scripts/lib/{bibliography-turtle.mjs → bibliography-turtle.ts} +0 -0
  91. /package/scripts/lib/{bibliography.mjs → bibliography.ts} +0 -0
  92. /package/scripts/lib/build/{image-assets.mjs → image-assets.ts} +0 -0
  93. /package/scripts/lib/build/{non-verbal-consumer.mjs → non-verbal-consumer.ts} +0 -0
  94. /package/scripts/lib/{build-activity-turtle.mjs → build-activity-turtle.ts} +0 -0
  95. /package/scripts/lib/{concept-groups.mjs → concept-groups.ts} +0 -0
  96. /package/scripts/lib/{first-non-empty.mjs → first-non-empty.ts} +0 -0
  97. /package/scripts/lib/{turtle-escape.mjs → turtle-escape.ts} +0 -0
  98. /package/scripts/lib/{version-turtle.mjs → version-turtle.ts} +0 -0
  99. /package/scripts/lib/{vocab-turtle.mjs → vocab-turtle.ts} +0 -0
  100. /package/scripts/{load-site-config.mjs → load-site-config.ts} +0 -0
  101. /package/scripts/{migrate-v1-to-v3.mjs → migrate-v1-to-v3.ts} +0 -0
  102. /package/scripts/{normalize-yaml.mjs → normalize-yaml.ts} +0 -0
  103. /package/scripts/{sync-concept-model.mjs → sync-concept-model.ts} +0 -0
@@ -10,12 +10,12 @@ function parseTurtle(text: string): Store {
10
10
  }
11
11
 
12
12
  function runInline(code: string): string {
13
- return execFileSync('node', ['--input-type=module', '-e', code], { encoding: 'utf8' }).toString();
13
+ return execFileSync('node', ['--import', 'tsx', '--input-type=module', '-e', code], { encoding: 'utf8' }).toString();
14
14
  }
15
15
 
16
16
  describe('WS F Layer 8 — build pipeline integration', () => {
17
- it('vocab-turtle.mjs produces parseable Turtle with 7 schemes', () => {
18
- const out = runInline(`import { buildVocabularyTurtle } from './scripts/lib/vocab-turtle.mjs'; console.log(await buildVocabularyTurtle());`);
17
+ it('vocab-turtle.ts produces parseable Turtle with 7 schemes', () => {
18
+ const out = runInline(`import { buildVocabularyTurtle } from './scripts/lib/vocab-turtle'; console.log(await buildVocabularyTurtle());`);
19
19
  const store = parseTurtle(out);
20
20
  const schemes = [...store].filter(q =>
21
21
  q.predicate.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' &&
@@ -24,9 +24,9 @@ describe('WS F Layer 8 — build pipeline integration', () => {
24
24
  expect(schemes.length).toBe(7);
25
25
  });
26
26
 
27
- it('dataset-turtle.mjs produces parseable dcat:Dataset', () => {
27
+ it('dataset-turtle.ts produces parseable dcat:Dataset', () => {
28
28
  const out = runInline(`
29
- import { buildDatasetTurtle } from './scripts/lib/dataset-turtle.mjs';
29
+ import { buildDatasetTurtle } from './scripts/lib/dataset-turtle';
30
30
  console.log(await buildDatasetTurtle({
31
31
  datasetIri: 'https://glossarist.org/test/',
32
32
  registerId: 'test', title: 'Test', modified: '2026-06-28',
@@ -42,9 +42,9 @@ describe('WS F Layer 8 — build pipeline integration', () => {
42
42
  expect(types).toContain('http://www.w3.org/2004/02/skos/core#ConceptScheme');
43
43
  });
44
44
 
45
- it('build-activity-turtle.mjs produces parseable prov:Activity', () => {
45
+ it('build-activity-turtle.ts produces parseable prov:Activity', () => {
46
46
  const out = runInline(`
47
- import { buildActivityTurtle } from './scripts/lib/build-activity-turtle.mjs';
47
+ import { buildActivityTurtle } from './scripts/lib/build-activity-turtle';
48
48
  console.log(await buildActivityTurtle({
49
49
  baseUri: 'https://glossarist.org',
50
50
  runId: 'test', startedAt: '2026-01-01T00:00:00Z', endedAt: '2026-01-01T00:05:00Z',
@@ -59,9 +59,9 @@ describe('WS F Layer 8 — build pipeline integration', () => {
59
59
  expect(activities.length).toBe(1);
60
60
  });
61
61
 
62
- it('agents-turtle.mjs produces parseable foaf:Person', () => {
62
+ it('agents-turtle.ts produces parseable foaf:Person', () => {
63
63
  const out = runInline(`
64
- import { buildAgentsTurtle } from './scripts/lib/agents-turtle.mjs';
64
+ import { buildAgentsTurtle } from './scripts/lib/agents-turtle';
65
65
  console.log(await buildAgentsTurtle([{ name: 'Ada Lovelace', role: 'Editor', organization: 'Royal Society' }], 'https://glossarist.org/agent', 'https://glossarist.org/org'));
66
66
  `);
67
67
  const store = parseTurtle(out);
@@ -72,9 +72,9 @@ describe('WS F Layer 8 — build pipeline integration', () => {
72
72
  expect(persons.length).toBe(1);
73
73
  });
74
74
 
75
- it('bibliography-turtle.mjs produces parseable dcterms:BibliographicResource', () => {
75
+ it('bibliography-turtle.ts produces parseable dcterms:BibliographicResource', () => {
76
76
  const out = runInline(`
77
- import { buildBibliographyTurtle } from './scripts/lib/bibliography-turtle.mjs';
77
+ import { buildBibliographyTurtle } from './scripts/lib/bibliography-turtle';
78
78
  console.log(await buildBibliographyTurtle('test', { iso704: { reference: 'ISO 704' } }, 'https://glossarist.org'));
79
79
  `);
80
80
  const store = parseTurtle(out);
@@ -85,9 +85,9 @@ describe('WS F Layer 8 — build pipeline integration', () => {
85
85
  expect(bibs.length).toBe(1);
86
86
  });
87
87
 
88
- it('version-turtle.mjs produces parseable prov:Entity chain', () => {
88
+ it('version-turtle.ts produces parseable prov:Entity chain', () => {
89
89
  const out = runInline(`
90
- import { buildVersionHistoryTurtle } from './scripts/lib/version-turtle.mjs';
90
+ import { buildVersionHistoryTurtle } from './scripts/lib/version-turtle';
91
91
  console.log(await buildVersionHistoryTurtle({
92
92
  registerId: 'test', datasetIri: 'https://glossarist.org/test/',
93
93
  versions: [
@@ -113,12 +113,12 @@ describe('WS F Layer 8 — build pipeline integration', () => {
113
113
 
114
114
  it('all six emitters produce parseable output in a single subprocess', () => {
115
115
  const out = runInline(`
116
- import { buildVocabularyTurtle } from './scripts/lib/vocab-turtle.mjs';
117
- import { buildDatasetTurtle } from './scripts/lib/dataset-turtle.mjs';
118
- import { buildActivityTurtle } from './scripts/lib/build-activity-turtle.mjs';
119
- import { buildAgentsTurtle } from './scripts/lib/agents-turtle.mjs';
120
- import { buildBibliographyTurtle } from './scripts/lib/bibliography-turtle.mjs';
121
- import { buildVersionHistoryTurtle } from './scripts/lib/version-turtle.mjs';
116
+ import { buildVocabularyTurtle } from './scripts/lib/vocab-turtle';
117
+ import { buildDatasetTurtle } from './scripts/lib/dataset-turtle';
118
+ import { buildActivityTurtle } from './scripts/lib/build-activity-turtle';
119
+ import { buildAgentsTurtle } from './scripts/lib/agents-turtle';
120
+ import { buildBibliographyTurtle } from './scripts/lib/bibliography-turtle';
121
+ import { buildVersionHistoryTurtle } from './scripts/lib/version-turtle';
122
122
  const parts = [
123
123
  await buildVocabularyTurtle(),
124
124
  await buildDatasetTurtle({ datasetIri: 'https://glossarist.org/test/', registerId: 'test', title: 't', modified: '2026-01-01', languages: ['eng'], distributions: [], topConceptUris: [], sections: [] }),
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { Parser, Store } from 'n3';
3
- import { buildDatasetTurtle } from '../../../scripts/lib/dataset-turtle.mjs';
3
+ import { buildDatasetTurtle } from '../../../scripts/lib/dataset-turtle';
4
4
 
5
5
  const BASE = 'https://glossarist.org/dataset';
6
6
 
@@ -5,7 +5,7 @@ import {
5
5
  migrateRelation,
6
6
  migrateConceptData,
7
7
  isAlreadyMigrated,
8
- } from '../../../scripts/migrate-v1-to-v3.mjs';
8
+ } from '../../../scripts/migrate-v1-to-v3';
9
9
 
10
10
  describe('migrate-v1-to-v3 pure conversion', () => {
11
11
  describe('isV1PartitiveRelation', () => {
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
2
  import { mkdtempSync, writeFileSync, readFileSync, rmSync, mkdirSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { tmpdir } from 'node:os';
5
- import { normalizeYaml } from '../../../scripts/normalize-yaml.mjs';
5
+ import { normalizeYaml } from '../../../scripts/normalize-yaml';
6
6
 
7
7
  const NFC_STRING = 'café';
8
8
  const NON_NFC_STRING = 'café';
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { ttlLit, ttlPrefixed, ttlIri, assertValidIri } from '../../../scripts/lib/turtle-escape.mjs';
2
+ import { ttlLit, ttlPrefixed, ttlIri, assertValidIri } from '../../../scripts/lib/turtle-escape';
3
3
 
4
4
  describe('D1/D2 — Turtle escaping hardening', () => {
5
5
  describe('ttlLit', () => {
@@ -0,0 +1,76 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readFileSync } from 'node:fs';
3
+ import { resolve, dirname } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { validateYaml } from '../../../scripts/validate-yaml';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const SCHEMA_PATH = resolve(__dirname, '..', '..', '..', 'data', 'concept-model', 'shapes', 'glossarist.concept.yaml.schema.json');
9
+ const schema = JSON.parse(readFileSync(SCHEMA_PATH, 'utf8'));
10
+
11
+ describe('validate-yaml', () => {
12
+ it('accepts a valid concept with termid and localization', () => {
13
+ const yaml = `
14
+ termid: '1'
15
+ eng:
16
+ terms:
17
+ - designation: measurement unit
18
+ normative_status: preferred
19
+ definition:
20
+ - content: A concept used as a basis for comparison.
21
+ `;
22
+ const result = validateYaml(yaml, schema);
23
+ if (!result.valid) console.log('Errors:', result.errors);
24
+ expect(result.valid).toBe(true);
25
+ });
26
+
27
+ it('rejects a concept without termid', () => {
28
+ const yaml = `
29
+ eng:
30
+ terms:
31
+ - designation: missing id
32
+ `;
33
+ const result = validateYaml(yaml, schema);
34
+ expect(result.valid).toBe(false);
35
+ expect(result.errors.some(e => e.message.includes('termid'))).toBe(true);
36
+ });
37
+
38
+ it('accepts managed concept format with underscore fields', () => {
39
+ const yaml = `
40
+ termid: '5'
41
+ _domains:
42
+ - physics
43
+ _status: valid
44
+ `;
45
+ const result = validateYaml(yaml, schema);
46
+ if (!result.valid) console.log('Errors:', result.errors);
47
+ expect(result.valid).toBe(true);
48
+ });
49
+
50
+ it('accepts partitive relations with MECE axes', () => {
51
+ const yaml = `
52
+ termid: '3'
53
+ _partitiveRelations:
54
+ - type: hasPart
55
+ members:
56
+ - ref: part-a
57
+ presence: required
58
+ count: exactly_one
59
+ `;
60
+ const result = validateYaml(yaml, schema);
61
+ if (!result.valid) console.log('Errors:', result.errors);
62
+ expect(result.valid).toBe(true);
63
+ });
64
+
65
+ it('rejects non-string termid', () => {
66
+ const yaml = `
67
+ termid: 12345
68
+ eng:
69
+ terms:
70
+ - designation: test
71
+ `;
72
+ const result = validateYaml(yaml, schema);
73
+ expect(result.valid).toBe(false);
74
+ });
75
+ });
76
+
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { Parser, Store } from 'n3';
3
- import { buildVersionTurtle, buildVersionHistoryTurtle } from '../../../scripts/lib/version-turtle.mjs';
3
+ import { buildVersionTurtle, buildVersionHistoryTurtle } from '../../../scripts/lib/version-turtle';
4
4
 
5
5
  function parse(turtle: string): Store {
6
6
  const parser = new Parser({ format: 'Turtle' });
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { Parser, Store } from 'n3';
3
- import { buildVocabularyTurtle, listVocabSchemes } from '../../../scripts/lib/vocab-turtle.mjs';
3
+ import { buildVocabularyTurtle, listVocabSchemes } from '../../../scripts/lib/vocab-turtle';
4
4
 
5
5
  function parse(turtle: string): Store {
6
6
  const parser = new Parser({ format: 'Turtle' });
@@ -9,11 +9,11 @@ const repoRoot = path.resolve(
9
9
  );
10
10
 
11
11
  describe('scripts syntax gate', () => {
12
- it('`npm run check:scripts` exits 0 (every .mjs/.js/.cjs in scripts/ and cli/ parses)', () => {
12
+ it('`npm run check:scripts` exits 0 (every .ts/.mjs/.js/.cjs in scripts/ and cli/ parses)', () => {
13
13
  const result = spawnSync(
14
14
  process.execPath,
15
- [path.join(repoRoot, 'scripts', 'check-syntax.mjs')],
16
- { encoding: 'utf8' },
15
+ ['--import', 'tsx', path.join(repoRoot, 'scripts', 'check-syntax.ts')],
16
+ { encoding: 'utf8', timeout: 60000 },
17
17
  );
18
18
 
19
19
  if (result.status !== 0) {
@@ -24,5 +24,5 @@ describe('scripts syntax gate', () => {
24
24
 
25
25
  expect(result.status).toBe(0);
26
26
  expect(result.stdout).toMatch(/syntax OK:/);
27
- });
27
+ }, 120000);
28
28
  });
@@ -1,10 +1,9 @@
1
- // @ts-nocheck — TODO.typescript/12: remove after glossarist TS migration
2
1
  import { describe, it, expect } from 'vitest';
3
2
  import { ref } from 'vue';
4
3
  import { Concept } from 'glossarist';
5
4
  import { useRdfDocument } from '../components/use-rdf-document';
6
5
 
7
- function makeConcept(): Concept {
6
+ function makeConcept(): any {
8
7
  return Concept.fromJSON({
9
8
  id: '3.1.1',
10
9
  uri: 'https://glossarist.org/test/concept/3.1.1',
@@ -24,7 +23,7 @@ function makeConcept(): Concept {
24
23
  });
25
24
  }
26
25
 
27
- function makeConceptWithNonVerbal(): Concept {
26
+ function makeConceptWithNonVerbal(): any {
28
27
  return Concept.fromJSON({
29
28
  id: '3.1.2',
30
29
  uri: 'https://glossarist.org/test/concept/3.1.2',
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { renderContent } from '../../utils/content-renderer';
3
+
4
+ describe('content-renderer XSS protection', () => {
5
+ it('blocks javascript: URLs in {{link:}} (no anchor tag)', () => {
6
+ const html = renderContent('{{link:javascript:alert(1)}}', {});
7
+ expect(html).not.toContain('<a');
8
+ expect(html).not.toContain('href=');
9
+ });
10
+
11
+ it('blocks javascript: URLs in {{link:}} with label (renders as text)', () => {
12
+ const html = renderContent('{{link:javascript:alert(1), click me}}', {});
13
+ expect(html).not.toContain('<a');
14
+ expect(html).not.toContain('href=');
15
+ expect(html).toContain('click me');
16
+ });
17
+
18
+ it('blocks data: URLs in {{link:}} (no anchor tag)', () => {
19
+ const html = renderContent('{{link:data:text/html,<script>alert(1)</script>}}', {});
20
+ expect(html).not.toContain('<a');
21
+ expect(html).not.toContain('href=');
22
+ expect(html).not.toContain('<script>');
23
+ });
24
+
25
+ it('allows https: URLs in {{link:}}', () => {
26
+ const html = renderContent('{{link:https://example.com}}', {});
27
+ expect(html).toContain('href="https://example.com"');
28
+ });
29
+
30
+ it('blocks javascript: URLs in {{image:}} (no img tag)', () => {
31
+ const html = renderContent('{{image:javascript:alert(1)}}', {});
32
+ expect(html).not.toContain('<img');
33
+ });
34
+
35
+ it('blocks data: URLs in {{image:}} (no img tag with executable src)', () => {
36
+ const html = renderContent('{{image:data:text/html,<script>}}', {});
37
+ expect(html).not.toContain('<img');
38
+ expect(html).not.toContain('data:text/html');
39
+ });
40
+
41
+ it('allows relative paths in {{image:}}', () => {
42
+ const html = renderContent('{{image:diagram.png}}', {});
43
+ expect(html).toContain('src="diagram.png"');
44
+ });
45
+
46
+ it('escapes HTML in unordered list items', () => {
47
+ const html = renderContent('* <script>alert(1)</script>\n* safe item', {});
48
+ expect(html).not.toContain('<script>');
49
+ expect(html).toContain('&lt;script&gt;');
50
+ });
51
+
52
+ it('escapes HTML in ordered list items', () => {
53
+ const html = renderContent('1. <img onerror=alert(1)>\n2. safe', {});
54
+ expect(html).not.toContain('<img onerror');
55
+ expect(html).toContain('&lt;img');
56
+ });
57
+ });
@@ -4,7 +4,7 @@ import { join, dirname } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
 
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
- const SCRIPT = join(__dirname, '..', '..', 'scripts', 'validate-shacl.mjs');
7
+ const SCRIPT = join(__dirname, '..', '..', 'scripts', 'validate-shacl.ts');
8
8
  const FIXTURES = join(__dirname, '..', '__fixtures__', 'shacl');
9
9
  const SHAPES = join(__dirname, '..', '__fixtures__', 'concept-shape.ttl');
10
10
 
@@ -31,13 +31,13 @@ function execCaught(cmd: string, args: string[], opts: Parameters<typeof execFil
31
31
  }
32
32
 
33
33
  function runValidate(dataDir: string, { env, shapes }: RunOptions = {}): RunResult {
34
- return execCaught('node', [SCRIPT, '--shapes', shapes ?? SHAPES, dataDir], {
34
+ return execCaught('node', ['--import', 'tsx', SCRIPT, '--shapes', shapes ?? SHAPES, dataDir], {
35
35
  env: { ...process.env, ...env },
36
36
  });
37
37
  }
38
38
 
39
39
  function runValidateRaw(args: string[], { env }: RunOptions = {}): RunResult {
40
- return execCaught('node', [SCRIPT, ...args], {
40
+ return execCaught('node', ['--import', 'tsx', SCRIPT, ...args], {
41
41
  env: { ...process.env, ...env },
42
42
  stdio: ['ignore', 'pipe', 'pipe'],
43
43
  });
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Wire-format types barrel — single import path for all wire types.
3
+ *
4
+ * Three MECE layers:
5
+ * - YAML author format (INPUT to build pipeline)
6
+ * - JSON-LD browser format (OUTPUT of build pipeline)
7
+ * - Manifest / site-config metadata
8
+ *
9
+ * Usage:
10
+ * import type { HarmonizedConcept, JsonLdConcept, Manifest } from '@/adapters/wire'
11
+ *
12
+ * Or layer-specific:
13
+ * import type { HarmonizedConcept } from '@/adapters/wire/yaml'
14
+ * import type { JsonLdConcept } from '@/adapters/wire/jsonld'
15
+ */
16
+
17
+ // YAML author format — what dataset authors write
18
+ export type {
19
+ YamlContent,
20
+ YamlTerm,
21
+ YamlOrigin,
22
+ YamlGrammarInfo,
23
+ YamlSource,
24
+ YamlDate,
25
+ YamlRelation,
26
+ YamlPartitiveMember,
27
+ YamlPartitiveRelation,
28
+ YamlGenericRelation,
29
+ YamlLocalization,
30
+ HarmonizedConcept,
31
+ YamlManagedConceptDoc,
32
+ YamlManifestSection,
33
+ YamlManifest,
34
+ YamlBibliographyEntry,
35
+ YamlNewsFrontmatter,
36
+ YamlContentPage,
37
+ ShaclConstraint,
38
+ PrefixMap,
39
+ } from '../../../scripts/lib/yaml-types';
40
+
41
+ // JSON-LD browser format — what the SPA reads
42
+ export type {
43
+ JsonLdContent,
44
+ JsonLdDate,
45
+ JsonLdPronunciation,
46
+ JsonLdGrammarInfo,
47
+ JsonLdRef,
48
+ JsonLdLocality,
49
+ JsonLdOrigin,
50
+ JsonLdSource,
51
+ JsonLdRelated,
52
+ JsonLdDesignation,
53
+ JsonLdLocalizedConcept,
54
+ JsonLdPartitiveMember,
55
+ JsonLdPartitiveRelation,
56
+ JsonLdConcept,
57
+ } from '../jsonld-types';
58
+
59
+ // Manifest + summary types
60
+ export type {
61
+ Manifest,
62
+ ManifestSection,
63
+ ConceptSummary,
64
+ CitationClassification,
65
+ CiteResolution,
66
+ } from '../types';
67
+
68
+ // Wire-key constants (runtime value, not just type)
69
+ export { GL } from '../wire-keys';