@vellumai/assistant 0.4.40 → 0.4.42
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/package.json +1 -1
- package/src/bundler/app-compiler.ts +131 -103
- package/src/bundler/compiler-tools.ts +248 -0
- package/src/cli/contacts.ts +255 -6
- package/src/config/bundled-skills/app-builder/SKILL.md +17 -3
- package/src/config/bundled-skills/contacts/SKILL.md +80 -168
- package/src/config/skills.ts +37 -0
- package/src/daemon/handlers/apps.ts +28 -3
- package/src/daemon/main.ts +1 -0
package/package.json
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Compiler for multi-file TSX apps.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Shells out to the esbuild CLI binary (JIT-downloaded on first use) to
|
|
5
|
+
* compile src/main.tsx -> dist/main.js, then copies index.html with
|
|
6
|
+
* script/style tag injection.
|
|
7
|
+
*
|
|
8
|
+
* This avoids importing esbuild's JS API (which caches its native binary
|
|
9
|
+
* path at module load time and breaks inside bun --compile's /$bunfs/).
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
import { existsSync, rmSync } from "node:fs";
|
|
9
13
|
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
import { build, type Message, type Plugin } from "esbuild";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
13
15
|
|
|
14
16
|
import { getLogger } from "../util/logger.js";
|
|
17
|
+
import { ensureCompilerTools } from "./compiler-tools.js";
|
|
15
18
|
import {
|
|
16
|
-
ALLOWED_PACKAGES,
|
|
17
19
|
getCacheDir,
|
|
18
20
|
isBareImport,
|
|
19
21
|
packageName,
|
|
@@ -34,19 +36,70 @@ export interface CompileResult {
|
|
|
34
36
|
durationMs: number;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Parse esbuild CLI stderr into structured diagnostics.
|
|
41
|
+
* esbuild outputs errors like:
|
|
42
|
+
* ✘ [ERROR] Could not resolve "foo"
|
|
43
|
+
* src/main.tsx:3:7:
|
|
44
|
+
*/
|
|
45
|
+
function parseEsbuildStderr(stderr: string): {
|
|
46
|
+
errors: CompileDiagnostic[];
|
|
47
|
+
warnings: CompileDiagnostic[];
|
|
48
|
+
} {
|
|
49
|
+
const errors: CompileDiagnostic[] = [];
|
|
50
|
+
const warnings: CompileDiagnostic[] = [];
|
|
51
|
+
const lines = stderr.split("\n");
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < lines.length; i++) {
|
|
54
|
+
const errorMatch = lines[i].match(/✘ \[ERROR\] (.+)/);
|
|
55
|
+
const warnMatch = lines[i].match(/▲ \[WARNING\] (.+)/);
|
|
56
|
+
|
|
57
|
+
if (errorMatch || warnMatch) {
|
|
58
|
+
const text = (errorMatch ?? warnMatch)![1];
|
|
59
|
+
const diag: CompileDiagnostic = { text };
|
|
60
|
+
|
|
61
|
+
// Next non-empty line may have location: " file:line:col:"
|
|
62
|
+
const locLine = lines[i + 1]?.trim();
|
|
63
|
+
if (locLine) {
|
|
64
|
+
const locMatch = locLine.match(/^(.+):(\d+):(\d+):?$/);
|
|
65
|
+
if (locMatch) {
|
|
66
|
+
diag.location = {
|
|
67
|
+
file: locMatch[1],
|
|
68
|
+
line: parseInt(locMatch[2], 10),
|
|
69
|
+
column: parseInt(locMatch[3], 10),
|
|
70
|
+
};
|
|
47
71
|
}
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (errorMatch) errors.push(diag);
|
|
75
|
+
else warnings.push(diag);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { errors, warnings };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Scan source files for bare import specifiers and pre-install any
|
|
84
|
+
* allowlisted packages into the shared cache so esbuild can resolve them.
|
|
85
|
+
*/
|
|
86
|
+
async function resolveAppImports(srcDir: string): Promise<void> {
|
|
87
|
+
const importRe = /(?:import|from)\s+["']([^"'.][^"']*)["']/g;
|
|
88
|
+
const seen = new Set<string>();
|
|
89
|
+
|
|
90
|
+
const files = await readdir(srcDir, { recursive: true });
|
|
91
|
+
for (const file of files) {
|
|
92
|
+
if (!/\.[jt]sx?$/.test(String(file))) continue;
|
|
93
|
+
const content = await readFile(join(srcDir, String(file)), "utf-8");
|
|
94
|
+
for (const match of content.matchAll(importRe)) {
|
|
95
|
+
const specifier = match[1];
|
|
96
|
+
if (!isBareImport(specifier)) continue;
|
|
97
|
+
const pkg = packageName(specifier);
|
|
98
|
+
if (seen.has(pkg)) continue;
|
|
99
|
+
seen.add(pkg);
|
|
100
|
+
await resolvePackage(pkg);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
50
103
|
}
|
|
51
104
|
|
|
52
105
|
/**
|
|
@@ -68,95 +121,70 @@ export async function compileApp(appDir: string): Promise<CompileResult> {
|
|
|
68
121
|
}
|
|
69
122
|
await mkdir(distDir, { recursive: true });
|
|
70
123
|
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
const preactDir = resolve(
|
|
74
|
-
import.meta.dirname ?? __dirname,
|
|
75
|
-
"../../node_modules/preact",
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
// Plugin that resolves bare third-party imports against the allowlist
|
|
79
|
-
const resolvePlugin: Plugin = {
|
|
80
|
-
name: "vellum-package-resolver",
|
|
81
|
-
setup(pluginBuild) {
|
|
82
|
-
pluginBuild.onResolve({ filter: /.*/ }, async (args) => {
|
|
83
|
-
// Only intercept bare specifiers (not relative, not preact/react aliases)
|
|
84
|
-
if (
|
|
85
|
-
args.kind !== "import-statement" &&
|
|
86
|
-
args.kind !== "dynamic-import"
|
|
87
|
-
) {
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
if (!isBareImport(args.path)) return undefined;
|
|
91
|
-
|
|
92
|
-
const pkg = packageName(args.path);
|
|
93
|
-
const nodeModulesDir = await resolvePackage(pkg);
|
|
94
|
-
|
|
95
|
-
if (nodeModulesDir) {
|
|
96
|
-
// Let esbuild resolve normally — nodePaths will pick it up
|
|
97
|
-
return undefined;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Not allowed — produce a clear error
|
|
101
|
-
return {
|
|
102
|
-
errors: [
|
|
103
|
-
{
|
|
104
|
-
text: `Package '${pkg}' is not in the allowed list. Allowed: ${ALLOWED_PACKAGES.join(", ")}`,
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
};
|
|
108
|
-
});
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const cacheNodeModules = join(getCacheDir(), "node_modules");
|
|
113
|
-
|
|
114
|
-
let result;
|
|
124
|
+
// JIT download esbuild binary + preact on first use
|
|
125
|
+
let tools;
|
|
115
126
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
bundle: true,
|
|
119
|
-
minify: true,
|
|
120
|
-
sourcemap: false,
|
|
121
|
-
outdir: distDir,
|
|
122
|
-
format: "esm",
|
|
123
|
-
target: ["es2022"],
|
|
124
|
-
jsx: "automatic",
|
|
125
|
-
jsxImportSource: "preact",
|
|
126
|
-
loader: {
|
|
127
|
-
".tsx": "tsx",
|
|
128
|
-
".ts": "ts",
|
|
129
|
-
".jsx": "jsx",
|
|
130
|
-
".js": "js",
|
|
131
|
-
".css": "css",
|
|
132
|
-
},
|
|
133
|
-
alias: {
|
|
134
|
-
react: "preact/compat",
|
|
135
|
-
"react-dom": "preact/compat",
|
|
136
|
-
},
|
|
137
|
-
plugins: [resolvePlugin],
|
|
138
|
-
// Point esbuild at assistant's preact and at the shared package cache
|
|
139
|
-
nodePaths: [resolve(preactDir, ".."), cacheNodeModules],
|
|
140
|
-
logLevel: "silent",
|
|
141
|
-
});
|
|
142
|
-
} catch (err: unknown) {
|
|
143
|
-
// esbuild throws on hard failures (e.g. syntax errors) with .errors/.warnings
|
|
144
|
-
const esbuildErr = err as {
|
|
145
|
-
errors?: Message[];
|
|
146
|
-
warnings?: Message[];
|
|
147
|
-
};
|
|
127
|
+
tools = await ensureCompilerTools();
|
|
128
|
+
} catch (err) {
|
|
148
129
|
const durationMs = Math.round(performance.now() - start);
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
130
|
+
const text = err instanceof Error ? err.message : String(err);
|
|
131
|
+
log.error({ err, durationMs }, "Failed to ensure compiler tools");
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
errors: [{ text: `Compiler setup failed: ${text}` }],
|
|
135
|
+
warnings: [],
|
|
136
|
+
durationMs,
|
|
137
|
+
};
|
|
153
138
|
}
|
|
154
139
|
|
|
155
|
-
|
|
156
|
-
|
|
140
|
+
// Scan source files for bare imports and JIT-install allowed packages
|
|
141
|
+
await resolveAppImports(srcDir);
|
|
157
142
|
|
|
158
|
-
|
|
143
|
+
// Build NODE_PATH: preact parent dir + shared package cache
|
|
144
|
+
const preactParent = dirname(tools.preactDir);
|
|
145
|
+
const cacheNodeModules = join(getCacheDir(), "node_modules");
|
|
146
|
+
const nodePath = [preactParent, cacheNodeModules]
|
|
147
|
+
.filter((p) => existsSync(p))
|
|
148
|
+
.join(":");
|
|
149
|
+
|
|
150
|
+
// Shell out to esbuild CLI
|
|
151
|
+
const args = [
|
|
152
|
+
entryPoint,
|
|
153
|
+
"--bundle",
|
|
154
|
+
"--minify",
|
|
155
|
+
`--outdir=${distDir}`,
|
|
156
|
+
"--format=esm",
|
|
157
|
+
"--target=es2022",
|
|
158
|
+
"--jsx=automatic",
|
|
159
|
+
"--jsx-import-source=preact",
|
|
160
|
+
"--alias:react=preact/compat",
|
|
161
|
+
"--alias:react-dom=preact/compat",
|
|
162
|
+
"--loader:.tsx=tsx",
|
|
163
|
+
"--loader:.ts=ts",
|
|
164
|
+
"--loader:.jsx=jsx",
|
|
165
|
+
"--loader:.js=js",
|
|
166
|
+
"--loader:.css=css",
|
|
167
|
+
"--log-level=warning",
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
const proc = Bun.spawn({
|
|
171
|
+
cmd: [tools.esbuildBin, ...args],
|
|
172
|
+
cwd: appDir,
|
|
173
|
+
stdout: "pipe",
|
|
174
|
+
stderr: "pipe",
|
|
175
|
+
env: { ...process.env, NODE_PATH: nodePath },
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await proc.exited;
|
|
179
|
+
const stderr = await new Response(proc.stderr).text();
|
|
180
|
+
|
|
181
|
+
if (proc.exitCode !== 0) {
|
|
159
182
|
const durationMs = Math.round(performance.now() - start);
|
|
183
|
+
const { errors, warnings } = parseEsbuildStderr(stderr);
|
|
184
|
+
// If parsing found nothing, use raw stderr as the error
|
|
185
|
+
if (errors.length === 0 && stderr.trim()) {
|
|
186
|
+
errors.push({ text: stderr.trim() });
|
|
187
|
+
}
|
|
160
188
|
log.info({ durationMs, errorCount: errors.length }, "Build failed");
|
|
161
189
|
return { ok: false, errors, warnings, durationMs };
|
|
162
190
|
}
|
|
@@ -191,5 +219,5 @@ export async function compileApp(appDir: string): Promise<CompileResult> {
|
|
|
191
219
|
|
|
192
220
|
const durationMs = Math.round(performance.now() - start);
|
|
193
221
|
log.info({ durationMs }, "Build succeeded");
|
|
194
|
-
return { ok: true, errors, warnings, durationMs };
|
|
222
|
+
return { ok: true, errors: [], warnings: [], durationMs };
|
|
195
223
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIT download and cache of esbuild binary + preact for app compilation.
|
|
3
|
+
*
|
|
4
|
+
* Instead of shipping these in the .app bundle (~11 MB), we download them
|
|
5
|
+
* on first compile to ~/.vellum/workspace/compiler-tools/. Follows the
|
|
6
|
+
* same pattern as EmbeddingRuntimeManager.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
chmodSync,
|
|
11
|
+
existsSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
readFileSync,
|
|
14
|
+
rmSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
import { arch, homedir, platform } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
|
|
20
|
+
import { getLogger } from "../util/logger.js";
|
|
21
|
+
import { PromiseGuard } from "../util/promise-guard.js";
|
|
22
|
+
|
|
23
|
+
const log = getLogger("compiler-tools");
|
|
24
|
+
|
|
25
|
+
// Pinned versions matching assistant/bun.lock
|
|
26
|
+
const ESBUILD_VERSION = "0.24.2";
|
|
27
|
+
const PREACT_VERSION = "10.28.4";
|
|
28
|
+
|
|
29
|
+
const TOOLS_VERSION = `esbuild-${ESBUILD_VERSION}_preact-${PREACT_VERSION}`;
|
|
30
|
+
|
|
31
|
+
export interface CompilerTools {
|
|
32
|
+
esbuildBin: string;
|
|
33
|
+
preactDir: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface VersionManifest {
|
|
37
|
+
toolsVersion: string;
|
|
38
|
+
esbuildVersion: string;
|
|
39
|
+
preactVersion: string;
|
|
40
|
+
platform: string;
|
|
41
|
+
arch: string;
|
|
42
|
+
installedAt: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getToolsDir(): string {
|
|
46
|
+
return join(homedir(), ".vellum", "workspace", "compiler-tools");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const installGuard = new PromiseGuard<void>();
|
|
50
|
+
|
|
51
|
+
function npmTarballUrl(pkg: string, version: string): string {
|
|
52
|
+
const encoded = pkg.replace("/", "%2f");
|
|
53
|
+
const basename = pkg.startsWith("@") ? pkg.split("/")[1] : pkg;
|
|
54
|
+
return `https://registry.npmjs.org/${encoded}/-/${basename}-${version}.tgz`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function downloadAndExtract(
|
|
58
|
+
url: string,
|
|
59
|
+
targetDir: string,
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
log.info({ url, targetDir }, "Downloading npm package");
|
|
62
|
+
|
|
63
|
+
const response = await fetch(url);
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Failed to download ${url}: ${response.status} ${response.statusText}`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const tarball = await response.arrayBuffer();
|
|
71
|
+
mkdirSync(targetDir, { recursive: true });
|
|
72
|
+
|
|
73
|
+
const tmpTar = join(targetDir, `download-${Date.now()}.tgz`);
|
|
74
|
+
writeFileSync(tmpTar, Buffer.from(tarball));
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const proc = Bun.spawn({
|
|
78
|
+
cmd: ["tar", "xzf", tmpTar, "-C", targetDir, "--strip-components=1"],
|
|
79
|
+
stdout: "ignore",
|
|
80
|
+
stderr: "pipe",
|
|
81
|
+
});
|
|
82
|
+
await proc.exited;
|
|
83
|
+
if (proc.exitCode !== 0) {
|
|
84
|
+
const stderr = await new Response(proc.stderr).text();
|
|
85
|
+
throw new Error(`Failed to extract ${url}: ${stderr}`);
|
|
86
|
+
}
|
|
87
|
+
} finally {
|
|
88
|
+
try {
|
|
89
|
+
rmSync(tmpTar);
|
|
90
|
+
} catch {
|
|
91
|
+
/* ignore */
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readManifest(baseDir: string): VersionManifest | null {
|
|
97
|
+
const manifestPath = join(baseDir, "version.json");
|
|
98
|
+
if (!existsSync(manifestPath)) return null;
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isReady(baseDir: string): boolean {
|
|
107
|
+
const manifest = readManifest(baseDir);
|
|
108
|
+
if (!manifest || manifest.toolsVersion !== TOOLS_VERSION) return false;
|
|
109
|
+
return (
|
|
110
|
+
existsSync(join(baseDir, "bin", "esbuild")) &&
|
|
111
|
+
existsSync(join(baseDir, "node_modules", "preact"))
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Ensure esbuild binary + preact are downloaded and cached.
|
|
117
|
+
* Safe to call concurrently — deduplicates via PromiseGuard.
|
|
118
|
+
*/
|
|
119
|
+
export async function ensureCompilerTools(): Promise<CompilerTools> {
|
|
120
|
+
const baseDir = getToolsDir();
|
|
121
|
+
|
|
122
|
+
if (!isReady(baseDir)) {
|
|
123
|
+
await installGuard.run(() => install(baseDir));
|
|
124
|
+
if (!isReady(baseDir)) {
|
|
125
|
+
installGuard.reset();
|
|
126
|
+
throw new Error("Compiler tools installation failed");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
esbuildBin: join(baseDir, "bin", "esbuild"),
|
|
132
|
+
preactDir: join(baseDir, "node_modules", "preact"),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function install(baseDir: string): Promise<void> {
|
|
137
|
+
if (isReady(baseDir)) return;
|
|
138
|
+
|
|
139
|
+
const os = platform();
|
|
140
|
+
const cpu = arch();
|
|
141
|
+
log.info(
|
|
142
|
+
{ os, cpu, toolsVersion: TOOLS_VERSION },
|
|
143
|
+
"Installing compiler tools",
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
mkdirSync(baseDir, { recursive: true });
|
|
147
|
+
|
|
148
|
+
// Lock file to prevent concurrent cross-process installs
|
|
149
|
+
const lockPath = join(baseDir, ".downloading");
|
|
150
|
+
if (existsSync(lockPath)) {
|
|
151
|
+
try {
|
|
152
|
+
const lockPid = parseInt(readFileSync(lockPath, "utf-8").trim(), 10);
|
|
153
|
+
if (!isNaN(lockPid) && lockPid !== process.pid) {
|
|
154
|
+
try {
|
|
155
|
+
process.kill(lockPid, 0);
|
|
156
|
+
log.info(
|
|
157
|
+
{ lockPid },
|
|
158
|
+
"Another process is installing compiler tools, waiting",
|
|
159
|
+
);
|
|
160
|
+
// Wait up to 60s for the other process to finish
|
|
161
|
+
for (let i = 0; i < 60; i++) {
|
|
162
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
163
|
+
if (isReady(baseDir)) return;
|
|
164
|
+
}
|
|
165
|
+
log.warn("Timed out waiting for other installer, proceeding");
|
|
166
|
+
} catch {
|
|
167
|
+
log.info({ lockPid }, "Cleaning up stale compiler tools lock");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
// Can't read lock, proceed
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
writeFileSync(lockPath, String(process.pid));
|
|
176
|
+
|
|
177
|
+
const tmpDir = join(baseDir, `.installing-${Date.now()}`);
|
|
178
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
// Determine esbuild platform package name
|
|
182
|
+
const esbuildPlatform =
|
|
183
|
+
os === "darwin"
|
|
184
|
+
? cpu === "arm64"
|
|
185
|
+
? "darwin-arm64"
|
|
186
|
+
: "darwin-x64"
|
|
187
|
+
: cpu === "arm64"
|
|
188
|
+
? "linux-arm64"
|
|
189
|
+
: "linux-x64";
|
|
190
|
+
|
|
191
|
+
// Download esbuild binary + preact in parallel
|
|
192
|
+
await Promise.all([
|
|
193
|
+
downloadAndExtract(
|
|
194
|
+
npmTarballUrl(`@esbuild/${esbuildPlatform}`, ESBUILD_VERSION),
|
|
195
|
+
join(tmpDir, "esbuild-pkg"),
|
|
196
|
+
),
|
|
197
|
+
downloadAndExtract(
|
|
198
|
+
npmTarballUrl("preact", PREACT_VERSION),
|
|
199
|
+
join(tmpDir, "node_modules", "preact"),
|
|
200
|
+
),
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
// Move esbuild binary to bin/
|
|
204
|
+
const esbuildBinSrc = join(tmpDir, "esbuild-pkg", "bin", "esbuild");
|
|
205
|
+
const binDir = join(tmpDir, "bin");
|
|
206
|
+
mkdirSync(binDir, { recursive: true });
|
|
207
|
+
const { renameSync } = await import("node:fs");
|
|
208
|
+
renameSync(esbuildBinSrc, join(binDir, "esbuild"));
|
|
209
|
+
chmodSync(join(binDir, "esbuild"), 0o755);
|
|
210
|
+
rmSync(join(tmpDir, "esbuild-pkg"), { recursive: true, force: true });
|
|
211
|
+
|
|
212
|
+
// Write version manifest
|
|
213
|
+
const manifest: VersionManifest = {
|
|
214
|
+
toolsVersion: TOOLS_VERSION,
|
|
215
|
+
esbuildVersion: ESBUILD_VERSION,
|
|
216
|
+
preactVersion: PREACT_VERSION,
|
|
217
|
+
platform: os,
|
|
218
|
+
arch: cpu,
|
|
219
|
+
installedAt: new Date().toISOString(),
|
|
220
|
+
};
|
|
221
|
+
writeFileSync(
|
|
222
|
+
join(tmpDir, "version.json"),
|
|
223
|
+
JSON.stringify(manifest, null, 2) + "\n",
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Atomic swap: clear old install, move new files in
|
|
227
|
+
const { readdirSync } = await import("node:fs");
|
|
228
|
+
for (const entry of readdirSync(baseDir)) {
|
|
229
|
+
if (entry.startsWith(".") || entry === tmpDir.split("/").pop()) continue;
|
|
230
|
+
rmSync(join(baseDir, entry), { recursive: true, force: true });
|
|
231
|
+
}
|
|
232
|
+
for (const entry of readdirSync(tmpDir)) {
|
|
233
|
+
renameSync(join(tmpDir, entry), join(baseDir, entry));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
log.info({ toolsVersion: TOOLS_VERSION }, "Compiler tools installed");
|
|
237
|
+
} catch (err) {
|
|
238
|
+
log.error({ err }, "Failed to install compiler tools");
|
|
239
|
+
throw err;
|
|
240
|
+
} finally {
|
|
241
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
242
|
+
try {
|
|
243
|
+
rmSync(lockPath);
|
|
244
|
+
} catch {
|
|
245
|
+
/* ignore */
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|