@swissjs/swite 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/config.json +11 -0
- package/.github/workflows/ci.yml +59 -0
- package/.github/workflows/publish.yml +50 -0
- package/.github/workflows/release.yml +53 -0
- package/BUILD_ANALYSIS.md +89 -0
- package/BUILD_STRATEGY.md +75 -0
- package/CHANGELOG.md +53 -0
- package/DIRECTIVE.md +488 -0
- package/__tests__/css-extraction.test.ts +261 -0
- package/__tests__/css-injection-integration.test.ts +247 -0
- package/__tests__/css-middleware.test.ts +191 -0
- package/__tests__/import-rewriter-bug.test.ts +135 -0
- package/dist/builder.d.ts +36 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +772 -0
- package/dist/cache/compilation-cache.d.ts +33 -0
- package/dist/cache/compilation-cache.d.ts.map +1 -0
- package/dist/cache/compilation-cache.js +130 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +85 -0
- package/dist/config-loader.d.ts +8 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/config-loader.js +40 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +7 -0
- package/dist/dev/pythonDevManager.d.ts +12 -0
- package/dist/dev/pythonDevManager.d.ts.map +1 -0
- package/dist/dev/pythonDevManager.js +85 -0
- package/dist/env.d.ts +19 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +112 -0
- package/dist/handlers/base-handler.d.ts +21 -0
- package/dist/handlers/base-handler.d.ts.map +1 -0
- package/dist/handlers/base-handler.js +38 -0
- package/dist/handlers/js-handler.d.ts +10 -0
- package/dist/handlers/js-handler.d.ts.map +1 -0
- package/dist/handlers/js-handler.js +87 -0
- package/dist/handlers/mjs-handler.d.ts +8 -0
- package/dist/handlers/mjs-handler.d.ts.map +1 -0
- package/dist/handlers/mjs-handler.js +44 -0
- package/dist/handlers/node-module-handler.d.ts +16 -0
- package/dist/handlers/node-module-handler.d.ts.map +1 -0
- package/dist/handlers/node-module-handler.js +267 -0
- package/dist/handlers/ts-handler.d.ts +11 -0
- package/dist/handlers/ts-handler.d.ts.map +1 -0
- package/dist/handlers/ts-handler.js +120 -0
- package/dist/handlers/ui-handler.d.ts +12 -0
- package/dist/handlers/ui-handler.d.ts.map +1 -0
- package/dist/handlers/ui-handler.js +182 -0
- package/dist/handlers/uix-handler.d.ts +12 -0
- package/dist/handlers/uix-handler.d.ts.map +1 -0
- package/dist/handlers/uix-handler.js +135 -0
- package/dist/hmr.d.ts +20 -0
- package/dist/hmr.d.ts.map +1 -0
- package/dist/hmr.js +265 -0
- package/dist/import-rewriter.d.ts +3 -0
- package/dist/import-rewriter.d.ts.map +1 -0
- package/dist/import-rewriter.js +351 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/middleware/hmr-routes.d.ts +12 -0
- package/dist/middleware/hmr-routes.d.ts.map +1 -0
- package/dist/middleware/hmr-routes.js +97 -0
- package/dist/middleware/middleware-setup.d.ts +23 -0
- package/dist/middleware/middleware-setup.d.ts.map +1 -0
- package/dist/middleware/middleware-setup.js +596 -0
- package/dist/middleware/static-files.d.ts +15 -0
- package/dist/middleware/static-files.d.ts.map +1 -0
- package/dist/middleware/static-files.js +585 -0
- package/dist/proxy/SwiteProxyError.d.ts +6 -0
- package/dist/proxy/SwiteProxyError.d.ts.map +1 -0
- package/dist/proxy/SwiteProxyError.js +9 -0
- package/dist/proxy/proxyToPython.d.ts +28 -0
- package/dist/proxy/proxyToPython.d.ts.map +1 -0
- package/dist/proxy/proxyToPython.js +66 -0
- package/dist/resolver/bare-import-resolver.d.ts +9 -0
- package/dist/resolver/bare-import-resolver.d.ts.map +1 -0
- package/dist/resolver/bare-import-resolver.js +363 -0
- package/dist/resolver/symlink-registry.d.ts +13 -0
- package/dist/resolver/symlink-registry.d.ts.map +1 -0
- package/dist/resolver/symlink-registry.js +98 -0
- package/dist/resolver/url-resolver.d.ts +11 -0
- package/dist/resolver/url-resolver.d.ts.map +1 -0
- package/dist/resolver/url-resolver.js +268 -0
- package/dist/resolver/workspace-package-resolver.d.ts +10 -0
- package/dist/resolver/workspace-package-resolver.d.ts.map +1 -0
- package/dist/resolver/workspace-package-resolver.js +185 -0
- package/dist/resolver.d.ts +17 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +191 -0
- package/dist/router/file-router.d.ts +19 -0
- package/dist/router/file-router.d.ts.map +1 -0
- package/dist/router/file-router.js +114 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +122 -0
- package/dist/utils/cdn-fallback.d.ts +14 -0
- package/dist/utils/cdn-fallback.d.ts.map +1 -0
- package/dist/utils/cdn-fallback.js +36 -0
- package/dist/utils/file-path-resolver.d.ts +9 -0
- package/dist/utils/file-path-resolver.d.ts.map +1 -0
- package/dist/utils/file-path-resolver.js +187 -0
- package/dist/utils/generate-import-map-cli.d.ts +3 -0
- package/dist/utils/generate-import-map-cli.d.ts.map +1 -0
- package/dist/utils/generate-import-map-cli.js +32 -0
- package/dist/utils/generate-import-map.d.ts +21 -0
- package/dist/utils/generate-import-map.d.ts.map +1 -0
- package/dist/utils/generate-import-map.js +119 -0
- package/dist/utils/package-finder.d.ts +24 -0
- package/dist/utils/package-finder.d.ts.map +1 -0
- package/dist/utils/package-finder.js +161 -0
- package/dist/utils/package-registry.d.ts +36 -0
- package/dist/utils/package-registry.d.ts.map +1 -0
- package/dist/utils/package-registry.js +159 -0
- package/dist/utils/workspace.d.ts +6 -0
- package/dist/utils/workspace.d.ts.map +1 -0
- package/dist/utils/workspace.js +65 -0
- package/docs/IMPORT_REWRITING.md +164 -0
- package/docs/IMPORT_REWRITING_TROUBLESHOOTING.md +139 -0
- package/docs/PATH_RESOLUTION_GUIDE.md +221 -0
- package/package.json +49 -0
- package/src/adapters/proxy/SwiteProxyError.ts +12 -0
- package/src/adapters/proxy/proxyToPython.ts +88 -0
- package/src/build-engine/builder.ts +960 -0
- package/src/cli.ts +109 -0
- package/src/config/config-loader.ts +46 -0
- package/src/config/config.ts +34 -0
- package/src/config/env.ts +98 -0
- package/src/dev-engine/handlers/base-handler.ts +68 -0
- package/src/dev-engine/handlers/js-handler.ts +134 -0
- package/src/dev-engine/handlers/mjs-handler.ts +65 -0
- package/src/dev-engine/handlers/node-module-handler.ts +339 -0
- package/src/dev-engine/handlers/ts-handler.ts +143 -0
- package/src/dev-engine/handlers/ui-handler.ts +105 -0
- package/src/dev-engine/handlers/uix-handler.ts +90 -0
- package/src/dev-engine/hmr/hmr-client-template.ts +122 -0
- package/src/dev-engine/hmr/hmr.ts +173 -0
- package/src/dev-engine/middleware/hmr-routes.ts +120 -0
- package/src/dev-engine/middleware/middleware-setup.ts +351 -0
- package/src/dev-engine/middleware/static-files.ts +728 -0
- package/src/dev-engine/pythonDevManager.ts +116 -0
- package/src/dev-engine/router/file-router.ts +164 -0
- package/src/dev-engine/server.ts +152 -0
- package/src/index.ts +26 -0
- package/src/internal/cache/compilation-cache.ts +182 -0
- package/src/internal/generate-import-map-cli.ts +40 -0
- package/src/internal/generate-import-map.ts +154 -0
- package/src/kernel/package-finder.ts +164 -0
- package/src/kernel/package-registry.ts +198 -0
- package/src/kernel/workspace.ts +62 -0
- package/src/resolution/bare-import-resolver.ts +400 -0
- package/src/resolution/cdn/cdn-fallback.ts +37 -0
- package/src/resolution/path/file-path-resolver.ts +190 -0
- package/src/resolution/path/path-fixup.ts +19 -0
- package/src/resolution/resolver.ts +198 -0
- package/src/resolution/rewriting/import-rewriter.ts +237 -0
- package/src/resolution/symlink-registry.ts +114 -0
- package/src/resolution/url-resolver.ts +231 -0
- package/src/resolution/workspace-package-resolver.ts +94 -0
- package/tsconfig.json +37 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { SwiteProxyError } from "./SwiteProxyError.js";
|
|
2
|
+
let _pythonConfig = null;
|
|
3
|
+
let _productionMode = false;
|
|
4
|
+
/**
|
|
5
|
+
* Called by swite start on startup.
|
|
6
|
+
* Disables localhost fallback — PYTHON_SERVICE_URL is the only valid base URL.
|
|
7
|
+
*/
|
|
8
|
+
export function setProductionMode() {
|
|
9
|
+
_productionMode = true;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Called by the swite dev process manager (S-03) on startup.
|
|
13
|
+
* Stores the resolved python service config for use by proxyToPython.
|
|
14
|
+
*/
|
|
15
|
+
export function initPythonProxy(config) {
|
|
16
|
+
_pythonConfig = config;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Proxy a request from the Node server to the internal Python service.
|
|
20
|
+
*
|
|
21
|
+
* Resolves base URL from PYTHON_SERVICE_URL env var if set,
|
|
22
|
+
* otherwise falls back to http://localhost:{python.port} from config.
|
|
23
|
+
*
|
|
24
|
+
* Always injects X-Internal-Token header.
|
|
25
|
+
* Throws SwiteProxyError on non-2xx responses.
|
|
26
|
+
*/
|
|
27
|
+
export async function proxyToPython(options) {
|
|
28
|
+
const envBaseUrl = process.env["PYTHON_SERVICE_URL"];
|
|
29
|
+
let baseUrl;
|
|
30
|
+
if (envBaseUrl) {
|
|
31
|
+
baseUrl = envBaseUrl.replace(/\/$/, "");
|
|
32
|
+
}
|
|
33
|
+
else if (!_productionMode && _pythonConfig) {
|
|
34
|
+
baseUrl = `http://localhost:${_pythonConfig.port}`;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
throw new Error(_productionMode
|
|
38
|
+
? "PYTHON_SERVICE_URL is required in production mode but is not set."
|
|
39
|
+
: "Python service not configured. Call initPythonProxy() before using proxyToPython, or set PYTHON_SERVICE_URL.");
|
|
40
|
+
}
|
|
41
|
+
const token = process.env["INTERNAL_API_TOKEN"] ?? "";
|
|
42
|
+
const url = `${baseUrl}${options.path}`;
|
|
43
|
+
const requestHeaders = {
|
|
44
|
+
"X-Internal-Token": token,
|
|
45
|
+
...options.headers,
|
|
46
|
+
};
|
|
47
|
+
if (options.body !== undefined) {
|
|
48
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
49
|
+
}
|
|
50
|
+
const response = await fetch(url, {
|
|
51
|
+
method: options.method,
|
|
52
|
+
headers: requestHeaders,
|
|
53
|
+
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
let responseBody;
|
|
57
|
+
try {
|
|
58
|
+
responseBody = await response.json();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
responseBody = await response.text();
|
|
62
|
+
}
|
|
63
|
+
throw new SwiteProxyError(response.status, `Python service responded with ${response.status} on ${options.method} ${options.path}`, responseBody);
|
|
64
|
+
}
|
|
65
|
+
return response.json();
|
|
66
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { UrlResolverContext } from "./url-resolver.js";
|
|
2
|
+
export interface BareImportResolverContext extends UrlResolverContext {
|
|
3
|
+
resolveWorkspacePackage: (pkgName: string) => Promise<string | null>;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolve bare import specifier (e.g., @kibologic/core, react, etc.)
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveBareImport(specifier: string, context: BareImportResolverContext): Promise<string>;
|
|
9
|
+
//# sourceMappingURL=bare-import-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bare-import-resolver.d.ts","sourceRoot":"","sources":["../../src/resolver/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,CAqQjB"}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Bare Import Resolver - Resolves bare module specifiers (@kibologic/core, etc.)
|
|
3
|
+
* Extracted from resolver.ts for modularity
|
|
4
|
+
*/
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { promises as fs } from "node:fs";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { findSwissLibMonorepo } from "../utils/package-finder.js";
|
|
9
|
+
import { shouldUseCdnFallback } from "../utils/cdn-fallback.js";
|
|
10
|
+
import { resolveWorkspacePackage } from "./workspace-package-resolver.js";
|
|
11
|
+
import { toUrl } from "./url-resolver.js";
|
|
12
|
+
/**
|
|
13
|
+
* Resolve bare import specifier (e.g., @kibologic/core, react, etc.)
|
|
14
|
+
*/
|
|
15
|
+
export async function resolveBareImport(specifier, context) {
|
|
16
|
+
console.log(`[SWITE] resolveBareImport CALLED: ${specifier}`);
|
|
17
|
+
// Extract package name outside the try/catch so fallback logic can reference it.
|
|
18
|
+
// This must stay project-agnostic: works for both scoped and unscoped packages.
|
|
19
|
+
const parts = specifier.split("/");
|
|
20
|
+
const isScoped = specifier.startsWith("@");
|
|
21
|
+
const pkgName = isScoped ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
22
|
+
const subPath = isScoped ? parts.slice(2).join("/") : parts.slice(1).join("/");
|
|
23
|
+
try {
|
|
24
|
+
console.log(`[SWITE] resolveBareImport: ${specifier} -> pkgName: ${pkgName}, subPath: ${subPath}`);
|
|
25
|
+
// Find package.json - check multiple node_modules locations
|
|
26
|
+
let pkgDir = null;
|
|
27
|
+
let pkgJsonPath = null;
|
|
28
|
+
const nodeModulesLocations = [
|
|
29
|
+
path.join(context.root, "node_modules"),
|
|
30
|
+
path.join(path.dirname(context.root), "node_modules"),
|
|
31
|
+
];
|
|
32
|
+
// Add workspace root node_modules
|
|
33
|
+
const workspaceRoot = await context.getWorkspaceRoot();
|
|
34
|
+
if (workspaceRoot) {
|
|
35
|
+
nodeModulesLocations.push(path.join(workspaceRoot, "node_modules"));
|
|
36
|
+
}
|
|
37
|
+
// Add swiss-lib monorepo node_modules
|
|
38
|
+
const swissLib = await findSwissLibMonorepo(context.root);
|
|
39
|
+
if (swissLib) {
|
|
40
|
+
const swissNodeModules = path.join(swissLib, "node_modules");
|
|
41
|
+
if (await context.fileExists(swissNodeModules)) {
|
|
42
|
+
nodeModulesLocations.push(swissNodeModules);
|
|
43
|
+
console.log(`[SWITE] Added swiss-lib monorepo node_modules for ${pkgName}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Try each location
|
|
47
|
+
for (const nodeModulesPath of nodeModulesLocations) {
|
|
48
|
+
const testPkgDir = path.join(nodeModulesPath, pkgName);
|
|
49
|
+
const testPkgJsonPath = path.join(testPkgDir, "package.json");
|
|
50
|
+
if (await context.fileExists(testPkgJsonPath)) {
|
|
51
|
+
pkgDir = testPkgDir;
|
|
52
|
+
pkgJsonPath = testPkgJsonPath;
|
|
53
|
+
console.log(`[SWITE] Found ${pkgName} in ${nodeModulesPath}`);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!pkgJsonPath || !pkgDir) {
|
|
58
|
+
console.log(`[SWITE] Package ${pkgName} not in node_modules, checking workspace...`);
|
|
59
|
+
// EMERGENCY FIX: For @kibologic/* packages, try direct path first
|
|
60
|
+
if (pkgName.startsWith('@kibologic/')) {
|
|
61
|
+
const pkgShortName = pkgName.replace('@kibologic/', '');
|
|
62
|
+
// Try relative path from SWS to swiss-lib
|
|
63
|
+
const potentialPaths = [
|
|
64
|
+
path.join(context.root, '../swiss-lib/packages', pkgShortName),
|
|
65
|
+
path.join(context.root, '../../swiss-lib/packages', pkgShortName),
|
|
66
|
+
path.join(context.root, '../../../swiss-lib/packages', pkgShortName),
|
|
67
|
+
];
|
|
68
|
+
for (const candidatePath of potentialPaths) {
|
|
69
|
+
const candidatePkgJson = path.join(candidatePath, 'package.json');
|
|
70
|
+
if (await context.fileExists(candidatePkgJson)) {
|
|
71
|
+
console.log(`[SWITE] Emergency: Found ${pkgName} at ${candidatePath}`);
|
|
72
|
+
pkgDir = candidatePath;
|
|
73
|
+
pkgJsonPath = candidatePkgJson;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// If still not found, try workspace resolver
|
|
79
|
+
if (!pkgJsonPath || !pkgDir) {
|
|
80
|
+
const workspacePkg = await context.resolveWorkspacePackage(pkgName);
|
|
81
|
+
if (workspacePkg) {
|
|
82
|
+
return await resolveWorkspacePackageEntry(workspacePkg, pkgName, subPath, specifier, context);
|
|
83
|
+
}
|
|
84
|
+
// Last resort: CDN fallback
|
|
85
|
+
if (!shouldUseCdnFallback(pkgName)) {
|
|
86
|
+
// Scoped packages may be private and are not necessarily available on public npm CDNs.
|
|
87
|
+
console.warn(`[SWITE] Package ${pkgName} not found anywhere. Scoped package detected; CDN fallback is disabled by default.`);
|
|
88
|
+
// Return a same-origin node_modules URL to make failures explicit and allow the
|
|
89
|
+
// node-module handler to try serving it.
|
|
90
|
+
return `/node_modules/${specifier}`;
|
|
91
|
+
}
|
|
92
|
+
console.warn(`[SWITE] Package ${pkgName} not found anywhere, using CDN fallback`);
|
|
93
|
+
return `https://cdn.jsdelivr.net/npm/${specifier}/+esm`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Continue with normal resolution if we found it
|
|
97
|
+
if (pkgJsonPath && pkgDir) {
|
|
98
|
+
return await resolveWorkspacePackageEntry(pkgDir, pkgName, subPath, specifier, context);
|
|
99
|
+
}
|
|
100
|
+
// Check if this is a workspace package (symlinked)
|
|
101
|
+
let realPkgDir;
|
|
102
|
+
try {
|
|
103
|
+
realPkgDir = await fs.realpath(pkgDir);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
realPkgDir = pkgDir;
|
|
107
|
+
}
|
|
108
|
+
const workspacePkg = await context.resolveWorkspacePackage(pkgName);
|
|
109
|
+
console.log(`[SWITE] resolveBareImport (node_modules): workspacePkg=${workspacePkg}, realPkgDir=${realPkgDir}, pkgName=${pkgName}`);
|
|
110
|
+
if (workspacePkg) {
|
|
111
|
+
const normalizedWorkspacePkg = path
|
|
112
|
+
.resolve(workspacePkg)
|
|
113
|
+
.replace(/\\/g, "/")
|
|
114
|
+
.toLowerCase();
|
|
115
|
+
const normalizedRealPkgDir = path
|
|
116
|
+
.resolve(realPkgDir)
|
|
117
|
+
.replace(/\\/g, "/")
|
|
118
|
+
.toLowerCase();
|
|
119
|
+
// If the real path is the workspace package, use workspace resolution
|
|
120
|
+
if (normalizedRealPkgDir === normalizedWorkspacePkg ||
|
|
121
|
+
normalizedRealPkgDir.includes(normalizedWorkspacePkg)) {
|
|
122
|
+
console.log(`[SWITE] resolveBareImport (node_modules): Using workspace resolution for ${pkgName}`);
|
|
123
|
+
return await resolveWorkspacePackageEntry(workspacePkg, pkgName, subPath, specifier, context);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Read package.json for exports/main resolution
|
|
127
|
+
const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"));
|
|
128
|
+
// Handle exports field if present
|
|
129
|
+
if (pkgJson.exports) {
|
|
130
|
+
const exportKey = subPath ? `./${subPath}` : ".";
|
|
131
|
+
const exportEntry = pkgJson.exports[exportKey];
|
|
132
|
+
if (exportEntry) {
|
|
133
|
+
let entryPoint;
|
|
134
|
+
if (typeof exportEntry === "string") {
|
|
135
|
+
entryPoint = exportEntry;
|
|
136
|
+
}
|
|
137
|
+
else if (exportEntry.import) {
|
|
138
|
+
entryPoint = exportEntry.import;
|
|
139
|
+
}
|
|
140
|
+
else if (exportEntry.default) {
|
|
141
|
+
entryPoint = exportEntry.default;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
entryPoint = exportEntry;
|
|
145
|
+
}
|
|
146
|
+
const fullPath = path.join(pkgDir, entryPoint);
|
|
147
|
+
// Prefer source over dist
|
|
148
|
+
const normalizedFullPath = fullPath.replace(/\\/g, "/");
|
|
149
|
+
if (normalizedFullPath.includes("/dist/")) {
|
|
150
|
+
const srcFullPath = normalizedFullPath
|
|
151
|
+
.replace("/dist/", "/src/")
|
|
152
|
+
.replace(/\.js$/, ".ts");
|
|
153
|
+
if (await context.fileExists(srcFullPath)) {
|
|
154
|
+
console.log(`[SWITE] resolveBareImport: Using source file instead of dist: ${srcFullPath}`);
|
|
155
|
+
return await toUrl(srcFullPath, context);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (await context.fileExists(fullPath)) {
|
|
159
|
+
return await toUrl(fullPath, context);
|
|
160
|
+
}
|
|
161
|
+
// Try extensions
|
|
162
|
+
for (const ext of [".js", ".mjs", ".ts", ".ui", ".uix"]) {
|
|
163
|
+
const withExt = fullPath.replace(/\.(js|mjs|ts|ui|uix)$/, ext);
|
|
164
|
+
if (await context.fileExists(withExt)) {
|
|
165
|
+
return await toUrl(withExt, context);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Try case-insensitive match
|
|
169
|
+
const dir = path.dirname(fullPath);
|
|
170
|
+
const fileName = path.basename(fullPath);
|
|
171
|
+
try {
|
|
172
|
+
const files = await fs.readdir(dir);
|
|
173
|
+
const caseInsensitiveMatch = files.find((f) => f.toLowerCase() === fileName.toLowerCase());
|
|
174
|
+
if (caseInsensitiveMatch) {
|
|
175
|
+
const correctedPath = path.join(dir, caseInsensitiveMatch);
|
|
176
|
+
if (await context.fileExists(correctedPath)) {
|
|
177
|
+
console.log(chalk.yellow(`[SWITE] Case-insensitive match for ${pkgName}: ${fileName} -> ${caseInsensitiveMatch}`));
|
|
178
|
+
return await toUrl(correctedPath, context);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Directory doesn't exist, continue to fallback
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Determine entry point
|
|
188
|
+
let entryPoint;
|
|
189
|
+
if (subPath) {
|
|
190
|
+
entryPoint = subPath;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
entryPoint = pkgJson.module || pkgJson.main || "index.js";
|
|
194
|
+
}
|
|
195
|
+
const fullPath = path.join(pkgDir, entryPoint);
|
|
196
|
+
// Try the exact path first
|
|
197
|
+
if (await context.fileExists(fullPath)) {
|
|
198
|
+
return await toUrl(fullPath, context);
|
|
199
|
+
}
|
|
200
|
+
// Try with extensions
|
|
201
|
+
for (const ext of [".js", ".mjs", ".ts", ".ui", ".uix"]) {
|
|
202
|
+
if (await context.fileExists(fullPath + ext)) {
|
|
203
|
+
return await toUrl(fullPath + ext, context);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Fallback to CDN (jsDelivr; esm.sh returns 500 for some packages) when allowed.
|
|
207
|
+
console.warn(`[SWITE] Could not resolve ${specifier}, using fallback`);
|
|
208
|
+
return shouldUseCdnFallback(pkgName)
|
|
209
|
+
? `https://cdn.jsdelivr.net/npm/${specifier}/+esm`
|
|
210
|
+
: `/node_modules/${specifier}`;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
console.warn(`[SWITE] Error resolving ${specifier}:`, error);
|
|
214
|
+
return shouldUseCdnFallback(pkgName)
|
|
215
|
+
? `https://cdn.jsdelivr.net/npm/${specifier}/+esm`
|
|
216
|
+
: `/node_modules/${specifier}`;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Resolve entry point for workspace package
|
|
221
|
+
*/
|
|
222
|
+
async function resolveWorkspacePackageEntry(workspacePkg, pkgName, subPath, specifier, context) {
|
|
223
|
+
const workspacePkgJson = JSON.parse(await fs.readFile(path.join(workspacePkg, "package.json"), "utf-8"));
|
|
224
|
+
// Handle exports field if present
|
|
225
|
+
if (workspacePkgJson.exports) {
|
|
226
|
+
const subPathWithoutExt = subPath
|
|
227
|
+
? subPath.replace(/\.(js|ts|ui|uix)$/, "")
|
|
228
|
+
: "";
|
|
229
|
+
let exportKey = subPathWithoutExt ? `./${subPathWithoutExt}` : ".";
|
|
230
|
+
console.log(`[SWITE] resolveBareImport (workspace): pkgName=${pkgName}, subPath=${subPath}, subPathWithoutExt=${subPathWithoutExt}, exportKey=${exportKey}`);
|
|
231
|
+
// Try to find matching export
|
|
232
|
+
if (subPath && !workspacePkgJson.exports[exportKey]) {
|
|
233
|
+
if (subPathWithoutExt) {
|
|
234
|
+
const withoutExtKey = `./${subPathWithoutExt}`;
|
|
235
|
+
if (workspacePkgJson.exports[withoutExtKey]) {
|
|
236
|
+
exportKey = withoutExtKey;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (!workspacePkgJson.exports[exportKey]) {
|
|
240
|
+
const subPathParts = subPathWithoutExt.split("/");
|
|
241
|
+
if (subPathParts.length > 1) {
|
|
242
|
+
const dirPath = subPathParts.slice(0, -1).join("/");
|
|
243
|
+
const dirExportKey = `./${dirPath}`;
|
|
244
|
+
if (workspacePkgJson.exports[dirExportKey]) {
|
|
245
|
+
exportKey = dirExportKey;
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
const firstDir = subPathParts[0];
|
|
249
|
+
const firstDirExportKey = `./${firstDir}`;
|
|
250
|
+
if (workspacePkgJson.exports[firstDirExportKey]) {
|
|
251
|
+
exportKey = firstDirExportKey;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
const lastDir = subPathParts[subPathParts.length - 2];
|
|
255
|
+
const lastDirExportKey = `./${lastDir}`;
|
|
256
|
+
if (workspacePkgJson.exports[lastDirExportKey]) {
|
|
257
|
+
exportKey = lastDirExportKey;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const exportEntry = workspacePkgJson.exports[exportKey];
|
|
265
|
+
if (exportEntry) {
|
|
266
|
+
let entryPoint;
|
|
267
|
+
if (typeof exportEntry === "string") {
|
|
268
|
+
entryPoint = exportEntry;
|
|
269
|
+
}
|
|
270
|
+
else if (exportEntry.import) {
|
|
271
|
+
entryPoint = exportEntry.import;
|
|
272
|
+
}
|
|
273
|
+
else if (exportEntry.default) {
|
|
274
|
+
entryPoint = exportEntry.default;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
entryPoint = exportEntry;
|
|
278
|
+
}
|
|
279
|
+
const normalizedEntryPoint = entryPoint.startsWith("./")
|
|
280
|
+
? entryPoint.slice(2)
|
|
281
|
+
: entryPoint;
|
|
282
|
+
const fullPath = path.join(workspacePkg, normalizedEntryPoint);
|
|
283
|
+
// Dev: prefer src over dist for workspace packages (unbuilt or dev mode)
|
|
284
|
+
const normalizedFull = fullPath.replace(/\\/g, "/");
|
|
285
|
+
if (normalizedFull.includes("/dist/")) {
|
|
286
|
+
const srcPath = fullPath.replace(/[/\\]dist[/\\]/, path.sep + "src" + path.sep).replace(/\.js$/i, ".ts");
|
|
287
|
+
if (await context.fileExists(srcPath)) {
|
|
288
|
+
console.log(`[SWITE] resolveBareImport (workspace): preferring src over dist: ${srcPath}`);
|
|
289
|
+
return await toUrl(srcPath, context);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (await context.fileExists(fullPath)) {
|
|
293
|
+
return await toUrl(fullPath, context);
|
|
294
|
+
}
|
|
295
|
+
// Try extensions
|
|
296
|
+
for (const ext of [".ui", ".uix", ".ts", ".js"]) {
|
|
297
|
+
const withExt = fullPath.replace(/\.(js|ts|ui|uix)$/, ext);
|
|
298
|
+
if (await context.fileExists(withExt)) {
|
|
299
|
+
return await toUrl(withExt, context);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Fallback to old logic
|
|
305
|
+
let entryPoint;
|
|
306
|
+
if (subPath) {
|
|
307
|
+
entryPoint = subPath;
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
entryPoint =
|
|
311
|
+
workspacePkgJson.module || workspacePkgJson.main || "index.js";
|
|
312
|
+
}
|
|
313
|
+
const fullPath = path.join(workspacePkg, entryPoint);
|
|
314
|
+
// Dev: prefer src over dist for workspace packages
|
|
315
|
+
const normalizedFull = fullPath.replace(/\\/g, "/");
|
|
316
|
+
if (normalizedFull.includes("/dist/")) {
|
|
317
|
+
const srcPath = fullPath.replace(/[/\\]dist[/\\]/, path.sep + "src" + path.sep).replace(/\.js$/i, ".ts");
|
|
318
|
+
if (await context.fileExists(srcPath)) {
|
|
319
|
+
console.log(`[SWITE] resolveBareImport (fallback): preferring src over dist: ${srcPath}`);
|
|
320
|
+
return await toUrl(srcPath, context);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (await context.fileExists(fullPath)) {
|
|
324
|
+
return await toUrl(fullPath, context);
|
|
325
|
+
}
|
|
326
|
+
// Try extensions
|
|
327
|
+
const ext = path.extname(entryPoint);
|
|
328
|
+
if (ext) {
|
|
329
|
+
const basePath = fullPath.slice(0, -ext.length);
|
|
330
|
+
for (const tryExt of [".js", ".mjs", ".ts", ".ui", ".uix"]) {
|
|
331
|
+
if (await context.fileExists(basePath + tryExt)) {
|
|
332
|
+
return await toUrl(basePath + tryExt, context);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
for (const tryExt of [".js", ".mjs", ".ts", ".ui", ".uix"]) {
|
|
338
|
+
if (await context.fileExists(fullPath + tryExt)) {
|
|
339
|
+
return await toUrl(fullPath + tryExt, context);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Try index files
|
|
344
|
+
for (const tryExt of [".js", ".ts", ".ui", ".uix"]) {
|
|
345
|
+
const indexFile = path.join(fullPath, `index${tryExt}`);
|
|
346
|
+
if (await context.fileExists(indexFile)) {
|
|
347
|
+
return await toUrl(indexFile, context);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Try src/ directory
|
|
351
|
+
const srcDir = path.join(workspacePkg, "src");
|
|
352
|
+
for (const ext of [".ts", ".ui", ".uix", ".js"]) {
|
|
353
|
+
const srcIndex = path.join(srcDir, `index${ext}`);
|
|
354
|
+
if (await context.fileExists(srcIndex)) {
|
|
355
|
+
console.log(`[SWITE] Found unbuilt workspace package ${pkgName} at ${srcIndex}`);
|
|
356
|
+
return await toUrl(srcIndex, context);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
console.warn(`[SWITE] Entry point not found for ${pkgName} at ${fullPath}, using fallback`);
|
|
360
|
+
return shouldUseCdnFallback(pkgName)
|
|
361
|
+
? `https://cdn.jsdelivr.net/npm/${specifier}/+esm`
|
|
362
|
+
: `/node_modules/${specifier}`;
|
|
363
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function buildSymlinkRegistry(nodeModulesDirs: string[]): Promise<void>;
|
|
2
|
+
/**
|
|
3
|
+
* Look up an absolute filesystem path in the symlink registry.
|
|
4
|
+
*
|
|
5
|
+
* Returns the browser URL if absolutePath is, or is within, a package whose
|
|
6
|
+
* realpath was registered at startup. Returns null if not found.
|
|
7
|
+
*
|
|
8
|
+
* Example:
|
|
9
|
+
* /mnt/c/.../swiss-lib/packages/core/src/index.ts
|
|
10
|
+
* → /node_modules/@kibologic/core/src/index.ts
|
|
11
|
+
*/
|
|
12
|
+
export declare function lookupInSymlinkRegistry(absolutePath: string): string | null;
|
|
13
|
+
//# sourceMappingURL=symlink-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symlink-registry.d.ts","sourceRoot":"","sources":["../../src/resolver/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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Symlink Registry - CG-03 root cause fix
|
|
3
|
+
*
|
|
4
|
+
* fs.realpath() throughout Swite's handler chain resolves symlinks to absolute
|
|
5
|
+
* filesystem paths (e.g. /mnt/c/.../swiss-lib/packages/core/src/index.ts).
|
|
6
|
+
* These leak into toUrl() and hit the startsWith("/") early-return before the
|
|
7
|
+
* proper node_modules/swiss-lib handling logic is reached.
|
|
8
|
+
*
|
|
9
|
+
* At server startup we scan node_modules directories for symlinks and build a
|
|
10
|
+
* map: realpath → /node_modules/<pkg-name>
|
|
11
|
+
* toUrl() consults this registry FIRST and short-circuits back to the correct
|
|
12
|
+
* browser URL.
|
|
13
|
+
*/
|
|
14
|
+
import { promises as fs } from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
// realpath (normalized, forward slashes) → browser URL prefix
|
|
17
|
+
const registry = new Map();
|
|
18
|
+
export async function buildSymlinkRegistry(nodeModulesDirs) {
|
|
19
|
+
registry.clear();
|
|
20
|
+
for (const dir of nodeModulesDirs) {
|
|
21
|
+
await scanNodeModulesDir(dir);
|
|
22
|
+
}
|
|
23
|
+
console.log(`[SWITE] Symlink registry built: ${registry.size} entries from ${nodeModulesDirs.length} node_modules dirs`);
|
|
24
|
+
}
|
|
25
|
+
async function scanNodeModulesDir(nodeModulesDir) {
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
|
+
let dirents;
|
|
28
|
+
try {
|
|
29
|
+
dirents = await fs.readdir(nodeModulesDir, {
|
|
30
|
+
withFileTypes: true,
|
|
31
|
+
encoding: "utf8",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return; // dir doesn't exist — skip silently
|
|
36
|
+
}
|
|
37
|
+
for (const dirent of dirents) {
|
|
38
|
+
if (dirent.name.startsWith("."))
|
|
39
|
+
continue;
|
|
40
|
+
if (dirent.name.startsWith("@")) {
|
|
41
|
+
// Scoped scope directory — scan one level deeper
|
|
42
|
+
const scopeDir = path.join(nodeModulesDir, dirent.name);
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
let scopedDirents;
|
|
45
|
+
try {
|
|
46
|
+
scopedDirents = await fs.readdir(scopeDir, {
|
|
47
|
+
withFileTypes: true,
|
|
48
|
+
encoding: "utf8",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
for (const scoped of scopedDirents) {
|
|
55
|
+
if (scoped.isSymbolicLink()) {
|
|
56
|
+
await registerSymlink(path.join(scopeDir, scoped.name), `${dirent.name}/${scoped.name}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (dirent.isSymbolicLink()) {
|
|
61
|
+
await registerSymlink(path.join(nodeModulesDir, dirent.name), dirent.name);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function registerSymlink(symlinkPath, pkgName) {
|
|
66
|
+
try {
|
|
67
|
+
const realPath = await fs.realpath(symlinkPath);
|
|
68
|
+
const key = realPath.replace(/\\/g, "/");
|
|
69
|
+
const value = `/node_modules/${pkgName}`;
|
|
70
|
+
registry.set(key, value);
|
|
71
|
+
console.log(`[SWITE] Registry: ${pkgName}: ${key} → ${value}`);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// broken symlink — ignore
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Look up an absolute filesystem path in the symlink registry.
|
|
79
|
+
*
|
|
80
|
+
* Returns the browser URL if absolutePath is, or is within, a package whose
|
|
81
|
+
* realpath was registered at startup. Returns null if not found.
|
|
82
|
+
*
|
|
83
|
+
* Example:
|
|
84
|
+
* /mnt/c/.../swiss-lib/packages/core/src/index.ts
|
|
85
|
+
* → /node_modules/@kibologic/core/src/index.ts
|
|
86
|
+
*/
|
|
87
|
+
export function lookupInSymlinkRegistry(absolutePath) {
|
|
88
|
+
const normalized = absolutePath.replace(/\\/g, "/");
|
|
89
|
+
for (const [realPkgPath, browserPrefix] of registry) {
|
|
90
|
+
if (normalized === realPkgPath) {
|
|
91
|
+
return browserPrefix;
|
|
92
|
+
}
|
|
93
|
+
if (normalized.startsWith(realPkgPath + "/")) {
|
|
94
|
+
return browserPrefix + normalized.slice(realPkgPath.length);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface UrlResolverContext {
|
|
2
|
+
root: string;
|
|
3
|
+
getWorkspaceRoot: () => Promise<string | null>;
|
|
4
|
+
fileExists: (filePath: string) => Promise<boolean>;
|
|
5
|
+
}
|
|
6
|
+
export type WorkspacePackageResolverContext = UrlResolverContext;
|
|
7
|
+
/**
|
|
8
|
+
* Convert file path to URL for browser
|
|
9
|
+
*/
|
|
10
|
+
export declare function toUrl(filePath: string, context: UrlResolverContext): Promise<string>;
|
|
11
|
+
//# sourceMappingURL=url-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-resolver.d.ts","sourceRoot":"","sources":["../../src/resolver/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;AAoBjE;;GAEG;AACH,wBAAsB,KAAK,CACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA4QjB"}
|