@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
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env node
2
+
3
+ // scripts/fetch-datasets.ts
4
+ import fs2 from "fs";
5
+ import path2 from "path";
6
+ import JSZip from "jszip";
7
+ import { loadGcr } from "glossarist";
8
+ import { execFileSync } from "child_process";
9
+ import { fileURLToPath as fileURLToPath2 } from "url";
10
+
11
+ // scripts/load-site-config.ts
12
+ import { readFileSync, existsSync } from "fs";
13
+ import { resolve, dirname } from "path";
14
+ import { fileURLToPath } from "url";
15
+ import yaml from "js-yaml";
16
+ var __dirname = dirname(fileURLToPath(import.meta.url));
17
+ var projectRoot = resolve(__dirname, "..");
18
+ var SNAKE_TO_CAMEL = {
19
+ // Site-level
20
+ uri_base: "uriBase",
21
+ base_path: "basePath",
22
+ ui_languages: "uiLanguages",
23
+ dataset_groups: "datasetGroups",
24
+ dataset_translations: "datasetTranslations",
25
+ primary_color: "primaryColor",
26
+ dark_color: "darkColor",
27
+ owner_name: "ownerName",
28
+ owner_url: "ownerUrl",
29
+ powered_by: "poweredBy",
30
+ footer_nav: "footerNav",
31
+ footer_logo: "footerLogo",
32
+ news_dir: "newsDir",
33
+ // Dataset-level
34
+ gcr_package: "gcrPackage",
35
+ source_repo: "sourceRepo",
36
+ local_path: "localPath",
37
+ local_light: "localLight",
38
+ local_dark: "localDark",
39
+ uri_aliases: "uriAliases",
40
+ language_order: "languageOrder",
41
+ ref_aliases: "refAliases",
42
+ urn_aliases: "urnAliases",
43
+ existing_site_url: "existingSiteUrl",
44
+ edition_status: "editionStatus",
45
+ dataset_uri: "datasetUri",
46
+ schema_version: "schemaVersion"
47
+ };
48
+ function normalizeKey(key) {
49
+ if (typeof key !== "string") return key;
50
+ if (Object.prototype.hasOwnProperty.call(SNAKE_TO_CAMEL, key)) {
51
+ return SNAKE_TO_CAMEL[key];
52
+ }
53
+ if (key.includes("_") && /^[a-z][a-z0-9_]*$/.test(key)) {
54
+ return key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
55
+ }
56
+ return key;
57
+ }
58
+ function normalizeObject(obj) {
59
+ if (Array.isArray(obj)) return obj.map(normalizeObject);
60
+ if (obj && typeof obj === "object") {
61
+ const out = {};
62
+ for (const [k, v] of Object.entries(obj)) {
63
+ out[normalizeKey(k)] = normalizeObject(v);
64
+ }
65
+ return out;
66
+ }
67
+ return obj;
68
+ }
69
+ function findConfigFile(args = []) {
70
+ if (process.env.SITE_CONFIG) {
71
+ return resolve(process.env.SITE_CONFIG);
72
+ }
73
+ for (const dir of [process.cwd(), projectRoot]) {
74
+ const p = resolve(dir, "site-config.yml");
75
+ if (existsSync(p)) return p;
76
+ }
77
+ throw new Error("No site config found. Set SITE_CONFIG or create site-config.yml");
78
+ }
79
+ function loadSiteConfig(args = []) {
80
+ const configPath = findConfigFile(args);
81
+ const raw = yaml.load(readFileSync(configPath, "utf-8"));
82
+ const config = normalizeObject(raw);
83
+ return { config, configPath };
84
+ }
85
+
86
+ // scripts/lib/local-path-safety.ts
87
+ import fs from "fs";
88
+ import path from "path";
89
+ function physicalPath(p) {
90
+ if (fs.existsSync(p)) return fs.realpathSync(p);
91
+ const parent = path.dirname(p);
92
+ const parentReal = fs.existsSync(parent) ? fs.realpathSync(parent) : physicalPath(parent);
93
+ return path.join(parentReal, path.basename(p));
94
+ }
95
+ function assertLocalPathSafe(datasetId, localPath, { root = process.cwd(), datasetsDir } = {}) {
96
+ const datasetsRoot = datasetsDir || path.join(root, ".datasets");
97
+ const localResolved = path.resolve(root, localPath);
98
+ if (!fs.existsSync(localResolved)) {
99
+ throw new Error(`localPath for ${datasetId} does not exist: ${localResolved}`);
100
+ }
101
+ const localReal = fs.realpathSync(localResolved);
102
+ const stagedAbs = path.join(datasetsRoot, datasetId);
103
+ const stagedReal = physicalPath(stagedAbs);
104
+ if (localReal === stagedReal) {
105
+ throw new Error(
106
+ `localPath for ${datasetId} resolves to the same physical location as .datasets/${datasetId} (${localReal}). Refusing to operate \u2014 source and staging would clobber. Use a path outside .datasets/.`
107
+ );
108
+ }
109
+ if (localReal.startsWith(stagedReal + path.sep)) {
110
+ throw new Error(
111
+ `localPath for ${datasetId} is nested inside .datasets/${datasetId}. Refusing to operate \u2014 staging operations would destroy source data. Use a path outside .datasets/.`
112
+ );
113
+ }
114
+ if (stagedReal.startsWith(localReal + path.sep)) {
115
+ throw new Error(
116
+ `localPath for ${datasetId} contains .datasets/${datasetId}. Refusing to operate \u2014 staging operations would destroy source data. Use a path outside localPath.`
117
+ );
118
+ }
119
+ return localReal;
120
+ }
121
+
122
+ // scripts/fetch-datasets.ts
123
+ var ROOT = process.cwd();
124
+ var DATASETS_DIR = path2.join(ROOT, ".datasets");
125
+ var GCR_DIR = path2.join(ROOT, ".gcr");
126
+ function matchUriPattern(uri, pattern) {
127
+ if (!pattern.endsWith("*")) return uri === pattern;
128
+ return uri.startsWith(pattern.slice(0, -1));
129
+ }
130
+ async function downloadGcr(url, destPath) {
131
+ fs2.mkdirSync(path2.dirname(destPath), { recursive: true });
132
+ console.log(` Downloading ${url}...`);
133
+ const resp = await fetch(url);
134
+ if (!resp.ok) throw new Error(`Download failed: ${resp.status} ${resp.statusText}`);
135
+ const buf = Buffer.from(await resp.arrayBuffer());
136
+ fs2.writeFileSync(destPath, buf);
137
+ console.log(` Saved to ${destPath} (${(buf.length / 1024).toFixed(0)} KB)`);
138
+ }
139
+ async function extractGcr(gcrPath, targetDir) {
140
+ const targetAbs = path2.resolve(targetDir);
141
+ if (fs2.existsSync(targetAbs)) {
142
+ fs2.rmSync(targetAbs, { recursive: true, force: true });
143
+ }
144
+ fs2.mkdirSync(targetAbs, { recursive: true });
145
+ const buf = fs2.readFileSync(gcrPath);
146
+ const zip = await JSZip.loadAsync(buf);
147
+ const entries = Object.values(zip.files);
148
+ for (const entry of entries) {
149
+ if (entry.dir) continue;
150
+ const dest = path2.resolve(targetAbs, entry.name);
151
+ if (dest !== targetAbs && !dest.startsWith(targetAbs + path2.sep)) {
152
+ throw new Error(`Refusing to extract entry outside target dir: ${entry.name}`);
153
+ }
154
+ fs2.mkdirSync(path2.dirname(dest), { recursive: true });
155
+ const content = await entry.async("nodebuffer");
156
+ fs2.writeFileSync(dest, content);
157
+ }
158
+ console.log(` Extracted to ${targetAbs}`);
159
+ }
160
+ async function readGcrMetadata(gcrPath) {
161
+ if (!fs2.existsSync(gcrPath)) return null;
162
+ try {
163
+ const buf = fs2.readFileSync(gcrPath);
164
+ const pkg = await loadGcr(buf);
165
+ const meta = await pkg.metadata();
166
+ return meta ? meta.toJSON() : null;
167
+ } catch {
168
+ return null;
169
+ }
170
+ }
171
+ function validateDependencies(config, gcrMetadata) {
172
+ const errors = [];
173
+ const allProvidedUris = [];
174
+ for (const ds of config.datasets) {
175
+ allProvidedUris.push(ds.uri);
176
+ if (ds.uriAliases) allProvidedUris.push(...ds.uriAliases);
177
+ }
178
+ const routingUris = (config.routing || []).map((r) => r.uri);
179
+ for (const ds of config.datasets) {
180
+ const meta = gcrMetadata[ds.id];
181
+ if (!meta?.dependencies?.length) continue;
182
+ for (const dep of meta.dependencies) {
183
+ const depUri = dep.uri;
184
+ const satisfied = [...allProvidedUris, ...routingUris].some((p) => matchUriPattern(depUri, p));
185
+ if (!satisfied) {
186
+ errors.push(`GCR '${ds.id}' depends on '${depUri}' (${dep.refCount} refs) but no provider or route configured`);
187
+ }
188
+ }
189
+ }
190
+ return errors;
191
+ }
192
+ function cloneOrUpdate(sourceRepo, targetDir) {
193
+ const env = { ...process.env };
194
+ let repoUrl = sourceRepo;
195
+ if (env.GITHUB_TOKEN) {
196
+ repoUrl = sourceRepo.replace("https://", `https://x-access-token:${env.GITHUB_TOKEN}@`);
197
+ }
198
+ const targetAbs = path2.resolve(targetDir);
199
+ if (fs2.existsSync(path2.join(targetAbs, ".git"))) {
200
+ console.log(` Updating existing clone...`);
201
+ try {
202
+ execFileSync("git", ["fetch", "origin"], { cwd: targetAbs, stdio: "pipe", env });
203
+ execFileSync("git", ["reset", "--hard", "origin/HEAD"], { cwd: targetAbs, stdio: "pipe", env });
204
+ execFileSync("git", ["clean", "-fd"], { cwd: targetAbs, stdio: "pipe", env });
205
+ } catch {
206
+ console.warn(` git update failed, re-cloning`);
207
+ fs2.rmSync(targetAbs, { recursive: true, force: true });
208
+ execFileSync("git", ["clone", "--depth", "1", repoUrl, targetAbs], { stdio: "pipe", env });
209
+ }
210
+ } else {
211
+ fs2.mkdirSync(targetAbs, { recursive: true });
212
+ console.log(` Cloning ${sourceRepo}...`);
213
+ execFileSync("git", ["clone", "--depth", "1", repoUrl, targetAbs], { stdio: "pipe", env });
214
+ }
215
+ }
216
+ async function main() {
217
+ console.log("Fetching glossarist datasets...\n");
218
+ const { config } = loadSiteConfig();
219
+ const gcrMetadata = {};
220
+ for (const ds of config.datasets) {
221
+ console.log(`${ds.id}:`);
222
+ const gcrPath = path2.join(GCR_DIR, `${ds.id}.gcr`);
223
+ const targetDir = path2.join(DATASETS_DIR, ds.id);
224
+ try {
225
+ if (fs2.existsSync(gcrPath)) {
226
+ console.log(` Using local .gcr/${ds.id}.gcr`);
227
+ await extractGcr(gcrPath, targetDir);
228
+ } else if (ds.gcrPackage) {
229
+ console.log(` Using GCR package: ${ds.gcrPackage}`);
230
+ try {
231
+ await downloadGcr(ds.gcrPackage, gcrPath);
232
+ } catch (e) {
233
+ console.warn(` GCR download failed: ${e.message}`);
234
+ console.warn(` Skipping ${ds.id}`);
235
+ console.log();
236
+ continue;
237
+ }
238
+ await extractGcr(gcrPath, targetDir);
239
+ } else if (ds.localPath) {
240
+ const localResolved = assertLocalPathSafe(ds.id, ds.localPath);
241
+ console.log(` Using localPath in-place: ${localResolved}`);
242
+ } else if (ds.sourceRepo) {
243
+ cloneOrUpdate(ds.sourceRepo, targetDir);
244
+ } else {
245
+ console.warn(` No source configured, skipping`);
246
+ console.log();
247
+ continue;
248
+ }
249
+ const meta = await readGcrMetadata(gcrPath);
250
+ if (meta) {
251
+ gcrMetadata[ds.id] = meta;
252
+ console.log(` ${meta.concept_count || "?"} concepts, ${meta.uri_prefix || "no uri"}`);
253
+ }
254
+ } catch (e) {
255
+ console.warn(` Failed: ${e.message}`);
256
+ console.warn(` Skipping ${ds.id}`);
257
+ }
258
+ console.log();
259
+ }
260
+ console.log("Validating dependencies...\n");
261
+ const errors = validateDependencies(config, gcrMetadata);
262
+ if (errors.length) {
263
+ console.error("Dependency validation FAILED:");
264
+ for (const err of errors) {
265
+ console.error(` \u2717 ${err}`);
266
+ }
267
+ process.exit(1);
268
+ }
269
+ console.log("All dependencies satisfied.\n");
270
+ console.log("Done.");
271
+ }
272
+ var isDirectInvocation = process.argv[1] && fs2.realpathSync(process.argv[1]) === fs2.realpathSync(fileURLToPath2(import.meta.url));
273
+ if (isDirectInvocation) {
274
+ await main();
275
+ }
276
+ export {
277
+ main
278
+ };
@@ -20,8 +20,8 @@ import JSZip from 'jszip';
20
20
  import { loadGcr } from 'glossarist';
21
21
  import { execFileSync } from 'child_process';
22
22
  import { fileURLToPath } from 'url';
23
- import { loadSiteConfig } from './load-site-config.mjs';
24
- import { assertLocalPathSafe } from './lib/local-path-safety.mjs';const ROOT = process.cwd();
23
+ import { loadSiteConfig } from './load-site-config';
24
+ import { assertLocalPathSafe } from './lib/local-path-safety';const ROOT = process.cwd();
25
25
  const DATASETS_DIR = path.join(ROOT, '.datasets');
26
26
  const GCR_DIR = path.join(ROOT, '.gcr');
27
27
 
@@ -1,15 +1,14 @@
1
- import { copyFileSync, existsSync } from 'fs';
2
- import { join, dirname } from 'path';
3
- import { fileURLToPath } from 'url';
4
-
5
- const __dirname = dirname(fileURLToPath(import.meta.url));
6
- const dist = join(process.cwd(), 'dist');
7
- const indexHtml = join(dist, 'index.html');
8
- const notFoundHtml = join(dist, '404.html');
9
-
1
+ // scripts/generate-404.ts
2
+ import { copyFileSync, existsSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+ var __dirname = dirname(fileURLToPath(import.meta.url));
6
+ var dist = join(process.cwd(), "dist");
7
+ var indexHtml = join(dist, "index.html");
8
+ var notFoundHtml = join(dist, "404.html");
10
9
  if (existsSync(indexHtml)) {
11
10
  copyFileSync(indexHtml, notFoundHtml);
12
- console.log('Created dist/404.html for SPA fallback');
11
+ console.log("Created dist/404.html for SPA fallback");
13
12
  } else {
14
- console.warn('dist/index.html not found — skipping 404.html generation');
13
+ console.warn("dist/index.html not found \u2014 skipping 404.html generation");
15
14
  }
@@ -0,0 +1,15 @@
1
+ import { copyFileSync, existsSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const dist = join(process.cwd(), 'dist');
7
+ const indexHtml = join(dist, 'index.html');
8
+ const notFoundHtml = join(dist, '404.html');
9
+
10
+ if (existsSync(indexHtml)) {
11
+ copyFileSync(indexHtml, notFoundHtml);
12
+ console.log('Created dist/404.html for SPA fallback');
13
+ } else {
14
+ console.warn('dist/index.html not found — skipping 404.html generation');
15
+ }