@blaxel/core 0.3.26 → 0.3.27-preview.317
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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/common/settings.js +2 -2
- package/dist/cjs/image/image.js +42 -18
- package/dist/cjs/image/image.test.js +30 -5
- package/dist/cjs/types/image/image.d.ts +5 -0
- package/dist/cjs-browser/.tsbuildinfo +1 -1
- package/dist/cjs-browser/common/settings.js +2 -2
- package/dist/cjs-browser/image/image.test.js +30 -5
- package/dist/cjs-browser/types/image/image.d.ts +5 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/common/settings.js +2 -2
- package/dist/esm/image/image.js +42 -18
- package/dist/esm/image/image.test.js +30 -5
- package/dist/esm-browser/.tsbuildinfo +1 -1
- package/dist/esm-browser/common/settings.js +2 -2
- package/dist/esm-browser/image/image.test.js +30 -5
- package/package.json +1 -1
|
@@ -30,8 +30,8 @@ function missingCredentialsMessage() {
|
|
|
30
30
|
return "No Blaxel credentials found. Set the BL_API_KEY and BL_WORKSPACE environment variables, or run `bl login`.";
|
|
31
31
|
}
|
|
32
32
|
// Build info - these placeholders are replaced at build time by build:replace-imports
|
|
33
|
-
const BUILD_VERSION = "0.3.
|
|
34
|
-
const BUILD_COMMIT = "
|
|
33
|
+
const BUILD_VERSION = "0.3.27-preview.317";
|
|
34
|
+
const BUILD_COMMIT = "b818d0f5560b92040baeb620b8d89bf148cba56d";
|
|
35
35
|
const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
|
|
36
36
|
const BLAXEL_API_VERSION = "2026-09-22";
|
|
37
37
|
// Bun < 1.3.11 never sends connection-level WINDOW_UPDATE: the pooled h2
|
package/dist/cjs/image/image.js
CHANGED
|
@@ -698,6 +698,39 @@ class ImageInstance {
|
|
|
698
698
|
const uploadUrl = response.headers.get("x-blaxel-upload-url");
|
|
699
699
|
return { response, uploadUrl };
|
|
700
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* Creates or updates the sandbox and returns its upload URL with the build
|
|
703
|
+
* metadata that URL was signed for, plus requested settings it was not.
|
|
704
|
+
*/
|
|
705
|
+
async _requestUpload(sandbox, requested) {
|
|
706
|
+
const { response, uploadUrl } = await this._createSandboxWithUpload(sandbox);
|
|
707
|
+
if (response.status >= 400) {
|
|
708
|
+
const text = await response.text();
|
|
709
|
+
throw new Error(`Failed to create sandbox: ${response.status} - ${text}`);
|
|
710
|
+
}
|
|
711
|
+
if (!uploadUrl) {
|
|
712
|
+
throw new Error("No upload URL returned from API");
|
|
713
|
+
}
|
|
714
|
+
// Sign-matching headers come from the labels the control plane stored,
|
|
715
|
+
// which can include build settings already on an existing sandbox.
|
|
716
|
+
let storedLabels = sandbox.metadata?.labels;
|
|
717
|
+
try {
|
|
718
|
+
const stored = (await response.json());
|
|
719
|
+
storedLabels = stored.metadata?.labels ?? storedLabels;
|
|
720
|
+
}
|
|
721
|
+
catch {
|
|
722
|
+
// Fall back to the labels we sent
|
|
723
|
+
}
|
|
724
|
+
// Storage rejects headers the URL was not signed for, so drop those.
|
|
725
|
+
let metadata = buildUploadMetadata(storedLabels);
|
|
726
|
+
const query = new URL(uploadUrl).searchParams;
|
|
727
|
+
const signedHeaders = query.get("X-Amz-SignedHeaders") ?? query.get("X-Goog-SignedHeaders");
|
|
728
|
+
if (signedHeaders) {
|
|
729
|
+
const signed = new Set(signedHeaders.split(";").map((header) => header.trim().toLowerCase()));
|
|
730
|
+
metadata = Object.fromEntries(Object.entries(metadata).filter(([name]) => signed.has(`x-amz-meta-${name}`)));
|
|
731
|
+
}
|
|
732
|
+
return { uploadUrl, metadata, missing: Object.keys(requested).filter((name) => !(name in metadata)) };
|
|
733
|
+
}
|
|
701
734
|
async _uploadZip(uploadUrl, zipContent, metadata = {}) {
|
|
702
735
|
const headers = {
|
|
703
736
|
"Content-Type": "application/zip",
|
|
@@ -798,26 +831,17 @@ class ImageInstance {
|
|
|
798
831
|
// Create sandbox payload
|
|
799
832
|
const sandboxPayload = this._createSandboxPayload(name, memory, buildLabels);
|
|
800
833
|
// Create/update sandbox and get upload URL
|
|
801
|
-
|
|
802
|
-
if (
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
// Sign-matching headers come from the labels the control plane stored,
|
|
810
|
-
// which can include build settings already on an existing sandbox.
|
|
811
|
-
let storedLabels = sandboxPayload.metadata?.labels;
|
|
812
|
-
try {
|
|
813
|
-
const created = (await response.json());
|
|
814
|
-
storedLabels = created.metadata?.labels ?? storedLabels;
|
|
815
|
-
}
|
|
816
|
-
catch {
|
|
817
|
-
// Fall back to the labels we sent
|
|
834
|
+
let { uploadUrl, metadata, missing } = await this._requestUpload(sandboxPayload, buildLabels);
|
|
835
|
+
if (missing.length > 0) {
|
|
836
|
+
// Creating a sandbox can return a URL without the build settings
|
|
837
|
+
// signed; asking again updates the now existing sandbox, which does.
|
|
838
|
+
({ uploadUrl, metadata, missing } = await this._requestUpload(sandboxPayload, buildLabels));
|
|
839
|
+
if (missing.length > 0) {
|
|
840
|
+
throw new Error(`The platform did not accept build settings ${missing.join(", ")}; retry without them`);
|
|
841
|
+
}
|
|
818
842
|
}
|
|
819
843
|
// Upload the zip
|
|
820
|
-
await this._uploadZip(uploadUrl, zipContent,
|
|
844
|
+
await this._uploadZip(uploadUrl, zipContent, metadata);
|
|
821
845
|
// Wait for deployment to complete
|
|
822
846
|
await this._waitForDeployment(name, timeout, 3000, onStatusChange);
|
|
823
847
|
// Get the final sandbox state
|
|
@@ -545,8 +545,12 @@ let tempSourceDir;
|
|
|
545
545
|
});
|
|
546
546
|
});
|
|
547
547
|
(0, vitest_1.describe)("ImageInstance.build worker resources", () => {
|
|
548
|
-
|
|
549
|
-
|
|
548
|
+
// signing: "all" signs every build label, "update" signs only on PUT after a
|
|
549
|
+
// 404 then POST create (production today), "none" never signs them.
|
|
550
|
+
let calls = [];
|
|
551
|
+
async function buildWith(options, storedLabels, signing = "all") {
|
|
552
|
+
calls = [];
|
|
553
|
+
let created = false;
|
|
550
554
|
vitest_1.vi.spyOn(settings_js_1.settings, "authenticate").mockResolvedValue(undefined);
|
|
551
555
|
vitest_1.vi.spyOn(settings_js_1.settings, "headers", "get").mockReturnValue({});
|
|
552
556
|
vitest_1.vi.spyOn(settings_js_1.settings, "baseUrl", "get").mockReturnValue("https://api.test");
|
|
@@ -555,10 +559,18 @@ let tempSourceDir;
|
|
|
555
559
|
const body = typeof init.body === "string" ? JSON.parse(init.body) : undefined;
|
|
556
560
|
calls.push({ url, method: init.method ?? "GET", headers, body });
|
|
557
561
|
if (url.includes("upload=true")) {
|
|
558
|
-
|
|
559
|
-
|
|
562
|
+
if (signing === "update" && init.method === "PUT" && !created) {
|
|
563
|
+
return Promise.resolve(new Response("{}", { status: 404 }));
|
|
564
|
+
}
|
|
565
|
+
created ||= init.method === "POST";
|
|
566
|
+
const labels = storedLabels ?? body.metadata?.labels ?? {};
|
|
567
|
+
const signed = ["host"];
|
|
568
|
+
if (signing === "all" || (signing === "update" && init.method === "PUT")) {
|
|
569
|
+
signed.push(...Object.keys(labels).filter((k) => k.startsWith("x-blaxel-build")).map((k) => `x-amz-meta-${k}`));
|
|
570
|
+
}
|
|
571
|
+
return Promise.resolve(new Response(JSON.stringify({ metadata: { name: "img", labels } }), {
|
|
560
572
|
status: 200,
|
|
561
|
-
headers: { "x-blaxel-upload-url":
|
|
573
|
+
headers: { "x-blaxel-upload-url": `https://upload.test/object?X-Amz-SignedHeaders=${signed.join(";")}` },
|
|
562
574
|
}));
|
|
563
575
|
}
|
|
564
576
|
if (url.startsWith("https://upload.test")) {
|
|
@@ -591,6 +603,19 @@ let tempSourceDir;
|
|
|
591
603
|
(0, vitest_1.expect)(upload.headers["x-amz-meta-x-blaxel-build-volume"]).toBe("32768");
|
|
592
604
|
(0, vitest_1.expect)(upload.headers["x-amz-meta-x-blaxel-auto-generated"]).toBeUndefined();
|
|
593
605
|
});
|
|
606
|
+
(0, vitest_1.it)("asks again when creating a sandbox does not sign the settings", async () => {
|
|
607
|
+
const { upload } = await buildWith({ buildMemoryMb: 2048 }, undefined, "update");
|
|
608
|
+
(0, vitest_1.expect)(calls.filter((call) => call.url.includes("upload=true")).map((call) => call.method)).toEqual(["PUT", "POST", "PUT"]);
|
|
609
|
+
(0, vitest_1.expect)(upload.headers["x-amz-meta-x-blaxel-build-memory"]).toBe("2048");
|
|
610
|
+
});
|
|
611
|
+
(0, vitest_1.it)("fails clearly when the settings are never signed", async () => {
|
|
612
|
+
await (0, vitest_1.expect)(buildWith({ buildVolumeMb: 32768 }, undefined, "none")).rejects.toThrow("x-blaxel-build-volume");
|
|
613
|
+
(0, vitest_1.expect)(calls.some((call) => call.url.startsWith("https://upload.test"))).toBe(false);
|
|
614
|
+
});
|
|
615
|
+
(0, vitest_1.it)("drops stored labels the upload URL was not signed for", async () => {
|
|
616
|
+
const { upload } = await buildWith({}, { "x-blaxel-auto-generated": "true", "x-blaxel-build-memory": "8192" }, "none");
|
|
617
|
+
(0, vitest_1.expect)(upload.headers["x-amz-meta-x-blaxel-build-memory"]).toBeUndefined();
|
|
618
|
+
});
|
|
594
619
|
(0, vitest_1.it)("keeps an explicit zero volume", async () => {
|
|
595
620
|
const { upload } = await buildWith({ buildVolumeMb: 0 });
|
|
596
621
|
(0, vitest_1.expect)(upload.headers["x-amz-meta-x-blaxel-build-volume"]).toBe("0");
|
|
@@ -287,6 +287,11 @@ export declare class ImageInstance {
|
|
|
287
287
|
private _createZip;
|
|
288
288
|
private _createSandboxPayload;
|
|
289
289
|
private _createSandboxWithUpload;
|
|
290
|
+
/**
|
|
291
|
+
* Creates or updates the sandbox and returns its upload URL with the build
|
|
292
|
+
* metadata that URL was signed for, plus requested settings it was not.
|
|
293
|
+
*/
|
|
294
|
+
private _requestUpload;
|
|
290
295
|
private _uploadZip;
|
|
291
296
|
private _getSandboxStatus;
|
|
292
297
|
private _waitForDeployment;
|