@glossarist/concept-browser 0.7.102 → 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 +229 -14
- package/package.json +1 -2
package/cli/index.mjs
CHANGED
|
@@ -1,20 +1,235 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 {
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
10
18
|
|
|
11
19
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
const
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
);
|
|
227
|
+
console.error(`Unknown command: ${cmd}`);
|
|
228
|
+
console.error('Run `concept-browser help` for usage.');
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
19
231
|
|
|
20
|
-
|
|
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.
|
|
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": {
|
|
@@ -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"
|