@glossarist/concept-browser 0.7.101 → 0.7.103
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/cli/index.mjs +6 -28
- package/cli/index.ts +289 -0
- package/package.json +2 -2
- package/src/__tests__/__fixtures__/concepts.ts +3 -3
- package/src/__tests__/composables/use-concept-edges-v2.test.ts +1 -0
- package/src/__tests__/designation-relationship.test.ts +2 -2
- package/src/__tests__/perf/serialization-perf.test.ts +1 -0
- package/src/__tests__/use-rdf-document.test.ts +1 -0
- package/src/adapters/bibliography-adapter.ts +1 -1
- package/src/adapters/model-bridge.ts +15 -15
- package/src/adapters/non-verbal/figure-bridge.ts +2 -2
- package/src/adapters/non-verbal/formula-bridge.ts +2 -2
- package/src/adapters/non-verbal/table-bridge.ts +2 -2
- package/src/components/CitationDisplay.vue +1 -1
- package/src/components/ConceptDetail.vue +2 -2
- package/src/components/ConceptDiffModal.vue +2 -2
- package/src/components/ConceptDiffView.vue +1 -1
- package/src/components/DesignationList.vue +4 -4
- package/src/components/NavIcon.vue +1 -0
- package/src/components/figure/FigureDisplay.vue +5 -5
- package/src/components/formula/FormulaDisplay.vue +4 -4
- package/src/components/table/TableDisplay.vue +3 -3
- package/src/components/use-rdf-document.ts +16 -7
- package/src/composables/use-concept-content.ts +3 -3
- package/src/composables/use-concept-diff.ts +2 -2
- package/src/composables/use-concept-edges.ts +3 -3
- package/src/config/page-types.ts +1 -1
- package/src/i18n/locales/eng.yml +12 -0
- package/src/i18n/locales/fra.yml +12 -0
- package/src/layouts/Default.astro +32 -2
- package/src/utils/content-renderer.ts +1 -1
- package/src/utils/partitive-multiplicity.ts +3 -2
- package/src/views/ConceptView.vue +3 -3
- package/src/views/learn/LearnRelationshipsView.vue +63 -0
- package/tailwind.config.js +3 -3
- package/tsconfig.json +1 -1
- package/src/adapters/model-bridge/bridges.ts +0 -159
- package/src/adapters/model-bridge/concept.ts +0 -97
- package/src/adapters/model-bridge/entity-refs.ts +0 -49
- package/src/adapters/model-bridge/hyperedge.ts +0 -103
- package/src/adapters/model-bridge/index.ts +0 -46
- package/src/adapters/model-bridge/mappers.ts +0 -277
- package/src/adapters/model-bridge/partitive.ts +0 -79
- package/src/adapters/non-verbal/glossarist-augment.d.ts +0 -339
- /package/src/adapters/{model-bridge/jsonld-types.ts → jsonld-types.ts} +0 -0
package/cli/index.mjs
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Glossarist Concept Browser CLI.
|
|
4
|
+
* Glossarist Concept Browser CLI — runtime entrypoint.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* generate Convert harmonized YAML concepts to JSON-LD static files
|
|
9
|
-
* edges Build cross-reference edges from generated concept data
|
|
10
|
-
* build Full pipeline: fetch + generate + edges + vite build
|
|
11
|
-
* site Same as build (alias)
|
|
12
|
-
* normalize NFC-normalize YAML files in .datasets/ (use --check for CI gate)
|
|
13
|
-
* doctor Diagnose the local environment (deps, datasets, shapes, context)
|
|
6
|
+
* This is the published bin entry. It stays as plain JS (no tsx dependency)
|
|
7
|
+
* so consumers who install with --omit=dev can use it.
|
|
14
8
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* Environment:
|
|
19
|
-
* SITE_CONFIG Path to site config file (overrides --site)
|
|
20
|
-
* SITE_ID Site config ID (overrides --site)
|
|
21
|
-
* GITHUB_TOKEN GitHub token for private repos
|
|
9
|
+
* The TypeScript version lives at cli/index.ts for type-checking during
|
|
10
|
+
* development. The two files should be kept in sync manually until
|
|
11
|
+
* TODO.typescript/03 adds a compile step.
|
|
22
12
|
*/
|
|
23
13
|
|
|
24
14
|
import { loadSiteConfig } from '../scripts/load-site-config.mjs';
|
|
@@ -79,7 +69,6 @@ Environment:
|
|
|
79
69
|
process.exit(cmd ? 0 : 1);
|
|
80
70
|
}
|
|
81
71
|
|
|
82
|
-
// Pre-load site config so scripts can use it
|
|
83
72
|
if (!process.env.SITE_CONFIG && !process.env.SITE_ID && named.site) {
|
|
84
73
|
process.env.SITE_ID = named.site;
|
|
85
74
|
}
|
|
@@ -94,7 +83,6 @@ Environment:
|
|
|
94
83
|
await commands[step]();
|
|
95
84
|
}
|
|
96
85
|
|
|
97
|
-
// Generate favicons from site logo
|
|
98
86
|
const fs = await import('fs');
|
|
99
87
|
const publicDir = resolve(process.cwd(), 'public');
|
|
100
88
|
fs.mkdirSync(publicDir, { recursive: true });
|
|
@@ -124,7 +112,6 @@ Environment:
|
|
|
124
112
|
},
|
|
125
113
|
});
|
|
126
114
|
|
|
127
|
-
// Write all generated images and files to public/
|
|
128
115
|
for (const img of response.images) {
|
|
129
116
|
fs.writeFileSync(resolve(publicDir, img.name), img.contents);
|
|
130
117
|
}
|
|
@@ -132,22 +119,18 @@ Environment:
|
|
|
132
119
|
fs.writeFileSync(resolve(publicDir, file.name), file.contents);
|
|
133
120
|
}
|
|
134
121
|
|
|
135
|
-
// Keep the original SVG for dark mode support
|
|
136
122
|
if (faviconSrc.endsWith('.svg')) {
|
|
137
123
|
fs.copyFileSync(faviconSrc, resolve(publicDir, 'favicon.svg'));
|
|
138
124
|
}
|
|
139
125
|
|
|
140
126
|
faviconHtml = response.html.join('\n ');
|
|
141
|
-
// Add SVG favicon
|
|
142
127
|
if (faviconSrc.endsWith('.svg')) {
|
|
143
128
|
faviconHtml += '\n <link rel="icon" type="image/svg+xml" href="/favicon.svg">';
|
|
144
129
|
}
|
|
145
|
-
// Write tags for Vite plugin to pick up
|
|
146
130
|
fs.writeFileSync(resolve(publicDir, 'favicon-links.html'), faviconHtml);
|
|
147
131
|
console.log(` Generated ${response.images.length} favicon files`);
|
|
148
132
|
}
|
|
149
133
|
|
|
150
|
-
// Pass favicon tags to Vite via env
|
|
151
134
|
if (faviconHtml) {
|
|
152
135
|
const basePath = process.env.BASE_PATH?.replace(/\/+$/, '') || '';
|
|
153
136
|
if (basePath) {
|
|
@@ -156,7 +139,6 @@ Environment:
|
|
|
156
139
|
process.env.FAVICON_HTML = faviconHtml;
|
|
157
140
|
}
|
|
158
141
|
|
|
159
|
-
// Pass branding info for HTML transformation
|
|
160
142
|
process.env.SITE_TITLE = config?.title || 'Glossarist';
|
|
161
143
|
if (branding.fonts?.header || branding.fonts?.body) {
|
|
162
144
|
const fontFamilies = [];
|
|
@@ -173,14 +155,12 @@ Environment:
|
|
|
173
155
|
}
|
|
174
156
|
}
|
|
175
157
|
|
|
176
|
-
// Bridge data to Astro content collections
|
|
177
158
|
console.log(`\n=== BRIDGE DATA ===\n`);
|
|
178
159
|
const bridge = resolve(pkgRoot, 'scripts', 'bridge-to-astro.mjs');
|
|
179
160
|
if (existsSync(bridge)) {
|
|
180
161
|
await import(`file://${bridge}`);
|
|
181
162
|
}
|
|
182
163
|
|
|
183
|
-
// Run Astro build (TODO.astro/22)
|
|
184
164
|
console.log(`\n=== BUILD ASTRO ===\n`);
|
|
185
165
|
const astroConfig = resolve(pkgRoot, 'astro.config.mjs');
|
|
186
166
|
if (existsSync(astroConfig)) {
|
|
@@ -195,14 +175,12 @@ Environment:
|
|
|
195
175
|
await viteBuild({ configFile: viteConfig, root: pkgRoot, mode: 'production' });
|
|
196
176
|
}
|
|
197
177
|
} else {
|
|
198
|
-
// Fall back to Vite SPA if Astro config doesn't exist
|
|
199
178
|
console.log(`\n=== BUILD SPA ===\n`);
|
|
200
179
|
const viteConfig = resolve(pkgRoot, 'vite.config.ts');
|
|
201
180
|
const { build: viteBuild } = await import('vite');
|
|
202
181
|
await viteBuild({ configFile: viteConfig, root: pkgRoot, mode: 'production' });
|
|
203
182
|
}
|
|
204
183
|
|
|
205
|
-
// Run postbuild (404 page) via dynamic import
|
|
206
184
|
const postbuild = resolve(pkgRoot, 'scripts', 'generate-404.js');
|
|
207
185
|
if (existsSync(postbuild)) {
|
|
208
186
|
await import(`file://${postbuild}`);
|
package/cli/index.ts
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Glossarist Concept Browser CLI.
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* fetch Fetch/update datasets (from GCR packages or source repos)
|
|
8
|
+
* generate Convert harmonized YAML concepts to JSON-LD static files
|
|
9
|
+
* edges Build cross-reference edges from generated concept data
|
|
10
|
+
* build Full pipeline: fetch + generate + edges + vite build
|
|
11
|
+
* site Same as build (alias)
|
|
12
|
+
* normalize NFC-normalize YAML files in .datasets/ (use --check for CI gate)
|
|
13
|
+
* doctor Diagnose the local environment (deps, datasets, shapes, context)
|
|
14
|
+
*
|
|
15
|
+
* Options:
|
|
16
|
+
* --site <id> Site config to use (looks for site-config.yml in CWD)
|
|
17
|
+
*
|
|
18
|
+
* Environment:
|
|
19
|
+
* SITE_CONFIG Path to site config file (overrides --site)
|
|
20
|
+
* SITE_ID Site config ID (overrides --site)
|
|
21
|
+
* GITHUB_TOKEN GitHub token for private repos
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// Scripts are still .mjs (TODO.typescript/02). These imports will be fully
|
|
25
|
+
// typed once the scripts convert to .ts.
|
|
26
|
+
// @ts-expect-error — .mjs module without declaration
|
|
27
|
+
import { loadSiteConfig } from '../scripts/load-site-config.mjs';
|
|
28
|
+
import { existsSync } from 'fs';
|
|
29
|
+
import { resolve, dirname } from 'path';
|
|
30
|
+
import { fileURLToPath } from 'url';
|
|
31
|
+
|
|
32
|
+
const __dirname: string = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const pkgRoot: string = resolve(__dirname, '..');
|
|
34
|
+
|
|
35
|
+
interface ParsedArgs {
|
|
36
|
+
positional: string[];
|
|
37
|
+
named: Record<string, string>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type CommandRunner = () => Promise<void>;
|
|
41
|
+
|
|
42
|
+
const commands: Record<string, CommandRunner> = {
|
|
43
|
+
fetch: async () => (await import('../scripts/fetch-datasets.mjs' as string)).main(),
|
|
44
|
+
generate: async () => { await import('../scripts/generate-data.mjs' as string); },
|
|
45
|
+
edges: async () => (await import('../scripts/build-edges.js' as string)).main(),
|
|
46
|
+
about: async () => (await import('../scripts/process-about-pages.mjs' as string)).main(),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
50
|
+
const positional: string[] = [];
|
|
51
|
+
const named: Record<string, string> = {};
|
|
52
|
+
for (let i = 0; i < argv.length; i++) {
|
|
53
|
+
if (argv[i].startsWith('--') && i + 1 < argv.length) {
|
|
54
|
+
named[argv[i].slice(2)] = argv[i + 1];
|
|
55
|
+
i++;
|
|
56
|
+
} else if (!argv[i].startsWith('-')) {
|
|
57
|
+
positional.push(argv[i]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { positional, named };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface BrandingFonts {
|
|
64
|
+
source?: string;
|
|
65
|
+
family?: string;
|
|
66
|
+
weights?: number[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface BrandingConfig {
|
|
70
|
+
favicon?: string;
|
|
71
|
+
logo?: { localPath?: string };
|
|
72
|
+
primaryColor?: string;
|
|
73
|
+
fonts?: { header?: BrandingFonts; body?: BrandingFonts };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface SiteConfig {
|
|
77
|
+
basePath?: string;
|
|
78
|
+
title?: string;
|
|
79
|
+
branding?: BrandingConfig;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface FaviconResponse {
|
|
83
|
+
images: Array<{ name: string; contents: string | Buffer }>;
|
|
84
|
+
files: Array<{ name: string; contents: string | Buffer }>;
|
|
85
|
+
html: string[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function runBuildPipeline(config: SiteConfig | null): Promise<void> {
|
|
89
|
+
if (!process.env.BASE_PATH && config?.basePath) {
|
|
90
|
+
process.env.BASE_PATH = config.basePath;
|
|
91
|
+
}
|
|
92
|
+
for (const step of ['fetch', 'generate', 'edges', 'about'] as const) {
|
|
93
|
+
console.log(`\n=== ${step.toUpperCase()} ===\n`);
|
|
94
|
+
await commands[step]();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const fs = await import('fs');
|
|
98
|
+
const publicDir: string = resolve(process.cwd(), 'public');
|
|
99
|
+
fs.mkdirSync(publicDir, { recursive: true });
|
|
100
|
+
|
|
101
|
+
const branding: BrandingConfig = config?.branding ?? {};
|
|
102
|
+
const faviconSrc: string =
|
|
103
|
+
(branding.favicon && resolve(process.cwd(), branding.favicon)) ||
|
|
104
|
+
(branding.logo?.localPath && resolve(process.cwd(), branding.logo.localPath)) ||
|
|
105
|
+
resolve(pkgRoot, 'public', 'favicon.svg');
|
|
106
|
+
|
|
107
|
+
let faviconHtml = '';
|
|
108
|
+
if (fs.existsSync(faviconSrc)) {
|
|
109
|
+
console.log(`\n=== FAVICONS ===\n`);
|
|
110
|
+
const favicons = (await import('favicons')).default;
|
|
111
|
+
const source: Buffer = fs.readFileSync(faviconSrc);
|
|
112
|
+
const response: FaviconResponse = await favicons(source, {
|
|
113
|
+
appName: config?.title || 'Glossarist',
|
|
114
|
+
background: branding.primaryColor || '#2563eb',
|
|
115
|
+
theme_color: branding.primaryColor || '#2563eb',
|
|
116
|
+
icons: {
|
|
117
|
+
android: false,
|
|
118
|
+
appleIcon: true,
|
|
119
|
+
appleStartup: false,
|
|
120
|
+
favicons: true,
|
|
121
|
+
windows: false,
|
|
122
|
+
yandex: false,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
for (const img of response.images) {
|
|
127
|
+
fs.writeFileSync(resolve(publicDir, img.name), img.contents);
|
|
128
|
+
}
|
|
129
|
+
for (const file of response.files) {
|
|
130
|
+
fs.writeFileSync(resolve(publicDir, file.name), file.contents);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (faviconSrc.endsWith('.svg')) {
|
|
134
|
+
fs.copyFileSync(faviconSrc, resolve(publicDir, 'favicon.svg'));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
faviconHtml = response.html.join('\n ');
|
|
138
|
+
if (faviconSrc.endsWith('.svg')) {
|
|
139
|
+
faviconHtml += '\n <link rel="icon" type="image/svg+xml" href="/favicon.svg">';
|
|
140
|
+
}
|
|
141
|
+
fs.writeFileSync(resolve(publicDir, 'favicon-links.html'), faviconHtml);
|
|
142
|
+
console.log(` Generated ${response.images.length} favicon files`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (faviconHtml) {
|
|
146
|
+
const basePath: string = process.env.BASE_PATH?.replace(/\/+$/, '') || '';
|
|
147
|
+
if (basePath) {
|
|
148
|
+
faviconHtml = faviconHtml.replace(/(href|content)="\/([^"]+)"/g, `$1="${basePath}/$2"`);
|
|
149
|
+
}
|
|
150
|
+
process.env.FAVICON_HTML = faviconHtml;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
process.env.SITE_TITLE = config?.title || 'Glossarist';
|
|
154
|
+
if (branding.fonts?.header || branding.fonts?.body) {
|
|
155
|
+
const fontFamilies: string[] = [];
|
|
156
|
+
if (branding.fonts.header?.source === 'google') {
|
|
157
|
+
const w: string = (branding.fonts.header.weights || [400, 700]).join(';');
|
|
158
|
+
fontFamilies.push(`family=${branding.fonts.header.family!.replace(/ /g, '+')}:wght@${w}`);
|
|
159
|
+
}
|
|
160
|
+
if (branding.fonts.body?.source === 'google') {
|
|
161
|
+
const w: string = (branding.fonts.body.weights || [400, 700]).join(';');
|
|
162
|
+
fontFamilies.push(`family=${branding.fonts.body.family!.replace(/ /g, '+')}:wght@${w}`);
|
|
163
|
+
}
|
|
164
|
+
if (fontFamilies.length) {
|
|
165
|
+
process.env.SITE_FONTS_URL = `https://fonts.googleapis.com/css2?${fontFamilies.join('&')}&display=swap`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(`\n=== BRIDGE DATA ===\n`);
|
|
170
|
+
const bridge: string = resolve(pkgRoot, 'scripts', 'bridge-to-astro.mjs');
|
|
171
|
+
if (existsSync(bridge)) {
|
|
172
|
+
await import(`file://${bridge}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log(`\n=== BUILD ASTRO ===\n`);
|
|
176
|
+
const astroConfig: string = resolve(pkgRoot, 'astro.config.mjs');
|
|
177
|
+
if (existsSync(astroConfig)) {
|
|
178
|
+
try {
|
|
179
|
+
const { build: astroBuild } = await import('astro');
|
|
180
|
+
await astroBuild({ root: pkgRoot, logLevel: 'info' });
|
|
181
|
+
} catch (e: unknown) {
|
|
182
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
183
|
+
console.warn(` Astro build failed (${msg}), falling back to Vite SPA`);
|
|
184
|
+
console.log(`\n=== BUILD SPA ===\n`);
|
|
185
|
+
const viteConfig: string = resolve(pkgRoot, 'vite.config.ts');
|
|
186
|
+
const { build: viteBuild } = await import('vite');
|
|
187
|
+
await viteBuild({ configFile: viteConfig, root: pkgRoot, mode: 'production' });
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
console.log(`\n=== BUILD SPA ===\n`);
|
|
191
|
+
const viteConfig: string = resolve(pkgRoot, 'vite.config.ts');
|
|
192
|
+
const { build: viteBuild } = await import('vite');
|
|
193
|
+
await viteBuild({ configFile: viteConfig, root: pkgRoot, mode: 'production' });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const postbuild: string = resolve(pkgRoot, 'scripts', 'generate-404.js');
|
|
197
|
+
if (existsSync(postbuild)) {
|
|
198
|
+
await import(`file://${postbuild}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function runNormalize(): Promise<void> {
|
|
203
|
+
const mod = await import('../scripts/normalize-yaml.mjs' as string);
|
|
204
|
+
const normalizeYaml = mod.normalizeYaml;
|
|
205
|
+
const check: boolean = process.argv.includes('--check');
|
|
206
|
+
const paths: string[] = process.argv.slice(2).filter(a => !a.startsWith('-') && a !== 'normalize');
|
|
207
|
+
const { checked, nonNfc, fixed }: { checked: number; nonNfc: number; fixed: string[] } = normalizeYaml({ check, paths });
|
|
208
|
+
if (check) {
|
|
209
|
+
if (nonNfc === 0) {
|
|
210
|
+
console.log(`NFC OK: ${checked} file(s) checked, all normalized`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
console.error(`NFC check failed: ${nonNfc} of ${checked} file(s) are not NFC-normalized\n`);
|
|
214
|
+
for (const f of fixed) console.error(` ${f}`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
if (nonNfc === 0) {
|
|
218
|
+
console.log(`NFC OK: ${checked} file(s) checked, all already normalized`);
|
|
219
|
+
} else {
|
|
220
|
+
console.log(`Normalized ${nonNfc} of ${checked} file(s)`);
|
|
221
|
+
for (const f of fixed) console.log(` ${f}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function main(): Promise<void> {
|
|
226
|
+
const args: readonly string[] = process.argv.slice(2);
|
|
227
|
+
const { positional, named }: ParsedArgs = parseArgs(args);
|
|
228
|
+
const cmd: string | undefined = positional[0];
|
|
229
|
+
|
|
230
|
+
if (!cmd || cmd === 'help' || cmd === '--help') {
|
|
231
|
+
console.log(`concept-browser <command> [options]
|
|
232
|
+
|
|
233
|
+
Commands:
|
|
234
|
+
fetch Fetch/update datasets from GCR packages or source repos
|
|
235
|
+
generate Convert YAML concepts to JSON-LD static data
|
|
236
|
+
edges Build cross-reference edges from generated concepts
|
|
237
|
+
about Compile per-dataset and per-group about pages
|
|
238
|
+
build Full pipeline (fetch + generate + edges + about + vite build)
|
|
239
|
+
site Same as build
|
|
240
|
+
normalize NFC-normalize YAML files in .datasets/
|
|
241
|
+
doctor Diagnose the local environment (deps, datasets, shapes, context)
|
|
242
|
+
|
|
243
|
+
Options:
|
|
244
|
+
--site <id> Site config ID (looks for site-config.yml in CWD)
|
|
245
|
+
--check (normalize only) Non-mutating; exit 1 if any file is not NFC
|
|
246
|
+
|
|
247
|
+
Environment:
|
|
248
|
+
SITE_CONFIG Site config file path (highest priority)
|
|
249
|
+
SITE_ID Site config ID (same as --site)
|
|
250
|
+
GITHUB_TOKEN GitHub token for private repos`);
|
|
251
|
+
process.exit(cmd ? 0 : 1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!process.env.SITE_CONFIG && !process.env.SITE_ID && named.site) {
|
|
255
|
+
process.env.SITE_ID = named.site;
|
|
256
|
+
}
|
|
257
|
+
const { config }: { config: SiteConfig | null } = loadSiteConfig(named.site ? [named.site] : []);
|
|
258
|
+
|
|
259
|
+
if (cmd === 'build' || cmd === 'site') {
|
|
260
|
+
await runBuildPipeline(config);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const runner: CommandRunner | undefined = commands[cmd];
|
|
265
|
+
if (runner) {
|
|
266
|
+
await runner();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (cmd === 'normalize') {
|
|
271
|
+
await runNormalize();
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (cmd === 'doctor') {
|
|
276
|
+
const mod = await import('../scripts/doctor.mjs' as string);
|
|
277
|
+
await mod.main();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.error(`Unknown command: ${cmd}`);
|
|
282
|
+
console.error('Run `concept-browser help` for usage.');
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
main().catch((e: unknown) => {
|
|
287
|
+
console.error(e);
|
|
288
|
+
process.exit(1);
|
|
289
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.103",
|
|
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": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"astro": "^7.0.7",
|
|
38
38
|
"d3": "^7.9.0",
|
|
39
39
|
"favicons": "^7.2.0",
|
|
40
|
-
"glossarist": "^0.4.
|
|
40
|
+
"glossarist": "^0.4.51",
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
42
42
|
"jszip": "^3.10.1",
|
|
43
43
|
"pinia": "^2.3.1",
|
|
@@ -87,8 +87,8 @@ function withSources(): Concept {
|
|
|
87
87
|
type: 'authoritative',
|
|
88
88
|
modification: 'revised 2024',
|
|
89
89
|
origin: {
|
|
90
|
-
ref: { source: 'ISO 704', id: '3.1', version: '2020' },
|
|
91
|
-
locality: { type: 'clause', referenceFrom: '3.1', referenceTo: '3.5' },
|
|
90
|
+
ref: { source: 'ISO 704', id: '3.1', version: '2020' } as any,
|
|
91
|
+
locality: { type: 'clause', referenceFrom: '3.1', referenceTo: '3.5' } as any,
|
|
92
92
|
link: 'https://example.org/iso-704',
|
|
93
93
|
original: 'Original wording here.',
|
|
94
94
|
},
|
|
@@ -98,7 +98,7 @@ function withSources(): Concept {
|
|
|
98
98
|
type: 'lineage',
|
|
99
99
|
origin: {
|
|
100
100
|
ref: { source: 'IEC 60050', id: '102-01', version: '2008' },
|
|
101
|
-
locality: { type: 'clause', referenceFrom: '102-01-01' },
|
|
101
|
+
locality: { type: 'clause', referenceFrom: '102-01-01' } as any,
|
|
102
102
|
},
|
|
103
103
|
},
|
|
104
104
|
{
|
|
@@ -197,8 +197,8 @@ describe('designation target matching robustness', () => {
|
|
|
197
197
|
// Terms are in model order, not raw order
|
|
198
198
|
const pdf = lc.terms.find(t => t.designation === 'PDF')!;
|
|
199
199
|
const iso = lc.terms.find(t => t.designation === 'ISO')!;
|
|
200
|
-
expect(getDesignationTarget(pdf.related[0])).toBe('Portable Document Format');
|
|
201
|
-
expect(getDesignationTarget(iso.related[0])).toBe('International Organization for Standardization');
|
|
200
|
+
expect(getDesignationTarget(pdf.related[0] as any)).toBe('Portable Document Format');
|
|
201
|
+
expect(getDesignationTarget(iso.related[0] as any)).toBe('International Organization for Standardization');
|
|
202
202
|
});
|
|
203
203
|
});
|
|
204
204
|
|
|
@@ -61,7 +61,7 @@ import type {
|
|
|
61
61
|
JsonLdPartitiveMember,
|
|
62
62
|
JsonLdPartitiveRelation,
|
|
63
63
|
JsonLdConcept,
|
|
64
|
-
} from './
|
|
64
|
+
} from './jsonld-types';
|
|
65
65
|
|
|
66
66
|
// ── Bridges for fields not yet in glossarist-js ────────────────────────────
|
|
67
67
|
// Remove each bridge when glossarist-js publishes native support.
|
|
@@ -173,7 +173,7 @@ function attachBridges(concept: Concept, localizations: Record<string, unknown>)
|
|
|
173
173
|
* instances, since RelatedConcept.fromJSON only reads type/content/ref.
|
|
174
174
|
*/
|
|
175
175
|
function attachRelatedBridges(
|
|
176
|
-
modelRelated:
|
|
176
|
+
modelRelated: ReadonlyArray<{ type?: string | null; content?: Record<string, string> | string | null; ref?: any; related?: ReadonlyArray<any> }>,
|
|
177
177
|
rawRelated: unknown[],
|
|
178
178
|
): void {
|
|
179
179
|
for (const rawRel of rawRelated) {
|
|
@@ -241,7 +241,7 @@ function mapDesignationFromJsonLd(d: JsonLdDesignation): Record<string, unknown>
|
|
|
241
241
|
|
|
242
242
|
const pronunciation = d[GL.PRONUNCIATION];
|
|
243
243
|
if (pronunciation?.length) {
|
|
244
|
-
result.pronunciation = pronunciation.map(p => ({
|
|
244
|
+
result.pronunciation = pronunciation.map((p: any) => ({
|
|
245
245
|
content: p[GL.CONTENT] ?? null,
|
|
246
246
|
language: p[GL.LANGUAGE] ?? null,
|
|
247
247
|
script: p[GL.SCRIPT] ?? null,
|
|
@@ -257,7 +257,7 @@ function mapDesignationFromJsonLd(d: JsonLdDesignation): Record<string, unknown>
|
|
|
257
257
|
|
|
258
258
|
const related = d[GL.RELATED];
|
|
259
259
|
if (related?.length) {
|
|
260
|
-
result.related = related.map(r => {
|
|
260
|
+
result.related = related.map((r: any) => {
|
|
261
261
|
const relType = r[GL.RELATIONSHIP_TYPE] ?? 'references';
|
|
262
262
|
if (DESIGNATION_REL_TYPES.has(relType) && r[GL.TARGET]) {
|
|
263
263
|
return { type: relType, target: r[GL.TARGET] };
|
|
@@ -272,7 +272,7 @@ function mapDesignationFromJsonLd(d: JsonLdDesignation): Record<string, unknown>
|
|
|
272
272
|
}
|
|
273
273
|
const grammarInfo = d[GL.GRAMMAR_INFO];
|
|
274
274
|
if (grammarInfo?.length) {
|
|
275
|
-
result.grammar_info = grammarInfo.map(gi => ({
|
|
275
|
+
result.grammar_info = grammarInfo.map((gi: any) => ({
|
|
276
276
|
gender: gi[GL.GENDER] ?? null,
|
|
277
277
|
number: gi[GL.NUMBER] ?? null,
|
|
278
278
|
part_of_speech: gi[GL.PART_OF_SPEECH] ?? null,
|
|
@@ -354,7 +354,7 @@ function mapSourceFromJsonLd(s: JsonLdSource): Record<string, unknown> {
|
|
|
354
354
|
|
|
355
355
|
const sf = s[GL.SOURCED_FROM] ?? s[GL.SOURCED_FROM_ALT];
|
|
356
356
|
if (sf?.length) {
|
|
357
|
-
result.sourced_from = sf.map(item => mapOriginFromJsonLd(item));
|
|
357
|
+
result.sourced_from = sf.map((item: any) => mapOriginFromJsonLd(item));
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
return result;
|
|
@@ -445,7 +445,7 @@ function mapLocalizedFromJsonLd(lc: JsonLdLocalizedConcept): Record<string, unkn
|
|
|
445
445
|
|
|
446
446
|
const dates = lc[GL.DATES];
|
|
447
447
|
if (dates?.length) {
|
|
448
|
-
data.dates = dates.map(d => ({
|
|
448
|
+
data.dates = dates.map((d: any) => ({
|
|
449
449
|
date: d[GL.DATE] ?? null,
|
|
450
450
|
type: d[GL.DATE_TYPE] ?? null,
|
|
451
451
|
}));
|
|
@@ -471,7 +471,7 @@ function mapPartitiveRelationFromJsonLd(r: JsonLdPartitiveRelation): Record<stri
|
|
|
471
471
|
if (!comprehensive) return null;
|
|
472
472
|
|
|
473
473
|
const partitives = (r[GL.HAS_PARTITIVE] ?? [])
|
|
474
|
-
.map((m): Record<string, unknown> | null => {
|
|
474
|
+
.map((m: any): Record<string, unknown> | null => {
|
|
475
475
|
const ref = m[GL.REF] ? mapRefFromJsonLd(m[GL.REF]) : null;
|
|
476
476
|
if (!ref) return null;
|
|
477
477
|
const out: Record<string, unknown> = { ref };
|
|
@@ -494,7 +494,7 @@ function mapPartitiveRelationFromJsonLd(r: JsonLdPartitiveRelation): Record<stri
|
|
|
494
494
|
if (m[GL.IS_DELIMITING] === true) out.is_delimiting = true;
|
|
495
495
|
return out;
|
|
496
496
|
})
|
|
497
|
-
.filter((m): m is Record<string, unknown> => m !== null);
|
|
497
|
+
.filter((m: any): m is Record<string, unknown> => m !== null);
|
|
498
498
|
|
|
499
499
|
// ISO 704 requires ≥2 partitives. Skip malformed relations rather
|
|
500
500
|
// than letting the constructor throw and break the whole concept.
|
|
@@ -529,7 +529,7 @@ function mapGenericRelationFromJsonLd(r: JsonLdPartitiveRelation): Record<string
|
|
|
529
529
|
if (!comprehensive) return null;
|
|
530
530
|
|
|
531
531
|
const members = (r[GL.HAS_GENERIC] ?? [])
|
|
532
|
-
.map((m): Record<string, unknown> | null => {
|
|
532
|
+
.map((m: any): Record<string, unknown> | null => {
|
|
533
533
|
const ref = m[GL.REF] ? mapRefFromJsonLd(m[GL.REF]) : null;
|
|
534
534
|
if (!ref) return null;
|
|
535
535
|
const out: Record<string, unknown> = { ref };
|
|
@@ -552,7 +552,7 @@ function mapGenericRelationFromJsonLd(r: JsonLdPartitiveRelation): Record<string
|
|
|
552
552
|
}
|
|
553
553
|
return out;
|
|
554
554
|
})
|
|
555
|
-
.filter((m): m is Record<string, unknown> => m !== null);
|
|
555
|
+
.filter((m: any): m is Record<string, unknown> => m !== null);
|
|
556
556
|
|
|
557
557
|
if (members.length < 2) return null;
|
|
558
558
|
|
|
@@ -589,7 +589,7 @@ function splitMultiplicity(m: string): { presence: 'required' | 'optional'; coun
|
|
|
589
589
|
|
|
590
590
|
function conceptFromJsonLd(doc: JsonLdConcept): Concept {
|
|
591
591
|
const id = String(doc[GL.IDENTIFIER] ?? doc['@id']?.split('/').pop() ?? '');
|
|
592
|
-
const localizations: Record<string, unknown
|
|
592
|
+
const localizations: Record<string, Record<string, unknown>> = {};
|
|
593
593
|
|
|
594
594
|
const rawLc = doc[GL.LOCALIZED_CONCEPT] ?? {};
|
|
595
595
|
for (const [lang, lc] of Object.entries(rawLc)) {
|
|
@@ -601,10 +601,10 @@ function conceptFromJsonLd(doc: JsonLdConcept): Concept {
|
|
|
601
601
|
const related = (doc[GL.RELATED] ?? []).map(mapRelatedFromJsonLd);
|
|
602
602
|
const partitiveRelations = (doc[GL.PARTITIVE_RELATIONS] ?? [])
|
|
603
603
|
.map(mapPartitiveRelationFromJsonLd)
|
|
604
|
-
.filter((r): r is Record<string, unknown> => r !== null);
|
|
604
|
+
.filter((r: any): r is Record<string, unknown> => r !== null);
|
|
605
605
|
const genericRelations = (doc[GL.GENERIC_RELATIONS] ?? [])
|
|
606
606
|
.map(mapGenericRelationFromJsonLd)
|
|
607
|
-
.filter((r): r is Record<string, unknown> => r !== null);
|
|
607
|
+
.filter((r: any): r is Record<string, unknown> => r !== null);
|
|
608
608
|
const tagsArr = doc[GL.TAGS];
|
|
609
609
|
const tags = Array.isArray(tagsArr) ? [...tagsArr] : [];
|
|
610
610
|
|
|
@@ -612,7 +612,7 @@ function conceptFromJsonLd(doc: JsonLdConcept): Concept {
|
|
|
612
612
|
id,
|
|
613
613
|
term: doc[GL.TERM] ?? null,
|
|
614
614
|
uri: doc['@id'] ?? null,
|
|
615
|
-
localizations,
|
|
615
|
+
localizations: localizations as Record<string, any>,
|
|
616
616
|
related,
|
|
617
617
|
partitive_relations: partitiveRelations,
|
|
618
618
|
generic_relations: genericRelations,
|
|
@@ -48,7 +48,7 @@ function imageFromJsonLd(raw: Record<string, unknown>): FigureImage | null {
|
|
|
48
48
|
...(typeof width === 'number' && { width }),
|
|
49
49
|
...(typeof height === 'number' && { height }),
|
|
50
50
|
...(typeof scale === 'number' && { scale }),
|
|
51
|
-
});
|
|
51
|
+
} as any);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function imagesFromJsonLd(raw: unknown): FigureImage[] {
|
|
@@ -96,5 +96,5 @@ export function figureFromJsonLd(doc: Record<string, unknown>): Figure | null {
|
|
|
96
96
|
...(description && { description }),
|
|
97
97
|
...(subfigures && { subfigures }),
|
|
98
98
|
...(sources.length && { sources }),
|
|
99
|
-
});
|
|
99
|
+
} as any);
|
|
100
100
|
}
|
|
@@ -40,11 +40,11 @@ export function formulaFromJsonLd(doc: Record<string, unknown>): Formula | null
|
|
|
40
40
|
|
|
41
41
|
return new Formula({
|
|
42
42
|
id,
|
|
43
|
-
expression,
|
|
43
|
+
expression: expression as any,
|
|
44
44
|
notation,
|
|
45
45
|
...(identifier && { identifier }),
|
|
46
46
|
...(caption && { caption }),
|
|
47
47
|
...(description && { description }),
|
|
48
48
|
...(sources.length && { sources }),
|
|
49
|
-
});
|
|
49
|
+
} as any);
|
|
50
50
|
}
|
|
@@ -90,11 +90,11 @@ export function tableFromJsonLd(doc: Record<string, unknown>): Table | null {
|
|
|
90
90
|
|
|
91
91
|
return new Table({
|
|
92
92
|
id,
|
|
93
|
-
content: content as
|
|
93
|
+
content: content as any,
|
|
94
94
|
...(identifier && { identifier }),
|
|
95
95
|
...(caption && { caption }),
|
|
96
96
|
...(description && { description }),
|
|
97
97
|
...(format && { format }),
|
|
98
98
|
...(sources.length && { sources }),
|
|
99
|
-
});
|
|
99
|
+
} as any);
|
|
100
100
|
}
|
|
@@ -107,7 +107,7 @@ async function loadPreview() {
|
|
|
107
107
|
const def = lc?.definitions?.[0]?.content || concept.definition('eng');
|
|
108
108
|
const term = lc?.terms?.[0]?.designation || concept.primaryDesignation('eng');
|
|
109
109
|
preview.value = {
|
|
110
|
-
designation: term || entry?.designations?.eng || conceptId,
|
|
110
|
+
designation: String(term || entry?.designations?.eng || conceptId),
|
|
111
111
|
definition: typeof def === 'string' ? def.slice(0, 200) : '',
|
|
112
112
|
};
|
|
113
113
|
} catch {
|