@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,241 @@
1
+ #!/usr/bin/env node
2
+
3
+ // scripts/process-about-pages.ts
4
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync, readdirSync, statSync, realpathSync } from "node:fs";
5
+ import { join, basename, extname, resolve as resolve2 } from "node:path";
6
+ import { cwd } from "node:process";
7
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
8
+
9
+ // scripts/load-site-config.ts
10
+ import { readFileSync, existsSync } from "fs";
11
+ import { resolve, dirname } from "path";
12
+ import { fileURLToPath } from "url";
13
+ import yaml from "js-yaml";
14
+ var __dirname = dirname(fileURLToPath(import.meta.url));
15
+ var projectRoot = resolve(__dirname, "..");
16
+ var SNAKE_TO_CAMEL = {
17
+ // Site-level
18
+ uri_base: "uriBase",
19
+ base_path: "basePath",
20
+ ui_languages: "uiLanguages",
21
+ dataset_groups: "datasetGroups",
22
+ dataset_translations: "datasetTranslations",
23
+ primary_color: "primaryColor",
24
+ dark_color: "darkColor",
25
+ owner_name: "ownerName",
26
+ owner_url: "ownerUrl",
27
+ powered_by: "poweredBy",
28
+ footer_nav: "footerNav",
29
+ footer_logo: "footerLogo",
30
+ news_dir: "newsDir",
31
+ // Dataset-level
32
+ gcr_package: "gcrPackage",
33
+ source_repo: "sourceRepo",
34
+ local_path: "localPath",
35
+ local_light: "localLight",
36
+ local_dark: "localDark",
37
+ uri_aliases: "uriAliases",
38
+ language_order: "languageOrder",
39
+ ref_aliases: "refAliases",
40
+ urn_aliases: "urnAliases",
41
+ existing_site_url: "existingSiteUrl",
42
+ edition_status: "editionStatus",
43
+ dataset_uri: "datasetUri",
44
+ schema_version: "schemaVersion"
45
+ };
46
+ function normalizeKey(key) {
47
+ if (typeof key !== "string") return key;
48
+ if (Object.prototype.hasOwnProperty.call(SNAKE_TO_CAMEL, key)) {
49
+ return SNAKE_TO_CAMEL[key];
50
+ }
51
+ if (key.includes("_") && /^[a-z][a-z0-9_]*$/.test(key)) {
52
+ return key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
53
+ }
54
+ return key;
55
+ }
56
+ function normalizeObject(obj) {
57
+ if (Array.isArray(obj)) return obj.map(normalizeObject);
58
+ if (obj && typeof obj === "object") {
59
+ const out = {};
60
+ for (const [k, v] of Object.entries(obj)) {
61
+ out[normalizeKey(k)] = normalizeObject(v);
62
+ }
63
+ return out;
64
+ }
65
+ return obj;
66
+ }
67
+ function findConfigFile(args = []) {
68
+ if (process.env.SITE_CONFIG) {
69
+ return resolve(process.env.SITE_CONFIG);
70
+ }
71
+ for (const dir of [process.cwd(), projectRoot]) {
72
+ const p = resolve(dir, "site-config.yml");
73
+ if (existsSync(p)) return p;
74
+ }
75
+ throw new Error("No site config found. Set SITE_CONFIG or create site-config.yml");
76
+ }
77
+ function loadSiteConfig(args = []) {
78
+ const configPath = findConfigFile(args);
79
+ const raw = yaml.load(readFileSync(configPath, "utf-8"));
80
+ const config = normalizeObject(raw);
81
+ return { config, configPath };
82
+ }
83
+
84
+ // scripts/process-about-pages.ts
85
+ function loadSiteConfigForAbout() {
86
+ try {
87
+ const { config } = loadSiteConfig([]);
88
+ return config;
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+ function roots() {
94
+ const ROOT = cwd();
95
+ return {
96
+ ROOT,
97
+ PUBLIC_PAGES: join(ROOT, "public", "pages"),
98
+ DATASETS_DIR: join(ROOT, ".datasets"),
99
+ GROUPS_CONTENT_DIR: join(ROOT, "site-content", "groups")
100
+ };
101
+ }
102
+ function renderMarkdown(text) {
103
+ const lines = text.split("\n");
104
+ const html = [];
105
+ let inList = false;
106
+ for (const line of lines) {
107
+ const trimmed = line.trim();
108
+ if (!trimmed) {
109
+ if (inList) {
110
+ html.push("</ul>");
111
+ inList = false;
112
+ }
113
+ continue;
114
+ }
115
+ if (/^#{1}\s/.test(trimmed)) {
116
+ if (inList) {
117
+ html.push("</ul>");
118
+ inList = false;
119
+ }
120
+ html.push(`<h1>${inline(trimmed.replace(/^#\s/, ""))}</h1>`);
121
+ } else if (/^#{2}\s/.test(trimmed)) {
122
+ if (inList) {
123
+ html.push("</ul>");
124
+ inList = false;
125
+ }
126
+ html.push(`<h2>${inline(trimmed.replace(/^##\s/, ""))}</h2>`);
127
+ } else if (/^#{3}\s/.test(trimmed)) {
128
+ if (inList) {
129
+ html.push("</ul>");
130
+ inList = false;
131
+ }
132
+ html.push(`<h3>${inline(trimmed.replace(/^###\s/, ""))}</h3>`);
133
+ } else if (/^[-*]\s/.test(trimmed)) {
134
+ if (!inList) {
135
+ html.push("<ul>");
136
+ inList = true;
137
+ }
138
+ html.push(`<li>${inline(trimmed.replace(/^[-*]\s/, ""))}</li>`);
139
+ } else {
140
+ if (inList) {
141
+ html.push("</ul>");
142
+ inList = false;
143
+ }
144
+ html.push(`<p>${inline(trimmed)}</p>`);
145
+ }
146
+ }
147
+ if (inList) html.push("</ul>");
148
+ return html.join("\n");
149
+ }
150
+ function inline(text) {
151
+ return text.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>").replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>').replace(/`(.+?)`/g, "<code>$1</code>");
152
+ }
153
+ function processAboutDir(sourceDir, outputPrefix, publicPagesDir) {
154
+ if (!existsSync2(sourceDir)) return 0;
155
+ let count = 0;
156
+ for (const file of readdirSync(sourceDir)) {
157
+ const fullPath = join(sourceDir, file);
158
+ if (!statSync(fullPath).isFile()) continue;
159
+ const ext = extname(file);
160
+ if (ext !== ".md" && ext !== ".adoc" && ext !== ".html") continue;
161
+ const base = basename(file, ext);
162
+ const langMatch = base.match(/^about\.(\w+)$/);
163
+ const lang = langMatch ? langMatch[1] : "eng";
164
+ const outputName = lang === "eng" ? `${outputPrefix}.json` : `${outputPrefix}.${lang}.json`;
165
+ const raw = readFileSync2(fullPath, "utf8");
166
+ let html;
167
+ let title;
168
+ if (ext === ".html") {
169
+ html = raw;
170
+ const titleMatch = raw.match(/<h1[^>]*>(.+?)<\/h1>/);
171
+ title = titleMatch ? titleMatch[1] : "About";
172
+ } else if (ext === ".md") {
173
+ html = renderMarkdown(raw);
174
+ const titleMatch = raw.match(/^#\s+(.+)$/m);
175
+ title = titleMatch ? titleMatch[1] : "About";
176
+ } else {
177
+ html = `<p>${raw.split("\n").filter((l) => l.trim() && !l.startsWith("=")).join("</p>\n<p>")}</p>`;
178
+ const titleMatch = raw.match(/^=\s+(.+)$/m);
179
+ title = titleMatch ? titleMatch[1] : "About";
180
+ }
181
+ const output = { title, html };
182
+ mkdirSync(publicPagesDir, { recursive: true });
183
+ writeFileSync(join(publicPagesDir, outputName), JSON.stringify(output));
184
+ count++;
185
+ console.log(` Compiled: ${file} \u2192 public/pages/${outputName}`);
186
+ }
187
+ return count;
188
+ }
189
+ function main() {
190
+ const { ROOT, PUBLIC_PAGES, DATASETS_DIR, GROUPS_CONTENT_DIR } = roots();
191
+ mkdirSync(PUBLIC_PAGES, { recursive: true });
192
+ let total = 0;
193
+ const siteConfig = loadSiteConfigForAbout();
194
+ const localPathMap = /* @__PURE__ */ new Map();
195
+ for (const ds of siteConfig?.datasets || []) {
196
+ if (ds.id && ds.localPath) {
197
+ localPathMap.set(ds.id, resolve2(ROOT, ds.localPath));
198
+ }
199
+ }
200
+ const seenIds = /* @__PURE__ */ new Set();
201
+ if (existsSync2(DATASETS_DIR)) {
202
+ for (const dsId of readdirSync(DATASETS_DIR)) {
203
+ seenIds.add(dsId);
204
+ const aboutDir = join(DATASETS_DIR, dsId, "about");
205
+ if (existsSync2(aboutDir) && statSync(aboutDir).isDirectory()) {
206
+ console.log(`Processing dataset: ${dsId}`);
207
+ total += processAboutDir(aboutDir, `dataset-${dsId}-about`, PUBLIC_PAGES);
208
+ }
209
+ }
210
+ }
211
+ for (const [dsId, dsPath] of localPathMap) {
212
+ if (seenIds.has(dsId)) continue;
213
+ const aboutDir = join(dsPath, "about");
214
+ if (existsSync2(aboutDir) && statSync(aboutDir).isDirectory()) {
215
+ console.log(`Processing dataset (localPath): ${dsId}`);
216
+ total += processAboutDir(aboutDir, `dataset-${dsId}-about`, PUBLIC_PAGES);
217
+ }
218
+ }
219
+ if (existsSync2(GROUPS_CONTENT_DIR)) {
220
+ for (const groupId of readdirSync(GROUPS_CONTENT_DIR)) {
221
+ const aboutDir = join(GROUPS_CONTENT_DIR, groupId, "about");
222
+ if (existsSync2(aboutDir) && statSync(aboutDir).isDirectory()) {
223
+ console.log(`Processing group: ${groupId}`);
224
+ total += processAboutDir(aboutDir, `group-${groupId}-about`, PUBLIC_PAGES);
225
+ }
226
+ }
227
+ }
228
+ console.log(total > 0 ? `
229
+ Compiled ${total} about page(s).` : "\nNo about pages found.");
230
+ }
231
+ var isDirectInvocation = process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath2(import.meta.url));
232
+ if (isDirectInvocation) {
233
+ main();
234
+ }
235
+ export {
236
+ loadSiteConfigForAbout,
237
+ main,
238
+ processAboutDir,
239
+ renderMarkdown,
240
+ roots
241
+ };
@@ -19,7 +19,7 @@ import { join, basename, extname, resolve } from 'node:path';
19
19
  import { cwd } from 'node:process';
20
20
  import { fileURLToPath } from 'node:url';
21
21
 
22
- import { loadSiteConfig } from './load-site-config.mjs';
22
+ import { loadSiteConfig } from './load-site-config';
23
23
 
24
24
  export function loadSiteConfigForAbout() {
25
25
  try {
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+
3
+ // scripts/smoke.ts
4
+ import { chromium } from "playwright";
5
+ import { createServer } from "http";
6
+ import { statSync, createReadStream } from "fs";
7
+ import { extname, join, normalize, resolve as pathResolve } from "path";
8
+ import { fileURLToPath } from "url";
9
+ var __dirname = fileURLToPath(new URL(".", import.meta.url));
10
+ var DIST = pathResolve(__dirname, "..", "dist");
11
+ var MIME = {
12
+ ".html": "text/html; charset=utf-8",
13
+ ".js": "application/javascript; charset=utf-8",
14
+ "": "application/javascript; charset=utf-8",
15
+ ".css": "text/css; charset=utf-8",
16
+ ".json": "application/json; charset=utf-8",
17
+ ".svg": "image/svg+xml",
18
+ ".png": "image/png",
19
+ ".jpg": "image/jpeg",
20
+ ".ico": "image/x-icon",
21
+ ".webp": "image/webp",
22
+ ".woff": "font/woff",
23
+ ".woff2": "font/woff2",
24
+ ".ttf": "font/ttf",
25
+ ".map": "application/json; charset=utf-8",
26
+ ".xml": "application/xml; charset=utf-8",
27
+ ".txt": "text/plain; charset=utf-8",
28
+ ".webmanifest": "application/manifest+json; charset=utf-8"
29
+ };
30
+ function serveDist(root) {
31
+ return new Promise((resolve) => {
32
+ const server = createServer((req, res) => {
33
+ try {
34
+ const urlPath = decodeURIComponent((req.url ?? "/").split("?")[0]);
35
+ let filePath = join(root, urlPath);
36
+ if (!extname(filePath)) {
37
+ if (statSafe(filePath)?.isFile()) {
38
+ return sendFile(res, filePath);
39
+ }
40
+ const candidates = [
41
+ filePath + ".html",
42
+ join(filePath, "index.html"),
43
+ join(root, "200.html"),
44
+ join(root, "index.html")
45
+ ];
46
+ for (const c of candidates) {
47
+ if (statSafe(c)?.isFile()) {
48
+ return sendFile(res, c);
49
+ }
50
+ }
51
+ res.writeHead(404);
52
+ return res.end("Not found");
53
+ }
54
+ if (statSafe(filePath)?.isFile()) {
55
+ return sendFile(res, filePath);
56
+ }
57
+ res.writeHead(404);
58
+ return res.end("Not found");
59
+ } catch (e) {
60
+ res.writeHead(500);
61
+ res.end(String(e ?? "server error"));
62
+ }
63
+ });
64
+ server.listen(0, "127.0.0.1", () => {
65
+ const addr = server.address();
66
+ const port = typeof addr === "object" && addr ? addr.port : 0;
67
+ resolve({ server, origin: `http://127.0.0.1:${port}` });
68
+ });
69
+ });
70
+ }
71
+ function statSafe(p) {
72
+ try {
73
+ return statSync(p);
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+ function sendFile(res, filePath) {
79
+ const safe = normalize(filePath);
80
+ const ct = MIME[extname(safe)] ?? "application/octet-stream";
81
+ res.writeHead(200, { "Content-Type": ct });
82
+ createReadStream(safe).pipe(res);
83
+ }
84
+ var PAGE_PATHS = [
85
+ "/",
86
+ "/learn/",
87
+ "/learn/relationships/",
88
+ "/learn/designations/",
89
+ "/learn/statuses/"
90
+ ];
91
+ var CONSUMER_DATA_FILES = [
92
+ "/datasets.json",
93
+ "/site-config.json",
94
+ "/data/cross-ref-index.json"
95
+ ];
96
+ function isConsumerDataFile(url) {
97
+ try {
98
+ const u = new URL(url);
99
+ return CONSUMER_DATA_FILES.some((f) => u.pathname === f || u.pathname.startsWith("/data/"));
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+ async function probePage(browser, origin, path) {
105
+ const url = new URL(path, origin).toString();
106
+ const page = await browser.newPage();
107
+ const errors = [];
108
+ const failedRequests = [];
109
+ page.on("pageerror", (e) => {
110
+ errors.push({ kind: "pageerror", message: e.message, stack: e.stack ?? "" });
111
+ });
112
+ page.on("console", (msg) => {
113
+ if (msg.type() !== "error") return;
114
+ const text = msg.text();
115
+ if (/Failed to load resource/i.test(text)) return;
116
+ if (/fonts\.g(oogle|static)\.com/.test(text)) return;
117
+ errors.push({ kind: "console.error", message: text });
118
+ });
119
+ page.on("requestfailed", (req) => {
120
+ const u = req.url();
121
+ if (u.startsWith(origin) && !isConsumerDataFile(u)) {
122
+ failedRequests.push({ url: u, failure: req.failure()?.errorText ?? "failed" });
123
+ }
124
+ });
125
+ page.on("response", (resp) => {
126
+ const u = resp.url();
127
+ if (u.startsWith(origin) && resp.status() >= 400 && !isConsumerDataFile(u)) {
128
+ failedRequests.push({ url: u, status: resp.status() });
129
+ }
130
+ });
131
+ try {
132
+ const resp = await page.goto(url, { waitUntil: "networkidle", timeout: 15e3 });
133
+ if (resp && resp.status() >= 400) {
134
+ return { path, url, status: resp.status(), bodyLen: 0, errors, failedRequests, skipped: path !== "/" };
135
+ }
136
+ await page.waitForTimeout(500);
137
+ const bodyLen = await page.evaluate(() => document.body?.innerText?.length ?? 0);
138
+ return { path, url, status: resp?.status() ?? 0, bodyLen, errors, failedRequests, skipped: false };
139
+ } catch (e) {
140
+ errors.push({ kind: "navigation", message: String(e) });
141
+ return { path, url, status: 0, bodyLen: 0, errors, failedRequests, skipped: false };
142
+ } finally {
143
+ await page.close();
144
+ }
145
+ }
146
+ function formatFindings(results) {
147
+ const lines = [];
148
+ for (const r of results) {
149
+ if (r.skipped) {
150
+ lines.push(`
151
+ ${r.path} (HTTP ${r.status} \u2014 skipped, not in this build)`);
152
+ continue;
153
+ }
154
+ if (r.errors.length === 0 && r.failedRequests.length === 0 && r.bodyLen > 0) continue;
155
+ lines.push(`
156
+ ${r.path} (HTTP ${r.status}, body ${r.bodyLen} chars)`);
157
+ for (const e of r.errors) {
158
+ lines.push(` [${e.kind}] ${e.message}`);
159
+ }
160
+ for (const f of r.failedRequests) {
161
+ lines.push(` [request] ${f.status ?? f.failure} ${f.url}`);
162
+ }
163
+ }
164
+ return lines.join("\n");
165
+ }
166
+ async function ensureDist() {
167
+ if (!statSafe(DIST)?.isDirectory()) {
168
+ console.error(`smoke: dist/ not found at ${DIST}`);
169
+ console.error(` run \`npm run build\` first, or use \`npm run build:full\`.`);
170
+ process.exit(2);
171
+ }
172
+ if (!statSafe(join(DIST, "index.html"))?.isFile()) {
173
+ console.error(`smoke: dist/index.html missing \u2014 build did not complete.`);
174
+ process.exit(2);
175
+ }
176
+ }
177
+ async function main() {
178
+ await ensureDist();
179
+ const { server, origin } = await serveDist(DIST);
180
+ let exitCode = 0;
181
+ const browser = await chromium.launch({ headless: true });
182
+ try {
183
+ const results = [];
184
+ for (const path of PAGE_PATHS) {
185
+ const r = await probePage(browser, origin, path);
186
+ results.push(r);
187
+ }
188
+ const anyFail = results.some(
189
+ (r) => !r.skipped && (r.errors.length > 0 || r.failedRequests.length > 0 || r.bodyLen === 0)
190
+ );
191
+ const findings = formatFindings(results);
192
+ if (anyFail) {
193
+ exitCode = 1;
194
+ console.error(`
195
+ FAIL smoke test found problems:${findings}
196
+ `);
197
+ } else {
198
+ const skipped = results.filter((r) => r.skipped).length;
199
+ console.log(`
200
+ OK smoke test passed (${results.length - skipped} pages, ${skipped} skipped, 0 errors).`);
201
+ }
202
+ } finally {
203
+ await browser.close();
204
+ server.close();
205
+ }
206
+ process.exit(exitCode);
207
+ }
208
+ main().catch((e) => {
209
+ console.error("smoke: fatal error", e);
210
+ process.exit(1);
211
+ });
@@ -36,7 +36,7 @@ const DIST = pathResolve(__dirname, '..', 'dist');
36
36
  const MIME = {
37
37
  '.html': 'text/html; charset=utf-8',
38
38
  '.js': 'application/javascript; charset=utf-8',
39
- '.mjs': 'application/javascript; charset=utf-8',
39
+ '': 'application/javascript; charset=utf-8',
40
40
  '.css': 'text/css; charset=utf-8',
41
41
  '.json': 'application/json; charset=utf-8',
42
42
  '.svg': 'image/svg+xml',
@@ -53,7 +53,7 @@ const MIME = {
53
53
  '.webmanifest': 'application/manifest+json; charset=utf-8',
54
54
  };
55
55
 
56
- function serveDist(root) {
56
+ function serveDist(root: string): Promise<{ server: ReturnType<typeof createServer>; origin: string }> {
57
57
  return new Promise((resolve) => {
58
58
  const server = createServer((req, res) => {
59
59
  try {
@@ -91,7 +91,8 @@ function serveDist(root) {
91
91
  });
92
92
  // Use port 0 to let the OS pick a free port; retrieve it after listen.
93
93
  server.listen(0, '127.0.0.1', () => {
94
- const { port } = server.address();
94
+ const addr = server.address();
95
+ const port = typeof addr === 'object' && addr ? addr.port : 0;
95
96
  resolve({ server, origin: `http://127.0.0.1:${port}` });
96
97
  });
97
98
  });
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+
3
+ // scripts/sync-concept-model.ts
4
+ import { mkdirSync, writeFileSync } from "node:fs";
5
+ import { dirname, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { execSync } from "node:child_process";
8
+ var ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
9
+ var OUT_DIR = resolve(ROOT, "data", "concept-model");
10
+ var REPO = "glossarist/concept-model";
11
+ function parseArgs(argv) {
12
+ const out = { ref: null };
13
+ for (let i = 2; i < argv.length; i++) {
14
+ const a = argv[i];
15
+ if (a === "--ref") out.ref = argv[++i];
16
+ else if (!a.startsWith("--")) out.ref = a;
17
+ }
18
+ return out;
19
+ }
20
+ function latestTag() {
21
+ return execSync(`gh api repos/${REPO}/releases/latest --jq .tag_name`, { encoding: "utf8" }).trim();
22
+ }
23
+ function fetchFile(ref2, path) {
24
+ const url = `https://raw.githubusercontent.com/${REPO}/${ref2}/${path}`;
25
+ return execSync(`curl -fsSL ${url}`, { encoding: "utf8" });
26
+ }
27
+ var args = parseArgs(process.argv);
28
+ var ref = args.ref || latestTag();
29
+ if (!ref) {
30
+ console.error("Could not determine concept-model ref to sync.");
31
+ process.exit(1);
32
+ }
33
+ console.log(`Syncing from ${REPO}@${ref}`);
34
+ mkdirSync(resolve(OUT_DIR, "shapes"), { recursive: true });
35
+ function fetchAny(ref2, candidates) {
36
+ for (const p of candidates) {
37
+ try {
38
+ return fetchFile(ref2, p);
39
+ } catch (e) {
40
+ }
41
+ }
42
+ throw new Error(`Could not fetch any of: ${candidates.join(", ")}`);
43
+ }
44
+ var targets = {
45
+ "glossarist.context.jsonld": ["ontologies/glossarist.context.jsonld", "glossarist.context.jsonld"],
46
+ "glossarist.ttl": ["ontologies/glossarist.ttl", "glossarist.ttl"],
47
+ "prefixes.ttl": ["ontologies/prefixes.ttl"],
48
+ "shapes/glossarist.shacl.ttl": ["ontologies/shapes/glossarist.shacl.ttl", "shapes/glossarist.shacl.ttl"]
49
+ };
50
+ var updated = 0;
51
+ for (const [outPath, candidates] of Object.entries(targets)) {
52
+ const content = fetchAny(ref, candidates);
53
+ writeFileSync(resolve(OUT_DIR, outPath), content);
54
+ console.log(` \u2713 ${outPath} (${content.length} bytes)`);
55
+ updated++;
56
+ }
57
+ writeFileSync(
58
+ resolve(OUT_DIR, "SOURCE.json"),
59
+ JSON.stringify({ repo: REPO, ref, syncedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
60
+ );
61
+ console.log(`
62
+ Synced ${updated} file(s) from ${REPO}@${ref}.`);
63
+ console.log("Review the diff, then commit.");