@moku-labs/web 1.13.1 → 1.14.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/dist/index.mjs CHANGED
@@ -1071,6 +1071,38 @@ function dynamicSegmentCount(pattern) {
1071
1071
  return count;
1072
1072
  }
1073
1073
  /**
1074
+ * Whether a route is rendered ENTIRELY on the client in `spa` mode: a dynamic route
1075
+ * (≥1 non-lang param) that declares no build-time `.generate()` enumerator, so its
1076
+ * concrete param paths are unknown until runtime.
1077
+ *
1078
+ * The build SKIPS such a route — emitting a static page for it would write one
1079
+ * param-less shell whose path (`/b/{id}`) matches no file (a 404) and carries no
1080
+ * param for the islands to read. Instead the SPA client-renders it from the URL on
1081
+ * boot and on navigation. Build and client share this ONE predicate (the same way
1082
+ * `dynamicSegmentCount`/`bySpecificity` are shared) so the two sides can never
1083
+ * disagree about which routes are pre-rendered vs. client-only.
1084
+ *
1085
+ * Static routes (`/`) and dynamic routes WITH `.generate()` are pre-rendered as
1086
+ * usual and so are NOT client-only. In `ssg`/`hybrid` mode nothing is client-only
1087
+ * (the build pre-renders every route), so this is always `false` outside `spa`.
1088
+ *
1089
+ * @param mode - The global render mode (`router.mode()`).
1090
+ * @param route - The route to test (only its `pattern` + `.generate()` presence are read).
1091
+ * @param route.pattern - The route's URL pattern string.
1092
+ * @param route._handlers - The route's handler bag.
1093
+ * @param route._handlers.generate - The build-only static-paths enumerator, if any (presence only).
1094
+ * @returns `true` when the route is client-only (spa mode, dynamic, no `.generate()`).
1095
+ * @example
1096
+ * ```ts
1097
+ * isClientOnlyRoute("spa", { pattern: "/b/{id}", _handlers: {} }); // true
1098
+ * isClientOnlyRoute("spa", { pattern: "/", _handlers: {} }); // false (static)
1099
+ * isClientOnlyRoute("hybrid", { pattern: "/b/{id}", _handlers: {} }); // false (not spa)
1100
+ * ```
1101
+ */
1102
+ function isClientOnlyRoute(mode, route) {
1103
+ return mode === "spa" && route._handlers.generate === void 0 && dynamicSegmentCount(route.pattern) > 0;
1104
+ }
1105
+ /**
1074
1106
  * Comparator that orders two routes most-specific-first (fewest dynamic segments
1075
1107
  * first). Equal specificity yields `0` so a stable sort preserves declaration
1076
1108
  * order — the exact ordering the compiled matcher table uses, guaranteeing
@@ -4515,16 +4547,23 @@ function resolveEntry(byPattern, definition) {
4515
4547
  * generate context is the spec `{ locale, require, has }`, so a `.generate()` handler
4516
4548
  * pulls sibling APIs the spec way.
4517
4549
  *
4550
+ * In `spa` mode a client-only route (dynamic, no `.generate()`) is SKIPPED entirely
4551
+ * (`[]`) — it is rendered on the client from the URL, so emitting a static param-less
4552
+ * shell here would only write a file at the wrong path (a 404 for any real param path)
4553
+ * carrying no param. See {@link isClientOnlyRoute}.
4554
+ *
4518
4555
  * @param definition - The route definition from the manifest.
4519
4556
  * @param locale - The active locale to generate param sets for.
4557
+ * @param mode - The global render mode (`router.mode()`); gates the spa client-only skip.
4520
4558
  * @param ctx - Plugin context (provides `require`/`has` for the generate context).
4521
- * @returns The param sets for this route+locale (`[{}]` when there is no `.generate()`).
4559
+ * @returns The param sets for this route+locale (`[{}]` when there is no `.generate()`; `[]` when client-only).
4522
4560
  * @example
4523
4561
  * ```ts
4524
- * const paramSets = await generateParamSets(def, "en", ctx);
4562
+ * const paramSets = await generateParamSets(def, "en", "hybrid", ctx);
4525
4563
  * ```
4526
4564
  */
4527
- async function generateParameterSets(definition, locale, ctx) {
4565
+ async function generateParameterSets(definition, locale, mode, ctx) {
4566
+ if (isClientOnlyRoute(mode, definition)) return [];
4528
4567
  const generateContext = {
4529
4568
  locale,
4530
4569
  require: ctx.require,
@@ -4548,21 +4587,22 @@ async function generateParameterSets(definition, locale, ctx) {
4548
4587
  * @param locales - Active locale codes from i18n.
4549
4588
  * @param defaultLocale - The i18n default locale (kept when locales collapse to one file).
4550
4589
  * @param byPattern - Pattern→compiled-`TypedRoute` map (see {@link makeEntryMap}).
4590
+ * @param mode - The global render mode (`router.mode()`); gates the spa client-only skip.
4551
4591
  * @param ctx - Plugin context (provides `require`/`has` for the generate context).
4552
4592
  * @returns The flattened, file-deduplicated list of page instances for this route.
4553
4593
  * @example
4554
4594
  * ```ts
4555
- * await expandRoute(def, ["en"], "en", byPattern, ctx);
4595
+ * await expandRoute(def, ["en"], "en", byPattern, "hybrid", ctx);
4556
4596
  * ```
4557
4597
  */
4558
- async function expandRoute(definition, locales, defaultLocale, byPattern, ctx) {
4598
+ async function expandRoute(definition, locales, defaultLocale, byPattern, mode, ctx) {
4559
4599
  const entry = resolveEntry(byPattern, definition);
4560
4600
  const { name } = entry;
4561
4601
  const orderedLocales = [defaultLocale, ...locales.filter((locale) => locale !== defaultLocale)];
4562
4602
  const instances = [];
4563
4603
  const claimedFiles = /* @__PURE__ */ new Set();
4564
4604
  for (const locale of orderedLocales) {
4565
- const parameterSets = await generateParameterSets(definition, locale, ctx);
4605
+ const parameterSets = await generateParameterSets(definition, locale, mode, ctx);
4566
4606
  for (const raw of parameterSets) {
4567
4607
  const params = raw ?? {};
4568
4608
  const file = entry.toFile(params);
@@ -4685,8 +4725,8 @@ function composeHeadHtml(ctx, instance, url, routeContext, data) {
4685
4725
  * layout is NOT re-applied on navigation), then serialize with
4686
4726
  * preact-render-to-string. Returns `""` when the route has no `.render()`.
4687
4727
  *
4688
- * @param definition - The route definition (provides `.render()`/`.layout()`/`._meta`).
4689
- * @param routeContext - The route context (params/data/locale/url) — extended with `meta` for the layout.
4728
+ * @param definition - The route definition (provides `.render()`/`.layout()`).
4729
+ * @param routeContext - The route context (params/data/locale/meta/url); `meta` flows to the layout.
4690
4730
  * @returns The SSR-rendered body HTML, or `""` when the route has no `.render()`.
4691
4731
  * @example
4692
4732
  * ```ts
@@ -4696,11 +4736,7 @@ function composeHeadHtml(ctx, instance, url, routeContext, data) {
4696
4736
  function renderBody(definition, routeContext) {
4697
4737
  const vnode = definition._handlers.render?.(routeContext);
4698
4738
  if (!vnode) return "";
4699
- const layoutContext = {
4700
- ...routeContext,
4701
- meta: definition._meta
4702
- };
4703
- return renderToString(definition._handlers.layout ? definition._handlers.layout(layoutContext, vnode) : vnode);
4739
+ return renderToString(definition._handlers.layout ? definition._handlers.layout(routeContext, vnode) : vnode);
4704
4740
  }
4705
4741
  /**
4706
4742
  * Hash a page's render inputs (its loaded data) for the render cache. `null` when the
@@ -4834,6 +4870,7 @@ async function renderInstance(ctx, instance, shell, reuse) {
4834
4870
  params,
4835
4871
  data,
4836
4872
  locale,
4873
+ meta: definition._meta,
4837
4874
  url: (routeName, routeParams = {}) => router.toUrl(routeName, routeParams)
4838
4875
  };
4839
4876
  const parts = {
@@ -4896,15 +4933,16 @@ async function prepareShell(ctx) {
4896
4933
  * @param locales - Active locale codes from i18n.
4897
4934
  * @param defaultLocale - The i18n default locale (kept when a route's locales collapse).
4898
4935
  * @param byPattern - Pattern→compiled-`TypedRoute` map (see {@link makeEntryMap}).
4936
+ * @param mode - The global render mode (`router.mode()`); gates the spa client-only skip.
4899
4937
  * @param ctx - Plugin context (provides `require`/`has` for generate contexts).
4900
4938
  * @returns The flattened list of page instances to render.
4901
4939
  * @example
4902
4940
  * ```ts
4903
- * const instances = await expandAllInstances(manifest, ["en"], "en", byPattern, ctx);
4941
+ * const instances = await expandAllInstances(manifest, ["en"], "en", byPattern, "hybrid", ctx);
4904
4942
  * ```
4905
4943
  */
4906
- async function expandAllInstances(manifest, locales, defaultLocale, byPattern, ctx) {
4907
- return (await Promise.all(manifest.map((definition) => expandRoute(definition, locales, defaultLocale, byPattern, ctx)))).flat();
4944
+ async function expandAllInstances(manifest, locales, defaultLocale, byPattern, mode, ctx) {
4945
+ return (await Promise.all(manifest.map((definition) => expandRoute(definition, locales, defaultLocale, byPattern, mode, ctx)))).flat();
4908
4946
  }
4909
4947
  /**
4910
4948
  * Persist per-page client-data sidecars when the app opts into client navigation
@@ -5014,14 +5052,15 @@ async function renderInBatches(items, batchSize, worker) {
5014
5052
  async function renderPages(ctx, options) {
5015
5053
  const reuse = options?.reuse === true;
5016
5054
  const router = ctx.require(routerPlugin);
5055
+ const mode = router.mode();
5017
5056
  const manifest = router.manifest();
5018
5057
  ctx.state.manifest = [...manifest];
5019
5058
  const locales = ctx.require(i18nPlugin).locales();
5020
5059
  const byPattern = makeEntryMap(router);
5021
5060
  if (!reuse) ctx.state.renderCache.clear();
5022
5061
  const shell = await prepareShell(ctx);
5023
- const rendered = (await renderInBatches(await expandAllInstances(manifest, locales, shell.defaultLocale, byPattern, ctx), reuse ? INCREMENTAL_BATCH_SIZE : RENDER_BATCH_SIZE, (instance) => renderInstance(ctx, instance, shell, reuse))).flat();
5024
- await writeDataSidecars(ctx, rendered, router.mode());
5062
+ const rendered = (await renderInBatches(await expandAllInstances(manifest, locales, shell.defaultLocale, byPattern, mode, ctx), reuse ? INCREMENTAL_BATCH_SIZE : RENDER_BATCH_SIZE, (instance) => renderInstance(ctx, instance, shell, reuse))).flat();
5063
+ await writeDataSidecars(ctx, rendered, mode);
5025
5064
  ctx.log.debug("build:pages", { count: rendered.length });
5026
5065
  return {
5027
5066
  pageCount: rendered.length,
@@ -9043,6 +9082,23 @@ const ERROR_PREFIX$2 = "[web]";
9043
9082
  /** The set of legal hook names, frozen for O(1) membership checks. */
9044
9083
  const HOOK_NAME_SET = new Set(COMPONENT_HOOK_NAMES);
9045
9084
  /**
9085
+ * No-op link builder for the {@link EMPTY_ROUTE} slice (used when no route matched).
9086
+ *
9087
+ * @returns An empty string.
9088
+ * @example
9089
+ * const href = noUrl();
9090
+ */
9091
+ function noUrl() {
9092
+ return "";
9093
+ }
9094
+ /** Empty route slice — used for mounts with no matched route (headless, tests, public `scan()`). */
9095
+ const EMPTY_ROUTE = {
9096
+ params: {},
9097
+ meta: {},
9098
+ locale: "",
9099
+ url: noUrl
9100
+ };
9101
+ /**
9046
9102
  * Validate a single hook entry: its key must be a known hook name and its value
9047
9103
  * must be a function. Throws fail-fast on the first violation.
9048
9104
  *
@@ -9130,18 +9186,25 @@ function runHook(instance, hook, ctx) {
9130
9186
  instance.def.hooks[hook]?.(ctx);
9131
9187
  }
9132
9188
  /**
9133
- * Builds the component context handed to a hook (the bound element + page data).
9189
+ * Builds the component context handed to a hook: the bound element + page data, merged
9190
+ * with the matched route's slice (params/meta/locale/url). Defaults to {@link EMPTY_ROUTE}
9191
+ * when no route is supplied (headless, tests, public `scan()`).
9134
9192
  *
9135
9193
  * @param element - The element the instance is bound to.
9136
9194
  * @param data - The current page data payload.
9195
+ * @param route - The matched-route slice for the current URL.
9137
9196
  * @returns The hook context.
9138
9197
  * @example
9139
- * const ctx = makeContext(element, data);
9198
+ * const ctx = makeContext(element, data, route);
9140
9199
  */
9141
- function makeContext(element, data) {
9200
+ function makeContext(element, data, route = EMPTY_ROUTE) {
9142
9201
  return {
9143
9202
  el: element,
9144
- data
9203
+ data,
9204
+ params: route.params,
9205
+ meta: route.meta,
9206
+ locale: route.locale,
9207
+ url: route.url
9145
9208
  };
9146
9209
  }
9147
9210
  /**
@@ -9155,17 +9218,18 @@ function makeContext(element, data) {
9155
9218
  * @param swapArea - The swap-region element, or null when none was found.
9156
9219
  * @param data - The current page data payload.
9157
9220
  * @param element - The candidate element carrying a `data-component` attribute.
9221
+ * @param route - The matched-route slice for the current URL (params/meta/locale/url).
9158
9222
  * @example
9159
- * mountElement(state, emit, swapArea, data, element);
9223
+ * mountElement(state, emit, swapArea, data, element, route);
9160
9224
  */
9161
- function mountElement(state, emit, swapArea, data, element) {
9225
+ function mountElement(state, emit, swapArea, data, element, route = EMPTY_ROUTE) {
9162
9226
  if (state.instances.has(element)) return;
9163
9227
  const name = element.dataset.component;
9164
9228
  if (!name) return;
9165
9229
  const definition = state.registeredComponents.get(name);
9166
9230
  if (!definition) return;
9167
9231
  const instance = createInstance(definition, element, swapArea ? !swapArea.contains(element) : true);
9168
- const ctx = makeContext(element, data);
9232
+ const ctx = makeContext(element, data, route);
9169
9233
  runHook(instance, "onCreate", ctx);
9170
9234
  runHook(instance, "onMount", ctx);
9171
9235
  state.instances.set(element, instance);
@@ -9183,14 +9247,15 @@ function mountElement(state, emit, swapArea, data, element) {
9183
9247
  * @param state - The plugin state (registeredComponents + instances).
9184
9248
  * @param emit - The event emitter for spa:component-mount.
9185
9249
  * @param swapSelector - CSS selector bounding page-specific components.
9250
+ * @param route - The matched-route slice for the current URL (params/meta/locale/url).
9186
9251
  * @example
9187
- * scanAndMount(state, emit, "main > section");
9252
+ * scanAndMount(state, emit, "main > section", route);
9188
9253
  */
9189
- function scanAndMount(state, emit, swapSelector) {
9254
+ function scanAndMount(state, emit, swapSelector, route = EMPTY_ROUTE) {
9190
9255
  if (typeof document === "undefined") return;
9191
9256
  const swapArea = document.querySelector(swapSelector);
9192
9257
  const data = extractPageData(document);
9193
- for (const element of document.querySelectorAll("[data-component]")) mountElement(state, emit, swapArea, data, element);
9258
+ for (const element of document.querySelectorAll("[data-component]")) mountElement(state, emit, swapArea, data, element, route);
9194
9259
  }
9195
9260
  /**
9196
9261
  * Unmounts page-specific instances inside the swap region (runs `onUnMount`
@@ -9256,12 +9321,13 @@ function notifyNavStart(state) {
9256
9321
  * instances were already destroyed and re-created by the swap).
9257
9322
  *
9258
9323
  * @param state - The plugin state holding live instances.
9324
+ * @param route - The matched-route slice for the destination URL (params/meta/locale/url).
9259
9325
  * @example
9260
- * notifyNavEnd(state);
9326
+ * notifyNavEnd(state, route);
9261
9327
  */
9262
- function notifyNavEnd(state) {
9328
+ function notifyNavEnd(state, route = EMPTY_ROUTE) {
9263
9329
  const data = typeof document === "undefined" ? {} : extractPageData(document);
9264
- for (const [element, instance] of state.instances) if (instance.persistent) runHook(instance, "onNavEnd", makeContext(element, data));
9330
+ for (const [element, instance] of state.instances) if (instance.persistent) runHook(instance, "onNavEnd", makeContext(element, data, route));
9265
9331
  }
9266
9332
  //#endregion
9267
9333
  //#region src/plugins/spa/head.ts
@@ -9904,6 +9970,26 @@ function createSpaKernel(state, config, emit, deps) {
9904
9970
  });
9905
9971
  };
9906
9972
  /**
9973
+ * Build the matched-route slice (params/meta/locale/url) for the component context at `path`,
9974
+ * so islands read their route's params/meta directly. An unmatched path yields an empty slice.
9975
+ *
9976
+ * @param path - The URL (pathname + search) to match.
9977
+ * @returns The route slice for the matched route.
9978
+ * @example
9979
+ * scanAndMount(state, emit, resolved.swapSelector, componentRouteContext(pathname));
9980
+ */
9981
+ const componentRouteContext = (path) => {
9982
+ const matchPath = path.split("?")[0] ?? path;
9983
+ const hit = deps.router.match(matchPath);
9984
+ const locale = hit?.params.lang ?? (typeof document === "undefined" ? "" : document.documentElement.lang) ?? "";
9985
+ return {
9986
+ params: hit?.params ?? {},
9987
+ meta: hit?.route._meta ?? {},
9988
+ locale,
9989
+ url: (name, params = {}) => deps.router.toUrl(name, params)
9990
+ };
9991
+ };
9992
+ /**
9907
9993
  * Process one navigation: head-sync, unmount, swap, re-mount, emit navigated.
9908
9994
  * When the region cannot be swapped (either document lacks the swap selector)
9909
9995
  * the SPA nav cannot complete — the head is already synced and the islands torn
@@ -9921,8 +10007,9 @@ function createSpaKernel(state, config, emit, deps) {
9921
10007
  syncHead(deps.head, doc);
9922
10008
  unmountPageSpecific(state, emit);
9923
10009
  if (!swapRegion(doc, resolved.swapSelector, resolved.viewTransitions, () => {
9924
- scanAndMount(state, emit, resolved.swapSelector);
9925
- notifyNavEnd(state);
10010
+ const routeSlice = componentRouteContext(pathname);
10011
+ scanAndMount(state, emit, resolved.swapSelector, routeSlice);
10012
+ notifyNavEnd(state, routeSlice);
9926
10013
  }, applyPendingScroll)) {
9927
10014
  handleError();
9928
10015
  location.href = pathname;
@@ -9976,17 +10063,22 @@ function createSpaKernel(state, config, emit, deps) {
9976
10063
  * const resolved = await resolveDataRender("/en/world/");
9977
10064
  */
9978
10065
  const resolveDataRender = async (pathname) => {
9979
- if (!deps.dataAt) return false;
9980
10066
  const matchPath = pathname.split("?")[0] ?? pathname;
9981
10067
  const hit = deps.router.match(matchPath);
9982
10068
  if (!hit?.route._handlers.render) return false;
9983
- const data = await deps.dataAt(pathname);
9984
- if (data === null) return false;
10069
+ let data = {};
10070
+ if (!isClientOnlyRoute(deps.router.mode(), hit.route)) {
10071
+ if (!deps.dataAt) return false;
10072
+ const persisted = await deps.dataAt(pathname);
10073
+ if (persisted === null) return false;
10074
+ data = persisted;
10075
+ }
9985
10076
  const locale = hit.params.lang ?? document.documentElement.lang ?? "";
9986
10077
  const routeContext = {
9987
10078
  params: hit.params,
9988
10079
  data,
9989
10080
  locale,
10081
+ meta: hit.route._meta,
9990
10082
  url: (routeName, routeParams = {}) => deps.router.toUrl(routeName, routeParams)
9991
10083
  };
9992
10084
  const vnode = hit.route._handlers.render(routeContext);
@@ -10018,6 +10110,7 @@ function createSpaKernel(state, config, emit, deps) {
10018
10110
  if (signal?.aborted) return;
10019
10111
  syncDataHead(deps.head, route, routeContext);
10020
10112
  unmountPageSpecific(state, emit);
10113
+ const routeSlice = componentRouteContext(pathname);
10021
10114
  /**
10022
10115
  * Render the VNode into the region and re-mount its islands in one paint — the
10023
10116
  * swap body handed to `runSwap` (optionally wrapped in a View Transition).
@@ -10029,8 +10122,8 @@ function createSpaKernel(state, config, emit, deps) {
10029
10122
  */
10030
10123
  const renderAndMount = () => {
10031
10124
  renderVNode(vnode, region);
10032
- scanAndMount(state, emit, resolved.swapSelector);
10033
- notifyNavEnd(state);
10125
+ scanAndMount(state, emit, resolved.swapSelector, routeSlice);
10126
+ notifyNavEnd(state, routeSlice);
10034
10127
  };
10035
10128
  runSwap(renderAndMount, resolved.viewTransitions, applyPendingScroll);
10036
10129
  state.currentUrl = pathname;
@@ -10063,6 +10156,30 @@ function createSpaKernel(state, config, emit, deps) {
10063
10156
  }
10064
10157
  };
10065
10158
  /**
10159
+ * Initial-load render for a spa client-only route (dynamic, no `.generate()`): the build emitted
10160
+ * no static HTML for it, so the host served a fallback shell. Client-render the matched route into
10161
+ * the swap region from the URL, then mount its islands — the deep-link / refresh paint. Unlike a
10162
+ * navigation there is nothing to unmount and no `spa:navigated` to emit. If the route cannot be
10163
+ * resolved (defensive — a matched client-only route always resolves), fall back to mounting the
10164
+ * served body so boot still wires up whatever islands the shell does carry.
10165
+ *
10166
+ * @param pathname - The current document path (pathname + search).
10167
+ * @example
10168
+ * await bootRender("/b/abc123");
10169
+ */
10170
+ const bootRender = async (pathname) => {
10171
+ const routeSlice = componentRouteContext(pathname);
10172
+ const resolvedRender = await resolveDataRender(pathname);
10173
+ if (resolvedRender === false) {
10174
+ scanAndMount(state, emit, resolved.swapSelector, routeSlice);
10175
+ return;
10176
+ }
10177
+ const { vnode, region } = resolvedRender;
10178
+ const { renderVNode } = await import("./render-BNe0s7fr.mjs");
10179
+ renderVNode(vnode, region);
10180
+ scanAndMount(state, emit, resolved.swapSelector, routeSlice);
10181
+ };
10182
+ /**
10066
10183
  * Unified navigation: try the client DATA path first (only when the `data`
10067
10184
  * plugin is composed), then fall back to HTML-over-fetch (which itself falls
10068
10185
  * back to a full `location.href` reload). Injected into the router so every
@@ -10107,7 +10224,10 @@ function createSpaKernel(state, config, emit, deps) {
10107
10224
  progress = createProgressBar(resolved.progressBar);
10108
10225
  state.currentUrl = currentLocationUrl();
10109
10226
  state.destroyRouter = attachRouter(handlers, navigate);
10110
- scanAndMount(state, emit, resolved.swapSelector);
10227
+ const matchPath = state.currentUrl.split("?")[0] ?? state.currentUrl;
10228
+ const hit = deps.router.match(matchPath);
10229
+ if (hit?.route._handlers.render && isClientOnlyRoute(deps.router.mode(), hit.route)) bootRender(state.currentUrl);
10230
+ else scanAndMount(state, emit, resolved.swapSelector, componentRouteContext(state.currentUrl));
10111
10231
  state.started = true;
10112
10232
  },
10113
10233
  /**
@@ -10138,7 +10258,7 @@ function createSpaKernel(state, config, emit, deps) {
10138
10258
  * kernel.scan();
10139
10259
  */
10140
10260
  scan() {
10141
- scanAndMount(state, emit, resolved.swapSelector);
10261
+ scanAndMount(state, emit, resolved.swapSelector, componentRouteContext(state.currentUrl));
10142
10262
  },
10143
10263
  /**
10144
10264
  * Tear down router listeners, dispose all instances, reset boot state.
package/package.json CHANGED
@@ -126,5 +126,5 @@
126
126
  "test:build-e2e": "bun test src/plugins/build/__tests__/e2e/",
127
127
  "test:coverage": "vitest run --project unit --project integration --coverage"
128
128
  },
129
- "version": "1.13.1"
129
+ "version": "1.14.0"
130
130
  }