@mistweaverco/kulala-core 0.1.1 → 0.1.3
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/README.md +4 -0
- package/dist/index.js +27 -35
- package/dist/index.js.map +4 -5
- package/dist/lib/runner/embedded-curl.d.ts.map +1 -1
- package/package.json +4 -2
- package/scripts/ensure-vendored-curl.ts +216 -0
- package/scripts/generate-vendored-curl.ts +108 -0
- package/dist/curl-1ra9mcaj. +0 -0
- package/dist/lib/runner/vendored-curl.generated.d.ts +0 -5
- package/dist/lib/runner/vendored-curl.generated.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embedded-curl.d.ts","sourceRoot":"","sources":["../../../src/lib/runner/embedded-curl.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"embedded-curl.d.ts","sourceRoot":"","sources":["../../../src/lib/runner/embedded-curl.ts"],"names":[],"mappings":"AAuGA,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CA4BvD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mistweaverco/kulala-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"dist",
|
|
29
|
+
"scripts",
|
|
29
30
|
"README.md",
|
|
30
31
|
"LICENSE"
|
|
31
32
|
],
|
|
@@ -33,8 +34,9 @@
|
|
|
33
34
|
"format": "prettier --write .",
|
|
34
35
|
"lint": "prettier --check . && eslint .",
|
|
35
36
|
"test": "bun test",
|
|
37
|
+
"postinstall": "bun run ./scripts/ensure-vendored-curl.ts",
|
|
36
38
|
"build": "bun run build:js && bun run build:types",
|
|
37
|
-
"build:js": "bun build src/index.ts --outdir dist --format esm --target bun --sourcemap",
|
|
39
|
+
"build:js": "bun build src/index.ts --outdir dist --format esm --target bun --sourcemap --define __KULALA_EMBED_CURL__=false",
|
|
38
40
|
"build:types": "tsc -p tsconfig.build.json",
|
|
39
41
|
"prepack": "cp ./../../README.md . && cp ./../../LICENSE . && bun run build"
|
|
40
42
|
},
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { chmod, mkdir, rm, writeFile, readFile } from "node:fs/promises";
|
|
2
|
+
import { constants as fsConstants } from "node:fs";
|
|
3
|
+
import { access } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
|
|
10
|
+
type Platform = "linux" | "darwin" | "win32";
|
|
11
|
+
type Arch = "x64" | "arm64";
|
|
12
|
+
|
|
13
|
+
const CURL_VERSION = "8.19.0";
|
|
14
|
+
|
|
15
|
+
type DownloadSpec = {
|
|
16
|
+
url: string;
|
|
17
|
+
expectedSha256: string;
|
|
18
|
+
archiveExeName: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Source: https://curl.se/download.html -> "Packages" table (prebuilt binaries by providers).
|
|
22
|
+
// We pin to a fixed version and hard-code URLs + SHA256 for reproducibility.
|
|
23
|
+
const DOWNLOAD_SPECS: Record<string, DownloadSpec> = {
|
|
24
|
+
"linux-x64": {
|
|
25
|
+
url: `https://github.com/stunnel/static-curl/releases/download/${CURL_VERSION}/curl-linux-x86_64-musl-${CURL_VERSION}.tar.xz`,
|
|
26
|
+
expectedSha256:
|
|
27
|
+
"3c5c62815d4a12bebd10c2884038d68102c81e5c058eaad1b3c3e66343634152",
|
|
28
|
+
archiveExeName: "curl",
|
|
29
|
+
},
|
|
30
|
+
"linux-arm64": {
|
|
31
|
+
url: `https://github.com/stunnel/static-curl/releases/download/${CURL_VERSION}/curl-linux-aarch64-musl-${CURL_VERSION}.tar.xz`,
|
|
32
|
+
expectedSha256:
|
|
33
|
+
"c60f5718765bfc83bff10e7aa8eada2e629e009738b38683164a19482dd43a53",
|
|
34
|
+
archiveExeName: "curl",
|
|
35
|
+
},
|
|
36
|
+
"darwin-arm64": {
|
|
37
|
+
url: `https://github.com/stunnel/static-curl/releases/download/${CURL_VERSION}/curl-macos-arm64-${CURL_VERSION}.tar.xz`,
|
|
38
|
+
expectedSha256:
|
|
39
|
+
"210adf449293bb55e86a7ce26e32a8b00e7fafec570c24d2fe82bec3ff4ac090",
|
|
40
|
+
archiveExeName: "curl",
|
|
41
|
+
},
|
|
42
|
+
"darwin-x64": {
|
|
43
|
+
url: `https://github.com/stunnel/static-curl/releases/download/${CURL_VERSION}/curl-macos-x86_64-${CURL_VERSION}.tar.xz`,
|
|
44
|
+
expectedSha256:
|
|
45
|
+
"732a165fd450f12bbb01170bb99a6cd00b904d1eeccbe5ecd3e25220007d73f2",
|
|
46
|
+
archiveExeName: "curl",
|
|
47
|
+
},
|
|
48
|
+
"win32-x64": {
|
|
49
|
+
url: `https://github.com/stunnel/static-curl/releases/download/${CURL_VERSION}/curl-windows-x86_64-${CURL_VERSION}.tar.xz`,
|
|
50
|
+
expectedSha256:
|
|
51
|
+
"40241a577f01e06d1ff31e9971fe5b188705f9a4ea9c26133eab237ed8c1528c",
|
|
52
|
+
archiveExeName: "curl.exe",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function platformVendorSubdir(): string {
|
|
57
|
+
const plat = process.platform as Platform;
|
|
58
|
+
const arch = process.arch as Arch;
|
|
59
|
+
return `${plat}-${arch}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function exeName(): string {
|
|
63
|
+
return process.platform === "win32" ? "curl.exe" : "curl";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function cacheBaseDir(): string {
|
|
67
|
+
const explicit = process.env.KULALA_CORE_CURL_CACHE_DIR;
|
|
68
|
+
if (explicit) return explicit;
|
|
69
|
+
|
|
70
|
+
// Respect XDG on any platform if explicitly set.
|
|
71
|
+
const xdg = process.env.XDG_CACHE_HOME;
|
|
72
|
+
if (xdg) return join(xdg, "kulala");
|
|
73
|
+
|
|
74
|
+
if (process.platform === "win32") {
|
|
75
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
76
|
+
if (localAppData) return join(localAppData, "kulala");
|
|
77
|
+
const appData = process.env.APPDATA;
|
|
78
|
+
if (appData) return join(appData, "kulala");
|
|
79
|
+
const userProfile = process.env.USERPROFILE;
|
|
80
|
+
if (userProfile) return join(userProfile, ".cache", "kulala");
|
|
81
|
+
return join(homedir(), ".cache", "kulala");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (process.platform === "darwin") {
|
|
85
|
+
return join(homedir(), "Library", "Caches", "kulala");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Linux and other unix-likes.
|
|
89
|
+
return join(homedir(), ".cache", "kulala");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function fileIsExecutable(path: string): Promise<boolean> {
|
|
93
|
+
try {
|
|
94
|
+
await access(path, fsConstants.X_OK);
|
|
95
|
+
return true;
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function downloadSpec(): DownloadSpec {
|
|
102
|
+
const plat = process.platform as Platform;
|
|
103
|
+
const arch = process.arch as Arch;
|
|
104
|
+
const key = `${plat}-${arch}`;
|
|
105
|
+
const spec = DOWNLOAD_SPECS[key];
|
|
106
|
+
if (!spec) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Unsupported platform/arch for automatic curl download: ${key}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return spec;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function download(url: string): Promise<Uint8Array> {
|
|
115
|
+
const res = await fetch(url, {
|
|
116
|
+
redirect: "follow",
|
|
117
|
+
headers: {
|
|
118
|
+
"User-Agent": "kulala-core(postinstall)",
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
123
|
+
}
|
|
124
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
125
|
+
if (buf.byteLength < 1024 * 64) {
|
|
126
|
+
// sanity check: a curl binary shouldn't be tiny (avoids saving HTML error pages)
|
|
127
|
+
throw new Error(`Downloaded file too small (${buf.byteLength} bytes)`);
|
|
128
|
+
}
|
|
129
|
+
return buf;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function sha256Hex(bytes: Uint8Array): string {
|
|
133
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function extractTarXz(
|
|
137
|
+
archivePath: string,
|
|
138
|
+
outDir: string,
|
|
139
|
+
): Promise<void> {
|
|
140
|
+
await new Promise<void>((resolve, reject) => {
|
|
141
|
+
const child = spawn("tar", ["-xJf", archivePath, "-C", outDir], {
|
|
142
|
+
stdio: "pipe",
|
|
143
|
+
});
|
|
144
|
+
let stderr = "";
|
|
145
|
+
child.stderr.on("data", (d) => (stderr += d.toString()));
|
|
146
|
+
child.on("error", reject);
|
|
147
|
+
child.on("close", (code) => {
|
|
148
|
+
if (code === 0) resolve();
|
|
149
|
+
else reject(new Error(stderr.trim() || `tar exited with code ${code}`));
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function main(): Promise<void> {
|
|
155
|
+
const exe = exeName();
|
|
156
|
+
const subdir = platformVendorSubdir();
|
|
157
|
+
const targetDir = join(cacheBaseDir(), "curl", subdir);
|
|
158
|
+
const targetPath = join(targetDir, exe);
|
|
159
|
+
|
|
160
|
+
if (await fileIsExecutable(targetPath)) return;
|
|
161
|
+
|
|
162
|
+
await mkdir(targetDir, { recursive: true });
|
|
163
|
+
const { url, expectedSha256, archiveExeName } = downloadSpec();
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const tmpBase = join(tmpdir(), `kulala-core-curl-${randomUUID()}`);
|
|
167
|
+
await mkdir(tmpBase, { recursive: true });
|
|
168
|
+
const archivePath = join(tmpBase, "curl.tar.xz");
|
|
169
|
+
|
|
170
|
+
const archiveBytes = await download(url);
|
|
171
|
+
await writeFile(archivePath, archiveBytes);
|
|
172
|
+
|
|
173
|
+
await extractTarXz(archivePath, targetDir);
|
|
174
|
+
|
|
175
|
+
const extractedPath = join(targetDir, archiveExeName);
|
|
176
|
+
const extractedBytes = new Uint8Array(await readFile(extractedPath));
|
|
177
|
+
const actualSha = sha256Hex(extractedBytes);
|
|
178
|
+
if (actualSha !== expectedSha256) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`SHA256 mismatch for ${archiveExeName}: expected ${expectedSha256} got ${actualSha}`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Ensure final name matches what runtime expects (curl / curl.exe).
|
|
185
|
+
if (archiveExeName !== exe) {
|
|
186
|
+
// Shouldn't happen with our mapping, but keep it safe.
|
|
187
|
+
await writeFile(targetPath, extractedBytes, { mode: 0o755 });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (process.platform !== "win32") {
|
|
191
|
+
await chmod(targetPath, 0o755);
|
|
192
|
+
}
|
|
193
|
+
// Windows tar includes a CA bundle; keep it readable.
|
|
194
|
+
// (No-op on other platforms if file isn't present.)
|
|
195
|
+
try {
|
|
196
|
+
const caPath = join(targetDir, "curl-ca-bundle.crt");
|
|
197
|
+
await chmod(caPath, 0o644);
|
|
198
|
+
} catch {
|
|
199
|
+
// ignore
|
|
200
|
+
}
|
|
201
|
+
process.stderr.write(`kulala-core: downloaded curl to ${targetPath}\n`);
|
|
202
|
+
|
|
203
|
+
await rm(tmpBase, { recursive: true, force: true });
|
|
204
|
+
} catch (err) {
|
|
205
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
206
|
+
process.stderr.write(
|
|
207
|
+
`kulala-core: could not download vendored curl (${url}): ${msg}\n`,
|
|
208
|
+
);
|
|
209
|
+
process.stderr.write(
|
|
210
|
+
`kulala-core: set KULALA_CURL_PATH to an existing curl.\n`,
|
|
211
|
+
);
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await main();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
|
|
6
|
+
type Platform = "linux" | "darwin" | "win32";
|
|
7
|
+
type Arch = "x64" | "arm64";
|
|
8
|
+
|
|
9
|
+
function parseTargetArg(argv: string[]): {
|
|
10
|
+
platform: Platform;
|
|
11
|
+
arch: Arch;
|
|
12
|
+
bunTarget?: string;
|
|
13
|
+
} {
|
|
14
|
+
const targetArg = argv.find((a) => a.startsWith("--target="));
|
|
15
|
+
if (targetArg) {
|
|
16
|
+
const bunTarget = targetArg.slice("--target=".length);
|
|
17
|
+
// Expected: bun-linux-x64, bun-darwin-arm64, bun-windows-x64, ...
|
|
18
|
+
const m = bunTarget.match(/^bun-(linux|darwin|windows)-(x64|arm64)/);
|
|
19
|
+
if (m) {
|
|
20
|
+
const platform = (m[1] === "windows" ? "win32" : m[1]) as Platform;
|
|
21
|
+
const arch = m[2] as Arch;
|
|
22
|
+
return { platform, arch, bunTarget };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
platform: process.platform as Platform,
|
|
27
|
+
arch: process.arch as Arch,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { platform, arch, bunTarget } = parseTargetArg(process.argv.slice(2));
|
|
32
|
+
const subdir = `${platform}-${arch}`;
|
|
33
|
+
const exe = platform === "win32" ? "curl.exe" : "curl";
|
|
34
|
+
|
|
35
|
+
function cacheBaseDir(): string {
|
|
36
|
+
const explicit = process.env.KULALA_CORE_CURL_CACHE_DIR;
|
|
37
|
+
if (explicit) return explicit;
|
|
38
|
+
|
|
39
|
+
// Respect XDG on any platform if explicitly set.
|
|
40
|
+
const xdg = process.env.XDG_CACHE_HOME;
|
|
41
|
+
if (xdg) return join(xdg, "kulala");
|
|
42
|
+
|
|
43
|
+
if (process.platform === "win32") {
|
|
44
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
45
|
+
if (localAppData) return join(localAppData, "kulala");
|
|
46
|
+
const appData = process.env.APPDATA;
|
|
47
|
+
if (appData) return join(appData, "kulala");
|
|
48
|
+
const userProfile = process.env.USERPROFILE;
|
|
49
|
+
if (userProfile) return join(userProfile, ".cache", "kulala");
|
|
50
|
+
return join(homedir(), ".cache", "kulala");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (process.platform === "darwin") {
|
|
54
|
+
return join(homedir(), "Library", "Caches", "kulala");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Linux and other unix-likes.
|
|
58
|
+
return join(homedir(), ".cache", "kulala");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveCurlAssetImportPath(): string {
|
|
62
|
+
// Prefer user-writable cached curl (populated by postinstall).
|
|
63
|
+
const cached = join(cacheBaseDir(), "curl", subdir, exe);
|
|
64
|
+
if (existsSync(cached)) return cached;
|
|
65
|
+
|
|
66
|
+
// Fallback to repo vendored curl (populated during build/release).
|
|
67
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
68
|
+
const repoRoot = join(scriptDir, "..", "..", "..");
|
|
69
|
+
const vendored = join(repoRoot, "vendor", "curl", subdir, exe);
|
|
70
|
+
if (existsSync(vendored)) return vendored;
|
|
71
|
+
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Missing curl binary for ${subdir}/${exe}. Expected one of:\n` +
|
|
74
|
+
`- ${cached} (run postinstall / packages/core/scripts/ensure-vendored-curl.ts)\n` +
|
|
75
|
+
`- ${vendored} (populate vendor/curl for releases)\n`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const assetImportPath = resolveCurlAssetImportPath();
|
|
80
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
81
|
+
const outPath = join(
|
|
82
|
+
scriptDir,
|
|
83
|
+
"..",
|
|
84
|
+
"src",
|
|
85
|
+
"lib",
|
|
86
|
+
"runner",
|
|
87
|
+
"vendored-curl.embed.generated.ts",
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const content = `// @ts-nocheck — generated; gitignored; import path is machine-specific (see packages/core/scripts/generate-vendored-curl.ts)
|
|
91
|
+
// This file is generated by packages/core/scripts/generate-vendored-curl.ts
|
|
92
|
+
// It exists to let Bun embed the platform-specific vendored curl binary
|
|
93
|
+
// into the single executable produced by \`bun build --compile\` with \`__KULALA_EMBED_CURL__=true\`.
|
|
94
|
+
|
|
95
|
+
import curlAsset from "${assetImportPath}" with { type: "file" };
|
|
96
|
+
|
|
97
|
+
export async function getVendoredCurl(): Promise<{ bytes: Buffer; filename: string } | null> {
|
|
98
|
+
const bytes = Buffer.from(await Bun.file(curlAsset).arrayBuffer());
|
|
99
|
+
return { bytes, filename: "${exe}" };
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
writeFileSync(outPath, content, "utf-8");
|
|
104
|
+
console.log(
|
|
105
|
+
`generated ${outPath} for ${subdir}/${exe}${
|
|
106
|
+
bunTarget ? ` (${bunTarget})` : ""
|
|
107
|
+
} from ${assetImportPath}`,
|
|
108
|
+
);
|
package/dist/curl-1ra9mcaj.
DELETED
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vendored-curl.generated.d.ts","sourceRoot":"","sources":["../../../src/lib/runner/vendored-curl.generated.ts"],"names":[],"mappings":"AAOA,wBAAsB,eAAe,IAAI,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAG3F"}
|