@ecopages/core 0.2.0-alpha.25 → 0.2.0-alpha.27

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 (111) hide show
  1. package/README.md +63 -7
  2. package/package.json +4 -47
  3. package/src/adapters/bun/create-app.ts +54 -2
  4. package/src/adapters/bun/hmr-manager.test.ts +0 -2
  5. package/src/adapters/bun/hmr-manager.ts +1 -24
  6. package/src/adapters/bun/server-adapter.ts +30 -4
  7. package/src/adapters/node/node-hmr-manager.test.ts +0 -2
  8. package/src/adapters/node/node-hmr-manager.ts +2 -25
  9. package/src/adapters/shared/explicit-static-render-preparation.ts +58 -0
  10. package/src/adapters/shared/explicit-static-route-matcher.test.ts +6 -6
  11. package/src/adapters/shared/explicit-static-route-matcher.ts +22 -31
  12. package/src/adapters/shared/file-route-middleware-pipeline.test.ts +5 -10
  13. package/src/adapters/shared/file-route-middleware-pipeline.ts +8 -17
  14. package/src/adapters/shared/fs-server-response-factory.test.ts +32 -43
  15. package/src/adapters/shared/fs-server-response-factory.ts +15 -37
  16. package/src/adapters/shared/fs-server-response-matcher.test.ts +65 -39
  17. package/src/adapters/shared/fs-server-response-matcher.ts +94 -43
  18. package/src/adapters/shared/hmr-manager.contract.test.ts +0 -4
  19. package/src/adapters/shared/render-context.ts +3 -3
  20. package/src/adapters/shared/server-adapter.test.ts +53 -0
  21. package/src/adapters/shared/server-adapter.ts +228 -159
  22. package/src/adapters/shared/server-route-handler.test.ts +6 -5
  23. package/src/adapters/shared/server-route-handler.ts +4 -4
  24. package/src/adapters/shared/server-static-builder.test.ts +4 -4
  25. package/src/adapters/shared/server-static-builder.ts +4 -4
  26. package/src/config/README.md +1 -1
  27. package/src/config/config-builder.test.ts +0 -1
  28. package/src/config/config-builder.ts +2 -7
  29. package/src/dev/host-runtime.ts +34 -0
  30. package/src/eco/eco.browser.test.ts +2 -2
  31. package/src/eco/eco.browser.ts +2 -2
  32. package/src/eco/eco.test.ts +6 -6
  33. package/src/eco/eco.ts +12 -12
  34. package/src/eco/eco.types.ts +3 -3
  35. package/src/errors/index.ts +1 -0
  36. package/src/hmr/client/hmr-runtime.ts +4 -2
  37. package/src/hmr/strategies/js-hmr-strategy.test.ts +0 -1
  38. package/src/hmr/strategies/js-hmr-strategy.ts +0 -6
  39. package/src/integrations/ghtml/ghtml-renderer.test.ts +7 -7
  40. package/src/integrations/ghtml/ghtml-renderer.ts +1 -11
  41. package/src/plugins/eco-component-meta-plugin.ts +0 -1
  42. package/src/plugins/integration-plugin.test.ts +9 -14
  43. package/src/plugins/integration-plugin.ts +34 -22
  44. package/src/plugins/processor.ts +17 -0
  45. package/src/route-renderer/GRAPH.md +81 -289
  46. package/src/route-renderer/README.md +67 -105
  47. package/src/route-renderer/orchestration/component-render-context.ts +45 -38
  48. package/src/route-renderer/orchestration/declared-ownership-graph.ts +62 -0
  49. package/src/route-renderer/orchestration/foreign-subtree-execution.service.ts +383 -0
  50. package/src/route-renderer/orchestration/integration-renderer.test.ts +118 -121
  51. package/src/route-renderer/orchestration/integration-renderer.ts +362 -403
  52. package/src/route-renderer/orchestration/ownership-planning.service.ts +97 -0
  53. package/src/route-renderer/orchestration/ownership-validation.service.ts +76 -0
  54. package/src/route-renderer/orchestration/processed-asset-dedupe.ts +1 -1
  55. package/src/route-renderer/orchestration/{queued-boundary-runtime.service.test.ts → queued-foreign-subtree-resolution.service.test.ts} +76 -71
  56. package/src/route-renderer/orchestration/{queued-boundary-runtime.service.ts → queued-foreign-subtree-resolution.service.ts} +68 -63
  57. package/src/route-renderer/orchestration/render-output.utils.ts +21 -13
  58. package/src/route-renderer/orchestration/{render-preparation.service.test.ts → route-render-orchestrator.prepare-render-options.test.ts} +160 -85
  59. package/src/route-renderer/orchestration/route-render-orchestrator.test.ts +265 -0
  60. package/src/route-renderer/orchestration/{render-preparation.service.ts → route-render-orchestrator.ts} +244 -160
  61. package/src/route-renderer/page-loading/component-dependency-collection.ts +9 -3
  62. package/src/route-renderer/page-loading/declared-asset-collection.ts +2 -5
  63. package/src/route-renderer/page-loading/dependency-resolver.test.ts +107 -11
  64. package/src/route-renderer/page-loading/dependency-resolver.ts +6 -12
  65. package/src/route-renderer/page-loading/ecopages-virtual-imports.ts +1 -1
  66. package/src/route-renderer/page-loading/lazy-entry-collection.ts +1 -1
  67. package/src/route-renderer/page-loading/lazy-trigger-planning.ts +1 -1
  68. package/src/route-renderer/page-loading/module-declaration-aggregation.ts +1 -1
  69. package/src/route-renderer/page-loading/module-declaration-scripts.ts +1 -1
  70. package/src/route-renderer/page-loading/page-dependency-bundling.ts +105 -66
  71. package/src/route-renderer/route-renderer.ts +28 -31
  72. package/src/router/README.md +16 -19
  73. package/src/router/server/route-registry.test.ts +176 -0
  74. package/src/router/server/route-registry.ts +382 -0
  75. package/src/services/README.md +1 -2
  76. package/src/services/assets/asset-processing-service/asset-dependency-keys.ts +1 -1
  77. package/src/services/assets/asset-processing-service/asset-processing.service.test.ts +1 -4
  78. package/src/services/assets/asset-processing-service/asset-processing.service.ts +1 -2
  79. package/src/services/assets/asset-processing-service/assets.types.ts +3 -0
  80. package/src/services/assets/asset-processing-service/grouped-content-bundles.ts +1 -1
  81. package/src/services/assets/asset-processing-service/index.ts +1 -0
  82. package/src/{route-renderer/orchestration/page-packaging.service.test.ts → services/assets/asset-processing-service/page-package.test.ts} +38 -14
  83. package/src/services/assets/asset-processing-service/page-package.ts +93 -0
  84. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.ts +4 -5
  85. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.test.ts +13 -10
  86. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.ts +3 -0
  87. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.ts +6 -0
  88. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.ts +2 -0
  89. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.ts +1 -0
  90. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.ts +2 -0
  91. package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.ts +1 -1
  92. package/src/services/html/html-transformer.service.test.ts +1 -4
  93. package/src/services/module-loading/app-server-module-transpiler.service.ts +1 -3
  94. package/src/services/module-loading/node-bootstrap-plugin.ts +17 -3
  95. package/src/services/module-loading/page-module-import.service.ts +0 -1
  96. package/src/services/module-loading/source-module-support.ts +1 -1
  97. package/src/static-site-generator/static-site-generator.test.ts +124 -32
  98. package/src/static-site-generator/static-site-generator.ts +168 -185
  99. package/src/types/internal-types.ts +13 -12
  100. package/src/types/public-types.ts +55 -39
  101. package/src/watchers/project-watcher.test-helpers.ts +4 -3
  102. package/src/route-renderer/orchestration/boundary-planning.service.ts +0 -146
  103. package/src/route-renderer/orchestration/page-packaging.service.ts +0 -85
  104. package/src/route-renderer/orchestration/render-execution.service.test.ts +0 -196
  105. package/src/route-renderer/orchestration/render-execution.service.ts +0 -182
  106. package/src/route-renderer/orchestration/route-shell-composer.service.ts +0 -162
  107. package/src/router/server/fs-router-scanner.test.ts +0 -83
  108. package/src/router/server/fs-router-scanner.ts +0 -224
  109. package/src/router/server/fs-router.test.ts +0 -214
  110. package/src/router/server/fs-router.ts +0 -122
  111. package/src/services/runtime-state/runtime-specifier-registry.service.ts +0 -96
@@ -1,10 +1,9 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import type { ProcessedAsset } from '../../services/assets/asset-processing-service/index.ts';
3
- import { PagePackagingService } from './page-packaging.service.ts';
2
+ import type { ProcessedAsset } from './assets.types.ts';
3
+ import { createPagePackage } from './page-package.ts';
4
4
 
5
- describe('PagePackagingService', () => {
5
+ describe('createPagePackage', () => {
6
6
  it('classifies page assets, inline assets, separate assets, and dynamic chunks', () => {
7
- const service = new PagePackagingService();
8
7
  const pageScript = {
9
8
  kind: 'script',
10
9
  srcUrl: '/assets/page.js',
@@ -35,13 +34,7 @@ describe('PagePackagingService', () => {
35
34
  packageRole: 'dynamic-chunk',
36
35
  } as ProcessedAsset;
37
36
 
38
- const result = service.createPagePackage([
39
- pageStylesheet,
40
- pageScript,
41
- runtimeScript,
42
- inlineScript,
43
- dynamicChunk,
44
- ]);
37
+ const result = createPagePackage([pageStylesheet, pageScript, runtimeScript, inlineScript, dynamicChunk]);
45
38
 
46
39
  expect(result.pageScript).toBe(pageScript);
47
40
  expect(result.pageStylesheet).toBe(pageStylesheet);
@@ -52,7 +45,6 @@ describe('PagePackagingService', () => {
52
45
  });
53
46
 
54
47
  it('prefers explicit page roles over visibility heuristics', () => {
55
- const service = new PagePackagingService();
56
48
  const hiddenPageScript = {
57
49
  kind: 'script',
58
50
  srcUrl: '/assets/pages/index.js',
@@ -67,10 +59,42 @@ describe('PagePackagingService', () => {
67
59
  packageRole: 'keep-separate',
68
60
  } as ProcessedAsset;
69
61
 
70
- const result = service.createPagePackage([hiddenPageScript, hydrationBootstrap]);
62
+ const result = createPagePackage([hiddenPageScript, hydrationBootstrap]);
71
63
 
72
64
  expect(result.pageScript).toBe(hiddenPageScript);
73
65
  expect(result.htmlAssets).toEqual([hydrationBootstrap]);
74
66
  expect(result.separateAssets).toEqual([hydrationBootstrap]);
75
67
  });
76
- });
68
+
69
+ it('suppresses bundled source stylesheet assets from final html assets', () => {
70
+ const pageStylesheet = {
71
+ kind: 'stylesheet',
72
+ srcUrl: '/assets/page.css',
73
+ position: 'head',
74
+ packageRole: 'page-style',
75
+ bundledSourceFilepaths: ['/app/src/styles/tailwind.css', '/app/src/styles/fonts.css'],
76
+ } as ProcessedAsset;
77
+ const bundledTailwind = {
78
+ kind: 'stylesheet',
79
+ srcUrl: '/assets/styles/tailwind.css',
80
+ position: 'head',
81
+ sourceFilepath: '/app/src/styles/tailwind.css',
82
+ } as ProcessedAsset;
83
+ const bundledFonts = {
84
+ kind: 'stylesheet',
85
+ srcUrl: '/assets/styles/fonts.css',
86
+ position: 'head',
87
+ sourceFilepath: '/app/src/styles/fonts.css',
88
+ } as ProcessedAsset;
89
+ const pageScript = {
90
+ kind: 'script',
91
+ srcUrl: '/assets/page.js',
92
+ position: 'head',
93
+ } as ProcessedAsset;
94
+
95
+ const result = createPagePackage([pageStylesheet, bundledTailwind, bundledFonts, pageScript]);
96
+
97
+ expect(result.pageStylesheet).toBe(pageStylesheet);
98
+ expect(result.htmlAssets).toEqual([pageStylesheet, pageScript]);
99
+ });
100
+ });
@@ -0,0 +1,93 @@
1
+ import type { PagePackageResult } from '../../../types/public-types.ts';
2
+ import type { ProcessedAsset } from './assets.types.ts';
3
+
4
+ function getSuppressedSourceFilepaths(assets: ProcessedAsset[]): Set<string> {
5
+ const suppressed = new Set<string>();
6
+
7
+ for (const asset of assets) {
8
+ if (
9
+ (asset.packageRole === 'page-style' || asset.packageRole === 'page-script') &&
10
+ Array.isArray(asset.bundledSourceFilepaths)
11
+ ) {
12
+ for (const filepath of asset.bundledSourceFilepaths) {
13
+ suppressed.add(filepath);
14
+ }
15
+ }
16
+ }
17
+
18
+ return suppressed;
19
+ }
20
+
21
+ export function createPagePackage(assets: ProcessedAsset[]): PagePackageResult {
22
+ const inlineAssets: ProcessedAsset[] = [];
23
+ const separateAssets: ProcessedAsset[] = [];
24
+ const dynamicChunks: ProcessedAsset[] = [];
25
+ let pageScript: ProcessedAsset | undefined;
26
+ let pageStylesheet: ProcessedAsset | undefined;
27
+ const suppressedSourceFilepaths = getSuppressedSourceFilepaths(assets);
28
+
29
+ for (const asset of assets) {
30
+ if (asset.inline) {
31
+ inlineAssets.push(asset);
32
+ continue;
33
+ }
34
+
35
+ if (asset.packageRole === 'dynamic-chunk') {
36
+ dynamicChunks.push(asset);
37
+ continue;
38
+ }
39
+
40
+ if (!pageScript && asset.packageRole === 'page-script') {
41
+ pageScript = asset;
42
+ continue;
43
+ }
44
+
45
+ if (!pageStylesheet && asset.packageRole === 'page-style') {
46
+ pageStylesheet = asset;
47
+ continue;
48
+ }
49
+
50
+ if (asset.packageRole === 'keep-separate' || asset.packageRole === 'runtime') {
51
+ separateAssets.push(asset);
52
+ continue;
53
+ }
54
+
55
+ if (!pageScript && asset.kind === 'script' && !asset.excludeFromHtml) {
56
+ pageScript = asset;
57
+ continue;
58
+ }
59
+
60
+ if (!pageStylesheet && asset.kind === 'stylesheet') {
61
+ pageStylesheet = asset;
62
+ continue;
63
+ }
64
+
65
+ separateAssets.push(asset);
66
+ }
67
+
68
+ return {
69
+ assets,
70
+ htmlAssets: assets.filter((asset) => shouldIncludeInHtml(asset, suppressedSourceFilepaths)),
71
+ pageScript,
72
+ pageStylesheet,
73
+ inlineAssets,
74
+ separateAssets,
75
+ dynamicChunks,
76
+ };
77
+ }
78
+
79
+ function shouldIncludeInHtml(asset: ProcessedAsset, suppressedSourceFilepaths: Set<string>): boolean {
80
+ if (asset.excludeFromHtml) {
81
+ return false;
82
+ }
83
+
84
+ if (asset.packageRole === 'runtime') {
85
+ return false;
86
+ }
87
+
88
+ if (asset.sourceFilepath && suppressedSourceFilepaths.has(asset.sourceFilepath)) {
89
+ return false;
90
+ }
91
+
92
+ return true;
93
+ }
@@ -6,10 +6,7 @@ import { fileSystem } from '@ecopages/file-system';
6
6
  import path from 'node:path';
7
7
  import type { ScriptAsset } from '../../assets.types.ts';
8
8
  import { BaseProcessor } from './base-processor.ts';
9
- import {
10
- BrowserBundleService,
11
- type BrowserBundleGroupedEntry,
12
- } from '../../../browser-bundle.service.ts';
9
+ import { BrowserBundleService, type BrowserBundleGroupedEntry } from '../../../browser-bundle.service.ts';
13
10
 
14
11
  export abstract class BaseScriptProcessor<T extends ScriptAsset> extends BaseProcessor<T> {
15
12
  private readonly browserBundleService: BrowserBundleService;
@@ -151,7 +148,9 @@ export abstract class BaseScriptProcessor<T extends ScriptAsset> extends BasePro
151
148
  continue;
152
149
  }
153
150
 
154
- const hashedOutput = outputs.find((outputPath) => path.basename(outputPath).startsWith(`${entry.entryName}-`));
151
+ const hashedOutput = outputs.find((outputPath) =>
152
+ path.basename(outputPath).startsWith(`${entry.entryName}-`),
153
+ );
155
154
  if (hashedOutput) {
156
155
  entryOutputs.set(entry.entryName, hashedOutput);
157
156
  continue;
@@ -143,15 +143,18 @@ describe('ContentScriptProcessor', () => {
143
143
 
144
144
  test('processGrouped should fall back to per-entry processing when bundling is disabled', async () => {
145
145
  const processor = new ContentScriptProcessor({ appConfig: createMockConfig() });
146
- const processSpy = vi.spyOn(processor, 'process').mockResolvedValueOnce({
147
- kind: 'script',
148
- inline: false,
149
- filepath: '/tmp/first.js',
150
- }).mockResolvedValueOnce({
151
- kind: 'script',
152
- inline: false,
153
- filepath: '/tmp/second.js',
154
- });
146
+ const processSpy = vi
147
+ .spyOn(processor, 'process')
148
+ .mockResolvedValueOnce({
149
+ kind: 'script',
150
+ inline: false,
151
+ filepath: '/tmp/first.js',
152
+ })
153
+ .mockResolvedValueOnce({
154
+ kind: 'script',
155
+ inline: false,
156
+ filepath: '/tmp/second.js',
157
+ });
155
158
 
156
159
  const results = await processor.processGrouped([
157
160
  {
@@ -189,4 +192,4 @@ describe('ContentScriptProcessor', () => {
189
192
 
190
193
  expect(fileSystem.remove).toHaveBeenCalledTimes(1);
191
194
  });
192
- });
195
+ });
@@ -62,6 +62,7 @@ export class ContentScriptProcessor extends BaseScriptProcessor<ContentScriptAss
62
62
  excludeFromHtml: dep.excludeFromHtml,
63
63
  packageRole: dep.packageRole,
64
64
  groupedBundle: dep.groupedBundle,
65
+ bundledSourceFilepaths: dep.bundledSourceFilepaths,
65
66
  };
66
67
  });
67
68
  } finally {
@@ -87,6 +88,7 @@ export class ContentScriptProcessor extends BaseScriptProcessor<ContentScriptAss
87
88
  inline: dep.inline,
88
89
  excludeFromHtml: dep.excludeFromHtml,
89
90
  packageRole: dep.packageRole,
91
+ bundledSourceFilepaths: dep.bundledSourceFilepaths,
90
92
  };
91
93
 
92
94
  this.writeCacheFile(filename, unbundledProcessedAsset);
@@ -120,6 +122,7 @@ export class ContentScriptProcessor extends BaseScriptProcessor<ContentScriptAss
120
122
  excludeFromHtml: dep.excludeFromHtml,
121
123
  packageRole: dep.packageRole,
122
124
  groupedBundle: dep.groupedBundle,
125
+ bundledSourceFilepaths: dep.bundledSourceFilepaths,
123
126
  };
124
127
 
125
128
  fileSystem.remove(tempFileName);
@@ -39,6 +39,7 @@ export class FileScriptProcessor extends BaseScriptProcessor<FileScriptAsset> {
39
39
  const outputFilepath = this.resolveHmrOutputFilepath(dep.filepath);
40
40
  return {
41
41
  filepath: outputFilepath ?? dep.filepath,
42
+ sourceFilepath: dep.filepath,
42
43
  srcUrl: outputUrl,
43
44
  kind: 'script',
44
45
  position: dep.position,
@@ -46,6 +47,7 @@ export class FileScriptProcessor extends BaseScriptProcessor<FileScriptAsset> {
46
47
  inline: false,
47
48
  excludeFromHtml: dep.excludeFromHtml,
48
49
  packageRole: dep.packageRole,
50
+ bundledSourceFilepaths: dep.bundledSourceFilepaths,
49
51
  };
50
52
  }
51
53
 
@@ -72,6 +74,7 @@ export class FileScriptProcessor extends BaseScriptProcessor<FileScriptAsset> {
72
74
 
73
75
  return {
74
76
  filepath,
77
+ sourceFilepath: dep.filepath,
75
78
  content,
76
79
  kind: 'script',
77
80
  position: dep.position,
@@ -79,6 +82,7 @@ export class FileScriptProcessor extends BaseScriptProcessor<FileScriptAsset> {
79
82
  inline: dep.inline,
80
83
  excludeFromHtml: dep.excludeFromHtml,
81
84
  packageRole: dep.packageRole,
85
+ bundledSourceFilepaths: dep.bundledSourceFilepaths,
82
86
  };
83
87
  }
84
88
 
@@ -97,6 +101,7 @@ export class FileScriptProcessor extends BaseScriptProcessor<FileScriptAsset> {
97
101
 
98
102
  return {
99
103
  filepath: bundledFilePath,
104
+ sourceFilepath: dep.filepath,
100
105
  content: dep.inline ? fileSystem.readFileSync(bundledFilePath).toString() : undefined,
101
106
  kind: 'script',
102
107
  position: dep.position,
@@ -104,6 +109,7 @@ export class FileScriptProcessor extends BaseScriptProcessor<FileScriptAsset> {
104
109
  inline: dep.inline,
105
110
  excludeFromHtml: dep.excludeFromHtml,
106
111
  packageRole: dep.packageRole,
112
+ bundledSourceFilepaths: dep.bundledSourceFilepaths,
107
113
  };
108
114
  });
109
115
  }
@@ -25,6 +25,7 @@ export class NodeModuleScriptProcessor extends BaseScriptProcessor<NodeModuleScr
25
25
  inline: true,
26
26
  excludeFromHtml: dep.excludeFromHtml,
27
27
  packageRole: dep.packageRole,
28
+ bundledSourceFilepaths: dep.bundledSourceFilepaths,
28
29
  };
29
30
  }
30
31
 
@@ -47,6 +48,7 @@ export class NodeModuleScriptProcessor extends BaseScriptProcessor<NodeModuleScr
47
48
  inline: dep.inline,
48
49
  excludeFromHtml: dep.excludeFromHtml,
49
50
  packageRole: dep.packageRole,
51
+ bundledSourceFilepaths: dep.bundledSourceFilepaths,
50
52
  };
51
53
  });
52
54
  }
@@ -65,6 +65,7 @@ export class ContentStylesheetProcessor extends BaseProcessor<ContentStylesheetA
65
65
  attributes: dep.attributes,
66
66
  inline: dep.inline,
67
67
  packageRole: dep.packageRole,
68
+ bundledSourceFilepaths: dep.bundledSourceFilepaths,
68
69
  };
69
70
  });
70
71
  }
@@ -69,12 +69,14 @@ export class FileStylesheetProcessor extends BaseProcessor<FileStylesheetAsset>
69
69
 
70
70
  return {
71
71
  filepath: filepath,
72
+ sourceFilepath: dep.filepath,
72
73
  content: dep.inline ? processedContent : undefined,
73
74
  kind: 'stylesheet',
74
75
  position: dep.position,
75
76
  attributes: dep.attributes,
76
77
  inline: dep.inline,
77
78
  packageRole: dep.packageRole,
79
+ bundledSourceFilepaths: dep.bundledSourceFilepaths,
78
80
  };
79
81
  });
80
82
  }
@@ -62,4 +62,4 @@ export async function processUngroupedDependency(
62
62
  logProcessingError(dep, error);
63
63
  return null;
64
64
  }
65
- }
65
+ }
@@ -435,10 +435,7 @@ describe('HtmlTransformerService', () => {
435
435
  packageRole: 'runtime',
436
436
  } as ProcessedAsset;
437
437
 
438
- expect(transformer.dedupeProcessedAssets([pageScript, runtimeScript])).toEqual([
439
- pageScript,
440
- runtimeScript,
441
- ]);
438
+ expect(transformer.dedupeProcessedAssets([pageScript, runtimeScript])).toEqual([pageScript, runtimeScript]);
442
439
  });
443
440
 
444
441
  it('should prefer page package html assets during transform', async () => {
@@ -53,9 +53,7 @@ export function createAppModuleLoader(appConfig: EcoPagesAppConfig): AppModuleLo
53
53
  getHostModuleLoader: () => getAppHostModuleLoader(appConfig),
54
54
  });
55
55
  const getDefaultPlugins =
56
- typeof Bun === 'undefined' && appConfig.rootDir
57
- ? () => [createAppNodeBootstrapPlugin(appConfig)]
58
- : () => [];
56
+ typeof Bun === 'undefined' && appConfig.rootDir ? () => [createAppNodeBootstrapPlugin(appConfig)] : () => [];
59
57
  const appModuleLoader: AppModuleLoader & {
60
58
  pageModuleImportService: PageModuleImportService;
61
59
  } = {
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync } from 'node:fs';
2
+ import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, unlinkSync } from 'node:fs';
3
3
  import { createRequire } from 'node:module';
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
5
  import type { EcoBuildOnResolveArgs, EcoBuildOnResolveResult, EcoBuildPlugin } from '../../build/build-types.ts';
@@ -72,7 +72,7 @@ function ensureRuntimePackageLink(nodeModulesDir: string, specifier: string, res
72
72
  return;
73
73
  }
74
74
 
75
- rmSync(linkPath, { recursive: true, force: true });
75
+ removeRuntimePackageLink(linkPath);
76
76
  }
77
77
 
78
78
  try {
@@ -86,11 +86,25 @@ function ensureRuntimePackageLink(nodeModulesDir: string, specifier: string, res
86
86
  return;
87
87
  }
88
88
 
89
- rmSync(linkPath, { recursive: true, force: true });
89
+ removeRuntimePackageLink(linkPath);
90
90
  symlinkSync(packageRoot, linkPath, 'dir');
91
91
  }
92
92
  }
93
93
 
94
+ function removeRuntimePackageLink(linkPath: string): void {
95
+ try {
96
+ const stats = lstatSync(linkPath);
97
+ if (stats.isSymbolicLink()) {
98
+ unlinkSync(linkPath);
99
+ return;
100
+ }
101
+ } catch {
102
+ return;
103
+ }
104
+
105
+ rmSync(linkPath, { recursive: true, force: true });
106
+ }
107
+
94
108
  export interface NodeBootstrapResolutionOptions {
95
109
  /**
96
110
  * App root used as the fallback package boundary when an importer does not
@@ -249,4 +249,3 @@ function createRuntimeModuleUrl(
249
249
  function sanitizeCacheScope(cacheScope: string): string {
250
250
  return cacheScope.replace(/[^a-zA-Z0-9_-]+/g, '-');
251
251
  }
252
-
@@ -16,4 +16,4 @@ export function supportsSourceModuleLoading(filePath: string): boolean {
16
16
  extension === '.cjs' ||
17
17
  extension === '.cts'
18
18
  );
19
- }
19
+ }