@glossarist/concept-browser 0.1.0
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/README.md +319 -0
- package/cli/index.mjs +119 -0
- package/env.d.ts +7 -0
- package/index.html +16 -0
- package/package.json +78 -0
- package/postcss.config.js +6 -0
- package/scripts/build-edges.js +112 -0
- package/scripts/fetch-datasets.mjs +195 -0
- package/scripts/generate-404.js +15 -0
- package/scripts/generate-data.mjs +606 -0
- package/scripts/load-site-config.mjs +56 -0
- package/src/App.vue +98 -0
- package/src/__tests__/data-integration.test.ts +135 -0
- package/src/__tests__/data-integrity.test.ts +101 -0
- package/src/__tests__/dataset-adapter.test.ts +336 -0
- package/src/__tests__/dataset-style.test.ts +37 -0
- package/src/__tests__/graph.test.ts +187 -0
- package/src/__tests__/lang.test.ts +29 -0
- package/src/__tests__/math.test.ts +113 -0
- package/src/__tests__/reference-resolver.test.ts +122 -0
- package/src/__tests__/site-config.test.ts +52 -0
- package/src/__tests__/uri-router.test.ts +76 -0
- package/src/adapters/DatasetAdapter.ts +270 -0
- package/src/adapters/ReferenceResolver.ts +95 -0
- package/src/adapters/UriRouter.ts +41 -0
- package/src/adapters/factory.ts +78 -0
- package/src/adapters/types.ts +162 -0
- package/src/components/AppHeader.vue +99 -0
- package/src/components/AppSidebar.vue +133 -0
- package/src/components/ConceptCard.vue +65 -0
- package/src/components/ConceptDetail.vue +540 -0
- package/src/components/ConceptTimeline.vue +410 -0
- package/src/components/FormatDownloads.vue +46 -0
- package/src/components/GraphPanel.vue +499 -0
- package/src/components/LanguageDetail.vue +211 -0
- package/src/components/NavIcon.vue +20 -0
- package/src/components/SearchBar.vue +241 -0
- package/src/composables/use-dataset-loader.ts +27 -0
- package/src/config/types.ts +130 -0
- package/src/config/use-site-config.ts +144 -0
- package/src/graph/GraphEngine.ts +137 -0
- package/src/graph/index.ts +1 -0
- package/src/main.ts +11 -0
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/src/router/index.ts +43 -0
- package/src/router/page-routes.ts +35 -0
- package/src/stores/ui.ts +59 -0
- package/src/stores/vocabulary.ts +309 -0
- package/src/style.css +314 -0
- package/src/utils/asciidoc-lite.ts +123 -0
- package/src/utils/concept-formats.ts +157 -0
- package/src/utils/dataset-style.ts +54 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/lang.ts +32 -0
- package/src/utils/math.ts +100 -0
- package/src/views/AboutView.vue +122 -0
- package/src/views/ConceptView.vue +119 -0
- package/src/views/ContributorsView.vue +110 -0
- package/src/views/DatasetView.vue +249 -0
- package/src/views/GraphView.vue +65 -0
- package/src/views/HomeView.vue +168 -0
- package/src/views/NewsView.vue +146 -0
- package/src/views/ResolveView.vue +63 -0
- package/src/views/SearchView.vue +33 -0
- package/src/views/StatsView.vue +121 -0
- package/tailwind.config.js +43 -0
- package/tsconfig.json +24 -0
- package/vite.config.ts +27 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* fetch-datasets.mjs — Load datasets from .gcr files or clone source repos.
|
|
4
|
+
*
|
|
5
|
+
* Reads site config (via load-site-config.mjs), for each dataset:
|
|
6
|
+
* 1. If .gcr/{id}.gcr exists, extract to .datasets/{id}/
|
|
7
|
+
* 2. Else download from gcrPackage URL and extract
|
|
8
|
+
* 3. Else clone/update source repo into .datasets/{id}/
|
|
9
|
+
*
|
|
10
|
+
* After fetching, validates that all GCR dependencies are satisfiable
|
|
11
|
+
* (either provided locally or routed externally).
|
|
12
|
+
*
|
|
13
|
+
* Supports DATASET_SOURCE_{ID} env var to override with local path.
|
|
14
|
+
* Supports GITHUB_TOKEN for private repos.
|
|
15
|
+
*/
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import yaml from 'js-yaml';
|
|
19
|
+
import { execSync } from 'child_process';
|
|
20
|
+
import { loadSiteConfig } from './load-site-config.mjs';
|
|
21
|
+
|
|
22
|
+
const ROOT = process.cwd();
|
|
23
|
+
const DATASETS_DIR = path.join(ROOT, '.datasets');
|
|
24
|
+
const GCR_DIR = path.join(ROOT, '.gcr');
|
|
25
|
+
|
|
26
|
+
function matchUriPattern(uri, pattern) {
|
|
27
|
+
if (!pattern.endsWith('*')) return uri === pattern;
|
|
28
|
+
return uri.startsWith(pattern.slice(0, -1));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// --- GCR download ---
|
|
32
|
+
async function downloadGcr(url, destPath) {
|
|
33
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
34
|
+
console.log(` Downloading ${url}...`);
|
|
35
|
+
const resp = await fetch(url);
|
|
36
|
+
if (!resp.ok) throw new Error(`Download failed: ${resp.status} ${resp.statusText}`);
|
|
37
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
38
|
+
fs.writeFileSync(destPath, buf);
|
|
39
|
+
console.log(` Saved to ${destPath} (${(buf.length / 1024).toFixed(0)} KB)`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// --- GCR extraction ---
|
|
43
|
+
function extractGcr(gcrPath, targetDir) {
|
|
44
|
+
if (fs.existsSync(targetDir)) {
|
|
45
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
execSync(`unzip -o -q "${gcrPath}" -d "${targetDir}"`, { stdio: 'pipe' });
|
|
51
|
+
} catch {
|
|
52
|
+
try {
|
|
53
|
+
execSync(`python3 -c "import zipfile; zipfile.ZipFile('${gcrPath}').extractall('${targetDir}')"`, { stdio: 'pipe' });
|
|
54
|
+
} catch (e2) {
|
|
55
|
+
throw new Error(`Failed to extract ${gcrPath}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
console.log(` Extracted to ${targetDir}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// --- Read GCR metadata from extracted dir ---
|
|
62
|
+
function readGcrMetadata(targetDir) {
|
|
63
|
+
const metaPath = path.join(targetDir, 'metadata.yaml');
|
|
64
|
+
if (!fs.existsSync(metaPath)) return null;
|
|
65
|
+
return yaml.load(fs.readFileSync(metaPath, 'utf8'));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- Dependency validation ---
|
|
69
|
+
function validateDependencies(config, gcrMetadata) {
|
|
70
|
+
const errors = [];
|
|
71
|
+
const allProvidedUris = [];
|
|
72
|
+
for (const ds of config.datasets) {
|
|
73
|
+
allProvidedUris.push(ds.uri);
|
|
74
|
+
if (ds.uriAliases) allProvidedUris.push(...ds.uriAliases);
|
|
75
|
+
}
|
|
76
|
+
const routingUris = (config.routing || []).map(r => r.uri);
|
|
77
|
+
|
|
78
|
+
for (const ds of config.datasets) {
|
|
79
|
+
const meta = gcrMetadata[ds.id];
|
|
80
|
+
if (!meta?.dependencies?.length) continue;
|
|
81
|
+
|
|
82
|
+
for (const dep of meta.dependencies) {
|
|
83
|
+
const depUri = dep.uri;
|
|
84
|
+
const satisfied = [...allProvidedUris, ...routingUris].some(p => matchUriPattern(depUri, p));
|
|
85
|
+
if (!satisfied) {
|
|
86
|
+
errors.push(`GCR '${ds.id}' depends on '${depUri}' (${dep.refCount} refs) but no provider or route configured`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return errors;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// --- Git operations ---
|
|
94
|
+
function cloneOrUpdate(sourceRepo, targetDir) {
|
|
95
|
+
const env = { ...process.env };
|
|
96
|
+
let repoUrl = sourceRepo;
|
|
97
|
+
if (env.GITHUB_TOKEN) {
|
|
98
|
+
repoUrl = sourceRepo.replace('https://', `https://x-access-token:${env.GITHUB_TOKEN}@`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (fs.existsSync(path.join(targetDir, '.git'))) {
|
|
102
|
+
console.log(` Updating existing clone...`);
|
|
103
|
+
try {
|
|
104
|
+
execSync('git fetch origin', { cwd: targetDir, stdio: 'pipe', env });
|
|
105
|
+
execSync('git reset --hard origin/HEAD', { cwd: targetDir, stdio: 'pipe', env });
|
|
106
|
+
execSync('git clean -fd', { cwd: targetDir, stdio: 'pipe', env });
|
|
107
|
+
} catch {
|
|
108
|
+
console.warn(` git update failed, re-cloning`);
|
|
109
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
110
|
+
execSync(`git clone --depth 1 "${repoUrl}" "${targetDir}"`, { stdio: 'pipe', env });
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
114
|
+
console.log(` Cloning ${sourceRepo}...`);
|
|
115
|
+
execSync(`git clone --depth 1 "${repoUrl}" "${targetDir}"`, { stdio: 'pipe', env });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// --- Main ---
|
|
120
|
+
console.log('Fetching glossarist datasets...\n');
|
|
121
|
+
|
|
122
|
+
const { config } = loadSiteConfig();
|
|
123
|
+
const gcrMetadata = {};
|
|
124
|
+
|
|
125
|
+
for (const ds of config.datasets) {
|
|
126
|
+
console.log(`${ds.id}:`);
|
|
127
|
+
|
|
128
|
+
const gcrPath = path.join(GCR_DIR, `${ds.id}.gcr`);
|
|
129
|
+
const targetDir = path.join(DATASETS_DIR, ds.id);
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
if (fs.existsSync(gcrPath)) {
|
|
133
|
+
console.log(` Using local .gcr/${ds.id}.gcr`);
|
|
134
|
+
extractGcr(gcrPath, targetDir);
|
|
135
|
+
} else if (ds.gcrPackage) {
|
|
136
|
+
console.log(` Using GCR package: ${ds.gcrPackage}`);
|
|
137
|
+
try {
|
|
138
|
+
await downloadGcr(ds.gcrPackage, gcrPath);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.warn(` GCR download failed: ${e.message}`);
|
|
141
|
+
console.warn(` Skipping ${ds.id}`);
|
|
142
|
+
console.log();
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
extractGcr(gcrPath, targetDir);
|
|
146
|
+
} else {
|
|
147
|
+
const envOverride = process.env[`DATASET_SOURCE_${ds.id.toUpperCase()}`];
|
|
148
|
+
if (envOverride) {
|
|
149
|
+
console.log(` Using local path: ${envOverride}`);
|
|
150
|
+
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
|
|
151
|
+
const localConcepts = path.join(envOverride, 'concepts');
|
|
152
|
+
const targetConcepts = path.join(targetDir, 'concepts');
|
|
153
|
+
if (fs.existsSync(localConcepts)) {
|
|
154
|
+
if (fs.existsSync(targetConcepts)) fs.rmSync(targetConcepts, { recursive: true, force: true });
|
|
155
|
+
execSync(`cp -r "${localConcepts}" "${targetConcepts}"`, { stdio: 'pipe' });
|
|
156
|
+
}
|
|
157
|
+
const registerYaml = path.join(envOverride, 'register.yaml');
|
|
158
|
+
if (fs.existsSync(registerYaml)) {
|
|
159
|
+
fs.copyFileSync(registerYaml, path.join(targetDir, 'register.yaml'));
|
|
160
|
+
}
|
|
161
|
+
} else if (ds.sourceRepo) {
|
|
162
|
+
cloneOrUpdate(ds.sourceRepo, targetDir);
|
|
163
|
+
} else {
|
|
164
|
+
console.warn(` No source configured, skipping`);
|
|
165
|
+
console.log();
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Read metadata for dependency validation
|
|
171
|
+
const meta = readGcrMetadata(targetDir);
|
|
172
|
+
if (meta) {
|
|
173
|
+
gcrMetadata[ds.id] = meta;
|
|
174
|
+
console.log(` ${meta.statistics?.total_concepts || '?'} concepts, ${meta.uri || 'no uri'}`);
|
|
175
|
+
}
|
|
176
|
+
} catch (e) {
|
|
177
|
+
console.warn(` Failed: ${e.message}`);
|
|
178
|
+
console.warn(` Skipping ${ds.id}`);
|
|
179
|
+
}
|
|
180
|
+
console.log();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Dependency validation
|
|
184
|
+
console.log('Validating dependencies...\n');
|
|
185
|
+
const errors = validateDependencies(config, gcrMetadata);
|
|
186
|
+
if (errors.length) {
|
|
187
|
+
console.error('Dependency validation FAILED:');
|
|
188
|
+
for (const err of errors) {
|
|
189
|
+
console.error(` ✗ ${err}`);
|
|
190
|
+
}
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
console.log('All dependencies satisfied.\n');
|
|
194
|
+
|
|
195
|
+
console.log('Done.');
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { copyFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const dist = join(process.cwd(), 'dist');
|
|
7
|
+
const indexHtml = join(dist, 'index.html');
|
|
8
|
+
const notFoundHtml = join(dist, '404.html');
|
|
9
|
+
|
|
10
|
+
if (existsSync(indexHtml)) {
|
|
11
|
+
copyFileSync(indexHtml, notFoundHtml);
|
|
12
|
+
console.log('Created dist/404.html for SPA fallback');
|
|
13
|
+
} else {
|
|
14
|
+
console.warn('dist/index.html not found — skipping 404.html generation');
|
|
15
|
+
}
|