@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/builder.js
ADDED
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024 Themba Mzumara
|
|
3
|
+
* SWITE - SWISS Production Builder
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
import { build as esbuild } from "esbuild";
|
|
7
|
+
import { UiCompiler } from "@kibologic/compiler";
|
|
8
|
+
import { promises as fs } from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import { ModuleResolver } from "./resolver.js";
|
|
12
|
+
export class SwiteBuilder {
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.compiler = new UiCompiler();
|
|
15
|
+
this.config = {
|
|
16
|
+
root: config.root,
|
|
17
|
+
entry: config.entry,
|
|
18
|
+
outDir: config.outDir,
|
|
19
|
+
publicDir: config.publicDir || "public",
|
|
20
|
+
minify: config.minify ?? true,
|
|
21
|
+
sourcemap: config.sourcemap ?? false,
|
|
22
|
+
format: config.format || "esm",
|
|
23
|
+
target: config.target || "es2020",
|
|
24
|
+
external: config.external || [],
|
|
25
|
+
};
|
|
26
|
+
this.resolver = new ModuleResolver(config.root);
|
|
27
|
+
}
|
|
28
|
+
async build() {
|
|
29
|
+
const startTime = Date.now();
|
|
30
|
+
console.log(chalk.cyan("\n⚡ SWITE - Production Build\n"));
|
|
31
|
+
try {
|
|
32
|
+
// Step 1: Clean output directory
|
|
33
|
+
await this.cleanOutputDir();
|
|
34
|
+
// Step 2: Compile Swiss files to temp directory
|
|
35
|
+
const tempDir = path.join(this.config.root, ".swite-build");
|
|
36
|
+
await this.compileSwissFiles(tempDir);
|
|
37
|
+
// Step 3: Bundle with esbuild
|
|
38
|
+
await this.bundle(tempDir);
|
|
39
|
+
// Step 4: Copy public assets
|
|
40
|
+
await this.copyPublicAssets();
|
|
41
|
+
// Step 5: Clean up temp directory
|
|
42
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
43
|
+
const duration = Date.now() - startTime;
|
|
44
|
+
console.log(chalk.green(`\n✅ Build completed in ${duration}ms\n`));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error(chalk.red("\n❌ Build failed:"), error);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async cleanOutputDir() {
|
|
52
|
+
console.log(chalk.blue("🧹 Cleaning output directory..."));
|
|
53
|
+
await fs.rm(this.config.outDir, { recursive: true, force: true });
|
|
54
|
+
await fs.mkdir(this.config.outDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
async compileSwissFiles(tempDir) {
|
|
57
|
+
console.log(chalk.blue("🔨 Compiling Swiss files..."));
|
|
58
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
59
|
+
const workspaceRoot = await this.findWorkspaceRoot(this.config.root);
|
|
60
|
+
const appRelativeToWorkspace = workspaceRoot
|
|
61
|
+
? path.relative(workspaceRoot, this.config.root)
|
|
62
|
+
: "";
|
|
63
|
+
// Step 1: Compile app's own files
|
|
64
|
+
const srcDir = path.join(this.config.root, "src");
|
|
65
|
+
const appTempDir = appRelativeToWorkspace
|
|
66
|
+
? path.join(tempDir, appRelativeToWorkspace, "src")
|
|
67
|
+
: path.join(tempDir, "src");
|
|
68
|
+
await this.compileDirectory(srcDir, appTempDir, "app");
|
|
69
|
+
// Step 2: Discover and compile workspace dependencies
|
|
70
|
+
const workspaceDeps = await this.discoverWorkspaceDependencies();
|
|
71
|
+
for (const dep of workspaceDeps) {
|
|
72
|
+
console.log(chalk.blue(`📦 Compiling dependency: ${dep.name}`));
|
|
73
|
+
// Preserve workspace structure: libraries/skltn/src or packages/cart/src or modules/cart/src
|
|
74
|
+
const depRelativeToWorkspace = workspaceRoot
|
|
75
|
+
? path.relative(workspaceRoot, dep.pkgDir)
|
|
76
|
+
: "";
|
|
77
|
+
const depTempDir = depRelativeToWorkspace
|
|
78
|
+
? path.join(tempDir, depRelativeToWorkspace, "src")
|
|
79
|
+
: path.join(tempDir, "src");
|
|
80
|
+
await this.compileDirectory(dep.srcDir, depTempDir, dep.name);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async compileDirectory(srcDir, tempDir, label) {
|
|
84
|
+
// Find all .ui and .uix files
|
|
85
|
+
const files = await this.findSwissFiles(srcDir);
|
|
86
|
+
for (const file of files) {
|
|
87
|
+
const relativePath = path.relative(srcDir, file);
|
|
88
|
+
const outputPath = path.join(tempDir, relativePath.replace(/\.(ui|uix)$/, ".tsx"));
|
|
89
|
+
// Ensure output directory exists
|
|
90
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
91
|
+
// Compile file
|
|
92
|
+
const source = await fs.readFile(file, "utf-8");
|
|
93
|
+
let compiled = await this.compiler.compileAsync(source, file);
|
|
94
|
+
// Rewrite .ui/.uix imports to .tsx in compiled output (esbuild needs .tsx for JSX)
|
|
95
|
+
compiled = compiled.replace(/from\s+['"]([^'"]*\.)(ui|uix)['"]/g, "from '$1tsx'");
|
|
96
|
+
compiled = compiled.replace(/import\s+['"]([^'"]*\.)(ui|uix)['"]/g, "import '$1tsx'");
|
|
97
|
+
// Add export default for the named export so default imports resolve at bundle time
|
|
98
|
+
const namedExportMatch = compiled.match(/export\s*\{\s*(\w+)\s*\}\s*;?\s*$/);
|
|
99
|
+
if (namedExportMatch?.[1]) {
|
|
100
|
+
compiled += `\nexport default ${namedExportMatch[1]};\n`;
|
|
101
|
+
}
|
|
102
|
+
await fs.writeFile(outputPath, compiled, "utf-8");
|
|
103
|
+
console.log(chalk.gray(` ✓ [${label}] ${relativePath}`));
|
|
104
|
+
}
|
|
105
|
+
// Copy .ts files and rewrite .ui/.uix imports to .tsx
|
|
106
|
+
const tsFiles = await this.findFiles(srcDir, /\.ts$/);
|
|
107
|
+
for (const file of tsFiles) {
|
|
108
|
+
const relativePath = path.relative(srcDir, file);
|
|
109
|
+
const outputPath = path.join(tempDir, relativePath);
|
|
110
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
111
|
+
// Read source and rewrite imports
|
|
112
|
+
const source = await fs.readFile(file, "utf-8");
|
|
113
|
+
// Replace .ui and .uix imports with .tsx (compiled Swiss files are emitted as .tsx)
|
|
114
|
+
const rewritten = source.replace(/from\s+['"](\.\/[^'"]*\.)(ui|uix)['"]/g, "from '$1tsx'");
|
|
115
|
+
await fs.writeFile(outputPath, rewritten, "utf-8");
|
|
116
|
+
}
|
|
117
|
+
// Copy .css and other static assets so imports resolve
|
|
118
|
+
const cssFiles = await this.findFiles(srcDir, /\.css$/);
|
|
119
|
+
for (const file of cssFiles) {
|
|
120
|
+
const relativePath = path.relative(srcDir, file);
|
|
121
|
+
const outputPath = path.join(tempDir, relativePath);
|
|
122
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
123
|
+
await fs.copyFile(file, outputPath);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async discoverWorkspaceDependencies() {
|
|
127
|
+
const deps = [];
|
|
128
|
+
try {
|
|
129
|
+
const packageJsonPath = path.join(this.config.root, "package.json");
|
|
130
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
|
|
131
|
+
const allDeps = {
|
|
132
|
+
...packageJson.dependencies,
|
|
133
|
+
...packageJson.peerDependencies,
|
|
134
|
+
...packageJson.devDependencies,
|
|
135
|
+
};
|
|
136
|
+
const workspaceRoot = await this.findWorkspaceRoot(this.config.root);
|
|
137
|
+
if (!workspaceRoot) {
|
|
138
|
+
return deps;
|
|
139
|
+
}
|
|
140
|
+
// Also discover packages imported in source files (for transitive dependencies)
|
|
141
|
+
const discoveredPackages = new Set();
|
|
142
|
+
const srcDir = path.join(this.config.root, "src");
|
|
143
|
+
// Scan source files for workspace package imports
|
|
144
|
+
const scanForImports = async (dir) => {
|
|
145
|
+
try {
|
|
146
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
147
|
+
for (const entry of entries) {
|
|
148
|
+
const fullPath = path.join(dir, entry.name);
|
|
149
|
+
if (entry.isDirectory()) {
|
|
150
|
+
await scanForImports(fullPath);
|
|
151
|
+
}
|
|
152
|
+
else if (entry.name.endsWith(".ts") ||
|
|
153
|
+
entry.name.endsWith(".ui") ||
|
|
154
|
+
entry.name.endsWith(".uix")) {
|
|
155
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
156
|
+
// Match imports like: import ... from '@swiss-enterprise/cart/...' or '@alpine/skltn/...'
|
|
157
|
+
const importMatches = content.matchAll(/from\s+['"](@[\w-]+\/[\w-]+)/g);
|
|
158
|
+
for (const match of importMatches) {
|
|
159
|
+
const pkgName = match[1];
|
|
160
|
+
if (pkgName && !discoveredPackages.has(pkgName)) {
|
|
161
|
+
discoveredPackages.add(pkgName);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Ignore errors
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
if (await this.fileExists(srcDir)) {
|
|
172
|
+
await scanForImports(srcDir);
|
|
173
|
+
}
|
|
174
|
+
for (const [depName, depVersion] of Object.entries(allDeps)) {
|
|
175
|
+
// Only process workspace dependencies
|
|
176
|
+
if (typeof depVersion === "string" &&
|
|
177
|
+
depVersion.startsWith("workspace:")) {
|
|
178
|
+
// Extract package name (handle scoped packages)
|
|
179
|
+
const pkgName = depName.startsWith("@")
|
|
180
|
+
? depName.split("/")[1]
|
|
181
|
+
: depName;
|
|
182
|
+
// Try common package locations
|
|
183
|
+
const possibleDirs = [
|
|
184
|
+
path.join(workspaceRoot, "lib", pkgName),
|
|
185
|
+
path.join(workspaceRoot, "packages", pkgName),
|
|
186
|
+
path.join(workspaceRoot, "packages", "runtime", pkgName),
|
|
187
|
+
path.join(workspaceRoot, "packages", "plugins", pkgName),
|
|
188
|
+
path.join(workspaceRoot, "packages", "domain", pkgName),
|
|
189
|
+
];
|
|
190
|
+
for (const pkgDir of possibleDirs) {
|
|
191
|
+
const pkgJsonPath = path.join(pkgDir, "package.json");
|
|
192
|
+
if (await this.fileExists(pkgJsonPath)) {
|
|
193
|
+
const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"));
|
|
194
|
+
// Verify it's the right package
|
|
195
|
+
if (pkgJson.name === depName) {
|
|
196
|
+
const srcDir = path.join(pkgDir, "src");
|
|
197
|
+
if (await this.fileExists(srcDir)) {
|
|
198
|
+
deps.push({
|
|
199
|
+
name: depName,
|
|
200
|
+
srcDir,
|
|
201
|
+
pkgDir,
|
|
202
|
+
});
|
|
203
|
+
console.log(chalk.gray(` 📦 Found workspace dependency: ${depName}`));
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Also process discovered packages from source files
|
|
212
|
+
for (const pkgName of discoveredPackages) {
|
|
213
|
+
// Skip if already in deps
|
|
214
|
+
if (deps.some((d) => d.name === pkgName)) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
// Extract package name (handle scoped packages)
|
|
218
|
+
const pkgNameOnly = pkgName.startsWith("@")
|
|
219
|
+
? pkgName.split("/")[1]
|
|
220
|
+
: pkgName;
|
|
221
|
+
// Try common package locations
|
|
222
|
+
const possibleDirs = [
|
|
223
|
+
path.join(workspaceRoot, "lib", pkgNameOnly),
|
|
224
|
+
path.join(workspaceRoot, "packages", pkgNameOnly),
|
|
225
|
+
path.join(workspaceRoot, "packages", "runtime", pkgNameOnly),
|
|
226
|
+
path.join(workspaceRoot, "packages", "plugins", pkgNameOnly),
|
|
227
|
+
path.join(workspaceRoot, "packages", "domain", pkgNameOnly),
|
|
228
|
+
];
|
|
229
|
+
for (const pkgDir of possibleDirs) {
|
|
230
|
+
const pkgJsonPath = path.join(pkgDir, "package.json");
|
|
231
|
+
if (await this.fileExists(pkgJsonPath)) {
|
|
232
|
+
const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"));
|
|
233
|
+
// Verify it's the right package
|
|
234
|
+
if (pkgJson.name === pkgName) {
|
|
235
|
+
const srcDir = path.join(pkgDir, "src");
|
|
236
|
+
if (await this.fileExists(srcDir)) {
|
|
237
|
+
deps.push({
|
|
238
|
+
name: pkgName,
|
|
239
|
+
srcDir,
|
|
240
|
+
pkgDir,
|
|
241
|
+
});
|
|
242
|
+
console.log(chalk.gray(` 📦 Discovered transitive dependency: ${pkgName}`));
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
console.warn(chalk.yellow("⚠️ Could not discover dependencies:"), error);
|
|
252
|
+
}
|
|
253
|
+
return deps;
|
|
254
|
+
}
|
|
255
|
+
async bundle(tempDir) {
|
|
256
|
+
console.log(chalk.blue("📦 Bundling with esbuild..."));
|
|
257
|
+
const workspaceRoot = await this.findWorkspaceRoot(this.config.root);
|
|
258
|
+
const appRelativeToWorkspace = workspaceRoot
|
|
259
|
+
? path.relative(workspaceRoot, this.config.root)
|
|
260
|
+
: "";
|
|
261
|
+
// Determine entry point - look for compiled version in temp
|
|
262
|
+
// const entryBasename = path.basename(this.config.entry, path.extname(this.config.entry)); // Unused
|
|
263
|
+
const entryExt = path.extname(this.config.entry);
|
|
264
|
+
let entryPoint;
|
|
265
|
+
// Entry point is always relative to src directory
|
|
266
|
+
const entryRelativeToSrc = path.relative(path.join(this.config.root, "src"), this.config.entry);
|
|
267
|
+
if (entryExt === ".ui" || entryExt === ".uix") {
|
|
268
|
+
// Entry was a Swiss file, use compiled .tsx version
|
|
269
|
+
const entryTsx = entryRelativeToSrc.replace(/\.(ui|uix)$/, ".tsx");
|
|
270
|
+
entryPoint = appRelativeToWorkspace
|
|
271
|
+
? path.join(tempDir, appRelativeToWorkspace, "src", entryTsx)
|
|
272
|
+
: path.join(tempDir, "src", entryTsx);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
// Entry is .ts or .js, use from temp
|
|
276
|
+
entryPoint = appRelativeToWorkspace
|
|
277
|
+
? path.join(tempDir, appRelativeToWorkspace, "src", entryRelativeToSrc)
|
|
278
|
+
: path.join(tempDir, "src", entryRelativeToSrc);
|
|
279
|
+
}
|
|
280
|
+
// Verify entry point exists
|
|
281
|
+
if (!(await this.fileExists(entryPoint))) {
|
|
282
|
+
throw new Error(`Entry point not found: ${entryPoint} (from ${this.config.entry})`);
|
|
283
|
+
}
|
|
284
|
+
// Configure esbuild to resolve workspace packages from temp directory
|
|
285
|
+
const absWorkingDir = workspaceRoot || this.config.root;
|
|
286
|
+
// const aliases = workspaceRoot ? await this.createAliases(workspaceRoot, tempDir) : {}; // Unused
|
|
287
|
+
// Mark Node.js built-ins and build-time-only deps as external
|
|
288
|
+
const nodeBuiltins = [
|
|
289
|
+
"@kibologic/swite",
|
|
290
|
+
"@kibologic/core",
|
|
291
|
+
"@kibologic/*",
|
|
292
|
+
"@kibologic/*",
|
|
293
|
+
"fs",
|
|
294
|
+
"path",
|
|
295
|
+
"os",
|
|
296
|
+
"crypto",
|
|
297
|
+
"http",
|
|
298
|
+
"https",
|
|
299
|
+
"net",
|
|
300
|
+
"stream",
|
|
301
|
+
"util",
|
|
302
|
+
"events",
|
|
303
|
+
"child_process",
|
|
304
|
+
"url",
|
|
305
|
+
"querystring",
|
|
306
|
+
"zlib",
|
|
307
|
+
"assert",
|
|
308
|
+
"constants",
|
|
309
|
+
"tty",
|
|
310
|
+
"node:fs",
|
|
311
|
+
"node:path",
|
|
312
|
+
"node:os",
|
|
313
|
+
"node:crypto",
|
|
314
|
+
"node:http",
|
|
315
|
+
"node:https",
|
|
316
|
+
"node:net",
|
|
317
|
+
"node:stream",
|
|
318
|
+
"node:util",
|
|
319
|
+
"node:events",
|
|
320
|
+
"node:child_process",
|
|
321
|
+
"node:url",
|
|
322
|
+
"node:querystring",
|
|
323
|
+
"node:zlib",
|
|
324
|
+
"node:assert",
|
|
325
|
+
"node:constants",
|
|
326
|
+
"node:tty",
|
|
327
|
+
"fs/promises",
|
|
328
|
+
"node:fs/promises",
|
|
329
|
+
];
|
|
330
|
+
// Resolve relative .js imports to .tsx when UiCompiler rewrites .ui→.js but emits .tsx files
|
|
331
|
+
const jsTsxFallbackPlugin = {
|
|
332
|
+
name: "js-tsx-fallback",
|
|
333
|
+
setup(build) {
|
|
334
|
+
build.onResolve({ filter: /\.js$/ }, async (args) => {
|
|
335
|
+
if (!args.path.startsWith("."))
|
|
336
|
+
return undefined;
|
|
337
|
+
const jsPath = path.resolve(path.dirname(args.resolveDir), args.path);
|
|
338
|
+
const tsxPath = jsPath.replace(/\.js$/, ".tsx");
|
|
339
|
+
try {
|
|
340
|
+
await fs.access(tsxPath);
|
|
341
|
+
return { path: tsxPath };
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
// Stub .css imports so they resolve (build output may copy CSS separately)
|
|
350
|
+
const cssStubPlugin = {
|
|
351
|
+
name: "css-stub",
|
|
352
|
+
setup(build) {
|
|
353
|
+
build.onLoad({ filter: /\.css$/ }, () => ({
|
|
354
|
+
contents: "export {};",
|
|
355
|
+
loader: "js",
|
|
356
|
+
}));
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
// Create plugin to resolve workspace packages to compiled files
|
|
360
|
+
const workspaceDeps = await this.discoverWorkspaceDependencies();
|
|
361
|
+
const fileExists = this.fileExists.bind(this);
|
|
362
|
+
const findWorkspaceRoot = this.findWorkspaceRoot.bind(this);
|
|
363
|
+
const appRoot = this.config.root;
|
|
364
|
+
const wsRoot = await findWorkspaceRoot(appRoot);
|
|
365
|
+
const tempDirForPlugin = tempDir; // Capture tempDir for plugin
|
|
366
|
+
// Helper function to safely join paths - filters out invalid values
|
|
367
|
+
const safePathJoin = (...parts) => {
|
|
368
|
+
const validParts = parts.filter((p) => p != null && typeof p === "string" && p.length > 0);
|
|
369
|
+
if (validParts.length === 0)
|
|
370
|
+
return null;
|
|
371
|
+
try {
|
|
372
|
+
return path.join(...validParts);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
const workspaceResolverPlugin = {
|
|
379
|
+
name: "workspace-resolver",
|
|
380
|
+
setup(build) {
|
|
381
|
+
// Resolve workspace packages
|
|
382
|
+
build.onResolve({ filter: /^@/ }, async (args) => {
|
|
383
|
+
// Early return if tempDirForPlugin is invalid
|
|
384
|
+
if (!tempDirForPlugin ||
|
|
385
|
+
typeof tempDirForPlugin !== "string" ||
|
|
386
|
+
tempDirForPlugin.length === 0) {
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
// Check if this is a workspace package (from dependencies or try to find it)
|
|
390
|
+
let matchingDep = workspaceDeps.find((d) => args.path.startsWith(d.name));
|
|
391
|
+
// If not in dependencies, try to find it in workspace
|
|
392
|
+
if (!matchingDep && wsRoot) {
|
|
393
|
+
const pkgName = args.path.split("/")[0];
|
|
394
|
+
const pkgNameOnly = pkgName.startsWith("@")
|
|
395
|
+
? pkgName.split("/")[1]
|
|
396
|
+
: pkgName;
|
|
397
|
+
if (!wsRoot ||
|
|
398
|
+
typeof wsRoot !== "string" ||
|
|
399
|
+
!pkgNameOnly ||
|
|
400
|
+
typeof pkgNameOnly !== "string") {
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
const possibleDirs = [
|
|
404
|
+
safePathJoin(wsRoot, "lib", pkgNameOnly),
|
|
405
|
+
safePathJoin(wsRoot, "packages", pkgNameOnly),
|
|
406
|
+
].filter((p) => p !== null);
|
|
407
|
+
for (const pkgDir of possibleDirs) {
|
|
408
|
+
const pkgJsonPath = safePathJoin(pkgDir, "package.json");
|
|
409
|
+
if (pkgJsonPath && (await fileExists(pkgJsonPath))) {
|
|
410
|
+
const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"));
|
|
411
|
+
if (pkgJson.name === pkgName) {
|
|
412
|
+
const srcDir = safePathJoin(pkgDir, "src");
|
|
413
|
+
if (srcDir && (await fileExists(srcDir))) {
|
|
414
|
+
matchingDep = {
|
|
415
|
+
name: pkgName,
|
|
416
|
+
srcDir: srcDir,
|
|
417
|
+
pkgDir: pkgDir,
|
|
418
|
+
};
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (matchingDep && wsRoot && matchingDep.pkgDir) {
|
|
426
|
+
try {
|
|
427
|
+
// Resolve to compiled file in temp directory
|
|
428
|
+
let depRelativeToWorkspace = "";
|
|
429
|
+
try {
|
|
430
|
+
if (wsRoot &&
|
|
431
|
+
matchingDep.pkgDir &&
|
|
432
|
+
typeof wsRoot === "string" &&
|
|
433
|
+
typeof matchingDep.pkgDir === "string") {
|
|
434
|
+
const rel = path.relative(wsRoot, matchingDep.pkgDir);
|
|
435
|
+
if (rel &&
|
|
436
|
+
typeof rel === "string" &&
|
|
437
|
+
rel !== "." &&
|
|
438
|
+
rel.length > 0) {
|
|
439
|
+
depRelativeToWorkspace = rel;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
catch (err) {
|
|
444
|
+
console.warn(`[SWITE] Error calculating relative path for ${matchingDep.name}:`, err);
|
|
445
|
+
depRelativeToWorkspace = "";
|
|
446
|
+
}
|
|
447
|
+
// Extract subpath (e.g., "@alpine/skltn/shell" -> "shell")
|
|
448
|
+
const subPath = args.path.replace(matchingDep.name + "/", "");
|
|
449
|
+
// Log for debugging
|
|
450
|
+
console.log(`[SWITE] Resolving ${args.path} -> subPath: ${subPath} from ${matchingDep.name} (${depRelativeToWorkspace || "root"})`);
|
|
451
|
+
// Try to resolve the subpath
|
|
452
|
+
let resolvedPath = null;
|
|
453
|
+
if (subPath) {
|
|
454
|
+
// Check package.json exports
|
|
455
|
+
if (!matchingDep.pkgDir ||
|
|
456
|
+
typeof matchingDep.pkgDir !== "string") {
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
const pkgJsonPath = safePathJoin(matchingDep.pkgDir, "package.json");
|
|
460
|
+
if (!pkgJsonPath) {
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
if (await fileExists(pkgJsonPath)) {
|
|
464
|
+
try {
|
|
465
|
+
const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"));
|
|
466
|
+
if (pkgJson.exports) {
|
|
467
|
+
const exportKey = `./${subPath}`;
|
|
468
|
+
let exportValue = pkgJson.exports[exportKey];
|
|
469
|
+
// Try directory-based matching
|
|
470
|
+
if (!exportValue && subPath.includes("/")) {
|
|
471
|
+
const dirPath = subPath.split("/")[0];
|
|
472
|
+
exportValue = pkgJson.exports[`./${dirPath}`];
|
|
473
|
+
}
|
|
474
|
+
if (exportValue) {
|
|
475
|
+
const exportPath = typeof exportValue === "string"
|
|
476
|
+
? exportValue
|
|
477
|
+
: exportValue.import || exportValue.default;
|
|
478
|
+
if (exportPath && typeof exportPath === "string") {
|
|
479
|
+
// Convert export path to compiled path
|
|
480
|
+
const srcRelative = exportPath.replace(/^\.\/src\//, "");
|
|
481
|
+
const compiledPath = srcRelative.replace(/\.(ui|uix)$/, ".tsx");
|
|
482
|
+
// Validate compiledPath is a valid string
|
|
483
|
+
if (!compiledPath ||
|
|
484
|
+
typeof compiledPath !== "string" ||
|
|
485
|
+
compiledPath.length === 0) {
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
const parts = [];
|
|
489
|
+
// Validate tempDirForPlugin is a valid string
|
|
490
|
+
if (tempDirForPlugin &&
|
|
491
|
+
typeof tempDirForPlugin === "string" &&
|
|
492
|
+
tempDirForPlugin.length > 0) {
|
|
493
|
+
parts.push(tempDirForPlugin);
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
// Skip this resolution if tempDir is invalid
|
|
497
|
+
return undefined;
|
|
498
|
+
}
|
|
499
|
+
if (depRelativeToWorkspace &&
|
|
500
|
+
typeof depRelativeToWorkspace === "string" &&
|
|
501
|
+
depRelativeToWorkspace.length > 0) {
|
|
502
|
+
parts.push(depRelativeToWorkspace);
|
|
503
|
+
}
|
|
504
|
+
// Validate compiledPath before adding
|
|
505
|
+
if (compiledPath &&
|
|
506
|
+
typeof compiledPath === "string" &&
|
|
507
|
+
compiledPath.length > 0) {
|
|
508
|
+
const joined = safePathJoin(tempDirForPlugin, depRelativeToWorkspace || undefined, "src", compiledPath);
|
|
509
|
+
if (joined) {
|
|
510
|
+
resolvedPath = joined;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
catch (err) {
|
|
518
|
+
// Fallback to index
|
|
519
|
+
console.warn(`[SWITE] Error reading exports for ${matchingDep.name}:`, err);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// Fallback: try index.js
|
|
524
|
+
if (!resolvedPath || !(await fileExists(resolvedPath))) {
|
|
525
|
+
const fallbackParts = [];
|
|
526
|
+
// Validate tempDirForPlugin is a valid string
|
|
527
|
+
if (tempDirForPlugin &&
|
|
528
|
+
typeof tempDirForPlugin === "string" &&
|
|
529
|
+
tempDirForPlugin.length > 0) {
|
|
530
|
+
fallbackParts.push(tempDirForPlugin);
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
// Cannot build without valid tempDir
|
|
534
|
+
return undefined;
|
|
535
|
+
}
|
|
536
|
+
if (depRelativeToWorkspace &&
|
|
537
|
+
typeof depRelativeToWorkspace === "string" &&
|
|
538
|
+
depRelativeToWorkspace.length > 0) {
|
|
539
|
+
fallbackParts.push(depRelativeToWorkspace);
|
|
540
|
+
}
|
|
541
|
+
// Use safe path join helper
|
|
542
|
+
const joined = safePathJoin(tempDirForPlugin, depRelativeToWorkspace || undefined, "src", "index.js");
|
|
543
|
+
if (joined) {
|
|
544
|
+
resolvedPath = joined;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// Final check - ensure path is valid string and exists
|
|
548
|
+
if (resolvedPath &&
|
|
549
|
+
typeof resolvedPath === "string" &&
|
|
550
|
+
resolvedPath.length > 0) {
|
|
551
|
+
try {
|
|
552
|
+
if (await fileExists(resolvedPath)) {
|
|
553
|
+
return { path: resolvedPath };
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
// Ignore file check errors
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
catch (err) {
|
|
562
|
+
console.warn(`[SWITE] Error resolving ${args.path}:`, err);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// Let esbuild handle it normally (return undefined, not null)
|
|
566
|
+
return undefined;
|
|
567
|
+
});
|
|
568
|
+
// Also resolve relative imports in compiled workspace packages
|
|
569
|
+
build.onResolve({ filter: /^\.\.?\/.*\.(js|ts|ui|uix)$/ }, async (args) => {
|
|
570
|
+
// Only handle if it's in a workspace package directory
|
|
571
|
+
if (args.importer && args.importer.includes(".swite-build")) {
|
|
572
|
+
// Check if the importer is in a workspace package
|
|
573
|
+
const importerPath = args.importer;
|
|
574
|
+
const match = importerPath.match(/\.swite-build[/\\]([^/\\]+[/\\][^/\\]+)[/\\]src/);
|
|
575
|
+
if (match) {
|
|
576
|
+
// Try to resolve relative to the importer
|
|
577
|
+
const importerDir = path.dirname(importerPath);
|
|
578
|
+
const resolved = safePathJoin(importerDir, args.path);
|
|
579
|
+
if (resolved && (await fileExists(resolved))) {
|
|
580
|
+
return { path: resolved };
|
|
581
|
+
}
|
|
582
|
+
// Try with .tsx extension if original had .ui/.uix
|
|
583
|
+
const pathWithoutExt = args.path.replace(/\.(ui|uix)$/, "");
|
|
584
|
+
const resolvedTsx = safePathJoin(importerDir, pathWithoutExt + ".tsx");
|
|
585
|
+
if (resolvedTsx && (await fileExists(resolvedTsx))) {
|
|
586
|
+
return { path: resolvedTsx };
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return undefined;
|
|
591
|
+
});
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
const buildOptions = {
|
|
595
|
+
entryPoints: [entryPoint],
|
|
596
|
+
bundle: true,
|
|
597
|
+
outdir: this.config.outDir,
|
|
598
|
+
format: this.config.format,
|
|
599
|
+
target: this.config.target,
|
|
600
|
+
minify: this.config.minify,
|
|
601
|
+
sourcemap: this.config.sourcemap,
|
|
602
|
+
external: [...this.config.external, ...nodeBuiltins],
|
|
603
|
+
platform: "node", // Keep as node for now - browser apps will need different strategy
|
|
604
|
+
splitting: this.config.format === "esm",
|
|
605
|
+
metafile: true,
|
|
606
|
+
logLevel: "info",
|
|
607
|
+
absWorkingDir, // Help esbuild resolve modules from workspace root
|
|
608
|
+
plugins: [jsTsxFallbackPlugin, cssStubPlugin, workspaceResolverPlugin],
|
|
609
|
+
};
|
|
610
|
+
// Add aliases via plugins if esbuild version supports it
|
|
611
|
+
// For now, we'll rely on the compiled files being in the right structure
|
|
612
|
+
const result = await esbuild(buildOptions);
|
|
613
|
+
// Log bundle stats (metafile paths can be relative to absWorkingDir)
|
|
614
|
+
if (result.metafile) {
|
|
615
|
+
const outputs = Object.keys(result.metafile.outputs);
|
|
616
|
+
console.log(chalk.green(`\n Generated ${outputs.length} file(s):`));
|
|
617
|
+
for (const output of outputs) {
|
|
618
|
+
const resolvedPath = path.isAbsolute(output)
|
|
619
|
+
? output
|
|
620
|
+
: path.join(absWorkingDir, output);
|
|
621
|
+
const stats = await fs.stat(resolvedPath);
|
|
622
|
+
const size = this.formatBytes(stats.size);
|
|
623
|
+
console.log(chalk.gray(` ${path.basename(output)}: ${size}`));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
async copyPublicAssets() {
|
|
628
|
+
const publicPath = path.join(this.config.root, this.config.publicDir);
|
|
629
|
+
try {
|
|
630
|
+
await fs.access(publicPath);
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
// Public directory doesn't exist, skip
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
console.log(chalk.blue("📁 Copying public assets..."));
|
|
637
|
+
await this.copyDir(publicPath, this.config.outDir);
|
|
638
|
+
}
|
|
639
|
+
async findSwissFiles(dir) {
|
|
640
|
+
const files = [];
|
|
641
|
+
try {
|
|
642
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
643
|
+
for (const entry of entries) {
|
|
644
|
+
if (entry.name === "node_modules")
|
|
645
|
+
continue;
|
|
646
|
+
const fullPath = path.join(dir, entry.name);
|
|
647
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
648
|
+
files.push(...(await this.findSwissFiles(fullPath)));
|
|
649
|
+
}
|
|
650
|
+
else if (entry.name.endsWith(".ui") || entry.name.endsWith(".uix")) {
|
|
651
|
+
files.push(fullPath);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
// Directory doesn't exist or can't be read
|
|
657
|
+
}
|
|
658
|
+
return files;
|
|
659
|
+
}
|
|
660
|
+
async findFiles(dir, pattern) {
|
|
661
|
+
const files = [];
|
|
662
|
+
try {
|
|
663
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
664
|
+
for (const entry of entries) {
|
|
665
|
+
if (entry.name === "node_modules")
|
|
666
|
+
continue;
|
|
667
|
+
const fullPath = path.join(dir, entry.name);
|
|
668
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
669
|
+
files.push(...(await this.findFiles(fullPath, pattern)));
|
|
670
|
+
}
|
|
671
|
+
else if (pattern.test(entry.name)) {
|
|
672
|
+
files.push(fullPath);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
catch {
|
|
677
|
+
// Directory doesn't exist or can't be read
|
|
678
|
+
}
|
|
679
|
+
return files;
|
|
680
|
+
}
|
|
681
|
+
async copyDir(src, dest) {
|
|
682
|
+
await fs.mkdir(dest, { recursive: true });
|
|
683
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
684
|
+
for (const entry of entries) {
|
|
685
|
+
const srcPath = path.join(src, entry.name);
|
|
686
|
+
const destPath = path.join(dest, entry.name);
|
|
687
|
+
if (entry.isDirectory()) {
|
|
688
|
+
await this.copyDir(srcPath, destPath);
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
await fs.copyFile(srcPath, destPath);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
formatBytes(bytes) {
|
|
696
|
+
if (bytes === 0)
|
|
697
|
+
return "0 B";
|
|
698
|
+
const k = 1024;
|
|
699
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
700
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
701
|
+
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
|
|
702
|
+
}
|
|
703
|
+
async findWorkspaceRoot(startDir) {
|
|
704
|
+
let current = startDir;
|
|
705
|
+
for (let i = 0; i < 5; i++) {
|
|
706
|
+
const workspaceFile = path.join(current, "pnpm-workspace.yaml");
|
|
707
|
+
const packageJson = path.join(current, "package.json");
|
|
708
|
+
try {
|
|
709
|
+
if (await this.fileExists(workspaceFile)) {
|
|
710
|
+
return current;
|
|
711
|
+
}
|
|
712
|
+
if (await this.fileExists(packageJson)) {
|
|
713
|
+
const pkg = JSON.parse(await fs.readFile(packageJson, "utf-8"));
|
|
714
|
+
if (pkg.workspaces) {
|
|
715
|
+
return current;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
// Continue searching
|
|
721
|
+
}
|
|
722
|
+
const parent = path.dirname(current);
|
|
723
|
+
if (parent === current)
|
|
724
|
+
break;
|
|
725
|
+
current = parent;
|
|
726
|
+
}
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
async fileExists(filePath) {
|
|
730
|
+
try {
|
|
731
|
+
await fs.access(filePath);
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
catch {
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
async createAliases(workspaceRoot, tempDir) {
|
|
739
|
+
const aliases = {};
|
|
740
|
+
const deps = await this.discoverWorkspaceDependencies();
|
|
741
|
+
for (const dep of deps) {
|
|
742
|
+
const depRelativeToWorkspace = path.relative(workspaceRoot, dep.pkgDir);
|
|
743
|
+
const aliasPath = path.join(tempDir, depRelativeToWorkspace, "src");
|
|
744
|
+
aliases[dep.name] = aliasPath;
|
|
745
|
+
// Also add subpath exports
|
|
746
|
+
const pkgJsonPath = path.join(dep.pkgDir, "package.json");
|
|
747
|
+
try {
|
|
748
|
+
const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"));
|
|
749
|
+
if (pkgJson.exports) {
|
|
750
|
+
for (const [exportKey, exportValue] of Object.entries(pkgJson.exports)) {
|
|
751
|
+
if (exportKey !== "." && typeof exportValue === "string") {
|
|
752
|
+
const fullAlias = `${dep.name}${exportKey}`;
|
|
753
|
+
const exportPath = exportValue.replace(/^\.\/src\//, "");
|
|
754
|
+
aliases[fullAlias] = path.join(aliasPath, exportPath);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
catch {
|
|
760
|
+
// Skip if can't read package.json
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return aliases;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Convenience function to build a project
|
|
768
|
+
*/
|
|
769
|
+
export async function build(config) {
|
|
770
|
+
const builder = new SwiteBuilder(config);
|
|
771
|
+
await builder.build();
|
|
772
|
+
}
|