@swissjs/swite 0.3.1 → 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 (49) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cli.js +0 -0
  3. package/dist/config/config.d.ts +11 -0
  4. package/dist/config/config.d.ts.map +1 -1
  5. package/dist/dev-engine/handlers/base-handler.d.ts +3 -1
  6. package/dist/dev-engine/handlers/base-handler.d.ts.map +1 -1
  7. package/dist/dev-engine/handlers/base-handler.js +1 -1
  8. package/dist/dev-engine/handlers/node-module-handler.d.ts.map +1 -1
  9. package/dist/dev-engine/handlers/node-module-handler.js +30 -41
  10. package/dist/dev-engine/middleware/middleware-setup.d.ts +1 -0
  11. package/dist/dev-engine/middleware/middleware-setup.d.ts.map +1 -1
  12. package/dist/dev-engine/server.d.ts.map +1 -1
  13. package/dist/dev-engine/server.js +4 -0
  14. package/dist/kernel/package-finder.d.ts +7 -7
  15. package/dist/kernel/package-finder.d.ts.map +1 -1
  16. package/dist/kernel/package-finder.js +56 -40
  17. package/dist/resolution/bare-import-resolver.d.ts.map +1 -1
  18. package/dist/resolution/bare-import-resolver.js +13 -4
  19. package/dist/resolution/path/file-path-resolver.d.ts +2 -1
  20. package/dist/resolution/path/file-path-resolver.d.ts.map +1 -1
  21. package/dist/resolution/path/file-path-resolver.js +36 -9
  22. package/docs/architecture/build-pipeline.md +97 -0
  23. package/docs/architecture/dev-server.md +87 -0
  24. package/docs/architecture/hmr.md +78 -0
  25. package/docs/architecture/import-rewriting.md +101 -0
  26. package/docs/architecture/index.md +16 -0
  27. package/docs/architecture/python-integration.md +93 -0
  28. package/docs/architecture/resolution.md +92 -0
  29. package/docs/cli/build.md +78 -0
  30. package/docs/cli/dev.md +90 -0
  31. package/docs/cli/index.md +15 -0
  32. package/docs/cli/start.md +45 -0
  33. package/docs/development/contributing.md +74 -0
  34. package/docs/development/index.md +12 -0
  35. package/docs/development/internals.md +101 -0
  36. package/docs/guide/configuration.md +89 -0
  37. package/docs/guide/index.md +13 -0
  38. package/docs/guide/project-structure.md +75 -0
  39. package/docs/guide/quickstart.md +113 -0
  40. package/docs/index.md +16 -0
  41. package/package.json +15 -24
  42. package/src/config/config.ts +11 -0
  43. package/src/dev-engine/handlers/base-handler.ts +4 -2
  44. package/src/dev-engine/handlers/node-module-handler.ts +51 -78
  45. package/src/dev-engine/middleware/middleware-setup.ts +1 -0
  46. package/src/dev-engine/server.ts +38 -33
  47. package/src/kernel/package-finder.ts +59 -43
  48. package/src/resolution/bare-import-resolver.ts +14 -4
  49. package/src/resolution/path/file-path-resolver.ts +44 -10
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Generalize resolution system and add internalScopes support
8
+
9
+ - Add `findSiblingRepository(startPath, repoName)` — replaces hardcoded swiss-lib discovery with a generic sibling repo finder. `findSwissLibMonorepo` preserved as a backward-compat wrapper.
10
+ - Add `findPackage` with local-first dev precedence — in development, resolves `@scoped/*` packages from local sibling source trees before falling back to `node_modules`.
11
+ - `internalScopes` from `swiss.config.ts` now flows through to all handlers at startup — prevents internal-scoped packages from being routed to jsDelivr CDN.
12
+ - `NodeModuleHandler` rewritten to use `findPackage` — replaces verbose walk-up/swiss-lib fallback chain with unified local-first resolver. Adds dist→src redirect for local sibling packages.
13
+ - CDN blacklist: packages matching `internalScopes` are blocked from jsDelivr fallback with a clear error log.
14
+ - Fix `cli.ts` builder import path (`./build-engine/builder.js`).
15
+ - Fix `url-resolver.ts` dynamic import path for `file-path-resolver`.
16
+ - Update peer deps: `@swissjs/core` → `0.1.8`, `@swissjs/compiler` → `0.1.5`.
17
+
3
18
  ## 0.2.31
4
19
 
5
20
  ### Patch Changes
package/dist/cli.js CHANGED
File without changes
@@ -20,6 +20,17 @@ export interface ServerConfig {
20
20
  export interface SwiteUserConfig {
21
21
  server?: ServerConfig;
22
22
  services?: ServicesConfig;
23
+ /**
24
+ * Package scopes that should be treated as "internal" or "private".
25
+ * These scopes prioritize local/monorepo resolution and are forbidden from CDN redirects.
26
+ * e.g. ["@kibologic", "@alpine"]
27
+ */
28
+ internalScopes?: string[];
29
+ /**
30
+ * Manual override for sibling repository lookup.
31
+ * Swite will search these directories for local package source code.
32
+ */
33
+ siblingRepositories?: string[];
23
34
  }
24
35
  /**
25
36
  * Define swite configuration with full TypeScript validation.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,SAAS,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,mBAAmB,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAErE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,SAAS,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,mBAAmB,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAErE"}
@@ -1,10 +1,12 @@
1
1
  import type { Response } from "express";
2
2
  import { ModuleResolver } from "../../resolution/resolver.js";
3
+ import type { SwiteUserConfig } from "../../config/config.js";
3
4
  export interface HandlerContext {
4
5
  resolver: ModuleResolver;
5
6
  root: string;
6
7
  workspaceRoot: string | null;
7
- env: Record<string, string>;
8
+ env?: Record<string, string>;
9
+ userConfig?: SwiteUserConfig;
8
10
  }
9
11
  /**
10
12
  * Set cache-busting headers for development
@@ -1 +1 @@
1
- {"version":3,"file":"base-handler.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/handlers/base-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAG9D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAMjD;AAED;;GAEG;AACH,qBAAa,WAAW;IACV,SAAS,CAAC,OAAO,EAAE,cAAc;gBAAvB,OAAO,EAAE,cAAc;cAE7B,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;cAI7C,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;cAS9C,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAmBrE"}
1
+ {"version":3,"file":"base-handler.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/handlers/base-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,UAAU,CAAC,EAAE,eAAe,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAMjD;AAED;;GAEG;AACH,qBAAa,WAAW;IACV,SAAS,CAAC,OAAO,EAAE,cAAc;gBAAvB,OAAO,EAAE,cAAc;cAE7B,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;cAI7C,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;cAS9C,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAmBrE"}
@@ -24,7 +24,7 @@ export class BaseHandler {
24
24
  this.context = context;
25
25
  }
26
26
  async resolveFilePath(url) {
27
- return resolveFilePath(url, this.context.root, this.context.workspaceRoot);
27
+ return resolveFilePath(url, this.context.root, this.context.workspaceRoot, this.context.userConfig);
28
28
  }
29
29
  async fileExists(filePath) {
30
30
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"node-module-handler.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/handlers/node-module-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAKxC,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAOrE,qBAAa,iBAAkB,SAAQ,WAAW;IAChD,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,SAAS,CAAY;gBAEjB,OAAO,EAAE,cAAc;IAO7B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAqRvD;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;CA0BjC"}
1
+ {"version":3,"file":"node-module-handler.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/handlers/node-module-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAKxC,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAQrE,qBAAa,iBAAkB,SAAQ,WAAW;IAChD,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,SAAS,CAAY;gBAEjB,OAAO,EAAE,cAAc;IAO7B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAgPvD;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;CAmCjC"}
@@ -12,6 +12,7 @@ import { UIHandler } from "./ui-handler.js";
12
12
  import { UIXHandler } from "./uix-handler.js";
13
13
  import { TSHandler } from "./ts-handler.js";
14
14
  import { findWorkspaceRoot } from "../../kernel/workspace.js";
15
+ import { findPackage } from "../../kernel/package-finder.js";
15
16
  import { shouldUseCdnFallback } from "../../resolution/cdn/cdn-fallback.js";
16
17
  export class NodeModuleHandler extends BaseHandler {
17
18
  constructor(context) {
@@ -64,52 +65,33 @@ export class NodeModuleHandler extends BaseHandler {
64
65
  current = parent;
65
66
  }
66
67
  }
68
+ // CONSOLIDATED DISCOVERY: Use the Generalized Package Finder
69
+ // This follows our "Local-First" priority: Siblings > Local node_modules > Workspace node_modules
67
70
  if (!filePath) {
68
- // Try workspace root node_modules
69
- if (workspaceRoot) {
70
- const workspaceNodeModulesPath = path.join(workspaceRoot, urlPath);
71
- console.log(chalk.blue(`[node_modules] Trying workspace path: ${workspaceNodeModulesPath}`));
72
- try {
73
- // Try to resolve symlinks first (realpath works even if path is a symlink)
74
- const resolvedPath = await fs.realpath(workspaceNodeModulesPath);
75
- console.log(chalk.blue(`[node_modules] Resolved to: ${resolvedPath}`));
76
- // Verify the resolved path exists
77
- await fs.access(resolvedPath);
78
- filePath = resolvedPath;
79
- console.log(chalk.green(`[node_modules] ✓ Found in workspace: ${urlPath}`));
80
- }
81
- catch (err2) {
82
- console.log(chalk.yellow(`[node_modules] Workspace path failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
83
- // Try swiss-lib monorepo node_modules (dynamically found)
84
- const { findSwissLibMonorepo } = await import("../../kernel/package-finder.js");
85
- const swissLib = await findSwissLibMonorepo(this.context.root);
86
- if (swissLib) {
87
- const swissNodeModulesPath = path.join(swissLib, urlPath);
88
- console.log(chalk.blue(`[node_modules] Trying swiss-lib path: ${swissNodeModulesPath}`));
89
- try {
90
- // Try to resolve symlinks first (realpath works even if path is a symlink)
91
- const resolvedPath = await fs.realpath(swissNodeModulesPath);
92
- console.log(chalk.blue(`[node_modules] Resolved to: ${resolvedPath}`));
93
- // Verify the resolved path exists
94
- await fs.access(resolvedPath);
95
- filePath = resolvedPath;
96
- console.log(chalk.green(`[node_modules] ✓ Found in swiss-lib monorepo: ${urlPath}`));
97
- }
98
- catch (err3) {
99
- console.log(chalk.yellow(`[node_modules] swiss-lib path failed: ${err3 instanceof Error ? err3.message : String(err3)}`));
100
- // File not found in any location, will trigger case-insensitive search below
101
- filePath = path.join(this.context.root, urlPath);
71
+ const urlParts = urlPath.split("/");
72
+ const packageName = urlParts[1].startsWith("@") ? `${urlParts[1]}/${urlParts[2]}` : urlParts[1];
73
+ const remainingPath = urlParts[1].startsWith("@") ? urlParts.slice(3).join("/") : urlParts.slice(2).join("/");
74
+ const location = await findPackage(packageName, this.context.root, workspaceRoot);
75
+ if (location) {
76
+ filePath = path.join(location.path, remainingPath);
77
+ console.log(chalk.green(`[node_modules] Found ${packageName} via ${location.type}: ${filePath}`));
78
+ // Re-use dist -> src fallback for local siblings
79
+ if (location.type !== 'node_modules' && filePath.includes("/dist/")) {
80
+ const srcPath = filePath.replace("/dist/", "/src/").replace(/\.[mc]?js$/, ".ts");
81
+ try {
82
+ await fs.access(srcPath);
83
+ console.log(chalk.yellow(`[node_modules] Intercept: Serving local source instead of dist: ${srcPath}`));
84
+ filePath = srcPath;
85
+ if (srcPath.endsWith(".ts")) {
86
+ return await this.tsHandler.handle(url.replace(/\.[mc]?js$/, ".ts"), res);
102
87
  }
103
88
  }
104
- else {
105
- // File not found in any location, will trigger case-insensitive search below
106
- filePath = path.join(this.context.root, urlPath);
107
- }
89
+ catch { /* Fallback to original filePath */ }
108
90
  }
109
91
  }
110
- else {
111
- filePath = path.join(this.context.root, urlPath);
112
- }
92
+ }
93
+ if (!filePath) {
94
+ filePath = path.join(this.context.root, urlPath);
113
95
  }
114
96
  console.log(chalk.gray(`[node_modules] Resolving: ${url} -> ${filePath}`));
115
97
  // File path is already resolved from above, no need to resolve again
@@ -259,6 +241,13 @@ export class NodeModuleHandler extends BaseHandler {
259
241
  }
260
242
  if (!pkgName || pkgName === "." || pkgName === "..")
261
243
  return null;
244
+ // Never redirect internal/private scoped packages to public CDNs
245
+ const internalScopes = this.context.userConfig?.internalScopes || [];
246
+ const isInternal = internalScopes.some(scope => pkgName === scope || pkgName.startsWith(scope + "/"));
247
+ if (isInternal) {
248
+ console.log(chalk.red(`[node_modules] CDN Blocked: Internal scope package ${pkgName} cannot be served from jsDelivr.`));
249
+ return null;
250
+ }
262
251
  if (!shouldUseCdnFallback(pkgName))
263
252
  return null;
264
253
  // jsDelivr +esm serves ESM build; works for reflect-metadata and most npm packages
@@ -10,6 +10,7 @@ export interface MiddlewareConfig {
10
10
  publicDir: string;
11
11
  resolver: ModuleResolver;
12
12
  hmr: HMREngine;
13
+ userConfig?: import("../../config/config.js").SwiteUserConfig;
13
14
  }
14
15
  export interface MiddlewareResult {
15
16
  routes: RouteDefinition[];
@@ -1 +1 @@
1
- {"version":3,"file":"middleware-setup.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/middleware/middleware-setup.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAmC,MAAM,SAAS,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAIpE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAa9D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAK1C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,SAAS,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,YAAY,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,GAAG,IAAI,CAAC;CACpE;AAuBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,gBAAgB,CAAC,CA2Q3B"}
1
+ {"version":3,"file":"middleware-setup.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/middleware/middleware-setup.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAmC,MAAM,SAAS,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAIpE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAa9D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAK1C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,SAAS,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,wBAAwB,EAAE,eAAe,CAAC;CAC/D;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,YAAY,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,GAAG,IAAI,CAAC;CACpE;AAuBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,gBAAgB,CAAC,CA2Q3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dev-engine/server.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IAKb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,YAAY,CACb;IACP,OAAO,CAAC,MAAM,CAAyB;gBAE3B,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM;YAe/B,iBAAiB;IAuBzB,KAAK;CAsEZ"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dev-engine/server.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IAKb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,YAAY,CACb;IACP,OAAO,CAAC,MAAM,CAAyB;gBAE3B,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM;YAe/B,iBAAiB;IAuBzB,KAAK;CA0EZ"}
@@ -14,6 +14,7 @@ import chalk from "chalk";
14
14
  import { setupMiddleware } from "./middleware/middleware-setup.js";
15
15
  import { buildSymlinkRegistry } from "../resolution/symlink-registry.js";
16
16
  import { findSwissLibMonorepo } from "../kernel/package-finder.js";
17
+ import { loadUserConfig } from "../config/config-loader.js";
17
18
  export class SwiteServer {
18
19
  constructor(config = {}) {
19
20
  this.app = express();
@@ -86,6 +87,8 @@ export class SwiteServer {
86
87
  console.warn(`[SWITE] Symlink registry build failed: ${err.message}`);
87
88
  }
88
89
  console.timeEnd("Symlink Registry");
90
+ // Load user config (swiss.config.ts) so internalScopes etc. flow into handlers
91
+ const userConfig = await loadUserConfig(this.config.root);
89
92
  // Setup middleware
90
93
  console.time("Middleware Setup");
91
94
  const workspaceRoot = await this.findWorkspaceRoot(this.config.root);
@@ -95,6 +98,7 @@ export class SwiteServer {
95
98
  publicDir: this.config.publicDir,
96
99
  resolver: this.resolver,
97
100
  hmr: this.hmr,
101
+ userConfig,
98
102
  });
99
103
  this.routes = middlewareResult.routes;
100
104
  this.routeScanner = middlewareResult.routeScanner;
@@ -7,16 +7,16 @@ export interface PackageLocation {
7
7
  type: 'swiss-lib' | 'workspace' | 'node_modules';
8
8
  }
9
9
  /**
10
- * Find a co-located framework monorepo by scanning sibling directories at each
11
- * ancestor level for any workspace root (pnpm-workspace.yaml) that also has a
12
- * packages/ directory. Works for any framework directory name.
10
+ * Find any sibling monorepo by searching for its package.json
11
+ */
12
+ export declare function findSiblingRepository(startPath: string, repoName: string): Promise<string | null>;
13
+ /**
14
+ * Backward compatibility wrapper for finding swiss-lib
13
15
  */
14
16
  export declare function findSwissLibMonorepo(startPath: string): Promise<string | null>;
15
17
  /**
16
- * Find a specific package by name, searching in:
17
- * 1. node_modules (local and workspace)
18
- * 2. swiss-lib/packages (if found)
19
- * 3. workspace packages (lib/, packages/, modules/)
18
+ * Find a specific package by name, with priority given based on environment.
19
+ * In development, we prioritize local sibling source code.
20
20
  */
21
21
  export declare function findPackage(packageName: string, startPath: string, workspaceRoot?: string | null): Promise<PackageLocation | null>;
22
22
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"package-finder.d.ts","sourceRoot":"","sources":["../../src/kernel/package-finder.ts"],"names":[],"mappings":"AASA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA+BpF;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,GAC5B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAoDjC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA2B7E"}
1
+ {"version":3,"file":"package-finder.d.ts","sourceRoot":"","sources":["../../src/kernel/package-finder.ts"],"names":[],"mappings":"AASA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;CAClD;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyBvG;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAEpF;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,GAC5B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAuEjC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA2B7E"}
@@ -6,48 +6,47 @@
6
6
  import { promises as fs } from "node:fs";
7
7
  import path from "node:path";
8
8
  /**
9
- * Find a co-located framework monorepo by scanning sibling directories at each
10
- * ancestor level for any workspace root (pnpm-workspace.yaml) that also has a
11
- * packages/ directory. Works for any framework directory name.
9
+ * Find any sibling monorepo by searching for its package.json
12
10
  */
13
- export async function findSwissLibMonorepo(startPath) {
14
- let current = path.resolve(startPath);
11
+ export async function findSiblingRepository(startPath, repoName) {
12
+ let current = startPath;
15
13
  for (let i = 0; i < 20; i++) {
16
- const parent = path.dirname(current);
17
- if (parent === current)
18
- break;
19
- // Scan siblings of `current` at this parent level
14
+ const siblingPath = path.join(current, repoName);
15
+ const pkgJson = path.join(siblingPath, "package.json");
16
+ if (await fileExists(pkgJson)) {
17
+ return siblingPath;
18
+ }
20
19
  try {
21
- const entries = await fs.readdir(parent, { withFileTypes: true });
20
+ const entries = await fs.readdir(current, { withFileTypes: true });
22
21
  for (const entry of entries) {
23
- if (entry.name === "node_modules")
24
- continue;
25
- if (!entry.isDirectory() && !entry.isSymbolicLink())
26
- continue;
27
- const sibling = path.join(parent, entry.name);
28
- if (path.resolve(sibling) === path.resolve(current))
29
- continue; // skip self
30
- if (await fileExists(path.join(sibling, "pnpm-workspace.yaml")) &&
31
- await fileExists(path.join(sibling, "packages"))) {
32
- return sibling;
22
+ if (entry.name === repoName && (entry.isDirectory() || entry.isSymbolicLink())) {
23
+ const subDir = path.join(current, entry.name);
24
+ if (await fileExists(path.join(subDir, "package.json"))) {
25
+ return subDir;
26
+ }
33
27
  }
34
28
  }
35
29
  }
36
- catch {
37
- // Skip on permission errors
38
- }
39
- current = parent;
30
+ catch { /* Continue */ }
31
+ current = path.dirname(current);
32
+ if (current === path.dirname(current))
33
+ break;
40
34
  }
41
35
  return null;
42
36
  }
43
37
  /**
44
- * Find a specific package by name, searching in:
45
- * 1. node_modules (local and workspace)
46
- * 2. swiss-lib/packages (if found)
47
- * 3. workspace packages (lib/, packages/, modules/)
38
+ * Backward compatibility wrapper for finding swiss-lib
39
+ */
40
+ export async function findSwissLibMonorepo(startPath) {
41
+ return findSiblingRepository(startPath, 'swiss-lib');
42
+ }
43
+ /**
44
+ * Find a specific package by name, with priority given based on environment.
45
+ * In development, we prioritize local sibling source code.
48
46
  */
49
47
  export async function findPackage(packageName, startPath, workspaceRoot) {
50
- // 1. Check local node_modules
48
+ const isDev = process.env.NODE_ENV !== 'production';
49
+ // 1. Check local node_modules (Standard resolution) - HIGHEST PRIORITY in Remote-First
51
50
  const localNodeModules = path.join(startPath, "node_modules", packageName);
52
51
  if (await fileExists(path.join(localNodeModules, "package.json"))) {
53
52
  return { path: localNodeModules, type: 'node_modules' };
@@ -70,23 +69,40 @@ export async function findPackage(packageName, startPath, workspaceRoot) {
70
69
  }
71
70
  }
72
71
  }
73
- // 4. Check workspace packages (lib/, packages/, modules/)
72
+ // 4. In dev: broader sibling scan across parent directories
73
+ if (isDev && packageName.includes("/")) {
74
+ const parts = packageName.split("/");
75
+ const unscoped = parts[parts.length - 1];
76
+ const parentDirs = [
77
+ path.join(startPath, ".."),
78
+ path.join(startPath, "../.."),
79
+ path.join(startPath, "../../.."),
80
+ ];
81
+ for (const parent of parentDirs) {
82
+ try {
83
+ const potentialRepos = await fs.readdir(parent);
84
+ for (const repo of potentialRepos) {
85
+ const siblingPath = path.join(parent, repo);
86
+ const packagePath = path.join(siblingPath, "packages", unscoped);
87
+ if (await fileExists(path.join(packagePath, "package.json"))) {
88
+ console.log(`[package-finder] Dev Intercept: Serving ${packageName} from local source: ${packagePath}`);
89
+ return { path: packagePath, type: 'swiss-lib' };
90
+ }
91
+ }
92
+ }
93
+ catch { /* Continue */ }
94
+ }
95
+ }
96
+ // 5. Fallback search in internal workspace packages (lib/, packages/, modules/)
74
97
  if (workspaceRoot) {
75
98
  const packageDirs = ["lib", "packages", "modules", "libraries", "apps"];
76
99
  for (const dir of packageDirs) {
77
100
  const searchDir = path.join(workspaceRoot, dir);
78
101
  if (!(await fileExists(searchDir)))
79
102
  continue;
80
- // Try scoped package name
81
- if (packageName.startsWith("@")) {
82
- const unscoped = packageName.split("/")[1];
83
- const packagePath = path.join(searchDir, unscoped);
84
- if (await fileExists(path.join(packagePath, "package.json"))) {
85
- return { path: packagePath, type: 'workspace' };
86
- }
87
- }
88
- // Try full package name
89
- const packagePath = path.join(searchDir, packageName);
103
+ const parts = packageName.split("/");
104
+ const unscoped = parts.length > 1 ? parts[1] : parts[0];
105
+ const packagePath = path.join(searchDir, unscoped);
90
106
  if (await fileExists(path.join(packagePath, "package.json"))) {
91
107
  return { path: packagePath, type: 'workspace' };
92
108
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bare-import-resolver.d.ts","sourceRoot":"","sources":["../../src/resolution/bare-import-resolver.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAmC,MAAM,mBAAmB,CAAC;AAI7F,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;IACnE,uBAAuB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACtE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,MAAM,CAAC,CAkNjB"}
1
+ {"version":3,"file":"bare-import-resolver.d.ts","sourceRoot":"","sources":["../../src/resolution/bare-import-resolver.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAmC,MAAM,mBAAmB,CAAC;AAI7F,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;IACnE,uBAAuB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACtE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,MAAM,CAAC,CA4NjB"}
@@ -32,7 +32,7 @@ export async function resolveBareImport(specifier, context) {
32
32
  if (workspaceRoot) {
33
33
  nodeModulesLocations.push(path.join(workspaceRoot, "node_modules"));
34
34
  }
35
- // Add swiss-lib monorepo node_modules
35
+ // Add monorepo node_modules if present
36
36
  const swissLib = await findSwissLibMonorepo(context.root);
37
37
  if (swissLib) {
38
38
  const swissNodeModules = path.join(swissLib, "node_modules");
@@ -159,9 +159,18 @@ export async function resolveBareImport(specifier, context) {
159
159
  entryPoint = pkgJson.module || pkgJson.main || "index.js";
160
160
  }
161
161
  const fullPath = path.join(pkgDir, entryPoint);
162
- // Try the exact path first
163
- if (await context.fileExists(fullPath)) {
164
- return await toUrl(fullPath, context);
162
+ // Try a few Swiss-specific variations of the entry point before CDN fallback
163
+ const swissVariations = [
164
+ fullPath,
165
+ fullPath.replace(/\.(js|mjs|ts)$/, '.ui'),
166
+ fullPath.replace(/\.(js|mjs|ts)$/, '.uix'),
167
+ path.join(pkgDir, 'src/index.ui'),
168
+ path.join(pkgDir, 'src/index.uix'),
169
+ ];
170
+ for (const v of swissVariations) {
171
+ if (await context.fileExists(v)) {
172
+ return await toUrl(v, context);
173
+ }
165
174
  }
166
175
  // Try with extensions
167
176
  for (const ext of [".js", ".mjs", ".ts", ".ui", ".uix"]) {
@@ -1,9 +1,10 @@
1
1
  export interface PathResolverContext {
2
2
  root: string;
3
3
  workspaceRoot: string | null;
4
+ userConfig?: any;
4
5
  }
5
6
  /**
6
7
  * Resolve file path from URL, handling SWISS packages, workspace packages, and app files
7
8
  */
8
- export declare function resolveFilePath(url: string, root: string, workspaceRoot?: string | null): Promise<string>;
9
+ export declare function resolveFilePath(url: string, root: string, workspaceRoot?: string | null, userConfig?: any): Promise<string>;
9
10
  //# sourceMappingURL=file-path-resolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-path-resolver.d.ts","sourceRoot":"","sources":["../../../src/resolution/path/file-path-resolver.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,aAAa,GAAE,MAAM,GAAG,IAAW,GAClC,OAAO,CAAC,MAAM,CAAC,CAsKjB"}
1
+ {"version":3,"file":"file-path-resolver.d.ts","sourceRoot":"","sources":["../../../src/resolution/path/file-path-resolver.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,aAAa,GAAE,MAAM,GAAG,IAAW,EACnC,UAAU,CAAC,EAAE,GAAG,GACf,OAAO,CAAC,MAAM,CAAC,CAsMjB"}
@@ -6,16 +6,48 @@
6
6
  import { promises as fs } from "node:fs";
7
7
  import path from "node:path";
8
8
  import { findWorkspaceRoot } from "../../kernel/workspace.js";
9
- import { findSwissLibMonorepo } from "../../kernel/package-finder.js";
9
+ import { findSwissLibMonorepo, findPackage } from "../../kernel/package-finder.js";
10
10
  /**
11
11
  * Resolve file path from URL, handling SWISS packages, workspace packages, and app files
12
12
  */
13
- export async function resolveFilePath(url, root, workspaceRoot = null) {
13
+ export async function resolveFilePath(url, root, workspaceRoot = null, userConfig) {
14
+ // Consolidate workspace root discovery for consistency across resolution blocks
15
+ const wsRoot = workspaceRoot || (await findWorkspaceRoot(root));
14
16
  // /node_modules/ URLs: walk up from app root until we find the package.
15
17
  // pnpm may place deps at the app root, one level up (workspace pkg), or at
16
18
  // the monorepo root depending on hoisting config and pnpm version.
17
19
  if (url.startsWith("/node_modules/")) {
18
20
  const urlPath = url.startsWith("/") ? url.slice(1) : url;
21
+ const parts = urlPath.split("/");
22
+ // Handle @scoped/package or standard-package
23
+ const packageName = parts[1].startsWith("@") ? `${parts[1]}/${parts[2]}` : parts[1];
24
+ // NEW: PNPM-aware Interceptor. Check if this request is for an "internal" scope package.
25
+ // In development, we always prioritize local siblings if they exist.
26
+ const internalScopes = userConfig?.internalScopes || [];
27
+ const match = internalScopes.length > 0
28
+ ? url.match(new RegExp(`(${internalScopes.join("|")})\/([^/]+)`))
29
+ : null;
30
+ if (process.env.NODE_ENV !== 'production' && match) {
31
+ const packageName = match[0];
32
+ const remainingPath = url.split(match[0])[1];
33
+ const localLoc = await findPackage(packageName, root, wsRoot);
34
+ if (localLoc && localLoc.type !== 'node_modules') {
35
+ // Found local source! Redirect the base path
36
+ const fullPath = path.join(localLoc.path, remainingPath);
37
+ // Re-use workspace fallback logic for dist -> src transition
38
+ if (fullPath.includes("/dist/")) {
39
+ const srcPath = fullPath.replace("/dist/", "/src/").replace(/\.[mc]?js$/, ".ts");
40
+ try {
41
+ await fs.access(srcPath);
42
+ console.log(`[file-path-resolver] Intercept: ${packageName} redirecting to local src: ${srcPath}`);
43
+ return srcPath;
44
+ }
45
+ catch { /* Fallback to dist if src not found */ }
46
+ }
47
+ console.log(`[file-path-resolver] Intercept: ${packageName} redirecting to local source: ${fullPath}`);
48
+ return fullPath;
49
+ }
50
+ }
19
51
  // Walk up the directory tree from root, trying node_modules at each level
20
52
  let current = path.resolve(root);
21
53
  const visited = new Set();
@@ -38,7 +70,6 @@ export async function resolveFilePath(url, root, workspaceRoot = null) {
38
70
  current = parent;
39
71
  }
40
72
  // Explicit workspace root (covers hoisted-to-root installs)
41
- const wsRoot = workspaceRoot || (await findWorkspaceRoot(root));
42
73
  if (wsRoot) {
43
74
  const wsPath = path.join(wsRoot, urlPath);
44
75
  if (!visited.has(wsPath)) {
@@ -78,13 +109,10 @@ export async function resolveFilePath(url, root, workspaceRoot = null) {
78
109
  url.startsWith("/libraries/") ||
79
110
  url.startsWith("/packages/") ||
80
111
  url.startsWith("/modules/")) {
81
- let wsRoot = workspaceRoot;
82
- if (!wsRoot) {
83
- wsRoot = await findWorkspaceRoot(root);
84
- console.log(`[file-path-resolver] Detected workspace root: ${wsRoot} (from app root: ${root})`);
85
- }
112
+ // Already detected wsRoot at function start
86
113
  // Normalize URL: path.join with leading slash is wrong on Windows (treats as drive root)
87
114
  const urlPath = url.startsWith("/") ? url.slice(1) : url;
115
+ // ...
88
116
  // CRITICAL: For /lib/ paths, we MUST find the SWS root (which has lib/ directory)
89
117
  // Start from app root and walk up until we find a directory with both pnpm-workspace.yaml AND lib/
90
118
  if (url.startsWith("/lib/")) {
@@ -141,7 +169,6 @@ export async function resolveFilePath(url, root, workspaceRoot = null) {
141
169
  }
142
170
  }
143
171
  // For app files, check if URL already includes the app path
144
- const wsRoot = workspaceRoot || (await findWorkspaceRoot(root));
145
172
  if (wsRoot) {
146
173
  const appRelativeToWorkspace = path
147
174
  .relative(wsRoot, root)
@@ -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.