@swissjs/swite 0.3.0 → 0.3.1

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 (157) hide show
  1. package/dist/adapters/proxy/SwiteProxyError.d.ts.map +1 -0
  2. package/dist/{proxy → adapters/proxy}/proxyToPython.d.ts +1 -1
  3. package/dist/adapters/proxy/proxyToPython.d.ts.map +1 -0
  4. package/dist/build-engine/builder.d.ts.map +1 -0
  5. package/dist/{builder.js → build-engine/builder.js} +8 -14
  6. package/dist/cli.js +5 -5
  7. package/dist/config/config-loader.d.ts.map +1 -0
  8. package/dist/config/config.d.ts.map +1 -0
  9. package/dist/config/env.d.ts +25 -0
  10. package/dist/config/env.d.ts.map +1 -0
  11. package/dist/config/env.js +84 -0
  12. package/dist/{handlers → dev-engine/handlers}/base-handler.d.ts +3 -1
  13. package/dist/dev-engine/handlers/base-handler.d.ts.map +1 -0
  14. package/dist/{handlers → dev-engine/handlers}/base-handler.js +22 -2
  15. package/dist/dev-engine/handlers/js-handler.d.ts.map +1 -0
  16. package/dist/{handlers → dev-engine/handlers}/js-handler.js +1 -1
  17. package/dist/dev-engine/handlers/mjs-handler.d.ts.map +1 -0
  18. package/dist/{handlers → dev-engine/handlers}/mjs-handler.js +1 -1
  19. package/dist/dev-engine/handlers/node-module-handler.d.ts.map +1 -0
  20. package/dist/{handlers → dev-engine/handlers}/node-module-handler.js +4 -4
  21. package/dist/{handlers → dev-engine/handlers}/ts-handler.d.ts +0 -4
  22. package/dist/dev-engine/handlers/ts-handler.d.ts.map +1 -0
  23. package/dist/{handlers → dev-engine/handlers}/ts-handler.js +5 -28
  24. package/dist/{handlers → dev-engine/handlers}/ui-handler.d.ts +0 -4
  25. package/dist/dev-engine/handlers/ui-handler.d.ts.map +1 -0
  26. package/dist/dev-engine/handlers/ui-handler.js +84 -0
  27. package/dist/{handlers → dev-engine/handlers}/uix-handler.d.ts +0 -4
  28. package/dist/dev-engine/handlers/uix-handler.d.ts.map +1 -0
  29. package/dist/dev-engine/handlers/uix-handler.js +70 -0
  30. package/dist/dev-engine/hmr/hmr-client-template.d.ts +10 -0
  31. package/dist/dev-engine/hmr/hmr-client-template.d.ts.map +1 -0
  32. package/dist/dev-engine/hmr/hmr-client-template.js +122 -0
  33. package/dist/dev-engine/hmr/hmr.d.ts.map +1 -0
  34. package/dist/{hmr.js → dev-engine/hmr/hmr.js} +2 -134
  35. package/dist/{middleware → dev-engine/middleware}/hmr-routes.d.ts +2 -2
  36. package/dist/dev-engine/middleware/hmr-routes.d.ts.map +1 -0
  37. package/dist/{middleware → dev-engine/middleware}/hmr-routes.js +1 -1
  38. package/dist/dev-engine/middleware/middleware-setup.d.ts +34 -0
  39. package/dist/dev-engine/middleware/middleware-setup.d.ts.map +1 -0
  40. package/dist/dev-engine/middleware/middleware-setup.js +327 -0
  41. package/dist/dev-engine/middleware/static-files.d.ts.map +1 -0
  42. package/dist/{middleware → dev-engine/middleware}/static-files.js +2 -2
  43. package/dist/{dev → dev-engine}/pythonDevManager.d.ts +1 -1
  44. package/dist/dev-engine/pythonDevManager.d.ts.map +1 -0
  45. package/dist/{dev → dev-engine}/pythonDevManager.js +1 -1
  46. package/dist/{router → dev-engine/router}/file-router.d.ts +4 -4
  47. package/dist/dev-engine/router/file-router.d.ts.map +1 -0
  48. package/dist/{router → dev-engine/router}/file-router.js +4 -4
  49. package/dist/dev-engine/server.d.ts.map +1 -0
  50. package/dist/{server.js → dev-engine/server.js} +6 -6
  51. package/dist/index.d.ts +13 -13
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +9 -9
  54. package/dist/internal/cache/compilation-cache.d.ts.map +1 -0
  55. package/dist/{cache → internal/cache}/compilation-cache.js +3 -2
  56. package/dist/internal/generate-import-map-cli.d.ts.map +1 -0
  57. package/dist/{utils → internal}/generate-import-map-cli.js +1 -1
  58. package/dist/internal/generate-import-map.d.ts.map +1 -0
  59. package/dist/{utils → internal}/generate-import-map.js +3 -3
  60. package/dist/{utils → kernel}/package-finder.d.ts +3 -1
  61. package/dist/kernel/package-finder.d.ts.map +1 -0
  62. package/dist/{utils → kernel}/package-finder.js +29 -52
  63. package/dist/kernel/package-registry.d.ts.map +1 -0
  64. package/dist/kernel/workspace.d.ts.map +1 -0
  65. package/dist/{resolver → resolution}/bare-import-resolver.d.ts +1 -1
  66. package/dist/resolution/bare-import-resolver.d.ts.map +1 -0
  67. package/dist/{resolver → resolution}/bare-import-resolver.js +12 -49
  68. package/dist/resolution/cdn/cdn-fallback.d.ts.map +1 -0
  69. package/dist/resolution/path/file-path-resolver.d.ts.map +1 -0
  70. package/dist/{utils → resolution/path}/file-path-resolver.js +11 -20
  71. package/dist/resolution/path/path-fixup.d.ts +13 -0
  72. package/dist/resolution/path/path-fixup.d.ts.map +1 -0
  73. package/dist/resolution/path/path-fixup.js +20 -0
  74. package/dist/{resolver.d.ts → resolution/resolver.d.ts} +1 -1
  75. package/dist/resolution/resolver.d.ts.map +1 -0
  76. package/dist/{resolver.js → resolution/resolver.js} +8 -37
  77. package/dist/{import-rewriter.d.ts → resolution/rewriting/import-rewriter.d.ts} +1 -1
  78. package/dist/resolution/rewriting/import-rewriter.d.ts.map +1 -0
  79. package/dist/resolution/rewriting/import-rewriter.js +199 -0
  80. package/dist/{resolver → resolution}/symlink-registry.d.ts +1 -1
  81. package/dist/resolution/symlink-registry.d.ts.map +1 -0
  82. package/dist/{resolver → resolution}/symlink-registry.js +1 -1
  83. package/dist/resolution/url-resolver.d.ts.map +1 -0
  84. package/dist/{resolver → resolution}/url-resolver.js +38 -109
  85. package/dist/resolution/workspace-package-resolver.d.ts.map +1 -0
  86. package/dist/resolution/workspace-package-resolver.js +77 -0
  87. package/package.json +24 -15
  88. package/src/cli.ts +1 -1
  89. package/src/resolution/url-resolver.ts +1 -1
  90. package/dist/builder.d.ts.map +0 -1
  91. package/dist/cache/compilation-cache.d.ts.map +0 -1
  92. package/dist/config-loader.d.ts.map +0 -1
  93. package/dist/config.d.ts.map +0 -1
  94. package/dist/dev/pythonDevManager.d.ts.map +0 -1
  95. package/dist/env.d.ts +0 -19
  96. package/dist/env.d.ts.map +0 -1
  97. package/dist/env.js +0 -112
  98. package/dist/handlers/base-handler.d.ts.map +0 -1
  99. package/dist/handlers/js-handler.d.ts.map +0 -1
  100. package/dist/handlers/mjs-handler.d.ts.map +0 -1
  101. package/dist/handlers/node-module-handler.d.ts.map +0 -1
  102. package/dist/handlers/ts-handler.d.ts.map +0 -1
  103. package/dist/handlers/ui-handler.d.ts.map +0 -1
  104. package/dist/handlers/ui-handler.js +0 -182
  105. package/dist/handlers/uix-handler.d.ts.map +0 -1
  106. package/dist/handlers/uix-handler.js +0 -135
  107. package/dist/hmr.d.ts.map +0 -1
  108. package/dist/import-rewriter.d.ts.map +0 -1
  109. package/dist/import-rewriter.js +0 -351
  110. package/dist/middleware/hmr-routes.d.ts.map +0 -1
  111. package/dist/middleware/middleware-setup.d.ts +0 -23
  112. package/dist/middleware/middleware-setup.d.ts.map +0 -1
  113. package/dist/middleware/middleware-setup.js +0 -596
  114. package/dist/middleware/static-files.d.ts.map +0 -1
  115. package/dist/proxy/SwiteProxyError.d.ts.map +0 -1
  116. package/dist/proxy/proxyToPython.d.ts.map +0 -1
  117. package/dist/resolver/bare-import-resolver.d.ts.map +0 -1
  118. package/dist/resolver/symlink-registry.d.ts.map +0 -1
  119. package/dist/resolver/url-resolver.d.ts.map +0 -1
  120. package/dist/resolver/workspace-package-resolver.d.ts.map +0 -1
  121. package/dist/resolver/workspace-package-resolver.js +0 -185
  122. package/dist/resolver.d.ts.map +0 -1
  123. package/dist/router/file-router.d.ts.map +0 -1
  124. package/dist/server.d.ts.map +0 -1
  125. package/dist/utils/cdn-fallback.d.ts.map +0 -1
  126. package/dist/utils/file-path-resolver.d.ts.map +0 -1
  127. package/dist/utils/generate-import-map-cli.d.ts.map +0 -1
  128. package/dist/utils/generate-import-map.d.ts.map +0 -1
  129. package/dist/utils/package-finder.d.ts.map +0 -1
  130. package/dist/utils/package-registry.d.ts.map +0 -1
  131. package/dist/utils/workspace.d.ts.map +0 -1
  132. /package/dist/{proxy → adapters/proxy}/SwiteProxyError.d.ts +0 -0
  133. /package/dist/{proxy → adapters/proxy}/SwiteProxyError.js +0 -0
  134. /package/dist/{proxy → adapters/proxy}/proxyToPython.js +0 -0
  135. /package/dist/{builder.d.ts → build-engine/builder.d.ts} +0 -0
  136. /package/dist/{config-loader.d.ts → config/config-loader.d.ts} +0 -0
  137. /package/dist/{config-loader.js → config/config-loader.js} +0 -0
  138. /package/dist/{config.d.ts → config/config.d.ts} +0 -0
  139. /package/dist/{config.js → config/config.js} +0 -0
  140. /package/dist/{handlers → dev-engine/handlers}/js-handler.d.ts +0 -0
  141. /package/dist/{handlers → dev-engine/handlers}/mjs-handler.d.ts +0 -0
  142. /package/dist/{handlers → dev-engine/handlers}/node-module-handler.d.ts +0 -0
  143. /package/dist/{hmr.d.ts → dev-engine/hmr/hmr.d.ts} +0 -0
  144. /package/dist/{middleware → dev-engine/middleware}/static-files.d.ts +0 -0
  145. /package/dist/{server.d.ts → dev-engine/server.d.ts} +0 -0
  146. /package/dist/{cache → internal/cache}/compilation-cache.d.ts +0 -0
  147. /package/dist/{utils → internal}/generate-import-map-cli.d.ts +0 -0
  148. /package/dist/{utils → internal}/generate-import-map.d.ts +0 -0
  149. /package/dist/{utils → kernel}/package-registry.d.ts +0 -0
  150. /package/dist/{utils → kernel}/package-registry.js +0 -0
  151. /package/dist/{utils → kernel}/workspace.d.ts +0 -0
  152. /package/dist/{utils → kernel}/workspace.js +0 -0
  153. /package/dist/{utils → resolution/cdn}/cdn-fallback.d.ts +0 -0
  154. /package/dist/{utils → resolution/cdn}/cdn-fallback.js +0 -0
  155. /package/dist/{utils → resolution/path}/file-path-resolver.d.ts +0 -0
  156. /package/dist/{resolver → resolution}/url-resolver.d.ts +0 -0
  157. /package/dist/{resolver → resolution}/workspace-package-resolver.d.ts +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/resolution/resolver.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAMpE,qBAAa,cAAc;IAIb,OAAO,CAAC,IAAI;IAHxB,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,SAAS,CAA0B;gBAEvB,IAAI,EAAE,MAAM;IAEhC;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI;YAWjC,gBAAgB;IAMxB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YA8HrD,UAAU;YASV,uBAAuB;YAavB,KAAK;CAUpB"}
@@ -4,10 +4,10 @@
4
4
  import path from "node:path";
5
5
  import { promises as fs } from "node:fs";
6
6
  import chalk from "chalk";
7
- import { findSwissLibMonorepo } from "./utils/package-finder.js";
8
- import { toUrl } from "./resolver/url-resolver.js";
9
- import { resolveWorkspacePackage } from "./resolver/workspace-package-resolver.js";
10
- import { resolveBareImport } from "./resolver/bare-import-resolver.js";
7
+ import { findWorkspaceRoot } from "../kernel/workspace.js";
8
+ import { toUrl } from "./url-resolver.js";
9
+ import { resolveWorkspacePackage } from "./workspace-package-resolver.js";
10
+ import { resolveBareImport } from "./bare-import-resolver.js";
11
11
  export class ModuleResolver {
12
12
  constructor(root) {
13
13
  this.root = root;
@@ -26,28 +26,10 @@ export class ModuleResolver {
26
26
  async getWorkspaceRoot() {
27
27
  if (this.workspaceRoot)
28
28
  return this.workspaceRoot;
29
- const findWorkspaceRoot = async (startDir) => {
30
- let current = startDir;
31
- for (let i = 0; i < 5; i++) {
32
- const workspaceFile = path.join(current, "pnpm-workspace.yaml");
33
- const packageJson = path.join(current, "package.json");
34
- if ((await this.fileExists(workspaceFile)) ||
35
- ((await this.fileExists(packageJson)) &&
36
- JSON.parse(await fs.readFile(packageJson, "utf-8")).workspaces)) {
37
- return current;
38
- }
39
- const parent = path.dirname(current);
40
- if (parent === current)
41
- break;
42
- current = parent;
43
- }
44
- return null;
45
- };
46
29
  this.workspaceRoot = await findWorkspaceRoot(this.root);
47
30
  return this.workspaceRoot;
48
31
  }
49
32
  async resolve(specifier, importer) {
50
- console.log(`[SWITE] resolve CALLED: specifier: ${specifier}, importer: ${importer}`);
51
33
  // Check import map first (fast path)
52
34
  if (this.importMap && !specifier.startsWith(".") && !specifier.startsWith("/")) {
53
35
  const mapped = this.importMap.imports[specifier];
@@ -81,9 +63,7 @@ export class ModuleResolver {
81
63
  fileExists: (p) => this.fileExists(p),
82
64
  resolveWorkspacePackage: (pkgName) => this.resolveWorkspacePackage(pkgName),
83
65
  };
84
- const result = await resolveBareImport(specifier, context);
85
- console.log(`[SWITE] resolve RESULT: ${specifier} -> ${result}`);
86
- return result;
66
+ return await resolveBareImport(specifier, context);
87
67
  }
88
68
  // Handle absolute paths (already URLs)
89
69
  if (specifier.startsWith("/")) {
@@ -126,30 +106,21 @@ export class ModuleResolver {
126
106
  // This preserves .ui/.uix extensions for SWISS files
127
107
  const hasExtension = /\.(ui|uix|ts|tsx|js|jsx|mjs)$/.test(specifier);
128
108
  if (hasExtension) {
129
- // Specifier has extension, resolve it directly
130
109
  const resolved = path.resolve(importerDir, specifier);
131
- console.log(`[SWITE] resolve relative (hasExt): ${specifier}, importerDir: ${importerDir}, resolved: ${resolved}, exists: ${await this.fileExists(resolved)}`);
132
110
  if (await this.fileExists(resolved)) {
133
- const url = await this.toUrl(resolved);
134
- console.log(`[SWITE] resolve relative: ${specifier} -> ${resolved} -> ${url}`);
135
- return url;
111
+ return await this.toUrl(resolved);
136
112
  }
137
113
  }
138
114
  // If no extension or file not found, try adding extensions
139
115
  // Strip any existing extension from specifier (but preserve .ui/.uix if present)
140
116
  const specifierWithoutExt = specifier.replace(/\.(js|ts|jsx|tsx|mjs)$/, "");
141
117
  const resolved = path.resolve(importerDir, specifierWithoutExt);
142
- console.log(`[SWITE] resolve relative (trying extensions): specifierWithoutExt: ${specifierWithoutExt}, resolved: ${resolved}`);
143
118
  // Try adding extensions (prioritize .ui and .uix for SWISS files)
144
119
  const extensions = [".ui", ".uix", ".ts", ".tsx", ".js", ".jsx", ".mjs"];
145
120
  for (const ext of extensions) {
146
121
  const withExt = resolved + ext;
147
- const exists = await this.fileExists(withExt);
148
- console.log(`[SWITE] trying extension ${ext}: ${withExt}, exists: ${exists}`);
149
- if (exists) {
150
- const url = await this.toUrl(withExt);
151
- console.log(`[SWITE] found with ${ext}: ${url}`);
152
- return url;
122
+ if (await this.fileExists(withExt)) {
123
+ return await this.toUrl(withExt);
153
124
  }
154
125
  }
155
126
  // Try index files
@@ -1,3 +1,3 @@
1
- import { ModuleResolver } from "./resolver.js";
1
+ import { ModuleResolver } from "../resolver.js";
2
2
  export declare function rewriteImports(code: string, importer: string, resolver: ModuleResolver): Promise<string>;
3
3
  //# sourceMappingURL=import-rewriter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-rewriter.d.ts","sourceRoot":"","sources":["../../../src/resolution/rewriting/import-rewriter.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAYhD,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,CAAC,CAqHjB"}
@@ -0,0 +1,199 @@
1
+ /*
2
+ * Import Rewriter for SWITE
3
+ *
4
+ * Design: collect-then-apply-right-to-left
5
+ *
6
+ * es-module-lexer gives positions {s, e} in the ORIGINAL string. The previous
7
+ * implementation tracked a running `offset` as replacements were applied, which
8
+ * accumulated errors when quote handling changed string lengths in unexpected
9
+ * ways and required three layers of fallback replacement logic.
10
+ *
11
+ * Instead we now:
12
+ * 1. Collect every replacement as {start, end, text} in original-string coordinates
13
+ * 2. Sort descending by start position
14
+ * 3. Apply right-to-left — each substitution cannot shift the position of any
15
+ * replacement to its left, so no offset tracking is needed at all.
16
+ */
17
+ import { init, parse } from "es-module-lexer";
18
+ import { ModuleResolver } from "../resolver.js";
19
+ import { promises as fs } from "node:fs";
20
+ import path from "node:path";
21
+ import chalk from "chalk";
22
+ import { shouldUseCdnFallback } from "../cdn/cdn-fallback.js";
23
+ export async function rewriteImports(code, importer, resolver) {
24
+ await init;
25
+ try {
26
+ const [imports] = parse(code);
27
+ if (imports.length === 0)
28
+ return code;
29
+ const replacements = [];
30
+ for (const imp of imports) {
31
+ const { s: rawStart, e: rawEnd } = imp;
32
+ const rawSpecifier = code.slice(rawStart, rawEnd);
33
+ // Skip CSS imports — handled as static assets
34
+ if (rawSpecifier.includes(".css"))
35
+ continue;
36
+ // Determine actual specifier string and the span in `code` that includes quotes
37
+ const { specifier, start, end } = resolveQuotedSpan(code, rawSpecifier, rawStart, rawEnd);
38
+ if (specifier === null)
39
+ continue;
40
+ // Fix compiler bug: .uix/.ui imports emitted as .js or .tsx
41
+ if (specifier.startsWith(".") &&
42
+ (specifier.endsWith(".js") || specifier.endsWith(".tsx")) &&
43
+ !specifier.includes("node_modules")) {
44
+ const newExt = await resolveExtensionFix(specifier, importer);
45
+ if (newExt) {
46
+ const base = specifier.endsWith(".tsx") ? specifier.slice(0, -4) : specifier.slice(0, -3);
47
+ replacements.push({ start, end, text: `"${base}${newExt}"` });
48
+ continue;
49
+ }
50
+ }
51
+ // Skip relative and absolute path imports (already resolved)
52
+ if (specifier.startsWith(".") || specifier.startsWith("/"))
53
+ continue;
54
+ if (!/^[@a-zA-Z]/.test(specifier)) {
55
+ console.warn(`[SWITE] import-rewriter: Invalid specifier format: ${specifier}`);
56
+ continue;
57
+ }
58
+ // Resolve bare import
59
+ let resolved;
60
+ try {
61
+ resolved = await resolver.resolve(specifier, importer);
62
+ if (!resolved || resolved === specifier || (!resolved.startsWith("/") && !resolved.startsWith("http"))) {
63
+ console.warn(chalk.yellow(`[SWITE] import-rewriter: Resolver returned invalid result for ${specifier}, using CDN fallback`));
64
+ resolved = shouldUseCdnFallback(specifier)
65
+ ? `https://cdn.jsdelivr.net/npm/${specifier}/+esm`
66
+ : `/node_modules/${specifier}`;
67
+ }
68
+ }
69
+ catch (error) {
70
+ console.error(chalk.red(`[SWITE] import-rewriter: Error resolving ${specifier}:`), error);
71
+ resolved = shouldUseCdnFallback(specifier)
72
+ ? `https://cdn.jsdelivr.net/npm/${specifier}/+esm`
73
+ : `/node_modules/${specifier}`;
74
+ }
75
+ // Prefer src/ over dist/ for workspace/swiss packages in dev
76
+ if (resolved.includes("/dist/") && (resolved.includes("/swiss-packages/") || resolved.includes("/packages/"))) {
77
+ resolved = resolved.replace("/dist/", "/src/").replace(/\.js$/, ".ts");
78
+ }
79
+ replacements.push({ start, end, text: `"${resolved}"` });
80
+ }
81
+ // Apply right-to-left so earlier positions are never shifted by later replacements
82
+ replacements.sort((a, b) => b.start - a.start);
83
+ let result = code;
84
+ for (const { start, end, text } of replacements) {
85
+ result = result.slice(0, start) + text + result.slice(end);
86
+ }
87
+ // Safety net: catch any bare scoped imports the lexer may have missed
88
+ const barePattern = /(?:import|from|export)\s+['"](@[^'"]+\/[^'"]+)[^'"]*['"]/g;
89
+ for (const match of Array.from(result.matchAll(barePattern))) {
90
+ const bareImport = match[1];
91
+ if (!bareImport.startsWith("/") && !bareImport.startsWith("http") && !bareImport.startsWith(".")) {
92
+ console.error(chalk.red(`[SWITE] import-rewriter: CRITICAL — bare import "${bareImport}" still present after rewriting`));
93
+ const replacement = shouldUseCdnFallback(bareImport)
94
+ ? `https://cdn.jsdelivr.net/npm/${bareImport}/+esm`
95
+ : `/node_modules/${bareImport}`;
96
+ result = result.replace(new RegExp(bareImport.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), replacement);
97
+ }
98
+ }
99
+ // Regex fallback: fix relative .js/.tsx extension mismatches the lexer may have missed
100
+ const normalizedImporter = importer.replace(/\\/g, "/");
101
+ const isSwissPackage = normalizedImporter.includes("/swiss-packages/");
102
+ const isUixFile = normalizedImporter.endsWith(".uix") || normalizedImporter.endsWith(".ui");
103
+ result = result.replace(/from\s+(["'])(\.\.?\/[^"']*?)(\.js|\.tsx)(\1)/g, (match, quote, importPath, _ext, endQuote) => {
104
+ if (importPath.includes("node_modules") || !importPath.startsWith("."))
105
+ return match;
106
+ const isLibPath = normalizedImporter.includes("/lib/");
107
+ let newExt;
108
+ if (isSwissPackage || isLibPath) {
109
+ newExt = ".ts";
110
+ }
111
+ else if (isUixFile) {
112
+ newExt = normalizedImporter.endsWith(".ui") ? ".ui" : ".uix";
113
+ }
114
+ else {
115
+ newExt = ".ts";
116
+ }
117
+ return `from ${quote}${importPath}${newExt}${endQuote}`;
118
+ });
119
+ return result;
120
+ }
121
+ catch (error) {
122
+ console.error(chalk.red(`[SWITE] import-rewriter: Error rewriting imports in ${importer}:`), error);
123
+ return code;
124
+ }
125
+ }
126
+ /**
127
+ * Given a raw specifier token from es-module-lexer, find the full quoted span
128
+ * in `code` (including the surrounding quote characters) and extract the clean
129
+ * specifier string. Returns `{specifier: null}` when the span cannot be found.
130
+ */
131
+ function resolveQuotedSpan(code, rawSpecifier, rawStart, rawEnd) {
132
+ const first = rawSpecifier[0];
133
+ const last = rawSpecifier[rawSpecifier.length - 1];
134
+ // Case 1: lexer returned the specifier WITH surrounding quotes
135
+ if ((first === '"' || first === "'") && first === last) {
136
+ return {
137
+ specifier: rawSpecifier.slice(1, -1),
138
+ start: rawStart,
139
+ end: rawEnd,
140
+ };
141
+ }
142
+ // Case 2: lexer returned the bare specifier; look one char back/forward for quotes
143
+ const charBefore = rawStart > 0 ? code[rawStart - 1] : "";
144
+ const charAfter = rawEnd < code.length ? code[rawEnd] : "";
145
+ if ((charBefore === '"' || charBefore === "'") && charBefore === charAfter) {
146
+ return {
147
+ specifier: rawSpecifier,
148
+ start: rawStart - 1,
149
+ end: rawEnd + 1,
150
+ };
151
+ }
152
+ // Case 3: search nearby for a quoted pattern
153
+ const escaped = rawSpecifier.replace(/[.*+?^${}()|[\]\\-]/g, "\\$&");
154
+ const pattern = new RegExp(`(['"])${escaped}\\1`);
155
+ const match = pattern.exec(code);
156
+ if (match) {
157
+ return {
158
+ specifier: rawSpecifier,
159
+ start: match.index,
160
+ end: match.index + match[0].length,
161
+ };
162
+ }
163
+ console.warn(`[SWITE] import-rewriter: Could not find quotes for specifier: ${rawSpecifier}`);
164
+ return { specifier: null, start: rawStart, end: rawEnd };
165
+ }
166
+ /**
167
+ * Determine what extension a .js/.tsx import should be rewritten to,
168
+ * based on the importer's context and the actual files present on disk.
169
+ * Returns null when no rewrite is needed.
170
+ */
171
+ async function resolveExtensionFix(specifier, importer) {
172
+ const normalizedImporter = importer.replace(/\\/g, "/");
173
+ const isSwissPackage = normalizedImporter.includes("/swiss-packages/");
174
+ const isLibPath = normalizedImporter.includes("/lib/");
175
+ const isUixFile = normalizedImporter.endsWith(".uix") || normalizedImporter.endsWith(".ui");
176
+ if (isSwissPackage || isLibPath)
177
+ return ".ts";
178
+ if (isUixFile) {
179
+ const base = specifier.endsWith(".tsx") ? specifier.slice(0, -4) : specifier.slice(0, -3);
180
+ const currentDir = path.dirname(importer);
181
+ const cleanPath = base.startsWith("./") ? base.slice(2) : base;
182
+ const uiPath = path.resolve(currentDir, cleanPath + ".ui");
183
+ const uixPath = path.resolve(currentDir, cleanPath + ".uix");
184
+ try {
185
+ await fs.access(uiPath);
186
+ return ".ui";
187
+ }
188
+ catch {
189
+ try {
190
+ await fs.access(uixPath);
191
+ return ".uix";
192
+ }
193
+ catch {
194
+ return ".ui";
195
+ }
196
+ }
197
+ }
198
+ return ".ts";
199
+ }
@@ -7,7 +7,7 @@ export declare function buildSymlinkRegistry(nodeModulesDirs: string[]): Promise
7
7
  *
8
8
  * Example:
9
9
  * /mnt/c/.../swiss-lib/packages/core/src/index.ts
10
- * → /node_modules/@kibologic/core/src/index.ts
10
+ * → /node_modules/@swissjs/core/src/index.ts
11
11
  */
12
12
  export declare function lookupInSymlinkRegistry(absolutePath: string): string | null;
13
13
  //# sourceMappingURL=symlink-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symlink-registry.d.ts","sourceRoot":"","sources":["../../src/resolution/symlink-registry.ts"],"names":[],"mappings":"AAoBA,wBAAsB,oBAAoB,CACxC,eAAe,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC,CAQf;AA8DD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW3E"}
@@ -82,7 +82,7 @@ async function registerSymlink(symlinkPath, pkgName) {
82
82
  *
83
83
  * Example:
84
84
  * /mnt/c/.../swiss-lib/packages/core/src/index.ts
85
- * → /node_modules/@kibologic/core/src/index.ts
85
+ * → /node_modules/@swissjs/core/src/index.ts
86
86
  */
87
87
  export function lookupInSymlinkRegistry(absolutePath) {
88
88
  const normalized = absolutePath.replace(/\\/g, "/");
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-resolver.d.ts","sourceRoot":"","sources":["../../src/resolution/url-resolver.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/C,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACpD;AAED,MAAM,MAAM,+BAA+B,GAAG,kBAAkB,CAAC;AAMjE;;GAEG;AACH,wBAAsB,KAAK,CACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA0MjB"}
@@ -4,24 +4,10 @@
4
4
  */
5
5
  import path from "node:path";
6
6
  import { promises as fs } from "node:fs";
7
- import { findSwissLibMonorepo } from "../utils/package-finder.js";
7
+ import { findSwissLibMonorepo } from "../kernel/package-finder.js";
8
8
  import { lookupInSymlinkRegistry } from "./symlink-registry.js";
9
- /**
10
- * Normalize result to ensure no /swiss-lib/ paths leak to browser
11
- */
12
9
  function normalizeResult(result) {
13
- const original = result;
14
- if (result.includes('/swiss-lib/')) {
15
- console.log(`[SWITE] normalizeResult: Found /swiss-lib/ in "${result}", fixing...`);
16
- result = result.replace(/\/swiss-lib\/packages\//g, '/swiss-packages/');
17
- result = result.replace(/\/swiss-lib\//g, '/swiss-packages/');
18
- console.log(`[SWITE] normalizeResult: Fixed to "${result}"`);
19
- }
20
- result = result.replace(/\\/g, '/');
21
- if (original !== result && original.includes('swiss-lib')) {
22
- console.log(`[SWITE] normalizeResult: Final result "${result}" (was "${original}")`);
23
- }
24
- return result;
10
+ return result.replace(/\\/g, '/');
25
11
  }
26
12
  /**
27
13
  * Convert file path to URL for browser
@@ -85,103 +71,72 @@ export async function toUrl(filePath, context) {
85
71
  return normalizeResult(url);
86
72
  }
87
73
  }
88
- // If path is already a URL (starts with / or http), check for source file first
74
+ // If path is already a URL (starts with / or http), prefer src over dist for workspace packages
89
75
  if (normalized.startsWith("/") || normalized.startsWith("http")) {
90
- let workingPath = normalized;
91
- if (normalized.includes("/swiss-lib/packages/")) {
92
- workingPath = normalized.replace(/\/swiss-lib\/packages\//g, "/swiss-packages/");
93
- console.log(`[SWITE] toUrl: Converting /swiss-lib/ to /swiss-packages/: ${normalized} -> ${workingPath}`);
94
- if (workingPath.includes("/dist/") && !workingPath.includes("/src/")) {
95
- const srcPath = workingPath.replace("/dist/", "/src/").replace(/\.js$/, ".ts");
96
- console.log(`[SWITE] toUrl: Checking for source file: ${srcPath}`);
97
- const { resolveFilePath } = await import("../utils/file-path-resolver.js");
98
- const workspaceRoot = await context.getWorkspaceRoot();
99
- const srcFilePath = await resolveFilePath(srcPath, context.root, workspaceRoot);
100
- console.log(`[SWITE] toUrl: Resolved source file path: ${srcFilePath}, exists: ${await context.fileExists(srcFilePath)}`);
101
- if (await context.fileExists(srcFilePath)) {
102
- console.log(`[SWITE] toUrl: Preferring source over dist: ${srcPath}`);
103
- return normalizeResult(srcPath);
104
- }
105
- }
106
- return normalizeResult(workingPath);
107
- }
108
- // Only prefer src over dist for workspace/swiss packages — never for node_modules
109
- // (published packages only ship dist; the swiss-lib monorepo finder can
110
- // accidentally resolve src/index.ts from a sibling repo for npm packages)
111
- if (workingPath.includes("/dist/") && !workingPath.includes("/src/") && !workingPath.includes("/node_modules/")) {
112
- const srcPath = workingPath.replace("/dist/", "/src/").replace(/\.js$/, ".ts");
113
- console.log(`[SWITE] toUrl: Checking for source file: ${srcPath}`);
114
- const { resolveFilePath } = await import("../utils/file-path-resolver.js");
76
+ // Only prefer src over dist for workspace packages — never for node_modules
77
+ if (normalized.includes("/dist/") && !normalized.includes("/src/") && !normalized.includes("/node_modules/")) {
78
+ const srcPath = normalized.replace("/dist/", "/src/").replace(/\.js$/, ".ts");
79
+ const { resolveFilePath } = await import("./path/file-path-resolver.js");
115
80
  const workspaceRoot = await context.getWorkspaceRoot();
116
81
  const srcFilePath = await resolveFilePath(srcPath, context.root, workspaceRoot);
117
- console.log(`[SWITE] toUrl: Resolved source file path: ${srcFilePath}, exists: ${await context.fileExists(srcFilePath)}`);
118
82
  if (await context.fileExists(srcFilePath)) {
119
- console.log(`[SWITE] toUrl: Preferring source over dist: ${srcPath}`);
120
83
  return normalizeResult(srcPath);
121
84
  }
122
85
  }
123
- return normalizeResult(workingPath);
86
+ return normalizeResult(normalized);
124
87
  }
125
- // If path is absolute, try to make it relative to workspace root or server root
88
+ // If path is absolute, convert to a browser-relative URL
126
89
  if (path.isAbsolute(filePath)) {
127
90
  const workspaceRoot = await context.getWorkspaceRoot();
128
- // Check if this is a swiss-lib monorepo package (@kibologic/*)
129
- const swissLib = await findSwissLibMonorepo(context.root);
130
- console.log(`[SWITE] toUrl: swiss-lib check - swissLib=${swissLib}, this.root=${context.root}, filePath=${filePath}`);
131
- if (swissLib) {
132
- const swissPackagesPath = path.join(swissLib, "packages");
133
- let resolvedSwissPackages;
91
+ // Check if the file lives in a co-located framework monorepo's packages/ directory.
92
+ // Serve those files under /swiss-packages/ so the browser can request them distinctly
93
+ // from the app's own files. Works for any framework at any directory name.
94
+ const monorepo = await findSwissLibMonorepo(context.root);
95
+ if (monorepo) {
96
+ const packagesPath = path.join(monorepo, "packages");
97
+ let resolvedPackages;
134
98
  let resolvedFilePath;
135
99
  try {
136
- resolvedSwissPackages = await fs.realpath(swissPackagesPath);
100
+ resolvedPackages = await fs.realpath(packagesPath);
137
101
  resolvedFilePath = await fs.realpath(filePath);
138
102
  }
139
103
  catch {
140
- resolvedSwissPackages = path.resolve(swissPackagesPath);
104
+ resolvedPackages = path.resolve(packagesPath);
141
105
  resolvedFilePath = path.resolve(filePath);
142
106
  }
143
- const normalizedSwissPackages = resolvedSwissPackages.replace(/\\/g, "/").toLowerCase();
144
- const normalizedFilePath = resolvedFilePath.replace(/\\/g, "/").toLowerCase();
145
- console.log(`[SWITE] toUrl: swiss-lib path comparison - normalizedSwissPackages="${normalizedSwissPackages}", normalizedFilePath="${normalizedFilePath}", startsWith=${normalizedFilePath.startsWith(normalizedSwissPackages)}`);
146
- if (normalizedFilePath.startsWith(normalizedSwissPackages)) {
147
- console.log(`[SWITE] toUrl: swiss-lib path MATCHED! Converting to /swiss-packages/`);
148
- const origSwissPackages = resolvedSwissPackages.replace(/\\/g, "/");
149
- const origFilePath = resolvedFilePath.replace(/\\/g, "/");
150
- const relative = origFilePath.slice(origSwissPackages.length);
107
+ const normalizedPackages = resolvedPackages.replace(/\\/g, "/").toLowerCase();
108
+ const normalizedResolved = resolvedFilePath.replace(/\\/g, "/").toLowerCase();
109
+ if (normalizedResolved.startsWith(normalizedPackages)) {
110
+ const origPackages = resolvedPackages.replace(/\\/g, "/");
111
+ const origFile = resolvedFilePath.replace(/\\/g, "/");
112
+ const relative = origFile.slice(origPackages.length);
151
113
  const url = "/swiss-packages" + (relative.startsWith("/") ? relative : "/" + relative);
152
114
  if (url.includes("/dist/") && !url.includes("/src/")) {
153
115
  const srcUrl = url.replace("/dist/", "/src/").replace(/\.js$/, ".ts");
154
116
  const srcRelative = srcUrl.replace("/swiss-packages/", "");
155
- const srcFilePath = path.join(swissPackagesPath, srcRelative);
156
- console.log(`[SWITE] toUrl: Checking source file: ${srcFilePath}, exists: ${await context.fileExists(srcFilePath)}`);
117
+ const srcFilePath = path.join(packagesPath, srcRelative);
157
118
  if (await context.fileExists(srcFilePath)) {
158
- console.log(`[SWITE] toUrl: Preferring source over dist: ${srcUrl}`);
159
119
  return normalizeResult(srcUrl);
160
120
  }
161
121
  }
162
- console.log(`[SWITE] toUrl: ${filePath} -> ${url} (swiss-lib package)`);
163
122
  return normalizeResult(url);
164
123
  }
165
124
  }
166
- // Check if this is a node_modules path
125
+ // node_modules absolute path browser /node_modules/... URL
167
126
  const origFilePathNormalized = path.resolve(filePath).replace(/\\/g, "/");
168
127
  if (origFilePathNormalized.toLowerCase().includes("/node_modules/")) {
169
128
  const nodeModulesIndex = origFilePathNormalized.toLowerCase().indexOf("/node_modules/");
170
129
  const afterNodeModules = origFilePathNormalized.slice(nodeModulesIndex + "/node_modules/".length);
171
- const url = "/node_modules/" + afterNodeModules;
172
- console.log(`[SWITE] toUrl: ${filePath} -> ${url} (node_modules, preserving case)`);
173
- return normalizeResult(url);
130
+ return normalizeResult("/node_modules/" + afterNodeModules);
174
131
  }
175
- // Try relative to app root FIRST
176
- const normalizedRoot = path.resolve(context.root).replace(/\\/g, "/").toLowerCase();
177
132
  const normalizedFilePath = path.resolve(filePath).replace(/\\/g, "/").toLowerCase();
133
+ // Try relative to app root first
134
+ const normalizedRoot = path.resolve(context.root).replace(/\\/g, "/").toLowerCase();
178
135
  if (normalizedFilePath.startsWith(normalizedRoot)) {
179
136
  const origRoot = path.resolve(context.root).replace(/\\/g, "/");
180
137
  const origFilePath = path.resolve(filePath).replace(/\\/g, "/");
181
138
  const relative = origFilePath.slice(origRoot.length);
182
- const url = relative.startsWith("/") ? relative : "/" + relative;
183
- console.log(`[SWITE] toUrl: ${filePath} -> ${url} (app root: ${context.root})`);
184
- return normalizeResult(url);
139
+ return normalizeResult(relative.startsWith("/") ? relative : "/" + relative);
185
140
  }
186
141
  // Try workspace root
187
142
  if (workspaceRoot) {
@@ -189,51 +144,25 @@ export async function toUrl(filePath, context) {
189
144
  if (normalizedFilePath.startsWith(normalizedWorkspaceRoot)) {
190
145
  const origWorkspaceRoot = path.resolve(workspaceRoot).replace(/\\/g, "/");
191
146
  const origFilePath = path.resolve(filePath).replace(/\\/g, "/");
192
- // CRITICAL: Check if this is a swiss-lib path BEFORE computing relative
193
- const normalizedOrigFilePath = origFilePath.toLowerCase();
194
- console.log(`[SWITE] toUrl DEBUG: origFilePath="${origFilePath}", normalized="${normalizedOrigFilePath}", checking for swiss-lib...`);
195
- if (normalizedOrigFilePath.includes("/swiss-lib/packages/") || normalizedOrigFilePath.includes("\\swiss-lib\\packages\\")) {
196
- console.log(`[SWITE] toUrl DEBUG: ✅ Found swiss-lib in path!`);
197
- const swissLibIndex = normalizedOrigFilePath.indexOf("/swiss-lib/packages/");
198
- const swissLibIndexBackslash = normalizedOrigFilePath.indexOf("\\swiss-lib\\packages\\");
199
- const index = swissLibIndex >= 0 ? swissLibIndex : swissLibIndexBackslash;
200
- const separator = swissLibIndex >= 0 ? "/swiss-lib/packages/" : "\\swiss-lib\\packages\\";
201
- const afterSwissLib = origFilePath.slice(index + separator.length);
202
- const url = "/swiss-packages/" + afterSwissLib.replace(/\\/g, "/");
203
- console.log(`[SWITE] toUrl: ${filePath} -> ${url} (swiss-lib via workspace root - FIXED)`);
204
- return normalizeResult(url);
205
- }
206
147
  const relative = origFilePath.slice(origWorkspaceRoot.length);
207
148
  let url = relative.startsWith("/") ? relative : "/" + relative;
208
- // Prefer src over dist for workspace /packages/ in dev (unbuilt packages)
209
- if (url.includes("/packages/") &&
210
- url.includes("/dist/") &&
211
- (await context.fileExists(filePath)) === false) {
212
- const srcPath = filePath.replace(/[/\\]dist[/\\]/, path.sep + "src" + path.sep).replace(/\.js$/i, ".ts");
149
+ // Prefer src over dist for workspace packages in dev
150
+ if (url.includes("/packages/") && url.includes("/dist/") && !(await context.fileExists(filePath))) {
151
+ const srcPath = filePath
152
+ .replace(/[/\\]dist[/\\]/, path.sep + "src" + path.sep)
153
+ .replace(/\.js$/i, ".ts");
213
154
  if (await context.fileExists(srcPath)) {
214
155
  const srcRelative = path.relative(workspaceRoot, srcPath).replace(/\\/g, "/");
215
156
  url = "/" + srcRelative;
216
- console.log(`[SWITE] toUrl: Preferring src over dist (packages): ${url}`);
217
157
  }
218
158
  }
219
- console.log(`[SWITE] toUrl DOUBLE CHECK: url="${url}", lowercase="${url.toLowerCase()}", includes="/swiss-lib/"=${url.toLowerCase().includes("/swiss-lib/")}`);
220
- // DOUBLE CHECK: If computed URL contains /swiss-lib/, fix it
221
- if (url.toLowerCase().includes("/swiss-lib/")) {
222
- console.log(`[SWITE] toUrl DOUBLE CHECK: ✅ MATCHED! Fixing URL...`);
223
- const fixedUrl = url.replace(/\/swiss-lib\/packages\//gi, "/swiss-packages/").replace(/\/swiss-lib\//gi, "/swiss-packages/");
224
- console.log(`[SWITE] toUrl: ${filePath} -> ${fixedUrl} (workspace root - FIXED /swiss-lib/ in URL)`);
225
- return normalizeResult(fixedUrl);
226
- }
227
- console.log(`[SWITE] toUrl: ${filePath} -> ${url} (workspace: ${workspaceRoot})`);
228
159
  return normalizeResult(url);
229
160
  }
230
161
  }
231
162
  // Fallback
232
- const fallbackWorkspaceRoot = await context.getWorkspaceRoot();
233
- const baseRoot = fallbackWorkspaceRoot || context.root;
163
+ const baseRoot = workspaceRoot || context.root;
234
164
  const rawRelative = path.relative(baseRoot, filePath);
235
- // CG-03: path.relative() returns an absolute path when crossing drives or WSL mounts.
236
- // Guard: if the result is absolute or escapes root with '..', strip prefix directly.
165
+ // CG-03: guard against absolute result from path.relative() on cross-drive/WSL paths
237
166
  let url;
238
167
  if (path.isAbsolute(rawRelative) || rawRelative.startsWith("..")) {
239
168
  const normalizedBase = path.resolve(baseRoot).replace(/\\/g, "/");
@@ -246,7 +175,7 @@ export async function toUrl(filePath, context) {
246
175
  else {
247
176
  url = "/" + rawRelative.replace(/\\/g, "/");
248
177
  }
249
- console.warn(`[SWITE] toUrl fallback: ${filePath} -> ${url} (may not work if path goes outside root)`);
178
+ console.warn(`[SWITE] toUrl fallback: ${filePath} -> ${url}`);
250
179
  return normalizeResult(url);
251
180
  }
252
181
  // Default: make relative to root
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-package-resolver.d.ts","sourceRoot":"","sources":["../../src/resolution/workspace-package-resolver.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,+BAA+B;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/C,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACpD;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwExB"}
@@ -0,0 +1,77 @@
1
+ /*
2
+ * Workspace Package Resolver - Finds packages in workspace
3
+ * Uses dynamic package registry instead of hardcoded paths
4
+ */
5
+ import path from "node:path";
6
+ import { findSwissLibMonorepo } from "../kernel/package-finder.js";
7
+ import { getPackageRegistry } from "../kernel/package-registry.js";
8
+ /**
9
+ * Resolve workspace package location
10
+ */
11
+ export async function resolveWorkspacePackage(pkgName, context) {
12
+ // Build the ordered list of roots to scan for workspace packages.
13
+ // Works for any package name/scope — no hardcoded scope guards.
14
+ const workspaceRoots = [];
15
+ let workspaceRoot = await context.getWorkspaceRoot();
16
+ if (workspaceRoot) {
17
+ workspaceRoots.push(workspaceRoot);
18
+ }
19
+ else {
20
+ // Walk up from app root looking for common workspace markers
21
+ for (const candidate of [
22
+ path.join(context.root, ".."),
23
+ path.join(context.root, "..", ".."),
24
+ ]) {
25
+ const normalized = path.resolve(candidate);
26
+ if ((await context.fileExists(path.join(normalized, "pnpm-workspace.yaml"))) ||
27
+ (await context.fileExists(path.join(normalized, "modules"))) ||
28
+ (await context.fileExists(path.join(normalized, "libraries")))) {
29
+ workspaceRoot = normalized;
30
+ workspaceRoots.push(normalized);
31
+ break;
32
+ }
33
+ }
34
+ if (!workspaceRoots.length) {
35
+ workspaceRoots.push(path.join(context.root, "..", ".."));
36
+ }
37
+ }
38
+ // Also include any co-located framework monorepo (any workspace with packages/)
39
+ try {
40
+ const monorepo = await findSwissLibMonorepo(context.root);
41
+ if (monorepo && !workspaceRoots.includes(monorepo)) {
42
+ workspaceRoots.unshift(monorepo);
43
+ }
44
+ if (monorepo) {
45
+ const packagesDir = path.join(monorepo, "packages");
46
+ if (await context.fileExists(packagesDir) && !workspaceRoots.includes(packagesDir)) {
47
+ workspaceRoots.unshift(packagesDir);
48
+ }
49
+ }
50
+ }
51
+ catch {
52
+ // monorepo not found — continue without it
53
+ }
54
+ const registry = getPackageRegistry();
55
+ const primaryRoot = workspaceRoots[0] ?? context.root;
56
+ const additionalRoots = workspaceRoots.slice(1);
57
+ if (!registry.getPackageCount() && primaryRoot) {
58
+ try {
59
+ await registry.scanWorkspace(primaryRoot, additionalRoots);
60
+ }
61
+ catch (error) {
62
+ console.error(`[SWITE] Error scanning package registry:`, error.message);
63
+ }
64
+ }
65
+ let packageInfo = registry.findPackage(pkgName);
66
+ if (packageInfo) {
67
+ return packageInfo.path;
68
+ }
69
+ // Rescan in case the package was added after the initial scan
70
+ await registry.rescan();
71
+ packageInfo = registry.findPackage(pkgName);
72
+ if (packageInfo) {
73
+ return packageInfo.path;
74
+ }
75
+ console.log(`[SWITE] Package ${pkgName} not found in workspace (scanned ${registry.getPackageCount()} packages)`);
76
+ return null;
77
+ }