@glossarist/concept-browser 0.7.102 → 0.7.104

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 CHANGED
@@ -1,20 +1,235 @@
1
1
  #!/usr/bin/env node
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.
7
- import { fileURLToPath } from 'url';
2
+
3
+ /**
4
+ * Glossarist Concept Browser CLI — runtime entrypoint.
5
+ *
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.
8
+ *
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.
12
+ */
13
+
14
+ import { loadSiteConfig } from '../scripts/load-site-config.mjs';
15
+ import { existsSync } from 'fs';
8
16
  import { resolve, dirname } from 'path';
9
- import { spawnSync } from 'child_process';
17
+ import { fileURLToPath } from 'url';
10
18
 
11
19
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
- const entry = resolve(__dirname, 'index.ts');
20
+ const pkgRoot = resolve(__dirname, '..');
21
+
22
+ const commands = {
23
+ fetch: async () => (await import('../scripts/fetch-datasets.mjs')).main(),
24
+ generate: async () => { await import('../scripts/generate-data.mjs'); },
25
+ edges: async () => (await import('../scripts/build-edges.js')).main(),
26
+ about: async () => (await import('../scripts/process-about-pages.mjs')).main(),
27
+ };
28
+
29
+ function parseArgs(argv) {
30
+ const positional = [];
31
+ const named = {};
32
+ for (let i = 0; i < argv.length; i++) {
33
+ if (argv[i].startsWith('--') && i + 1 < argv.length) {
34
+ named[argv[i].slice(2)] = argv[i + 1];
35
+ i++;
36
+ } else if (!argv[i].startsWith('-')) {
37
+ positional.push(argv[i]);
38
+ }
39
+ }
40
+ return { positional, named };
41
+ }
42
+
43
+ async function main() {
44
+ const args = process.argv.slice(2);
45
+ const { positional, named } = parseArgs(args);
46
+ const cmd = positional[0];
47
+
48
+ if (!cmd || cmd === 'help' || cmd === '--help') {
49
+ console.log(`concept-browser <command> [options]
50
+
51
+ Commands:
52
+ fetch Fetch/update datasets from GCR packages or source repos
53
+ generate Convert YAML concepts to JSON-LD static data
54
+ edges Build cross-reference edges from generated concepts
55
+ about Compile per-dataset and per-group about pages
56
+ build Full pipeline (fetch + generate + edges + about + vite build)
57
+ site Same as build
58
+ normalize NFC-normalize YAML files in .datasets/
59
+ doctor Diagnose the local environment (deps, datasets, shapes, context)
60
+
61
+ Options:
62
+ --site <id> Site config ID (looks for site-config.yml in CWD)
63
+ --check (normalize only) Non-mutating; exit 1 if any file is not NFC
64
+
65
+ Environment:
66
+ SITE_CONFIG Site config file path (highest priority)
67
+ SITE_ID Site config ID (same as --site)
68
+ GITHUB_TOKEN GitHub token for private repos`);
69
+ process.exit(cmd ? 0 : 1);
70
+ }
71
+
72
+ if (!process.env.SITE_CONFIG && !process.env.SITE_ID && named.site) {
73
+ process.env.SITE_ID = named.site;
74
+ }
75
+ const { config } = loadSiteConfig(named.site ? [named.site] : []);
76
+
77
+ if (cmd === 'build' || cmd === 'site') {
78
+ if (!process.env.BASE_PATH && config?.basePath) {
79
+ process.env.BASE_PATH = config.basePath;
80
+ }
81
+ for (const step of ['fetch', 'generate', 'edges', 'about']) {
82
+ console.log(`\n=== ${step.toUpperCase()} ===\n`);
83
+ await commands[step]();
84
+ }
85
+
86
+ const fs = await import('fs');
87
+ const publicDir = resolve(process.cwd(), 'public');
88
+ fs.mkdirSync(publicDir, { recursive: true });
89
+
90
+ const branding = config?.branding || {};
91
+ const faviconSrc =
92
+ (branding.favicon && resolve(process.cwd(), branding.favicon)) ||
93
+ (branding.logo?.localPath && resolve(process.cwd(), branding.logo.localPath)) ||
94
+ resolve(pkgRoot, 'public', 'favicon.svg');
95
+
96
+ let faviconHtml = '';
97
+ if (fs.existsSync(faviconSrc)) {
98
+ console.log(`\n=== FAVICONS ===\n`);
99
+ const favicons = (await import('favicons')).default;
100
+ const source = fs.readFileSync(faviconSrc);
101
+ const response = await favicons(source, {
102
+ appName: config?.title || 'Glossarist',
103
+ background: branding.primaryColor || '#2563eb',
104
+ theme_color: branding.primaryColor || '#2563eb',
105
+ icons: {
106
+ android: false,
107
+ appleIcon: true,
108
+ appleStartup: false,
109
+ favicons: true,
110
+ windows: false,
111
+ yandex: false,
112
+ },
113
+ });
114
+
115
+ for (const img of response.images) {
116
+ fs.writeFileSync(resolve(publicDir, img.name), img.contents);
117
+ }
118
+ for (const file of response.files) {
119
+ fs.writeFileSync(resolve(publicDir, file.name), file.contents);
120
+ }
121
+
122
+ if (faviconSrc.endsWith('.svg')) {
123
+ fs.copyFileSync(faviconSrc, resolve(publicDir, 'favicon.svg'));
124
+ }
125
+
126
+ faviconHtml = response.html.join('\n ');
127
+ if (faviconSrc.endsWith('.svg')) {
128
+ faviconHtml += '\n <link rel="icon" type="image/svg+xml" href="/favicon.svg">';
129
+ }
130
+ fs.writeFileSync(resolve(publicDir, 'favicon-links.html'), faviconHtml);
131
+ console.log(` Generated ${response.images.length} favicon files`);
132
+ }
133
+
134
+ if (faviconHtml) {
135
+ const basePath = process.env.BASE_PATH?.replace(/\/+$/, '') || '';
136
+ if (basePath) {
137
+ faviconHtml = faviconHtml.replace(/(href|content)="\/([^"]+)"/g, `$1="${basePath}/$2"`);
138
+ }
139
+ process.env.FAVICON_HTML = faviconHtml;
140
+ }
141
+
142
+ process.env.SITE_TITLE = config?.title || 'Glossarist';
143
+ if (branding.fonts?.header || branding.fonts?.body) {
144
+ const fontFamilies = [];
145
+ if (branding.fonts.header?.source === 'google') {
146
+ const w = (branding.fonts.header.weights || [400, 700]).join(';');
147
+ fontFamilies.push(`family=${branding.fonts.header.family.replace(/ /g, '+')}:wght@${w}`);
148
+ }
149
+ if (branding.fonts.body?.source === 'google') {
150
+ const w = (branding.fonts.body.weights || [400, 700]).join(';');
151
+ fontFamilies.push(`family=${branding.fonts.body.family.replace(/ /g, '+')}:wght@${w}`);
152
+ }
153
+ if (fontFamilies.length) {
154
+ process.env.SITE_FONTS_URL = `https://fonts.googleapis.com/css2?${fontFamilies.join('&')}&display=swap`;
155
+ }
156
+ }
157
+
158
+ console.log(`\n=== BRIDGE DATA ===\n`);
159
+ const bridge = resolve(pkgRoot, 'scripts', 'bridge-to-astro.mjs');
160
+ if (existsSync(bridge)) {
161
+ await import(`file://${bridge}`);
162
+ }
163
+
164
+ console.log(`\n=== BUILD ASTRO ===\n`);
165
+ const astroConfig = resolve(pkgRoot, 'astro.config.mjs');
166
+ if (existsSync(astroConfig)) {
167
+ try {
168
+ const { build: astroBuild } = await import('astro');
169
+ await astroBuild({ root: pkgRoot, logLevel: 'info' });
170
+ } catch (e) {
171
+ console.warn(` Astro build failed (${e.message}), falling back to Vite SPA`);
172
+ console.log(`\n=== BUILD SPA ===\n`);
173
+ const viteConfig = resolve(pkgRoot, 'vite.config.ts');
174
+ const { build: viteBuild } = await import('vite');
175
+ await viteBuild({ configFile: viteConfig, root: pkgRoot, mode: 'production' });
176
+ }
177
+ } else {
178
+ console.log(`\n=== BUILD SPA ===\n`);
179
+ const viteConfig = resolve(pkgRoot, 'vite.config.ts');
180
+ const { build: viteBuild } = await import('vite');
181
+ await viteBuild({ configFile: viteConfig, root: pkgRoot, mode: 'production' });
182
+ }
183
+
184
+ const postbuild = resolve(pkgRoot, 'scripts', 'generate-404.js');
185
+ if (existsSync(postbuild)) {
186
+ await import(`file://${postbuild}`);
187
+ }
188
+
189
+ return;
190
+ }
191
+
192
+ const runner = commands[cmd];
193
+ if (runner) {
194
+ await runner();
195
+ return;
196
+ }
197
+
198
+ if (cmd === 'normalize') {
199
+ const { normalizeYaml } = await import('../scripts/normalize-yaml.mjs');
200
+ const check = process.argv.includes('--check');
201
+ const paths = process.argv.slice(2).filter(a => !a.startsWith('-') && a !== 'normalize');
202
+ const { checked, nonNfc, fixed } = normalizeYaml({ check, paths });
203
+ if (check) {
204
+ if (nonNfc === 0) {
205
+ console.log(`NFC OK: ${checked} file(s) checked, all normalized`);
206
+ return;
207
+ }
208
+ console.error(`NFC check failed: ${nonNfc} of ${checked} file(s) are not NFC-normalized\n`);
209
+ for (const f of fixed) console.error(` ${f}`);
210
+ process.exit(1);
211
+ }
212
+ if (nonNfc === 0) {
213
+ console.log(`NFC OK: ${checked} file(s) checked, all already normalized`);
214
+ } else {
215
+ console.log(`Normalized ${nonNfc} of ${checked} file(s)`);
216
+ for (const f of fixed) console.log(` ${f}`);
217
+ }
218
+ return;
219
+ }
220
+
221
+ if (cmd === 'doctor') {
222
+ const { main: doctorMain } = await import('../scripts/doctor.mjs');
223
+ await doctorMain();
224
+ return;
225
+ }
13
226
 
14
- const result = spawnSync(
15
- process.execPath,
16
- ['--import', 'tsx', entry, ...process.argv.slice(2)],
17
- { stdio: 'inherit', cwd: process.cwd() }
18
- );
227
+ console.error(`Unknown command: ${cmd}`);
228
+ console.error('Run `concept-browser help` for usage.');
229
+ process.exit(1);
230
+ }
19
231
 
20
- process.exit(result.status ?? 1);
232
+ main().catch(e => {
233
+ console.error(e);
234
+ process.exit(1);
235
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glossarist/concept-browser",
3
- "version": "0.7.102",
3
+ "version": "0.7.104",
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": {
@@ -59,7 +59,6 @@
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",
63
62
  "typescript": "~5.7.3",
64
63
  "vitest": "^4.1.5",
65
64
  "vue-tsc": "^2.2.8"
@@ -68,12 +68,7 @@ const fontBody = brandingFonts?.body?.family
68
68
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
69
69
  <link rel="preload" as="style" href={fontsUrl} onload="this.rel='stylesheet'">
70
70
  <noscript><link href={fontsUrl} rel="stylesheet"></noscript>
71
- <style is:global>
72
- :root {
73
- --font-header: ${fontHeader};
74
- --font-body: ${fontBody};
75
- }
76
- </style>
71
+ <Fragment set:html={`<style>:root{--font-header:${fontHeader};--font-body:${fontBody}}</style>`} />
77
72
  <script is:inline>
78
73
  (function() {
79
74
  const c = document.cookie.split(';').map(s => s.trim()).find(s => s.startsWith('theme='));