@swissjs/swite 0.3.3 → 0.3.5

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 (40) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cli.js +8 -2
  3. package/dist/config/config.d.ts +39 -2
  4. package/dist/config/config.d.ts.map +1 -1
  5. package/dist/dev-engine/handlers/ui-handler.d.ts.map +1 -1
  6. package/dist/dev-engine/handlers/ui-handler.js +6 -3
  7. package/dist/dev-engine/handlers/uix-handler.d.ts.map +1 -1
  8. package/dist/dev-engine/handlers/uix-handler.js +6 -3
  9. package/dist/dev-engine/hmr/hmr.d.ts +1 -1
  10. package/dist/dev-engine/hmr/hmr.d.ts.map +1 -1
  11. package/dist/dev-engine/hmr/hmr.js +14 -2
  12. package/dist/dev-engine/middleware/middleware-setup.d.ts.map +1 -1
  13. package/dist/dev-engine/middleware/middleware-setup.js +2 -0
  14. package/dist/dev-engine/middleware/static-files.d.ts +2 -0
  15. package/dist/dev-engine/middleware/static-files.d.ts.map +1 -1
  16. package/dist/dev-engine/middleware/static-files.js +2 -1
  17. package/dist/dev-engine/server.js +4 -4
  18. package/dist/kernel/package-finder.d.ts +5 -2
  19. package/dist/kernel/package-finder.d.ts.map +1 -1
  20. package/dist/kernel/package-finder.js +11 -3
  21. package/dist/resolution/bare-import-resolver.d.ts.map +1 -1
  22. package/dist/resolution/bare-import-resolver.js +10 -2
  23. package/dist/resolution/cdn/cdn-fallback.d.ts.map +1 -1
  24. package/dist/resolution/cdn/cdn-fallback.js +4 -1
  25. package/dist/resolution/path/path-fixup.d.ts +6 -3
  26. package/dist/resolution/path/path-fixup.d.ts.map +1 -1
  27. package/dist/resolution/path/path-fixup.js +14 -10
  28. package/package.json +2 -2
  29. package/src/cli.ts +9 -2
  30. package/src/config/config.ts +36 -2
  31. package/src/dev-engine/handlers/ui-handler.ts +8 -3
  32. package/src/dev-engine/handlers/uix-handler.ts +8 -3
  33. package/src/dev-engine/hmr/hmr.ts +19 -3
  34. package/src/dev-engine/middleware/middleware-setup.ts +2 -0
  35. package/src/dev-engine/middleware/static-files.ts +4 -1
  36. package/src/dev-engine/server.ts +5 -5
  37. package/src/kernel/package-finder.ts +13 -3
  38. package/src/resolution/bare-import-resolver.ts +11 -2
  39. package/src/resolution/cdn/cdn-fallback.ts +4 -1
  40. package/src/resolution/path/path-fixup.ts +17 -9
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.5
4
+
5
+ ### Patch Changes
6
+
7
+ - deps: bump @swissjs/core 0.1.10 → 0.1.11 (browser hotfix — guards `process.env` in reconciliation to prevent ReferenceError crash in browser environments)
8
+
9
+ ## 0.3.4
10
+
11
+ ### Patch Changes
12
+
13
+ - fix(resolution): `findSwissLibMonorepo` now accepts `siblingRepositories` from user config, making sibling repo discovery configurable (#1)
14
+ - fix(resolution): Compiler path fixup (`/swiss-lib/` → `/swiss-packages/`) is now configurable via `compilerPathFixup` config key; can be disabled or given custom patterns (#2)
15
+ - fix(config): Added `publicDir`, `hmrPort`, `hmrHost`, `aliases`, `excludeFromHmr`, and `entry` to `SwiteUserConfig` (#3)
16
+ - fix(resolution): CDN fallback default changed to `false`; unscoped packages no longer fall through to jsDelivr without explicit `SWITE_CDN_FALLBACK_SCOPES` opt-in (#4)
17
+ - fix(hmr): HMR watcher now handles `add` and `unlink` events with full-page reload; `excludeFromHmr` patterns added to chokidar `ignored` list (#5)
18
+ - fix(dx): Added `--verbose` / `-v` flag and `SWITE_DEBUG=1` env var for resolver diagnostics; unresolved packages now log searched paths (#6)
19
+ - fix(static): CSS extraction entry point configurable via `SwiteUserConfig.entry` (defaults to `src/index.ui`); missing entry file no longer crashes server (#7)
20
+ - deps: bump `@swissjs/core` 0.1.9 → 0.1.10
21
+
3
22
  ## 0.3.3
4
23
 
5
24
  ### Patch Changes
package/dist/cli.js CHANGED
@@ -7,6 +7,10 @@ import { startPythonDevService, stopPythonDevService, } from "./dev-engine/pytho
7
7
  import { setProductionMode } from "./adapters/proxy/proxyToPython.js";
8
8
  const [, , command, ...args] = process.argv;
9
9
  const root = resolve(process.cwd());
10
+ // --verbose / -v enables full resolver diagnostic output
11
+ if (args.includes("--verbose") || args.includes("-v")) {
12
+ process.env["SWITE_DEBUG"] = "1";
13
+ }
10
14
  async function dev() {
11
15
  const config = await loadUserConfig(root);
12
16
  const python = config.services?.python;
@@ -26,7 +30,8 @@ async function dev() {
26
30
  root,
27
31
  port: config.server?.port ?? 3000,
28
32
  host: config.server?.host ?? "localhost",
29
- publicDir: "public",
33
+ hmrPort: config.server?.hmrPort,
34
+ publicDir: config.publicDir ?? "public",
30
35
  open: false,
31
36
  });
32
37
  await server.start();
@@ -43,7 +48,8 @@ async function start() {
43
48
  root,
44
49
  port: config.server?.port ?? 3000,
45
50
  host: config.server?.host ?? "localhost",
46
- publicDir: "public",
51
+ hmrPort: config.server?.hmrPort,
52
+ publicDir: config.publicDir ?? "public",
47
53
  open: false,
48
54
  });
49
55
  await server.start();
@@ -16,10 +16,34 @@ export interface ServicesConfig {
16
16
  export interface ServerConfig {
17
17
  port?: number;
18
18
  host?: string;
19
+ /** Port for the HMR WebSocket server. Defaults to 24678. */
20
+ hmrPort?: number;
21
+ /** Host for the HMR WebSocket server. Defaults to server.host. */
22
+ hmrHost?: string;
19
23
  }
20
24
  export interface SwiteUserConfig {
21
25
  server?: ServerConfig;
22
26
  services?: ServicesConfig;
27
+ /**
28
+ * Directory to serve static assets from. Defaults to "public".
29
+ * Path is relative to the project root.
30
+ */
31
+ publicDir?: string;
32
+ /**
33
+ * Application entry file for CSS extraction. Defaults to "src/index.ui".
34
+ * Path is relative to the project root.
35
+ */
36
+ entry?: string;
37
+ /**
38
+ * Module aliases resolved during bare import resolution.
39
+ * e.g. { "@/": "src/" } maps @/ imports to the src/ directory.
40
+ */
41
+ aliases?: Record<string, string>;
42
+ /**
43
+ * Glob patterns to exclude from HMR watching in addition to the defaults
44
+ * (node_modules, .git, dist). Useful for generated files or large assets.
45
+ */
46
+ excludeFromHmr?: string[];
23
47
  /**
24
48
  * Package scopes that should be treated as "internal" or "private".
25
49
  * These scopes prioritize local/monorepo resolution and are forbidden from CDN redirects.
@@ -27,10 +51,23 @@ export interface SwiteUserConfig {
27
51
  */
28
52
  internalScopes?: string[];
29
53
  /**
30
- * Manual override for sibling repository lookup.
31
- * Swite will search these directories for local package source code.
54
+ * Names of sibling monorepos to search for framework packages.
55
+ * Defaults to ['swiss-lib']. Override when your framework lives in a differently-named repo.
32
56
  */
33
57
  siblingRepositories?: string[];
58
+ /**
59
+ * Control the compiler path fixup that rewrites `/swiss-lib/` → `/swiss-packages/`.
60
+ * Disable entirely or supply custom from/to pairs when your project uses different paths.
61
+ */
62
+ compilerPathFixup?: {
63
+ /** When false, no path fixup is applied. Defaults to true for backward compatibility. */
64
+ enabled?: boolean;
65
+ /** Custom replacement patterns. Defaults to the built-in swiss-lib → swiss-packages pairs. */
66
+ patterns?: Array<{
67
+ from: string;
68
+ to: string;
69
+ }>;
70
+ };
34
71
  }
35
72
  /**
36
73
  * 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;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
+ {"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;IACd,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B;;;OAGG;IACH,iBAAiB,CAAC,EAAE;QAClB,yFAAyF;QACzF,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,8FAA8F;QAC9F,QAAQ,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAChD,CAAC;CACH;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAErE"}
@@ -1 +1 @@
1
- {"version":3,"file":"ui-handler.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/handlers/ui-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQxC,OAAO,EACL,WAAW,EAEX,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,SAAU,SAAQ,WAAW;IACxC,OAAO,CAAC,QAAQ,CAAoB;gBAExB,OAAO,EAAE,cAAc;IAI7B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAkExD"}
1
+ {"version":3,"file":"ui-handler.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/handlers/ui-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQxC,OAAO,EACL,WAAW,EAEX,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,SAAU,SAAQ,WAAW;IACxC,OAAO,CAAC,QAAQ,CAAoB;gBAExB,OAAO,EAAE,cAAc;IAI7B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAuExD"}
@@ -26,10 +26,13 @@ export class UIHandler extends BaseHandler {
26
26
  console.error(chalk.red(`[.ui] File not found: ${filePath}`));
27
27
  throw new Error(`File not found: ${url} (resolved to: ${filePath})`);
28
28
  }
29
+ const pathFixupEnabled = this.context.userConfig?.compilerPathFixup?.enabled !== false;
30
+ const pathFixupPatterns = this.context.userConfig?.compilerPathFixup?.patterns;
31
+ const applyPathFixup = (code) => pathFixupEnabled ? fixSwissLibPaths(code, pathFixupPatterns) : code;
29
32
  // Cache hit
30
33
  const cached = await compilationCache.get(filePath, (compiled) => this.getDependencies(compiled));
31
34
  if (cached) {
32
- const fixed = fixSwissLibPaths(cached);
35
+ const fixed = applyPathFixup(cached);
33
36
  setDevHeaders(res);
34
37
  res.setHeader("Content-Type", "application/javascript; charset=utf-8");
35
38
  res.setHeader("Content-Length", Buffer.byteLength(fixed, "utf-8"));
@@ -48,7 +51,7 @@ export class UIHandler extends BaseHandler {
48
51
  });
49
52
  compiled = tsResult.code;
50
53
  // Fix compiler-emitted wrong paths before import rewriting
51
- compiled = fixSwissLibPaths(compiled);
54
+ compiled = applyPathFixup(compiled);
52
55
  // Inline import.meta.env references before import rewriting
53
56
  compiled = inlineEnvReferences(compiled, this.context.env);
54
57
  // Strip CSS static-asset imports — they are not ES modules
@@ -58,7 +61,7 @@ export class UIHandler extends BaseHandler {
58
61
  console.warn(`[.ui] Compiled output contains bare imports: ${url}`);
59
62
  }
60
63
  const rewritten = await rewriteImports(compiled, filePath, this.context.resolver);
61
- const finalCode = fixSwissLibPaths(rewritten);
64
+ const finalCode = applyPathFixup(rewritten);
62
65
  await compilationCache.set(filePath, compiled, finalCode, (c) => this.getDependencies(c));
63
66
  if (bareImportPattern.test(finalCode)) {
64
67
  console.error(`[.ui] Bare imports still present after rewriting: ${url}`);
@@ -1 +1 @@
1
- {"version":3,"file":"uix-handler.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/handlers/uix-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQxC,OAAO,EACL,WAAW,EAEX,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,UAAW,SAAQ,WAAW;IACzC,OAAO,CAAC,QAAQ,CAAoB;gBAExB,OAAO,EAAE,cAAc;IAI7B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CA8DxD"}
1
+ {"version":3,"file":"uix-handler.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/handlers/uix-handler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQxC,OAAO,EACL,WAAW,EAEX,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,UAAW,SAAQ,WAAW;IACzC,OAAO,CAAC,QAAQ,CAAoB;gBAExB,OAAO,EAAE,cAAc;IAI7B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAmExD"}
@@ -19,10 +19,13 @@ export class UIXHandler extends BaseHandler {
19
19
  async handle(url, res) {
20
20
  const filePath = await this.resolveFilePath(url);
21
21
  console.log(chalk.blue(`[.uix] ${url}`));
22
+ const pathFixupEnabled = this.context.userConfig?.compilerPathFixup?.enabled !== false;
23
+ const pathFixupPatterns = this.context.userConfig?.compilerPathFixup?.patterns;
24
+ const applyPathFixup = (code) => pathFixupEnabled ? fixSwissLibPaths(code, pathFixupPatterns) : code;
22
25
  // Cache hit
23
26
  const cached = await compilationCache.get(filePath, (compiled) => this.getDependencies(compiled));
24
27
  if (cached) {
25
- const fixed = fixSwissLibPaths(cached);
28
+ const fixed = applyPathFixup(cached);
26
29
  setDevHeaders(res);
27
30
  res.setHeader("Content-Type", "application/javascript; charset=utf-8");
28
31
  res.send(fixed);
@@ -40,7 +43,7 @@ export class UIXHandler extends BaseHandler {
40
43
  });
41
44
  compiled = tsResult.code;
42
45
  // Fix compiler-emitted wrong paths before import rewriting
43
- compiled = fixSwissLibPaths(compiled);
46
+ compiled = applyPathFixup(compiled);
44
47
  // Inline import.meta.env references before import rewriting
45
48
  compiled = inlineEnvReferences(compiled, this.context.env);
46
49
  // Strip CSS static-asset imports — they are not ES modules
@@ -55,7 +58,7 @@ export class UIXHandler extends BaseHandler {
55
58
  console.warn(`[.uix] Compiled output contains bare imports: ${url}`);
56
59
  }
57
60
  const rewritten = await rewriteImports(compiled, filePath, this.context.resolver);
58
- const finalCode = fixSwissLibPaths(rewritten);
61
+ const finalCode = applyPathFixup(rewritten);
59
62
  await compilationCache.set(filePath, compiled, finalCode, (c) => this.getDependencies(c));
60
63
  if (bareImportPattern.test(finalCode)) {
61
64
  console.error(`[.uix] Bare imports still present after rewriting: ${url}`);
@@ -10,7 +10,7 @@ export declare class HMREngine {
10
10
  private setupWebSocket;
11
11
  private findFreePort;
12
12
  getPort(): number;
13
- start(): Promise<void>;
13
+ start(excludeFromHmr?: string[]): Promise<void>;
14
14
  notifyChange(filePath: string): void;
15
15
  getClientScript(): string;
16
16
  private getUpdateType;
@@ -1 +1 @@
1
- {"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/hmr/hmr.ts"],"names":[],"mappings":"AAUA,qBAAa,SAAS;IAOlB,OAAO,CAAC,IAAI;IANd,OAAO,CAAC,GAAG,CAAmB;IAC9B,OAAO,CAAC,OAAO,CAAC,CAAqB;IACrC,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,IAAI,CAAS;gBAGX,IAAI,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM;IAOZ,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAiBnB,kBAAkB;IAUhC,OAAO,CAAC,cAAc;YAYR,YAAY;IAmB1B,OAAO,IAAI,MAAM;IAIX,KAAK;IA4BX,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAYpC,eAAe,IAAI,MAAM;IAIzB,OAAO,CAAC,aAAa;IAuBrB,OAAO,CAAC,SAAS;IAcX,IAAI;CAIX"}
1
+ {"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/hmr/hmr.ts"],"names":[],"mappings":"AAUA,qBAAa,SAAS;IAOlB,OAAO,CAAC,IAAI;IANd,OAAO,CAAC,GAAG,CAAmB;IAC9B,OAAO,CAAC,OAAO,CAAC,CAAqB;IACrC,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,IAAI,CAAS;gBAGX,IAAI,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM;IAOZ,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAiBnB,kBAAkB;IAUhC,OAAO,CAAC,cAAc;YAYR,YAAY;IAmB1B,OAAO,IAAI,MAAM;IAIX,KAAK,CAAC,cAAc,CAAC,EAAE,MAAM,EAAE;IA2CrC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAYpC,eAAe,IAAI,MAAM;IAIzB,OAAO,CAAC,aAAa;IAuBrB,OAAO,CAAC,SAAS;IAeX,IAAI;CAIX"}
@@ -65,9 +65,11 @@ export class HMREngine {
65
65
  getPort() {
66
66
  return this.port;
67
67
  }
68
- async start() {
68
+ async start(excludeFromHmr) {
69
+ const baseIgnored = ["**/node_modules/**", "**/.git/**", "**/dist/**"];
70
+ const ignored = excludeFromHmr ? [...baseIgnored, ...excludeFromHmr] : baseIgnored;
69
71
  this.watcher = chokidar.watch(this.root, {
70
- ignored: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
72
+ ignored,
71
73
  ignoreInitial: true,
72
74
  awaitWriteFinish: {
73
75
  stabilityThreshold: 100,
@@ -86,6 +88,16 @@ export class HMREngine {
86
88
  timestamp: Date.now(),
87
89
  });
88
90
  });
91
+ this.watcher.on("add", (filePath) => {
92
+ console.log(chalk.yellow(`[HMR] File added: ${filePath}`));
93
+ // New file — dependents are unknown, trigger a full reload
94
+ this.broadcast({ type: "reload", path: filePath, reason: "file-added" });
95
+ });
96
+ this.watcher.on("unlink", (filePath) => {
97
+ console.log(chalk.yellow(`[HMR] File deleted: ${filePath}`));
98
+ // Deleted file — its dependents will 404 on next import, trigger reload
99
+ this.broadcast({ type: "reload", path: filePath, reason: "file-deleted" });
100
+ });
89
101
  console.log(chalk.green("[HMR] Watching for file changes..."));
90
102
  }
91
103
  notifyChange(filePath) {
@@ -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;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
+ {"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,CA6Q3B"}
@@ -83,6 +83,7 @@ export async function setupMiddleware(app, config) {
83
83
  root: config.root,
84
84
  workspaceRoot,
85
85
  env,
86
+ userConfig: config.userConfig,
86
87
  };
87
88
  const uiHandler = new UIHandler(handlerContext);
88
89
  const uixHandler = new UIXHandler(handlerContext);
@@ -318,6 +319,7 @@ export async function setupMiddleware(app, config) {
318
319
  await setupSPAFallback(app, {
319
320
  root: config.root,
320
321
  publicDir: config.publicDir,
322
+ entry: config.userConfig?.entry,
321
323
  });
322
324
  return {
323
325
  routes: fileRouterResult.routes,
@@ -3,6 +3,8 @@ export interface StaticFilesConfig {
3
3
  root: string;
4
4
  publicDir: string;
5
5
  workspaceRoot?: string | null;
6
+ /** Entry file for CSS extraction, relative to root. Defaults to "src/index.ui". */
7
+ entry?: string;
6
8
  }
7
9
  /**
8
10
  * Setup static file serving for public directory, node_modules, and workspace packages
@@ -1 +1 @@
1
- {"version":3,"file":"static-files.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/middleware/static-files.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAMvC,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,IAAI,CAAC,CAuYf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,IAAI,CAAC,CA+Sf"}
1
+ {"version":3,"file":"static-files.d.ts","sourceRoot":"","sources":["../../../src/dev-engine/middleware/static-files.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAMvC,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,IAAI,CAAC,CAuYf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,IAAI,CAAC,CAgTf"}
@@ -412,7 +412,8 @@ export async function setupSPAFallback(app, config) {
412
412
  console.log(chalk.magenta(`[SWITE CSS] ========== CSS EXTRACTION START (VERSION 3.0.0) ==========`));
413
413
  console.log(chalk.magenta(`[SWITE CSS] App root: ${config.root}`));
414
414
  try {
415
- const entryPointPath = path.join(config.root, "src", "index.ui");
415
+ const entryFile = config.entry ?? "src/index.ui";
416
+ const entryPointPath = path.join(config.root, entryFile);
416
417
  console.log(chalk.blue(`[SWITE CSS] Checking entry point: ${entryPointPath}`));
417
418
  const entryPointContent = await fs.readFile(entryPointPath, "utf-8");
418
419
  // Extract CSS imports using regex
@@ -61,6 +61,8 @@ export class SwiteServer {
61
61
  const startTime = Date.now();
62
62
  console.log(chalk.cyan("\n⚡ SWITE - SWISS Development Server\n"));
63
63
  console.time("Startup");
64
+ // Load user config (swiss.config.ts) so internalScopes etc. flow into handlers
65
+ const userConfig = await loadUserConfig(this.config.root);
64
66
  // CG-03: Build symlink registry before serving any requests.
65
67
  // Maps realpath(node_modules/pkg symlink) → /node_modules/pkg browser URL
66
68
  // so toUrl() can map absolute filesystem paths back to browser URLs.
@@ -77,7 +79,7 @@ export class SwiteServer {
77
79
  if (workspaceRoot) {
78
80
  nodeModulesDirs.push(path.join(workspaceRoot, "node_modules"));
79
81
  }
80
- const swissLib = await findSwissLibMonorepo(this.config.root);
82
+ const swissLib = await findSwissLibMonorepo(this.config.root, userConfig?.siblingRepositories);
81
83
  if (swissLib) {
82
84
  nodeModulesDirs.push(path.join(swissLib, "node_modules"));
83
85
  }
@@ -87,8 +89,6 @@ export class SwiteServer {
87
89
  console.warn(`[SWITE] Symlink registry build failed: ${err.message}`);
88
90
  }
89
91
  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);
92
92
  // Setup middleware
93
93
  console.time("Middleware Setup");
94
94
  const workspaceRoot = await this.findWorkspaceRoot(this.config.root);
@@ -107,7 +107,7 @@ export class SwiteServer {
107
107
  // Start HMR
108
108
  console.time("HMR Start");
109
109
  await this.hmr.initialize();
110
- await this.hmr.start();
110
+ await this.hmr.start(userConfig?.excludeFromHmr);
111
111
  console.timeEnd("HMR Start");
112
112
  // Start HTTP server
113
113
  // Use 0.0.0.0 to bind to all interfaces (IPv4 and IPv6)
@@ -11,9 +11,12 @@ export interface PackageLocation {
11
11
  */
12
12
  export declare function findSiblingRepository(startPath: string, repoName: string): Promise<string | null>;
13
13
  /**
14
- * Backward compatibility wrapper for finding swiss-lib
14
+ * Find the co-located framework monorepo. Tries each name in `fallbackNames`
15
+ * in order and returns the first match. The default list is ['swiss-lib'] for
16
+ * backward compatibility; callers that have a user config should forward
17
+ * `userConfig.siblingRepositories` here to make the lookup configurable.
15
18
  */
16
- export declare function findSwissLibMonorepo(startPath: string): Promise<string | null>;
19
+ export declare function findSwissLibMonorepo(startPath: string, fallbackNames?: string[]): Promise<string | null>;
17
20
  /**
18
21
  * Find a specific package by name, with priority given based on environment.
19
22
  * In development, we prioritize local sibling source code.
@@ -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;;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"}
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;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,aAAa,GAAE,MAAM,EAAkB,GACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB;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"}
@@ -35,10 +35,18 @@ export async function findSiblingRepository(startPath, repoName) {
35
35
  return null;
36
36
  }
37
37
  /**
38
- * Backward compatibility wrapper for finding swiss-lib
38
+ * Find the co-located framework monorepo. Tries each name in `fallbackNames`
39
+ * in order and returns the first match. The default list is ['swiss-lib'] for
40
+ * backward compatibility; callers that have a user config should forward
41
+ * `userConfig.siblingRepositories` here to make the lookup configurable.
39
42
  */
40
- export async function findSwissLibMonorepo(startPath) {
41
- return findSiblingRepository(startPath, 'swiss-lib');
43
+ export async function findSwissLibMonorepo(startPath, fallbackNames = ['swiss-lib']) {
44
+ for (const name of fallbackNames) {
45
+ const result = await findSiblingRepository(startPath, name);
46
+ if (result)
47
+ return result;
48
+ }
49
+ return null;
42
50
  }
43
51
  /**
44
52
  * Find a specific package by name, with priority given based on environment.
@@ -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,CA4NjB"}
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,CAqOjB"}
@@ -13,6 +13,7 @@ import { toUrl } from "./url-resolver.js";
13
13
  * Resolve bare import specifier (e.g., @swissjs/core, react, etc.)
14
14
  */
15
15
  export async function resolveBareImport(specifier, context) {
16
+ const debug = process.env["SWITE_DEBUG"] === "1";
16
17
  // Extract package name outside the try/catch so fallback logic can reference it.
17
18
  // This must stay project-agnostic: works for both scoped and unscoped packages.
18
19
  const parts = specifier.split("/");
@@ -40,6 +41,9 @@ export async function resolveBareImport(specifier, context) {
40
41
  nodeModulesLocations.push(swissNodeModules);
41
42
  }
42
43
  }
44
+ if (debug) {
45
+ console.log(`[swite:resolve] Trying "${specifier}" in:`, nodeModulesLocations);
46
+ }
43
47
  // Try each location
44
48
  for (const nodeModulesPath of nodeModulesLocations) {
45
49
  const testPkgDir = path.join(nodeModulesPath, pkgName);
@@ -56,10 +60,14 @@ export async function resolveBareImport(specifier, context) {
56
60
  return await resolveWorkspacePackageEntry(workspacePkg, pkgName, subPath, specifier, context);
57
61
  }
58
62
  if (!shouldUseCdnFallback(pkgName)) {
59
- console.warn(`[SWITE] Package ${pkgName} not found anywhere. Scoped package detected; CDN fallback is disabled by default.`);
63
+ console.warn(`[SWITE] Cannot resolve "${pkgName}"\n` +
64
+ ` Searched:\n${nodeModulesLocations.map(p => ` - ${p}`).join('\n')}\n` +
65
+ ` CDN fallback is disabled. To enable for a scope, set:\n` +
66
+ ` SWITE_CDN_FALLBACK_SCOPES=@scope1,@scope2\n` +
67
+ ` Run with --verbose for full resolution trace.`);
60
68
  return `/node_modules/${specifier}`;
61
69
  }
62
- console.warn(`[SWITE] Package ${pkgName} not found anywhere, using CDN fallback`);
70
+ console.warn(`[SWITE] Package ${pkgName} not found in any local location, using CDN fallback`);
63
71
  return `https://cdn.jsdelivr.net/npm/${specifier}/+esm`;
64
72
  }
65
73
  // Continue with normal resolution if we found it
@@ -1 +1 @@
1
- {"version":3,"file":"cdn-fallback.d.ts","sourceRoot":"","sources":["../../../src/resolution/cdn/cdn-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAmBH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAKpE"}
1
+ {"version":3,"file":"cdn-fallback.d.ts","sourceRoot":"","sources":["../../../src/resolution/cdn/cdn-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAmBH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAQpE"}
@@ -29,8 +29,11 @@ function parseAllowList() {
29
29
  }
30
30
  export function shouldUseCdnFallback(specifierOrPkg) {
31
31
  const scope = getScope(specifierOrPkg);
32
+ // Unscoped packages (e.g. "react") are not automatically CDN-eligible; require
33
+ // explicit opt-in via SWITE_CDN_FALLBACK_SCOPES to prevent accidental exfiltration
34
+ // of package requests for private-registry or unscoped internal packages.
32
35
  if (!scope)
33
- return true; // unscoped: allow by default
36
+ return false;
34
37
  const allow = parseAllowList();
35
38
  return allow.has(scope);
36
39
  }
@@ -6,8 +6,11 @@
6
6
  * compiler is fixed at source this single function is the authoritative fixup.
7
7
  * Apply it once per compilation, before passing code to the import rewriter.
8
8
  *
9
- * All seven previous fixup locations across ui-handler, uix-handler, and
10
- * import-rewriter have been removed in favour of this call.
9
+ * Pass `patterns` from `userConfig.compilerPathFixup.patterns` to override the
10
+ * defaults. Pass an empty array to disable all fixups.
11
11
  */
12
- export declare function fixSwissLibPaths(code: string): string;
12
+ export declare function fixSwissLibPaths(code: string, patterns?: Array<{
13
+ from: string;
14
+ to: string;
15
+ }>): string;
13
16
  //# sourceMappingURL=path-fixup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"path-fixup.d.ts","sourceRoot":"","sources":["../../../src/resolution/path/path-fixup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOrD"}
1
+ {"version":3,"file":"path-fixup.d.ts","sourceRoot":"","sources":["../../../src/resolution/path/path-fixup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7C,MAAM,CAYR"}
@@ -6,15 +6,19 @@
6
6
  * compiler is fixed at source this single function is the authoritative fixup.
7
7
  * Apply it once per compilation, before passing code to the import rewriter.
8
8
  *
9
- * All seven previous fixup locations across ui-handler, uix-handler, and
10
- * import-rewriter have been removed in favour of this call.
9
+ * Pass `patterns` from `userConfig.compilerPathFixup.patterns` to override the
10
+ * defaults. Pass an empty array to disable all fixups.
11
11
  */
12
- export function fixSwissLibPaths(code) {
13
- if (!code.includes('/swiss-lib/'))
14
- return code;
15
- // More-specific pattern first so `/swiss-lib/packages/` doesn't leave a
16
- // dangling `/swiss-packages/packages/` if the replacement ran twice.
17
- return code
18
- .replace(/\/swiss-lib\/packages\//g, '/swiss-packages/')
19
- .replace(/\/swiss-lib\//g, '/swiss-packages/');
12
+ export function fixSwissLibPaths(code, patterns) {
13
+ const activePatterns = patterns ?? [
14
+ { from: '/swiss-lib/packages/', to: '/swiss-packages/' },
15
+ { from: '/swiss-lib/', to: '/swiss-packages/' },
16
+ ];
17
+ let result = code;
18
+ for (const { from, to } of activePatterns) {
19
+ if (result.includes(from)) {
20
+ result = result.split(from).join(to);
21
+ }
22
+ }
23
+ return result;
20
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swissjs/swite",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "SWITE - SWISS Development Server (Vite replacement)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,7 +9,7 @@
9
9
  "swite": "dist/cli.js"
10
10
  },
11
11
  "dependencies": {
12
- "@swissjs/core": "0.1.9",
12
+ "@swissjs/core": "0.1.11",
13
13
  "@swissjs/compiler": "0.1.5",
14
14
  "@swissjs/plugin-file-router": "1.0.2",
15
15
  "chalk": "^5.3.0",
package/src/cli.ts CHANGED
@@ -12,6 +12,11 @@ import { setProductionMode } from "./adapters/proxy/proxyToPython.js";
12
12
  const [, , command, ...args] = process.argv;
13
13
  const root = resolve(process.cwd());
14
14
 
15
+ // --verbose / -v enables full resolver diagnostic output
16
+ if (args.includes("--verbose") || args.includes("-v")) {
17
+ process.env["SWITE_DEBUG"] = "1";
18
+ }
19
+
15
20
  async function dev(): Promise<void> {
16
21
  const config = await loadUserConfig(root);
17
22
  const python = config.services?.python;
@@ -35,7 +40,8 @@ async function dev(): Promise<void> {
35
40
  root,
36
41
  port: config.server?.port ?? 3000,
37
42
  host: config.server?.host ?? "localhost",
38
- publicDir: "public",
43
+ hmrPort: config.server?.hmrPort,
44
+ publicDir: config.publicDir ?? "public",
39
45
  open: false,
40
46
  });
41
47
 
@@ -61,7 +67,8 @@ async function start(): Promise<void> {
61
67
  root,
62
68
  port: config.server?.port ?? 3000,
63
69
  host: config.server?.host ?? "localhost",
64
- publicDir: "public",
70
+ hmrPort: config.server?.hmrPort,
71
+ publicDir: config.publicDir ?? "public",
65
72
  open: false,
66
73
  });
67
74
 
@@ -18,11 +18,35 @@ export interface ServicesConfig {
18
18
  export interface ServerConfig {
19
19
  port?: number;
20
20
  host?: string;
21
+ /** Port for the HMR WebSocket server. Defaults to 24678. */
22
+ hmrPort?: number;
23
+ /** Host for the HMR WebSocket server. Defaults to server.host. */
24
+ hmrHost?: string;
21
25
  }
22
26
 
23
27
  export interface SwiteUserConfig {
24
28
  server?: ServerConfig;
25
29
  services?: ServicesConfig;
30
+ /**
31
+ * Directory to serve static assets from. Defaults to "public".
32
+ * Path is relative to the project root.
33
+ */
34
+ publicDir?: string;
35
+ /**
36
+ * Application entry file for CSS extraction. Defaults to "src/index.ui".
37
+ * Path is relative to the project root.
38
+ */
39
+ entry?: string;
40
+ /**
41
+ * Module aliases resolved during bare import resolution.
42
+ * e.g. { "@/": "src/" } maps @/ imports to the src/ directory.
43
+ */
44
+ aliases?: Record<string, string>;
45
+ /**
46
+ * Glob patterns to exclude from HMR watching in addition to the defaults
47
+ * (node_modules, .git, dist). Useful for generated files or large assets.
48
+ */
49
+ excludeFromHmr?: string[];
26
50
  /**
27
51
  * Package scopes that should be treated as "internal" or "private".
28
52
  * These scopes prioritize local/monorepo resolution and are forbidden from CDN redirects.
@@ -30,10 +54,20 @@ export interface SwiteUserConfig {
30
54
  */
31
55
  internalScopes?: string[];
32
56
  /**
33
- * Manual override for sibling repository lookup.
34
- * Swite will search these directories for local package source code.
57
+ * Names of sibling monorepos to search for framework packages.
58
+ * Defaults to ['swiss-lib']. Override when your framework lives in a differently-named repo.
35
59
  */
36
60
  siblingRepositories?: string[];
61
+ /**
62
+ * Control the compiler path fixup that rewrites `/swiss-lib/` → `/swiss-packages/`.
63
+ * Disable entirely or supply custom from/to pairs when your project uses different paths.
64
+ */
65
+ compilerPathFixup?: {
66
+ /** When false, no path fixup is applied. Defaults to true for backward compatibility. */
67
+ enabled?: boolean;
68
+ /** Custom replacement patterns. Defaults to the built-in swiss-lib → swiss-packages pairs. */
69
+ patterns?: Array<{ from: string; to: string }>;
70
+ };
37
71
  }
38
72
 
39
73
  /**
@@ -36,10 +36,15 @@ export class UIHandler extends BaseHandler {
36
36
  throw new Error(`File not found: ${url} (resolved to: ${filePath})`);
37
37
  }
38
38
 
39
+ const pathFixupEnabled = this.context.userConfig?.compilerPathFixup?.enabled !== false;
40
+ const pathFixupPatterns = this.context.userConfig?.compilerPathFixup?.patterns;
41
+ const applyPathFixup = (code: string) =>
42
+ pathFixupEnabled ? fixSwissLibPaths(code, pathFixupPatterns) : code;
43
+
39
44
  // Cache hit
40
45
  const cached = await compilationCache.get(filePath, (compiled) => this.getDependencies(compiled));
41
46
  if (cached) {
42
- const fixed = fixSwissLibPaths(cached);
47
+ const fixed = applyPathFixup(cached);
43
48
  setDevHeaders(res);
44
49
  res.setHeader("Content-Type", "application/javascript; charset=utf-8");
45
50
  res.setHeader("Content-Length", Buffer.byteLength(fixed, "utf-8"));
@@ -61,7 +66,7 @@ export class UIHandler extends BaseHandler {
61
66
  compiled = tsResult.code;
62
67
 
63
68
  // Fix compiler-emitted wrong paths before import rewriting
64
- compiled = fixSwissLibPaths(compiled);
69
+ compiled = applyPathFixup(compiled);
65
70
 
66
71
  // Inline import.meta.env references before import rewriting
67
72
  compiled = inlineEnvReferences(compiled, this.context.env);
@@ -75,7 +80,7 @@ export class UIHandler extends BaseHandler {
75
80
  }
76
81
 
77
82
  const rewritten = await rewriteImports(compiled, filePath, this.context.resolver);
78
- const finalCode = fixSwissLibPaths(rewritten);
83
+ const finalCode = applyPathFixup(rewritten);
79
84
 
80
85
  await compilationCache.set(filePath, compiled, finalCode, (c) => this.getDependencies(c));
81
86
 
@@ -29,10 +29,15 @@ export class UIXHandler extends BaseHandler {
29
29
  const filePath = await this.resolveFilePath(url);
30
30
  console.log(chalk.blue(`[.uix] ${url}`));
31
31
 
32
+ const pathFixupEnabled = this.context.userConfig?.compilerPathFixup?.enabled !== false;
33
+ const pathFixupPatterns = this.context.userConfig?.compilerPathFixup?.patterns;
34
+ const applyPathFixup = (code: string) =>
35
+ pathFixupEnabled ? fixSwissLibPaths(code, pathFixupPatterns) : code;
36
+
32
37
  // Cache hit
33
38
  const cached = await compilationCache.get(filePath, (compiled) => this.getDependencies(compiled));
34
39
  if (cached) {
35
- const fixed = fixSwissLibPaths(cached);
40
+ const fixed = applyPathFixup(cached);
36
41
  setDevHeaders(res);
37
42
  res.setHeader("Content-Type", "application/javascript; charset=utf-8");
38
43
  res.send(fixed);
@@ -53,7 +58,7 @@ export class UIXHandler extends BaseHandler {
53
58
  compiled = tsResult.code;
54
59
 
55
60
  // Fix compiler-emitted wrong paths before import rewriting
56
- compiled = fixSwissLibPaths(compiled);
61
+ compiled = applyPathFixup(compiled);
57
62
 
58
63
  // Inline import.meta.env references before import rewriting
59
64
  compiled = inlineEnvReferences(compiled, this.context.env);
@@ -72,7 +77,7 @@ export class UIXHandler extends BaseHandler {
72
77
  }
73
78
 
74
79
  const rewritten = await rewriteImports(compiled, filePath, this.context.resolver);
75
- const finalCode = fixSwissLibPaths(rewritten);
80
+ const finalCode = applyPathFixup(rewritten);
76
81
 
77
82
  await compilationCache.set(filePath, compiled, finalCode, (c) => this.getDependencies(c));
78
83
 
@@ -85,9 +85,12 @@ export class HMREngine {
85
85
  return this.port;
86
86
  }
87
87
 
88
- async start() {
88
+ async start(excludeFromHmr?: string[]) {
89
+ const baseIgnored = ["**/node_modules/**", "**/.git/**", "**/dist/**"];
90
+ const ignored = excludeFromHmr ? [...baseIgnored, ...excludeFromHmr] : baseIgnored;
91
+
89
92
  this.watcher = chokidar.watch(this.root, {
90
- ignored: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
93
+ ignored,
91
94
  ignoreInitial: true,
92
95
  awaitWriteFinish: {
93
96
  stabilityThreshold: 100,
@@ -110,6 +113,18 @@ export class HMREngine {
110
113
  });
111
114
  });
112
115
 
116
+ this.watcher.on("add", (filePath) => {
117
+ console.log(chalk.yellow(`[HMR] File added: ${filePath}`));
118
+ // New file — dependents are unknown, trigger a full reload
119
+ this.broadcast({ type: "reload", path: filePath, reason: "file-added" });
120
+ });
121
+
122
+ this.watcher.on("unlink", (filePath) => {
123
+ console.log(chalk.yellow(`[HMR] File deleted: ${filePath}`));
124
+ // Deleted file — its dependents will 404 on next import, trigger reload
125
+ this.broadcast({ type: "reload", path: filePath, reason: "file-deleted" });
126
+ });
127
+
113
128
  console.log(chalk.green("[HMR] Watching for file changes..."));
114
129
  }
115
130
 
@@ -156,7 +171,8 @@ export class HMREngine {
156
171
  type: string;
157
172
  path: string;
158
173
  updateType?: string;
159
- timestamp: number;
174
+ reason?: string;
175
+ timestamp?: number;
160
176
  }) {
161
177
  const payload = JSON.stringify(message);
162
178
  this.clients.forEach((client) => {
@@ -118,6 +118,7 @@ export async function setupMiddleware(
118
118
  root: config.root,
119
119
  workspaceRoot,
120
120
  env,
121
+ userConfig: config.userConfig,
121
122
  };
122
123
 
123
124
  const uiHandler = new UIHandler(handlerContext);
@@ -342,6 +343,7 @@ export async function setupMiddleware(
342
343
  await setupSPAFallback(app, {
343
344
  root: config.root,
344
345
  publicDir: config.publicDir,
346
+ entry: config.userConfig?.entry,
345
347
  });
346
348
 
347
349
  return {
@@ -15,6 +15,8 @@ export interface StaticFilesConfig {
15
15
  root: string;
16
16
  publicDir: string;
17
17
  workspaceRoot?: string | null;
18
+ /** Entry file for CSS extraction, relative to root. Defaults to "src/index.ui". */
19
+ entry?: string;
18
20
  }
19
21
 
20
22
  /**
@@ -549,7 +551,8 @@ export async function setupSPAFallback(
549
551
  console.log(chalk.magenta(`[SWITE CSS] ========== CSS EXTRACTION START (VERSION 3.0.0) ==========`));
550
552
  console.log(chalk.magenta(`[SWITE CSS] App root: ${config.root}`));
551
553
  try {
552
- const entryPointPath = path.join(config.root, "src", "index.ui");
554
+ const entryFile = config.entry ?? "src/index.ui";
555
+ const entryPointPath = path.join(config.root, entryFile);
553
556
  console.log(chalk.blue(`[SWITE CSS] Checking entry point: ${entryPointPath}`));
554
557
  const entryPointContent = await fs.readFile(entryPointPath, "utf-8");
555
558
 
@@ -85,6 +85,9 @@ export class SwiteServer {
85
85
  console.log(chalk.cyan("\n⚡ SWITE - SWISS Development Server\n"));
86
86
  console.time("Startup");
87
87
 
88
+ // Load user config (swiss.config.ts) so internalScopes etc. flow into handlers
89
+ const userConfig = await loadUserConfig(this.config.root);
90
+
88
91
  // CG-03: Build symlink registry before serving any requests.
89
92
  // Maps realpath(node_modules/pkg symlink) → /node_modules/pkg browser URL
90
93
  // so toUrl() can map absolute filesystem paths back to browser URLs.
@@ -101,7 +104,7 @@ export class SwiteServer {
101
104
  if (workspaceRoot) {
102
105
  nodeModulesDirs.push(path.join(workspaceRoot, "node_modules"));
103
106
  }
104
- const swissLib = await findSwissLibMonorepo(this.config.root);
107
+ const swissLib = await findSwissLibMonorepo(this.config.root, userConfig?.siblingRepositories);
105
108
  if (swissLib) {
106
109
  nodeModulesDirs.push(path.join(swissLib, "node_modules"));
107
110
  }
@@ -111,9 +114,6 @@ export class SwiteServer {
111
114
  }
112
115
  console.timeEnd("Symlink Registry");
113
116
 
114
- // Load user config (swiss.config.ts) so internalScopes etc. flow into handlers
115
- const userConfig = await loadUserConfig(this.config.root);
116
-
117
117
  // Setup middleware
118
118
  console.time("Middleware Setup");
119
119
  const workspaceRoot = await this.findWorkspaceRoot(this.config.root);
@@ -133,7 +133,7 @@ export class SwiteServer {
133
133
  // Start HMR
134
134
  console.time("HMR Start");
135
135
  await this.hmr.initialize();
136
- await this.hmr.start();
136
+ await this.hmr.start(userConfig?.excludeFromHmr);
137
137
  console.timeEnd("HMR Start");
138
138
 
139
139
  // Start HTTP server
@@ -48,10 +48,20 @@ export async function findSiblingRepository(startPath: string, repoName: string)
48
48
  }
49
49
 
50
50
  /**
51
- * Backward compatibility wrapper for finding swiss-lib
51
+ * Find the co-located framework monorepo. Tries each name in `fallbackNames`
52
+ * in order and returns the first match. The default list is ['swiss-lib'] for
53
+ * backward compatibility; callers that have a user config should forward
54
+ * `userConfig.siblingRepositories` here to make the lookup configurable.
52
55
  */
53
- export async function findSwissLibMonorepo(startPath: string): Promise<string | null> {
54
- return findSiblingRepository(startPath, 'swiss-lib');
56
+ export async function findSwissLibMonorepo(
57
+ startPath: string,
58
+ fallbackNames: string[] = ['swiss-lib'],
59
+ ): Promise<string | null> {
60
+ for (const name of fallbackNames) {
61
+ const result = await findSiblingRepository(startPath, name);
62
+ if (result) return result;
63
+ }
64
+ return null;
55
65
  }
56
66
 
57
67
  /**
@@ -23,6 +23,7 @@ export async function resolveBareImport(
23
23
  specifier: string,
24
24
  context: BareImportResolverContext
25
25
  ): Promise<string> {
26
+ const debug = process.env["SWITE_DEBUG"] === "1";
26
27
 
27
28
  // Extract package name outside the try/catch so fallback logic can reference it.
28
29
  // This must stay project-agnostic: works for both scoped and unscoped packages.
@@ -56,6 +57,10 @@ export async function resolveBareImport(
56
57
  }
57
58
  }
58
59
 
60
+ if (debug) {
61
+ console.log(`[swite:resolve] Trying "${specifier}" in:`, nodeModulesLocations);
62
+ }
63
+
59
64
  // Try each location
60
65
  for (const nodeModulesPath of nodeModulesLocations) {
61
66
  const testPkgDir = path.join(nodeModulesPath, pkgName);
@@ -81,12 +86,16 @@ export async function resolveBareImport(
81
86
 
82
87
  if (!shouldUseCdnFallback(pkgName)) {
83
88
  console.warn(
84
- `[SWITE] Package ${pkgName} not found anywhere. Scoped package detected; CDN fallback is disabled by default.`,
89
+ `[SWITE] Cannot resolve "${pkgName}"\n` +
90
+ ` Searched:\n${nodeModulesLocations.map(p => ` - ${p}`).join('\n')}\n` +
91
+ ` CDN fallback is disabled. To enable for a scope, set:\n` +
92
+ ` SWITE_CDN_FALLBACK_SCOPES=@scope1,@scope2\n` +
93
+ ` Run with --verbose for full resolution trace.`,
85
94
  );
86
95
  return `/node_modules/${specifier}`;
87
96
  }
88
97
 
89
- console.warn(`[SWITE] Package ${pkgName} not found anywhere, using CDN fallback`);
98
+ console.warn(`[SWITE] Package ${pkgName} not found in any local location, using CDN fallback`);
90
99
  return `https://cdn.jsdelivr.net/npm/${specifier}/+esm`;
91
100
  }
92
101
 
@@ -30,7 +30,10 @@ function parseAllowList(): Set<string> {
30
30
 
31
31
  export function shouldUseCdnFallback(specifierOrPkg: string): boolean {
32
32
  const scope = getScope(specifierOrPkg);
33
- if (!scope) return true; // unscoped: allow by default
33
+ // Unscoped packages (e.g. "react") are not automatically CDN-eligible; require
34
+ // explicit opt-in via SWITE_CDN_FALLBACK_SCOPES to prevent accidental exfiltration
35
+ // of package requests for private-registry or unscoped internal packages.
36
+ if (!scope) return false;
34
37
  const allow = parseAllowList();
35
38
  return allow.has(scope);
36
39
  }
@@ -6,14 +6,22 @@
6
6
  * compiler is fixed at source this single function is the authoritative fixup.
7
7
  * Apply it once per compilation, before passing code to the import rewriter.
8
8
  *
9
- * All seven previous fixup locations across ui-handler, uix-handler, and
10
- * import-rewriter have been removed in favour of this call.
9
+ * Pass `patterns` from `userConfig.compilerPathFixup.patterns` to override the
10
+ * defaults. Pass an empty array to disable all fixups.
11
11
  */
12
- export function fixSwissLibPaths(code: string): string {
13
- if (!code.includes('/swiss-lib/')) return code;
14
- // More-specific pattern first so `/swiss-lib/packages/` doesn't leave a
15
- // dangling `/swiss-packages/packages/` if the replacement ran twice.
16
- return code
17
- .replace(/\/swiss-lib\/packages\//g, '/swiss-packages/')
18
- .replace(/\/swiss-lib\//g, '/swiss-packages/');
12
+ export function fixSwissLibPaths(
13
+ code: string,
14
+ patterns?: Array<{ from: string; to: string }>,
15
+ ): string {
16
+ const activePatterns = patterns ?? [
17
+ { from: '/swiss-lib/packages/', to: '/swiss-packages/' },
18
+ { from: '/swiss-lib/', to: '/swiss-packages/' },
19
+ ];
20
+ let result = code;
21
+ for (const { from, to } of activePatterns) {
22
+ if (result.includes(from)) {
23
+ result = result.split(from).join(to);
24
+ }
25
+ }
26
+ return result;
19
27
  }