@glossarist/concept-browser 0.7.136 → 0.7.138

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.136",
3
+ "version": "0.7.138",
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": {
@@ -43,7 +43,7 @@
43
43
  "astro": "^7.0.7",
44
44
  "d3": "^7.9.0",
45
45
  "favicons": "^7.2.0",
46
- "glossarist": "^0.4.58",
46
+ "glossarist": "^0.4.61",
47
47
  "js-yaml": "^4.1.0",
48
48
  "jszip": "^3.10.1",
49
49
  "n3": "^1.26.0",
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ buildConceptUri,
4
+ buildConceptUriPrefix,
5
+ buildDatasetUriPrefix,
6
+ buildSiteOrigin,
7
+ } from '../lib/concept-uri';
8
+
9
+ describe('buildSiteOrigin — deployment hosting coordinate', () => {
10
+ it('prepends https:// to a scheme-less domain that carries a custom path', () => {
11
+ expect(buildSiteOrigin('oimlsmart.github.io/vocab', 'https://identity.example/viml'))
12
+ .toBe('https://oimlsmart.github.io/vocab');
13
+ });
14
+
15
+ it('keeps the configured path segment (/iala-vocab)', () => {
16
+ expect(buildSiteOrigin('www.glossarist.org/iala-vocab', 'https://identity.example/iala'))
17
+ .toBe('https://www.glossarist.org/iala-vocab');
18
+ });
19
+
20
+ it('handles an apex domain with no path', () => {
21
+ expect(buildSiteOrigin('isotc204.geolexica.org', 'https://isotc204.geolexica.org'))
22
+ .toBe('https://isotc204.geolexica.org');
23
+ });
24
+
25
+ it('normalizes a domain that already declares a scheme and trailing slash', () => {
26
+ expect(buildSiteOrigin('https://www.example.org/site/', 'https://identity.example'))
27
+ .toBe('https://www.example.org/site');
28
+ });
29
+
30
+ it('falls back to uriBase only when no domain is declared', () => {
31
+ expect(buildSiteOrigin(undefined, 'https://identity.example/reg')).toBe('https://identity.example/reg');
32
+ expect(buildSiteOrigin(null, 'https://identity.example/reg')).toBe('https://identity.example/reg');
33
+ });
34
+
35
+ it('keeps identity (uriBase) and hosting (domain) independent — /dataset/ routes build on either', () => {
36
+ const sitePrefix = buildConceptUriPrefix(buildSiteOrigin('oimlsmart.github.io/vocab', 'https://identity.example/viml'), 'viml-2013');
37
+ expect(sitePrefix).toBe('https://oimlsmart.github.io/vocab/dataset/viml-2013/concept/');
38
+ const identityPrefix = buildConceptUriPrefix('https://identity.example/viml', 'viml-2013');
39
+ expect(identityPrefix).toBe('https://identity.example/viml/dataset/viml-2013/concept/');
40
+ expect(buildConceptUri('https://identity.example/viml', 'viml-2013', '0.01'))
41
+ .toBe('https://identity.example/viml/dataset/viml-2013/concept/0.01');
42
+ expect(buildDatasetUriPrefix('https://identity.example/viml', 'viml-2013'))
43
+ .toBe('https://identity.example/viml/dataset/viml-2013/');
44
+ });
45
+ });
@@ -558,6 +558,13 @@ function buildConceptUri(uriBase, registerId, conceptId) {
558
558
  function buildConceptUriPrefix(uriBase, registerId) {
559
559
  return `${uriBase}/dataset/${registerId}/concept/`;
560
560
  }
561
+ function buildSiteOrigin(domain, uriBase) {
562
+ if (domain) {
563
+ const trimmed = String(domain).trim().replace(/\/+$/, "");
564
+ return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
565
+ }
566
+ return uriBase.replace(/\/+$/, "");
567
+ }
561
568
 
562
569
  // scripts/lib/harmonize-concept.ts
563
570
  import fs from "fs";
@@ -931,7 +938,8 @@ function buildRefMaps(config2, registerCache2) {
931
938
  if (xref.refPrefixMap) Object.assign(refPrefixMap, xref.refPrefixMap);
932
939
  const uriBase = config2.uriBase;
933
940
  if (!uriBase) throw new Error("site-config.yml: uriBase is required");
934
- return { patternIndex, refPrefixMap, uriBase, register: null };
941
+ const siteOrigin = buildSiteOrigin(config2.domain, uriBase);
942
+ return { patternIndex, refPrefixMap, uriBase, siteOrigin, register: null };
935
943
  }
936
944
  function resolveIevRef(display, term, refPrefixMap, uriBase) {
937
945
  if (!display.startsWith("IEV:")) return null;
@@ -1374,7 +1382,8 @@ async function processDataset(dir, register, opts) {
1374
1382
  const aggregate = await emitOutputSet(conceptInstances, {
1375
1383
  registerId: register,
1376
1384
  uriBase: refMaps.uriBase,
1377
- languageOrder: opts.languageOrder || opts.languages
1385
+ languageOrder: opts.languageOrder || opts.languages,
1386
+ conceptUriPrefix: buildConceptUriPrefix(refMaps.siteOrigin, register)
1378
1387
  });
1379
1388
  fs2.writeFileSync(path3.join(DATA, register, `${register}.csv`), aggregate.csv);
1380
1389
  fs2.writeFileSync(path3.join(DATA, register, `${register}.jsonld`), aggregate.jsonld);
@@ -35,7 +35,7 @@ import type {
35
35
  YamlNewsFrontmatter,
36
36
  YamlContentPage,
37
37
  } from './lib/yaml-types';
38
- import { buildConceptUri, buildConceptUriPrefix } from './lib/concept-uri';
38
+ import { buildConceptUri, buildConceptUriPrefix, buildSiteOrigin } from './lib/concept-uri';
39
39
  import {
40
40
  loadConceptFile,
41
41
  readYaml,
@@ -246,7 +246,11 @@ function buildRefMaps(config, registerCache) {
246
246
 
247
247
  const uriBase = config.uriBase;
248
248
  if (!uriBase) throw new Error('site-config.yml: uriBase is required');
249
- return { patternIndex, refPrefixMap, uriBase, register: null };
249
+ // Public hosting coordinate from the deployment's own configuration —
250
+ // `domain` carries any custom path (/vocab/, /iala-vocab). Used for
251
+ // human-facing links (CSV uri column); uriBase stays the RDF identity.
252
+ const siteOrigin = buildSiteOrigin(config.domain, uriBase);
253
+ return { patternIndex, refPrefixMap, uriBase, siteOrigin, register: null };
250
254
  }
251
255
 
252
256
 
@@ -857,10 +861,14 @@ async function processDataset(dir, register, opts) {
857
861
 
858
862
  // Aggregate distributions: single-file exports of the whole register,
859
863
  // emitted by glossarist's output-set API from the canonical model walk.
864
+ // The CSV uri column is a link a human opens — it carries the SPA route
865
+ // shape on the deployment's configured host (siteOrigin), not the
866
+ // RDF-canonical IRI.
860
867
  const aggregate = await emitOutputSet(conceptInstances, {
861
868
  registerId: register,
862
869
  uriBase: refMaps.uriBase,
863
870
  languageOrder: opts.languageOrder || opts.languages,
871
+ conceptUriPrefix: buildConceptUriPrefix(refMaps.siteOrigin, register),
864
872
  });
865
873
  fs.writeFileSync(path.join(DATA, register, `${register}.csv`), aggregate.csv!);
866
874
  fs.writeFileSync(path.join(DATA, register, `${register}.jsonld`), aggregate.jsonld!);
@@ -26,3 +26,21 @@ export function buildConceptUriPrefix(uriBase: string, registerId: string): stri
26
26
  export function buildDatasetUriPrefix(uriBase: string, registerId: string): string {
27
27
  return `${uriBase}/dataset/${registerId}/`;
28
28
  }
29
+
30
+ /**
31
+ * Public site origin from the deployment's own configuration.
32
+ *
33
+ * site-config `domain` is the hosting coordinate and carries any custom
34
+ * path (e.g. "oimlsmart.github.io/vocab", "www.glossarist.org/iala-vocab",
35
+ * "isotc204.geolexica.org"). Links a human opens (CSV `uri` column) must
36
+ * use this — NOT uriBase, which is the RDF identity root and may differ
37
+ * from the deployed host. Falls back to uriBase only when a deployment
38
+ * declares no domain.
39
+ */
40
+ export function buildSiteOrigin(domain: string | undefined | null, uriBase: string): string {
41
+ if (domain) {
42
+ const trimmed = String(domain).trim().replace(/\/+$/, '');
43
+ return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
44
+ }
45
+ return uriBase.replace(/\/+$/, '');
46
+ }
@@ -103,4 +103,19 @@ describe('conceptsToCsv (glossarist output API)', () => {
103
103
  expect(rows[1]![0]).toBe('222-02-02');
104
104
  expect(rows[1]![4]).toBe('');
105
105
  });
106
+
107
+ // generate-data passes conceptUriPrefix = buildConceptUriPrefix(uriBase,
108
+ // register) so the uri column carries the SPA route shape
109
+ // (/dataset/<register>/concept/<id>) and opens in a browser — the
110
+ // RDF-canonical fallback would 404.
111
+ it('uri column carries the SPA route shape via conceptUriPrefix', () => {
112
+ const bare = new Concept({ id: '1-1-010', termid: '1-1-010', status: 'valid' });
113
+ const csv = conceptsToCsv([bare], {
114
+ uriBase: 'https://www.glossarist.org/iala-vocab',
115
+ registerId: 'iala-2023',
116
+ conceptUriPrefix: 'https://www.glossarist.org/iala-vocab/dataset/iala-2023/concept/',
117
+ });
118
+ const rows = parseCsv(csv);
119
+ expect(rows[1]![1]).toBe('https://www.glossarist.org/iala-vocab/dataset/iala-2023/concept/1-1-010');
120
+ });
106
121
  });