@glossarist/concept-browser 0.7.116 → 0.7.117
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.117",
|
|
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": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"@astrojs/sitemap": "^3.7.3",
|
|
37
37
|
"@astrojs/vue": "^7.0.1",
|
|
38
38
|
"@plurimath/plurimath": "^0.2.2",
|
|
39
|
+
"@rdfjs/dataset": "^2.0.2",
|
|
39
40
|
"@tailwindcss/vite": "^4.3.2",
|
|
40
41
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
41
42
|
"astro": "^7.0.7",
|
|
@@ -44,7 +45,9 @@
|
|
|
44
45
|
"glossarist": "^0.4.52",
|
|
45
46
|
"js-yaml": "^4.1.0",
|
|
46
47
|
"jszip": "^3.10.1",
|
|
48
|
+
"n3": "^1.26.0",
|
|
47
49
|
"pinia": "^2.3.1",
|
|
50
|
+
"rdf-validate-shacl": "^0.4.5",
|
|
48
51
|
"sharp": "^0.34.5",
|
|
49
52
|
"tailwindcss": "^4.3.2",
|
|
50
53
|
"tsx": "^4.23.4",
|
|
@@ -54,19 +57,16 @@
|
|
|
54
57
|
},
|
|
55
58
|
"devDependencies": {
|
|
56
59
|
"@astrojs/check": "^0.9.9",
|
|
57
|
-
"@rdfjs/dataset": "^2.0.2",
|
|
58
60
|
"@stryker-mutator/core": "^9.6.1",
|
|
59
61
|
"@stryker-mutator/vitest-runner": "^9.6.1",
|
|
60
62
|
"@types/d3": "^7.4.3",
|
|
61
63
|
"@types/n3": "^1.26.1",
|
|
62
|
-
"esbuild": "^0.28.1",
|
|
63
|
-
"glob": "^10.5.0",
|
|
64
64
|
"@vue/test-utils": "^2.4.8",
|
|
65
|
+
"esbuild": "^0.28.1",
|
|
65
66
|
"fast-check": "^4.8.0",
|
|
67
|
+
"glob": "^10.5.0",
|
|
66
68
|
"happy-dom": "^20.9.0",
|
|
67
|
-
"n3": "^1.26.0",
|
|
68
69
|
"playwright": "^1.62.1",
|
|
69
|
-
"rdf-validate-shacl": "^0.4.5",
|
|
70
70
|
"typescript": "~5.7.3",
|
|
71
71
|
"vitest": "^4.1.5",
|
|
72
72
|
"vue-tsc": "^2.2.8"
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { build } from 'esbuild';
|
|
2
2
|
import { globSync } from 'glob';
|
|
3
|
-
import {
|
|
3
|
+
import { relative } from 'path';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Exclude scripts that are dev/CI-only and depend on devDependencies.
|
|
6
|
+
// These are not consumer-facing CLI commands and would pull in heavy deps
|
|
7
|
+
// (e.g. playwright for smoke) that consumers shouldn't need.
|
|
8
|
+
const DEV_ONLY = new Set([
|
|
9
|
+
'scripts/smoke.ts', // needs playwright (devDep, heavy)
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
const files = globSync('scripts/*.ts').filter(f =>
|
|
13
|
+
!f.includes('__tests__') && !DEV_ONLY.has(f),
|
|
14
|
+
);
|
|
6
15
|
|
|
7
16
|
await Promise.all(files.map(async (file) => {
|
|
8
17
|
const outfile = file.replace(/\.ts$/, '.js');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { resolve, join, dirname } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
@@ -21,7 +21,6 @@ const PIPELINE_SCRIPTS = [
|
|
|
21
21
|
'generate-data',
|
|
22
22
|
'build-edges',
|
|
23
23
|
'process-about-pages',
|
|
24
|
-
'smoke',
|
|
25
24
|
'generate-ontology-data',
|
|
26
25
|
'generate-ontology-schema',
|
|
27
26
|
'doctor',
|
|
@@ -81,4 +80,21 @@ describe('published-package contract — compiled .js scripts load under plain n
|
|
|
81
80
|
expect(result.status).toBe(0);
|
|
82
81
|
expect(result.stdout).toContain('valid=true');
|
|
83
82
|
});
|
|
83
|
+
|
|
84
|
+
it('compiled scripts do not import devDependencies (runtime packaging regression)', () => {
|
|
85
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'));
|
|
86
|
+
const deps = new Set(Object.keys(pkg.dependencies || {}));
|
|
87
|
+
const devDeps = new Set(Object.keys(pkg.devDependencies || {}));
|
|
88
|
+
for (const name of LIBRARY_SCRIPTS) {
|
|
89
|
+
const jsPath = join(SCRIPTS_DIR, `${name}.js`);
|
|
90
|
+
if (!existsSync(jsPath)) continue;
|
|
91
|
+
const src = readFileSync(jsPath, 'utf8');
|
|
92
|
+
for (const devDep of devDeps) {
|
|
93
|
+
if (deps.has(devDep)) continue;
|
|
94
|
+
expect(src).not.toContain(`from "${devDep}"`);
|
|
95
|
+
expect(src).not.toContain(`from '${devDep}'`);
|
|
96
|
+
expect(src).not.toMatch(new RegExp(`import\\s*\\(\\s*["']${devDep}["']`));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
84
100
|
});
|
package/scripts/smoke.js
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
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
|
-
});
|