@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,772 @@
1
+ /*
2
+ * Copyright (c) 2024 Themba Mzumara
3
+ * SWITE - SWISS Production Builder
4
+ * Licensed under the MIT License.
5
+ */
6
+ import { build as esbuild } from "esbuild";
7
+ import { UiCompiler } from "@kibologic/compiler";
8
+ import { promises as fs } from "node:fs";
9
+ import path from "node:path";
10
+ import chalk from "chalk";
11
+ import { ModuleResolver } from "./resolver.js";
12
+ export class SwiteBuilder {
13
+ constructor(config) {
14
+ this.compiler = new UiCompiler();
15
+ this.config = {
16
+ root: config.root,
17
+ entry: config.entry,
18
+ outDir: config.outDir,
19
+ publicDir: config.publicDir || "public",
20
+ minify: config.minify ?? true,
21
+ sourcemap: config.sourcemap ?? false,
22
+ format: config.format || "esm",
23
+ target: config.target || "es2020",
24
+ external: config.external || [],
25
+ };
26
+ this.resolver = new ModuleResolver(config.root);
27
+ }
28
+ async build() {
29
+ const startTime = Date.now();
30
+ console.log(chalk.cyan("\n⚡ SWITE - Production Build\n"));
31
+ try {
32
+ // Step 1: Clean output directory
33
+ await this.cleanOutputDir();
34
+ // Step 2: Compile Swiss files to temp directory
35
+ const tempDir = path.join(this.config.root, ".swite-build");
36
+ await this.compileSwissFiles(tempDir);
37
+ // Step 3: Bundle with esbuild
38
+ await this.bundle(tempDir);
39
+ // Step 4: Copy public assets
40
+ await this.copyPublicAssets();
41
+ // Step 5: Clean up temp directory
42
+ await fs.rm(tempDir, { recursive: true, force: true });
43
+ const duration = Date.now() - startTime;
44
+ console.log(chalk.green(`\n✅ Build completed in ${duration}ms\n`));
45
+ }
46
+ catch (error) {
47
+ console.error(chalk.red("\n❌ Build failed:"), error);
48
+ throw error;
49
+ }
50
+ }
51
+ async cleanOutputDir() {
52
+ console.log(chalk.blue("🧹 Cleaning output directory..."));
53
+ await fs.rm(this.config.outDir, { recursive: true, force: true });
54
+ await fs.mkdir(this.config.outDir, { recursive: true });
55
+ }
56
+ async compileSwissFiles(tempDir) {
57
+ console.log(chalk.blue("🔨 Compiling Swiss files..."));
58
+ await fs.mkdir(tempDir, { recursive: true });
59
+ const workspaceRoot = await this.findWorkspaceRoot(this.config.root);
60
+ const appRelativeToWorkspace = workspaceRoot
61
+ ? path.relative(workspaceRoot, this.config.root)
62
+ : "";
63
+ // Step 1: Compile app's own files
64
+ const srcDir = path.join(this.config.root, "src");
65
+ const appTempDir = appRelativeToWorkspace
66
+ ? path.join(tempDir, appRelativeToWorkspace, "src")
67
+ : path.join(tempDir, "src");
68
+ await this.compileDirectory(srcDir, appTempDir, "app");
69
+ // Step 2: Discover and compile workspace dependencies
70
+ const workspaceDeps = await this.discoverWorkspaceDependencies();
71
+ for (const dep of workspaceDeps) {
72
+ console.log(chalk.blue(`📦 Compiling dependency: ${dep.name}`));
73
+ // Preserve workspace structure: libraries/skltn/src or packages/cart/src or modules/cart/src
74
+ const depRelativeToWorkspace = workspaceRoot
75
+ ? path.relative(workspaceRoot, dep.pkgDir)
76
+ : "";
77
+ const depTempDir = depRelativeToWorkspace
78
+ ? path.join(tempDir, depRelativeToWorkspace, "src")
79
+ : path.join(tempDir, "src");
80
+ await this.compileDirectory(dep.srcDir, depTempDir, dep.name);
81
+ }
82
+ }
83
+ async compileDirectory(srcDir, tempDir, label) {
84
+ // Find all .ui and .uix files
85
+ const files = await this.findSwissFiles(srcDir);
86
+ for (const file of files) {
87
+ const relativePath = path.relative(srcDir, file);
88
+ const outputPath = path.join(tempDir, relativePath.replace(/\.(ui|uix)$/, ".tsx"));
89
+ // Ensure output directory exists
90
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
91
+ // Compile file
92
+ const source = await fs.readFile(file, "utf-8");
93
+ let compiled = await this.compiler.compileAsync(source, file);
94
+ // Rewrite .ui/.uix imports to .tsx in compiled output (esbuild needs .tsx for JSX)
95
+ compiled = compiled.replace(/from\s+['"]([^'"]*\.)(ui|uix)['"]/g, "from '$1tsx'");
96
+ compiled = compiled.replace(/import\s+['"]([^'"]*\.)(ui|uix)['"]/g, "import '$1tsx'");
97
+ // Add export default for the named export so default imports resolve at bundle time
98
+ const namedExportMatch = compiled.match(/export\s*\{\s*(\w+)\s*\}\s*;?\s*$/);
99
+ if (namedExportMatch?.[1]) {
100
+ compiled += `\nexport default ${namedExportMatch[1]};\n`;
101
+ }
102
+ await fs.writeFile(outputPath, compiled, "utf-8");
103
+ console.log(chalk.gray(` ✓ [${label}] ${relativePath}`));
104
+ }
105
+ // Copy .ts files and rewrite .ui/.uix imports to .tsx
106
+ const tsFiles = await this.findFiles(srcDir, /\.ts$/);
107
+ for (const file of tsFiles) {
108
+ const relativePath = path.relative(srcDir, file);
109
+ const outputPath = path.join(tempDir, relativePath);
110
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
111
+ // Read source and rewrite imports
112
+ const source = await fs.readFile(file, "utf-8");
113
+ // Replace .ui and .uix imports with .tsx (compiled Swiss files are emitted as .tsx)
114
+ const rewritten = source.replace(/from\s+['"](\.\/[^'"]*\.)(ui|uix)['"]/g, "from '$1tsx'");
115
+ await fs.writeFile(outputPath, rewritten, "utf-8");
116
+ }
117
+ // Copy .css and other static assets so imports resolve
118
+ const cssFiles = await this.findFiles(srcDir, /\.css$/);
119
+ for (const file of cssFiles) {
120
+ const relativePath = path.relative(srcDir, file);
121
+ const outputPath = path.join(tempDir, relativePath);
122
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
123
+ await fs.copyFile(file, outputPath);
124
+ }
125
+ }
126
+ async discoverWorkspaceDependencies() {
127
+ const deps = [];
128
+ try {
129
+ const packageJsonPath = path.join(this.config.root, "package.json");
130
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
131
+ const allDeps = {
132
+ ...packageJson.dependencies,
133
+ ...packageJson.peerDependencies,
134
+ ...packageJson.devDependencies,
135
+ };
136
+ const workspaceRoot = await this.findWorkspaceRoot(this.config.root);
137
+ if (!workspaceRoot) {
138
+ return deps;
139
+ }
140
+ // Also discover packages imported in source files (for transitive dependencies)
141
+ const discoveredPackages = new Set();
142
+ const srcDir = path.join(this.config.root, "src");
143
+ // Scan source files for workspace package imports
144
+ const scanForImports = async (dir) => {
145
+ try {
146
+ const entries = await fs.readdir(dir, { withFileTypes: true });
147
+ for (const entry of entries) {
148
+ const fullPath = path.join(dir, entry.name);
149
+ if (entry.isDirectory()) {
150
+ await scanForImports(fullPath);
151
+ }
152
+ else if (entry.name.endsWith(".ts") ||
153
+ entry.name.endsWith(".ui") ||
154
+ entry.name.endsWith(".uix")) {
155
+ const content = await fs.readFile(fullPath, "utf-8");
156
+ // Match imports like: import ... from '@swiss-enterprise/cart/...' or '@alpine/skltn/...'
157
+ const importMatches = content.matchAll(/from\s+['"](@[\w-]+\/[\w-]+)/g);
158
+ for (const match of importMatches) {
159
+ const pkgName = match[1];
160
+ if (pkgName && !discoveredPackages.has(pkgName)) {
161
+ discoveredPackages.add(pkgName);
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ catch {
168
+ // Ignore errors
169
+ }
170
+ };
171
+ if (await this.fileExists(srcDir)) {
172
+ await scanForImports(srcDir);
173
+ }
174
+ for (const [depName, depVersion] of Object.entries(allDeps)) {
175
+ // Only process workspace dependencies
176
+ if (typeof depVersion === "string" &&
177
+ depVersion.startsWith("workspace:")) {
178
+ // Extract package name (handle scoped packages)
179
+ const pkgName = depName.startsWith("@")
180
+ ? depName.split("/")[1]
181
+ : depName;
182
+ // Try common package locations
183
+ const possibleDirs = [
184
+ path.join(workspaceRoot, "lib", pkgName),
185
+ path.join(workspaceRoot, "packages", pkgName),
186
+ path.join(workspaceRoot, "packages", "runtime", pkgName),
187
+ path.join(workspaceRoot, "packages", "plugins", pkgName),
188
+ path.join(workspaceRoot, "packages", "domain", pkgName),
189
+ ];
190
+ for (const pkgDir of possibleDirs) {
191
+ const pkgJsonPath = path.join(pkgDir, "package.json");
192
+ if (await this.fileExists(pkgJsonPath)) {
193
+ const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"));
194
+ // Verify it's the right package
195
+ if (pkgJson.name === depName) {
196
+ const srcDir = path.join(pkgDir, "src");
197
+ if (await this.fileExists(srcDir)) {
198
+ deps.push({
199
+ name: depName,
200
+ srcDir,
201
+ pkgDir,
202
+ });
203
+ console.log(chalk.gray(` 📦 Found workspace dependency: ${depName}`));
204
+ break;
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+ // Also process discovered packages from source files
212
+ for (const pkgName of discoveredPackages) {
213
+ // Skip if already in deps
214
+ if (deps.some((d) => d.name === pkgName)) {
215
+ continue;
216
+ }
217
+ // Extract package name (handle scoped packages)
218
+ const pkgNameOnly = pkgName.startsWith("@")
219
+ ? pkgName.split("/")[1]
220
+ : pkgName;
221
+ // Try common package locations
222
+ const possibleDirs = [
223
+ path.join(workspaceRoot, "lib", pkgNameOnly),
224
+ path.join(workspaceRoot, "packages", pkgNameOnly),
225
+ path.join(workspaceRoot, "packages", "runtime", pkgNameOnly),
226
+ path.join(workspaceRoot, "packages", "plugins", pkgNameOnly),
227
+ path.join(workspaceRoot, "packages", "domain", pkgNameOnly),
228
+ ];
229
+ for (const pkgDir of possibleDirs) {
230
+ const pkgJsonPath = path.join(pkgDir, "package.json");
231
+ if (await this.fileExists(pkgJsonPath)) {
232
+ const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"));
233
+ // Verify it's the right package
234
+ if (pkgJson.name === pkgName) {
235
+ const srcDir = path.join(pkgDir, "src");
236
+ if (await this.fileExists(srcDir)) {
237
+ deps.push({
238
+ name: pkgName,
239
+ srcDir,
240
+ pkgDir,
241
+ });
242
+ console.log(chalk.gray(` 📦 Discovered transitive dependency: ${pkgName}`));
243
+ break;
244
+ }
245
+ }
246
+ }
247
+ }
248
+ }
249
+ }
250
+ catch (error) {
251
+ console.warn(chalk.yellow("⚠️ Could not discover dependencies:"), error);
252
+ }
253
+ return deps;
254
+ }
255
+ async bundle(tempDir) {
256
+ console.log(chalk.blue("📦 Bundling with esbuild..."));
257
+ const workspaceRoot = await this.findWorkspaceRoot(this.config.root);
258
+ const appRelativeToWorkspace = workspaceRoot
259
+ ? path.relative(workspaceRoot, this.config.root)
260
+ : "";
261
+ // Determine entry point - look for compiled version in temp
262
+ // const entryBasename = path.basename(this.config.entry, path.extname(this.config.entry)); // Unused
263
+ const entryExt = path.extname(this.config.entry);
264
+ let entryPoint;
265
+ // Entry point is always relative to src directory
266
+ const entryRelativeToSrc = path.relative(path.join(this.config.root, "src"), this.config.entry);
267
+ if (entryExt === ".ui" || entryExt === ".uix") {
268
+ // Entry was a Swiss file, use compiled .tsx version
269
+ const entryTsx = entryRelativeToSrc.replace(/\.(ui|uix)$/, ".tsx");
270
+ entryPoint = appRelativeToWorkspace
271
+ ? path.join(tempDir, appRelativeToWorkspace, "src", entryTsx)
272
+ : path.join(tempDir, "src", entryTsx);
273
+ }
274
+ else {
275
+ // Entry is .ts or .js, use from temp
276
+ entryPoint = appRelativeToWorkspace
277
+ ? path.join(tempDir, appRelativeToWorkspace, "src", entryRelativeToSrc)
278
+ : path.join(tempDir, "src", entryRelativeToSrc);
279
+ }
280
+ // Verify entry point exists
281
+ if (!(await this.fileExists(entryPoint))) {
282
+ throw new Error(`Entry point not found: ${entryPoint} (from ${this.config.entry})`);
283
+ }
284
+ // Configure esbuild to resolve workspace packages from temp directory
285
+ const absWorkingDir = workspaceRoot || this.config.root;
286
+ // const aliases = workspaceRoot ? await this.createAliases(workspaceRoot, tempDir) : {}; // Unused
287
+ // Mark Node.js built-ins and build-time-only deps as external
288
+ const nodeBuiltins = [
289
+ "@kibologic/swite",
290
+ "@kibologic/core",
291
+ "@kibologic/*",
292
+ "@kibologic/*",
293
+ "fs",
294
+ "path",
295
+ "os",
296
+ "crypto",
297
+ "http",
298
+ "https",
299
+ "net",
300
+ "stream",
301
+ "util",
302
+ "events",
303
+ "child_process",
304
+ "url",
305
+ "querystring",
306
+ "zlib",
307
+ "assert",
308
+ "constants",
309
+ "tty",
310
+ "node:fs",
311
+ "node:path",
312
+ "node:os",
313
+ "node:crypto",
314
+ "node:http",
315
+ "node:https",
316
+ "node:net",
317
+ "node:stream",
318
+ "node:util",
319
+ "node:events",
320
+ "node:child_process",
321
+ "node:url",
322
+ "node:querystring",
323
+ "node:zlib",
324
+ "node:assert",
325
+ "node:constants",
326
+ "node:tty",
327
+ "fs/promises",
328
+ "node:fs/promises",
329
+ ];
330
+ // Resolve relative .js imports to .tsx when UiCompiler rewrites .ui→.js but emits .tsx files
331
+ const jsTsxFallbackPlugin = {
332
+ name: "js-tsx-fallback",
333
+ setup(build) {
334
+ build.onResolve({ filter: /\.js$/ }, async (args) => {
335
+ if (!args.path.startsWith("."))
336
+ return undefined;
337
+ const jsPath = path.resolve(path.dirname(args.resolveDir), args.path);
338
+ const tsxPath = jsPath.replace(/\.js$/, ".tsx");
339
+ try {
340
+ await fs.access(tsxPath);
341
+ return { path: tsxPath };
342
+ }
343
+ catch {
344
+ return undefined;
345
+ }
346
+ });
347
+ },
348
+ };
349
+ // Stub .css imports so they resolve (build output may copy CSS separately)
350
+ const cssStubPlugin = {
351
+ name: "css-stub",
352
+ setup(build) {
353
+ build.onLoad({ filter: /\.css$/ }, () => ({
354
+ contents: "export {};",
355
+ loader: "js",
356
+ }));
357
+ },
358
+ };
359
+ // Create plugin to resolve workspace packages to compiled files
360
+ const workspaceDeps = await this.discoverWorkspaceDependencies();
361
+ const fileExists = this.fileExists.bind(this);
362
+ const findWorkspaceRoot = this.findWorkspaceRoot.bind(this);
363
+ const appRoot = this.config.root;
364
+ const wsRoot = await findWorkspaceRoot(appRoot);
365
+ const tempDirForPlugin = tempDir; // Capture tempDir for plugin
366
+ // Helper function to safely join paths - filters out invalid values
367
+ const safePathJoin = (...parts) => {
368
+ const validParts = parts.filter((p) => p != null && typeof p === "string" && p.length > 0);
369
+ if (validParts.length === 0)
370
+ return null;
371
+ try {
372
+ return path.join(...validParts);
373
+ }
374
+ catch {
375
+ return null;
376
+ }
377
+ };
378
+ const workspaceResolverPlugin = {
379
+ name: "workspace-resolver",
380
+ setup(build) {
381
+ // Resolve workspace packages
382
+ build.onResolve({ filter: /^@/ }, async (args) => {
383
+ // Early return if tempDirForPlugin is invalid
384
+ if (!tempDirForPlugin ||
385
+ typeof tempDirForPlugin !== "string" ||
386
+ tempDirForPlugin.length === 0) {
387
+ return undefined;
388
+ }
389
+ // Check if this is a workspace package (from dependencies or try to find it)
390
+ let matchingDep = workspaceDeps.find((d) => args.path.startsWith(d.name));
391
+ // If not in dependencies, try to find it in workspace
392
+ if (!matchingDep && wsRoot) {
393
+ const pkgName = args.path.split("/")[0];
394
+ const pkgNameOnly = pkgName.startsWith("@")
395
+ ? pkgName.split("/")[1]
396
+ : pkgName;
397
+ if (!wsRoot ||
398
+ typeof wsRoot !== "string" ||
399
+ !pkgNameOnly ||
400
+ typeof pkgNameOnly !== "string") {
401
+ return undefined;
402
+ }
403
+ const possibleDirs = [
404
+ safePathJoin(wsRoot, "lib", pkgNameOnly),
405
+ safePathJoin(wsRoot, "packages", pkgNameOnly),
406
+ ].filter((p) => p !== null);
407
+ for (const pkgDir of possibleDirs) {
408
+ const pkgJsonPath = safePathJoin(pkgDir, "package.json");
409
+ if (pkgJsonPath && (await fileExists(pkgJsonPath))) {
410
+ const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"));
411
+ if (pkgJson.name === pkgName) {
412
+ const srcDir = safePathJoin(pkgDir, "src");
413
+ if (srcDir && (await fileExists(srcDir))) {
414
+ matchingDep = {
415
+ name: pkgName,
416
+ srcDir: srcDir,
417
+ pkgDir: pkgDir,
418
+ };
419
+ break;
420
+ }
421
+ }
422
+ }
423
+ }
424
+ }
425
+ if (matchingDep && wsRoot && matchingDep.pkgDir) {
426
+ try {
427
+ // Resolve to compiled file in temp directory
428
+ let depRelativeToWorkspace = "";
429
+ try {
430
+ if (wsRoot &&
431
+ matchingDep.pkgDir &&
432
+ typeof wsRoot === "string" &&
433
+ typeof matchingDep.pkgDir === "string") {
434
+ const rel = path.relative(wsRoot, matchingDep.pkgDir);
435
+ if (rel &&
436
+ typeof rel === "string" &&
437
+ rel !== "." &&
438
+ rel.length > 0) {
439
+ depRelativeToWorkspace = rel;
440
+ }
441
+ }
442
+ }
443
+ catch (err) {
444
+ console.warn(`[SWITE] Error calculating relative path for ${matchingDep.name}:`, err);
445
+ depRelativeToWorkspace = "";
446
+ }
447
+ // Extract subpath (e.g., "@alpine/skltn/shell" -> "shell")
448
+ const subPath = args.path.replace(matchingDep.name + "/", "");
449
+ // Log for debugging
450
+ console.log(`[SWITE] Resolving ${args.path} -> subPath: ${subPath} from ${matchingDep.name} (${depRelativeToWorkspace || "root"})`);
451
+ // Try to resolve the subpath
452
+ let resolvedPath = null;
453
+ if (subPath) {
454
+ // Check package.json exports
455
+ if (!matchingDep.pkgDir ||
456
+ typeof matchingDep.pkgDir !== "string") {
457
+ return undefined;
458
+ }
459
+ const pkgJsonPath = safePathJoin(matchingDep.pkgDir, "package.json");
460
+ if (!pkgJsonPath) {
461
+ return undefined;
462
+ }
463
+ if (await fileExists(pkgJsonPath)) {
464
+ try {
465
+ const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"));
466
+ if (pkgJson.exports) {
467
+ const exportKey = `./${subPath}`;
468
+ let exportValue = pkgJson.exports[exportKey];
469
+ // Try directory-based matching
470
+ if (!exportValue && subPath.includes("/")) {
471
+ const dirPath = subPath.split("/")[0];
472
+ exportValue = pkgJson.exports[`./${dirPath}`];
473
+ }
474
+ if (exportValue) {
475
+ const exportPath = typeof exportValue === "string"
476
+ ? exportValue
477
+ : exportValue.import || exportValue.default;
478
+ if (exportPath && typeof exportPath === "string") {
479
+ // Convert export path to compiled path
480
+ const srcRelative = exportPath.replace(/^\.\/src\//, "");
481
+ const compiledPath = srcRelative.replace(/\.(ui|uix)$/, ".tsx");
482
+ // Validate compiledPath is a valid string
483
+ if (!compiledPath ||
484
+ typeof compiledPath !== "string" ||
485
+ compiledPath.length === 0) {
486
+ return undefined;
487
+ }
488
+ const parts = [];
489
+ // Validate tempDirForPlugin is a valid string
490
+ if (tempDirForPlugin &&
491
+ typeof tempDirForPlugin === "string" &&
492
+ tempDirForPlugin.length > 0) {
493
+ parts.push(tempDirForPlugin);
494
+ }
495
+ else {
496
+ // Skip this resolution if tempDir is invalid
497
+ return undefined;
498
+ }
499
+ if (depRelativeToWorkspace &&
500
+ typeof depRelativeToWorkspace === "string" &&
501
+ depRelativeToWorkspace.length > 0) {
502
+ parts.push(depRelativeToWorkspace);
503
+ }
504
+ // Validate compiledPath before adding
505
+ if (compiledPath &&
506
+ typeof compiledPath === "string" &&
507
+ compiledPath.length > 0) {
508
+ const joined = safePathJoin(tempDirForPlugin, depRelativeToWorkspace || undefined, "src", compiledPath);
509
+ if (joined) {
510
+ resolvedPath = joined;
511
+ }
512
+ }
513
+ }
514
+ }
515
+ }
516
+ }
517
+ catch (err) {
518
+ // Fallback to index
519
+ console.warn(`[SWITE] Error reading exports for ${matchingDep.name}:`, err);
520
+ }
521
+ }
522
+ }
523
+ // Fallback: try index.js
524
+ if (!resolvedPath || !(await fileExists(resolvedPath))) {
525
+ const fallbackParts = [];
526
+ // Validate tempDirForPlugin is a valid string
527
+ if (tempDirForPlugin &&
528
+ typeof tempDirForPlugin === "string" &&
529
+ tempDirForPlugin.length > 0) {
530
+ fallbackParts.push(tempDirForPlugin);
531
+ }
532
+ else {
533
+ // Cannot build without valid tempDir
534
+ return undefined;
535
+ }
536
+ if (depRelativeToWorkspace &&
537
+ typeof depRelativeToWorkspace === "string" &&
538
+ depRelativeToWorkspace.length > 0) {
539
+ fallbackParts.push(depRelativeToWorkspace);
540
+ }
541
+ // Use safe path join helper
542
+ const joined = safePathJoin(tempDirForPlugin, depRelativeToWorkspace || undefined, "src", "index.js");
543
+ if (joined) {
544
+ resolvedPath = joined;
545
+ }
546
+ }
547
+ // Final check - ensure path is valid string and exists
548
+ if (resolvedPath &&
549
+ typeof resolvedPath === "string" &&
550
+ resolvedPath.length > 0) {
551
+ try {
552
+ if (await fileExists(resolvedPath)) {
553
+ return { path: resolvedPath };
554
+ }
555
+ }
556
+ catch {
557
+ // Ignore file check errors
558
+ }
559
+ }
560
+ }
561
+ catch (err) {
562
+ console.warn(`[SWITE] Error resolving ${args.path}:`, err);
563
+ }
564
+ }
565
+ // Let esbuild handle it normally (return undefined, not null)
566
+ return undefined;
567
+ });
568
+ // Also resolve relative imports in compiled workspace packages
569
+ build.onResolve({ filter: /^\.\.?\/.*\.(js|ts|ui|uix)$/ }, async (args) => {
570
+ // Only handle if it's in a workspace package directory
571
+ if (args.importer && args.importer.includes(".swite-build")) {
572
+ // Check if the importer is in a workspace package
573
+ const importerPath = args.importer;
574
+ const match = importerPath.match(/\.swite-build[/\\]([^/\\]+[/\\][^/\\]+)[/\\]src/);
575
+ if (match) {
576
+ // Try to resolve relative to the importer
577
+ const importerDir = path.dirname(importerPath);
578
+ const resolved = safePathJoin(importerDir, args.path);
579
+ if (resolved && (await fileExists(resolved))) {
580
+ return { path: resolved };
581
+ }
582
+ // Try with .tsx extension if original had .ui/.uix
583
+ const pathWithoutExt = args.path.replace(/\.(ui|uix)$/, "");
584
+ const resolvedTsx = safePathJoin(importerDir, pathWithoutExt + ".tsx");
585
+ if (resolvedTsx && (await fileExists(resolvedTsx))) {
586
+ return { path: resolvedTsx };
587
+ }
588
+ }
589
+ }
590
+ return undefined;
591
+ });
592
+ },
593
+ };
594
+ const buildOptions = {
595
+ entryPoints: [entryPoint],
596
+ bundle: true,
597
+ outdir: this.config.outDir,
598
+ format: this.config.format,
599
+ target: this.config.target,
600
+ minify: this.config.minify,
601
+ sourcemap: this.config.sourcemap,
602
+ external: [...this.config.external, ...nodeBuiltins],
603
+ platform: "node", // Keep as node for now - browser apps will need different strategy
604
+ splitting: this.config.format === "esm",
605
+ metafile: true,
606
+ logLevel: "info",
607
+ absWorkingDir, // Help esbuild resolve modules from workspace root
608
+ plugins: [jsTsxFallbackPlugin, cssStubPlugin, workspaceResolverPlugin],
609
+ };
610
+ // Add aliases via plugins if esbuild version supports it
611
+ // For now, we'll rely on the compiled files being in the right structure
612
+ const result = await esbuild(buildOptions);
613
+ // Log bundle stats (metafile paths can be relative to absWorkingDir)
614
+ if (result.metafile) {
615
+ const outputs = Object.keys(result.metafile.outputs);
616
+ console.log(chalk.green(`\n Generated ${outputs.length} file(s):`));
617
+ for (const output of outputs) {
618
+ const resolvedPath = path.isAbsolute(output)
619
+ ? output
620
+ : path.join(absWorkingDir, output);
621
+ const stats = await fs.stat(resolvedPath);
622
+ const size = this.formatBytes(stats.size);
623
+ console.log(chalk.gray(` ${path.basename(output)}: ${size}`));
624
+ }
625
+ }
626
+ }
627
+ async copyPublicAssets() {
628
+ const publicPath = path.join(this.config.root, this.config.publicDir);
629
+ try {
630
+ await fs.access(publicPath);
631
+ }
632
+ catch {
633
+ // Public directory doesn't exist, skip
634
+ return;
635
+ }
636
+ console.log(chalk.blue("📁 Copying public assets..."));
637
+ await this.copyDir(publicPath, this.config.outDir);
638
+ }
639
+ async findSwissFiles(dir) {
640
+ const files = [];
641
+ try {
642
+ const entries = await fs.readdir(dir, { withFileTypes: true });
643
+ for (const entry of entries) {
644
+ if (entry.name === "node_modules")
645
+ continue;
646
+ const fullPath = path.join(dir, entry.name);
647
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
648
+ files.push(...(await this.findSwissFiles(fullPath)));
649
+ }
650
+ else if (entry.name.endsWith(".ui") || entry.name.endsWith(".uix")) {
651
+ files.push(fullPath);
652
+ }
653
+ }
654
+ }
655
+ catch {
656
+ // Directory doesn't exist or can't be read
657
+ }
658
+ return files;
659
+ }
660
+ async findFiles(dir, pattern) {
661
+ const files = [];
662
+ try {
663
+ const entries = await fs.readdir(dir, { withFileTypes: true });
664
+ for (const entry of entries) {
665
+ if (entry.name === "node_modules")
666
+ continue;
667
+ const fullPath = path.join(dir, entry.name);
668
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
669
+ files.push(...(await this.findFiles(fullPath, pattern)));
670
+ }
671
+ else if (pattern.test(entry.name)) {
672
+ files.push(fullPath);
673
+ }
674
+ }
675
+ }
676
+ catch {
677
+ // Directory doesn't exist or can't be read
678
+ }
679
+ return files;
680
+ }
681
+ async copyDir(src, dest) {
682
+ await fs.mkdir(dest, { recursive: true });
683
+ const entries = await fs.readdir(src, { withFileTypes: true });
684
+ for (const entry of entries) {
685
+ const srcPath = path.join(src, entry.name);
686
+ const destPath = path.join(dest, entry.name);
687
+ if (entry.isDirectory()) {
688
+ await this.copyDir(srcPath, destPath);
689
+ }
690
+ else {
691
+ await fs.copyFile(srcPath, destPath);
692
+ }
693
+ }
694
+ }
695
+ formatBytes(bytes) {
696
+ if (bytes === 0)
697
+ return "0 B";
698
+ const k = 1024;
699
+ const sizes = ["B", "KB", "MB", "GB"];
700
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
701
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
702
+ }
703
+ async findWorkspaceRoot(startDir) {
704
+ let current = startDir;
705
+ for (let i = 0; i < 5; i++) {
706
+ const workspaceFile = path.join(current, "pnpm-workspace.yaml");
707
+ const packageJson = path.join(current, "package.json");
708
+ try {
709
+ if (await this.fileExists(workspaceFile)) {
710
+ return current;
711
+ }
712
+ if (await this.fileExists(packageJson)) {
713
+ const pkg = JSON.parse(await fs.readFile(packageJson, "utf-8"));
714
+ if (pkg.workspaces) {
715
+ return current;
716
+ }
717
+ }
718
+ }
719
+ catch {
720
+ // Continue searching
721
+ }
722
+ const parent = path.dirname(current);
723
+ if (parent === current)
724
+ break;
725
+ current = parent;
726
+ }
727
+ return null;
728
+ }
729
+ async fileExists(filePath) {
730
+ try {
731
+ await fs.access(filePath);
732
+ return true;
733
+ }
734
+ catch {
735
+ return false;
736
+ }
737
+ }
738
+ async createAliases(workspaceRoot, tempDir) {
739
+ const aliases = {};
740
+ const deps = await this.discoverWorkspaceDependencies();
741
+ for (const dep of deps) {
742
+ const depRelativeToWorkspace = path.relative(workspaceRoot, dep.pkgDir);
743
+ const aliasPath = path.join(tempDir, depRelativeToWorkspace, "src");
744
+ aliases[dep.name] = aliasPath;
745
+ // Also add subpath exports
746
+ const pkgJsonPath = path.join(dep.pkgDir, "package.json");
747
+ try {
748
+ const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"));
749
+ if (pkgJson.exports) {
750
+ for (const [exportKey, exportValue] of Object.entries(pkgJson.exports)) {
751
+ if (exportKey !== "." && typeof exportValue === "string") {
752
+ const fullAlias = `${dep.name}${exportKey}`;
753
+ const exportPath = exportValue.replace(/^\.\/src\//, "");
754
+ aliases[fullAlias] = path.join(aliasPath, exportPath);
755
+ }
756
+ }
757
+ }
758
+ }
759
+ catch {
760
+ // Skip if can't read package.json
761
+ }
762
+ }
763
+ return aliases;
764
+ }
765
+ }
766
+ /**
767
+ * Convenience function to build a project
768
+ */
769
+ export async function build(config) {
770
+ const builder = new SwiteBuilder(config);
771
+ await builder.build();
772
+ }