@swissjs/swite 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/cli.js +0 -0
- package/dist/config/config.d.ts +11 -0
- package/dist/config/config.d.ts.map +1 -1
- package/dist/dev-engine/handlers/base-handler.d.ts +3 -1
- package/dist/dev-engine/handlers/base-handler.d.ts.map +1 -1
- package/dist/dev-engine/handlers/base-handler.js +1 -1
- package/dist/dev-engine/handlers/node-module-handler.d.ts.map +1 -1
- package/dist/dev-engine/handlers/node-module-handler.js +30 -41
- package/dist/dev-engine/middleware/middleware-setup.d.ts +1 -0
- package/dist/dev-engine/middleware/middleware-setup.d.ts.map +1 -1
- package/dist/dev-engine/server.d.ts.map +1 -1
- package/dist/dev-engine/server.js +4 -0
- package/dist/kernel/package-finder.d.ts +7 -7
- package/dist/kernel/package-finder.d.ts.map +1 -1
- package/dist/kernel/package-finder.js +56 -40
- package/dist/resolution/bare-import-resolver.d.ts.map +1 -1
- package/dist/resolution/bare-import-resolver.js +13 -4
- package/dist/resolution/path/file-path-resolver.d.ts +2 -1
- package/dist/resolution/path/file-path-resolver.d.ts.map +1 -1
- package/dist/resolution/path/file-path-resolver.js +36 -9
- package/docs/architecture/build-pipeline.md +97 -0
- package/docs/architecture/dev-server.md +87 -0
- package/docs/architecture/hmr.md +78 -0
- package/docs/architecture/import-rewriting.md +101 -0
- package/docs/architecture/index.md +16 -0
- package/docs/architecture/python-integration.md +93 -0
- package/docs/architecture/resolution.md +92 -0
- package/docs/cli/build.md +78 -0
- package/docs/cli/dev.md +90 -0
- package/docs/cli/index.md +15 -0
- package/docs/cli/start.md +45 -0
- package/docs/development/contributing.md +74 -0
- package/docs/development/index.md +12 -0
- package/docs/development/internals.md +101 -0
- package/docs/guide/configuration.md +89 -0
- package/docs/guide/index.md +13 -0
- package/docs/guide/project-structure.md +75 -0
- package/docs/guide/quickstart.md +113 -0
- package/docs/index.md +16 -0
- package/package.json +15 -24
- package/src/config/config.ts +11 -0
- package/src/dev-engine/handlers/base-handler.ts +4 -2
- package/src/dev-engine/handlers/node-module-handler.ts +51 -78
- package/src/dev-engine/middleware/middleware-setup.ts +1 -0
- package/src/dev-engine/server.ts +38 -33
- package/src/kernel/package-finder.ts +59 -43
- package/src/resolution/bare-import-resolver.ts +14 -4
- package/src/resolution/path/file-path-resolver.ts +44 -10
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
import type { Response } from "express";
|
|
8
8
|
import { promises as fs } from "node:fs";
|
|
9
9
|
import * as path from "node:path";
|
|
10
|
-
import chalk from "chalk";
|
|
11
|
-
import { rewriteImports } from "../../resolution/rewriting/import-rewriter.js";
|
|
12
|
-
import { BaseHandler, type HandlerContext } from "./base-handler.js";
|
|
13
|
-
import { UIHandler } from "./ui-handler.js";
|
|
14
|
-
import { UIXHandler } from "./uix-handler.js";
|
|
15
|
-
import { TSHandler } from "./ts-handler.js";
|
|
16
|
-
import { findWorkspaceRoot } from "../../kernel/workspace.js";
|
|
17
|
-
import {
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import { rewriteImports } from "../../resolution/rewriting/import-rewriter.js";
|
|
12
|
+
import { BaseHandler, type HandlerContext } from "./base-handler.js";
|
|
13
|
+
import { UIHandler } from "./ui-handler.js";
|
|
14
|
+
import { UIXHandler } from "./uix-handler.js";
|
|
15
|
+
import { TSHandler } from "./ts-handler.js";
|
|
16
|
+
import { findWorkspaceRoot } from "../../kernel/workspace.js";
|
|
17
|
+
import { findPackage } from "../../kernel/package-finder.js";
|
|
18
|
+
import { shouldUseCdnFallback } from "../../resolution/cdn/cdn-fallback.js";
|
|
18
19
|
|
|
19
20
|
export class NodeModuleHandler extends BaseHandler {
|
|
20
21
|
private uiHandler: UIHandler;
|
|
@@ -76,76 +77,39 @@ export class NodeModuleHandler extends BaseHandler {
|
|
|
76
77
|
current = parent;
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
// CONSOLIDATED DISCOVERY: Use the Generalized Package Finder
|
|
81
|
+
// This follows our "Local-First" priority: Siblings > Local node_modules > Workspace node_modules
|
|
79
82
|
if (!filePath) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
} catch (err2) {
|
|
101
|
-
console.log(
|
|
102
|
-
chalk.yellow(
|
|
103
|
-
`[node_modules] Workspace path failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
104
|
-
),
|
|
105
|
-
);
|
|
106
|
-
// Try swiss-lib monorepo node_modules (dynamically found)
|
|
107
|
-
const { findSwissLibMonorepo } = await import("../../kernel/package-finder.js");
|
|
108
|
-
const swissLib = await findSwissLibMonorepo(this.context.root);
|
|
109
|
-
if (swissLib) {
|
|
110
|
-
const swissNodeModulesPath = path.join(swissLib, urlPath);
|
|
111
|
-
console.log(
|
|
112
|
-
chalk.blue(
|
|
113
|
-
`[node_modules] Trying swiss-lib path: ${swissNodeModulesPath}`,
|
|
114
|
-
),
|
|
115
|
-
);
|
|
116
|
-
try {
|
|
117
|
-
// Try to resolve symlinks first (realpath works even if path is a symlink)
|
|
118
|
-
const resolvedPath = await fs.realpath(swissNodeModulesPath);
|
|
119
|
-
console.log(
|
|
120
|
-
chalk.blue(`[node_modules] Resolved to: ${resolvedPath}`),
|
|
121
|
-
);
|
|
122
|
-
// Verify the resolved path exists
|
|
123
|
-
await fs.access(resolvedPath);
|
|
124
|
-
filePath = resolvedPath;
|
|
125
|
-
console.log(
|
|
126
|
-
chalk.green(
|
|
127
|
-
`[node_modules] ✓ Found in swiss-lib monorepo: ${urlPath}`,
|
|
128
|
-
),
|
|
129
|
-
);
|
|
130
|
-
} catch (err3) {
|
|
131
|
-
console.log(
|
|
132
|
-
chalk.yellow(
|
|
133
|
-
`[node_modules] swiss-lib path failed: ${err3 instanceof Error ? err3.message : String(err3)}`,
|
|
134
|
-
),
|
|
135
|
-
);
|
|
136
|
-
// File not found in any location, will trigger case-insensitive search below
|
|
137
|
-
filePath = path.join(this.context.root, urlPath);
|
|
83
|
+
const urlParts = urlPath.split("/");
|
|
84
|
+
const packageName = urlParts[1].startsWith("@") ? `${urlParts[1]}/${urlParts[2]}` : urlParts[1];
|
|
85
|
+
const remainingPath = urlParts[1].startsWith("@") ? urlParts.slice(3).join("/") : urlParts.slice(2).join("/");
|
|
86
|
+
|
|
87
|
+
const location = await findPackage(packageName, this.context.root, workspaceRoot);
|
|
88
|
+
|
|
89
|
+
if (location) {
|
|
90
|
+
filePath = path.join(location.path, remainingPath);
|
|
91
|
+
console.log(chalk.green(`[node_modules] ✓ Found ${packageName} via ${location.type}: ${filePath}`));
|
|
92
|
+
|
|
93
|
+
// Re-use dist -> src fallback for local siblings
|
|
94
|
+
if (location.type !== 'node_modules' && filePath.includes("/dist/")) {
|
|
95
|
+
const srcPath = filePath.replace("/dist/", "/src/").replace(/\.[mc]?js$/, ".ts");
|
|
96
|
+
try {
|
|
97
|
+
await fs.access(srcPath);
|
|
98
|
+
console.log(chalk.yellow(`[node_modules] Intercept: Serving local source instead of dist: ${srcPath}`));
|
|
99
|
+
filePath = srcPath;
|
|
100
|
+
|
|
101
|
+
if (srcPath.endsWith(".ts")) {
|
|
102
|
+
return await this.tsHandler.handle(url.replace(/\.[mc]?js$/, ".ts"), res);
|
|
138
103
|
}
|
|
139
|
-
}
|
|
140
|
-
// File not found in any location, will trigger case-insensitive search below
|
|
141
|
-
filePath = path.join(this.context.root, urlPath);
|
|
142
|
-
}
|
|
104
|
+
} catch { /* Fallback to original filePath */ }
|
|
143
105
|
}
|
|
144
|
-
} else {
|
|
145
|
-
filePath = path.join(this.context.root, urlPath);
|
|
146
106
|
}
|
|
147
107
|
}
|
|
148
108
|
|
|
109
|
+
if (!filePath) {
|
|
110
|
+
filePath = path.join(this.context.root, urlPath);
|
|
111
|
+
}
|
|
112
|
+
|
|
149
113
|
console.log(
|
|
150
114
|
chalk.gray(`[node_modules] Resolving: ${url} -> ${filePath}`),
|
|
151
115
|
);
|
|
@@ -331,9 +295,18 @@ export class NodeModuleHandler extends BaseHandler {
|
|
|
331
295
|
pkgName = firstSlash === -1 ? after : after.slice(0, firstSlash);
|
|
332
296
|
}
|
|
333
297
|
|
|
334
|
-
if (!pkgName || pkgName === "." || pkgName === "..") return null;
|
|
335
|
-
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
298
|
+
if (!pkgName || pkgName === "." || pkgName === "..") return null;
|
|
299
|
+
|
|
300
|
+
// Never redirect internal/private scoped packages to public CDNs
|
|
301
|
+
const internalScopes = this.context.userConfig?.internalScopes || [];
|
|
302
|
+
const isInternal = internalScopes.some(scope => pkgName === scope || pkgName.startsWith(scope + "/"));
|
|
303
|
+
if (isInternal) {
|
|
304
|
+
console.log(chalk.red(`[node_modules] CDN Blocked: Internal scope package ${pkgName} cannot be served from jsDelivr.`));
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!shouldUseCdnFallback(pkgName)) return null;
|
|
309
|
+
// jsDelivr +esm serves ESM build; works for reflect-metadata and most npm packages
|
|
310
|
+
return `https://cdn.jsdelivr.net/npm/${pkgName}/+esm`;
|
|
311
|
+
}
|
|
312
|
+
}
|
package/src/dev-engine/server.ts
CHANGED
|
@@ -16,20 +16,21 @@ import chalk from "chalk";
|
|
|
16
16
|
import { setupMiddleware } from "./middleware/middleware-setup.js";
|
|
17
17
|
import { buildSymlinkRegistry } from "../resolution/symlink-registry.js";
|
|
18
18
|
import { findSwissLibMonorepo } from "../kernel/package-finder.js";
|
|
19
|
+
import { loadUserConfig } from "../config/config-loader.js";
|
|
19
20
|
|
|
20
|
-
export interface SwiteConfig {
|
|
21
|
-
root: string;
|
|
22
|
-
// Workspace/monorepo root. When set, Swite will use this for resolving
|
|
23
|
-
// node_modules, workspace packages, and import-map generation.
|
|
24
|
-
// This avoids relying on auto-detection (pnpm-workspace.yaml) which may not
|
|
25
|
-
// exist in some deployment contexts.
|
|
26
|
-
rootDir?: string;
|
|
27
|
-
publicDir: string;
|
|
28
|
-
port: number;
|
|
29
|
-
host: string;
|
|
30
|
-
open: boolean;
|
|
31
|
-
hmrPort?: number; // Optional HMR WebSocket port
|
|
32
|
-
}
|
|
21
|
+
export interface SwiteConfig {
|
|
22
|
+
root: string;
|
|
23
|
+
// Workspace/monorepo root. When set, Swite will use this for resolving
|
|
24
|
+
// node_modules, workspace packages, and import-map generation.
|
|
25
|
+
// This avoids relying on auto-detection (pnpm-workspace.yaml) which may not
|
|
26
|
+
// exist in some deployment contexts.
|
|
27
|
+
rootDir?: string;
|
|
28
|
+
publicDir: string;
|
|
29
|
+
port: number;
|
|
30
|
+
host: string;
|
|
31
|
+
open: boolean;
|
|
32
|
+
hmrPort?: number; // Optional HMR WebSocket port
|
|
33
|
+
}
|
|
33
34
|
|
|
34
35
|
export class SwiteServer {
|
|
35
36
|
private app = express();
|
|
@@ -55,16 +56,16 @@ export class SwiteServer {
|
|
|
55
56
|
this.hmr = new HMREngine(this.config.root, this.config.hmrPort);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
// CG-03: find workspace root by walking up from startDir
|
|
59
|
-
private async findWorkspaceRoot(startDir: string): Promise<string | null> {
|
|
60
|
-
if (this.config.rootDir) {
|
|
61
|
-
return path.resolve(this.config.rootDir);
|
|
62
|
-
}
|
|
63
|
-
let current = startDir;
|
|
64
|
-
for (let i = 0; i < 6; i++) {
|
|
65
|
-
try {
|
|
66
|
-
await fs.access(path.join(current, "pnpm-workspace.yaml"));
|
|
67
|
-
return current;
|
|
59
|
+
// CG-03: find workspace root by walking up from startDir
|
|
60
|
+
private async findWorkspaceRoot(startDir: string): Promise<string | null> {
|
|
61
|
+
if (this.config.rootDir) {
|
|
62
|
+
return path.resolve(this.config.rootDir);
|
|
63
|
+
}
|
|
64
|
+
let current = startDir;
|
|
65
|
+
for (let i = 0; i < 6; i++) {
|
|
66
|
+
try {
|
|
67
|
+
await fs.access(path.join(current, "pnpm-workspace.yaml"));
|
|
68
|
+
return current;
|
|
68
69
|
} catch {}
|
|
69
70
|
try {
|
|
70
71
|
const pkgJson = JSON.parse(
|
|
@@ -110,16 +111,20 @@ export class SwiteServer {
|
|
|
110
111
|
}
|
|
111
112
|
console.timeEnd("Symlink Registry");
|
|
112
113
|
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
// Load user config (swiss.config.ts) so internalScopes etc. flow into handlers
|
|
115
|
+
const userConfig = await loadUserConfig(this.config.root);
|
|
116
|
+
|
|
117
|
+
// Setup middleware
|
|
118
|
+
console.time("Middleware Setup");
|
|
119
|
+
const workspaceRoot = await this.findWorkspaceRoot(this.config.root);
|
|
120
|
+
const middlewareResult = await setupMiddleware(this.app, {
|
|
121
|
+
root: this.config.root,
|
|
122
|
+
workspaceRoot,
|
|
123
|
+
publicDir: this.config.publicDir,
|
|
124
|
+
resolver: this.resolver,
|
|
125
|
+
hmr: this.hmr,
|
|
126
|
+
userConfig,
|
|
127
|
+
});
|
|
123
128
|
this.routes = middlewareResult.routes;
|
|
124
129
|
this.routeScanner = middlewareResult.routeScanner;
|
|
125
130
|
this.routeWatcher = middlewareResult.routeWatcher;
|
|
@@ -18,55 +18,54 @@ export interface PackageLocation {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Find
|
|
22
|
-
* ancestor level for any workspace root (pnpm-workspace.yaml) that also has a
|
|
23
|
-
* packages/ directory. Works for any framework directory name.
|
|
21
|
+
* Find any sibling monorepo by searching for its package.json
|
|
24
22
|
*/
|
|
25
|
-
export async function
|
|
26
|
-
let current =
|
|
27
|
-
|
|
23
|
+
export async function findSiblingRepository(startPath: string, repoName: string): Promise<string | null> {
|
|
24
|
+
let current = startPath;
|
|
28
25
|
for (let i = 0; i < 20; i++) {
|
|
29
|
-
const
|
|
30
|
-
|
|
26
|
+
const siblingPath = path.join(current, repoName);
|
|
27
|
+
const pkgJson = path.join(siblingPath, "package.json");
|
|
28
|
+
if (await fileExists(pkgJson)) {
|
|
29
|
+
return siblingPath;
|
|
30
|
+
}
|
|
31
31
|
|
|
32
|
-
// Scan siblings of `current` at this parent level
|
|
33
32
|
try {
|
|
34
|
-
const entries = await fs.readdir(
|
|
33
|
+
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
35
34
|
for (const entry of entries) {
|
|
36
|
-
if (entry.name ===
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
await fileExists(path.join(sibling, "pnpm-workspace.yaml")) &&
|
|
43
|
-
await fileExists(path.join(sibling, "packages"))
|
|
44
|
-
) {
|
|
45
|
-
return sibling;
|
|
35
|
+
if (entry.name === repoName && (entry.isDirectory() || entry.isSymbolicLink())) {
|
|
36
|
+
const subDir = path.join(current, entry.name);
|
|
37
|
+
if (await fileExists(path.join(subDir, "package.json"))) {
|
|
38
|
+
return subDir;
|
|
39
|
+
}
|
|
46
40
|
}
|
|
47
41
|
}
|
|
48
|
-
} catch {
|
|
49
|
-
// Skip on permission errors
|
|
50
|
-
}
|
|
42
|
+
} catch { /* Continue */ }
|
|
51
43
|
|
|
52
|
-
current =
|
|
44
|
+
current = path.dirname(current);
|
|
45
|
+
if (current === path.dirname(current)) break;
|
|
53
46
|
}
|
|
54
|
-
|
|
55
47
|
return null;
|
|
56
48
|
}
|
|
57
49
|
|
|
58
50
|
/**
|
|
59
|
-
*
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
* Backward compatibility wrapper for finding swiss-lib
|
|
52
|
+
*/
|
|
53
|
+
export async function findSwissLibMonorepo(startPath: string): Promise<string | null> {
|
|
54
|
+
return findSiblingRepository(startPath, 'swiss-lib');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Find a specific package by name, with priority given based on environment.
|
|
59
|
+
* In development, we prioritize local sibling source code.
|
|
63
60
|
*/
|
|
64
61
|
export async function findPackage(
|
|
65
62
|
packageName: string,
|
|
66
63
|
startPath: string,
|
|
67
64
|
workspaceRoot?: string | null
|
|
68
65
|
): Promise<PackageLocation | null> {
|
|
69
|
-
|
|
66
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
67
|
+
|
|
68
|
+
// 1. Check local node_modules (Standard resolution) - HIGHEST PRIORITY in Remote-First
|
|
70
69
|
const localNodeModules = path.join(startPath, "node_modules", packageName);
|
|
71
70
|
if (await fileExists(path.join(localNodeModules, "package.json"))) {
|
|
72
71
|
return { path: localNodeModules, type: 'node_modules' };
|
|
@@ -79,7 +78,7 @@ export async function findPackage(
|
|
|
79
78
|
return { path: workspaceNodeModules, type: 'node_modules' };
|
|
80
79
|
}
|
|
81
80
|
}
|
|
82
|
-
|
|
81
|
+
|
|
83
82
|
// 3. Check co-located framework monorepo packages/ for any scoped package
|
|
84
83
|
if (packageName.startsWith("@")) {
|
|
85
84
|
const monorepo = await findSwissLibMonorepo(startPath);
|
|
@@ -92,24 +91,41 @@ export async function findPackage(
|
|
|
92
91
|
}
|
|
93
92
|
}
|
|
94
93
|
|
|
95
|
-
// 4.
|
|
94
|
+
// 4. In dev: broader sibling scan across parent directories
|
|
95
|
+
if (isDev && packageName.includes("/")) {
|
|
96
|
+
const parts = packageName.split("/");
|
|
97
|
+
const unscoped = parts[parts.length - 1];
|
|
98
|
+
const parentDirs = [
|
|
99
|
+
path.join(startPath, ".."),
|
|
100
|
+
path.join(startPath, "../.."),
|
|
101
|
+
path.join(startPath, "../../.."),
|
|
102
|
+
];
|
|
103
|
+
for (const parent of parentDirs) {
|
|
104
|
+
try {
|
|
105
|
+
const potentialRepos = await fs.readdir(parent);
|
|
106
|
+
for (const repo of potentialRepos) {
|
|
107
|
+
const siblingPath = path.join(parent, repo);
|
|
108
|
+
const packagePath = path.join(siblingPath, "packages", unscoped);
|
|
109
|
+
if (await fileExists(path.join(packagePath, "package.json"))) {
|
|
110
|
+
console.log(`[package-finder] Dev Intercept: Serving ${packageName} from local source: ${packagePath}`);
|
|
111
|
+
return { path: packagePath, type: 'swiss-lib' };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch { /* Continue */ }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 5. Fallback search in internal workspace packages (lib/, packages/, modules/)
|
|
96
119
|
if (workspaceRoot) {
|
|
97
120
|
const packageDirs = ["lib", "packages", "modules", "libraries", "apps"];
|
|
98
121
|
for (const dir of packageDirs) {
|
|
99
122
|
const searchDir = path.join(workspaceRoot, dir);
|
|
100
123
|
if (!(await fileExists(searchDir))) continue;
|
|
101
124
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (await fileExists(path.join(packagePath, "package.json"))) {
|
|
107
|
-
return { path: packagePath, type: 'workspace' };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Try full package name
|
|
112
|
-
const packagePath = path.join(searchDir, packageName);
|
|
125
|
+
const parts = packageName.split("/");
|
|
126
|
+
const unscoped = parts.length > 1 ? parts[1] : parts[0];
|
|
127
|
+
const packagePath = path.join(searchDir, unscoped);
|
|
128
|
+
|
|
113
129
|
if (await fileExists(path.join(packagePath, "package.json"))) {
|
|
114
130
|
return { path: packagePath, type: 'workspace' };
|
|
115
131
|
}
|
|
@@ -47,7 +47,7 @@ export async function resolveBareImport(
|
|
|
47
47
|
nodeModulesLocations.push(path.join(workspaceRoot, "node_modules"));
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
// Add
|
|
50
|
+
// Add monorepo node_modules if present
|
|
51
51
|
const swissLib = await findSwissLibMonorepo(context.root);
|
|
52
52
|
if (swissLib) {
|
|
53
53
|
const swissNodeModules = path.join(swissLib, "node_modules");
|
|
@@ -209,9 +209,19 @@ export async function resolveBareImport(
|
|
|
209
209
|
|
|
210
210
|
const fullPath = path.join(pkgDir, entryPoint);
|
|
211
211
|
|
|
212
|
-
// Try the
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
// Try a few Swiss-specific variations of the entry point before CDN fallback
|
|
213
|
+
const swissVariations = [
|
|
214
|
+
fullPath,
|
|
215
|
+
fullPath.replace(/\.(js|mjs|ts)$/, '.ui'),
|
|
216
|
+
fullPath.replace(/\.(js|mjs|ts)$/, '.uix'),
|
|
217
|
+
path.join(pkgDir, 'src/index.ui'),
|
|
218
|
+
path.join(pkgDir, 'src/index.uix'),
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
for (const v of swissVariations) {
|
|
222
|
+
if (await context.fileExists(v)) {
|
|
223
|
+
return await toUrl(v, context);
|
|
224
|
+
}
|
|
215
225
|
}
|
|
216
226
|
|
|
217
227
|
// Try with extensions
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
import { promises as fs } from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { findWorkspaceRoot } from "../../kernel/workspace.js";
|
|
10
|
-
import { findSwissLibMonorepo } from "../../kernel/package-finder.js";
|
|
10
|
+
import { findSwissLibMonorepo, findPackage } from "../../kernel/package-finder.js";
|
|
11
11
|
|
|
12
12
|
export interface PathResolverContext {
|
|
13
13
|
root: string;
|
|
14
14
|
workspaceRoot: string | null;
|
|
15
|
+
userConfig?: any; // SwiteUserConfig
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -21,15 +22,52 @@ export async function resolveFilePath(
|
|
|
21
22
|
url: string,
|
|
22
23
|
root: string,
|
|
23
24
|
workspaceRoot: string | null = null,
|
|
25
|
+
userConfig?: any
|
|
24
26
|
): Promise<string> {
|
|
27
|
+
// Consolidate workspace root discovery for consistency across resolution blocks
|
|
28
|
+
const wsRoot = workspaceRoot || (await findWorkspaceRoot(root));
|
|
29
|
+
|
|
25
30
|
// /node_modules/ URLs: walk up from app root until we find the package.
|
|
26
31
|
// pnpm may place deps at the app root, one level up (workspace pkg), or at
|
|
27
32
|
// the monorepo root depending on hoisting config and pnpm version.
|
|
28
33
|
if (url.startsWith("/node_modules/")) {
|
|
29
34
|
const urlPath = url.startsWith("/") ? url.slice(1) : url;
|
|
35
|
+
const parts = urlPath.split("/");
|
|
36
|
+
// Handle @scoped/package or standard-package
|
|
37
|
+
const packageName = parts[1].startsWith("@") ? `${parts[1]}/${parts[2]}` : parts[1];
|
|
38
|
+
// NEW: PNPM-aware Interceptor. Check if this request is for an "internal" scope package.
|
|
39
|
+
// In development, we always prioritize local siblings if they exist.
|
|
40
|
+
const internalScopes = userConfig?.internalScopes || [];
|
|
41
|
+
const match = internalScopes.length > 0
|
|
42
|
+
? url.match(new RegExp(`(${internalScopes.join("|")})\/([^/]+)`))
|
|
43
|
+
: null;
|
|
44
|
+
|
|
45
|
+
if (process.env.NODE_ENV !== 'production' && match) {
|
|
46
|
+
const packageName = match[0];
|
|
47
|
+
const remainingPath = url.split(match[0])[1];
|
|
48
|
+
|
|
49
|
+
const localLoc = await findPackage(packageName, root, wsRoot);
|
|
50
|
+
if (localLoc && localLoc.type !== 'node_modules') {
|
|
51
|
+
// Found local source! Redirect the base path
|
|
52
|
+
const fullPath = path.join(localLoc.path, remainingPath);
|
|
53
|
+
|
|
54
|
+
// Re-use workspace fallback logic for dist -> src transition
|
|
55
|
+
if (fullPath.includes("/dist/")) {
|
|
56
|
+
const srcPath = fullPath.replace("/dist/", "/src/").replace(/\.[mc]?js$/, ".ts");
|
|
57
|
+
try {
|
|
58
|
+
await fs.access(srcPath);
|
|
59
|
+
console.log(`[file-path-resolver] Intercept: ${packageName} redirecting to local src: ${srcPath}`);
|
|
60
|
+
return srcPath;
|
|
61
|
+
} catch { /* Fallback to dist if src not found */ }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(`[file-path-resolver] Intercept: ${packageName} redirecting to local source: ${fullPath}`);
|
|
65
|
+
return fullPath;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
30
68
|
|
|
31
|
-
|
|
32
|
-
|
|
69
|
+
// Walk up the directory tree from root, trying node_modules at each level
|
|
70
|
+
let current = path.resolve(root);
|
|
33
71
|
const visited = new Set<string>();
|
|
34
72
|
for (let i = 0; i < 8; i++) {
|
|
35
73
|
const candidate = path.join(current, urlPath);
|
|
@@ -49,7 +87,6 @@ export async function resolveFilePath(
|
|
|
49
87
|
}
|
|
50
88
|
|
|
51
89
|
// Explicit workspace root (covers hoisted-to-root installs)
|
|
52
|
-
const wsRoot = workspaceRoot || (await findWorkspaceRoot(root));
|
|
53
90
|
if (wsRoot) {
|
|
54
91
|
const wsPath = path.join(wsRoot, urlPath);
|
|
55
92
|
if (!visited.has(wsPath)) {
|
|
@@ -92,14 +129,12 @@ export async function resolveFilePath(
|
|
|
92
129
|
url.startsWith("/packages/") ||
|
|
93
130
|
url.startsWith("/modules/")
|
|
94
131
|
) {
|
|
95
|
-
|
|
96
|
-
if (!wsRoot) {
|
|
97
|
-
wsRoot = await findWorkspaceRoot(root);
|
|
98
|
-
console.log(`[file-path-resolver] Detected workspace root: ${wsRoot} (from app root: ${root})`);
|
|
99
|
-
}
|
|
132
|
+
// Already detected wsRoot at function start
|
|
100
133
|
|
|
101
134
|
// Normalize URL: path.join with leading slash is wrong on Windows (treats as drive root)
|
|
102
135
|
const urlPath = url.startsWith("/") ? url.slice(1) : url;
|
|
136
|
+
|
|
137
|
+
// ...
|
|
103
138
|
|
|
104
139
|
// CRITICAL: For /lib/ paths, we MUST find the SWS root (which has lib/ directory)
|
|
105
140
|
// Start from app root and walk up until we find a directory with both pnpm-workspace.yaml AND lib/
|
|
@@ -156,7 +191,6 @@ export async function resolveFilePath(
|
|
|
156
191
|
}
|
|
157
192
|
|
|
158
193
|
// For app files, check if URL already includes the app path
|
|
159
|
-
const wsRoot = workspaceRoot || (await findWorkspaceRoot(root));
|
|
160
194
|
if (wsRoot) {
|
|
161
195
|
const appRelativeToWorkspace = path
|
|
162
196
|
.relative(wsRoot, root)
|