@ecopages/core 0.2.0-beta.27 → 0.2.0-beta.29

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.
Files changed (103) hide show
  1. package/README.md +1 -1
  2. package/package.json +42 -26
  3. package/src/adapters/abstract/application-adapter.d.ts +22 -4
  4. package/src/adapters/abstract/application-adapter.js +27 -4
  5. package/src/adapters/abstract/server-adapter.d.ts +10 -0
  6. package/src/adapters/bun/create-app.d.ts +1 -0
  7. package/src/adapters/bun/create-app.js +11 -4
  8. package/src/adapters/bun/server-adapter.js +1 -0
  9. package/src/adapters/node/create-app.d.ts +1 -0
  10. package/src/adapters/node/create-app.js +10 -3
  11. package/src/adapters/node/server-adapter.js +1 -0
  12. package/src/adapters/shared/fs-server-response-factory.d.ts +5 -0
  13. package/src/adapters/shared/fs-server-response-factory.js +20 -0
  14. package/src/adapters/shared/fs-server-response-matcher.d.ts +18 -0
  15. package/src/adapters/shared/fs-server-response-matcher.js +75 -15
  16. package/src/config/config-builder.d.ts +12 -2
  17. package/src/config/config-builder.js +41 -2
  18. package/src/config/constants.d.ts +1 -0
  19. package/src/config/constants.js +2 -1
  20. package/src/eco/eco.js +2 -2
  21. package/src/eco/eco.utils.d.ts +1 -1
  22. package/src/eco/eco.utils.js +1 -1
  23. package/src/hmr/client/hmr-runtime.js +1 -1
  24. package/src/hmr/hmr-file-change-prep.d.ts +1 -1
  25. package/src/hmr/hmr-file-change-prep.js +1 -1
  26. package/src/plugins/processor.d.ts +8 -0
  27. package/src/route-renderer/GRAPH.md +11 -11
  28. package/src/route-renderer/README.md +11 -6
  29. package/src/route-renderer/orchestration/{document-shell-render.service.d.ts → document-shell/document-shell-render.service.d.ts} +19 -2
  30. package/src/route-renderer/orchestration/{document-shell-render.service.js → document-shell/document-shell-render.service.js} +28 -0
  31. package/src/route-renderer/orchestration/{layout-shell-props.service.d.ts → document-shell/layout-shell-props.service.d.ts} +1 -1
  32. package/src/route-renderer/orchestration/{component-render-context.d.ts → foreign-child/component-render-context.d.ts} +1 -1
  33. package/src/route-renderer/orchestration/{component-render-context.js → foreign-child/component-render-context.js} +1 -1
  34. package/src/route-renderer/orchestration/{render-output.utils.d.ts → foreign-child/foreign-child-output.utils.d.ts} +1 -7
  35. package/src/route-renderer/orchestration/{render-output.utils.js → foreign-child/foreign-child-output.utils.js} +1 -26
  36. package/src/route-renderer/orchestration/{foreign-subtree-execution.service.d.ts → foreign-child/foreign-subtree-execution.service.d.ts} +5 -5
  37. package/src/route-renderer/orchestration/{foreign-subtree-execution.service.js → foreign-child/foreign-subtree-execution.service.js} +2 -2
  38. package/src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.d.ts +19 -0
  39. package/src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.js +35 -0
  40. package/src/route-renderer/orchestration/integration-renderer.d.ts +4 -46
  41. package/src/route-renderer/orchestration/integration-renderer.js +53 -157
  42. package/src/route-renderer/orchestration/integration-renderer.test-fixtures.d.ts +75 -0
  43. package/src/route-renderer/orchestration/integration-renderer.test-fixtures.js +177 -0
  44. package/src/route-renderer/orchestration/{component-graph-collectors.d.ts → ownership-graph/component-graph-collectors.d.ts} +4 -4
  45. package/src/route-renderer/orchestration/{component-graph-collectors.js → ownership-graph/component-graph-collectors.js} +1 -1
  46. package/src/route-renderer/orchestration/{component-graph.d.ts → ownership-graph/component-graph.d.ts} +1 -3
  47. package/src/route-renderer/orchestration/{component-graph.js → ownership-graph/component-graph.js} +0 -2
  48. package/src/route-renderer/orchestration/{ownership-validation.service.d.ts → ownership-graph/ownership-validation.service.d.ts} +2 -2
  49. package/src/route-renderer/orchestration/{ownership-validation.service.js → ownership-graph/ownership-validation.service.js} +3 -4
  50. package/src/route-renderer/orchestration/{global-injector-assets.service.d.ts → page-browser-graph/global-injector-assets.service.d.ts} +3 -3
  51. package/src/route-renderer/orchestration/{global-injector-assets.service.js → page-browser-graph/global-injector-assets.service.js} +2 -2
  52. package/src/route-renderer/orchestration/{page-browser-graph-contribution.loader.d.ts → page-browser-graph/page-browser-graph-contribution.loader.d.ts} +1 -1
  53. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.d.ts +12 -0
  54. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.js +29 -0
  55. package/src/route-renderer/orchestration/{page-browser-graph-session.d.ts → page-browser-graph/page-browser-graph-session.d.ts} +3 -3
  56. package/src/route-renderer/orchestration/{page-browser-graph-session.js → page-browser-graph/page-browser-graph-session.js} +1 -1
  57. package/src/route-renderer/orchestration/{page-browser-graph.service.d.ts → page-browser-graph/page-browser-graph.service.d.ts} +3 -3
  58. package/src/route-renderer/orchestration/{page-browser-graph.service.js → page-browser-graph/page-browser-graph.service.js} +3 -3
  59. package/src/route-renderer/orchestration/{processed-asset-dedupe.d.ts → page-browser-graph/processed-asset-dedupe.d.ts} +1 -1
  60. package/src/route-renderer/orchestration/{integration-route-render-adapter.d.ts → route-pipeline/integration-route-render-adapter.d.ts} +3 -3
  61. package/src/route-renderer/orchestration/{integration-route-render-adapter.js → route-pipeline/integration-route-render-adapter.js} +1 -1
  62. package/src/route-renderer/orchestration/route-pipeline/marker-artifact.utils.d.ts +6 -0
  63. package/src/route-renderer/orchestration/route-pipeline/marker-artifact.utils.js +27 -0
  64. package/src/route-renderer/orchestration/route-pipeline/robots-meta.contribution.d.ts +15 -0
  65. package/src/route-renderer/orchestration/route-pipeline/robots-meta.contribution.js +36 -0
  66. package/src/route-renderer/orchestration/{route-html-finalization.service.d.ts → route-pipeline/route-html-finalization.service.d.ts} +6 -2
  67. package/src/route-renderer/orchestration/{route-html-finalization.service.js → route-pipeline/route-html-finalization.service.js} +5 -2
  68. package/src/route-renderer/orchestration/{route-prepared-options.builder.d.ts → route-pipeline/route-prepared-options.builder.d.ts} +3 -3
  69. package/src/route-renderer/orchestration/{route-prepared-options.builder.js → route-pipeline/route-prepared-options.builder.js} +8 -4
  70. package/src/route-renderer/orchestration/{route-prepared-options.utils.js → route-pipeline/route-prepared-options.utils.js} +1 -1
  71. package/src/route-renderer/orchestration/{route-render-orchestrator.d.ts → route-pipeline/route-render-orchestrator.d.ts} +6 -6
  72. package/src/route-renderer/orchestration/{route-render-orchestrator.js → route-pipeline/route-render-orchestrator.js} +8 -5
  73. package/src/route-renderer/orchestration/string-markup-renderer.js +1 -1
  74. package/src/services/html/html-transformer.service.js +1 -1
  75. package/src/services/invalidation/development-invalidation.service.d.ts +5 -0
  76. package/src/services/invalidation/development-invalidation.service.js +8 -9
  77. package/src/static-site-generator/README.md +9 -1
  78. package/src/static-site-generator/production-page-browser-graph-prebuild.js +1 -1
  79. package/src/static-site-generator/sitemap-routes.d.ts +14 -0
  80. package/src/static-site-generator/sitemap-routes.js +31 -0
  81. package/src/static-site-generator/sitemap.d.ts +15 -0
  82. package/src/static-site-generator/sitemap.js +33 -0
  83. package/src/static-site-generator/static-export-context.d.ts +9 -1
  84. package/src/static-site-generator/static-site-generator.d.ts +27 -6
  85. package/src/static-site-generator/static-site-generator.js +200 -50
  86. package/src/types/internal-types.d.ts +9 -2
  87. package/src/types/public-types.d.ts +78 -2
  88. package/src/utils/ecopages-route-info.d.ts +26 -0
  89. package/src/utils/ecopages-route-info.js +26 -0
  90. package/src/utils/html-escaping.d.ts +7 -0
  91. package/src/utils/html-escaping.js +5 -1
  92. package/src/utils/path-pattern.d.ts +23 -0
  93. package/src/utils/path-pattern.js +27 -0
  94. package/src/watchers/project-watcher.d.ts +3 -3
  95. package/src/watchers/project-watcher.js +4 -4
  96. package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +0 -1
  97. package/src/route-renderer/orchestration/declared-ownership-graph.js +0 -8
  98. package/src/route-renderer/orchestration/template-serialization.d.ts +0 -38
  99. package/src/route-renderer/orchestration/template-serialization.js +0 -45
  100. /package/src/route-renderer/orchestration/{layout-shell-props.service.js → document-shell/layout-shell-props.service.js} +0 -0
  101. /package/src/route-renderer/orchestration/{page-browser-graph-contribution.loader.js → page-browser-graph/page-browser-graph-contribution.loader.js} +0 -0
  102. /package/src/route-renderer/orchestration/{processed-asset-dedupe.js → page-browser-graph/processed-asset-dedupe.js} +0 -0
  103. /package/src/route-renderer/orchestration/{route-prepared-options.utils.d.ts → route-pipeline/route-prepared-options.utils.d.ts} +0 -0
@@ -0,0 +1,29 @@
1
+ import { createPagePackage } from "../../../services/assets/asset-processing-service/index.js";
2
+ import { buildProcessedAssetDedupeKey, dedupeProcessedAssets } from "./processed-asset-dedupe.js";
3
+ function mergePageBrowserGraph(currentPagePackage, processedDependencies, pageBrowserGraph) {
4
+ const mergedGraph = currentPagePackage?.pageBrowserGraph ? {
5
+ entryAssets: dedupeProcessedAssets([
6
+ ...currentPagePackage.pageBrowserGraph.entryAssets,
7
+ ...pageBrowserGraph.entryAssets
8
+ ]),
9
+ chunkAssets: dedupeProcessedAssets([
10
+ ...currentPagePackage.pageBrowserGraph.chunkAssets,
11
+ ...pageBrowserGraph.chunkAssets
12
+ ])
13
+ } : pageBrowserGraph;
14
+ const pageBrowserGraphAssetKeys = new Set(
15
+ [...mergedGraph.entryAssets, ...mergedGraph.chunkAssets].map((asset) => buildProcessedAssetDedupeKey(asset))
16
+ );
17
+ const baseAssets = currentPagePackage ? currentPagePackage.assets.filter(
18
+ (asset) => !pageBrowserGraphAssetKeys.has(buildProcessedAssetDedupeKey(asset))
19
+ ) : processedDependencies;
20
+ return {
21
+ pagePackage: createPagePackage(dedupeProcessedAssets(baseAssets), {
22
+ pageBrowserGraph: mergedGraph
23
+ }),
24
+ mergedGraph
25
+ };
26
+ }
27
+ export {
28
+ mergePageBrowserGraph
29
+ };
@@ -1,6 +1,6 @@
1
- import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
- import type { PageBrowserGraphContribution, PageBrowserGraphResult } from '../../types/public-types.js';
3
- import type { ProcessedAsset } from '../../services/assets/asset-processing-service/assets.types.js';
1
+ import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
2
+ import type { PageBrowserGraphContribution, PageBrowserGraphResult } from '../../../types/public-types.js';
3
+ import type { ProcessedAsset } from '../../../services/assets/asset-processing-service/assets.types.js';
4
4
  export type GraphPolicy = 'development' | 'production';
5
5
  export type GraphKey = {
6
6
  integrationName: string;
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { createHash } from "node:crypto";
3
- import { appLogger } from "../../global/app-logger.js";
3
+ import { appLogger } from "../../../global/app-logger.js";
4
4
  function isSkipCacheGroupedOutcome(outcome) {
5
5
  return "skipCache" in outcome;
6
6
  }
@@ -1,6 +1,6 @@
1
- import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
- import type { PageBrowserGraphContribution, PageBrowserGraphResult } from '../../types/public-types.js';
3
- import { type AssetProcessingService } from '../../services/assets/asset-processing-service/index.js';
1
+ import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
2
+ import type { PageBrowserGraphContribution, PageBrowserGraphResult } from '../../../types/public-types.js';
3
+ import { type AssetProcessingService } from '../../../services/assets/asset-processing-service/index.js';
4
4
  import { type GraphPolicy } from './page-browser-graph-session.js';
5
5
  export type PageBrowserGraphResolveInput = {
6
6
  routeFile: string;
@@ -1,9 +1,9 @@
1
1
  import path from "node:path";
2
2
  import { fileSystem } from "@ecopages/file-system";
3
3
  import {
4
- } from "../../services/assets/asset-processing-service/index.js";
5
- import { appLogger } from "../../global/app-logger.js";
6
- import { startupTrace } from "../../diagnostics/startup-trace.js";
4
+ } from "../../../services/assets/asset-processing-service/index.js";
5
+ import { appLogger } from "../../../global/app-logger.js";
6
+ import { startupTrace } from "../../../diagnostics/startup-trace.js";
7
7
  import {
8
8
  collectPageBrowserGraphDependencyPaths,
9
9
  createPageBrowserGraphEntryFingerprint,
@@ -1,3 +1,3 @@
1
- import type { ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
1
+ import type { ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
2
2
  export declare function buildProcessedAssetDedupeKey(asset: ProcessedAsset): string;
3
3
  export declare function dedupeProcessedAssets(assets: ProcessedAsset[]): ProcessedAsset[];
@@ -1,6 +1,6 @@
1
- import type { ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
2
- import type { HtmlDocumentContribution } from '../../services/html/html-transformer.service.js';
3
- import type { EcoComponent, EcoPageFile, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphContributionContext, PagePackageResult, RouteRendererBody, RouteRendererOptions } from '../../types/public-types.js';
1
+ import type { ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
2
+ import type { HtmlDocumentContribution } from '../../../services/html/html-transformer.service.js';
3
+ import type { EcoComponent, EcoPageFile, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphContributionContext, PagePackageResult, RouteRendererBody, RouteRendererOptions } from '../../../types/public-types.js';
4
4
  import type { RouteHtmlFinalization, RouteRenderOrchestratorAdapter, RouteRenderOrchestratorResolvedInputs } from './route-render-orchestrator.js';
5
5
  /**
6
6
  * Host surface required to build the orchestrator adapter for one integration renderer.
@@ -1,4 +1,4 @@
1
- import { loadPageBrowserGraphContribution } from "./page-browser-graph-contribution.loader.js";
1
+ import { loadPageBrowserGraphContribution } from "../page-browser-graph/page-browser-graph-contribution.loader.js";
2
2
  import { buildRouteHtmlFinalization } from "./route-html-finalization.service.js";
3
3
  function createIntegrationRouteRenderAdapter(host) {
4
4
  return {
@@ -0,0 +1,6 @@
1
+ export declare function decodeHtmlEntities(value: string): string;
2
+ export declare function normalizeUnresolvedMarkerArtifactHtml(html: string): string;
3
+ export declare function inspectUnresolvedMarkerArtifactHtml(html: string): {
4
+ hasUnresolvedMarkerArtifacts: boolean;
5
+ normalizedHtml: string;
6
+ };
@@ -0,0 +1,27 @@
1
+ function decodeHtmlEntities(value) {
2
+ let decoded = value;
3
+ let previous;
4
+ do {
5
+ previous = decoded;
6
+ decoded = decoded.replaceAll("&quot;", '"').replaceAll("&#39;", "'").replaceAll("&#x27;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
7
+ } while (decoded !== previous);
8
+ return decoded;
9
+ }
10
+ function normalizeUnresolvedMarkerArtifactHtml(html) {
11
+ return html.replace(
12
+ /&(?:amp;)?lt;eco-marker\b[\s\S]*?&(?:amp;)?gt;&(?:amp;)?lt;\/eco-marker&(?:amp;)?gt;/g,
13
+ (marker) => decodeHtmlEntities(marker)
14
+ );
15
+ }
16
+ function inspectUnresolvedMarkerArtifactHtml(html) {
17
+ const normalizedHtml = normalizeUnresolvedMarkerArtifactHtml(html);
18
+ return {
19
+ normalizedHtml,
20
+ hasUnresolvedMarkerArtifacts: normalizedHtml.includes("<eco-marker")
21
+ };
22
+ }
23
+ export {
24
+ decodeHtmlEntities,
25
+ inspectUnresolvedMarkerArtifactHtml,
26
+ normalizeUnresolvedMarkerArtifactHtml
27
+ };
@@ -0,0 +1,15 @@
1
+ import type { HtmlDocumentContribution } from '../../../services/html/html-transformer.service.js';
2
+ import type { PageMetadataProps, PageRobotsMetadata } from '../../../types/public-types.js';
3
+ /**
4
+ * Builds a head contribution for non-default `metadata.robots` values.
5
+ *
6
+ * @remarks
7
+ * When `robots` is omitted or matches the default `index,follow` behaviour,
8
+ * no tag is emitted (Next.js parity). Otherwise a single
9
+ * `<meta name="robots" content="...">` contribution is returned.
10
+ */
11
+ export declare function buildRobotsMetaContribution(metadata: Pick<PageMetadataProps, 'robots'>): HtmlDocumentContribution | undefined;
12
+ /**
13
+ * Formats robots directives for a meta tag, or `undefined` when defaults apply.
14
+ */
15
+ export declare function formatRobotsMetaContent(robots: PageRobotsMetadata | undefined): string | undefined;
@@ -0,0 +1,36 @@
1
+ import { escapeHtmlAttribute } from "../../../utils/html-escaping.js";
2
+ function buildRobotsMetaContribution(metadata) {
3
+ const content = formatRobotsMetaContent(metadata.robots);
4
+ if (!content) {
5
+ return void 0;
6
+ }
7
+ return {
8
+ placement: "head-append",
9
+ html: `<meta name="robots" content="${escapeHtmlAttribute(content)}">`
10
+ };
11
+ }
12
+ function formatRobotsMetaContent(robots) {
13
+ if (!robots) {
14
+ return void 0;
15
+ }
16
+ const index = robots.index !== false;
17
+ const follow = robots.follow !== false;
18
+ const directives = [];
19
+ if (!index) {
20
+ directives.push("noindex");
21
+ }
22
+ if (!follow) {
23
+ directives.push("nofollow");
24
+ }
25
+ if (robots.nocache) {
26
+ directives.push("nocache");
27
+ }
28
+ if (directives.length === 0) {
29
+ return void 0;
30
+ }
31
+ return directives.join(", ");
32
+ }
33
+ export {
34
+ buildRobotsMetaContribution,
35
+ formatRobotsMetaContent
36
+ };
@@ -1,5 +1,5 @@
1
- import type { HtmlDocumentContribution } from '../../services/html/html-transformer.service.js';
2
- import type { IntegrationRendererRenderOptions } from '../../types/public-types.js';
1
+ import type { HtmlDocumentContribution } from '../../../services/html/html-transformer.service.js';
2
+ import type { IntegrationRendererRenderOptions } from '../../../types/public-types.js';
3
3
  import type { RouteHtmlFinalization } from './route-render-orchestrator.js';
4
4
  export type RouteHtmlFinalizationContext<C> = {
5
5
  renderOptions: IntegrationRendererRenderOptions<C>;
@@ -12,5 +12,9 @@ export type RouteHtmlFinalizationContext<C> = {
12
12
  };
13
13
  /**
14
14
  * Builds the structural HTML finalization plan for one prepared route render.
15
+ *
16
+ * @remarks
17
+ * Merges core robots meta contributions with integration-owned document
18
+ * contributions so page-level `metadata.robots` works across templates.
15
19
  */
16
20
  export declare function buildRouteHtmlFinalization<C>(context: RouteHtmlFinalizationContext<C>): RouteHtmlFinalization;
@@ -1,9 +1,12 @@
1
+ import { buildRobotsMetaContribution } from "./robots-meta.contribution.js";
1
2
  function buildRouteHtmlFinalization(context) {
2
3
  const { renderOptions } = context;
3
4
  const documentAttributes = context.getDocumentAttributes(renderOptions);
4
- const htmlContributions = context.getHtmlDocumentContributions({ renderOptions, partial: false });
5
+ const integrationContributions = context.getHtmlDocumentContributions({ renderOptions, partial: false }) ?? [];
6
+ const robotsContribution = buildRobotsMetaContribution(renderOptions.metadata ?? {});
7
+ const htmlContributions = robotsContribution ? [robotsContribution, ...integrationContributions] : integrationContributions;
5
8
  const hasStructuralFinalization = documentAttributes && Object.keys(documentAttributes).length > 0;
6
- if (!hasStructuralFinalization && (!htmlContributions || htmlContributions.length === 0)) {
9
+ if (!hasStructuralFinalization && htmlContributions.length === 0) {
7
10
  return {};
8
11
  }
9
12
  return {
@@ -1,6 +1,6 @@
1
- import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
- import type { EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphResult, PageMetadataProps, EcoPageLayoutEntry, RouteRendererOptions } from '../../types/public-types.js';
3
- import { type ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
1
+ import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
2
+ import type { EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphResult, PageMetadataProps, EcoPageLayoutEntry, RouteRendererOptions } from '../../../types/public-types.js';
3
+ import { type ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
4
4
  type PreparedRenderInputs = {
5
5
  Page: EcoPageFile['default'] | EcoPageComponent<any>;
6
6
  HtmlTemplate: EcoComponent<HtmlTemplateProps>;
@@ -1,13 +1,17 @@
1
- import { createPagePackage } from "../../services/assets/asset-processing-service/index.js";
2
- import { dedupeProcessedAssets } from "./processed-asset-dedupe.js";
1
+ import { createPagePackage } from "../../../services/assets/asset-processing-service/index.js";
2
+ import { dedupeProcessedAssets } from "../page-browser-graph/processed-asset-dedupe.js";
3
3
  import { createPageLocalsProxy } from "./route-prepared-options.utils.js";
4
4
  function buildPreparedRenderOptions(input) {
5
5
  const { routeOptions, resolvedInputs, resolvedDependencies, allDependencies, pageBrowserGraph, appConfig } = input;
6
6
  const { Page, HtmlTemplate, Layouts, Layout, layoutEntries, props, metadata, integrationSpecificProps } = resolvedInputs;
7
7
  const dedupedDependencies = dedupeProcessedAssets(allDependencies);
8
8
  const pagePackage = createPagePackage(dedupedDependencies, { pageBrowserGraph });
9
- const pageProps = {
9
+ const resolvedProps = {
10
10
  ...props,
11
+ ...routeOptions.props ?? {}
12
+ };
13
+ const pageProps = {
14
+ ...resolvedProps,
11
15
  params: routeOptions.params || {},
12
16
  query: routeOptions.query || {}
13
17
  };
@@ -25,7 +29,7 @@ function buildPreparedRenderOptions(input) {
25
29
  Layouts,
26
30
  Layout,
27
31
  layoutEntries,
28
- props,
32
+ props: resolvedProps,
29
33
  Page,
30
34
  metadata,
31
35
  params: routeOptions.params || {},
@@ -1,4 +1,4 @@
1
- import { LocalsAccessError } from "../../errors/locals-access-error.js";
1
+ import { LocalsAccessError } from "../../../errors/locals-access-error.js";
2
2
  function createPageLocalsProxy(filePath) {
3
3
  const errorMessage = `[ecopages] Request locals are only available during request-time rendering with cache: 'dynamic'. Page: ${filePath}. If you meant to use locals here, set cache: 'dynamic' and provide locals from route middleware/handlers.`;
4
4
  return new Proxy(
@@ -1,9 +1,9 @@
1
- import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
- import type { EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult, EcoPageLayoutEntry } from '../../types/public-types.js';
3
- import type { AssetProcessingService, ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
4
- import type { HtmlDocumentContribution } from '../../services/html/html-transformer.service.js';
5
- import { OwnershipValidationService } from './ownership-validation.service.js';
6
- import { PageBrowserGraphService } from './page-browser-graph.service.js';
1
+ import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
2
+ import type { EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult, EcoPageLayoutEntry } from '../../../types/public-types.js';
3
+ import type { AssetProcessingService, ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
4
+ import type { HtmlDocumentContribution } from '../../../services/html/html-transformer.service.js';
5
+ import { OwnershipValidationService } from '../ownership-graph/ownership-validation.service.js';
6
+ import { PageBrowserGraphService } from '../page-browser-graph/page-browser-graph.service.js';
7
7
  export type RouteRenderOrchestratorResolvedInputs = {
8
8
  Page: EcoPageFile['default'] | EcoPageComponent<any>;
9
9
  HtmlTemplate: EcoComponent<HtmlTemplateProps>;
@@ -1,12 +1,15 @@
1
- import { inspectUnresolvedMarkerArtifactHtml } from "./render-output.utils.js";
2
- import { OwnershipValidationService, throwIfOwnershipInvalid } from "./ownership-validation.service.js";
1
+ import { inspectUnresolvedMarkerArtifactHtml } from "./marker-artifact.utils.js";
2
+ import {
3
+ OwnershipValidationService,
4
+ throwIfOwnershipInvalid
5
+ } from "../ownership-graph/ownership-validation.service.js";
3
6
  import {
4
7
  buildEagerSsrLazyAssetsFromGraph,
5
8
  collectResolvedLazyTriggersFromGraph,
6
9
  collectUsedIntegrationDependenciesFromGraph
7
- } from "./component-graph-collectors.js";
8
- import { buildGlobalInjectorAssets } from "./global-injector-assets.service.js";
9
- import { PageBrowserGraphService } from "./page-browser-graph.service.js";
10
+ } from "../ownership-graph/component-graph-collectors.js";
11
+ import { buildGlobalInjectorAssets } from "../page-browser-graph/global-injector-assets.service.js";
12
+ import { PageBrowserGraphService } from "../page-browser-graph/page-browser-graph.service.js";
10
13
  import { buildPreparedRenderOptions } from "./route-prepared-options.builder.js";
11
14
  class RouteRenderOrchestrator {
12
15
  appConfig;
@@ -1,5 +1,5 @@
1
1
  import { IntegrationRenderer } from "./integration-renderer.js";
2
- import { resolveInnermostPageLayout } from "./layout-shell-props.service.js";
2
+ import { resolveInnermostPageLayout } from "./document-shell/layout-shell-props.service.js";
3
3
  class StringMarkupRenderer extends IntegrationRenderer {
4
4
  async renderComponent(input) {
5
5
  if (typeof input.component !== "function") {
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  buildProcessedAssetDedupeKey,
6
6
  dedupeProcessedAssets
7
- } from "../../route-renderer/orchestration/processed-asset-dedupe.js";
7
+ } from "../../route-renderer/orchestration/page-browser-graph/processed-asset-dedupe.js";
8
8
  class HtmlTransformerService {
9
9
  processedDependencies = [];
10
10
  pagePackage;
@@ -87,6 +87,11 @@ export declare class DevelopmentInvalidationService {
87
87
  isServerModuleSourceFile(filePath: string): boolean;
88
88
  /**
89
89
  * Returns whether a processor owns the changed file as an asset input.
90
+ *
91
+ * @remarks
92
+ * Watch config drives processor notifications only. Asset ownership requires
93
+ * declared capabilities so dependency-only watches (for example content
94
+ * collection MDX scans) do not skip server invalidation and HMR.
90
95
  */
91
96
  isProcessorOwnedAsset(filePath: string): boolean;
92
97
  }
@@ -218,21 +218,20 @@ class DevelopmentInvalidationService {
218
218
  }
219
219
  /**
220
220
  * Returns whether a processor owns the changed file as an asset input.
221
+ *
222
+ * @remarks
223
+ * Watch config drives processor notifications only. Asset ownership requires
224
+ * declared capabilities so dependency-only watches (for example content
225
+ * collection MDX scans) do not skip server invalidation and HMR.
221
226
  */
222
227
  isProcessorOwnedAsset(filePath) {
223
228
  for (const processor of this.appConfig.processors.values()) {
224
229
  const capabilities = processor.getAssetCapabilities?.() ?? [];
225
- if (capabilities.length > 0) {
226
- const matchesConfiguredAsset = typeof processor.matchesFileFilter !== "function" || processor.matchesFileFilter(filePath);
227
- if (matchesConfiguredAsset && capabilities.some((capability) => processor.canProcessAsset?.(capability.kind, filePath))) {
228
- return true;
229
- }
230
+ if (capabilities.length === 0) {
230
231
  continue;
231
232
  }
232
- const watchConfig = processor.getWatchConfig();
233
- if (!watchConfig) continue;
234
- const { extensions = [] } = watchConfig;
235
- if (extensions.length && extensions.some((ext) => filePath.endsWith(ext))) {
233
+ const matchesConfiguredAsset = typeof processor.matchesFileFilter !== "function" || processor.matchesFileFilter(filePath);
234
+ if (matchesConfiguredAsset && capabilities.some((capability) => processor.canProcessAsset?.(capability.kind, filePath))) {
236
235
  return true;
237
236
  }
238
237
  }
@@ -29,8 +29,10 @@ It should not invent a parallel rendering stack just for build mode.
29
29
 
30
30
  | File | Role |
31
31
  | ------------------------------ | ---------------------------------------------------------------------------- |
32
- | `static-site-generator.ts` | Route enumeration, HTML artifact writes, integration export hooks |
32
+ | `static-site-generator.ts` | Route enumeration, HTML artifact writes, integration export hooks, sitemap |
33
33
  | `static-export-context.ts` | Hook context type for `beforeStaticExport` / `afterStaticExport` |
34
+ | `sitemap.ts` | Pure sitemap.xml renderer |
35
+ | `sitemap-routes.ts` | Sitemap location assembly (`exclude`, `extraUrls`, dedupe) |
34
36
  | `static-build-invalidation.ts` | `dist/` reset policy, production cache clearing, static-render cache context |
35
37
 
36
38
  Build-input fingerprinting (`hashAppConfigFile`, `createBuildInputsFingerprint`) lives in `packages/core/src/build/cache/build-input-fingerprint.ts` and is shared with server-entry and unified-graph caches.
@@ -56,4 +58,10 @@ Integrations may implement:
56
58
  - `beforeStaticExport(context)` — runs after unified-graph prebuild, before page rendering
57
59
  - `afterStaticExport(context)` — runs in a `finally` block after generation completes
58
60
 
61
+ When `appConfig.sitemap.enabled` is true, `sitemap.xml` is written **after** `afterStaticExport` so integration-generated URLs can be listed via `extraUrls`.
62
+
63
+ Sitemap eligibility is decided during successful page export (`activeStaticPathnames` plus `robots.index !== false`, fail-closed on metadata errors). `resolveSitemapLocations` then applies `exclude` and appends `extraUrls`.
64
+
65
+ `StaticExportContext.routes` is the unfiltered static-generation route list. Sitemap filtering is applied separately.
66
+
59
67
  See `StaticExportContext` in `static-export-context.ts`.
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { getAppPageBrowserGraphSession } from "../route-renderer/orchestration/page-browser-graph-session.js";
2
+ import { getAppPageBrowserGraphSession } from "../route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js";
3
3
  function shouldPrebuildProductionPageBrowserGraphs() {
4
4
  return process.env.NODE_ENV === "production";
5
5
  }
@@ -0,0 +1,14 @@
1
+ import type { SitemapConfig } from '../types/public-types.js';
2
+ /**
3
+ * Builds the ordered absolute locations for sitemap.xml.
4
+ *
5
+ * @remarks
6
+ * `eligiblePathnames` must already be successfully exported and robots-indexable.
7
+ * Filtering here is only `sitemap.exclude`, then `extraUrls` are appended.
8
+ * `extraUrls` always include (deduped) and are not subject to `exclude` or page robots.
9
+ */
10
+ export declare function resolveSitemapLocations(input: {
11
+ eligiblePathnames: readonly string[];
12
+ sitemap: SitemapConfig;
13
+ baseUrl: string;
14
+ }): string[];
@@ -0,0 +1,31 @@
1
+ import { matchesAnyPathPattern, normalizePathname } from "../utils/path-pattern.js";
2
+ import { buildSitemapLocation } from "./sitemap.js";
3
+ function resolveSitemapLocations(input) {
4
+ const exclude = input.sitemap.exclude ?? [];
5
+ const locations = [];
6
+ const seen = /* @__PURE__ */ new Set();
7
+ for (const pathname of input.eligiblePathnames) {
8
+ const normalized = normalizePathname(pathname);
9
+ if (matchesAnyPathPattern(normalized, exclude)) {
10
+ continue;
11
+ }
12
+ const location = buildSitemapLocation(input.baseUrl, normalized);
13
+ if (seen.has(location)) {
14
+ continue;
15
+ }
16
+ seen.add(location);
17
+ locations.push(location);
18
+ }
19
+ for (const extraUrl of input.sitemap.extraUrls ?? []) {
20
+ const location = buildSitemapLocation(input.baseUrl, extraUrl);
21
+ if (seen.has(location)) {
22
+ continue;
23
+ }
24
+ seen.add(location);
25
+ locations.push(location);
26
+ }
27
+ return locations;
28
+ }
29
+ export {
30
+ resolveSitemapLocations
31
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Joins a base origin with a pathname or absolute URL into a sitemap `<loc>` value.
3
+ *
4
+ * @remarks
5
+ * Trailing slashes are stripped except for the site root (`/`). Absolute `http(s)`
6
+ * URLs in `extraUrls` are returned as-is (still slash-normalized).
7
+ */
8
+ export declare function buildSitemapLocation(baseUrl: string, pathnameOrUrl: string): string;
9
+ /**
10
+ * Renders a sitemap.org 0.9 urlset document from absolute location URLs.
11
+ *
12
+ * @remarks
13
+ * Callers are responsible for deduplication; this serializer preserves input order.
14
+ */
15
+ export declare function renderSitemap(locations: readonly string[]): string;
@@ -0,0 +1,33 @@
1
+ import { escapeXmlText } from "../utils/html-escaping.js";
2
+ import { normalizePathname } from "../utils/path-pattern.js";
3
+ function buildSitemapLocation(baseUrl, pathnameOrUrl) {
4
+ const trimmed = pathnameOrUrl.trim();
5
+ if (/^https?:\/\//i.test(trimmed)) {
6
+ return normalizeLocation(trimmed);
7
+ }
8
+ const origin = baseUrl.replace(/\/+$/, "");
9
+ const pathname = normalizePathname(trimmed);
10
+ return pathname === "/" ? `${origin}/` : `${origin}${pathname}`;
11
+ }
12
+ function renderSitemap(locations) {
13
+ const urls = locations.map((loc) => ` <url>
14
+ <loc>${escapeXmlText(normalizeLocation(loc))}</loc>
15
+ </url>`).join("\n");
16
+ return [
17
+ '<?xml version="1.0" encoding="UTF-8"?>',
18
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
19
+ urls,
20
+ "</urlset>",
21
+ ""
22
+ ].join("\n");
23
+ }
24
+ function normalizeLocation(location) {
25
+ if (/^https?:\/\/[^/]+\/?$/i.test(location)) {
26
+ return `${location.replace(/\/+$/, "")}/`;
27
+ }
28
+ return location.replace(/\/+$/, "");
29
+ }
30
+ export {
31
+ buildSitemapLocation,
32
+ renderSitemap
33
+ };
@@ -1,5 +1,5 @@
1
1
  import type { EcoPagesAppConfig } from '../types/internal-types.js';
2
- import type { StaticRoute } from '../types/public-types.js';
2
+ import type { EcopagesRouteInfo, StaticRoute } from '../types/public-types.js';
3
3
  import type { StaticGenerationRendererResolver } from '../route-renderer/route-renderer.js';
4
4
  import type { StaticGenerationRoute } from '../router/server/route-registry.js';
5
5
  type StaticGenerationRouteSource = {
@@ -18,5 +18,13 @@ export interface StaticExportContext {
18
18
  staticRoutes?: StaticRoute[];
19
19
  force: boolean;
20
20
  preserveExportDirectory: boolean;
21
+ /**
22
+ * Unfiltered static-generation routes (exact + expanded dynamic).
23
+ *
24
+ * @remarks
25
+ * Sitemap filtering is applied separately; integrations that build custom
26
+ * artifacts should start from this full list.
27
+ */
28
+ routes: EcopagesRouteInfo[];
21
29
  }
22
30
  export {};
@@ -1,5 +1,5 @@
1
1
  import type { EcoPagesAppConfig } from '../types/internal-types.js';
2
- import type { StaticRoute } from '../types/public-types.js';
2
+ import type { SitemapConfig, StaticRoute } from '../types/public-types.js';
3
3
  import type { PageRendererResolver, StaticGenerationRendererResolver } from '../route-renderer/route-renderer.js';
4
4
  import type { StaticGenerationRoute } from '../router/server/route-registry.js';
5
5
  import type { RouteModuleBuildCache } from '../services/module-loading/route-module-build-cache.store.js';
@@ -31,6 +31,7 @@ export declare class StaticSiteGenerator {
31
31
  private readonly routeModuleBuildCacheOverride?;
32
32
  private staticRenderCacheContext;
33
33
  private forceFullStaticGeneration;
34
+ private pageModuleLoader?;
34
35
  /**
35
36
  * Creates the static-site generator for one app config.
36
37
  */
@@ -46,6 +47,10 @@ export declare class StaticSiteGenerator {
46
47
  * Writes the robots.txt file declared by the app config.
47
48
  */
48
49
  generateRobotsTxt(): void;
50
+ /**
51
+ * Writes the configured sitemap file to the export directory.
52
+ */
53
+ generateSitemap(locations: readonly string[], sitemap: SitemapConfig): void;
49
54
  /**
50
55
  * Returns whether the input path points at the root directory.
51
56
  */
@@ -56,6 +61,18 @@ export declare class StaticSiteGenerator {
56
61
  getDirectories(routes: string[]): string[];
57
62
  private writeStaticOutput;
58
63
  private writeStaticPageArtifact;
64
+ /**
65
+ * Marks a successfully exported pathname as sitemap-eligible when robots allow indexing.
66
+ *
67
+ * @remarks
68
+ * Fail-closed: metadata resolution errors omit the URL. `robots.index === false`
69
+ * also omits it. Eligibility is decided during generation, not by reloading modules
70
+ * after `afterStaticExport`.
71
+ */
72
+ private considerSitemapEligibility;
73
+ private getPageModuleLoader;
74
+ private resolveFilesystemPageMetadata;
75
+ private resolveExplicitViewMetadata;
59
76
  private pruneStaleStaticOutputs;
60
77
  private createFilesystemStaticContents;
61
78
  /**
@@ -64,7 +81,15 @@ export declare class StaticSiteGenerator {
64
81
  * @remarks
65
82
  * Routes are rendered through the normal route renderer directly.
66
83
  */
67
- generateStaticPages(router: StaticGenerationRouteSource, baseUrl: string, routeRendererFactory?: StaticPageRouteRendererFactory, skipped?: string[], activeStaticPathnames?: Set<string>, preloadedRoutes?: readonly StaticGenerationRoute[]): Promise<void>;
84
+ generateStaticPages(input: {
85
+ router: StaticGenerationRouteSource;
86
+ baseUrl: string;
87
+ routeRendererFactory?: StaticPageRouteRendererFactory;
88
+ skipped?: string[];
89
+ activeStaticPathnames?: Set<string>;
90
+ preloadedRoutes?: readonly StaticGenerationRoute[];
91
+ sitemapEligiblePathnames?: Set<string>;
92
+ }): Promise<void>;
68
93
  private createStaticExportContext;
69
94
  private invokeStaticExportHook;
70
95
  /**
@@ -78,10 +103,6 @@ export declare class StaticSiteGenerator {
78
103
  force?: boolean;
79
104
  preserveExportDirectory?: boolean;
80
105
  }): Promise<void>;
81
- /**
82
- * Generates static pages from explicit static routes registered via app.static().
83
- * These routes use eco.page views via loader functions for HMR support.
84
- */
85
106
  private generateExplicitStaticPages;
86
107
  private resolveExplicitViewSourceFile;
87
108
  private generateExplicitStaticRoute;