@nextwebwg/htmlkit 1.0.0-alpha.49 → 1.0.0-alpha.51

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/config.d.ts CHANGED
@@ -1,4 +1,9 @@
1
- import type { ApplicationOptions, HtmlKitPlugin } from "./types.js";
1
+ import type { ApplicationOptions, HtmlKitPlugin, RouteLayer } from "./types.js";
2
+ /**
3
+ * The options htmlkit.config.ts (or .js) at root exports by default, loaded through Vite as the CLI
4
+ * loads them; {} without a config file.
5
+ */
6
+ export declare function loadConfig(root?: string): Promise<ApplicationOptions>;
2
7
  export declare class HtmlKitError extends Error {
3
8
  readonly source?: string | undefined;
4
9
  constructor(message: string, source?: string | undefined);
@@ -11,6 +16,11 @@ export declare function configure(options: ApplicationOptions): {
11
16
  outDir: string;
12
17
  prefetch: import("./types.js").PrefetchPolicy;
13
18
  };
19
+ /** Plugins' named layouts, kept apart from the site's: a site's app/layouts/<name>.html wins over them. */
20
+ export declare const pluginLayouts: unique symbol;
21
+ export type PluginOptions = ApplicationOptions & {
22
+ readonly [pluginLayouts]?: Readonly<Record<string, RouteLayer>>;
23
+ };
14
24
  /** Merge each plugin's config() once; options already merged pass through, so nested entry points don't repeat hooks. */
15
25
  export declare function withPlugins(options: ApplicationOptions): Promise<ApplicationOptions>;
16
26
  /** Run each plugin's hook in order; a failure names the plugin and the hook. */
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEpE,qBAAa,YAAa,SAAQ,KAAK;IACR,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IAArD,YAAY,OAAO,EAAE,MAAM,EAAW,MAAM,CAAC,EAAE,MAAM,YAAA,EAGpD;CACF;AACD,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAG7D;AACD,wBAAgB,SAAS,CAAC,OAAO,EAAE,kBAAkB;;;;;;EAkBpD;AAGD,yHAAyH;AACzH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAc1F;AAED,gFAAgF;AAChF,wBAAsB,OAAO,CAAC,IAAI,SAAS,YAAY,GAAG,UAAU,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,IAAI,EAC3G,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CASzE;AAED,2FAA2F;AAC3F,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM;;;IAErC;AAED,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAE9F"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEhF;;;GAGG;AACH,wBAAsB,UAAU,CAAC,IAAI,SAAgB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAWlF;AAED,qBAAa,YAAa,SAAQ,KAAK;IACR,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IAArD,YAAY,OAAO,EAAE,MAAM,EAAW,MAAM,CAAC,EAAE,MAAM,YAAA,EAGpD;CACF;AACD,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAG7D;AACD,wBAAgB,SAAS,CAAC,OAAO,EAAE,kBAAkB;;;;;;EAkBpD;AAED,2GAA2G;AAC3G,eAAO,MAAM,aAAa,eAAkC,CAAC;AAC7D,MAAM,MAAM,aAAa,GAAG,kBAAkB,GAAG;IAAE,QAAQ,CAAC,CAAC,aAAa,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA;CAAE,CAAC;AAGrH,yHAAyH;AACzH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAgB1F;AAED,gFAAgF;AAChF,wBAAsB,OAAO,CAAC,IAAI,SAAS,YAAY,GAAG,UAAU,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,IAAI,EAC3G,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CASzE;AAED,2FAA2F;AAC3F,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM;;;IAErC;AAED,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAE9F"}
package/dist/config.js CHANGED
@@ -1,5 +1,29 @@
1
+ import { existsSync } from "node:fs";
1
2
  import { isAbsolute, relative, resolve, sep } from "node:path";
2
- import { normalizePath } from "vite";
3
+ import { createServer, normalizePath } from "vite";
4
+ /**
5
+ * The options htmlkit.config.ts (or .js) at root exports by default, loaded through Vite as the CLI
6
+ * loads them; {} without a config file.
7
+ */
8
+ export async function loadConfig(root = process.cwd()) {
9
+ const files = ["ts", "js"].map(ext => resolve(root, `htmlkit.config.${ext}`)).filter(existsSync);
10
+ if (files.length > 1)
11
+ throw new HtmlKitError("Choose one htmlkit.config.ts or .js.", resolve(root));
12
+ if (files[0] === undefined)
13
+ return {};
14
+ const loader = await createServer({ root: resolve(root), configFile: false, appType: "custom", logLevel: "silent",
15
+ server: { middlewareMode: true, watch: null, hmr: false }, optimizeDeps: { noDiscovery: true, include: [] } });
16
+ let configured;
17
+ try {
18
+ configured = (await loader.ssrLoadModule(files[0])).default;
19
+ }
20
+ finally {
21
+ await loader.close();
22
+ }
23
+ if (configured === null || typeof configured !== "object")
24
+ throw new HtmlKitError("Configuration must export a default ApplicationOptions object.", files[0]);
25
+ return configured;
26
+ }
3
27
  export class HtmlKitError extends Error {
4
28
  source;
5
29
  constructor(message, source) {
@@ -34,6 +58,8 @@ export function configure(options) {
34
58
  throw new HtmlKitError('prefetch must be "interaction", "visible", or "none".');
35
59
  return { root, base, origin: origin.origin, outDir, prefetch };
36
60
  }
61
+ /** Plugins' named layouts, kept apart from the site's: a site's app/layouts/<name>.html wins over them. */
62
+ export const pluginLayouts = Symbol("htmlkit.pluginLayouts");
37
63
  const pluginsApplied = new WeakSet();
38
64
  /** Merge each plugin's config() once; options already merged pass through, so nested entry points don't repeat hooks. */
39
65
  export async function withPlugins(options) {
@@ -41,11 +67,13 @@ export async function withPlugins(options) {
41
67
  return options;
42
68
  let merged = options;
43
69
  for (const plugin of options.plugins) {
44
- const added = await plugin.config?.(merged) ?? {};
70
+ const { layouts, ...added } = await plugin.config?.(merged) ?? {};
45
71
  // Page folders, stylesheets, and routes add to the application's own; app/pages stays the default folder.
46
72
  merged = { ...merged, ...added, plugins: options.plugins,
47
73
  ...(added.pages === undefined ? {} : { pages: [...merged.pages ?? [{ dir: "app/pages", optional: true }], ...added.pages] }),
48
74
  ...(added.css === undefined ? {} : { css: [...merged.css ?? [], ...added.css] }),
75
+ // An earlier plugin's named layout wins over a later plugin's of the same name.
76
+ ...(layouts === undefined ? {} : { [pluginLayouts]: { ...layouts, ...merged[pluginLayouts] } }),
49
77
  ...(added.routes === undefined ? {} : { routes: [...merged.routes ?? [], ...added.routes] }) };
50
78
  }
51
79
  merged = { ...merged };
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,15 @@ export interface PageAssets {
7
7
  }
8
8
  export declare function escapeHTML(value: string): string;
9
9
  export declare function documentHTML(body: string, head: RenderedHead, assets?: PageAssets): string;
10
+ /**
11
+ * The page at a redirect's old URL, for static hosts without server redirects: a meta refresh and a
12
+ * canonical link to the Location, and a plain link for readers that don't refresh.
13
+ */
14
+ export declare function redirectPage(location: string): {
15
+ head: RenderedHead;
16
+ body: string;
17
+ html: string;
18
+ };
10
19
  /**
11
20
  * A page's payload URL: its route path under the reserved _htmlkit/pages/ tree, which no route or
12
21
  * public file may use, so it never collides with an application's own files.
@@ -1 +1 @@
1
- {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1E,eAAO,MAAM,mBAAmB,EAAE,SAAS,WAAW,EAGrD,CAAC;AACF,iGAAiG;AACjG,MAAM,WAAW,UAAU;IAAG,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE;AACvG,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AACD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,GAAE,UAAwC,GAAG,MAAM,CAavH;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElE;AAYD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU;;;;;;;;;;;cAKjE"}
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1E,eAAO,MAAM,mBAAmB,EAAE,SAAS,WAAW,EAGrD,CAAC;AACF,iGAAiG;AACjG,MAAM,WAAW,UAAU;IAAG,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE;AACvG,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AACD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,GAAE,UAAwC,GAAG,MAAM,CAavH;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM;;;;EAK5C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElE;AAYD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU;;;;;;;;;;;cAKjE"}
package/dist/document.js CHANGED
@@ -18,6 +18,16 @@ export function documentHTML(body, head, assets = { styles: [], modules: [] }) {
18
18
  assets.modules.map(src => `<script type="module" src="${escapeHTML(src)}"></script>`).join("") +
19
19
  `</head><body>${body}</body></html>`;
20
20
  }
21
+ /**
22
+ * The page at a redirect's old URL, for static hosts without server redirects: a meta refresh and a
23
+ * canonical link to the Location, and a plain link for readers that don't refresh.
24
+ */
25
+ export function redirectPage(location) {
26
+ const head = { title: "Redirecting", elements: [...defaultHeadElements,
27
+ { tag: "meta", attributes: { "http-equiv": "refresh", content: `0; url=${location}` } }, { tag: "link", attributes: { rel: "canonical", href: location } }] };
28
+ const body = `<main><p>This page is now at <a href="${escapeHTML(location)}">${escapeHTML(location)}</a>.</p></main>`;
29
+ return { head, body, html: documentHTML(body, head) };
30
+ }
21
31
  /**
22
32
  * A page's payload URL: its route path under the reserved _htmlkit/pages/ tree, which no route or
23
33
  * public file may use, so it never collides with an application's own files.
@@ -1 +1 @@
1
- {"version":3,"file":"document.js","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"document.js","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,9 +2,9 @@ export { createApplication } from "./application.js";
2
2
  export { buildApplication, readManifest } from "./build.js";
3
3
  export { devApplication, previewApplication } from "./serve.js";
4
4
  export { discoverRoutes } from "./routes.js";
5
- export { HtmlKitError } from "./config.js";
5
+ export { HtmlKitError, loadConfig } from "./config.js";
6
6
  import type { ApplicationOptions } from "./types.js";
7
7
  /** Type-check an htmlkit.config.ts file without changing its values. */
8
8
  export declare function defineConfig(options: ApplicationOptions): ApplicationOptions;
9
- export type { Application, ApplicationOptions, ApplicationRoute, ApplicationServer, BrowserDefinition, BuildEndContext, BuildManifest, BuildResult, BuildStartContext, HtmlKitPlugin, PageContext, PageDirectory, HeadElement, LoadContext, LoaderResult, ManifestPage, NavigationItem, NavigationQuery, PagerLinks, PageHead, PrefetchPolicy, RenderedHead, RenderedLayer, RenderedPage, RouteInput, RouteLayer, ServerModule, ServerOptions } from "./types.js";
9
+ export type { Application, ApplicationOptions, ApplicationRoute, ApplicationServer, BrowserDefinition, BuildEndContext, BuildManifest, BuildResult, BuildStartContext, HtmlKitPlugin, PageContext, PageDirectory, HeadElement, LoadContext, LoaderResult, ManifestPage, ManifestRedirect, NavigationItem, NavigationNode, NavigationQuery, PagerLinks, PageHead, PrefetchPolicy, RedirectStatus, RenderedHead, RenderedLayer, RenderedPage, RouteGroup, RouteInput, RouteLayer, RouteParam, ServerModule, ServerOptions } from "./types.js";
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,OAAO,EAAE,kBAAkB,GAAG,kBAAkB,CAAoB;AACjG,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAC9M,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,OAAO,EAAE,kBAAkB,GAAG,kBAAkB,CAAoB;AACjG,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAC9M,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EACxN,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ export { createApplication } from "./application.js";
2
2
  export { buildApplication, readManifest } from "./build.js";
3
3
  export { devApplication, previewApplication } from "./serve.js";
4
4
  export { discoverRoutes } from "./routes.js";
5
- export { HtmlKitError } from "./config.js";
5
+ export { HtmlKitError, loadConfig } from "./config.js";
6
6
  /** Type-check an htmlkit.config.ts file without changing its values. */
7
7
  export function defineConfig(options) { return options; }
8
8
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,wEAAwE;AACxE,MAAM,UAAU,YAAY,CAAC,OAA2B,IAAwB,OAAO,OAAO,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEvD,wEAAwE;AACxE,MAAM,UAAU,YAAY,CAAC,OAA2B,IAAwB,OAAO,OAAO,CAAC,CAAC,CAAC"}
@@ -2,6 +2,8 @@ import { type ComponentDefinition } from "@nextwebwg/html-next";
2
2
  interface ComponentMetadata {
3
3
  readonly layout?: string;
4
4
  readonly label?: string;
5
+ /** hk:order, the page's place among its neighbours, as a number-and-dot prefix gives it. */
6
+ readonly order?: string;
5
7
  readonly hidden?: boolean;
6
8
  /** Extra route patterns for a page, relative to its page directory's prefix. */
7
9
  readonly aliases: readonly string[];
@@ -1 +1 @@
1
- {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAIxF,UAAU,iBAAiB;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAOD,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;;;;;EA8C/D;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAQlH"}
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAIxF,UAAU,iBAAiB;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4FAA4F;IAC5F,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAOD,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;;;;;EA+C/D;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAQlH"}
package/dist/resource.js CHANGED
@@ -50,13 +50,15 @@ export function applicationResource(source, file) {
50
50
  aliases.push(content);
51
51
  continue;
52
52
  }
53
- const key = { "hk:layout": "layout", "hk:label": "label", "hk:navigation": "hidden" }[name];
53
+ const key = { "hk:layout": "layout", "hk:label": "label", "hk:order": "order", "hk:navigation": "hidden" }[name];
54
54
  if (key === undefined)
55
- throw new HtmlKitError(`Component configuration is hk:layout, hk:label, hk:navigation, or hk:alias; hk:page belongs outside the carrier (${name}).`, file);
55
+ throw new HtmlKitError(`Component configuration is hk:layout, hk:label, hk:order, hk:navigation, or hk:alias; hk:page belongs outside the carrier (${name}).`, file);
56
56
  if (configuration[key] !== undefined)
57
57
  throw new HtmlKitError(`Declare ${name} once per component.`, file);
58
58
  if (key === "hidden" && content !== "hidden")
59
59
  throw new HtmlKitError('hk:navigation accepts only content="hidden".', file);
60
+ if (key === "order" && !/^\d+$/.test(content))
61
+ throw new HtmlKitError("hk:order needs a whole number, such as content=\"3\".", file);
60
62
  if (key === "hidden")
61
63
  configuration.hidden = true;
62
64
  else
@@ -1 +1 @@
1
- {"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
package/dist/routes.d.ts CHANGED
@@ -1,7 +1,15 @@
1
1
  import type { ApplicationOptions, ApplicationRoute } from "./types.js";
2
+ /** The name of a [name], [...name] catch-all, or [[...name]] optional catch-all segment. */
2
3
  export declare function parameter(segment: string): string | undefined;
4
+ /** How broadly a segment matches: 0 a literal, 1 [name], 2 [...name], 3 [[...name]]. A missing segment is -1. */
5
+ export declare function breadth(segment: string | undefined): number;
3
6
  export declare function validSegment(value: unknown): value is string;
4
- type DiscoveryOptions = Pick<ApplicationOptions, "base" | "routes" | "fileRoutes" | "pages" | "layout" | "layoutDefaults" | "plugins">;
7
+ export declare const redirectStatuses: readonly number[];
8
+ /** A redirect's Location: an application path, under base, or an absolute http(s) URL. */
9
+ export declare function redirectLocation(base: string, to: unknown, source: string): string;
10
+ /** The redirects option by deployment pathname, each to its Location. */
11
+ export declare function redirectTable(base: string, redirects?: Readonly<Record<string, string>>): ReadonlyMap<string, string>;
12
+ type DiscoveryOptions = Pick<ApplicationOptions, "base" | "routes" | "fileRoutes" | "pages" | "layout" | "layouts" | "layoutDefaults" | "plugins">;
5
13
  export declare function discoverRoutes(root?: string, options?: DiscoveryOptions): Promise<readonly ApplicationRoute[]>;
6
14
  /**
7
15
  * Routes plus what page plugins produced: compiled page sources by component path, and the files
@@ -15,7 +23,7 @@ export declare function discover(root: string, options: DiscoveryOptions): Promi
15
23
  }>;
16
24
  export declare function matchRoute(routes: readonly ApplicationRoute[], pathname: string, base: string): {
17
25
  route: ApplicationRoute;
18
- params: Readonly<Record<string, string>>;
26
+ params: Readonly<Record<string, string | readonly string[]>>;
19
27
  } | undefined;
20
28
  export {};
21
29
  //# sourceMappingURL=routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAyC,MAAM,YAAY,CAAC;AAE9G,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAA+D;AAC7H,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE5D;AACD,KAAK,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,EAAE,MAAM,GAAG,QAAQ,GAAG,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,gBAAgB,GAAG,SAAS,CAAC,CAAC;AAEvI,wBAAsB,cAAc,CAAC,IAAI,SAAgB,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC,CAE/H;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB;;;;;GA8LrE;AACD,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;;;cAyB7F"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAqD,MAAM,YAAY,CAAC;AAE1H,4FAA4F;AAC5F,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG7D;AACD,iHAAiH;AACjH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG3D;AACD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE5D;AACD,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAA8B,CAAC;AAC7E,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIlF;AACD,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAUzH;AACD,KAAK,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,EAAE,MAAM,GAAG,QAAQ,GAAG,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CAAC,CAAC;AAEnJ,wBAAsB,cAAc,CAAC,IAAI,SAAgB,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC,CAE/H;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB;;;;;GA2OrE;AACD,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;;;cAgC7F"}
package/dist/routes.js CHANGED
@@ -3,12 +3,46 @@ import { realpathSync, statSync } from "node:fs";
3
3
  import { readFile, readdir, realpath, stat } from "node:fs/promises";
4
4
  import { basename, join, relative, resolve, sep } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
- import { HtmlKitError, within } from "./config.js";
6
+ import { HtmlKitError, pluginLayouts, within } from "./config.js";
7
7
  import { applicationResource, pageDefinition } from "./resource.js";
8
- export function parameter(segment) { return /^\[([A-Za-z][A-Za-z0-9_]*)\]$/.exec(segment)?.[1]; }
8
+ /** The name of a [name], [...name] catch-all, or [[...name]] optional catch-all segment. */
9
+ export function parameter(segment) {
10
+ const match = /^\[(?:\.\.\.)?([A-Za-z][A-Za-z0-9_]*)\]$|^\[\[\.\.\.([A-Za-z][A-Za-z0-9_]*)\]\]$/.exec(segment);
11
+ return match?.[1] ?? match?.[2];
12
+ }
13
+ /** How broadly a segment matches: 0 a literal, 1 [name], 2 [...name], 3 [[...name]]. A missing segment is -1. */
14
+ export function breadth(segment) {
15
+ if (segment === undefined)
16
+ return -1;
17
+ return parameter(segment) === undefined ? 0 : segment.startsWith("[[") ? 3 : segment.startsWith("[...") ? 2 : 1;
18
+ }
9
19
  export function validSegment(value) {
10
20
  return typeof value === "string" && value.length > 0 && value !== "." && value !== ".." && !value.includes("/") && !/[\\\0?#]/.test(value);
11
21
  }
22
+ export const redirectStatuses = [301, 302, 303, 307, 308];
23
+ /** A redirect's Location: an application path, under base, or an absolute http(s) URL. */
24
+ export function redirectLocation(base, to, source) {
25
+ if (typeof to === "string" && /^\/(?![/\\])/.test(to))
26
+ return base + to.slice(1);
27
+ try {
28
+ if (typeof to === "string" && /^https?:$/.test(new URL(to).protocol))
29
+ return new URL(to).href;
30
+ }
31
+ catch { /* Reported below. */ }
32
+ throw new HtmlKitError(`Redirect target ${String(to)} must be an application path starting with / or an http(s) URL.`, source);
33
+ }
34
+ /** The redirects option by deployment pathname, each to its Location. */
35
+ export function redirectTable(base, redirects = {}) {
36
+ const table = new Map();
37
+ for (const [from, to] of Object.entries(redirects)) {
38
+ const segments = from.slice(1, -1).split("/").filter(Boolean);
39
+ if (!/^\/(?:[^/]+\/)*$/.test(from) || segments[0] === "_htmlkit" || !segments.every(validSegment)) {
40
+ throw new HtmlKitError("Redirects need application paths with leading and trailing slashes, outside _htmlkit.", from);
41
+ }
42
+ table.set(new URL(base + from.slice(1), "http://localhost").pathname, redirectLocation(base, to, from));
43
+ }
44
+ return table;
45
+ }
12
46
  export async function discoverRoutes(root = process.cwd(), options = {}) {
13
47
  return (await discover(root, options)).routes;
14
48
  }
@@ -31,7 +65,7 @@ export async function discover(root, options) {
31
65
  // Each route remembers its page directory's prefix, which page aliases are relative to.
32
66
  // folderLayouts are the _layout.html files from the page directory down to the page, outermost first.
33
67
  const routes = [];
34
- const add = (input, prefix, order, directoryLayout, folderLayouts = []) => {
68
+ const add = (input, prefix, order, directoryLayout, folderLayouts = [], groups = []) => {
35
69
  if (!/^\/(?:[^/]+\/)*$/.test(input.pattern))
36
70
  throw new HtmlKitError("Route patterns require leading and trailing slashes.", input.component);
37
71
  const segments = input.pattern.slice(1, -1).split("/").filter(Boolean);
@@ -40,15 +74,17 @@ export async function discover(root, options) {
40
74
  if (segments.some(segment => parameter(segment) === undefined && !/^[A-Za-z0-9_-]+$/.test(segment))) {
41
75
  throw new HtmlKitError("Route segments must be URL slugs or [named] parameters.", input.component);
42
76
  }
77
+ if (segments.slice(0, -1).some(segment => breadth(segment) > 1))
78
+ throw new HtmlKitError("A [...catch-all] parameter must be the last route segment.", input.component);
43
79
  const params = segments.flatMap(segment => parameter(segment) ?? []);
44
80
  if (new Set(params).size !== params.length)
45
81
  throw new HtmlKitError("Route parameter names must be unique.", input.component);
46
82
  const absolute = (layer) => ({ component: resolve(root, layer.component),
47
83
  ...(layer.server === undefined ? {} : { server: typeof layer.server === "string" ? resolve(root, layer.server) : layer.server }) });
48
84
  routes.push({ ...absolute(input), pattern: input.pattern, segments, params, layouts: (input.layouts ?? []).map(absolute), prefix, folderLayouts,
49
- ...(directoryLayout === undefined ? {} : { directoryLayout }),
85
+ ...(directoryLayout === undefined ? {} : { directoryLayout }), ...(groups.length === 0 ? {} : { groups }),
50
86
  ...(order === undefined ? {} : { order }), ...(input.canonical === undefined ? {} : { canonical: input.canonical }),
51
- ...(input.notFound ? { notFound: true } : {}) });
87
+ ...(input.notFound ? { notFound: true } : {}), ...(input.index ? { index: true } : {}) });
52
88
  };
53
89
  // A number and a dot ("01.guide") order a file or directory and stay out of its URL. Route
54
90
  // segments cannot contain dots, so the prefix is never part of a name.
@@ -58,9 +94,10 @@ export async function discover(root, options) {
58
94
  throw new HtmlKitError("Ordering prefix leaves an empty route segment.", source);
59
95
  return { slug: prefix?.[2] ?? name, rank: prefix?.[1] ?? null };
60
96
  };
61
- const visit = async (directory, prefix, segments, order, directoryLayout, outer) => {
97
+ const visit = async (directory, prefix, segments, order, directoryLayout, outer, groups) => {
62
98
  const files = await readdir(directory, { withFileTypes: true });
63
99
  const names = new Set(files.filter(file => file.isFile()).map(file => file.name));
100
+ const prefixDepth = prefix.split("/").filter(Boolean).length;
64
101
  const layer = (name, stem) => {
65
102
  const servers = ["ts", "js"].filter(ext => names.has(`${stem}.server.${ext}`));
66
103
  if (servers.length > 1)
@@ -76,18 +113,29 @@ export async function discover(root, options) {
76
113
  const stem = name.slice(0, -extension.length);
77
114
  const { slug, rank } = ordered(stem, join(directory, name));
78
115
  const parts = slug === "index" ? segments : [...segments, slug];
79
- // A 404 page at the root of the application's pages is the not-found page, not a /404/ route.
80
- const notFound = parts.length === 1 && parts[0] === "404";
81
- add({ ...layer(name, stem), pattern: `/${parts.length === 0 ? "" : parts.join("/") + "/"}`, ...(notFound ? { notFound: true } : {}) }, prefix, slug === "index" ? order : [...order, rank], directoryLayout, folderLayouts);
116
+ // A 404 page at the root of a page directory is never a route: at the / prefix it is the
117
+ // not-found page, and at any other prefix the site's not-found page serves those URLs too.
118
+ const notFound = slug === "404" && parts.length === prefixDepth + 1;
119
+ if (notFound && prefix !== "/")
120
+ continue;
121
+ add({ ...layer(name, stem), pattern: `/${parts.length === 0 ? "" : parts.join("/") + "/"}`, ...(notFound ? { notFound: true } : {}), ...(slug === "index" ? { index: true } : {}) }, prefix, slug === "index" ? order : [...order, rank], directoryLayout, folderLayouts, groups);
82
122
  }
83
123
  for (const file of files.sort((a, b) => a.name.localeCompare(b.name))) {
84
124
  if (!file.isDirectory() || file.name.startsWith(".") || file.name.startsWith("_"))
85
125
  continue;
86
126
  const { slug, rank } = ordered(file.name, join(directory, file.name));
127
+ // A (group) folder orders and labels its pages and adds no URL segment.
128
+ const label = /^\(([^()]*)\)$/.exec(slug)?.[1];
129
+ if (label !== undefined) {
130
+ if (label.trim() === "")
131
+ throw new HtmlKitError("A route group needs a label inside its parentheses.", join(directory, file.name));
132
+ await visit(join(directory, file.name), prefix, segments, order, directoryLayout, folderLayouts, [...groups, { label, order: rank, depth: segments.length }]);
133
+ continue;
134
+ }
87
135
  if (parameter(slug) === undefined && !/^[A-Za-z0-9_-]+$/.test(slug)) {
88
- throw new HtmlKitError("Route directories must be URL slugs or [named] parameters.", join(directory, file.name));
136
+ throw new HtmlKitError("Route directories must be URL slugs, [named] parameters, or (groups).", join(directory, file.name));
89
137
  }
90
- await visit(join(directory, file.name), prefix, [...segments, slug], [...order, rank], directoryLayout, folderLayouts);
138
+ await visit(join(directory, file.name), prefix, [...segments, slug], [...order, rank], directoryLayout, folderLayouts, groups);
91
139
  }
92
140
  };
93
141
  if (options.fileRoutes !== false) {
@@ -111,7 +159,7 @@ export async function discover(root, options) {
111
159
  throw new HtmlKitError("Page directory does not exist.", directory);
112
160
  const segments = prefix.split("/").filter(Boolean);
113
161
  if (present)
114
- await visit(directory, prefix, segments, segments.map(() => null), layout, []);
162
+ await visit(directory, prefix, segments, segments.map(() => null), layout, [], []);
115
163
  }
116
164
  }
117
165
  for (const route of options.routes ?? [])
@@ -181,7 +229,7 @@ export async function discover(root, options) {
181
229
  }
182
230
  const patterns = new Map();
183
231
  for (const route of routes) {
184
- const normalized = route.segments.map(segment => parameter(segment) === undefined ? segment.toLowerCase() : "[]").join("/");
232
+ const normalized = route.segments.map(segment => ["", "[]", "[...]", "[[...]]"][breadth(segment)] || segment.toLowerCase()).join("/");
185
233
  if (patterns.has(normalized))
186
234
  throw new HtmlKitError(`Route conflict: ${patterns.get(normalized)} and ${route.component} (${route.pattern}).`, route.component);
187
235
  patterns.set(normalized, `${route.component} (${route.pattern})`);
@@ -189,8 +237,25 @@ export async function discover(root, options) {
189
237
  const pageNames = new Map();
190
238
  const discovered = [];
191
239
  let notFound;
240
+ const folderOrders = [];
192
241
  const layoutDefaults = Object.entries(options.layoutDefaults ?? {}).sort(([a], [b]) => b.length - a.length);
193
- for (const { prefix: _prefix, directoryLayout, folderLayouts, notFound: isNotFound, ...route } of routes) {
242
+ // A layout name is the site's layouts entry, then its app/layouts/<name>.html (the name itself),
243
+ // then a plugin's entry, so a site overrides a plugin's layout with a file of the same name.
244
+ const named = async (name) => {
245
+ if (options.layouts !== undefined && Object.hasOwn(options.layouts, name))
246
+ return options.layouts[name];
247
+ try {
248
+ await stat(resolve(root, `app/layouts/${name}.html`));
249
+ return name;
250
+ }
251
+ catch (error) {
252
+ if (error.code !== "ENOENT")
253
+ throw error;
254
+ }
255
+ const plugins = options[pluginLayouts];
256
+ return plugins !== undefined && Object.hasOwn(plugins, name) ? plugins[name] : undefined;
257
+ };
258
+ for (const { prefix: _prefix, directoryLayout, folderLayouts, notFound: isNotFound, index, ...route } of routes) {
194
259
  const { identity, resource } = await resourceOf(route.component);
195
260
  const pageName = pageDefinition(resource, route.component).contract.tag;
196
261
  const previous = pageNames.get(pageName);
@@ -202,15 +267,17 @@ export async function discover(root, options) {
202
267
  // An alias is a further URL for its page, so it takes the layout of the page's own URL.
203
268
  const directory = layoutDefaults.find(([prefix]) => (route.canonical ?? route.pattern).startsWith(prefix));
204
269
  let selected = metadata.layout ?? directoryLayout ?? directory?.[1] ?? options.layout;
205
- if (selected === undefined && route.layouts.length === 0) {
206
- try {
207
- await stat(resolve(root, "app/layouts/default.html"));
208
- selected = "default";
209
- }
210
- catch (error) {
211
- if (error.code !== "ENOENT")
212
- throw error;
270
+ if (selected === undefined && route.layouts.length === 0 && await named("default") !== undefined)
271
+ selected = "default";
272
+ if (typeof selected === "string" && selected !== "none") {
273
+ if (!/^[A-Za-z0-9_-]+$/.test(selected))
274
+ throw new HtmlKitError("Layout names must be filename slugs.", route.component);
275
+ const found = await named(selected);
276
+ if (found === undefined) {
277
+ throw new HtmlKitError(`Layout ${selected} needs ${resolve(root, `app/layouts/${selected}.html`)} or a layouts entry named ${selected}.`, route.component);
213
278
  }
279
+ if (typeof found === "object")
280
+ selected = found;
214
281
  }
215
282
  let layouts = route.layouts;
216
283
  if (selected === false || selected === "none")
@@ -220,10 +287,7 @@ export async function discover(root, options) {
220
287
  ...(selected.server === undefined ? {} : { server: typeof selected.server === "string" ? resolve(root, selected.server) : selected.server }) }];
221
288
  }
222
289
  else if (selected !== undefined) {
223
- if (!/^[A-Za-z0-9_-]+$/.test(selected))
224
- throw new HtmlKitError("Layout names must be filename slugs.", route.component);
225
290
  const component = resolve(root, `app/layouts/${selected}.html`);
226
- await stat(component);
227
291
  const servers = [];
228
292
  for (const extension of ["ts", "js"]) {
229
293
  const candidate = resolve(root, `app/layouts/${selected}.server.${extension}`);
@@ -243,13 +307,37 @@ export async function discover(root, options) {
243
307
  // The selected layout is outermost and folder layouts nest inside it; none drops them all.
244
308
  if (selected !== false && selected !== "none")
245
309
  layouts = [...layouts, ...folderLayouts];
246
- const discoveredRoute = { ...route, layouts, pageName, ...(metadata.label === undefined ? {} : { label: metadata.label }),
247
- ...(metadata.hidden ? { hidden: true } : {}) };
310
+ // hk:order takes the place of the number-and-dot prefix on the page's own last segment.
311
+ // hk:order takes the place of the number-and-dot prefix on the page's own last segment; on a
312
+ // folder's index page, that segment is the folder's, so it orders the folder (below).
313
+ let order = route.order;
314
+ if (metadata.order !== undefined && route.canonical === undefined && !isNotFound) {
315
+ if (route.segments.length === 0)
316
+ throw new HtmlKitError("hk:order on the site's home page has no folder to order; it orders a page among its folder's pages.", route.component);
317
+ order = [...route.segments.slice(0, -1).map((_, i) => route.order?.[i] ?? null), metadata.order];
318
+ if (index)
319
+ folderOrders.push({ segments: route.segments, rank: metadata.order });
320
+ }
321
+ // A static <title> labels the page's folder in navigation trees when hk:label doesn't.
322
+ const title = /^<title>([^<{]*)<\/title>$/m.exec(metadata.head.replaceAll("><", ">\n<"))?.[1]?.trim()
323
+ .replace(/&(lt|gt|quot|#39|amp);/g, (_, name) => ({ lt: "<", gt: ">", quot: '"', "#39": "'", amp: "&" })[name]);
324
+ const discoveredRoute = { ...route, layouts, pageName, ...(order === undefined ? {} : { order }), ...(metadata.label === undefined ? {} : { label: metadata.label }),
325
+ ...(title ? { title } : {}), ...(metadata.hidden ? { hidden: true } : {}) };
248
326
  if (isNotFound)
249
327
  notFound = discoveredRoute;
250
328
  else
251
329
  discovered.push(discoveredRoute);
252
330
  }
331
+ // Every page in a folder whose index page sets hk:order takes that rank for the folder's segment.
332
+ for (const { segments, rank } of folderOrders) {
333
+ for (const [i, route] of discovered.entries()) {
334
+ if (route.canonical !== undefined || route.segments.length < segments.length || segments.some((segment, j) => route.segments[j] !== segment))
335
+ continue;
336
+ const order = route.segments.map((_, j) => route.order?.[j] ?? null);
337
+ order[segments.length - 1] = rank;
338
+ discovered[i] = { ...route, order };
339
+ }
340
+ }
253
341
  return { routes: discovered, ...(notFound === undefined ? {} : { notFound }), sources, files };
254
342
  }
255
343
  export function matchRoute(routes, pathname, base) {
@@ -264,27 +352,34 @@ export function matchRoute(routes, pathname, base) {
264
352
  }
265
353
  if (segments.some(segment => !validSegment(segment)))
266
354
  return undefined;
267
- // Literal segments precede parameters; never depend on filesystem order.
355
+ // Segment by segment, a literal precedes [name], which precedes [...name], which precedes
356
+ // [[...name]]; never depend on filesystem order.
268
357
  const sorted = [...routes].sort((a, b) => {
269
- for (let i = 0; i < Math.min(a.segments.length, b.segments.length); i++) {
270
- const difference = Number(parameter(a.segments[i]) !== undefined) - Number(parameter(b.segments[i]) !== undefined);
358
+ for (let i = 0; i < Math.max(a.segments.length, b.segments.length); i++) {
359
+ const difference = breadth(a.segments[i]) - breadth(b.segments[i]);
271
360
  if (difference !== 0)
272
361
  return difference;
273
362
  }
274
363
  return a.pattern.localeCompare(b.pattern);
275
364
  });
276
365
  for (const route of sorted) {
277
- if (route.segments.length !== segments.length)
366
+ // A catch-all takes the remaining segments: at least one, or any number for [[...name]].
367
+ const rest = Math.max(0, breadth(route.segments.at(-1)) - 1);
368
+ const fixed = rest === 0 ? route.segments.length : route.segments.length - 1;
369
+ if (rest === 0 ? segments.length !== fixed : segments.length < fixed + 2 - rest)
278
370
  continue;
279
371
  const params = {};
280
- if (route.segments.every((segment, index) => {
372
+ if (route.segments.slice(0, fixed).every((segment, index) => {
281
373
  const name = parameter(segment);
282
374
  if (name === undefined)
283
375
  return segment === segments[index];
284
376
  params[name] = segments[index];
285
377
  return true;
286
- }))
378
+ })) {
379
+ if (rest > 0)
380
+ params[parameter(route.segments.at(-1))] = Object.freeze(segments.slice(fixed));
287
381
  return { route, params: Object.freeze(params) };
382
+ }
288
383
  }
289
384
  return undefined;
290
385
  }