@swissjs/swite 0.3.0 → 0.3.2

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 (187) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/adapters/proxy/SwiteProxyError.d.ts.map +1 -0
  3. package/dist/{proxy → adapters/proxy}/proxyToPython.d.ts +1 -1
  4. package/dist/adapters/proxy/proxyToPython.d.ts.map +1 -0
  5. package/dist/build-engine/builder.d.ts.map +1 -0
  6. package/dist/{builder.js → build-engine/builder.js} +8 -14
  7. package/dist/cli.js +5 -5
  8. package/dist/config/config-loader.d.ts.map +1 -0
  9. package/dist/{config.d.ts → config/config.d.ts} +11 -0
  10. package/dist/config/config.d.ts.map +1 -0
  11. package/dist/config/env.d.ts +25 -0
  12. package/dist/config/env.d.ts.map +1 -0
  13. package/dist/config/env.js +84 -0
  14. package/dist/{handlers → dev-engine/handlers}/base-handler.d.ts +5 -1
  15. package/dist/dev-engine/handlers/base-handler.d.ts.map +1 -0
  16. package/dist/dev-engine/handlers/base-handler.js +58 -0
  17. package/dist/dev-engine/handlers/js-handler.d.ts.map +1 -0
  18. package/dist/{handlers → dev-engine/handlers}/js-handler.js +1 -1
  19. package/dist/dev-engine/handlers/mjs-handler.d.ts.map +1 -0
  20. package/dist/{handlers → dev-engine/handlers}/mjs-handler.js +1 -1
  21. package/dist/dev-engine/handlers/node-module-handler.d.ts.map +1 -0
  22. package/dist/{handlers → dev-engine/handlers}/node-module-handler.js +33 -44
  23. package/dist/{handlers → dev-engine/handlers}/ts-handler.d.ts +0 -4
  24. package/dist/dev-engine/handlers/ts-handler.d.ts.map +1 -0
  25. package/dist/{handlers → dev-engine/handlers}/ts-handler.js +5 -28
  26. package/dist/{handlers → dev-engine/handlers}/ui-handler.d.ts +0 -4
  27. package/dist/dev-engine/handlers/ui-handler.d.ts.map +1 -0
  28. package/dist/dev-engine/handlers/ui-handler.js +84 -0
  29. package/dist/{handlers → dev-engine/handlers}/uix-handler.d.ts +0 -4
  30. package/dist/dev-engine/handlers/uix-handler.d.ts.map +1 -0
  31. package/dist/dev-engine/handlers/uix-handler.js +70 -0
  32. package/dist/dev-engine/hmr/hmr-client-template.d.ts +10 -0
  33. package/dist/dev-engine/hmr/hmr-client-template.d.ts.map +1 -0
  34. package/dist/dev-engine/hmr/hmr-client-template.js +122 -0
  35. package/dist/dev-engine/hmr/hmr.d.ts.map +1 -0
  36. package/dist/{hmr.js → dev-engine/hmr/hmr.js} +2 -134
  37. package/dist/{middleware → dev-engine/middleware}/hmr-routes.d.ts +2 -2
  38. package/dist/dev-engine/middleware/hmr-routes.d.ts.map +1 -0
  39. package/dist/{middleware → dev-engine/middleware}/hmr-routes.js +1 -1
  40. package/dist/dev-engine/middleware/middleware-setup.d.ts +35 -0
  41. package/dist/dev-engine/middleware/middleware-setup.d.ts.map +1 -0
  42. package/dist/dev-engine/middleware/middleware-setup.js +327 -0
  43. package/dist/dev-engine/middleware/static-files.d.ts.map +1 -0
  44. package/dist/{middleware → dev-engine/middleware}/static-files.js +2 -2
  45. package/dist/{dev → dev-engine}/pythonDevManager.d.ts +1 -1
  46. package/dist/dev-engine/pythonDevManager.d.ts.map +1 -0
  47. package/dist/{dev → dev-engine}/pythonDevManager.js +1 -1
  48. package/dist/{router → dev-engine/router}/file-router.d.ts +4 -4
  49. package/dist/dev-engine/router/file-router.d.ts.map +1 -0
  50. package/dist/{router → dev-engine/router}/file-router.js +4 -4
  51. package/dist/dev-engine/server.d.ts.map +1 -0
  52. package/dist/{server.js → dev-engine/server.js} +10 -6
  53. package/dist/index.d.ts +13 -13
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +9 -9
  56. package/dist/internal/cache/compilation-cache.d.ts.map +1 -0
  57. package/dist/{cache → internal/cache}/compilation-cache.js +3 -2
  58. package/dist/internal/generate-import-map-cli.d.ts.map +1 -0
  59. package/dist/{utils → internal}/generate-import-map-cli.js +1 -1
  60. package/dist/internal/generate-import-map.d.ts.map +1 -0
  61. package/dist/{utils → internal}/generate-import-map.js +3 -3
  62. package/dist/{utils → kernel}/package-finder.d.ts +7 -5
  63. package/dist/kernel/package-finder.d.ts.map +1 -0
  64. package/dist/kernel/package-finder.js +154 -0
  65. package/dist/kernel/package-registry.d.ts.map +1 -0
  66. package/dist/kernel/workspace.d.ts.map +1 -0
  67. package/dist/{resolver → resolution}/bare-import-resolver.d.ts +1 -1
  68. package/dist/resolution/bare-import-resolver.d.ts.map +1 -0
  69. package/dist/{resolver → resolution}/bare-import-resolver.js +25 -53
  70. package/dist/resolution/cdn/cdn-fallback.d.ts.map +1 -0
  71. package/dist/{utils → resolution/path}/file-path-resolver.d.ts +2 -1
  72. package/dist/resolution/path/file-path-resolver.d.ts.map +1 -0
  73. package/dist/{utils → resolution/path}/file-path-resolver.js +46 -28
  74. package/dist/resolution/path/path-fixup.d.ts +13 -0
  75. package/dist/resolution/path/path-fixup.d.ts.map +1 -0
  76. package/dist/resolution/path/path-fixup.js +20 -0
  77. package/dist/{resolver.d.ts → resolution/resolver.d.ts} +1 -1
  78. package/dist/resolution/resolver.d.ts.map +1 -0
  79. package/dist/{resolver.js → resolution/resolver.js} +8 -37
  80. package/dist/{import-rewriter.d.ts → resolution/rewriting/import-rewriter.d.ts} +1 -1
  81. package/dist/resolution/rewriting/import-rewriter.d.ts.map +1 -0
  82. package/dist/resolution/rewriting/import-rewriter.js +199 -0
  83. package/dist/{resolver → resolution}/symlink-registry.d.ts +1 -1
  84. package/dist/resolution/symlink-registry.d.ts.map +1 -0
  85. package/dist/{resolver → resolution}/symlink-registry.js +1 -1
  86. package/dist/resolution/url-resolver.d.ts.map +1 -0
  87. package/dist/{resolver → resolution}/url-resolver.js +38 -109
  88. package/dist/resolution/workspace-package-resolver.d.ts.map +1 -0
  89. package/dist/resolution/workspace-package-resolver.js +77 -0
  90. package/docs/architecture/build-pipeline.md +97 -0
  91. package/docs/architecture/dev-server.md +87 -0
  92. package/docs/architecture/hmr.md +78 -0
  93. package/docs/architecture/import-rewriting.md +101 -0
  94. package/docs/architecture/index.md +16 -0
  95. package/docs/architecture/python-integration.md +93 -0
  96. package/docs/architecture/resolution.md +92 -0
  97. package/docs/cli/build.md +78 -0
  98. package/docs/cli/dev.md +90 -0
  99. package/docs/cli/index.md +15 -0
  100. package/docs/cli/start.md +45 -0
  101. package/docs/development/contributing.md +74 -0
  102. package/docs/development/index.md +12 -0
  103. package/docs/development/internals.md +101 -0
  104. package/docs/guide/configuration.md +89 -0
  105. package/docs/guide/index.md +13 -0
  106. package/docs/guide/project-structure.md +75 -0
  107. package/docs/guide/quickstart.md +113 -0
  108. package/docs/index.md +16 -0
  109. package/package.json +5 -5
  110. package/src/cli.ts +1 -1
  111. package/src/config/config.ts +11 -0
  112. package/src/dev-engine/handlers/base-handler.ts +4 -2
  113. package/src/dev-engine/handlers/node-module-handler.ts +51 -78
  114. package/src/dev-engine/middleware/middleware-setup.ts +1 -0
  115. package/src/dev-engine/server.ts +38 -33
  116. package/src/kernel/package-finder.ts +59 -43
  117. package/src/resolution/bare-import-resolver.ts +14 -4
  118. package/src/resolution/path/file-path-resolver.ts +44 -10
  119. package/src/resolution/url-resolver.ts +1 -1
  120. package/dist/builder.d.ts.map +0 -1
  121. package/dist/cache/compilation-cache.d.ts.map +0 -1
  122. package/dist/config-loader.d.ts.map +0 -1
  123. package/dist/config.d.ts.map +0 -1
  124. package/dist/dev/pythonDevManager.d.ts.map +0 -1
  125. package/dist/env.d.ts +0 -19
  126. package/dist/env.d.ts.map +0 -1
  127. package/dist/env.js +0 -112
  128. package/dist/handlers/base-handler.d.ts.map +0 -1
  129. package/dist/handlers/base-handler.js +0 -38
  130. package/dist/handlers/js-handler.d.ts.map +0 -1
  131. package/dist/handlers/mjs-handler.d.ts.map +0 -1
  132. package/dist/handlers/node-module-handler.d.ts.map +0 -1
  133. package/dist/handlers/ts-handler.d.ts.map +0 -1
  134. package/dist/handlers/ui-handler.d.ts.map +0 -1
  135. package/dist/handlers/ui-handler.js +0 -182
  136. package/dist/handlers/uix-handler.d.ts.map +0 -1
  137. package/dist/handlers/uix-handler.js +0 -135
  138. package/dist/hmr.d.ts.map +0 -1
  139. package/dist/import-rewriter.d.ts.map +0 -1
  140. package/dist/import-rewriter.js +0 -351
  141. package/dist/middleware/hmr-routes.d.ts.map +0 -1
  142. package/dist/middleware/middleware-setup.d.ts +0 -23
  143. package/dist/middleware/middleware-setup.d.ts.map +0 -1
  144. package/dist/middleware/middleware-setup.js +0 -596
  145. package/dist/middleware/static-files.d.ts.map +0 -1
  146. package/dist/proxy/SwiteProxyError.d.ts.map +0 -1
  147. package/dist/proxy/proxyToPython.d.ts.map +0 -1
  148. package/dist/resolver/bare-import-resolver.d.ts.map +0 -1
  149. package/dist/resolver/symlink-registry.d.ts.map +0 -1
  150. package/dist/resolver/url-resolver.d.ts.map +0 -1
  151. package/dist/resolver/workspace-package-resolver.d.ts.map +0 -1
  152. package/dist/resolver/workspace-package-resolver.js +0 -185
  153. package/dist/resolver.d.ts.map +0 -1
  154. package/dist/router/file-router.d.ts.map +0 -1
  155. package/dist/server.d.ts.map +0 -1
  156. package/dist/utils/cdn-fallback.d.ts.map +0 -1
  157. package/dist/utils/file-path-resolver.d.ts.map +0 -1
  158. package/dist/utils/generate-import-map-cli.d.ts.map +0 -1
  159. package/dist/utils/generate-import-map.d.ts.map +0 -1
  160. package/dist/utils/package-finder.d.ts.map +0 -1
  161. package/dist/utils/package-finder.js +0 -161
  162. package/dist/utils/package-registry.d.ts.map +0 -1
  163. package/dist/utils/workspace.d.ts.map +0 -1
  164. /package/dist/{proxy → adapters/proxy}/SwiteProxyError.d.ts +0 -0
  165. /package/dist/{proxy → adapters/proxy}/SwiteProxyError.js +0 -0
  166. /package/dist/{proxy → adapters/proxy}/proxyToPython.js +0 -0
  167. /package/dist/{builder.d.ts → build-engine/builder.d.ts} +0 -0
  168. /package/dist/{config-loader.d.ts → config/config-loader.d.ts} +0 -0
  169. /package/dist/{config-loader.js → config/config-loader.js} +0 -0
  170. /package/dist/{config.js → config/config.js} +0 -0
  171. /package/dist/{handlers → dev-engine/handlers}/js-handler.d.ts +0 -0
  172. /package/dist/{handlers → dev-engine/handlers}/mjs-handler.d.ts +0 -0
  173. /package/dist/{handlers → dev-engine/handlers}/node-module-handler.d.ts +0 -0
  174. /package/dist/{hmr.d.ts → dev-engine/hmr/hmr.d.ts} +0 -0
  175. /package/dist/{middleware → dev-engine/middleware}/static-files.d.ts +0 -0
  176. /package/dist/{server.d.ts → dev-engine/server.d.ts} +0 -0
  177. /package/dist/{cache → internal/cache}/compilation-cache.d.ts +0 -0
  178. /package/dist/{utils → internal}/generate-import-map-cli.d.ts +0 -0
  179. /package/dist/{utils → internal}/generate-import-map.d.ts +0 -0
  180. /package/dist/{utils → kernel}/package-registry.d.ts +0 -0
  181. /package/dist/{utils → kernel}/package-registry.js +0 -0
  182. /package/dist/{utils → kernel}/workspace.d.ts +0 -0
  183. /package/dist/{utils → kernel}/workspace.js +0 -0
  184. /package/dist/{utils → resolution/cdn}/cdn-fallback.d.ts +0 -0
  185. /package/dist/{utils → resolution/cdn}/cdn-fallback.js +0 -0
  186. /package/dist/{resolver → resolution}/url-resolver.d.ts +0 -0
  187. /package/dist/{resolver → resolution}/workspace-package-resolver.d.ts +0 -0
@@ -0,0 +1,199 @@
1
+ /*
2
+ * Import Rewriter for SWITE
3
+ *
4
+ * Design: collect-then-apply-right-to-left
5
+ *
6
+ * es-module-lexer gives positions {s, e} in the ORIGINAL string. The previous
7
+ * implementation tracked a running `offset` as replacements were applied, which
8
+ * accumulated errors when quote handling changed string lengths in unexpected
9
+ * ways and required three layers of fallback replacement logic.
10
+ *
11
+ * Instead we now:
12
+ * 1. Collect every replacement as {start, end, text} in original-string coordinates
13
+ * 2. Sort descending by start position
14
+ * 3. Apply right-to-left — each substitution cannot shift the position of any
15
+ * replacement to its left, so no offset tracking is needed at all.
16
+ */
17
+ import { init, parse } from "es-module-lexer";
18
+ import { ModuleResolver } from "../resolver.js";
19
+ import { promises as fs } from "node:fs";
20
+ import path from "node:path";
21
+ import chalk from "chalk";
22
+ import { shouldUseCdnFallback } from "../cdn/cdn-fallback.js";
23
+ export async function rewriteImports(code, importer, resolver) {
24
+ await init;
25
+ try {
26
+ const [imports] = parse(code);
27
+ if (imports.length === 0)
28
+ return code;
29
+ const replacements = [];
30
+ for (const imp of imports) {
31
+ const { s: rawStart, e: rawEnd } = imp;
32
+ const rawSpecifier = code.slice(rawStart, rawEnd);
33
+ // Skip CSS imports — handled as static assets
34
+ if (rawSpecifier.includes(".css"))
35
+ continue;
36
+ // Determine actual specifier string and the span in `code` that includes quotes
37
+ const { specifier, start, end } = resolveQuotedSpan(code, rawSpecifier, rawStart, rawEnd);
38
+ if (specifier === null)
39
+ continue;
40
+ // Fix compiler bug: .uix/.ui imports emitted as .js or .tsx
41
+ if (specifier.startsWith(".") &&
42
+ (specifier.endsWith(".js") || specifier.endsWith(".tsx")) &&
43
+ !specifier.includes("node_modules")) {
44
+ const newExt = await resolveExtensionFix(specifier, importer);
45
+ if (newExt) {
46
+ const base = specifier.endsWith(".tsx") ? specifier.slice(0, -4) : specifier.slice(0, -3);
47
+ replacements.push({ start, end, text: `"${base}${newExt}"` });
48
+ continue;
49
+ }
50
+ }
51
+ // Skip relative and absolute path imports (already resolved)
52
+ if (specifier.startsWith(".") || specifier.startsWith("/"))
53
+ continue;
54
+ if (!/^[@a-zA-Z]/.test(specifier)) {
55
+ console.warn(`[SWITE] import-rewriter: Invalid specifier format: ${specifier}`);
56
+ continue;
57
+ }
58
+ // Resolve bare import
59
+ let resolved;
60
+ try {
61
+ resolved = await resolver.resolve(specifier, importer);
62
+ if (!resolved || resolved === specifier || (!resolved.startsWith("/") && !resolved.startsWith("http"))) {
63
+ console.warn(chalk.yellow(`[SWITE] import-rewriter: Resolver returned invalid result for ${specifier}, using CDN fallback`));
64
+ resolved = shouldUseCdnFallback(specifier)
65
+ ? `https://cdn.jsdelivr.net/npm/${specifier}/+esm`
66
+ : `/node_modules/${specifier}`;
67
+ }
68
+ }
69
+ catch (error) {
70
+ console.error(chalk.red(`[SWITE] import-rewriter: Error resolving ${specifier}:`), error);
71
+ resolved = shouldUseCdnFallback(specifier)
72
+ ? `https://cdn.jsdelivr.net/npm/${specifier}/+esm`
73
+ : `/node_modules/${specifier}`;
74
+ }
75
+ // Prefer src/ over dist/ for workspace/swiss packages in dev
76
+ if (resolved.includes("/dist/") && (resolved.includes("/swiss-packages/") || resolved.includes("/packages/"))) {
77
+ resolved = resolved.replace("/dist/", "/src/").replace(/\.js$/, ".ts");
78
+ }
79
+ replacements.push({ start, end, text: `"${resolved}"` });
80
+ }
81
+ // Apply right-to-left so earlier positions are never shifted by later replacements
82
+ replacements.sort((a, b) => b.start - a.start);
83
+ let result = code;
84
+ for (const { start, end, text } of replacements) {
85
+ result = result.slice(0, start) + text + result.slice(end);
86
+ }
87
+ // Safety net: catch any bare scoped imports the lexer may have missed
88
+ const barePattern = /(?:import|from|export)\s+['"](@[^'"]+\/[^'"]+)[^'"]*['"]/g;
89
+ for (const match of Array.from(result.matchAll(barePattern))) {
90
+ const bareImport = match[1];
91
+ if (!bareImport.startsWith("/") && !bareImport.startsWith("http") && !bareImport.startsWith(".")) {
92
+ console.error(chalk.red(`[SWITE] import-rewriter: CRITICAL — bare import "${bareImport}" still present after rewriting`));
93
+ const replacement = shouldUseCdnFallback(bareImport)
94
+ ? `https://cdn.jsdelivr.net/npm/${bareImport}/+esm`
95
+ : `/node_modules/${bareImport}`;
96
+ result = result.replace(new RegExp(bareImport.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), replacement);
97
+ }
98
+ }
99
+ // Regex fallback: fix relative .js/.tsx extension mismatches the lexer may have missed
100
+ const normalizedImporter = importer.replace(/\\/g, "/");
101
+ const isSwissPackage = normalizedImporter.includes("/swiss-packages/");
102
+ const isUixFile = normalizedImporter.endsWith(".uix") || normalizedImporter.endsWith(".ui");
103
+ result = result.replace(/from\s+(["'])(\.\.?\/[^"']*?)(\.js|\.tsx)(\1)/g, (match, quote, importPath, _ext, endQuote) => {
104
+ if (importPath.includes("node_modules") || !importPath.startsWith("."))
105
+ return match;
106
+ const isLibPath = normalizedImporter.includes("/lib/");
107
+ let newExt;
108
+ if (isSwissPackage || isLibPath) {
109
+ newExt = ".ts";
110
+ }
111
+ else if (isUixFile) {
112
+ newExt = normalizedImporter.endsWith(".ui") ? ".ui" : ".uix";
113
+ }
114
+ else {
115
+ newExt = ".ts";
116
+ }
117
+ return `from ${quote}${importPath}${newExt}${endQuote}`;
118
+ });
119
+ return result;
120
+ }
121
+ catch (error) {
122
+ console.error(chalk.red(`[SWITE] import-rewriter: Error rewriting imports in ${importer}:`), error);
123
+ return code;
124
+ }
125
+ }
126
+ /**
127
+ * Given a raw specifier token from es-module-lexer, find the full quoted span
128
+ * in `code` (including the surrounding quote characters) and extract the clean
129
+ * specifier string. Returns `{specifier: null}` when the span cannot be found.
130
+ */
131
+ function resolveQuotedSpan(code, rawSpecifier, rawStart, rawEnd) {
132
+ const first = rawSpecifier[0];
133
+ const last = rawSpecifier[rawSpecifier.length - 1];
134
+ // Case 1: lexer returned the specifier WITH surrounding quotes
135
+ if ((first === '"' || first === "'") && first === last) {
136
+ return {
137
+ specifier: rawSpecifier.slice(1, -1),
138
+ start: rawStart,
139
+ end: rawEnd,
140
+ };
141
+ }
142
+ // Case 2: lexer returned the bare specifier; look one char back/forward for quotes
143
+ const charBefore = rawStart > 0 ? code[rawStart - 1] : "";
144
+ const charAfter = rawEnd < code.length ? code[rawEnd] : "";
145
+ if ((charBefore === '"' || charBefore === "'") && charBefore === charAfter) {
146
+ return {
147
+ specifier: rawSpecifier,
148
+ start: rawStart - 1,
149
+ end: rawEnd + 1,
150
+ };
151
+ }
152
+ // Case 3: search nearby for a quoted pattern
153
+ const escaped = rawSpecifier.replace(/[.*+?^${}()|[\]\\-]/g, "\\$&");
154
+ const pattern = new RegExp(`(['"])${escaped}\\1`);
155
+ const match = pattern.exec(code);
156
+ if (match) {
157
+ return {
158
+ specifier: rawSpecifier,
159
+ start: match.index,
160
+ end: match.index + match[0].length,
161
+ };
162
+ }
163
+ console.warn(`[SWITE] import-rewriter: Could not find quotes for specifier: ${rawSpecifier}`);
164
+ return { specifier: null, start: rawStart, end: rawEnd };
165
+ }
166
+ /**
167
+ * Determine what extension a .js/.tsx import should be rewritten to,
168
+ * based on the importer's context and the actual files present on disk.
169
+ * Returns null when no rewrite is needed.
170
+ */
171
+ async function resolveExtensionFix(specifier, importer) {
172
+ const normalizedImporter = importer.replace(/\\/g, "/");
173
+ const isSwissPackage = normalizedImporter.includes("/swiss-packages/");
174
+ const isLibPath = normalizedImporter.includes("/lib/");
175
+ const isUixFile = normalizedImporter.endsWith(".uix") || normalizedImporter.endsWith(".ui");
176
+ if (isSwissPackage || isLibPath)
177
+ return ".ts";
178
+ if (isUixFile) {
179
+ const base = specifier.endsWith(".tsx") ? specifier.slice(0, -4) : specifier.slice(0, -3);
180
+ const currentDir = path.dirname(importer);
181
+ const cleanPath = base.startsWith("./") ? base.slice(2) : base;
182
+ const uiPath = path.resolve(currentDir, cleanPath + ".ui");
183
+ const uixPath = path.resolve(currentDir, cleanPath + ".uix");
184
+ try {
185
+ await fs.access(uiPath);
186
+ return ".ui";
187
+ }
188
+ catch {
189
+ try {
190
+ await fs.access(uixPath);
191
+ return ".uix";
192
+ }
193
+ catch {
194
+ return ".ui";
195
+ }
196
+ }
197
+ }
198
+ return ".ts";
199
+ }
@@ -7,7 +7,7 @@ export declare function buildSymlinkRegistry(nodeModulesDirs: string[]): Promise
7
7
  *
8
8
  * Example:
9
9
  * /mnt/c/.../swiss-lib/packages/core/src/index.ts
10
- * → /node_modules/@kibologic/core/src/index.ts
10
+ * → /node_modules/@swissjs/core/src/index.ts
11
11
  */
12
12
  export declare function lookupInSymlinkRegistry(absolutePath: string): string | null;
13
13
  //# sourceMappingURL=symlink-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symlink-registry.d.ts","sourceRoot":"","sources":["../../src/resolution/symlink-registry.ts"],"names":[],"mappings":"AAoBA,wBAAsB,oBAAoB,CACxC,eAAe,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC,CAQf;AA8DD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW3E"}
@@ -82,7 +82,7 @@ async function registerSymlink(symlinkPath, pkgName) {
82
82
  *
83
83
  * Example:
84
84
  * /mnt/c/.../swiss-lib/packages/core/src/index.ts
85
- * → /node_modules/@kibologic/core/src/index.ts
85
+ * → /node_modules/@swissjs/core/src/index.ts
86
86
  */
87
87
  export function lookupInSymlinkRegistry(absolutePath) {
88
88
  const normalized = absolutePath.replace(/\\/g, "/");
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-resolver.d.ts","sourceRoot":"","sources":["../../src/resolution/url-resolver.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/C,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACpD;AAED,MAAM,MAAM,+BAA+B,GAAG,kBAAkB,CAAC;AAMjE;;GAEG;AACH,wBAAsB,KAAK,CACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA0MjB"}
@@ -4,24 +4,10 @@
4
4
  */
5
5
  import path from "node:path";
6
6
  import { promises as fs } from "node:fs";
7
- import { findSwissLibMonorepo } from "../utils/package-finder.js";
7
+ import { findSwissLibMonorepo } from "../kernel/package-finder.js";
8
8
  import { lookupInSymlinkRegistry } from "./symlink-registry.js";
9
- /**
10
- * Normalize result to ensure no /swiss-lib/ paths leak to browser
11
- */
12
9
  function normalizeResult(result) {
13
- const original = result;
14
- if (result.includes('/swiss-lib/')) {
15
- console.log(`[SWITE] normalizeResult: Found /swiss-lib/ in "${result}", fixing...`);
16
- result = result.replace(/\/swiss-lib\/packages\//g, '/swiss-packages/');
17
- result = result.replace(/\/swiss-lib\//g, '/swiss-packages/');
18
- console.log(`[SWITE] normalizeResult: Fixed to "${result}"`);
19
- }
20
- result = result.replace(/\\/g, '/');
21
- if (original !== result && original.includes('swiss-lib')) {
22
- console.log(`[SWITE] normalizeResult: Final result "${result}" (was "${original}")`);
23
- }
24
- return result;
10
+ return result.replace(/\\/g, '/');
25
11
  }
26
12
  /**
27
13
  * Convert file path to URL for browser
@@ -85,103 +71,72 @@ export async function toUrl(filePath, context) {
85
71
  return normalizeResult(url);
86
72
  }
87
73
  }
88
- // If path is already a URL (starts with / or http), check for source file first
74
+ // If path is already a URL (starts with / or http), prefer src over dist for workspace packages
89
75
  if (normalized.startsWith("/") || normalized.startsWith("http")) {
90
- let workingPath = normalized;
91
- if (normalized.includes("/swiss-lib/packages/")) {
92
- workingPath = normalized.replace(/\/swiss-lib\/packages\//g, "/swiss-packages/");
93
- console.log(`[SWITE] toUrl: Converting /swiss-lib/ to /swiss-packages/: ${normalized} -> ${workingPath}`);
94
- if (workingPath.includes("/dist/") && !workingPath.includes("/src/")) {
95
- const srcPath = workingPath.replace("/dist/", "/src/").replace(/\.js$/, ".ts");
96
- console.log(`[SWITE] toUrl: Checking for source file: ${srcPath}`);
97
- const { resolveFilePath } = await import("../utils/file-path-resolver.js");
98
- const workspaceRoot = await context.getWorkspaceRoot();
99
- const srcFilePath = await resolveFilePath(srcPath, context.root, workspaceRoot);
100
- console.log(`[SWITE] toUrl: Resolved source file path: ${srcFilePath}, exists: ${await context.fileExists(srcFilePath)}`);
101
- if (await context.fileExists(srcFilePath)) {
102
- console.log(`[SWITE] toUrl: Preferring source over dist: ${srcPath}`);
103
- return normalizeResult(srcPath);
104
- }
105
- }
106
- return normalizeResult(workingPath);
107
- }
108
- // Only prefer src over dist for workspace/swiss packages — never for node_modules
109
- // (published packages only ship dist; the swiss-lib monorepo finder can
110
- // accidentally resolve src/index.ts from a sibling repo for npm packages)
111
- if (workingPath.includes("/dist/") && !workingPath.includes("/src/") && !workingPath.includes("/node_modules/")) {
112
- const srcPath = workingPath.replace("/dist/", "/src/").replace(/\.js$/, ".ts");
113
- console.log(`[SWITE] toUrl: Checking for source file: ${srcPath}`);
114
- const { resolveFilePath } = await import("../utils/file-path-resolver.js");
76
+ // Only prefer src over dist for workspace packages — never for node_modules
77
+ if (normalized.includes("/dist/") && !normalized.includes("/src/") && !normalized.includes("/node_modules/")) {
78
+ const srcPath = normalized.replace("/dist/", "/src/").replace(/\.js$/, ".ts");
79
+ const { resolveFilePath } = await import("./path/file-path-resolver.js");
115
80
  const workspaceRoot = await context.getWorkspaceRoot();
116
81
  const srcFilePath = await resolveFilePath(srcPath, context.root, workspaceRoot);
117
- console.log(`[SWITE] toUrl: Resolved source file path: ${srcFilePath}, exists: ${await context.fileExists(srcFilePath)}`);
118
82
  if (await context.fileExists(srcFilePath)) {
119
- console.log(`[SWITE] toUrl: Preferring source over dist: ${srcPath}`);
120
83
  return normalizeResult(srcPath);
121
84
  }
122
85
  }
123
- return normalizeResult(workingPath);
86
+ return normalizeResult(normalized);
124
87
  }
125
- // If path is absolute, try to make it relative to workspace root or server root
88
+ // If path is absolute, convert to a browser-relative URL
126
89
  if (path.isAbsolute(filePath)) {
127
90
  const workspaceRoot = await context.getWorkspaceRoot();
128
- // Check if this is a swiss-lib monorepo package (@kibologic/*)
129
- const swissLib = await findSwissLibMonorepo(context.root);
130
- console.log(`[SWITE] toUrl: swiss-lib check - swissLib=${swissLib}, this.root=${context.root}, filePath=${filePath}`);
131
- if (swissLib) {
132
- const swissPackagesPath = path.join(swissLib, "packages");
133
- let resolvedSwissPackages;
91
+ // Check if the file lives in a co-located framework monorepo's packages/ directory.
92
+ // Serve those files under /swiss-packages/ so the browser can request them distinctly
93
+ // from the app's own files. Works for any framework at any directory name.
94
+ const monorepo = await findSwissLibMonorepo(context.root);
95
+ if (monorepo) {
96
+ const packagesPath = path.join(monorepo, "packages");
97
+ let resolvedPackages;
134
98
  let resolvedFilePath;
135
99
  try {
136
- resolvedSwissPackages = await fs.realpath(swissPackagesPath);
100
+ resolvedPackages = await fs.realpath(packagesPath);
137
101
  resolvedFilePath = await fs.realpath(filePath);
138
102
  }
139
103
  catch {
140
- resolvedSwissPackages = path.resolve(swissPackagesPath);
104
+ resolvedPackages = path.resolve(packagesPath);
141
105
  resolvedFilePath = path.resolve(filePath);
142
106
  }
143
- const normalizedSwissPackages = resolvedSwissPackages.replace(/\\/g, "/").toLowerCase();
144
- const normalizedFilePath = resolvedFilePath.replace(/\\/g, "/").toLowerCase();
145
- console.log(`[SWITE] toUrl: swiss-lib path comparison - normalizedSwissPackages="${normalizedSwissPackages}", normalizedFilePath="${normalizedFilePath}", startsWith=${normalizedFilePath.startsWith(normalizedSwissPackages)}`);
146
- if (normalizedFilePath.startsWith(normalizedSwissPackages)) {
147
- console.log(`[SWITE] toUrl: swiss-lib path MATCHED! Converting to /swiss-packages/`);
148
- const origSwissPackages = resolvedSwissPackages.replace(/\\/g, "/");
149
- const origFilePath = resolvedFilePath.replace(/\\/g, "/");
150
- const relative = origFilePath.slice(origSwissPackages.length);
107
+ const normalizedPackages = resolvedPackages.replace(/\\/g, "/").toLowerCase();
108
+ const normalizedResolved = resolvedFilePath.replace(/\\/g, "/").toLowerCase();
109
+ if (normalizedResolved.startsWith(normalizedPackages)) {
110
+ const origPackages = resolvedPackages.replace(/\\/g, "/");
111
+ const origFile = resolvedFilePath.replace(/\\/g, "/");
112
+ const relative = origFile.slice(origPackages.length);
151
113
  const url = "/swiss-packages" + (relative.startsWith("/") ? relative : "/" + relative);
152
114
  if (url.includes("/dist/") && !url.includes("/src/")) {
153
115
  const srcUrl = url.replace("/dist/", "/src/").replace(/\.js$/, ".ts");
154
116
  const srcRelative = srcUrl.replace("/swiss-packages/", "");
155
- const srcFilePath = path.join(swissPackagesPath, srcRelative);
156
- console.log(`[SWITE] toUrl: Checking source file: ${srcFilePath}, exists: ${await context.fileExists(srcFilePath)}`);
117
+ const srcFilePath = path.join(packagesPath, srcRelative);
157
118
  if (await context.fileExists(srcFilePath)) {
158
- console.log(`[SWITE] toUrl: Preferring source over dist: ${srcUrl}`);
159
119
  return normalizeResult(srcUrl);
160
120
  }
161
121
  }
162
- console.log(`[SWITE] toUrl: ${filePath} -> ${url} (swiss-lib package)`);
163
122
  return normalizeResult(url);
164
123
  }
165
124
  }
166
- // Check if this is a node_modules path
125
+ // node_modules absolute path browser /node_modules/... URL
167
126
  const origFilePathNormalized = path.resolve(filePath).replace(/\\/g, "/");
168
127
  if (origFilePathNormalized.toLowerCase().includes("/node_modules/")) {
169
128
  const nodeModulesIndex = origFilePathNormalized.toLowerCase().indexOf("/node_modules/");
170
129
  const afterNodeModules = origFilePathNormalized.slice(nodeModulesIndex + "/node_modules/".length);
171
- const url = "/node_modules/" + afterNodeModules;
172
- console.log(`[SWITE] toUrl: ${filePath} -> ${url} (node_modules, preserving case)`);
173
- return normalizeResult(url);
130
+ return normalizeResult("/node_modules/" + afterNodeModules);
174
131
  }
175
- // Try relative to app root FIRST
176
- const normalizedRoot = path.resolve(context.root).replace(/\\/g, "/").toLowerCase();
177
132
  const normalizedFilePath = path.resolve(filePath).replace(/\\/g, "/").toLowerCase();
133
+ // Try relative to app root first
134
+ const normalizedRoot = path.resolve(context.root).replace(/\\/g, "/").toLowerCase();
178
135
  if (normalizedFilePath.startsWith(normalizedRoot)) {
179
136
  const origRoot = path.resolve(context.root).replace(/\\/g, "/");
180
137
  const origFilePath = path.resolve(filePath).replace(/\\/g, "/");
181
138
  const relative = origFilePath.slice(origRoot.length);
182
- const url = relative.startsWith("/") ? relative : "/" + relative;
183
- console.log(`[SWITE] toUrl: ${filePath} -> ${url} (app root: ${context.root})`);
184
- return normalizeResult(url);
139
+ return normalizeResult(relative.startsWith("/") ? relative : "/" + relative);
185
140
  }
186
141
  // Try workspace root
187
142
  if (workspaceRoot) {
@@ -189,51 +144,25 @@ export async function toUrl(filePath, context) {
189
144
  if (normalizedFilePath.startsWith(normalizedWorkspaceRoot)) {
190
145
  const origWorkspaceRoot = path.resolve(workspaceRoot).replace(/\\/g, "/");
191
146
  const origFilePath = path.resolve(filePath).replace(/\\/g, "/");
192
- // CRITICAL: Check if this is a swiss-lib path BEFORE computing relative
193
- const normalizedOrigFilePath = origFilePath.toLowerCase();
194
- console.log(`[SWITE] toUrl DEBUG: origFilePath="${origFilePath}", normalized="${normalizedOrigFilePath}", checking for swiss-lib...`);
195
- if (normalizedOrigFilePath.includes("/swiss-lib/packages/") || normalizedOrigFilePath.includes("\\swiss-lib\\packages\\")) {
196
- console.log(`[SWITE] toUrl DEBUG: ✅ Found swiss-lib in path!`);
197
- const swissLibIndex = normalizedOrigFilePath.indexOf("/swiss-lib/packages/");
198
- const swissLibIndexBackslash = normalizedOrigFilePath.indexOf("\\swiss-lib\\packages\\");
199
- const index = swissLibIndex >= 0 ? swissLibIndex : swissLibIndexBackslash;
200
- const separator = swissLibIndex >= 0 ? "/swiss-lib/packages/" : "\\swiss-lib\\packages\\";
201
- const afterSwissLib = origFilePath.slice(index + separator.length);
202
- const url = "/swiss-packages/" + afterSwissLib.replace(/\\/g, "/");
203
- console.log(`[SWITE] toUrl: ${filePath} -> ${url} (swiss-lib via workspace root - FIXED)`);
204
- return normalizeResult(url);
205
- }
206
147
  const relative = origFilePath.slice(origWorkspaceRoot.length);
207
148
  let url = relative.startsWith("/") ? relative : "/" + relative;
208
- // Prefer src over dist for workspace /packages/ in dev (unbuilt packages)
209
- if (url.includes("/packages/") &&
210
- url.includes("/dist/") &&
211
- (await context.fileExists(filePath)) === false) {
212
- const srcPath = filePath.replace(/[/\\]dist[/\\]/, path.sep + "src" + path.sep).replace(/\.js$/i, ".ts");
149
+ // Prefer src over dist for workspace packages in dev
150
+ if (url.includes("/packages/") && url.includes("/dist/") && !(await context.fileExists(filePath))) {
151
+ const srcPath = filePath
152
+ .replace(/[/\\]dist[/\\]/, path.sep + "src" + path.sep)
153
+ .replace(/\.js$/i, ".ts");
213
154
  if (await context.fileExists(srcPath)) {
214
155
  const srcRelative = path.relative(workspaceRoot, srcPath).replace(/\\/g, "/");
215
156
  url = "/" + srcRelative;
216
- console.log(`[SWITE] toUrl: Preferring src over dist (packages): ${url}`);
217
157
  }
218
158
  }
219
- console.log(`[SWITE] toUrl DOUBLE CHECK: url="${url}", lowercase="${url.toLowerCase()}", includes="/swiss-lib/"=${url.toLowerCase().includes("/swiss-lib/")}`);
220
- // DOUBLE CHECK: If computed URL contains /swiss-lib/, fix it
221
- if (url.toLowerCase().includes("/swiss-lib/")) {
222
- console.log(`[SWITE] toUrl DOUBLE CHECK: ✅ MATCHED! Fixing URL...`);
223
- const fixedUrl = url.replace(/\/swiss-lib\/packages\//gi, "/swiss-packages/").replace(/\/swiss-lib\//gi, "/swiss-packages/");
224
- console.log(`[SWITE] toUrl: ${filePath} -> ${fixedUrl} (workspace root - FIXED /swiss-lib/ in URL)`);
225
- return normalizeResult(fixedUrl);
226
- }
227
- console.log(`[SWITE] toUrl: ${filePath} -> ${url} (workspace: ${workspaceRoot})`);
228
159
  return normalizeResult(url);
229
160
  }
230
161
  }
231
162
  // Fallback
232
- const fallbackWorkspaceRoot = await context.getWorkspaceRoot();
233
- const baseRoot = fallbackWorkspaceRoot || context.root;
163
+ const baseRoot = workspaceRoot || context.root;
234
164
  const rawRelative = path.relative(baseRoot, filePath);
235
- // CG-03: path.relative() returns an absolute path when crossing drives or WSL mounts.
236
- // Guard: if the result is absolute or escapes root with '..', strip prefix directly.
165
+ // CG-03: guard against absolute result from path.relative() on cross-drive/WSL paths
237
166
  let url;
238
167
  if (path.isAbsolute(rawRelative) || rawRelative.startsWith("..")) {
239
168
  const normalizedBase = path.resolve(baseRoot).replace(/\\/g, "/");
@@ -246,7 +175,7 @@ export async function toUrl(filePath, context) {
246
175
  else {
247
176
  url = "/" + rawRelative.replace(/\\/g, "/");
248
177
  }
249
- console.warn(`[SWITE] toUrl fallback: ${filePath} -> ${url} (may not work if path goes outside root)`);
178
+ console.warn(`[SWITE] toUrl fallback: ${filePath} -> ${url}`);
250
179
  return normalizeResult(url);
251
180
  }
252
181
  // Default: make relative to root
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-package-resolver.d.ts","sourceRoot":"","sources":["../../src/resolution/workspace-package-resolver.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,+BAA+B;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/C,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACpD;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwExB"}
@@ -0,0 +1,77 @@
1
+ /*
2
+ * Workspace Package Resolver - Finds packages in workspace
3
+ * Uses dynamic package registry instead of hardcoded paths
4
+ */
5
+ import path from "node:path";
6
+ import { findSwissLibMonorepo } from "../kernel/package-finder.js";
7
+ import { getPackageRegistry } from "../kernel/package-registry.js";
8
+ /**
9
+ * Resolve workspace package location
10
+ */
11
+ export async function resolveWorkspacePackage(pkgName, context) {
12
+ // Build the ordered list of roots to scan for workspace packages.
13
+ // Works for any package name/scope — no hardcoded scope guards.
14
+ const workspaceRoots = [];
15
+ let workspaceRoot = await context.getWorkspaceRoot();
16
+ if (workspaceRoot) {
17
+ workspaceRoots.push(workspaceRoot);
18
+ }
19
+ else {
20
+ // Walk up from app root looking for common workspace markers
21
+ for (const candidate of [
22
+ path.join(context.root, ".."),
23
+ path.join(context.root, "..", ".."),
24
+ ]) {
25
+ const normalized = path.resolve(candidate);
26
+ if ((await context.fileExists(path.join(normalized, "pnpm-workspace.yaml"))) ||
27
+ (await context.fileExists(path.join(normalized, "modules"))) ||
28
+ (await context.fileExists(path.join(normalized, "libraries")))) {
29
+ workspaceRoot = normalized;
30
+ workspaceRoots.push(normalized);
31
+ break;
32
+ }
33
+ }
34
+ if (!workspaceRoots.length) {
35
+ workspaceRoots.push(path.join(context.root, "..", ".."));
36
+ }
37
+ }
38
+ // Also include any co-located framework monorepo (any workspace with packages/)
39
+ try {
40
+ const monorepo = await findSwissLibMonorepo(context.root);
41
+ if (monorepo && !workspaceRoots.includes(monorepo)) {
42
+ workspaceRoots.unshift(monorepo);
43
+ }
44
+ if (monorepo) {
45
+ const packagesDir = path.join(monorepo, "packages");
46
+ if (await context.fileExists(packagesDir) && !workspaceRoots.includes(packagesDir)) {
47
+ workspaceRoots.unshift(packagesDir);
48
+ }
49
+ }
50
+ }
51
+ catch {
52
+ // monorepo not found — continue without it
53
+ }
54
+ const registry = getPackageRegistry();
55
+ const primaryRoot = workspaceRoots[0] ?? context.root;
56
+ const additionalRoots = workspaceRoots.slice(1);
57
+ if (!registry.getPackageCount() && primaryRoot) {
58
+ try {
59
+ await registry.scanWorkspace(primaryRoot, additionalRoots);
60
+ }
61
+ catch (error) {
62
+ console.error(`[SWITE] Error scanning package registry:`, error.message);
63
+ }
64
+ }
65
+ let packageInfo = registry.findPackage(pkgName);
66
+ if (packageInfo) {
67
+ return packageInfo.path;
68
+ }
69
+ // Rescan in case the package was added after the initial scan
70
+ await registry.rescan();
71
+ packageInfo = registry.findPackage(pkgName);
72
+ if (packageInfo) {
73
+ return packageInfo.path;
74
+ }
75
+ console.log(`[SWITE] Package ${pkgName} not found in workspace (scanned ${registry.getPackageCount()} packages)`);
76
+ return null;
77
+ }
@@ -0,0 +1,97 @@
1
+ <!--
2
+ Copyright (c) 2024 Themba Mzumara
3
+ SWITE - SWISS Development Server
4
+ Licensed under the MIT License.
5
+ -->
6
+
7
+ # Build Pipeline
8
+
9
+ `SwiteBuilder` (`src/build-engine/builder.ts`) handles production builds. It is invoked by `swite build` with a fixed entry point of `src/index.ui` and output directory of `dist/`.
10
+
11
+ ---
12
+
13
+ ## Overview
14
+
15
+ The build runs three sequential phases inside a try/finally block. The temporary directory `.swite-build/` is always removed when the build finishes, whether it succeeded or failed.
16
+
17
+ ```
18
+ swite build
19
+ └── SwiteBuilder.build()
20
+ ├── 1. cleanOutputDir() — rm -rf dist/, mkdir dist/
21
+ ├── 2. compileSwissFiles() — @swissjs/compiler → .tsx in .swite-build/
22
+ ├── 3. bundle() — esbuild bundles from .swite-build/
23
+ └── 4. copyPublicAssets() — cp -r public/ dist/
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Phase 1: Compile Swiss files
29
+
30
+ `compileSwissFiles` traverses `src/` and every workspace dependency's `src/` directory.
31
+
32
+ For each `.ui` or `.uix` file:
33
+
34
+ 1. `UiCompiler.compileAsync(source, filePath)` — transforms SwissJS component syntax to TypeScript/JSX.
35
+ 2. Relative `.ui`/`.uix` imports in the compiled output are rewritten to `.tsx` (esbuild needs `.tsx` for JSX, not `.ui`).
36
+ 3. If the compiled output ends with `export { Foo }`, a `export default Foo` line is appended so default imports resolve at bundle time.
37
+ 4. Written to `.swite-build/<relative-path>.tsx`.
38
+
39
+ For each `.ts` file, `.ui`/`.uix` imports in `from` clauses are rewritten to `.tsx`, then the file is copied as-is.
40
+
41
+ CSS files are copied verbatim so that any CSS import stubs in the bundle phase can resolve.
42
+
43
+ ---
44
+
45
+ ## Phase 2: Bundle with esbuild
46
+
47
+ esbuild is invoked with:
48
+
49
+ ```typescript
50
+ {
51
+ bundle: true,
52
+ format: 'esm',
53
+ target: 'es2020',
54
+ minify: true,
55
+ sourcemap: false,
56
+ splitting: true, // ESM code splitting
57
+ metafile: true,
58
+ platform: 'node',
59
+ }
60
+ ```
61
+
62
+ Three plugins are registered (see [CLI / build](../cli/build.md) for descriptions). Node built-in modules are marked external. The `absWorkingDir` is set to the workspace root (or app root if no workspace is detected) so esbuild resolves node_modules correctly.
63
+
64
+ ---
65
+
66
+ ## Phase 3: Copy public assets
67
+
68
+ `public/` is copied recursively to `dist/`. If `public/` does not exist, this phase is skipped silently.
69
+
70
+ ---
71
+
72
+ ## Workspace dependency discovery
73
+
74
+ `discoverWorkspaceDependencies()` reads the app's `package.json` and collects all `workspace:*` entries from `dependencies`, `devDependencies`, and `peerDependencies`. It also scans source files for `@scope/pkg` import patterns to catch transitive workspace imports not listed in `package.json`.
75
+
76
+ For each candidate package name, it searches these directories under the workspace root:
77
+
78
+ ```
79
+ lib/<pkgName>
80
+ packages/<pkgName>
81
+ packages/runtime/<pkgName>
82
+ packages/plugins/<pkgName>
83
+ packages/domain/<pkgName>
84
+ ```
85
+
86
+ A match requires the directory to have a `package.json` whose `name` field matches the expected package name, and a `src/` subdirectory.
87
+
88
+ ---
89
+
90
+ ## Workspace resolver plugin
91
+
92
+ The `workspace-resolver` esbuild plugin handles `@scope/pkg` imports during bundling. It maps each import to the compiled `.tsx` files in `.swite-build/` by:
93
+
94
+ 1. Finding the matching workspace dependency from the discovery step.
95
+ 2. Reading the package's `package.json` exports field to resolve the subpath.
96
+ 3. Converting the export path from `./src/Foo.uix` to `.swite-build/<depRelPath>/src/Foo.tsx`.
97
+ 4. Falling back to `src/index.js` if exports resolution fails.