@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,159 @@
1
+ /*
2
+ * Package Registry - Dynamic package discovery
3
+ * Scans workspace to find all packages and caches their locations
4
+ * No hardcoded paths - discovers packages by scanning package.json files
5
+ */
6
+ import { promises as fs } from "node:fs";
7
+ import path from "node:path";
8
+ import chalk from "chalk";
9
+ export class PackageRegistry {
10
+ constructor() {
11
+ this.packages = new Map();
12
+ this.scanned = false;
13
+ this.scanRoots = [];
14
+ }
15
+ /**
16
+ * Scan workspace for all packages
17
+ */
18
+ async scanWorkspace(workspaceRoot, additionalRoots = []) {
19
+ if (this.scanned) {
20
+ return; // Already scanned
21
+ }
22
+ // Validate workspace root exists
23
+ if (!workspaceRoot) {
24
+ console.warn(chalk.yellow("[PackageRegistry] No workspace root provided, skipping scan"));
25
+ return;
26
+ }
27
+ try {
28
+ const rootStat = await fs.stat(workspaceRoot);
29
+ if (!rootStat.isDirectory()) {
30
+ console.warn(chalk.yellow(`[PackageRegistry] Workspace root is not a directory: ${workspaceRoot}`));
31
+ return;
32
+ }
33
+ }
34
+ catch (error) {
35
+ console.warn(chalk.yellow(`[PackageRegistry] Cannot access workspace root ${workspaceRoot}:`, error.message));
36
+ return;
37
+ }
38
+ this.scanRoots = [workspaceRoot, ...additionalRoots.filter(root => root && root !== workspaceRoot)];
39
+ console.log(chalk.blue(`[PackageRegistry] Scanning workspace for packages...`));
40
+ console.log(chalk.gray(`[PackageRegistry] Roots: ${this.scanRoots.join(", ")}`));
41
+ for (const root of this.scanRoots) {
42
+ if (root) {
43
+ await this.scanDirectory(root);
44
+ }
45
+ }
46
+ this.scanned = true;
47
+ console.log(chalk.green(`[PackageRegistry] ✅ Found ${this.packages.size} packages`));
48
+ }
49
+ /**
50
+ * Recursively scan directory for package.json files
51
+ */
52
+ async scanDirectory(dir, depth = 0) {
53
+ if (depth > 15)
54
+ return; // Prevent infinite recursion
55
+ // Validate directory exists and is accessible
56
+ try {
57
+ const dirStat = await fs.stat(dir);
58
+ if (!dirStat.isDirectory()) {
59
+ return;
60
+ }
61
+ }
62
+ catch (error) {
63
+ // Directory doesn't exist or permission denied, skip silently
64
+ if (error.code === "ENOENT" || error.code === "EACCES") {
65
+ return;
66
+ }
67
+ console.warn(chalk.yellow(`[PackageRegistry] Cannot access ${dir}:`, error.message));
68
+ return;
69
+ }
70
+ try {
71
+ const entries = await fs.readdir(dir, { withFileTypes: true });
72
+ for (const entry of entries) {
73
+ if (entry.isDirectory()) {
74
+ // Skip common directories that shouldn't be scanned
75
+ if (entry.name === "node_modules" ||
76
+ entry.name === "dist" ||
77
+ entry.name === ".git" ||
78
+ entry.name === ".swite" ||
79
+ entry.name.startsWith(".")) {
80
+ continue;
81
+ }
82
+ const packageJsonPath = path.join(dir, entry.name, "package.json");
83
+ try {
84
+ const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
85
+ const packageJson = JSON.parse(packageJsonContent);
86
+ if (packageJson.name) {
87
+ const packagePath = path.join(dir, entry.name);
88
+ const packageInfo = {
89
+ name: packageJson.name,
90
+ path: packagePath,
91
+ packageJson,
92
+ };
93
+ // Store package - if duplicate name, prefer the one found first (or closest to workspace root)
94
+ if (!this.packages.has(packageJson.name)) {
95
+ this.packages.set(packageJson.name, packageInfo);
96
+ console.log(chalk.gray(`[PackageRegistry] Found: ${packageJson.name} at ${packagePath}`));
97
+ }
98
+ else {
99
+ // Log duplicate but don't overwrite (first found wins)
100
+ console.log(chalk.yellow(`[PackageRegistry] Duplicate package ${packageJson.name} found at ${packagePath}, keeping first`));
101
+ }
102
+ }
103
+ }
104
+ catch (error) {
105
+ // Not a package.json or invalid JSON, continue scanning
106
+ // Silently ignore - this is expected for non-package directories
107
+ }
108
+ // Recurse into subdirectories (but skip if we found a package.json here)
109
+ // This allows nested package layouts (e.g. packages/foo/modules/bar)
110
+ await this.scanDirectory(path.join(dir, entry.name), depth + 1);
111
+ }
112
+ }
113
+ }
114
+ catch (error) {
115
+ // Directory read error, log but don't fail
116
+ if (error.code !== "ENOENT" && error.code !== "EACCES") {
117
+ console.warn(chalk.yellow(`[PackageRegistry] Error reading directory ${dir}:`, error.message));
118
+ }
119
+ }
120
+ }
121
+ /**
122
+ * Find package by name
123
+ */
124
+ findPackage(packageName) {
125
+ return this.packages.get(packageName) || null;
126
+ }
127
+ /**
128
+ * Get all packages
129
+ */
130
+ getAllPackages() {
131
+ return Array.from(this.packages.values());
132
+ }
133
+ /**
134
+ * Clear cache and rescan
135
+ */
136
+ async rescan() {
137
+ const roots = [...this.scanRoots];
138
+ this.packages.clear();
139
+ this.scanned = false;
140
+ this.scanRoots = [];
141
+ if (roots.length > 0) {
142
+ await this.scanWorkspace(roots[0], roots.slice(1));
143
+ }
144
+ }
145
+ /**
146
+ * Get package count
147
+ */
148
+ getPackageCount() {
149
+ return this.packages.size;
150
+ }
151
+ }
152
+ // Singleton instance
153
+ let registryInstance = null;
154
+ export function getPackageRegistry() {
155
+ if (!registryInstance) {
156
+ registryInstance = new PackageRegistry();
157
+ }
158
+ return registryInstance;
159
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Find the workspace root by looking for pnpm-workspace.yaml or package.json with workspaces
3
+ * Updated: Now also checks for lib/ directory to ensure we find the correct SWS root
4
+ */
5
+ export declare function findWorkspaceRoot(root: string): Promise<string | null>;
6
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/utils/workspace.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgD5E"}
@@ -0,0 +1,65 @@
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 path from "node:path";
8
+ /**
9
+ * Find the workspace root by looking for pnpm-workspace.yaml or package.json with workspaces
10
+ * Updated: Now also checks for lib/ directory to ensure we find the correct SWS root
11
+ */
12
+ export async function findWorkspaceRoot(root) {
13
+ let current = root;
14
+ for (let i = 0; i < 10; i++) { // Increased from 5 to 10 to go higher up
15
+ const workspaceFile = path.join(current, "pnpm-workspace.yaml");
16
+ const packageJson = path.join(current, "package.json");
17
+ const libDir = path.join(current, "lib");
18
+ try {
19
+ await fs.access(workspaceFile);
20
+ // Accept root if it has lib/ (SWS with lib/) or packages/ (SWS with packages/ at root)
21
+ const packagesDir = path.join(current, "packages");
22
+ try {
23
+ await fs.access(libDir);
24
+ console.log(`[workspace] Found workspace root with lib/: ${current}`);
25
+ return current;
26
+ }
27
+ catch {
28
+ try {
29
+ await fs.access(packagesDir);
30
+ console.log(`[workspace] Found workspace root with packages/: ${current}`);
31
+ return current;
32
+ }
33
+ catch {
34
+ // Workspace file exists but no lib/ or packages/, continue searching up
35
+ console.log(`[workspace] Found workspace file at ${current} but no lib/ or packages/, continuing search...`);
36
+ }
37
+ }
38
+ }
39
+ catch {
40
+ try {
41
+ const pkgJson = JSON.parse(await fs.readFile(packageJson, "utf-8"));
42
+ if (pkgJson?.workspaces) {
43
+ // Also check for lib/ when package.json has workspaces
44
+ try {
45
+ await fs.access(libDir);
46
+ console.log(`[workspace] Found workspace root with lib/ (via package.json): ${current}`);
47
+ return current;
48
+ }
49
+ catch {
50
+ // Has workspaces but no lib/, continue searching
51
+ }
52
+ }
53
+ }
54
+ catch {
55
+ // Continue searching
56
+ }
57
+ }
58
+ const parent = path.dirname(current);
59
+ if (parent === current)
60
+ break;
61
+ current = parent;
62
+ }
63
+ console.warn(`[workspace] No workspace root found starting from: ${root}`);
64
+ return null;
65
+ }
@@ -0,0 +1,164 @@
1
+ # Import Rewriting in SWITE
2
+
3
+ ## Overview
4
+
5
+ SWITE automatically rewrites bare module specifiers (e.g., `@swissjs/core`) to valid URLs that the browser can resolve. This document explains how it works and common issues.
6
+
7
+ ## How It Works
8
+
9
+ ### Static Imports
10
+
11
+ Static imports like `import { X } from '@swissjs/core'` are automatically rewritten to use the resolved path:
12
+
13
+ ```javascript
14
+ // Before
15
+ import { SwissApp } from '@swissjs/core';
16
+
17
+ // After (rewritten)
18
+ import { SwissApp } from '/swiss-packages/core/src/index.ts';
19
+ ```
20
+
21
+ ### Dynamic Imports
22
+
23
+ Dynamic imports with **string literals** are rewritten:
24
+
25
+ ```javascript
26
+ // ✅ This WILL be rewritten
27
+ const module = await import('@swissjs/router');
28
+
29
+ // ❌ This will NOT be rewritten (variable reference)
30
+ const module = await import(def.componentUrl);
31
+ ```
32
+
33
+ ## Critical Rule: Variable References Are NOT Rewritten
34
+
35
+ **IMPORTANT**: SWITE only rewrites **string literal** module specifiers. Variable references in dynamic imports are left unchanged.
36
+
37
+ ### Why?
38
+
39
+ Variable references like `import(def.componentUrl)` are runtime values that cannot be statically analyzed. Rewriting them would break the code:
40
+
41
+ ```javascript
42
+ // ❌ WRONG - This would break
43
+ const module = await import(https://esm.sh/def.componentUrl); // Syntax error!
44
+
45
+ // ✅ CORRECT - Variable references are left as-is
46
+ const module = await import(def.componentUrl);
47
+ ```
48
+
49
+ ### How SWITE Detects This
50
+
51
+ 1. **es-module-lexer path**: Only processes imports with quoted string literals
52
+ 2. **Regex fallback path**: Only matches patterns like `import('"specifier"')` with quotes
53
+
54
+ ## Common Issues
55
+
56
+ ### SyntaxError: Unexpected token ':'
57
+
58
+ **Symptom**: Browser error like `ShellRouter.js:41 Uncaught SyntaxError: Unexpected token ':'`
59
+
60
+ **Cause**: The import rewriter incorrectly rewrote a variable reference in a dynamic import.
61
+
62
+ **Solution**: Use an intermediate variable to ensure the import rewriter skips it:
63
+
64
+ ```javascript
65
+ // ✅ REQUIRED - Use intermediate variable
66
+ const url = def.componentUrl;
67
+ const module = await import(url);
68
+
69
+ // ❌ Wrong - Property access may be incorrectly processed
70
+ const module = await import(def.componentUrl);
71
+
72
+ // ❌ Wrong - Template literals are still processed
73
+ const module = await import(`${def.componentUrl}`);
74
+ ```
75
+
76
+ ### Module Not Found After Rewriting
77
+
78
+ **Symptom**: Module resolves but file doesn't exist
79
+
80
+ **Cause**: The resolver returned a path that doesn't exist (e.g., pointing to `dist/` when only `src/` exists)
81
+
82
+ **Solution**: SWITE automatically prefers `src/` over `dist/` in development. If issues persist, check:
83
+ 1. The package's `package.json` exports field
84
+ 2. The file actually exists at the resolved path
85
+ 3. Server logs for resolution details
86
+
87
+ ## Debugging
88
+
89
+ Enable verbose logging to see what's being rewritten:
90
+
91
+ ```bash
92
+ # Check server logs for:
93
+ [SWITE] import-rewriter: Resolved <specifier> -> <resolved>
94
+ ```
95
+
96
+ If you see a variable being resolved (e.g., `def.componentUrl`), that's a bug - report it.
97
+
98
+ ## Implementation Details
99
+
100
+ ### es-module-lexer Path (Primary)
101
+
102
+ 1. Parses the code to find all imports
103
+ 2. Extracts specifiers (with quotes)
104
+ 3. Only processes specifiers that have quotes (string literals)
105
+ 4. Skips variable references automatically
106
+
107
+ ### Regex Fallback Path
108
+
109
+ Used when es-module-lexer fails to parse (e.g., complex template literals):
110
+
111
+ 1. Matches only quoted string literals: `/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g`
112
+ 2. Validates specifier looks like a module path
113
+ 3. Skips property access patterns (e.g., `def.componentUrl`)
114
+
115
+ ## Best Practices
116
+
117
+ 1. **Use string literals for static module paths**:
118
+ ```javascript
119
+ const module = await import('@swissjs/router');
120
+ ```
121
+
122
+ 2. **Use intermediate variables for runtime-determined paths**:
123
+ ```javascript
124
+ // ✅ REQUIRED PATTERN - Use intermediate variable
125
+ const url = def.componentUrl;
126
+ const module = await import(url);
127
+
128
+ // ❌ WRONG - Property access may be incorrectly processed
129
+ const module = await import(def.componentUrl);
130
+ ```
131
+
132
+ 3. **Why intermediate variables?**
133
+ - Import rewriter skips simple identifiers (like `url`, `x`, etc.)
134
+ - Property access patterns (like `def.componentUrl`) may be incorrectly processed
135
+ - This ensures the variable is treated as a variable, not a module specifier
136
+
137
+ ## Required Pattern for Dynamic Imports with Variables
138
+
139
+ **ALWAYS** use this pattern when importing from a variable:
140
+
141
+ ```typescript
142
+ // Extract to simple identifier first
143
+ const url = def.componentUrl;
144
+ const module = await import(url);
145
+ ```
146
+
147
+ **Why this works:**
148
+ - Import rewriter validates specifiers and skips simple identifiers
149
+ - Simple identifiers (single word, no dots, no @) are treated as variables
150
+ - Property access patterns may pass validation and be incorrectly processed
151
+ - Using an intermediate variable ensures the import rewriter skips it
152
+
153
+ **What NOT to do:**
154
+ ```typescript
155
+ // ❌ Don't use property access directly
156
+ const module = await import(def.componentUrl);
157
+
158
+ // ❌ Don't use template literals
159
+ const module = await import(`${def.componentUrl}`);
160
+
161
+ // ❌ Don't use complex expressions
162
+ const module = await import(config.paths.component);
163
+ ```
164
+
@@ -0,0 +1,139 @@
1
+ # Import Rewriting Troubleshooting Guide
2
+
3
+ ## The Problem
4
+
5
+ **Browser Error:**
6
+ ```
7
+ Failed to resolve module specifier "@swiss-enterprise/ai-agents".
8
+ Relative references must start with either "/", "./", or "../".
9
+ ```
10
+
11
+ **Root Cause:**
12
+ The browser cannot resolve bare module specifiers (like `@swiss-enterprise/ai-agents`). SWITE must rewrite them to valid paths (like `/EnterpriseRepo/packages/ai-agents/src/index.ui`) before sending code to the browser.
13
+
14
+ ## Why This Keeps Happening
15
+
16
+ ### 1. **es-module-lexer Returns Unquoted Specifiers**
17
+
18
+ `es-module-lexer` sometimes returns import positions WITHOUT including the surrounding quotes. For example:
19
+ - Code: `import { X } from '@swiss-enterprise/ai-agents'`
20
+ - Lexer returns: `@swiss-enterprise/ai-agents` (no quotes)
21
+ - Our code must detect the quotes in the original code
22
+
23
+ ### 2. **Quote Detection Logic Fails**
24
+
25
+ If quote detection fails, the import rewriter skips the import, leaving bare imports in the code sent to the browser.
26
+
27
+ ### 3. **Browser Can't Resolve Bare Imports**
28
+
29
+ Browsers require:
30
+ - Absolute paths: `/path/to/file.js`
31
+ - Relative paths: `./file.js` or `../file.js`
32
+ - NOT bare specifiers: `@package/name`
33
+
34
+ ## The Fix
35
+
36
+ ### Detection Strategy
37
+
38
+ 1. **Check if lexer included quotes:**
39
+ ```typescript
40
+ const hasQuotes = (firstChar === '"' || firstChar === "'") && firstChar === lastChar;
41
+ ```
42
+
43
+ 2. **If no quotes, check surrounding code:**
44
+ ```typescript
45
+ const codeBefore = code.slice(start - 1, start);
46
+ const codeAfter = code.slice(end, end + 1);
47
+ const hasQuotesInCode = codeBefore === codeAfter && (codeBefore === '"' || codeBefore === "'");
48
+ ```
49
+
50
+ 3. **If still no quotes, search for quoted version:**
51
+ ```typescript
52
+ const quotedPattern = new RegExp(`(['"])${specifier}\\1`);
53
+ const match = quotedPattern.exec(code);
54
+ ```
55
+
56
+ 4. **If found, use those positions for replacement**
57
+
58
+ ### Replacement Strategy
59
+
60
+ - Always preserve the original quote style (`'` or `"`)
61
+ - Replace the ENTIRE quoted string, not just the specifier
62
+ - Ensure the resolved path is also properly quoted
63
+
64
+ ## What Breaks
65
+
66
+ ### ❌ What Breaks:
67
+ 1. **Skipping unquoted specifiers** - Leaves bare imports in browser code
68
+ 2. **Wrong quote detection** - Replaces wrong parts of code
69
+ 3. **Not finding quoted version** - Skips valid imports
70
+ 4. **Variable references** - Accidentally rewriting `import(variable)` as module specifiers
71
+
72
+ ### ✅ What Works:
73
+ 1. **Proper quote detection** - Finds quotes even when lexer doesn't include them
74
+ 2. **Regex fallback** - Searches code for quoted version if direct detection fails
75
+ 3. **Variable validation** - Skips property access patterns (`def.componentUrl`)
76
+ 4. **Package name validation** - Only processes valid package names (`@scope/name`)
77
+
78
+ ## How to Avoid This
79
+
80
+ ### For Developers:
81
+
82
+ 1. **Always use quoted imports:**
83
+ ```typescript
84
+ // ✅ Good
85
+ import { X } from '@swiss-enterprise/ai-agents';
86
+
87
+ // ❌ Bad (if compiler removes quotes)
88
+ import { X } from @swiss-enterprise/ai-agents;
89
+ ```
90
+
91
+ 2. **Check server logs:**
92
+ ```
93
+ [SWITE] import-rewriter: ⚠️ SKIPPING unquoted specifier
94
+ [.ui] ERROR: Bare imports still present after rewriting
95
+ ```
96
+
97
+ 3. **Verify rewritten code:**
98
+ ```bash
99
+ curl http://localhost:3001/src/index.ui | grep "@swiss-enterprise"
100
+ # Should return nothing (all imports rewritten)
101
+ ```
102
+
103
+ ### For SWITE Maintainers:
104
+
105
+ 1. **Test with real code:**
106
+ - Don't just test with simple strings
107
+ - Test with actual compiled `.ui` files
108
+ - Test with `es-module-lexer` output
109
+
110
+ 2. **Handle edge cases:**
111
+ - Unquoted specifiers from lexer
112
+ - Mixed quote styles
113
+ - Dynamic imports with variables
114
+
115
+ 3. **Add comprehensive logging:**
116
+ - Log when quotes are detected
117
+ - Log when quotes are NOT detected
118
+ - Log when regex fallback is used
119
+
120
+ ## Current Status
121
+
122
+ ✅ **Fixed:** Quote detection now uses regex fallback to find quoted versions in code
123
+ ✅ **Fixed:** Properly adjusts positions when quotes are found
124
+ ✅ **Fixed:** Preserves original quote style during replacement
125
+
126
+ ## Testing
127
+
128
+ ```bash
129
+ # Test import rewriter
130
+ cd SWS/SWISS/packages/swite
131
+ node test-import-rewriter.mjs
132
+
133
+ # Check server logs
134
+ tail -f /tmp/alpine-dev.log | grep "import-rewriter"
135
+
136
+ # Verify browser code
137
+ curl http://localhost:3001/src/index.ui | grep "@swiss-enterprise"
138
+ ```
139
+