@glossarist/concept-browser 0.4.18 → 0.5.1

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 CHANGED
@@ -291,6 +291,38 @@ BASE_PATH=/vocab/ npm run build
291
291
 
292
292
  This sets the Vite `base` config so all asset paths are prefixed correctly.
293
293
 
294
+ **How it works:**
295
+
296
+ The `BASE_PATH` environment variable controls subpath deployment through two mechanisms:
297
+
298
+ 1. **Build time (Node.js scripts):** `generate-data.mjs` uses `BASE_PATH` to prefix logo paths in the generated `site-config.json`. The favicon generation in `cli/index.mjs` also prefixes favicon link hrefs. These are build-time rewrites because they produce static JSON/HTML that can't use Vite's runtime resolution.
299
+
300
+ 2. **Runtime (browser):** The Vue app uses `import.meta.env.BASE_URL` (a Vite compile-time constant derived from `base`) to prefix all `fetch()` paths. This includes:
301
+ - `datasets.json` (dataset registry)
302
+ - `site-config.json` (branding, features)
303
+ - `data/{id}/...` (concept data via `DatasetAdapter`)
304
+ - `pages/*.json` (content pages)
305
+ - `news.json` (news feed)
306
+ - `data/{id}/bibliography.json` and `data/{id}/images/*` (render-time resources)
307
+
308
+ The Vite `base` config normalizes trailing slashes automatically, so `BASE_PATH=/vocab` and `BASE_PATH=/vocab/` both work. The value is passed through to `import.meta.env.BASE_URL` which always ends with `/`.
309
+
310
+ **CLI usage:**
311
+
312
+ When using the `concept-browser` CLI from a deployment repo (not the package source):
313
+
314
+ ```bash
315
+ git clone --branch fix/subpath-base-url --depth 1 https://github.com/glossarist/concept-browser.git /tmp/cb
316
+ cd /tmp/cb && npm install --ignore-scripts
317
+ npm install --prefix /tmp/cb sharp 2>/dev/null || true
318
+
319
+ # Build from the deployment repo's working directory
320
+ cd /path/to/deployment-repo
321
+ node /tmp/cb/cli/index.mjs build
322
+ ```
323
+
324
+ The CLI looks for `site-config.yml` in the CWD first, then falls back to the package's own `site-config.yml`.
325
+
294
326
  ### Other hosting platforms
295
327
 
296
328
  The build produces static files in `dist/` with an SPA `404.html` fallback. Deploy `dist/` to any static host:
package/cli/index.mjs CHANGED
@@ -141,6 +141,10 @@ Environment:
141
141
 
142
142
  // Pass favicon tags to Vite via env
143
143
  if (faviconHtml) {
144
+ const basePath = process.env.BASE_PATH?.replace(/\/+$/, '') || '';
145
+ if (basePath) {
146
+ faviconHtml = faviconHtml.replace(/(href|content)="\/([^"]+)"/g, `$1="${basePath}/$2"`);
147
+ }
144
148
  process.env.FAVICON_HTML = faviconHtml;
145
149
  }
146
150
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glossarist/concept-browser",
3
- "version": "0.4.18",
3
+ "version": "0.5.1",
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": {
@@ -1063,10 +1063,11 @@ const processedPages = processPages(config);
1063
1063
  // Generate site-config.json from site config
1064
1064
  const siteBranding = { ...config.branding };
1065
1065
  // Rewrite logo paths to destination filenames and strip build-time fields
1066
+ const basePathPrefix = process.env.BASE_PATH?.replace(/\/+$/, '') || '';
1066
1067
  for (const key of ['logo', 'footerLogo']) {
1067
1068
  const suffix = key === 'logo' ? 'logo.svg' : 'footer-logo.svg';
1068
1069
  if (siteBranding[key]) {
1069
- siteBranding[key] = { ...siteBranding[key], path: `/logos/${config.id}-${suffix}` };
1070
+ siteBranding[key] = { ...siteBranding[key], path: `${basePathPrefix}/logos/${config.id}-${suffix}` };
1070
1071
  delete siteBranding[key].localPath;
1071
1072
  delete siteBranding[key].remoteUrl;
1072
1073
  }
@@ -18,9 +18,10 @@ export class AdapterFactory {
18
18
  if (!resp.ok) throw new Error(`Failed to load dataset registry: ${resp.status}`);
19
19
  const registry = (await resp.json()) as DatasetRegistry[];
20
20
 
21
+ const base = import.meta.env.BASE_URL;
21
22
  const adapters: DatasetAdapter[] = [];
22
23
  for (const reg of registry) {
23
- const adapter = new DatasetAdapter(reg.id, `/data/${reg.id}`);
24
+ const adapter = new DatasetAdapter(reg.id, `${base}data/${reg.id}`);
24
25
  this.adapters.set(reg.id, adapter);
25
26
  adapters.push(adapter);
26
27
  }
@@ -45,7 +46,7 @@ export class AdapterFactory {
45
46
  const manifest = await adapter.loadManifest();
46
47
  await adapter.loadIndex();
47
48
 
48
- this.router.registerDataset(registerId, `/data/${registerId}`, manifest);
49
+ this.router.registerDataset(registerId, `${import.meta.env.BASE_URL}data/${registerId}`, manifest);
49
50
 
50
51
  const uriPatterns = [
51
52
  manifest.datasetUri,
@@ -14,7 +14,7 @@ const bibCache = new Map<string, Record<string, BibEntry>>();
14
14
  async function loadBibliography(registerId: string): Promise<Record<string, BibEntry> | null> {
15
15
  if (bibCache.has(registerId)) return bibCache.get(registerId)!;
16
16
  try {
17
- const resp = await fetch(`/data/${registerId}/bibliography.json`);
17
+ const resp = await fetch(`${import.meta.env.BASE_URL}data/${registerId}/bibliography.json`);
18
18
  if (!resp.ok) return null;
19
19
  const data = await resp.json();
20
20
  bibCache.set(registerId, data);
@@ -47,7 +47,7 @@ export function useRenderOptions(registerId: () => string) {
47
47
 
48
48
  const figResolver: FigResolver = (figId) => {
49
49
  const id = registerId();
50
- const imgSrc = `/data/${id}/images/${figId}.png`;
50
+ const imgSrc = `${import.meta.env.BASE_URL}data/${id}/images/${figId}.png`;
51
51
  return `<span class="fig-ref"><a href="${escapeAttr(imgSrc)}" target="_blank" rel="noopener">${escapeAttr(figId)}</a></span>`;
52
52
  };
53
53
 
@@ -94,7 +94,7 @@ function applyBranding(config: RuntimeSiteConfig) {
94
94
  async function loadConfig(): Promise<RuntimeSiteConfig | null> {
95
95
  if (loaded.value) return siteConfig.value;
96
96
  try {
97
- const resp = await fetch('/site-config.json');
97
+ const resp = await fetch(`${import.meta.env.BASE_URL}site-config.json`);
98
98
  if (resp.ok) {
99
99
  siteConfig.value = await resp.json();
100
100
  if (siteConfig.value) applyBranding(siteConfig.value);
@@ -52,7 +52,7 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
52
52
  loading.value = true;
53
53
  error.value = null;
54
54
  try {
55
- const adapters = await factory.discoverDatasets('/datasets.json');
55
+ const adapters = await factory.discoverDatasets(`${import.meta.env.BASE_URL}datasets.json`);
56
56
  for (const adapter of adapters) {
57
57
  datasets.value.set(adapter.registerId, adapter);
58
58
  if (adapter.manifest) {
@@ -23,7 +23,7 @@ const activeLoading = ref(false);
23
23
 
24
24
  onMounted(async () => {
25
25
  try {
26
- const resp = await fetch('/news.json');
26
+ const resp = await fetch(`${import.meta.env.BASE_URL}news.json`);
27
27
  if (resp.ok) posts.value = await resp.json();
28
28
  } catch (e: any) {
29
29
  error.value = e.message;
@@ -23,9 +23,10 @@ onMounted(async () => {
23
23
  const page = pageName.value;
24
24
  const dsId = registerId.value;
25
25
 
26
+ const base = import.meta.env.BASE_URL;
26
27
  const urls = dsId
27
- ? [`/pages/${dsId}-${page}.json`, `/pages/${page}.json`]
28
- : [`/pages/${page}.json`];
28
+ ? [`${base}pages/${dsId}-${page}.json`, `${base}pages/${page}.json`]
29
+ : [`${base}pages/${page}.json`];
29
30
 
30
31
  for (const url of urls) {
31
32
  try {