@glossarist/concept-browser 0.7.117 → 0.7.118
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.118",
|
|
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": {
|
package/scripts/generate-data.js
CHANGED
|
@@ -420,7 +420,7 @@ async function buildActivityTurtle(input) {
|
|
|
420
420
|
}
|
|
421
421
|
|
|
422
422
|
// scripts/lib/vocab-turtle.ts
|
|
423
|
-
import { readFileSync as readFileSync2 } from "node:fs";
|
|
423
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
|
|
424
424
|
import { join, dirname as dirname2 } from "node:path";
|
|
425
425
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
426
426
|
import {
|
|
@@ -430,9 +430,21 @@ import {
|
|
|
430
430
|
PREFIXES as PREFIXES2
|
|
431
431
|
} from "glossarist/rdf";
|
|
432
432
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
433
|
+
function findVocabPath() {
|
|
434
|
+
const candidates = [
|
|
435
|
+
join(__dirname2, "..", "..", "data", "glossarist-vocab.json"),
|
|
436
|
+
join(__dirname2, "..", "data", "glossarist-vocab.json")
|
|
437
|
+
];
|
|
438
|
+
for (const p of candidates) {
|
|
439
|
+
if (existsSync2(p)) return p;
|
|
440
|
+
}
|
|
441
|
+
throw new Error(
|
|
442
|
+
`glossarist-vocab.json not found. Tried:
|
|
443
|
+
${candidates.map((p) => " " + p).join("\n")}`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
function loadVocab() {
|
|
447
|
+
return JSON.parse(readFileSync2(findVocabPath(), "utf8"));
|
|
436
448
|
}
|
|
437
449
|
function writerPrefixes3() {
|
|
438
450
|
const out = {};
|
|
@@ -1090,6 +1102,9 @@ function buildPatternIndex(datasets, registerCache2) {
|
|
|
1090
1102
|
function buildRefMaps(config2, registerCache2) {
|
|
1091
1103
|
const refPrefixMap = {};
|
|
1092
1104
|
const patternIndex = buildPatternIndex(config2.datasets, registerCache2);
|
|
1105
|
+
for (const ds of config2.datasets) {
|
|
1106
|
+
if (ds.ref) refPrefixMap[ds.ref] = ds.id;
|
|
1107
|
+
}
|
|
1093
1108
|
for (const route of config2.routing || []) {
|
|
1094
1109
|
if (route.uri && route.uri.includes("iec") && route.uri.includes("60050")) {
|
|
1095
1110
|
const mapped = route.targetDataset;
|
|
@@ -1316,6 +1331,13 @@ function yamlToJsonLd(conceptYaml, register, refMaps2) {
|
|
|
1316
1331
|
if (r.ref.id) ref["gl:id"] = r.ref.id;
|
|
1317
1332
|
if (r.ref.text) ref["gl:text"] = r.ref.text;
|
|
1318
1333
|
rel["gl:ref"] = ref;
|
|
1334
|
+
if (r.ref.source && r.ref.id && refMaps2?.refPrefixMap?.[r.ref.source]) {
|
|
1335
|
+
const datasetId = refMaps2.refPrefixMap[r.ref.source];
|
|
1336
|
+
rel["gl:target"] = buildConceptUri(refMaps2.uriBase, datasetId, r.ref.id);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (r.target) {
|
|
1340
|
+
rel["gl:target"] = r.target;
|
|
1319
1341
|
}
|
|
1320
1342
|
return rel;
|
|
1321
1343
|
});
|
package/scripts/generate-data.ts
CHANGED
|
@@ -221,6 +221,14 @@ function buildRefMaps(config, registerCache) {
|
|
|
221
221
|
const refPrefixMap = {};
|
|
222
222
|
const patternIndex = buildPatternIndex(config.datasets, registerCache);
|
|
223
223
|
|
|
224
|
+
// Auto-map each dataset's `ref` field to its dataset ID.
|
|
225
|
+
// This enables cross-dataset link resolution: a concept in cie-2020
|
|
226
|
+
// with ref: { source: "CIE S 017:2011", id: "17-245" } resolves to
|
|
227
|
+
// cie-2011/concept/17-245 because cie-2011 has ref: "CIE S 017:2011".
|
|
228
|
+
for (const ds of config.datasets) {
|
|
229
|
+
if (ds.ref) refPrefixMap[ds.ref] = ds.id;
|
|
230
|
+
}
|
|
231
|
+
|
|
224
232
|
for (const route of config.routing || []) {
|
|
225
233
|
if (route.uri && route.uri.includes('iec') && route.uri.includes('60050')) {
|
|
226
234
|
const mapped = route.targetDataset;
|
|
@@ -508,6 +516,18 @@ function yamlToJsonLd(conceptYaml, register, refMaps) {
|
|
|
508
516
|
if (r.ref.id) ref['gl:id'] = r.ref.id;
|
|
509
517
|
if (r.ref.text) ref['gl:text'] = r.ref.text;
|
|
510
518
|
rel['gl:ref'] = ref;
|
|
519
|
+
|
|
520
|
+
// Resolve cross-dataset refs to gl:target URIs.
|
|
521
|
+
// refPrefixMap maps source labels (e.g. "CIE S 017:2011") to
|
|
522
|
+
// dataset IDs (e.g. "cie-2011"). When the source matches a
|
|
523
|
+
// co-deployed dataset, we can build a navigable concept URI.
|
|
524
|
+
if (r.ref.source && r.ref.id && refMaps?.refPrefixMap?.[r.ref.source]) {
|
|
525
|
+
const datasetId = refMaps.refPrefixMap[r.ref.source];
|
|
526
|
+
rel['gl:target'] = buildConceptUri(refMaps.uriBase, datasetId, r.ref.id);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (r.target) {
|
|
530
|
+
rel['gl:target'] = r.target;
|
|
511
531
|
}
|
|
512
532
|
return rel;
|
|
513
533
|
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// to absolute IRIs, then re-rendered via glossarist-js's n3 writer with
|
|
8
8
|
// the canonical prefix map ).
|
|
9
9
|
|
|
10
|
-
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
11
11
|
import { join, dirname } from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
import {
|
|
@@ -18,10 +18,25 @@ import {
|
|
|
18
18
|
} from 'glossarist/rdf';
|
|
19
19
|
|
|
20
20
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
-
const VOCAB_PATH = join(__dirname, '..', '..', 'data', 'glossarist-vocab.json');
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
// When running from source (tsx), this file is at scripts/lib/ and
|
|
23
|
+
// the data dir is two levels up. When bundled by esbuild into
|
|
24
|
+
// scripts/*.js, __dirname is scripts/ and the data dir is one level up.
|
|
25
|
+
function findVocabPath(): string {
|
|
26
|
+
const candidates = [
|
|
27
|
+
join(__dirname, '..', '..', 'data', 'glossarist-vocab.json'),
|
|
28
|
+
join(__dirname, '..', 'data', 'glossarist-vocab.json'),
|
|
29
|
+
];
|
|
30
|
+
for (const p of candidates) {
|
|
31
|
+
if (existsSync(p)) return p;
|
|
32
|
+
}
|
|
33
|
+
throw new Error(
|
|
34
|
+
`glossarist-vocab.json not found. Tried:\n${candidates.map(p => ' ' + p).join('\n')}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function loadVocab() {
|
|
39
|
+
return JSON.parse(readFileSync(findVocabPath(), 'utf8'));
|
|
25
40
|
}
|
|
26
41
|
|
|
27
42
|
// n3's writer prefixes must omit the trailing # or / — those go in
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end consumer simulation test.
|
|
3
|
+
*
|
|
4
|
+
* Simulates what a real consumer experiences: creates a temp "consumer
|
|
5
|
+
* project" with the compiled package in node_modules, then runs scripts
|
|
6
|
+
* with plain node. Catches packaging bugs that dev-mode tests miss.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import {
|
|
12
|
+
mkdirSync, writeFileSync, cpSync, rmSync, existsSync, readFileSync, readdirSync,
|
|
13
|
+
} from 'node:fs';
|
|
14
|
+
import { resolve, join, dirname } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const ROOT = resolve(__dirname, '..', '..');
|
|
19
|
+
const SCRIPTS_DIR = join(ROOT, 'scripts');
|
|
20
|
+
const CONSUMER_DIR = join(ROOT, '.tmp-consumer-test');
|
|
21
|
+
|
|
22
|
+
describe('end-to-end consumer simulation — compiled package works under plain node', () => {
|
|
23
|
+
beforeAll(() => {
|
|
24
|
+
const build = spawnSync('node', [join(ROOT, 'scripts', 'build-scripts.mjs')], {
|
|
25
|
+
encoding: 'utf8', cwd: ROOT,
|
|
26
|
+
});
|
|
27
|
+
expect(build.status).toBe(0);
|
|
28
|
+
|
|
29
|
+
rmSync(CONSUMER_DIR, { recursive: true, force: true });
|
|
30
|
+
const pkgDest = join(CONSUMER_DIR, 'node_modules', '@glossarist', 'concept-browser');
|
|
31
|
+
mkdirSync(pkgDest, { recursive: true });
|
|
32
|
+
cpSync(join(ROOT, 'scripts'), join(pkgDest, 'scripts'), { recursive: true });
|
|
33
|
+
cpSync(join(ROOT, 'data'), join(pkgDest, 'data'), { recursive: true });
|
|
34
|
+
cpSync(join(ROOT, 'cli'), join(pkgDest, 'cli'), { recursive: true });
|
|
35
|
+
writeFileSync(join(pkgDest, 'package.json'), JSON.stringify({
|
|
36
|
+
name: '@glossarist/concept-browser', version: '0.0.0-test', type: 'module',
|
|
37
|
+
bin: { 'concept-browser': './cli/index.mjs' },
|
|
38
|
+
}));
|
|
39
|
+
}, 60000);
|
|
40
|
+
|
|
41
|
+
afterAll(() => {
|
|
42
|
+
rmSync(CONSUMER_DIR, { recursive: true, force: true });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('compiled scripts can find package data files from node_modules location', () => {
|
|
46
|
+
const scriptDir = join(CONSUMER_DIR, 'node_modules', '@glossarist', 'concept-browser', 'scripts');
|
|
47
|
+
const pathFromBundled = join(scriptDir, '..', 'data', 'glossarist-vocab.json');
|
|
48
|
+
expect(existsSync(pathFromBundled)).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('compiled scripts list matches source scripts (no missing outputs)', () => {
|
|
52
|
+
const sourceTs = readdirSync(SCRIPTS_DIR)
|
|
53
|
+
.filter(f => f.endsWith('.ts') && !f.includes('__tests__') && f !== 'smoke.ts')
|
|
54
|
+
.map(f => f.replace(/\.ts$/, ''));
|
|
55
|
+
const compiledJs = readdirSync(SCRIPTS_DIR)
|
|
56
|
+
.filter(f => f.endsWith('.js'))
|
|
57
|
+
.map(f => f.replace(/\.js$/, ''));
|
|
58
|
+
for (const name of sourceTs) {
|
|
59
|
+
expect(compiledJs).toContain(name);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('validate-yaml.js validates against the shipped schema from node_modules', () => {
|
|
64
|
+
const consumerPkg = join(CONSUMER_DIR, 'node_modules', '@glossarist', 'concept-browser');
|
|
65
|
+
const scriptPath = join(consumerPkg, 'scripts', 'validate-yaml.js');
|
|
66
|
+
const schemaPath = join(consumerPkg, 'data', 'concept-model', 'shapes', 'glossarist.concept.yaml.schema.json');
|
|
67
|
+
const code = [
|
|
68
|
+
'import { validateYaml } from ' + JSON.stringify(scriptPath) + ';',
|
|
69
|
+
'import { readFileSync } from "fs";',
|
|
70
|
+
'const schema = JSON.parse(readFileSync(' + JSON.stringify(schemaPath) + ', "utf8"));',
|
|
71
|
+
'const r = validateYaml("termid: test", schema);',
|
|
72
|
+
'console.log("valid=" + r.valid);',
|
|
73
|
+
].join('\n');
|
|
74
|
+
const result = spawnSync('node', ['--input-type=module', '-e', code], {
|
|
75
|
+
encoding: 'utf8', cwd: ROOT,
|
|
76
|
+
});
|
|
77
|
+
expect(result.status).toBe(0);
|
|
78
|
+
expect(result.stdout).toContain('valid=true');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('no compiled script references a devDependency-only package', () => {
|
|
82
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'));
|
|
83
|
+
const deps = new Set(Object.keys(pkg.dependencies || {}));
|
|
84
|
+
const devDeps = new Set(Object.keys(pkg.devDependencies || {}));
|
|
85
|
+
const violations: string[] = [];
|
|
86
|
+
for (const file of readdirSync(SCRIPTS_DIR).filter(f => f.endsWith('.js'))) {
|
|
87
|
+
const src = readFileSync(join(SCRIPTS_DIR, file), 'utf8');
|
|
88
|
+
const importRe = new RegExp('(?:from\\s+|import\\s*\\(\\s*)["\']([^"\'./@][^"\']*)["\']', 'g');
|
|
89
|
+
let m;
|
|
90
|
+
while ((m = importRe.exec(src)) !== null) {
|
|
91
|
+
const name = m[1].startsWith('@') ? m[1].split('/').slice(0, 2).join('/') : m[1].split('/')[0];
|
|
92
|
+
if (name.startsWith('node:') || name === 'node') continue;
|
|
93
|
+
if (devDeps.has(name) && !deps.has(name)) violations.push(file + ' -> ' + name);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
expect(violations).toEqual([]);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -97,4 +97,14 @@ describe('published-package contract — compiled .js scripts load under plain n
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
});
|
|
100
|
+
|
|
101
|
+
it('compiled scripts can find package data files (glossarist-vocab.json path regression)', () => {
|
|
102
|
+
// When esbuild bundles scripts/lib/vocab-turtle.ts into scripts/generate-data.js,
|
|
103
|
+
// __dirname changes from scripts/lib/ to scripts/. The path to data/ must
|
|
104
|
+
// resolve correctly in both modes. This test verifies the fallback logic.
|
|
105
|
+
const dataDir = join(ROOT, 'data');
|
|
106
|
+
expect(existsSync(join(dataDir, 'glossarist-vocab.json'))).toBe(true);
|
|
107
|
+
expect(existsSync(join(dataDir, 'colors.json'))).toBe(true);
|
|
108
|
+
expect(existsSync(join(dataDir, 'concept-model', 'shapes', 'glossarist.shacl.ttl'))).toBe(true);
|
|
109
|
+
});
|
|
100
110
|
});
|