@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
package/dist/resolver.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Module Resolver for SWITE
|
|
3
|
+
*/
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promises as fs } from "node:fs";
|
|
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";
|
|
11
|
+
export class ModuleResolver {
|
|
12
|
+
constructor(root) {
|
|
13
|
+
this.root = root;
|
|
14
|
+
this.workspaceRoot = null;
|
|
15
|
+
this.importMap = null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Set pre-resolved import map (from build-time generation)
|
|
19
|
+
*/
|
|
20
|
+
setImportMap(importMap) {
|
|
21
|
+
this.importMap = importMap;
|
|
22
|
+
if (importMap) {
|
|
23
|
+
console.log(chalk.green(`[Resolver] Loaded import map with ${Object.keys(importMap).length - 2} entries`));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async getWorkspaceRoot() {
|
|
27
|
+
if (this.workspaceRoot)
|
|
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
|
+
this.workspaceRoot = await findWorkspaceRoot(this.root);
|
|
47
|
+
return this.workspaceRoot;
|
|
48
|
+
}
|
|
49
|
+
async resolve(specifier, importer) {
|
|
50
|
+
console.log(`[SWITE] resolve CALLED: specifier: ${specifier}, importer: ${importer}`);
|
|
51
|
+
// Check import map first (fast path)
|
|
52
|
+
if (this.importMap && !specifier.startsWith(".") && !specifier.startsWith("/")) {
|
|
53
|
+
const mapped = this.importMap.imports[specifier];
|
|
54
|
+
if (mapped) {
|
|
55
|
+
console.log(chalk.green(`[Resolver] ✅ Import map hit: ${specifier} -> ${mapped}`));
|
|
56
|
+
return mapped;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// CRITICAL: Skip variable references - they should never be resolved as modules
|
|
60
|
+
// Variables like def.componentUrl, someVar, etc. should be left as-is
|
|
61
|
+
// Only resolve actual module specifiers (bare imports starting with @ or valid package names)
|
|
62
|
+
if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
|
|
63
|
+
// Check if this looks like a variable reference (property access, camelCase without @, etc.)
|
|
64
|
+
// Valid module specifiers: @scope/name, package-name, ./relative, /absolute
|
|
65
|
+
// Variable references: def.componentUrl, someVar, obj.prop, etc.
|
|
66
|
+
if (specifier.includes(".") && !specifier.startsWith("@")) {
|
|
67
|
+
// Property access pattern (def.componentUrl) - this is a variable, not a module
|
|
68
|
+
console.warn(`[SWITE] resolve: Skipping variable reference: ${specifier}`);
|
|
69
|
+
return specifier; // Return as-is, don't try to resolve
|
|
70
|
+
}
|
|
71
|
+
// Additional check: if it doesn't look like a valid package name, it might be a variable
|
|
72
|
+
// Valid package names: start with letter or @, contain only alphanumeric, -, _, /
|
|
73
|
+
// Also allow file extensions at the end: .js, .ts, .ui, .uix, etc.
|
|
74
|
+
if (!/^[@a-zA-Z][a-zA-Z0-9_/@-]*(\.(js|ts|ui|uix|mjs|cjs|jsx|tsx))?$/.test(specifier)) {
|
|
75
|
+
console.warn(`[SWITE] resolve: Invalid module specifier (likely variable): ${specifier}`);
|
|
76
|
+
return specifier; // Return as-is
|
|
77
|
+
}
|
|
78
|
+
const context = {
|
|
79
|
+
root: this.root,
|
|
80
|
+
getWorkspaceRoot: () => this.getWorkspaceRoot(),
|
|
81
|
+
fileExists: (p) => this.fileExists(p),
|
|
82
|
+
resolveWorkspacePackage: (pkgName) => this.resolveWorkspacePackage(pkgName),
|
|
83
|
+
};
|
|
84
|
+
const result = await resolveBareImport(specifier, context);
|
|
85
|
+
console.log(`[SWITE] resolve RESULT: ${specifier} -> ${result}`);
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
// Handle absolute paths (already URLs)
|
|
89
|
+
if (specifier.startsWith("/")) {
|
|
90
|
+
return specifier;
|
|
91
|
+
}
|
|
92
|
+
// Handle relative imports
|
|
93
|
+
// importer might be a URL path (/src/modules/index.ui) or file path
|
|
94
|
+
// Convert URL to file path if needed
|
|
95
|
+
let importerPath = importer;
|
|
96
|
+
if (importer.startsWith("/")) {
|
|
97
|
+
// URL path - convert to file path
|
|
98
|
+
// Prioritize app root for app files (src/, public/, assets/)
|
|
99
|
+
if (importer.startsWith("/src/") ||
|
|
100
|
+
importer.startsWith("/public/") ||
|
|
101
|
+
importer.startsWith("/assets/")) {
|
|
102
|
+
// App-specific paths - resolve from app root
|
|
103
|
+
importerPath = path.join(this.root, importer);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Other paths - try workspace root first (for libraries/, packages/)
|
|
107
|
+
const workspaceRoot = await this.getWorkspaceRoot();
|
|
108
|
+
if (workspaceRoot) {
|
|
109
|
+
const workspacePath = path.join(workspaceRoot, importer);
|
|
110
|
+
if (await this.fileExists(workspacePath)) {
|
|
111
|
+
importerPath = workspacePath;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Fallback to app root
|
|
115
|
+
importerPath = path.join(this.root, importer);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// No workspace root, use app root
|
|
120
|
+
importerPath = path.join(this.root, importer);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const importerDir = path.dirname(importerPath);
|
|
125
|
+
// If specifier already has an extension (.ui, .uix, .ts, .js, etc.), try it first
|
|
126
|
+
// This preserves .ui/.uix extensions for SWISS files
|
|
127
|
+
const hasExtension = /\.(ui|uix|ts|tsx|js|jsx|mjs)$/.test(specifier);
|
|
128
|
+
if (hasExtension) {
|
|
129
|
+
// Specifier has extension, resolve it directly
|
|
130
|
+
const resolved = path.resolve(importerDir, specifier);
|
|
131
|
+
console.log(`[SWITE] resolve relative (hasExt): ${specifier}, importerDir: ${importerDir}, resolved: ${resolved}, exists: ${await this.fileExists(resolved)}`);
|
|
132
|
+
if (await this.fileExists(resolved)) {
|
|
133
|
+
const url = await this.toUrl(resolved);
|
|
134
|
+
console.log(`[SWITE] resolve relative: ${specifier} -> ${resolved} -> ${url}`);
|
|
135
|
+
return url;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// If no extension or file not found, try adding extensions
|
|
139
|
+
// Strip any existing extension from specifier (but preserve .ui/.uix if present)
|
|
140
|
+
const specifierWithoutExt = specifier.replace(/\.(js|ts|jsx|tsx|mjs)$/, "");
|
|
141
|
+
const resolved = path.resolve(importerDir, specifierWithoutExt);
|
|
142
|
+
console.log(`[SWITE] resolve relative (trying extensions): specifierWithoutExt: ${specifierWithoutExt}, resolved: ${resolved}`);
|
|
143
|
+
// Try adding extensions (prioritize .ui and .uix for SWISS files)
|
|
144
|
+
const extensions = [".ui", ".uix", ".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
145
|
+
for (const ext of extensions) {
|
|
146
|
+
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;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Try index files
|
|
156
|
+
for (const ext of extensions) {
|
|
157
|
+
const indexFile = path.join(resolved, `index${ext}`);
|
|
158
|
+
if (await this.fileExists(indexFile)) {
|
|
159
|
+
return await this.toUrl(indexFile);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Return as-is if nothing found
|
|
163
|
+
return await this.toUrl(resolved);
|
|
164
|
+
}
|
|
165
|
+
async fileExists(filePath) {
|
|
166
|
+
try {
|
|
167
|
+
await fs.access(filePath);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async resolveWorkspacePackage(pkgName) {
|
|
175
|
+
const context = {
|
|
176
|
+
root: this.root,
|
|
177
|
+
getWorkspaceRoot: () => this.getWorkspaceRoot(),
|
|
178
|
+
fileExists: (p) => this.fileExists(p),
|
|
179
|
+
};
|
|
180
|
+
return resolveWorkspacePackage(pkgName, context);
|
|
181
|
+
}
|
|
182
|
+
// OLD IMPLEMENTATION REMOVED - now uses resolver/workspace-package-resolver.ts
|
|
183
|
+
async toUrl(filePath) {
|
|
184
|
+
const context = {
|
|
185
|
+
root: this.root,
|
|
186
|
+
getWorkspaceRoot: () => this.getWorkspaceRoot(),
|
|
187
|
+
fileExists: (p) => this.fileExists(p),
|
|
188
|
+
};
|
|
189
|
+
return toUrl(filePath, context);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RouteDefinition } from "@kibologic/core";
|
|
2
|
+
import { RouteScanner } from "@kibologic/plugin-file-router/core";
|
|
3
|
+
import { createFileWatcher } from "@kibologic/plugin-file-router/dev";
|
|
4
|
+
import { HMREngine } from "../hmr.js";
|
|
5
|
+
export interface FileRouterConfig {
|
|
6
|
+
root: string;
|
|
7
|
+
hmr: HMREngine;
|
|
8
|
+
}
|
|
9
|
+
export interface FileRouterResult {
|
|
10
|
+
routeScanner: RouteScanner | null;
|
|
11
|
+
routeWatcher: Awaited<ReturnType<typeof createFileWatcher>> | null;
|
|
12
|
+
routes: RouteDefinition[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Setup file-based routing
|
|
16
|
+
* Scans for route files in pages/ directories and watches for changes
|
|
17
|
+
*/
|
|
18
|
+
export declare function setupFileRouter(config: FileRouterConfig): Promise<FileRouterResult>;
|
|
19
|
+
//# sourceMappingURL=file-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-router.d.ts","sourceRoot":"","sources":["../../src/router/file-router.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,SAAS,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,YAAY,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,GAAG,IAAI,CAAC;IACnE,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAmI3B"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024 Themba Mzumara
|
|
3
|
+
* SWITE - SWISS Development Server
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
import { promises as fs } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { RouteScanner } from "@kibologic/plugin-file-router/core";
|
|
10
|
+
import { createFileWatcher } from "@kibologic/plugin-file-router/dev";
|
|
11
|
+
import { HMREngine } from "../hmr.js";
|
|
12
|
+
import { findWorkspaceRoot } from "../utils/workspace.js";
|
|
13
|
+
/**
|
|
14
|
+
* Setup file-based routing
|
|
15
|
+
* Scans for route files in pages/ directories and watches for changes
|
|
16
|
+
*/
|
|
17
|
+
export async function setupFileRouter(config) {
|
|
18
|
+
const result = {
|
|
19
|
+
routeScanner: null,
|
|
20
|
+
routeWatcher: null,
|
|
21
|
+
routes: [],
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
const workspaceRoot = await findWorkspaceRoot(config.root);
|
|
25
|
+
const appRoot = config.root;
|
|
26
|
+
// Initialize route scanner
|
|
27
|
+
result.routeScanner = new RouteScanner({
|
|
28
|
+
routesDir: "./src/pages",
|
|
29
|
+
extensions: [".ui", ".uix"],
|
|
30
|
+
layouts: true,
|
|
31
|
+
lazyLoading: true,
|
|
32
|
+
});
|
|
33
|
+
// Scan routes from multiple locations:
|
|
34
|
+
// 1. App's pages directory (apps/alpine/src/pages)
|
|
35
|
+
// 2. SKLTN's pages directory (framework/skltn/src/pages) - for reusable auth pages
|
|
36
|
+
const routesToScan = [];
|
|
37
|
+
// App pages
|
|
38
|
+
const appPagesDir = path.join(appRoot, "src", "pages");
|
|
39
|
+
try {
|
|
40
|
+
await fs.access(appPagesDir);
|
|
41
|
+
routesToScan.push(appPagesDir);
|
|
42
|
+
console.log(chalk.gray(` 📄 Scanning app routes from ${appPagesDir}`));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// pages directory doesn't exist, skip
|
|
46
|
+
}
|
|
47
|
+
// SKLTN pages (if workspace root exists)
|
|
48
|
+
if (workspaceRoot && workspaceRoot !== appRoot) {
|
|
49
|
+
// Try framework/skltn first (new location), then fallback to lib/skltn (legacy)
|
|
50
|
+
const skltnPagesDir = path.join(workspaceRoot, "framework", "skltn", "src", "pages");
|
|
51
|
+
const legacySkltnPagesDir = path.join(workspaceRoot, "lib", "skltn", "src", "pages");
|
|
52
|
+
try {
|
|
53
|
+
await fs.access(skltnPagesDir);
|
|
54
|
+
routesToScan.push(skltnPagesDir);
|
|
55
|
+
console.log(chalk.gray(` 📄 Scanning SKLTN routes from ${skltnPagesDir}`));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Try legacy location
|
|
59
|
+
try {
|
|
60
|
+
await fs.access(legacySkltnPagesDir);
|
|
61
|
+
routesToScan.push(legacySkltnPagesDir);
|
|
62
|
+
console.log(chalk.gray(` 📄 Scanning SKLTN routes from ${legacySkltnPagesDir} (legacy)`));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// pages directory doesn't exist, skip
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Scan all route directories
|
|
70
|
+
for (const pagesDir of routesToScan) {
|
|
71
|
+
try {
|
|
72
|
+
const scannedRoutes = await result.routeScanner.scanRoutes(pagesDir);
|
|
73
|
+
result.routes.push(...scannedRoutes);
|
|
74
|
+
console.log(chalk.green(` ✓ Found ${scannedRoutes.length} routes in ${pagesDir}`));
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.warn(chalk.yellow(` ⚠ Failed to scan routes from ${pagesDir}:`), error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Setup file watcher for route changes
|
|
81
|
+
if (routesToScan.length > 0) {
|
|
82
|
+
// Watch the first pages directory (can be extended to watch multiple)
|
|
83
|
+
result.routeWatcher = await createFileWatcher({
|
|
84
|
+
directory: routesToScan[0],
|
|
85
|
+
extensions: [".ui", ".uix"],
|
|
86
|
+
});
|
|
87
|
+
result.routeWatcher.on("change", async (filePath) => {
|
|
88
|
+
console.log(chalk.yellow(` 🔄 Route file changed: ${filePath}`));
|
|
89
|
+
// Rescan routes
|
|
90
|
+
result.routes = [];
|
|
91
|
+
for (const pagesDir of routesToScan) {
|
|
92
|
+
try {
|
|
93
|
+
const scannedRoutes = await result.routeScanner.scanRoutes(pagesDir);
|
|
94
|
+
result.routes.push(...scannedRoutes);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.warn(`Failed to rescan routes:`, error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Notify HMR about route changes
|
|
101
|
+
config.hmr.notifyChange(filePath);
|
|
102
|
+
});
|
|
103
|
+
console.log(chalk.green(` ✓ File router initialized with ${result.routes.length} routes`));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
console.log(chalk.gray(` ⚠ No pages directories found, file router disabled`));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.warn(chalk.yellow(` ⚠ File router setup failed:`), error);
|
|
111
|
+
// Continue without file router
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface SwiteConfig {
|
|
2
|
+
root: string;
|
|
3
|
+
rootDir?: string;
|
|
4
|
+
publicDir: string;
|
|
5
|
+
port: number;
|
|
6
|
+
host: string;
|
|
7
|
+
open: boolean;
|
|
8
|
+
hmrPort?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare class SwiteServer {
|
|
11
|
+
private app;
|
|
12
|
+
private resolver;
|
|
13
|
+
private hmr;
|
|
14
|
+
private config;
|
|
15
|
+
private routeScanner;
|
|
16
|
+
private routeWatcher;
|
|
17
|
+
private routes;
|
|
18
|
+
constructor(config?: Partial<SwiteConfig>);
|
|
19
|
+
private findWorkspaceRoot;
|
|
20
|
+
start(): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IAKb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,YAAY,CACb;IACP,OAAO,CAAC,MAAM,CAAyB;gBAE3B,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM;YAe/B,iBAAiB;IAuBzB,KAAK;CAsEZ"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024 Themba Mzumara
|
|
3
|
+
* SWITE - SWISS Development Server
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
import { RouteScanner } from "@kibologic/plugin-file-router/core";
|
|
7
|
+
import { createFileWatcher } from "@kibologic/plugin-file-router/dev";
|
|
8
|
+
import express from "express";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { promises as fs } from "node:fs";
|
|
11
|
+
import { ModuleResolver } from "./resolver.js";
|
|
12
|
+
import { HMREngine } from "./hmr.js";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import { setupMiddleware } from "./middleware/middleware-setup.js";
|
|
15
|
+
import { buildSymlinkRegistry } from "./resolver/symlink-registry.js";
|
|
16
|
+
import { findSwissLibMonorepo } from "./utils/package-finder.js";
|
|
17
|
+
export class SwiteServer {
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
this.app = express();
|
|
20
|
+
this.routeScanner = null;
|
|
21
|
+
this.routeWatcher = null;
|
|
22
|
+
this.routes = [];
|
|
23
|
+
this.config = {
|
|
24
|
+
root: process.cwd(),
|
|
25
|
+
publicDir: "public",
|
|
26
|
+
port: 3000,
|
|
27
|
+
host: "localhost",
|
|
28
|
+
open: true,
|
|
29
|
+
...config,
|
|
30
|
+
};
|
|
31
|
+
this.resolver = new ModuleResolver(this.config.root);
|
|
32
|
+
this.hmr = new HMREngine(this.config.root, this.config.hmrPort);
|
|
33
|
+
}
|
|
34
|
+
// CG-03: find workspace root by walking up from startDir
|
|
35
|
+
async findWorkspaceRoot(startDir) {
|
|
36
|
+
if (this.config.rootDir) {
|
|
37
|
+
return path.resolve(this.config.rootDir);
|
|
38
|
+
}
|
|
39
|
+
let current = startDir;
|
|
40
|
+
for (let i = 0; i < 6; i++) {
|
|
41
|
+
try {
|
|
42
|
+
await fs.access(path.join(current, "pnpm-workspace.yaml"));
|
|
43
|
+
return current;
|
|
44
|
+
}
|
|
45
|
+
catch { }
|
|
46
|
+
try {
|
|
47
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(current, "package.json"), "utf-8"));
|
|
48
|
+
if (pkgJson.workspaces)
|
|
49
|
+
return current;
|
|
50
|
+
}
|
|
51
|
+
catch { }
|
|
52
|
+
const parent = path.dirname(current);
|
|
53
|
+
if (parent === current)
|
|
54
|
+
break;
|
|
55
|
+
current = parent;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
async start() {
|
|
60
|
+
const startTime = Date.now();
|
|
61
|
+
console.log(chalk.cyan("\n⚡ SWITE - SWISS Development Server\n"));
|
|
62
|
+
console.time("Startup");
|
|
63
|
+
// CG-03: Build symlink registry before serving any requests.
|
|
64
|
+
// Maps realpath(node_modules/pkg symlink) → /node_modules/pkg browser URL
|
|
65
|
+
// so toUrl() can map absolute filesystem paths back to browser URLs.
|
|
66
|
+
console.time("Symlink Registry");
|
|
67
|
+
try {
|
|
68
|
+
const nodeModulesDirs = [
|
|
69
|
+
path.join(this.config.root, "node_modules"),
|
|
70
|
+
// Also scan the server package's own node_modules (one level up from the
|
|
71
|
+
// app root, e.g. apps/server/node_modules) — pnpm places workspace package
|
|
72
|
+
// symlinks there, not in the app root's node_modules subfolder.
|
|
73
|
+
path.join(path.dirname(this.config.root), "node_modules"),
|
|
74
|
+
];
|
|
75
|
+
const workspaceRoot = await this.findWorkspaceRoot(this.config.root);
|
|
76
|
+
if (workspaceRoot) {
|
|
77
|
+
nodeModulesDirs.push(path.join(workspaceRoot, "node_modules"));
|
|
78
|
+
}
|
|
79
|
+
const swissLib = await findSwissLibMonorepo(this.config.root);
|
|
80
|
+
if (swissLib) {
|
|
81
|
+
nodeModulesDirs.push(path.join(swissLib, "node_modules"));
|
|
82
|
+
}
|
|
83
|
+
await buildSymlinkRegistry(nodeModulesDirs);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.warn(`[SWITE] Symlink registry build failed: ${err.message}`);
|
|
87
|
+
}
|
|
88
|
+
console.timeEnd("Symlink Registry");
|
|
89
|
+
// Setup middleware
|
|
90
|
+
console.time("Middleware Setup");
|
|
91
|
+
const workspaceRoot = await this.findWorkspaceRoot(this.config.root);
|
|
92
|
+
const middlewareResult = await setupMiddleware(this.app, {
|
|
93
|
+
root: this.config.root,
|
|
94
|
+
workspaceRoot,
|
|
95
|
+
publicDir: this.config.publicDir,
|
|
96
|
+
resolver: this.resolver,
|
|
97
|
+
hmr: this.hmr,
|
|
98
|
+
});
|
|
99
|
+
this.routes = middlewareResult.routes;
|
|
100
|
+
this.routeScanner = middlewareResult.routeScanner;
|
|
101
|
+
this.routeWatcher = middlewareResult.routeWatcher;
|
|
102
|
+
console.timeEnd("Middleware Setup");
|
|
103
|
+
// Start HMR
|
|
104
|
+
console.time("HMR Start");
|
|
105
|
+
await this.hmr.initialize();
|
|
106
|
+
await this.hmr.start();
|
|
107
|
+
console.timeEnd("HMR Start");
|
|
108
|
+
// Start HTTP server
|
|
109
|
+
// Use 0.0.0.0 to bind to all interfaces (IPv4 and IPv6)
|
|
110
|
+
const bindHost = this.config.host === "localhost" ? "0.0.0.0" : this.config.host;
|
|
111
|
+
console.time("HTTP Listen");
|
|
112
|
+
await new Promise((resolve) => {
|
|
113
|
+
this.app.listen(this.config.port, bindHost, () => {
|
|
114
|
+
console.timeEnd("HTTP Listen");
|
|
115
|
+
console.timeEnd("Startup");
|
|
116
|
+
console.log(chalk.green(` ➜ Local: http://localhost:${this.config.port}/`));
|
|
117
|
+
console.log(chalk.gray(` ➜ Ready in ${Date.now() - startTime}ms\n`));
|
|
118
|
+
resolve();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDN fallback policy.
|
|
3
|
+
*
|
|
4
|
+
* Swite can fall back to jsDelivr (+esm) for packages it can't resolve locally.
|
|
5
|
+
* This must be safe and project-agnostic:
|
|
6
|
+
* - Unscoped packages (e.g. "react") are usually public on npm; allow by default.
|
|
7
|
+
* - Scoped packages (e.g. "@scope/pkg") may be private; do NOT CDN-fallback by default.
|
|
8
|
+
*
|
|
9
|
+
* Opt-in:
|
|
10
|
+
* - Set `SWITE_CDN_FALLBACK_SCOPES` to a comma-separated list of scopes to allow,
|
|
11
|
+
* e.g. "@types,@tanstack".
|
|
12
|
+
*/
|
|
13
|
+
export declare function shouldUseCdnFallback(specifierOrPkg: string): boolean;
|
|
14
|
+
//# sourceMappingURL=cdn-fallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdn-fallback.d.ts","sourceRoot":"","sources":["../../src/utils/cdn-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAmBH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAKpE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDN fallback policy.
|
|
3
|
+
*
|
|
4
|
+
* Swite can fall back to jsDelivr (+esm) for packages it can't resolve locally.
|
|
5
|
+
* This must be safe and project-agnostic:
|
|
6
|
+
* - Unscoped packages (e.g. "react") are usually public on npm; allow by default.
|
|
7
|
+
* - Scoped packages (e.g. "@scope/pkg") may be private; do NOT CDN-fallback by default.
|
|
8
|
+
*
|
|
9
|
+
* Opt-in:
|
|
10
|
+
* - Set `SWITE_CDN_FALLBACK_SCOPES` to a comma-separated list of scopes to allow,
|
|
11
|
+
* e.g. "@types,@tanstack".
|
|
12
|
+
*/
|
|
13
|
+
function getScope(specifierOrPkg) {
|
|
14
|
+
if (!specifierOrPkg.startsWith("@"))
|
|
15
|
+
return null;
|
|
16
|
+
const firstSlash = specifierOrPkg.indexOf("/");
|
|
17
|
+
if (firstSlash === -1)
|
|
18
|
+
return null;
|
|
19
|
+
return specifierOrPkg.slice(0, firstSlash); // "@scope"
|
|
20
|
+
}
|
|
21
|
+
function parseAllowList() {
|
|
22
|
+
const raw = process.env.SWITE_CDN_FALLBACK_SCOPES || "";
|
|
23
|
+
const scopes = raw
|
|
24
|
+
.split(",")
|
|
25
|
+
.map((s) => s.trim())
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.map((s) => (s.startsWith("@") ? s : `@${s}`));
|
|
28
|
+
return new Set(scopes);
|
|
29
|
+
}
|
|
30
|
+
export function shouldUseCdnFallback(specifierOrPkg) {
|
|
31
|
+
const scope = getScope(specifierOrPkg);
|
|
32
|
+
if (!scope)
|
|
33
|
+
return true; // unscoped: allow by default
|
|
34
|
+
const allow = parseAllowList();
|
|
35
|
+
return allow.has(scope);
|
|
36
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface PathResolverContext {
|
|
2
|
+
root: string;
|
|
3
|
+
workspaceRoot: string | null;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolve file path from URL, handling SWISS packages, workspace packages, and app files
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveFilePath(url: string, root: string, workspaceRoot?: string | null): Promise<string>;
|
|
9
|
+
//# sourceMappingURL=file-path-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-path-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/file-path-resolver.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,aAAa,GAAE,MAAM,GAAG,IAAW,GAClC,OAAO,CAAC,MAAM,CAAC,CAmLjB"}
|