@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.
- package/README.md +45 -2
- package/cli/index.mjs +10 -13
- package/cli/index.ts +9 -11
- package/data/concept-model/shapes/glossarist.concept.yaml.schema.json +255 -0
- package/package.json +18 -12
- package/scripts/__tests__/{bibliography.test.mjs → bibliography.test.ts} +1 -1
- package/scripts/__tests__/{cli-script-contract.test.mjs → cli-script-contract.test.ts} +3 -3
- package/scripts/__tests__/{concept-groups.test.mjs → concept-groups.test.ts} +1 -1
- package/scripts/__tests__/{doctor.test.mjs → doctor.test.ts} +1 -1
- package/scripts/__tests__/{fetch-datasets.test.mjs → fetch-datasets.test.ts} +1 -1
- package/scripts/__tests__/{first-non-empty.test.mjs → first-non-empty.test.ts} +1 -1
- package/scripts/__tests__/{process-about-pages.test.mjs → process-about-pages.test.ts} +1 -1
- package/scripts/bridge-to-astro.js +132 -0
- package/scripts/{bridge-to-astro.mjs → bridge-to-astro.ts} +2 -4
- package/scripts/build-edges.js +242 -336
- package/scripts/build-edges.ts +572 -0
- package/scripts/build-scripts.mjs +22 -0
- package/scripts/check-syntax.js +81 -0
- package/scripts/{check-syntax.mjs → check-syntax.ts} +20 -13
- package/scripts/doctor.js +278 -0
- package/scripts/extract-source-refs.js +16 -24
- package/scripts/extract-source-refs.ts +32 -0
- package/scripts/fetch-datasets.js +278 -0
- package/scripts/{fetch-datasets.mjs → fetch-datasets.ts} +2 -2
- package/scripts/generate-404.js +10 -11
- package/scripts/generate-404.ts +15 -0
- package/scripts/generate-data.js +2220 -0
- package/scripts/{generate-data.mjs → generate-data.ts} +73 -460
- package/scripts/generate-ontology-data.js +139 -0
- package/scripts/generate-ontology-schema.js +472 -0
- package/scripts/{generate-ontology-schema.mjs → generate-ontology-schema.ts} +2 -2
- package/scripts/lib/{build-cache.mjs → build-cache.ts} +3 -2
- package/scripts/lib/concept-formats.ts +206 -0
- package/scripts/lib/concept-uri.ts +14 -0
- package/scripts/lib/{dataset-turtle.mjs → dataset-turtle.ts} +4 -3
- package/scripts/lib/harmonize-concept.ts +86 -0
- package/scripts/lib/jsonld-emitter.ts +169 -0
- package/scripts/lib/{local-path-safety.mjs → local-path-safety.ts} +6 -2
- package/scripts/lib/yaml-types.ts +262 -0
- package/scripts/load-site-config.js +83 -0
- package/scripts/migrate-v1-to-v3.js +113 -0
- package/scripts/normalize-yaml.js +96 -0
- package/scripts/process-about-pages.js +241 -0
- package/scripts/{process-about-pages.mjs → process-about-pages.ts} +1 -1
- package/scripts/smoke.js +211 -0
- package/scripts/{smoke.mjs → smoke.ts} +4 -3
- package/scripts/sync-concept-model.js +63 -0
- package/scripts/validate-shacl.js +152 -0
- package/scripts/{validate-shacl.mjs → validate-shacl.ts} +5 -5
- package/scripts/validate-yaml.js +132 -0
- package/scripts/validate-yaml.ts +144 -0
- package/src/__tests__/extract-source-refs.test.ts +2 -3
- package/src/__tests__/perf/serialization-perf.test.ts +5 -5
- package/src/__tests__/published-package-contract.test.ts +84 -0
- package/src/__tests__/scripts/agents-turtle.test.ts +1 -1
- package/src/__tests__/scripts/bibliography-turtle.test.ts +1 -1
- package/src/__tests__/scripts/build-activity-turtle.test.ts +1 -1
- package/src/__tests__/scripts/build-cache.test.ts +1 -1
- package/src/__tests__/scripts/build-integration.test.ts +19 -19
- package/src/__tests__/scripts/dataset-turtle.test.ts +1 -1
- package/src/__tests__/scripts/migrate-v1-to-v3.test.ts +1 -1
- package/src/__tests__/scripts/normalize-yaml.test.ts +1 -1
- package/src/__tests__/scripts/turtle-escape.test.ts +1 -1
- package/src/__tests__/scripts/validate-yaml.test.ts +76 -0
- package/src/__tests__/scripts/version-turtle.test.ts +1 -1
- package/src/__tests__/scripts/vocab-turtle.test.ts +1 -1
- package/src/__tests__/scripts-syntax.test.ts +4 -4
- package/src/__tests__/use-rdf-document.test.ts +2 -3
- package/src/__tests__/utils/content-renderer-xss.test.ts +57 -0
- package/src/__tests__/validate-shacl.test.ts +3 -3
- package/src/adapters/wire/index.ts +69 -0
- package/src/data/ontology-schema.json +1336 -183
- package/src/utils/content-renderer.ts +7 -3
- package/src/utils/url-safety.ts +1 -5
- package/tsconfig.json +2 -2
- package/vite.config.ts +1 -1
- package/src/__tests__/composables/use-concept-edges-v2.test.ts +0 -167
- package/src/__tests__/composables/use-concept-edges-v2.test.ts.bak +0 -166
- package/src/types/agents-version-turtle.d.ts +0 -27
- package/src/types/bibliography-turtle.d.ts +0 -12
- package/src/types/build-activity-turtle.d.ts +0 -16
- package/src/types/build-cache.d.ts +0 -20
- package/src/types/dataset-turtle.d.ts +0 -32
- package/src/types/normalize-yaml.d.ts +0 -16
- package/src/types/turtle-escape.d.ts +0 -6
- package/src/types/vocab-turtle.d.ts +0 -13
- /package/scripts/{doctor.mjs → doctor.ts} +0 -0
- /package/scripts/{generate-ontology-data.mjs → generate-ontology-data.ts} +0 -0
- /package/scripts/lib/{agents-turtle.mjs → agents-turtle.ts} +0 -0
- /package/scripts/lib/{bibliography-turtle.mjs → bibliography-turtle.ts} +0 -0
- /package/scripts/lib/{bibliography.mjs → bibliography.ts} +0 -0
- /package/scripts/lib/build/{image-assets.mjs → image-assets.ts} +0 -0
- /package/scripts/lib/build/{non-verbal-consumer.mjs → non-verbal-consumer.ts} +0 -0
- /package/scripts/lib/{build-activity-turtle.mjs → build-activity-turtle.ts} +0 -0
- /package/scripts/lib/{concept-groups.mjs → concept-groups.ts} +0 -0
- /package/scripts/lib/{first-non-empty.mjs → first-non-empty.ts} +0 -0
- /package/scripts/lib/{turtle-escape.mjs → turtle-escape.ts} +0 -0
- /package/scripts/lib/{version-turtle.mjs → version-turtle.ts} +0 -0
- /package/scripts/lib/{vocab-turtle.mjs → vocab-turtle.ts} +0 -0
- /package/scripts/{load-site-config.mjs → load-site-config.ts} +0 -0
- /package/scripts/{migrate-v1-to-v3.mjs → migrate-v1-to-v3.ts} +0 -0
- /package/scripts/{normalize-yaml.mjs → normalize-yaml.ts} +0 -0
- /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.
|
|
18
|
-
const out = runInline(`import { buildVocabularyTurtle } from './scripts/lib/vocab-turtle
|
|
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.
|
|
27
|
+
it('dataset-turtle.ts produces parseable dcat:Dataset', () => {
|
|
28
28
|
const out = runInline(`
|
|
29
|
-
import { buildDatasetTurtle } from './scripts/lib/dataset-turtle
|
|
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.
|
|
45
|
+
it('build-activity-turtle.ts produces parseable prov:Activity', () => {
|
|
46
46
|
const out = runInline(`
|
|
47
|
-
import { buildActivityTurtle } from './scripts/lib/build-activity-turtle
|
|
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.
|
|
62
|
+
it('agents-turtle.ts produces parseable foaf:Person', () => {
|
|
63
63
|
const out = runInline(`
|
|
64
|
-
import { buildAgentsTurtle } from './scripts/lib/agents-turtle
|
|
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.
|
|
75
|
+
it('bibliography-turtle.ts produces parseable dcterms:BibliographicResource', () => {
|
|
76
76
|
const out = runInline(`
|
|
77
|
-
import { buildBibliographyTurtle } from './scripts/lib/bibliography-turtle
|
|
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.
|
|
88
|
+
it('version-turtle.ts produces parseable prov:Entity chain', () => {
|
|
89
89
|
const out = runInline(`
|
|
90
|
-
import { buildVersionHistoryTurtle } from './scripts/lib/version-turtle
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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():
|
|
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():
|
|
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('<script>');
|
|
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('<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.
|
|
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';
|