@koehler8/cms 1.3.1 → 1.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,82 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.1
4
+
5
+ ### Fixed
6
+
7
+ - **The embedded config payload is now actually used on locale-prefixed routes.**
8
+ vite-ssg runs the setup fn *before* `app.use(router)`, so `router.currentRoute`
9
+ is still `START_LOCATION` (`'/'`, `params: {}`) there — identically on every
10
+ page. During SSG that was already handled, because vite-ssg hands the route
11
+ over on `ctx.routePath`. On the **client** it calls the setup fn with no
12
+ argument at all, so there was nothing to resolve and the code fell through to
13
+ `START_LOCATION`.
14
+
15
+ `loadSiteConfig` derives its cache key from `params.locale`, so the key came
16
+ out `'default'` on every client page while the SSG had stamped the real locale
17
+ on `siteConfigLocale`. On a locale-prefixed route the two never matched, so
18
+ `primeConfigSync` was skipped, `hydrateFromSyncCache` missed, and the client
19
+ discarded the payload it had just been served and awaited a full config load
20
+ **inside the setup fn, on the critical path, before mount** — the exact
21
+ regression the priming machinery exists to prevent.
22
+
23
+ Measured on a multi-locale site, same machine and build, only this patch
24
+ differing. First config chunk on a `/th/` piece page: **44 ms before**
25
+ (DOMContentLoaded 34 ms — on the critical path), **1364 ms after** (deferred
26
+ to `requestIdleCallback`, as designed). The load is *deferred, not removed*:
27
+ `trimConfigToPage` stamps `pagesPartial` on every page, so the idle reload
28
+ still runs.
29
+
30
+ ⚠️ A cross-environment comparison hides this completely. A production bundle
31
+ that needs ~1.4 s to download and execute shows its config chunks at ~1607 ms,
32
+ which *looks* deferred when it is in fact immediate. Only a same-environment
33
+ A/B separates the two.
34
+
35
+ **No action needed in a site.** Single-locale sites already stamped `default`
36
+ and primed correctly, so their behaviour is unchanged; multi-locale sites gain
37
+ the deferral.
38
+
39
+ ## 1.4.0
40
+
41
+ ### Changed
42
+
43
+ - **The bundled Pinia is now 4.x (was 3.x).** `createPinia()` runs Pinia 4.0.3
44
+ and that is the instance handed to every extension through
45
+ `runExtensionSetups({ app, router, pinia, ... })`.
46
+
47
+ **If you maintain an extension that declares `peerDependencies.pinia`, widen
48
+ it to admit `^4`** — for example `"pinia": "^3.0.0 || ^4.0.0"`. An extension
49
+ pinned to `^3` will fail to install alongside this release.
50
+ (`@koehler8/cms-ext-crypto` 1.0.0-beta.4 is in exactly that position; it is
51
+ not installed on any site in the fleet.)
52
+
53
+ Nothing else about extensions changes: the instance is still a single Pinia
54
+ per site — this package deliberately excludes `vue`, `vue-router` and `pinia`
55
+ from Vite's dependency pre-bundling so a linked extension cannot end up with
56
+ a second copy — and `vue-router` 5.3.1 already accepts `pinia@4`
57
+ (`^3.0.4 || ^4.0.2`).
58
+
59
+ Two consequences worth knowing, neither of which needs action in a site:
60
+
61
+ - Pinia 4 makes `@vue/devtools-api` a **required peer** at `^8.1.5`, where
62
+ Pinia 3 carried it as a dependency. npm installs it automatically; on a
63
+ consumer site the resolved version moves 7.x → 8.x, which also lets
64
+ `vue-router`'s own nested copies collapse into the hoisted one. In this
65
+ repo that made the lockfile *smaller* (318 → 307 entries).
66
+ - Pinia 4 is ESM-only and depends on `nostics`. That package was already
67
+ present in this tree, so no new package enters a site's dependency graph.
68
+
69
+ No framework source change was needed: this package defines no store of its
70
+ own and uses none of the APIs Pinia changed.
71
+
72
+ ### Internal (not shipped)
73
+
74
+ - Dev dependency `vitest` 4.1.11 → 5.0.1 (with `@vitest/coverage-v8`). This
75
+ restores the repo's ability to regenerate its own `package-lock.json` under
76
+ npm 10: on `vitest` 4.1.x a fresh `npm install` died in arborist with
77
+ `Cannot read properties of null (reading 'edgesOut')`, and only the existing
78
+ lockfile kept the repo installable. Neither package ships.
79
+
3
80
  ## 1.3.1
4
81
 
5
82
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koehler8/cms",
3
- "version": "1.3.1",
3
+ "version": "1.4.1",
4
4
  "description": "Vertex CMS — lightweight, config-driven Vue 3 framework with theming, extensions, and SSG support",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -80,7 +80,7 @@
80
80
  "@unhead/vue": "^2.1.15",
81
81
  "ajv": "^8.20.0",
82
82
  "ajv-formats": "^3.0.1",
83
- "pinia": "^3.0.1",
83
+ "pinia": "^4.0.3",
84
84
  "sharp": "^0.35.3"
85
85
  },
86
86
  "optionalDependencies": {
@@ -90,11 +90,11 @@
90
90
  },
91
91
  "devDependencies": {
92
92
  "@vitejs/plugin-vue": "^6.0.8",
93
- "@vitest/coverage-v8": "^4.1.11",
94
- "@vue/test-utils": "^2.4.10",
93
+ "@vitest/coverage-v8": "^5.0.1",
94
+ "@vue/test-utils": "^2.5.1",
95
95
  "happy-dom": "^20.9.0",
96
96
  "vite": "^8.3.0",
97
- "vitest": "^4.1.11",
97
+ "vitest": "^5.0.1",
98
98
  "vue": "^3.5.43",
99
99
  "vue-router": "^5.3.1"
100
100
  },
package/src/main.js CHANGED
@@ -8,6 +8,7 @@ import { routes, resolveHistory, applyRouterGuards } from './router/index.js';
8
8
  import { shouldEnableAnalytics, scheduleAnalyticsLoad, configureAnalyticsConsentMode } from './utils/cookieConsent.js';
9
9
  import { loadConfigData, primeConfigSync, trimConfigToPage, resolvePageIdForRoute } from './utils/loadConfig.js';
10
10
  import { applyInitialStateTrims } from './utils/initialStateTrim.js';
11
+ import { currentRouteHref } from './utils/currentRoute.js';
11
12
  import { persistAttributionFromLocation } from './utils/trackingContext.js';
12
13
  import { applyThemeVariables } from './themes/themeManager.js';
13
14
  import { setActiveThemeKey } from './utils/themeColors.js';
@@ -136,19 +137,22 @@ export function createCmsApp() {
136
137
  };
137
138
 
138
139
  const extractCurrentRoute = () => {
139
- // During SSG, vite-ssg passes the route being pre-rendered as a string
140
- // on `ctx.routePath` and only pushes the router AFTER this setup fn runs
141
- // — so `router.currentRoute` is still vue-router's START_LOCATION ('/')
142
- // here, identically on every page. Resolve `routePath` through the router
143
- // (a pure match, no navigation) to recover the real path + `params.locale`.
144
- // Without this, `resolveCurrentPageId` sees '/' for every route, so the
145
- // payload trim targets the wrong page — or, when '/' is ambiguous, none.
146
- const routePath = ctx?.routePath;
147
- if (typeof routePath === 'string' && routePath) {
140
+ // vite-ssg runs this setup fn BEFORE `app.use(router)`, so
141
+ // `router.currentRoute` is still vue-router's START_LOCATION ('/') here,
142
+ // identically on every page — during SSG *and* on the client. The href
143
+ // that IS authoritative differs per half (ctx.routePath vs the window's
144
+ // own location); currentRouteHref picks it, with the full account of why
145
+ // the client half is not optional. Resolve it through the router — a pure
146
+ // match, no navigation — to recover the real path + `params.locale`.
147
+ const href = currentRouteHref(
148
+ ctx,
149
+ typeof window !== 'undefined' ? window.location : null,
150
+ );
151
+ if (href) {
148
152
  try {
149
- return router.resolve(routePath);
153
+ return router.resolve(href);
150
154
  } catch {
151
- return { path: routePath, params: {} };
155
+ return { path: href, params: {} };
152
156
  }
153
157
  }
154
158
  if (ctx?.route) {
@@ -0,0 +1,49 @@
1
+ // Which href the app should resolve through the router to learn the route it is
2
+ // actually on.
3
+ //
4
+ // Both halves of the lifecycle are blind to this in the same way, for the same
5
+ // reason: vite-ssg runs the setup fn BEFORE `app.use(router)`, so
6
+ // `router.currentRoute` is still vue-router's START_LOCATION ('/', `params: {}`)
7
+ // — identically on every page.
8
+ //
9
+ // - During SSG, vite-ssg hands the route being pre-rendered over as a string on
10
+ // `ctx.routePath`, so that is authoritative.
11
+ // - On the CLIENT it calls the setup fn with no argument at all: no
12
+ // `ctx.routePath`, no `ctx.route`. The only authoritative source left is the
13
+ // window's own location.
14
+ //
15
+ // Falling through to START_LOCATION on the client is not a cosmetic miss. The
16
+ // config cache key is derived from `params.locale`, so it came out 'default' on
17
+ // every page while the SSG had stamped the real locale on `siteConfigLocale`.
18
+ // The two never matched on a locale-prefixed route, `primeConfigSync` was
19
+ // skipped, and the client discarded the embedded payload and re-fetched the
20
+ // whole config tree on the critical path — the exact regression the priming
21
+ // machinery exists to prevent. Measured on sparkbang.com/th/<piece>/ before the
22
+ // fix: 151 JS requests / 405 KB, 112 of them per-piece config chunks, while the
23
+ // 17 KB embedded payload was parsed and thrown away.
24
+ //
25
+ // Pure and dependency-free so the fall-through can be pinned by a unit test;
26
+ // `main.js` owns the router.resolve() call itself (a pure match, no navigation).
27
+ //
28
+ // Returns null when the caller should use `ctx.route` as-is, or when there is
29
+ // nothing authoritative to resolve and START_LOCATION is genuinely all there is.
30
+
31
+ /**
32
+ * @param {{routePath?: unknown, route?: unknown}|null|undefined} ctx
33
+ * The vite-ssg setup context. `routePath` is set during SSG only.
34
+ * @param {{pathname?: unknown, search?: unknown}|null|undefined} location
35
+ * `window.location` on the client; omit/null on the server.
36
+ * @returns {string|null} the href to resolve, or null to fall back.
37
+ */
38
+ export function currentRouteHref(ctx, location) {
39
+ const routePath = ctx?.routePath;
40
+ if (typeof routePath === 'string' && routePath) return routePath;
41
+ // A context that already carries a resolved route needs no href at all.
42
+ if (ctx?.route) return null;
43
+ const pathname = location?.pathname;
44
+ if (typeof pathname === 'string' && pathname) {
45
+ const search = typeof location?.search === 'string' ? location.search : '';
46
+ return `${pathname}${search}`;
47
+ }
48
+ return null;
49
+ }
@@ -1 +1 @@
1
- 20.19.0
1
+ 22.23.2
@@ -89,14 +89,14 @@ Edit `themes/<slug>/theme.config.js`. The CMS regenerates `virtual:cms-theme-var
89
89
 
90
90
  ## Lockfile and npm version — read before bumping any dep
91
91
 
92
- This project pins **Node 20.19 / npm 10.8** to match AWS Amplify exactly. Local installs with a different npm version produce subtly different lockfiles that look fine locally but break CI.
92
+ This project pins **Node 22.23.2 / npm 10.9** to match AWS Amplify exactly (never 24 — it ships npm 11). Local installs with a different npm version produce subtly different lockfiles that look fine locally but break CI.
93
93
 
94
94
  Two failure modes seen on sister sites:
95
95
  1. `Cannot find module '@rolldown/binding-linux-x64-gnu'` — npm 11 regen stripped optional-dep entries Amplify needs.
96
96
  2. `npm ci ... Missing: @types/react@..., @noble/hashes@... from lock file` — npm 11 omitted entries npm 10 expects.
97
97
 
98
98
  Rules:
99
- - **Always run `nvm use` before `npm install`**. `.nvmrc` pins 20.19.0 (which ships npm 10.8.x).
99
+ - **Always run `nvm use` before `npm install`**. `.nvmrc` pins 22.23.2 (which ships npm 10.9.x).
100
100
  - **Prefer targeted bumps**: `npm install <pkg>@<ver> --no-audit --no-fund`. **Never** `rm package-lock.json && npm install` unless you're on the pinned npm version.
101
101
  - **The build uses `npm install`**, not `npm ci` (see `amplify.yml`) for drift tolerance.
102
102
 
@@ -7,7 +7,7 @@ For architecture, conventions, and a navigation guide for "where things go" (con
7
7
  ## Local development
8
8
 
9
9
  ```bash
10
- nvm use # Node 20.19.0 per .nvmrc
10
+ nvm use # Node 22.23.2 per .nvmrc
11
11
  cp .env.example .env
12
12
  npm install
13
13
  npm run dev
@@ -3,7 +3,7 @@ frontend:
3
3
  phases:
4
4
  preBuild:
5
5
  commands:
6
- - nvm install 20.19.0 && nvm use 20.19.0
6
+ - nvm install 22.23.2 && nvm use 22.23.2
7
7
  # Use `npm install` (not `npm ci`) so the build tolerates
8
8
  # lockfile/npm-version drift. See CLAUDE.md → "Lockfile and
9
9
  # npm version" for the rationale.