@glossarist/concept-browser 0.7.100 → 0.7.102

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 (58) hide show
  1. package/cli/index.mjs +14 -251
  2. package/cli/index.ts +289 -0
  3. package/package.json +3 -2
  4. package/src/__tests__/__fixtures__/concepts.ts +3 -3
  5. package/src/__tests__/components/learn/learn-card-dimension-card.test.ts +112 -0
  6. package/src/__tests__/composables/use-concept-edges-v2.test.ts +1 -0
  7. package/src/__tests__/designation-relationship.test.ts +2 -2
  8. package/src/__tests__/perf/serialization-perf.test.ts +1 -0
  9. package/src/__tests__/use-rdf-document.test.ts +1 -0
  10. package/src/__tests__/views/learn/learn-designations-view.test.ts +84 -0
  11. package/src/__tests__/views/learn/learn-index-view.test.ts +45 -0
  12. package/src/__tests__/views/learn/learn-relationships-view.test.ts +83 -0
  13. package/src/__tests__/views/learn/learn-statuses-view.test.ts +60 -0
  14. package/src/adapters/bibliography-adapter.ts +1 -1
  15. package/src/adapters/model-bridge.ts +15 -15
  16. package/src/adapters/non-verbal/figure-bridge.ts +2 -2
  17. package/src/adapters/non-verbal/formula-bridge.ts +2 -2
  18. package/src/adapters/non-verbal/table-bridge.ts +2 -2
  19. package/src/components/CitationDisplay.vue +1 -1
  20. package/src/components/ConceptDetail.vue +2 -2
  21. package/src/components/ConceptDiffModal.vue +2 -2
  22. package/src/components/ConceptDiffView.vue +1 -1
  23. package/src/components/DesignationList.vue +4 -4
  24. package/src/components/NavIcon.vue +1 -0
  25. package/src/components/RelationSphere.vue +1 -1
  26. package/src/components/figure/FigureDisplay.vue +5 -5
  27. package/src/components/formula/FormulaDisplay.vue +4 -4
  28. package/src/components/learn/DimensionCard.vue +71 -0
  29. package/src/components/learn/LearnCard.vue +59 -0
  30. package/src/components/table/TableDisplay.vue +3 -3
  31. package/src/components/use-rdf-document.ts +16 -7
  32. package/src/composables/use-concept-content.ts +3 -3
  33. package/src/composables/use-concept-diff.ts +2 -2
  34. package/src/composables/use-concept-edges.ts +3 -3
  35. package/src/config/page-types.ts +1 -1
  36. package/src/i18n/locales/eng.yml +114 -9
  37. package/src/i18n/locales/fra.yml +114 -9
  38. package/src/layouts/Default.astro +32 -2
  39. package/src/router/index.ts +21 -2
  40. package/src/utils/content-renderer.ts +1 -1
  41. package/src/utils/partitive-multiplicity.ts +3 -2
  42. package/src/views/ConceptView.vue +3 -3
  43. package/src/views/learn/LearnDesignationsView.vue +190 -0
  44. package/src/views/learn/LearnIndexView.vue +75 -0
  45. package/src/views/learn/LearnRelationshipsView.vue +317 -0
  46. package/src/views/learn/LearnStatusesView.vue +114 -0
  47. package/tailwind.config.js +3 -3
  48. package/tsconfig.json +1 -1
  49. package/src/__tests__/views/relation-types-view.test.ts +0 -85
  50. package/src/adapters/model-bridge/bridges.ts +0 -159
  51. package/src/adapters/model-bridge/concept.ts +0 -97
  52. package/src/adapters/model-bridge/entity-refs.ts +0 -49
  53. package/src/adapters/model-bridge/hyperedge.ts +0 -103
  54. package/src/adapters/model-bridge/index.ts +0 -46
  55. package/src/adapters/model-bridge/mappers.ts +0 -277
  56. package/src/adapters/model-bridge/partitive.ts +0 -79
  57. package/src/adapters/non-verbal/glossarist-augment.d.ts +0 -339
  58. /package/src/adapters/{model-bridge/jsonld-types.ts → jsonld-types.ts} +0 -0
package/cli/index.mjs CHANGED
@@ -1,257 +1,20 @@
1
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
- import { loadSiteConfig } from '../scripts/load-site-config.mjs';
25
- import { existsSync } from 'fs';
26
- import { resolve, dirname } from 'path';
2
+ // Thin ESM shim so `concept-browser` bin works from npm consumers that
3
+ // don't run TypeScript natively. The real logic lives in cli/index.ts.
4
+ // This shim uses tsx to load the .ts entry point at runtime.
5
+ // When TODO.typescript/03 is complete, this shim may be replaced by a
6
+ // compiled cli/index.js output.
27
7
  import { fileURLToPath } from 'url';
8
+ import { resolve, dirname } from 'path';
9
+ import { spawnSync } from 'child_process';
28
10
 
29
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
30
- const pkgRoot = resolve(__dirname, '..');
31
-
32
- const commands = {
33
- fetch: async () => (await import('../scripts/fetch-datasets.mjs')).main(),
34
- generate: async () => { await import('../scripts/generate-data.mjs'); },
35
- edges: async () => (await import('../scripts/build-edges.js')).main(),
36
- about: async () => (await import('../scripts/process-about-pages.mjs')).main(),
37
- };
38
-
39
- function parseArgs(argv) {
40
- const positional = [];
41
- const named = {};
42
- for (let i = 0; i < argv.length; i++) {
43
- if (argv[i].startsWith('--') && i + 1 < argv.length) {
44
- named[argv[i].slice(2)] = argv[i + 1];
45
- i++;
46
- } else if (!argv[i].startsWith('-')) {
47
- positional.push(argv[i]);
48
- }
49
- }
50
- return { positional, named };
51
- }
52
-
53
- async function main() {
54
- const args = process.argv.slice(2);
55
- const { positional, named } = parseArgs(args);
56
- const cmd = positional[0];
57
-
58
- if (!cmd || cmd === 'help' || cmd === '--help') {
59
- console.log(`concept-browser <command> [options]
60
-
61
- Commands:
62
- fetch Fetch/update datasets from GCR packages or source repos
63
- generate Convert YAML concepts to JSON-LD static data
64
- edges Build cross-reference edges from generated concepts
65
- about Compile per-dataset and per-group about pages
66
- build Full pipeline (fetch + generate + edges + about + vite build)
67
- site Same as build
68
- normalize NFC-normalize YAML files in .datasets/
69
- doctor Diagnose the local environment (deps, datasets, shapes, context)
70
-
71
- Options:
72
- --site <id> Site config ID (looks for site-config.yml in CWD)
73
- --check (normalize only) Non-mutating; exit 1 if any file is not NFC
74
-
75
- Environment:
76
- SITE_CONFIG Site config file path (highest priority)
77
- SITE_ID Site config ID (same as --site)
78
- GITHUB_TOKEN GitHub token for private repos`);
79
- process.exit(cmd ? 0 : 1);
80
- }
81
-
82
- // Pre-load site config so scripts can use it
83
- if (!process.env.SITE_CONFIG && !process.env.SITE_ID && named.site) {
84
- process.env.SITE_ID = named.site;
85
- }
86
- const { config } = loadSiteConfig(named.site ? [named.site] : []);
87
-
88
- if (cmd === 'build' || cmd === 'site') {
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']) {
93
- console.log(`\n=== ${step.toUpperCase()} ===\n`);
94
- await commands[step]();
95
- }
96
-
97
- // Generate favicons from site logo
98
- const fs = await import('fs');
99
- const publicDir = resolve(process.cwd(), 'public');
100
- fs.mkdirSync(publicDir, { recursive: true });
101
-
102
- const branding = config?.branding || {};
103
- const faviconSrc =
104
- (branding.favicon && resolve(process.cwd(), branding.favicon)) ||
105
- (branding.logo?.localPath && resolve(process.cwd(), branding.logo.localPath)) ||
106
- resolve(pkgRoot, 'public', 'favicon.svg');
107
-
108
- let faviconHtml = '';
109
- if (fs.existsSync(faviconSrc)) {
110
- console.log(`\n=== FAVICONS ===\n`);
111
- const favicons = (await import('favicons')).default;
112
- const source = fs.readFileSync(faviconSrc);
113
- const response = await favicons(source, {
114
- appName: config?.title || 'Glossarist',
115
- background: branding.primaryColor || '#2563eb',
116
- theme_color: branding.primaryColor || '#2563eb',
117
- icons: {
118
- android: false,
119
- appleIcon: true,
120
- appleStartup: false,
121
- favicons: true,
122
- windows: false,
123
- yandex: false,
124
- },
125
- });
126
-
127
- // Write all generated images and files to public/
128
- for (const img of response.images) {
129
- fs.writeFileSync(resolve(publicDir, img.name), img.contents);
130
- }
131
- for (const file of response.files) {
132
- fs.writeFileSync(resolve(publicDir, file.name), file.contents);
133
- }
134
-
135
- // Keep the original SVG for dark mode support
136
- if (faviconSrc.endsWith('.svg')) {
137
- fs.copyFileSync(faviconSrc, resolve(publicDir, 'favicon.svg'));
138
- }
139
-
140
- faviconHtml = response.html.join('\n ');
141
- // Add SVG favicon
142
- if (faviconSrc.endsWith('.svg')) {
143
- faviconHtml += '\n <link rel="icon" type="image/svg+xml" href="/favicon.svg">';
144
- }
145
- // Write tags for Vite plugin to pick up
146
- fs.writeFileSync(resolve(publicDir, 'favicon-links.html'), faviconHtml);
147
- console.log(` Generated ${response.images.length} favicon files`);
148
- }
149
-
150
- // Pass favicon tags to Vite via env
151
- if (faviconHtml) {
152
- const basePath = process.env.BASE_PATH?.replace(/\/+$/, '') || '';
153
- if (basePath) {
154
- faviconHtml = faviconHtml.replace(/(href|content)="\/([^"]+)"/g, `$1="${basePath}/$2"`);
155
- }
156
- process.env.FAVICON_HTML = faviconHtml;
157
- }
158
-
159
- // Pass branding info for HTML transformation
160
- process.env.SITE_TITLE = config?.title || 'Glossarist';
161
- if (branding.fonts?.header || branding.fonts?.body) {
162
- const fontFamilies = [];
163
- if (branding.fonts.header?.source === 'google') {
164
- const w = (branding.fonts.header.weights || [400, 700]).join(';');
165
- fontFamilies.push(`family=${branding.fonts.header.family.replace(/ /g, '+')}:wght@${w}`);
166
- }
167
- if (branding.fonts.body?.source === 'google') {
168
- const w = (branding.fonts.body.weights || [400, 700]).join(';');
169
- fontFamilies.push(`family=${branding.fonts.body.family.replace(/ /g, '+')}:wght@${w}`);
170
- }
171
- if (fontFamilies.length) {
172
- process.env.SITE_FONTS_URL = `https://fonts.googleapis.com/css2?${fontFamilies.join('&')}&display=swap`;
173
- }
174
- }
175
-
176
- // Bridge data to Astro content collections
177
- console.log(`\n=== BRIDGE DATA ===\n`);
178
- const bridge = resolve(pkgRoot, 'scripts', 'bridge-to-astro.mjs');
179
- if (existsSync(bridge)) {
180
- await import(`file://${bridge}`);
181
- }
182
-
183
- // Run Astro build (TODO.astro/22)
184
- console.log(`\n=== BUILD ASTRO ===\n`);
185
- const astroConfig = resolve(pkgRoot, 'astro.config.mjs');
186
- if (existsSync(astroConfig)) {
187
- try {
188
- const { build: astroBuild } = await import('astro');
189
- await astroBuild({ root: pkgRoot, logLevel: 'info' });
190
- } catch (e) {
191
- console.warn(` Astro build failed (${e.message}), falling back to Vite SPA`);
192
- console.log(`\n=== BUILD SPA ===\n`);
193
- const viteConfig = resolve(pkgRoot, 'vite.config.ts');
194
- const { build: viteBuild } = await import('vite');
195
- await viteBuild({ configFile: viteConfig, root: pkgRoot, mode: 'production' });
196
- }
197
- } else {
198
- // Fall back to Vite SPA if Astro config doesn't exist
199
- console.log(`\n=== BUILD SPA ===\n`);
200
- const viteConfig = resolve(pkgRoot, 'vite.config.ts');
201
- const { build: viteBuild } = await import('vite');
202
- await viteBuild({ configFile: viteConfig, root: pkgRoot, mode: 'production' });
203
- }
204
-
205
- // Run postbuild (404 page) via dynamic import
206
- const postbuild = resolve(pkgRoot, 'scripts', 'generate-404.js');
207
- if (existsSync(postbuild)) {
208
- await import(`file://${postbuild}`);
209
- }
210
-
211
- return;
212
- }
213
-
214
- const runner = commands[cmd];
215
- if (runner) {
216
- await runner();
217
- return;
218
- }
219
-
220
- if (cmd === 'normalize') {
221
- const { normalizeYaml } = await import('../scripts/normalize-yaml.mjs');
222
- const check = process.argv.includes('--check');
223
- const paths = process.argv.slice(2).filter(a => !a.startsWith('-') && a !== 'normalize');
224
- const { checked, nonNfc, fixed } = normalizeYaml({ check, paths });
225
- if (check) {
226
- if (nonNfc === 0) {
227
- console.log(`NFC OK: ${checked} file(s) checked, all normalized`);
228
- return;
229
- }
230
- console.error(`NFC check failed: ${nonNfc} of ${checked} file(s) are not NFC-normalized\n`);
231
- for (const f of fixed) console.error(` ${f}`);
232
- process.exit(1);
233
- }
234
- if (nonNfc === 0) {
235
- console.log(`NFC OK: ${checked} file(s) checked, all already normalized`);
236
- } else {
237
- console.log(`Normalized ${nonNfc} of ${checked} file(s)`);
238
- for (const f of fixed) console.log(` ${f}`);
239
- }
240
- return;
241
- }
242
-
243
- if (cmd === 'doctor') {
244
- const { main: doctorMain } = await import('../scripts/doctor.mjs');
245
- await doctorMain();
246
- return;
247
- }
12
+ const entry = resolve(__dirname, 'index.ts');
248
13
 
249
- console.error(`Unknown command: ${cmd}`);
250
- console.error('Run `concept-browser help` for usage.');
251
- process.exit(1);
252
- }
14
+ const result = spawnSync(
15
+ process.execPath,
16
+ ['--import', 'tsx', entry, ...process.argv.slice(2)],
17
+ { stdio: 'inherit', cwd: process.cwd() }
18
+ );
253
19
 
254
- main().catch(e => {
255
- console.error(e);
256
- process.exit(1);
257
- });
20
+ process.exit(result.status ?? 1);
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.100",
3
+ "version": "0.7.102",
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.34",
40
+ "glossarist": "^0.4.51",
41
41
  "js-yaml": "^4.1.0",
42
42
  "jszip": "^3.10.1",
43
43
  "pinia": "^2.3.1",
@@ -59,6 +59,7 @@
59
59
  "happy-dom": "^20.9.0",
60
60
  "n3": "^1.26.0",
61
61
  "rdf-validate-shacl": "^0.4.5",
62
+ "tsx": "^4.23.1",
62
63
  "typescript": "~5.7.3",
63
64
  "vitest": "^4.1.5",
64
65
  "vue-tsc": "^2.2.8"
@@ -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
  {