@swissjs/swite 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.github/workflows/ci.yml +59 -0
  3. package/.github/workflows/publish.yml +50 -0
  4. package/.github/workflows/release.yml +53 -0
  5. package/BUILD_ANALYSIS.md +89 -0
  6. package/BUILD_STRATEGY.md +75 -0
  7. package/CHANGELOG.md +53 -0
  8. package/DIRECTIVE.md +488 -0
  9. package/__tests__/css-extraction.test.ts +261 -0
  10. package/__tests__/css-injection-integration.test.ts +247 -0
  11. package/__tests__/css-middleware.test.ts +191 -0
  12. package/__tests__/import-rewriter-bug.test.ts +135 -0
  13. package/dist/builder.d.ts +36 -0
  14. package/dist/builder.d.ts.map +1 -0
  15. package/dist/builder.js +772 -0
  16. package/dist/cache/compilation-cache.d.ts +33 -0
  17. package/dist/cache/compilation-cache.d.ts.map +1 -0
  18. package/dist/cache/compilation-cache.js +130 -0
  19. package/dist/cli.d.ts +3 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +85 -0
  22. package/dist/config-loader.d.ts +8 -0
  23. package/dist/config-loader.d.ts.map +1 -0
  24. package/dist/config-loader.js +40 -0
  25. package/dist/config.d.ts +29 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +7 -0
  28. package/dist/dev/pythonDevManager.d.ts +12 -0
  29. package/dist/dev/pythonDevManager.d.ts.map +1 -0
  30. package/dist/dev/pythonDevManager.js +85 -0
  31. package/dist/env.d.ts +19 -0
  32. package/dist/env.d.ts.map +1 -0
  33. package/dist/env.js +112 -0
  34. package/dist/handlers/base-handler.d.ts +21 -0
  35. package/dist/handlers/base-handler.d.ts.map +1 -0
  36. package/dist/handlers/base-handler.js +38 -0
  37. package/dist/handlers/js-handler.d.ts +10 -0
  38. package/dist/handlers/js-handler.d.ts.map +1 -0
  39. package/dist/handlers/js-handler.js +87 -0
  40. package/dist/handlers/mjs-handler.d.ts +8 -0
  41. package/dist/handlers/mjs-handler.d.ts.map +1 -0
  42. package/dist/handlers/mjs-handler.js +44 -0
  43. package/dist/handlers/node-module-handler.d.ts +16 -0
  44. package/dist/handlers/node-module-handler.d.ts.map +1 -0
  45. package/dist/handlers/node-module-handler.js +267 -0
  46. package/dist/handlers/ts-handler.d.ts +11 -0
  47. package/dist/handlers/ts-handler.d.ts.map +1 -0
  48. package/dist/handlers/ts-handler.js +120 -0
  49. package/dist/handlers/ui-handler.d.ts +12 -0
  50. package/dist/handlers/ui-handler.d.ts.map +1 -0
  51. package/dist/handlers/ui-handler.js +182 -0
  52. package/dist/handlers/uix-handler.d.ts +12 -0
  53. package/dist/handlers/uix-handler.d.ts.map +1 -0
  54. package/dist/handlers/uix-handler.js +135 -0
  55. package/dist/hmr.d.ts +20 -0
  56. package/dist/hmr.d.ts.map +1 -0
  57. package/dist/hmr.js +265 -0
  58. package/dist/import-rewriter.d.ts +3 -0
  59. package/dist/import-rewriter.d.ts.map +1 -0
  60. package/dist/import-rewriter.js +351 -0
  61. package/dist/index.d.ts +14 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +13 -0
  64. package/dist/middleware/hmr-routes.d.ts +12 -0
  65. package/dist/middleware/hmr-routes.d.ts.map +1 -0
  66. package/dist/middleware/hmr-routes.js +97 -0
  67. package/dist/middleware/middleware-setup.d.ts +23 -0
  68. package/dist/middleware/middleware-setup.d.ts.map +1 -0
  69. package/dist/middleware/middleware-setup.js +596 -0
  70. package/dist/middleware/static-files.d.ts +15 -0
  71. package/dist/middleware/static-files.d.ts.map +1 -0
  72. package/dist/middleware/static-files.js +585 -0
  73. package/dist/proxy/SwiteProxyError.d.ts +6 -0
  74. package/dist/proxy/SwiteProxyError.d.ts.map +1 -0
  75. package/dist/proxy/SwiteProxyError.js +9 -0
  76. package/dist/proxy/proxyToPython.d.ts +28 -0
  77. package/dist/proxy/proxyToPython.d.ts.map +1 -0
  78. package/dist/proxy/proxyToPython.js +66 -0
  79. package/dist/resolver/bare-import-resolver.d.ts +9 -0
  80. package/dist/resolver/bare-import-resolver.d.ts.map +1 -0
  81. package/dist/resolver/bare-import-resolver.js +363 -0
  82. package/dist/resolver/symlink-registry.d.ts +13 -0
  83. package/dist/resolver/symlink-registry.d.ts.map +1 -0
  84. package/dist/resolver/symlink-registry.js +98 -0
  85. package/dist/resolver/url-resolver.d.ts +11 -0
  86. package/dist/resolver/url-resolver.d.ts.map +1 -0
  87. package/dist/resolver/url-resolver.js +268 -0
  88. package/dist/resolver/workspace-package-resolver.d.ts +10 -0
  89. package/dist/resolver/workspace-package-resolver.d.ts.map +1 -0
  90. package/dist/resolver/workspace-package-resolver.js +185 -0
  91. package/dist/resolver.d.ts +17 -0
  92. package/dist/resolver.d.ts.map +1 -0
  93. package/dist/resolver.js +191 -0
  94. package/dist/router/file-router.d.ts +19 -0
  95. package/dist/router/file-router.d.ts.map +1 -0
  96. package/dist/router/file-router.js +114 -0
  97. package/dist/server.d.ts +22 -0
  98. package/dist/server.d.ts.map +1 -0
  99. package/dist/server.js +122 -0
  100. package/dist/utils/cdn-fallback.d.ts +14 -0
  101. package/dist/utils/cdn-fallback.d.ts.map +1 -0
  102. package/dist/utils/cdn-fallback.js +36 -0
  103. package/dist/utils/file-path-resolver.d.ts +9 -0
  104. package/dist/utils/file-path-resolver.d.ts.map +1 -0
  105. package/dist/utils/file-path-resolver.js +187 -0
  106. package/dist/utils/generate-import-map-cli.d.ts +3 -0
  107. package/dist/utils/generate-import-map-cli.d.ts.map +1 -0
  108. package/dist/utils/generate-import-map-cli.js +32 -0
  109. package/dist/utils/generate-import-map.d.ts +21 -0
  110. package/dist/utils/generate-import-map.d.ts.map +1 -0
  111. package/dist/utils/generate-import-map.js +119 -0
  112. package/dist/utils/package-finder.d.ts +24 -0
  113. package/dist/utils/package-finder.d.ts.map +1 -0
  114. package/dist/utils/package-finder.js +161 -0
  115. package/dist/utils/package-registry.d.ts +36 -0
  116. package/dist/utils/package-registry.d.ts.map +1 -0
  117. package/dist/utils/package-registry.js +159 -0
  118. package/dist/utils/workspace.d.ts +6 -0
  119. package/dist/utils/workspace.d.ts.map +1 -0
  120. package/dist/utils/workspace.js +65 -0
  121. package/docs/IMPORT_REWRITING.md +164 -0
  122. package/docs/IMPORT_REWRITING_TROUBLESHOOTING.md +139 -0
  123. package/docs/PATH_RESOLUTION_GUIDE.md +221 -0
  124. package/package.json +49 -0
  125. package/src/adapters/proxy/SwiteProxyError.ts +12 -0
  126. package/src/adapters/proxy/proxyToPython.ts +88 -0
  127. package/src/build-engine/builder.ts +960 -0
  128. package/src/cli.ts +109 -0
  129. package/src/config/config-loader.ts +46 -0
  130. package/src/config/config.ts +34 -0
  131. package/src/config/env.ts +98 -0
  132. package/src/dev-engine/handlers/base-handler.ts +68 -0
  133. package/src/dev-engine/handlers/js-handler.ts +134 -0
  134. package/src/dev-engine/handlers/mjs-handler.ts +65 -0
  135. package/src/dev-engine/handlers/node-module-handler.ts +339 -0
  136. package/src/dev-engine/handlers/ts-handler.ts +143 -0
  137. package/src/dev-engine/handlers/ui-handler.ts +105 -0
  138. package/src/dev-engine/handlers/uix-handler.ts +90 -0
  139. package/src/dev-engine/hmr/hmr-client-template.ts +122 -0
  140. package/src/dev-engine/hmr/hmr.ts +173 -0
  141. package/src/dev-engine/middleware/hmr-routes.ts +120 -0
  142. package/src/dev-engine/middleware/middleware-setup.ts +351 -0
  143. package/src/dev-engine/middleware/static-files.ts +728 -0
  144. package/src/dev-engine/pythonDevManager.ts +116 -0
  145. package/src/dev-engine/router/file-router.ts +164 -0
  146. package/src/dev-engine/server.ts +152 -0
  147. package/src/index.ts +26 -0
  148. package/src/internal/cache/compilation-cache.ts +182 -0
  149. package/src/internal/generate-import-map-cli.ts +40 -0
  150. package/src/internal/generate-import-map.ts +154 -0
  151. package/src/kernel/package-finder.ts +164 -0
  152. package/src/kernel/package-registry.ts +198 -0
  153. package/src/kernel/workspace.ts +62 -0
  154. package/src/resolution/bare-import-resolver.ts +400 -0
  155. package/src/resolution/cdn/cdn-fallback.ts +37 -0
  156. package/src/resolution/path/file-path-resolver.ts +190 -0
  157. package/src/resolution/path/path-fixup.ts +19 -0
  158. package/src/resolution/resolver.ts +198 -0
  159. package/src/resolution/rewriting/import-rewriter.ts +237 -0
  160. package/src/resolution/symlink-registry.ts +114 -0
  161. package/src/resolution/url-resolver.ts +231 -0
  162. package/src/resolution/workspace-package-resolver.ts +94 -0
  163. package/tsconfig.json +37 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"js-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/js-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAIxC,OAAO,EACL,WAAW,EAEX,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAK3B,qBAAa,SAAU,SAAQ,WAAW;IACxC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,SAAS,CAAY;gBAEjB,OAAO,EAAE,cAAc;IAO7B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAsGxD"}
@@ -0,0 +1,87 @@
1
+ /*
2
+ * Copyright (c) 2024 Themba Mzumara
3
+ * SWITE - SWISS Development Server
4
+ * Licensed under the MIT License.
5
+ */
6
+ import { promises as fs } from "node:fs";
7
+ import chalk from "chalk";
8
+ import { rewriteImports } from "../import-rewriter.js";
9
+ import { BaseHandler, setDevHeaders, } from "./base-handler.js";
10
+ import { UIHandler } from "./ui-handler.js";
11
+ import { UIXHandler } from "./uix-handler.js";
12
+ import { TSHandler } from "./ts-handler.js";
13
+ export class JSHandler extends BaseHandler {
14
+ constructor(context) {
15
+ super(context);
16
+ this.uiHandler = new UIHandler(context);
17
+ this.uixHandler = new UIXHandler(context);
18
+ this.tsHandler = new TSHandler(context);
19
+ }
20
+ async handle(url, res) {
21
+ const filePath = await this.resolveFilePath(url);
22
+ // Check if .js file exists, if not try .ts, .ui, .uix
23
+ try {
24
+ await fs.access(filePath);
25
+ }
26
+ catch {
27
+ // .js doesn't exist, try alternatives
28
+ const basePath = filePath.slice(0, -3); // Remove .js
29
+ const alternatives = [
30
+ {
31
+ ext: ".ts",
32
+ handler: () => this.tsHandler.handle(url.replace(/\.js$/, ".ts"), res),
33
+ },
34
+ {
35
+ ext: ".ui",
36
+ handler: () => this.uiHandler.handle(url.replace(/\.js$/, ".ui"), res),
37
+ },
38
+ {
39
+ ext: ".uix",
40
+ handler: () => this.uixHandler.handle(url.replace(/\.js$/, ".uix"), res),
41
+ },
42
+ ];
43
+ for (const alt of alternatives) {
44
+ try {
45
+ const altPath = basePath + alt.ext;
46
+ await fs.access(altPath);
47
+ console.log(chalk.yellow(`[.js→${alt.ext}] ${url} → ${url.replace(/\.js$/, alt.ext)} (file: ${altPath})`));
48
+ return await alt.handler();
49
+ }
50
+ catch {
51
+ // Try next alternative
52
+ console.log(chalk.gray(`[.js→${alt.ext}] ${basePath + alt.ext} not found, trying next...`));
53
+ }
54
+ }
55
+ // No alternatives found, throw error
56
+ console.error(chalk.red(`[.js] File not found: ${url} (tried .js, .ts, .ui, .uix)`));
57
+ console.error(chalk.red(`[.js] filePath was: ${filePath}`));
58
+ console.error(chalk.red(`[.js] basePath was: ${basePath}`));
59
+ throw new Error(`File not found: ${url} (tried .js, .ts, .ui, .uix)`);
60
+ }
61
+ // .js file exists, process it normally
62
+ const source = await fs.readFile(filePath, "utf-8");
63
+ // Debug: Check for bare imports (including simple npm packages like bcryptjs)
64
+ const bareImportPattern = /(?:import|from|export).*['"](@[^'"]+\/[^'"]+)[^'"]*['"]/;
65
+ const simpleNpmPattern = /(?:import|from|export).*['"]([a-zA-Z][a-zA-Z0-9_-]*)[^'"]*['"]/;
66
+ if (bareImportPattern.test(source) || simpleNpmPattern.test(source)) {
67
+ console.log(chalk.yellow(`[.js] Found imports in ${url}, rewriting...`));
68
+ // Log the actual imports found
69
+ const importMatches = source.matchAll(/(?:import|from)\s+['"]([^'"]+)['"]/g);
70
+ for (const match of importMatches) {
71
+ console.log(chalk.cyan(`[.js] Found import: ${match[1]}`));
72
+ }
73
+ }
74
+ const rewritten = await rewriteImports(source, filePath, this.context.resolver);
75
+ // Debug: Verify no bare imports remain after rewriting
76
+ if (bareImportPattern.test(rewritten)) {
77
+ console.error(chalk.red(`[.js] WARNING: Bare imports still present in ${url} after rewriting!`));
78
+ const matches = Array.from(rewritten.matchAll(/(?:import|from|export).*['"](@[^'"]+\/[^'"]+)[^'"]*['"]/g));
79
+ for (const match of matches.slice(0, 3)) {
80
+ console.error(chalk.red(`[.js] Unresolved: ${match[1]}`));
81
+ }
82
+ }
83
+ setDevHeaders(res);
84
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
85
+ res.send(rewritten);
86
+ }
87
+ }
@@ -0,0 +1,8 @@
1
+ import type { Response } from "express";
2
+ import { BaseHandler, type HandlerContext } from "./base-handler.js";
3
+ export declare class MJSHandler extends BaseHandler {
4
+ private jsHandler;
5
+ constructor(context: HandlerContext);
6
+ handle(url: string, res: Response): Promise<void>;
7
+ }
8
+ //# sourceMappingURL=mjs-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mjs-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/mjs-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAIxC,OAAO,EACL,WAAW,EAEX,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAG3B,qBAAa,UAAW,SAAQ,WAAW;IACzC,OAAO,CAAC,SAAS,CAAY;gBAEjB,OAAO,EAAE,cAAc;IAK7B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAuCxD"}
@@ -0,0 +1,44 @@
1
+ /*
2
+ * Copyright (c) 2024 Themba Mzumara
3
+ * SWITE - SWISS Development Server
4
+ * Licensed under the MIT License.
5
+ */
6
+ import { promises as fs } from "node:fs";
7
+ import chalk from "chalk";
8
+ import { rewriteImports } from "../import-rewriter.js";
9
+ import { BaseHandler, setDevHeaders, } from "./base-handler.js";
10
+ import { JSHandler } from "./js-handler.js";
11
+ export class MJSHandler extends BaseHandler {
12
+ constructor(context) {
13
+ super(context);
14
+ this.jsHandler = new JSHandler(context);
15
+ }
16
+ async handle(url, res) {
17
+ const filePath = await this.resolveFilePath(url);
18
+ // Check if .mjs file exists
19
+ try {
20
+ await fs.access(filePath);
21
+ }
22
+ catch {
23
+ // .mjs doesn't exist, try .js as fallback
24
+ const jsPath = filePath.replace(/\.mjs$/, ".js");
25
+ try {
26
+ await fs.access(jsPath);
27
+ console.log(chalk.yellow(`[.mjs→.js] ${url} → ${url.replace(/\.mjs$/, ".js")} (file: ${jsPath})`));
28
+ return await this.jsHandler.handle(url.replace(/\.mjs$/, ".js"), res);
29
+ }
30
+ catch {
31
+ console.error(chalk.red(`[.mjs] File not found: ${url} (tried .mjs, .js)`));
32
+ throw new Error(`File not found: ${url} (tried .mjs, .js)`);
33
+ }
34
+ }
35
+ // .mjs file exists, process it normally
36
+ const source = await fs.readFile(filePath, "utf-8");
37
+ const rewritten = await rewriteImports(source, filePath, this.context.resolver);
38
+ // Set proper MIME type for ES modules (.mjs)
39
+ // According to MDN Web Standards: .mjs files should use "application/javascript"
40
+ setDevHeaders(res);
41
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
42
+ res.send(rewritten);
43
+ }
44
+ }
@@ -0,0 +1,16 @@
1
+ import type { Response } from "express";
2
+ import { BaseHandler, type HandlerContext } from "./base-handler.js";
3
+ export declare class NodeModuleHandler extends BaseHandler {
4
+ private uiHandler;
5
+ private uixHandler;
6
+ private tsHandler;
7
+ constructor(context: HandlerContext);
8
+ handle(url: string, res: Response): Promise<void>;
9
+ /**
10
+ * Get CDN URL for a /node_modules/... request when the file is not found locally.
11
+ * Uses jsDelivr (+esm) for reliable ESM delivery; esm.sh can return 500 for some packages.
12
+ * e.g. /node_modules/reflect-metadata/Reflect.js -> https://cdn.jsdelivr.net/npm/reflect-metadata/+esm
13
+ */
14
+ private getNodeModuleCdnRedirect;
15
+ }
16
+ //# sourceMappingURL=node-module-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-module-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/node-module-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAKxC,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAOrE,qBAAa,iBAAkB,SAAQ,WAAW;IAChD,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,SAAS,CAAY;gBAEjB,OAAO,EAAE,cAAc;IAO7B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAqRvD;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;CA0BjC"}
@@ -0,0 +1,267 @@
1
+ /*
2
+ * Copyright (c) 2024 Themba Mzumara
3
+ * SWITE - SWISS Development Server
4
+ * Licensed under the MIT License.
5
+ */
6
+ import { promises as fs } from "node:fs";
7
+ import * as path from "node:path";
8
+ import chalk from "chalk";
9
+ import { rewriteImports } from "../import-rewriter.js";
10
+ import { BaseHandler } from "./base-handler.js";
11
+ import { UIHandler } from "./ui-handler.js";
12
+ import { UIXHandler } from "./uix-handler.js";
13
+ import { TSHandler } from "./ts-handler.js";
14
+ import { findWorkspaceRoot } from "../utils/workspace.js";
15
+ import { shouldUseCdnFallback } from "../utils/cdn-fallback.js";
16
+ export class NodeModuleHandler extends BaseHandler {
17
+ constructor(context) {
18
+ super(context);
19
+ this.uiHandler = new UIHandler(context);
20
+ this.uixHandler = new UIXHandler(context);
21
+ this.tsHandler = new TSHandler(context);
22
+ }
23
+ async handle(url, res) {
24
+ try {
25
+ // Special case: reflect-metadata/reflect.js -> Reflect.js (case fix)
26
+ if (url.includes("/reflect-metadata/reflect.js")) {
27
+ url = url.replace("/reflect.js", "/Reflect.js");
28
+ }
29
+ // Handle node_modules paths - try multiple locations
30
+ // URL is like /node_modules/reflect-metadata/Reflect.js
31
+ // We need to remove the leading / and join with the appropriate root
32
+ const urlPath = url.startsWith("/") ? url.slice(1) : url;
33
+ let filePath = null;
34
+ const workspaceRoot = this.context.workspaceRoot ||
35
+ (await findWorkspaceRoot(this.context.root));
36
+ console.log(chalk.blue(`[node_modules] Processing: ${url}`));
37
+ console.log(chalk.blue(`[node_modules] App root: ${this.context.root}`));
38
+ console.log(chalk.blue(`[node_modules] Workspace root: ${workspaceRoot || "none"}`));
39
+ // Walk up directory tree from app root to find node_modules at any level
40
+ // (handles pnpm isolated AND hoisted workspace layouts)
41
+ {
42
+ let current = path.resolve(this.context.root);
43
+ const visited = new Set();
44
+ for (let i = 0; i < 8; i++) {
45
+ const candidate = path.join(current, urlPath);
46
+ if (!visited.has(candidate)) {
47
+ visited.add(candidate);
48
+ console.log(chalk.blue(`[node_modules] Trying path: ${candidate}`));
49
+ try {
50
+ const resolvedPath = await fs.realpath(candidate);
51
+ console.log(chalk.blue(`[node_modules] Resolved to: ${resolvedPath}`));
52
+ await fs.access(resolvedPath);
53
+ filePath = resolvedPath;
54
+ console.log(chalk.green(`[node_modules] ✓ Found: ${urlPath}`));
55
+ break;
56
+ }
57
+ catch (err) {
58
+ console.log(chalk.yellow(`[node_modules] Path failed: ${err instanceof Error ? err.message : String(err)}`));
59
+ }
60
+ }
61
+ const parent = path.dirname(current);
62
+ if (parent === current)
63
+ break;
64
+ current = parent;
65
+ }
66
+ }
67
+ if (!filePath) {
68
+ // Try workspace root node_modules
69
+ if (workspaceRoot) {
70
+ const workspaceNodeModulesPath = path.join(workspaceRoot, urlPath);
71
+ console.log(chalk.blue(`[node_modules] Trying workspace path: ${workspaceNodeModulesPath}`));
72
+ try {
73
+ // Try to resolve symlinks first (realpath works even if path is a symlink)
74
+ const resolvedPath = await fs.realpath(workspaceNodeModulesPath);
75
+ console.log(chalk.blue(`[node_modules] Resolved to: ${resolvedPath}`));
76
+ // Verify the resolved path exists
77
+ await fs.access(resolvedPath);
78
+ filePath = resolvedPath;
79
+ console.log(chalk.green(`[node_modules] ✓ Found in workspace: ${urlPath}`));
80
+ }
81
+ catch (err2) {
82
+ console.log(chalk.yellow(`[node_modules] Workspace path failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
83
+ // Try swiss-lib monorepo node_modules (dynamically found)
84
+ const { findSwissLibMonorepo } = await import("../utils/package-finder.js");
85
+ const swissLib = await findSwissLibMonorepo(this.context.root);
86
+ if (swissLib) {
87
+ const swissNodeModulesPath = path.join(swissLib, urlPath);
88
+ console.log(chalk.blue(`[node_modules] Trying swiss-lib path: ${swissNodeModulesPath}`));
89
+ try {
90
+ // Try to resolve symlinks first (realpath works even if path is a symlink)
91
+ const resolvedPath = await fs.realpath(swissNodeModulesPath);
92
+ console.log(chalk.blue(`[node_modules] Resolved to: ${resolvedPath}`));
93
+ // Verify the resolved path exists
94
+ await fs.access(resolvedPath);
95
+ filePath = resolvedPath;
96
+ console.log(chalk.green(`[node_modules] ✓ Found in swiss-lib monorepo: ${urlPath}`));
97
+ }
98
+ catch (err3) {
99
+ console.log(chalk.yellow(`[node_modules] swiss-lib path failed: ${err3 instanceof Error ? err3.message : String(err3)}`));
100
+ // File not found in any location, will trigger case-insensitive search below
101
+ filePath = path.join(this.context.root, urlPath);
102
+ }
103
+ }
104
+ else {
105
+ // File not found in any location, will trigger case-insensitive search below
106
+ filePath = path.join(this.context.root, urlPath);
107
+ }
108
+ }
109
+ }
110
+ else {
111
+ filePath = path.join(this.context.root, urlPath);
112
+ }
113
+ }
114
+ console.log(chalk.gray(`[node_modules] Resolving: ${url} -> ${filePath}`));
115
+ // File path is already resolved from above, no need to resolve again
116
+ // Check if file exists, if .js doesn't exist try case-insensitive match and alternatives
117
+ try {
118
+ await fs.access(filePath);
119
+ }
120
+ catch (error) {
121
+ console.log(chalk.yellow(`[node_modules] File not found at ${filePath}, trying case-insensitive match...`));
122
+ // File doesn't exist with exact case, try case-insensitive match (for Reflect.js vs reflect.js)
123
+ if (url.endsWith(".js")) {
124
+ const dir = path.dirname(filePath);
125
+ const requestedName = path.basename(filePath);
126
+ try {
127
+ // Resolve directory symlink (for pnpm)
128
+ const resolvedDir = await fs.realpath(dir).catch(() => dir);
129
+ // Check if directory exists first
130
+ await fs.access(resolvedDir);
131
+ const files = await fs.readdir(resolvedDir);
132
+ const caseInsensitiveMatch = files.find((f) => f.toLowerCase() === requestedName.toLowerCase());
133
+ if (caseInsensitiveMatch) {
134
+ filePath = path.join(resolvedDir, caseInsensitiveMatch);
135
+ console.log(chalk.yellow(`[node_modules] Case-insensitive match: ${requestedName} -> ${caseInsensitiveMatch}`));
136
+ // Verify the file exists with the correct case
137
+ await fs.access(filePath);
138
+ // File found, continue to serve it below
139
+ }
140
+ else {
141
+ throw new Error("No case-insensitive match found");
142
+ }
143
+ }
144
+ catch {
145
+ // Directory doesn't exist or no case-insensitive match, try alternatives
146
+ console.log(chalk.gray(`[node_modules] Case-insensitive match failed for ${url}, trying alternatives...`));
147
+ const basePath = filePath.slice(0, -3); // Remove .js
148
+ const alternatives = [
149
+ {
150
+ ext: ".ts",
151
+ handler: () => this.tsHandler.handle(url.replace(/\.js$/, ".ts"), res),
152
+ },
153
+ {
154
+ ext: ".ui",
155
+ handler: () => this.uiHandler.handle(url.replace(/\.js$/, ".ui"), res),
156
+ },
157
+ {
158
+ ext: ".uix",
159
+ handler: () => this.uixHandler.handle(url.replace(/\.js$/, ".uix"), res),
160
+ },
161
+ ];
162
+ for (const alt of alternatives) {
163
+ try {
164
+ await fs.access(basePath + alt.ext);
165
+ console.log(chalk.yellow(`[.js→${alt.ext}] ${url} → ${url.replace(/\.js$/, alt.ext)}`));
166
+ return await alt.handler();
167
+ }
168
+ catch {
169
+ // Try next alternative
170
+ }
171
+ }
172
+ // No alternatives found - redirect to CDN instead of 500
173
+ const cdnRedirect = this.getNodeModuleCdnRedirect(url);
174
+ if (cdnRedirect) {
175
+ console.log(chalk.yellow(`[node_modules] Not found locally, redirecting to CDN: ${cdnRedirect}`));
176
+ res.redirect(302, cdnRedirect);
177
+ return;
178
+ }
179
+ res.status(404).send(`Module not found: ${url}`);
180
+ return;
181
+ }
182
+ }
183
+ else {
184
+ // Not a .js file and doesn't exist - try CDN redirect or 404
185
+ const cdnRedirect = this.getNodeModuleCdnRedirect(url);
186
+ if (cdnRedirect) {
187
+ res.redirect(302, cdnRedirect);
188
+ return;
189
+ }
190
+ res.status(404).send(`Module not found: ${url}`);
191
+ return;
192
+ }
193
+ }
194
+ // File exists, process it normally
195
+ // For node_modules files, skip import rewriting - they should work as-is
196
+ // and rewriting can cause issues with package internals
197
+ try {
198
+ console.log(chalk.blue(`[node_modules] Reading file: ${filePath}`));
199
+ const source = await fs.readFile(filePath, "utf-8");
200
+ console.log(chalk.green(`[node_modules] ✓ File read successfully, length: ${source.length}`));
201
+ // Skip import rewriting for node_modules - serve as-is
202
+ // This is safer and faster for third-party packages
203
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
204
+ res.send(source);
205
+ console.log(chalk.green(`[node_modules] ✓ Served ${url} successfully`));
206
+ }
207
+ catch (error) {
208
+ console.error(chalk.red(`[node_modules] Error processing ${url} at ${filePath}:`));
209
+ console.error(chalk.red(`[node_modules] Error details:`), error);
210
+ if (error instanceof Error) {
211
+ console.error(chalk.red(`[node_modules] Error stack:`), error.stack);
212
+ }
213
+ throw error;
214
+ }
215
+ }
216
+ catch (outerError) {
217
+ console.error(chalk.red(`[node_modules] FATAL ERROR handling ${url}:`));
218
+ console.error(chalk.red(`[node_modules] Error type: ${outerError instanceof Error ? outerError.constructor.name : typeof outerError}`));
219
+ console.error(chalk.red(`[node_modules] Error message: ${outerError instanceof Error ? outerError.message : String(outerError)}`));
220
+ if (outerError instanceof Error && outerError.stack) {
221
+ console.error(chalk.red(`[node_modules] Stack trace:`));
222
+ console.error(outerError.stack);
223
+ }
224
+ // Try CDN redirect before giving up with 500
225
+ const cdnRedirect = this.getNodeModuleCdnRedirect(url);
226
+ if (cdnRedirect) {
227
+ console.log(chalk.yellow(`[node_modules] Error handling locally, redirecting to CDN: ${cdnRedirect}`));
228
+ res.redirect(302, cdnRedirect);
229
+ return;
230
+ }
231
+ res.status(404).setHeader("Content-Type", "text/plain").send(`Module not found: ${url}. ${outerError instanceof Error ? outerError.message : String(outerError)}`);
232
+ }
233
+ }
234
+ /**
235
+ * Get CDN URL for a /node_modules/... request when the file is not found locally.
236
+ * Uses jsDelivr (+esm) for reliable ESM delivery; esm.sh can return 500 for some packages.
237
+ * e.g. /node_modules/reflect-metadata/Reflect.js -> https://cdn.jsdelivr.net/npm/reflect-metadata/+esm
238
+ */
239
+ getNodeModuleCdnRedirect(url) {
240
+ const prefix = "/node_modules/";
241
+ if (!url.startsWith(prefix))
242
+ return null;
243
+ // For nested paths like /node_modules/@scope/pkg/node_modules/dep/file.js,
244
+ // find the LAST node_modules segment and extract the package name from there.
245
+ const lastNodeModulesIdx = url.lastIndexOf("/node_modules/");
246
+ const after = url.slice(lastNodeModulesIdx + prefix.length);
247
+ if (!after)
248
+ return null;
249
+ // Extract package name: handle @scope/name and plain-name
250
+ let pkgName;
251
+ if (after.startsWith("@")) {
252
+ // Scoped package: need TWO path segments — @scope/name
253
+ const secondSlash = after.indexOf("/", after.indexOf("/") + 1);
254
+ pkgName = secondSlash === -1 ? after : after.slice(0, secondSlash);
255
+ }
256
+ else {
257
+ const firstSlash = after.indexOf("/");
258
+ pkgName = firstSlash === -1 ? after : after.slice(0, firstSlash);
259
+ }
260
+ if (!pkgName || pkgName === "." || pkgName === "..")
261
+ return null;
262
+ if (!shouldUseCdnFallback(pkgName))
263
+ return null;
264
+ // jsDelivr +esm serves ESM build; works for reflect-metadata and most npm packages
265
+ return `https://cdn.jsdelivr.net/npm/${pkgName}/+esm`;
266
+ }
267
+ }
@@ -0,0 +1,11 @@
1
+ import type { Response } from "express";
2
+ import { BaseHandler, type HandlerContext } from "./base-handler.js";
3
+ export declare class TSHandler extends BaseHandler {
4
+ constructor(context: HandlerContext);
5
+ /**
6
+ * Extract dependencies from compiled code (import paths)
7
+ */
8
+ private getDependencies;
9
+ handle(url: string, res: Response): Promise<void>;
10
+ }
11
+ //# sourceMappingURL=ts-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ts-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/ts-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAKxC,OAAO,EACL,WAAW,EAEX,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,SAAU,SAAQ,WAAW;gBAC5B,OAAO,EAAE,cAAc;IAInC;;OAEG;YACW,eAAe;IAwBvB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAuHxD"}
@@ -0,0 +1,120 @@
1
+ /*
2
+ * Copyright (c) 2024 Themba Mzumara
3
+ * SWITE - SWISS Development Server
4
+ * Licensed under the MIT License.
5
+ */
6
+ import { promises as fs } from "node:fs";
7
+ import chalk from "chalk";
8
+ import { rewriteImports } from "../import-rewriter.js";
9
+ import { compilationCache } from "../cache/compilation-cache.js";
10
+ import { BaseHandler, setDevHeaders, } from "./base-handler.js";
11
+ export class TSHandler extends BaseHandler {
12
+ constructor(context) {
13
+ super(context);
14
+ }
15
+ /**
16
+ * Extract dependencies from compiled code (import paths)
17
+ */
18
+ async getDependencies(compiled) {
19
+ const deps = [];
20
+ const importPattern = /(?:import|from|export).*['"]([^'"]+)['"]/g;
21
+ let match;
22
+ while ((match = importPattern.exec(compiled)) !== null) {
23
+ const specifier = match[1];
24
+ // Only track absolute paths and workspace paths (not relative)
25
+ if (specifier.startsWith("/") || specifier.startsWith("@")) {
26
+ try {
27
+ const resolved = await this.context.resolver.resolve(specifier, "");
28
+ if (resolved && !resolved.startsWith("http")) {
29
+ deps.push(resolved);
30
+ }
31
+ }
32
+ catch {
33
+ // Ignore resolution errors
34
+ }
35
+ }
36
+ }
37
+ return deps;
38
+ }
39
+ async handle(url, res) {
40
+ const filePath = await this.resolveFilePath(url);
41
+ console.log(chalk.gray(`[.ts] ${url}`));
42
+ // Check if .ts file exists, if not try .ui, .uix
43
+ try {
44
+ await fs.access(filePath);
45
+ }
46
+ catch {
47
+ // .ts doesn't exist, try alternatives
48
+ const basePath = filePath.slice(0, -3); // Remove .ts
49
+ const alternatives = [
50
+ {
51
+ ext: ".ui",
52
+ url: url.replace(/\.ts$/, ".ui"),
53
+ },
54
+ {
55
+ ext: ".uix",
56
+ url: url.replace(/\.ts$/, ".uix"),
57
+ },
58
+ ];
59
+ for (const alt of alternatives) {
60
+ try {
61
+ const altPath = basePath + alt.ext;
62
+ await fs.access(altPath);
63
+ console.log(chalk.yellow(`[.ts→${alt.ext}] ${url} → ${alt.url} (file: ${altPath})`));
64
+ // Import and use the appropriate handler
65
+ if (alt.ext === ".ui") {
66
+ const { UIHandler } = await import("./ui-handler.js");
67
+ const uiHandler = new UIHandler(this.context);
68
+ return await uiHandler.handle(alt.url, res);
69
+ }
70
+ else if (alt.ext === ".uix") {
71
+ const { UIXHandler } = await import("./uix-handler.js");
72
+ const uixHandler = new UIXHandler(this.context);
73
+ return await uixHandler.handle(alt.url, res);
74
+ }
75
+ }
76
+ catch {
77
+ // Try next alternative
78
+ console.log(chalk.gray(`[.ts→${alt.ext}] ${basePath + alt.ext} not found, trying next...`));
79
+ }
80
+ }
81
+ // No alternatives found, throw error
82
+ console.error(chalk.red(`[.ts] File not found: ${filePath} (and no alternatives found)`));
83
+ res.status(404).send(`File not found: ${url}`);
84
+ return;
85
+ }
86
+ // Check cache first
87
+ const cached = await compilationCache.get(filePath, (compiled) => this.getDependencies(compiled));
88
+ if (cached) {
89
+ setDevHeaders(res);
90
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
91
+ res.send(cached);
92
+ return;
93
+ }
94
+ // Cache miss - compile
95
+ const source = await fs.readFile(filePath, "utf-8");
96
+ // Use esbuild for fast TS transformation
97
+ const esbuild = await import("esbuild");
98
+ const result = await esbuild.transform(source, {
99
+ loader: "ts",
100
+ format: "esm",
101
+ target: "esnext",
102
+ sourcefile: filePath,
103
+ });
104
+ const rewritten = await rewriteImports(result.code, filePath, this.context.resolver);
105
+ // Store in cache (use result.code as "compiled" for dependency tracking)
106
+ await compilationCache.set(filePath, result.code, rewritten, (compiled) => this.getDependencies(compiled));
107
+ // Debug: Check for bare imports after rewriting
108
+ const bareImportPattern = /(?:import|from|export).*['"](@[^'"]+\/[^'"]+)(?!\/)[^'"]*['"]/;
109
+ if (bareImportPattern.test(rewritten)) {
110
+ console.log(chalk.red(`[.ts] ERROR: Bare imports still present after rewriting: ${url}`));
111
+ const matches = Array.from(rewritten.matchAll(/(?:import|from|export).*['"](@[^'"]+\/[^'"]+)(?!\/)[^'"]*['"]/g));
112
+ for (const match of matches.slice(0, 3)) {
113
+ console.log(chalk.red(`[.ts] Unresolved import: ${match[1]}`));
114
+ }
115
+ }
116
+ setDevHeaders(res);
117
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
118
+ res.send(rewritten);
119
+ }
120
+ }
@@ -0,0 +1,12 @@
1
+ import type { Response } from "express";
2
+ import { BaseHandler, type HandlerContext } from "./base-handler.js";
3
+ export declare class UIHandler extends BaseHandler {
4
+ private compiler;
5
+ constructor(context: HandlerContext);
6
+ /**
7
+ * Extract dependencies from compiled code (import paths)
8
+ */
9
+ private getDependencies;
10
+ handle(url: string, res: Response): Promise<void>;
11
+ }
12
+ //# sourceMappingURL=ui-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/ui-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAMxC,OAAO,EACL,WAAW,EAEX,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,SAAU,SAAQ,WAAW;IACxC,OAAO,CAAC,QAAQ,CAAoB;gBAExB,OAAO,EAAE,cAAc;IAInC;;OAEG;YACW,eAAe;IAyBvB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAwLxD"}