everything-dev 2.0.0-rc.4 → 2.0.0-rc.5
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/api-contract.cjs +3 -6
- package/dist/api-contract.cjs.map +1 -1
- package/dist/api-contract.mjs +3 -6
- package/dist/api-contract.mjs.map +1 -1
- package/dist/apps/upload/src/client.cjs +188 -0
- package/dist/apps/upload/src/client.cjs.map +1 -0
- package/dist/apps/upload/src/client.mjs +180 -0
- package/dist/apps/upload/src/client.mjs.map +1 -0
- package/dist/cdn-deploy.cjs +29 -11
- package/dist/cdn-deploy.cjs.map +1 -1
- package/dist/cdn-deploy.mjs +29 -11
- package/dist/cdn-deploy.mjs.map +1 -1
- package/dist/cli/init.cjs +0 -2
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.d.cts +1 -1
- package/dist/cli/init.d.mts +1 -1
- package/dist/cli/init.mjs +0 -2
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli/sync.cjs +0 -1
- package/dist/cli/sync.cjs.map +1 -1
- package/dist/cli/sync.mjs +0 -1
- package/dist/cli/sync.mjs.map +1 -1
- package/dist/commands/deploy.cjs +0 -2
- package/dist/commands/deploy.cjs.map +1 -1
- package/dist/commands/deploy.mjs +0 -2
- package/dist/commands/deploy.mjs.map +1 -1
- package/dist/commands/upgrade.cjs +30 -7
- package/dist/commands/upgrade.cjs.map +1 -1
- package/dist/commands/upgrade.mjs +30 -7
- package/dist/commands/upgrade.mjs.map +1 -1
- package/dist/config.cjs.map +1 -1
- package/dist/config.mjs.map +1 -1
- package/dist/contract.meta.cjs +1 -1
- package/dist/contract.meta.cjs.map +1 -1
- package/dist/contract.meta.d.cts +1 -1
- package/dist/contract.meta.d.mts +1 -1
- package/dist/contract.meta.mjs +1 -1
- package/dist/contract.meta.mjs.map +1 -1
- package/dist/dev-program.cjs +1 -0
- package/dist/dev-program.cjs.map +1 -1
- package/dist/dev-program.mjs +1 -1
- package/dist/dev-program.mjs.map +1 -1
- package/dist/image-deploy.cjs +38 -14
- package/dist/image-deploy.cjs.map +1 -1
- package/dist/image-deploy.mjs +39 -15
- package/dist/image-deploy.mjs.map +1 -1
- package/dist/storage-upload.cjs +7 -68
- package/dist/storage-upload.cjs.map +1 -1
- package/dist/storage-upload.mjs +7 -68
- package/dist/storage-upload.mjs.map +1 -1
- package/dist/ui/api.cjs +11 -69
- package/dist/ui/api.cjs.map +1 -1
- package/dist/ui/api.d.cts +2 -4
- package/dist/ui/api.d.mts +2 -4
- package/dist/ui/api.mjs +11 -69
- package/dist/ui/api.mjs.map +1 -1
- package/dist/ui/auth.d.cts +43 -43
- package/dist/ui/auth.d.mts +43 -43
- package/package.json +3 -2
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
let effect = require("effect");
|
|
2
|
+
let zod = require("zod");
|
|
3
|
+
let every_plugin_version_manifest = require("every-plugin/version-manifest");
|
|
4
|
+
|
|
5
|
+
//#region ../../apps/upload/src/client.ts
|
|
6
|
+
const BundleFileSchema = zod.z.object({
|
|
7
|
+
path: zod.z.string().min(1).max(512),
|
|
8
|
+
contentBase64: zod.z.string().min(1)
|
|
9
|
+
});
|
|
10
|
+
const UploadRequestSchema = zod.z.object({
|
|
11
|
+
account: zod.z.string().min(1),
|
|
12
|
+
gateway: zod.z.string().min(1),
|
|
13
|
+
workspace: zod.z.string().min(1),
|
|
14
|
+
files: zod.z.array(BundleFileSchema).min(1).max(1e4)
|
|
15
|
+
});
|
|
16
|
+
const UploadResponseSchema = zod.z.object({
|
|
17
|
+
stored: zod.z.number().int(),
|
|
18
|
+
/** files recognized as byte-identical to what the store already holds (SRI metadata match) and skipped */
|
|
19
|
+
unchanged: zod.z.number().int().optional(),
|
|
20
|
+
totalBytes: zod.z.number().int(),
|
|
21
|
+
integrity: zod.z.record(zod.z.string(), zod.z.string()),
|
|
22
|
+
storage: zod.z.enum(["s3", "memory"]).optional().describe("Resolved bundle-storage backend: `s3` (R2/MinIO, persistent) or `memory` (ephemeral — bytes are lost on restart)")
|
|
23
|
+
});
|
|
24
|
+
const NamespaceQuerySchema = zod.z.object({
|
|
25
|
+
account: zod.z.string().min(1),
|
|
26
|
+
gateway: zod.z.string().min(1)
|
|
27
|
+
});
|
|
28
|
+
const ListBundlesQuerySchema = NamespaceQuerySchema.extend({ workspace: zod.z.string().min(1).optional() });
|
|
29
|
+
const BundleObjectSummarySchema = zod.z.object({
|
|
30
|
+
key: zod.z.string(),
|
|
31
|
+
size: zod.z.number().int(),
|
|
32
|
+
lastModified: zod.z.string().nullable().optional()
|
|
33
|
+
});
|
|
34
|
+
const ListBundlesResultSchema = zod.z.object({
|
|
35
|
+
storage: zod.z.enum(["s3", "memory"]),
|
|
36
|
+
prefix: zod.z.string(),
|
|
37
|
+
objects: zod.z.array(BundleObjectSummarySchema),
|
|
38
|
+
truncated: zod.z.boolean()
|
|
39
|
+
});
|
|
40
|
+
const ListVersionsQuerySchema = NamespaceQuerySchema.extend({ workspace: zod.z.string().min(1) });
|
|
41
|
+
const VersionSummarySchema = zod.z.object({
|
|
42
|
+
version: zod.z.string().regex(/^[0-9a-f]{16}$/),
|
|
43
|
+
entry: zod.z.string(),
|
|
44
|
+
entryIntegrity: zod.z.string(),
|
|
45
|
+
gitSha: zod.z.string().optional(),
|
|
46
|
+
builtAt: zod.z.string().optional(),
|
|
47
|
+
fileCount: zod.z.number().int().optional(),
|
|
48
|
+
lastModified: zod.z.string().nullable().optional()
|
|
49
|
+
});
|
|
50
|
+
const ListVersionsResultSchema = zod.z.object({
|
|
51
|
+
storage: zod.z.enum(["s3", "memory"]),
|
|
52
|
+
versions: zod.z.array(VersionSummarySchema),
|
|
53
|
+
truncated: zod.z.boolean()
|
|
54
|
+
});
|
|
55
|
+
const IntegrityReportQuerySchema = NamespaceQuerySchema.extend({
|
|
56
|
+
workspace: zod.z.string().min(1),
|
|
57
|
+
version: zod.z.string().regex(/^[0-9a-f]{16}$/)
|
|
58
|
+
});
|
|
59
|
+
const IntegrityReportResultSchema = zod.z.object({
|
|
60
|
+
storage: zod.z.enum(["s3", "memory"]),
|
|
61
|
+
manifest: every_plugin_version_manifest.WorkspaceVersionManifestSchema,
|
|
62
|
+
/** SRI recompute of the manifest's entry object against `entryIntegrity`;
|
|
63
|
+
* null when the entry object is missing from the store. */
|
|
64
|
+
entryVerified: zod.z.boolean().nullable()
|
|
65
|
+
});
|
|
66
|
+
const STATUS_ATTEMPTS = 3;
|
|
67
|
+
const TRANSPORT_ATTEMPTS = 5;
|
|
68
|
+
const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
|
|
69
|
+
408,
|
|
70
|
+
429,
|
|
71
|
+
500,
|
|
72
|
+
502,
|
|
73
|
+
503,
|
|
74
|
+
504
|
|
75
|
+
]);
|
|
76
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
77
|
+
const backoffMs = (attempt) => 1e3 * attempt + Math.floor(Math.random() * 500);
|
|
78
|
+
var UploadTransferError = class UploadTransferError extends effect.Data.TaggedError("UploadTransferError") {
|
|
79
|
+
constructor(status, detail, note) {
|
|
80
|
+
super({
|
|
81
|
+
status,
|
|
82
|
+
detail,
|
|
83
|
+
note
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
get message() {
|
|
87
|
+
return `${this.detail}${this.note ?? ""}`;
|
|
88
|
+
}
|
|
89
|
+
withNote(note) {
|
|
90
|
+
return new UploadTransferError(this.status, this.detail, `${this.note ?? ""}${note}`);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function describeTransferFailure(status, detail) {
|
|
94
|
+
const trimmed = detail.slice(0, 300);
|
|
95
|
+
if (status === 408 || /request timeout|bundle upload timed out/i.test(trimmed)) return `${trimmed} — the serving host timed out the upload; raise BOS_STORAGE_UPLOAD_TIMEOUT_MS there or upload smaller batches (BOS_MAX_BUNDLE_UPLOAD_BYTES)`;
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(detail);
|
|
98
|
+
if (parsed?.code === "INTERNAL_SERVER_ERROR" && parsed?.message === "Internal Server Error") return `${trimmed} — the host hid the underlying error; check the host logs for the cause`;
|
|
99
|
+
} catch {}
|
|
100
|
+
return trimmed;
|
|
101
|
+
}
|
|
102
|
+
async function requestJson(config, label, path, parse, init) {
|
|
103
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
104
|
+
const headers = { ...init?.headers };
|
|
105
|
+
if (config.apiKey) headers["x-api-key"] = config.apiKey;
|
|
106
|
+
if (init?.body && !headers["content-type"]) headers["content-type"] = "application/json";
|
|
107
|
+
let lastError;
|
|
108
|
+
let transportFailures = 0;
|
|
109
|
+
let statusFailures = 0;
|
|
110
|
+
for (let attempt = 1; attempt <= TRANSPORT_ATTEMPTS; attempt++) {
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetchImpl(`${config.origin.replace(/\/$/, "")}${path}`, {
|
|
113
|
+
...init,
|
|
114
|
+
headers: transportFailures > 0 ? {
|
|
115
|
+
...headers,
|
|
116
|
+
connection: "close"
|
|
117
|
+
} : headers
|
|
118
|
+
});
|
|
119
|
+
if (response.ok) return parse(await response.json());
|
|
120
|
+
const detail = await response.text().catch(() => "");
|
|
121
|
+
lastError = new UploadTransferError(response.status, `[upload] ${label} failed: ${response.status} ${describeTransferFailure(response.status, detail)}`);
|
|
122
|
+
if (!RETRYABLE_STATUSES.has(response.status)) break;
|
|
123
|
+
statusFailures += 1;
|
|
124
|
+
if (statusFailures >= STATUS_ATTEMPTS) break;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
transportFailures += 1;
|
|
127
|
+
lastError = new UploadTransferError(0, `[upload] ${label} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
128
|
+
}
|
|
129
|
+
if (attempt < TRANSPORT_ATTEMPTS) await sleep(backoffMs(attempt));
|
|
130
|
+
}
|
|
131
|
+
if (lastError instanceof UploadTransferError && lastError.status === 0) lastError = lastError.withNote(" (Bun fetch note: re-run with fetch verbose diagnostics for detail)");
|
|
132
|
+
throw lastError ?? /* @__PURE__ */ new Error(`[upload] ${label} failed`);
|
|
133
|
+
}
|
|
134
|
+
const parseJsonBody = (label, schema) => {
|
|
135
|
+
return (response) => {
|
|
136
|
+
try {
|
|
137
|
+
return schema.parse(response);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
throw new UploadTransferError(0, `[upload] ${label} returned an unexpected payload: ${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
function createUploadClient(config) {
|
|
144
|
+
return {
|
|
145
|
+
uploadBundle(input) {
|
|
146
|
+
const payload = UploadRequestSchema.parse(input);
|
|
147
|
+
return requestJson(config, `bundle upload for ${payload.workspace}`, "/api/storage/bundles", parseJsonBody("bundle upload", UploadResponseSchema), {
|
|
148
|
+
method: "POST",
|
|
149
|
+
body: JSON.stringify(payload)
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
listBundles(input) {
|
|
153
|
+
const query = new URLSearchParams({
|
|
154
|
+
account: input.account,
|
|
155
|
+
gateway: input.gateway
|
|
156
|
+
});
|
|
157
|
+
if (input.workspace) query.set("workspace", input.workspace);
|
|
158
|
+
return requestJson(config, "bundle listing", `/api/storage/bundles?${query.toString()}`, parseJsonBody("bundle listing", ListBundlesResultSchema), { method: "GET" });
|
|
159
|
+
},
|
|
160
|
+
listVersions(input) {
|
|
161
|
+
return requestJson(config, "version listing", `/api/storage/versions?${new URLSearchParams({
|
|
162
|
+
account: input.account,
|
|
163
|
+
gateway: input.gateway,
|
|
164
|
+
workspace: input.workspace
|
|
165
|
+
}).toString()}`, parseJsonBody("version listing", ListVersionsResultSchema), { method: "GET" });
|
|
166
|
+
},
|
|
167
|
+
integrityReport(input) {
|
|
168
|
+
return requestJson(config, "integrity report", `/api/storage/integrity?${new URLSearchParams({
|
|
169
|
+
account: input.account,
|
|
170
|
+
gateway: input.gateway,
|
|
171
|
+
workspace: input.workspace,
|
|
172
|
+
version: input.version
|
|
173
|
+
}).toString()}`, parseJsonBody("integrity report", IntegrityReportResultSchema), { method: "GET" });
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
exports.BundleFileSchema = BundleFileSchema;
|
|
180
|
+
exports.BundleObjectSummarySchema = BundleObjectSummarySchema;
|
|
181
|
+
exports.IntegrityReportResultSchema = IntegrityReportResultSchema;
|
|
182
|
+
exports.ListBundlesResultSchema = ListBundlesResultSchema;
|
|
183
|
+
exports.ListVersionsResultSchema = ListVersionsResultSchema;
|
|
184
|
+
exports.UploadRequestSchema = UploadRequestSchema;
|
|
185
|
+
exports.UploadResponseSchema = UploadResponseSchema;
|
|
186
|
+
exports.VersionSummarySchema = VersionSummarySchema;
|
|
187
|
+
exports.createUploadClient = createUploadClient;
|
|
188
|
+
//# sourceMappingURL=client.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.cjs","names":["z","WorkspaceVersionManifestSchema","Data"],"sources":["../../../../../../apps/upload/src/client.ts"],"sourcesContent":["import { Data } from \"effect\";\nimport { WorkspaceVersionManifestSchema } from \"every-plugin/version-manifest\";\nimport { z } from \"zod\";\n\nexport const BundleFileSchema = z.object({\n path: z.string().min(1).max(512),\n contentBase64: z.string().min(1),\n});\n\nexport const UploadRequestSchema = z.object({\n account: z.string().min(1),\n gateway: z.string().min(1),\n workspace: z.string().min(1),\n files: z.array(BundleFileSchema).min(1).max(10_000),\n});\n\nexport const UploadResponseSchema = z.object({\n stored: z.number().int(),\n /** files recognized as byte-identical to what the store already holds (SRI metadata match) and skipped */\n unchanged: z.number().int().optional(),\n totalBytes: z.number().int(),\n integrity: z.record(z.string(), z.string()),\n storage: z\n .enum([\"s3\", \"memory\"])\n .optional()\n .describe(\n \"Resolved bundle-storage backend: `s3` (R2/MinIO, persistent) or `memory` (ephemeral — bytes are lost on restart)\",\n ),\n});\n\nconst NamespaceQuerySchema = z.object({\n account: z.string().min(1),\n gateway: z.string().min(1),\n});\n\nexport const ListBundlesQuerySchema = NamespaceQuerySchema.extend({\n workspace: z.string().min(1).optional(),\n});\n\nexport const BundleObjectSummarySchema = z.object({\n key: z.string(),\n size: z.number().int(),\n lastModified: z.string().nullable().optional(),\n});\n\nexport const ListBundlesResultSchema = z.object({\n storage: z.enum([\"s3\", \"memory\"]),\n prefix: z.string(),\n objects: z.array(BundleObjectSummarySchema),\n truncated: z.boolean(),\n});\n\nexport const ListVersionsQuerySchema = NamespaceQuerySchema.extend({\n workspace: z.string().min(1),\n});\n\nexport const VersionSummarySchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n entry: z.string(),\n entryIntegrity: z.string(),\n gitSha: z.string().optional(),\n builtAt: z.string().optional(),\n fileCount: z.number().int().optional(),\n lastModified: z.string().nullable().optional(),\n});\n\nexport const ListVersionsResultSchema = z.object({\n storage: z.enum([\"s3\", \"memory\"]),\n versions: z.array(VersionSummarySchema),\n truncated: z.boolean(),\n});\n\nexport const IntegrityReportQuerySchema = NamespaceQuerySchema.extend({\n workspace: z.string().min(1),\n version: z.string().regex(/^[0-9a-f]{16}$/),\n});\n\nexport const IntegrityReportResultSchema = z.object({\n storage: z.enum([\"s3\", \"memory\"]),\n manifest: WorkspaceVersionManifestSchema,\n /** SRI recompute of the manifest's entry object against `entryIntegrity`;\n * null when the entry object is missing from the store. */\n entryVerified: z.boolean().nullable(),\n});\n\nexport type BundleFile = z.infer<typeof BundleFileSchema>;\nexport type UploadRequest = z.infer<typeof UploadRequestSchema>;\nexport type UploadResponse = z.infer<typeof UploadResponseSchema>;\nexport type ListBundlesQuery = z.infer<typeof ListBundlesQuerySchema>;\nexport type BundleObjectSummary = z.infer<typeof BundleObjectSummarySchema>;\nexport type ListBundlesResult = z.infer<typeof ListBundlesResultSchema>;\nexport type ListVersionsQuery = z.infer<typeof ListVersionsQuerySchema>;\nexport type VersionSummary = z.infer<typeof VersionSummarySchema>;\nexport type ListVersionsResult = z.infer<typeof ListVersionsResultSchema>;\nexport type IntegrityReportQuery = z.infer<typeof IntegrityReportQuerySchema>;\nexport type IntegrityReportResult = z.infer<typeof IntegrityReportResultSchema>;\n\nexport interface UploadClientConfig {\n origin: string;\n apiKey?: string;\n fetchImpl?: typeof fetch;\n}\n\nexport interface UploadClient {\n uploadBundle(input: UploadRequest): Promise<UploadResponse>;\n listBundles(input: ListBundlesQuery): Promise<ListBundlesResult>;\n listVersions(input: ListVersionsQuery): Promise<ListVersionsResult>;\n integrityReport(input: IntegrityReportQuery): Promise<IntegrityReportResult>;\n}\n\nconst STATUS_ATTEMPTS = 3;\nconst TRANSPORT_ATTEMPTS = 5;\nconst RETRYABLE_STATUSES = new Set([408, 429, 500, 502, 503, 504]);\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\nconst backoffMs = (attempt: number) => 1000 * attempt + Math.floor(Math.random() * 500);\n\nclass UploadTransferError extends Data.TaggedError(\"UploadTransferError\")<{\n readonly status: number;\n readonly detail: string;\n readonly note?: string;\n}> {\n constructor(status: number, detail: string, note?: string) {\n super({ status, detail, note });\n }\n\n override get message() {\n return `${this.detail}${this.note ?? \"\"}`;\n }\n\n withNote(note: string) {\n return new UploadTransferError(this.status, this.detail, `${this.note ?? \"\"}${note}`);\n }\n}\n\nfunction describeTransferFailure(status: number, detail: string): string {\n const trimmed = detail.slice(0, 300);\n if (status === 408 || /request timeout|bundle upload timed out/i.test(trimmed)) {\n return `${trimmed} — the serving host timed out the upload; raise BOS_STORAGE_UPLOAD_TIMEOUT_MS there or upload smaller batches (BOS_MAX_BUNDLE_UPLOAD_BYTES)`;\n }\n try {\n const parsed = JSON.parse(detail) as { code?: unknown; message?: unknown };\n if (parsed?.code === \"INTERNAL_SERVER_ERROR\" && parsed?.message === \"Internal Server Error\") {\n return `${trimmed} — the host hid the underlying error; check the host logs for the cause`;\n }\n } catch {\n // not a JSON error body\n }\n return trimmed;\n}\n\nasync function requestJson<T>(\n config: UploadClientConfig,\n label: string,\n path: string,\n parse: (value: unknown) => T,\n init?: RequestInit,\n): Promise<T> {\n const fetchImpl = config.fetchImpl ?? fetch;\n const headers: Record<string, string> = { ...(init?.headers as Record<string, string>) };\n if (config.apiKey) headers[\"x-api-key\"] = config.apiKey;\n if (init?.body && !headers[\"content-type\"]) headers[\"content-type\"] = \"application/json\";\n\n let lastError: UploadTransferError | Error | undefined;\n let transportFailures = 0;\n let statusFailures = 0;\n for (let attempt = 1; attempt <= TRANSPORT_ATTEMPTS; attempt++) {\n try {\n const response = await fetchImpl(`${config.origin.replace(/\\/$/, \"\")}${path}`, {\n ...init,\n headers: transportFailures > 0 ? { ...headers, connection: \"close\" } : headers,\n });\n\n if (response.ok) {\n return parse(await response.json());\n }\n\n const detail = await response.text().catch(() => \"\");\n lastError = new UploadTransferError(\n response.status,\n `[upload] ${label} failed: ${response.status} ${describeTransferFailure(response.status, detail)}`,\n );\n if (!RETRYABLE_STATUSES.has(response.status)) break;\n statusFailures += 1;\n if (statusFailures >= STATUS_ATTEMPTS) break;\n } catch (error) {\n transportFailures += 1;\n lastError = new UploadTransferError(\n 0,\n `[upload] ${label} failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n if (attempt < TRANSPORT_ATTEMPTS) await sleep(backoffMs(attempt));\n }\n\n if (lastError instanceof UploadTransferError && lastError.status === 0) {\n lastError = lastError.withNote(\n \" (Bun fetch note: re-run with fetch verbose diagnostics for detail)\",\n );\n }\n throw lastError ?? new Error(`[upload] ${label} failed`);\n}\n\nconst parseJsonBody = <T>(label: string, schema: { parse: (value: unknown) => T }) => {\n return (response: unknown): T => {\n try {\n return schema.parse(response);\n } catch (error) {\n throw new UploadTransferError(\n 0,\n `[upload] ${label} returned an unexpected payload: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n };\n};\n\nexport function createUploadClient(config: UploadClientConfig): UploadClient {\n return {\n uploadBundle(input) {\n const payload = UploadRequestSchema.parse(input);\n return requestJson(\n config,\n `bundle upload for ${payload.workspace}`,\n \"/api/storage/bundles\",\n parseJsonBody(\"bundle upload\", UploadResponseSchema),\n { method: \"POST\", body: JSON.stringify(payload) },\n );\n },\n\n listBundles(input) {\n const query = new URLSearchParams({ account: input.account, gateway: input.gateway });\n if (input.workspace) query.set(\"workspace\", input.workspace);\n return requestJson(\n config,\n \"bundle listing\",\n `/api/storage/bundles?${query.toString()}`,\n parseJsonBody(\"bundle listing\", ListBundlesResultSchema),\n { method: \"GET\" },\n );\n },\n\n listVersions(input) {\n const query = new URLSearchParams({\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n });\n return requestJson(\n config,\n \"version listing\",\n `/api/storage/versions?${query.toString()}`,\n parseJsonBody(\"version listing\", ListVersionsResultSchema),\n { method: \"GET\" },\n );\n },\n\n integrityReport(input) {\n const query = new URLSearchParams({\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n version: input.version,\n });\n return requestJson(\n config,\n \"integrity report\",\n `/api/storage/integrity?${query.toString()}`,\n parseJsonBody(\"integrity report\", IntegrityReportResultSchema),\n { method: \"GET\" },\n );\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { WorkspaceVersionManifestSchema } from "every-plugin/version-manifest";
|
|
4
|
+
|
|
5
|
+
//#region ../../apps/upload/src/client.ts
|
|
6
|
+
const BundleFileSchema = z.object({
|
|
7
|
+
path: z.string().min(1).max(512),
|
|
8
|
+
contentBase64: z.string().min(1)
|
|
9
|
+
});
|
|
10
|
+
const UploadRequestSchema = z.object({
|
|
11
|
+
account: z.string().min(1),
|
|
12
|
+
gateway: z.string().min(1),
|
|
13
|
+
workspace: z.string().min(1),
|
|
14
|
+
files: z.array(BundleFileSchema).min(1).max(1e4)
|
|
15
|
+
});
|
|
16
|
+
const UploadResponseSchema = z.object({
|
|
17
|
+
stored: z.number().int(),
|
|
18
|
+
/** files recognized as byte-identical to what the store already holds (SRI metadata match) and skipped */
|
|
19
|
+
unchanged: z.number().int().optional(),
|
|
20
|
+
totalBytes: z.number().int(),
|
|
21
|
+
integrity: z.record(z.string(), z.string()),
|
|
22
|
+
storage: z.enum(["s3", "memory"]).optional().describe("Resolved bundle-storage backend: `s3` (R2/MinIO, persistent) or `memory` (ephemeral — bytes are lost on restart)")
|
|
23
|
+
});
|
|
24
|
+
const NamespaceQuerySchema = z.object({
|
|
25
|
+
account: z.string().min(1),
|
|
26
|
+
gateway: z.string().min(1)
|
|
27
|
+
});
|
|
28
|
+
const ListBundlesQuerySchema = NamespaceQuerySchema.extend({ workspace: z.string().min(1).optional() });
|
|
29
|
+
const BundleObjectSummarySchema = z.object({
|
|
30
|
+
key: z.string(),
|
|
31
|
+
size: z.number().int(),
|
|
32
|
+
lastModified: z.string().nullable().optional()
|
|
33
|
+
});
|
|
34
|
+
const ListBundlesResultSchema = z.object({
|
|
35
|
+
storage: z.enum(["s3", "memory"]),
|
|
36
|
+
prefix: z.string(),
|
|
37
|
+
objects: z.array(BundleObjectSummarySchema),
|
|
38
|
+
truncated: z.boolean()
|
|
39
|
+
});
|
|
40
|
+
const ListVersionsQuerySchema = NamespaceQuerySchema.extend({ workspace: z.string().min(1) });
|
|
41
|
+
const VersionSummarySchema = z.object({
|
|
42
|
+
version: z.string().regex(/^[0-9a-f]{16}$/),
|
|
43
|
+
entry: z.string(),
|
|
44
|
+
entryIntegrity: z.string(),
|
|
45
|
+
gitSha: z.string().optional(),
|
|
46
|
+
builtAt: z.string().optional(),
|
|
47
|
+
fileCount: z.number().int().optional(),
|
|
48
|
+
lastModified: z.string().nullable().optional()
|
|
49
|
+
});
|
|
50
|
+
const ListVersionsResultSchema = z.object({
|
|
51
|
+
storage: z.enum(["s3", "memory"]),
|
|
52
|
+
versions: z.array(VersionSummarySchema),
|
|
53
|
+
truncated: z.boolean()
|
|
54
|
+
});
|
|
55
|
+
const IntegrityReportQuerySchema = NamespaceQuerySchema.extend({
|
|
56
|
+
workspace: z.string().min(1),
|
|
57
|
+
version: z.string().regex(/^[0-9a-f]{16}$/)
|
|
58
|
+
});
|
|
59
|
+
const IntegrityReportResultSchema = z.object({
|
|
60
|
+
storage: z.enum(["s3", "memory"]),
|
|
61
|
+
manifest: WorkspaceVersionManifestSchema,
|
|
62
|
+
/** SRI recompute of the manifest's entry object against `entryIntegrity`;
|
|
63
|
+
* null when the entry object is missing from the store. */
|
|
64
|
+
entryVerified: z.boolean().nullable()
|
|
65
|
+
});
|
|
66
|
+
const STATUS_ATTEMPTS = 3;
|
|
67
|
+
const TRANSPORT_ATTEMPTS = 5;
|
|
68
|
+
const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
|
|
69
|
+
408,
|
|
70
|
+
429,
|
|
71
|
+
500,
|
|
72
|
+
502,
|
|
73
|
+
503,
|
|
74
|
+
504
|
|
75
|
+
]);
|
|
76
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
77
|
+
const backoffMs = (attempt) => 1e3 * attempt + Math.floor(Math.random() * 500);
|
|
78
|
+
var UploadTransferError = class UploadTransferError extends Data.TaggedError("UploadTransferError") {
|
|
79
|
+
constructor(status, detail, note) {
|
|
80
|
+
super({
|
|
81
|
+
status,
|
|
82
|
+
detail,
|
|
83
|
+
note
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
get message() {
|
|
87
|
+
return `${this.detail}${this.note ?? ""}`;
|
|
88
|
+
}
|
|
89
|
+
withNote(note) {
|
|
90
|
+
return new UploadTransferError(this.status, this.detail, `${this.note ?? ""}${note}`);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function describeTransferFailure(status, detail) {
|
|
94
|
+
const trimmed = detail.slice(0, 300);
|
|
95
|
+
if (status === 408 || /request timeout|bundle upload timed out/i.test(trimmed)) return `${trimmed} — the serving host timed out the upload; raise BOS_STORAGE_UPLOAD_TIMEOUT_MS there or upload smaller batches (BOS_MAX_BUNDLE_UPLOAD_BYTES)`;
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(detail);
|
|
98
|
+
if (parsed?.code === "INTERNAL_SERVER_ERROR" && parsed?.message === "Internal Server Error") return `${trimmed} — the host hid the underlying error; check the host logs for the cause`;
|
|
99
|
+
} catch {}
|
|
100
|
+
return trimmed;
|
|
101
|
+
}
|
|
102
|
+
async function requestJson(config, label, path, parse, init) {
|
|
103
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
104
|
+
const headers = { ...init?.headers };
|
|
105
|
+
if (config.apiKey) headers["x-api-key"] = config.apiKey;
|
|
106
|
+
if (init?.body && !headers["content-type"]) headers["content-type"] = "application/json";
|
|
107
|
+
let lastError;
|
|
108
|
+
let transportFailures = 0;
|
|
109
|
+
let statusFailures = 0;
|
|
110
|
+
for (let attempt = 1; attempt <= TRANSPORT_ATTEMPTS; attempt++) {
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetchImpl(`${config.origin.replace(/\/$/, "")}${path}`, {
|
|
113
|
+
...init,
|
|
114
|
+
headers: transportFailures > 0 ? {
|
|
115
|
+
...headers,
|
|
116
|
+
connection: "close"
|
|
117
|
+
} : headers
|
|
118
|
+
});
|
|
119
|
+
if (response.ok) return parse(await response.json());
|
|
120
|
+
const detail = await response.text().catch(() => "");
|
|
121
|
+
lastError = new UploadTransferError(response.status, `[upload] ${label} failed: ${response.status} ${describeTransferFailure(response.status, detail)}`);
|
|
122
|
+
if (!RETRYABLE_STATUSES.has(response.status)) break;
|
|
123
|
+
statusFailures += 1;
|
|
124
|
+
if (statusFailures >= STATUS_ATTEMPTS) break;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
transportFailures += 1;
|
|
127
|
+
lastError = new UploadTransferError(0, `[upload] ${label} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
128
|
+
}
|
|
129
|
+
if (attempt < TRANSPORT_ATTEMPTS) await sleep(backoffMs(attempt));
|
|
130
|
+
}
|
|
131
|
+
if (lastError instanceof UploadTransferError && lastError.status === 0) lastError = lastError.withNote(" (Bun fetch note: re-run with fetch verbose diagnostics for detail)");
|
|
132
|
+
throw lastError ?? /* @__PURE__ */ new Error(`[upload] ${label} failed`);
|
|
133
|
+
}
|
|
134
|
+
const parseJsonBody = (label, schema) => {
|
|
135
|
+
return (response) => {
|
|
136
|
+
try {
|
|
137
|
+
return schema.parse(response);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
throw new UploadTransferError(0, `[upload] ${label} returned an unexpected payload: ${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
function createUploadClient(config) {
|
|
144
|
+
return {
|
|
145
|
+
uploadBundle(input) {
|
|
146
|
+
const payload = UploadRequestSchema.parse(input);
|
|
147
|
+
return requestJson(config, `bundle upload for ${payload.workspace}`, "/api/storage/bundles", parseJsonBody("bundle upload", UploadResponseSchema), {
|
|
148
|
+
method: "POST",
|
|
149
|
+
body: JSON.stringify(payload)
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
listBundles(input) {
|
|
153
|
+
const query = new URLSearchParams({
|
|
154
|
+
account: input.account,
|
|
155
|
+
gateway: input.gateway
|
|
156
|
+
});
|
|
157
|
+
if (input.workspace) query.set("workspace", input.workspace);
|
|
158
|
+
return requestJson(config, "bundle listing", `/api/storage/bundles?${query.toString()}`, parseJsonBody("bundle listing", ListBundlesResultSchema), { method: "GET" });
|
|
159
|
+
},
|
|
160
|
+
listVersions(input) {
|
|
161
|
+
return requestJson(config, "version listing", `/api/storage/versions?${new URLSearchParams({
|
|
162
|
+
account: input.account,
|
|
163
|
+
gateway: input.gateway,
|
|
164
|
+
workspace: input.workspace
|
|
165
|
+
}).toString()}`, parseJsonBody("version listing", ListVersionsResultSchema), { method: "GET" });
|
|
166
|
+
},
|
|
167
|
+
integrityReport(input) {
|
|
168
|
+
return requestJson(config, "integrity report", `/api/storage/integrity?${new URLSearchParams({
|
|
169
|
+
account: input.account,
|
|
170
|
+
gateway: input.gateway,
|
|
171
|
+
workspace: input.workspace,
|
|
172
|
+
version: input.version
|
|
173
|
+
}).toString()}`, parseJsonBody("integrity report", IntegrityReportResultSchema), { method: "GET" });
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
export { BundleFileSchema, BundleObjectSummarySchema, IntegrityReportResultSchema, ListBundlesResultSchema, ListVersionsResultSchema, UploadRequestSchema, UploadResponseSchema, VersionSummarySchema, createUploadClient };
|
|
180
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../../../../../../apps/upload/src/client.ts"],"sourcesContent":["import { Data } from \"effect\";\nimport { WorkspaceVersionManifestSchema } from \"every-plugin/version-manifest\";\nimport { z } from \"zod\";\n\nexport const BundleFileSchema = z.object({\n path: z.string().min(1).max(512),\n contentBase64: z.string().min(1),\n});\n\nexport const UploadRequestSchema = z.object({\n account: z.string().min(1),\n gateway: z.string().min(1),\n workspace: z.string().min(1),\n files: z.array(BundleFileSchema).min(1).max(10_000),\n});\n\nexport const UploadResponseSchema = z.object({\n stored: z.number().int(),\n /** files recognized as byte-identical to what the store already holds (SRI metadata match) and skipped */\n unchanged: z.number().int().optional(),\n totalBytes: z.number().int(),\n integrity: z.record(z.string(), z.string()),\n storage: z\n .enum([\"s3\", \"memory\"])\n .optional()\n .describe(\n \"Resolved bundle-storage backend: `s3` (R2/MinIO, persistent) or `memory` (ephemeral — bytes are lost on restart)\",\n ),\n});\n\nconst NamespaceQuerySchema = z.object({\n account: z.string().min(1),\n gateway: z.string().min(1),\n});\n\nexport const ListBundlesQuerySchema = NamespaceQuerySchema.extend({\n workspace: z.string().min(1).optional(),\n});\n\nexport const BundleObjectSummarySchema = z.object({\n key: z.string(),\n size: z.number().int(),\n lastModified: z.string().nullable().optional(),\n});\n\nexport const ListBundlesResultSchema = z.object({\n storage: z.enum([\"s3\", \"memory\"]),\n prefix: z.string(),\n objects: z.array(BundleObjectSummarySchema),\n truncated: z.boolean(),\n});\n\nexport const ListVersionsQuerySchema = NamespaceQuerySchema.extend({\n workspace: z.string().min(1),\n});\n\nexport const VersionSummarySchema = z.object({\n version: z.string().regex(/^[0-9a-f]{16}$/),\n entry: z.string(),\n entryIntegrity: z.string(),\n gitSha: z.string().optional(),\n builtAt: z.string().optional(),\n fileCount: z.number().int().optional(),\n lastModified: z.string().nullable().optional(),\n});\n\nexport const ListVersionsResultSchema = z.object({\n storage: z.enum([\"s3\", \"memory\"]),\n versions: z.array(VersionSummarySchema),\n truncated: z.boolean(),\n});\n\nexport const IntegrityReportQuerySchema = NamespaceQuerySchema.extend({\n workspace: z.string().min(1),\n version: z.string().regex(/^[0-9a-f]{16}$/),\n});\n\nexport const IntegrityReportResultSchema = z.object({\n storage: z.enum([\"s3\", \"memory\"]),\n manifest: WorkspaceVersionManifestSchema,\n /** SRI recompute of the manifest's entry object against `entryIntegrity`;\n * null when the entry object is missing from the store. */\n entryVerified: z.boolean().nullable(),\n});\n\nexport type BundleFile = z.infer<typeof BundleFileSchema>;\nexport type UploadRequest = z.infer<typeof UploadRequestSchema>;\nexport type UploadResponse = z.infer<typeof UploadResponseSchema>;\nexport type ListBundlesQuery = z.infer<typeof ListBundlesQuerySchema>;\nexport type BundleObjectSummary = z.infer<typeof BundleObjectSummarySchema>;\nexport type ListBundlesResult = z.infer<typeof ListBundlesResultSchema>;\nexport type ListVersionsQuery = z.infer<typeof ListVersionsQuerySchema>;\nexport type VersionSummary = z.infer<typeof VersionSummarySchema>;\nexport type ListVersionsResult = z.infer<typeof ListVersionsResultSchema>;\nexport type IntegrityReportQuery = z.infer<typeof IntegrityReportQuerySchema>;\nexport type IntegrityReportResult = z.infer<typeof IntegrityReportResultSchema>;\n\nexport interface UploadClientConfig {\n origin: string;\n apiKey?: string;\n fetchImpl?: typeof fetch;\n}\n\nexport interface UploadClient {\n uploadBundle(input: UploadRequest): Promise<UploadResponse>;\n listBundles(input: ListBundlesQuery): Promise<ListBundlesResult>;\n listVersions(input: ListVersionsQuery): Promise<ListVersionsResult>;\n integrityReport(input: IntegrityReportQuery): Promise<IntegrityReportResult>;\n}\n\nconst STATUS_ATTEMPTS = 3;\nconst TRANSPORT_ATTEMPTS = 5;\nconst RETRYABLE_STATUSES = new Set([408, 429, 500, 502, 503, 504]);\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\nconst backoffMs = (attempt: number) => 1000 * attempt + Math.floor(Math.random() * 500);\n\nclass UploadTransferError extends Data.TaggedError(\"UploadTransferError\")<{\n readonly status: number;\n readonly detail: string;\n readonly note?: string;\n}> {\n constructor(status: number, detail: string, note?: string) {\n super({ status, detail, note });\n }\n\n override get message() {\n return `${this.detail}${this.note ?? \"\"}`;\n }\n\n withNote(note: string) {\n return new UploadTransferError(this.status, this.detail, `${this.note ?? \"\"}${note}`);\n }\n}\n\nfunction describeTransferFailure(status: number, detail: string): string {\n const trimmed = detail.slice(0, 300);\n if (status === 408 || /request timeout|bundle upload timed out/i.test(trimmed)) {\n return `${trimmed} — the serving host timed out the upload; raise BOS_STORAGE_UPLOAD_TIMEOUT_MS there or upload smaller batches (BOS_MAX_BUNDLE_UPLOAD_BYTES)`;\n }\n try {\n const parsed = JSON.parse(detail) as { code?: unknown; message?: unknown };\n if (parsed?.code === \"INTERNAL_SERVER_ERROR\" && parsed?.message === \"Internal Server Error\") {\n return `${trimmed} — the host hid the underlying error; check the host logs for the cause`;\n }\n } catch {\n // not a JSON error body\n }\n return trimmed;\n}\n\nasync function requestJson<T>(\n config: UploadClientConfig,\n label: string,\n path: string,\n parse: (value: unknown) => T,\n init?: RequestInit,\n): Promise<T> {\n const fetchImpl = config.fetchImpl ?? fetch;\n const headers: Record<string, string> = { ...(init?.headers as Record<string, string>) };\n if (config.apiKey) headers[\"x-api-key\"] = config.apiKey;\n if (init?.body && !headers[\"content-type\"]) headers[\"content-type\"] = \"application/json\";\n\n let lastError: UploadTransferError | Error | undefined;\n let transportFailures = 0;\n let statusFailures = 0;\n for (let attempt = 1; attempt <= TRANSPORT_ATTEMPTS; attempt++) {\n try {\n const response = await fetchImpl(`${config.origin.replace(/\\/$/, \"\")}${path}`, {\n ...init,\n headers: transportFailures > 0 ? { ...headers, connection: \"close\" } : headers,\n });\n\n if (response.ok) {\n return parse(await response.json());\n }\n\n const detail = await response.text().catch(() => \"\");\n lastError = new UploadTransferError(\n response.status,\n `[upload] ${label} failed: ${response.status} ${describeTransferFailure(response.status, detail)}`,\n );\n if (!RETRYABLE_STATUSES.has(response.status)) break;\n statusFailures += 1;\n if (statusFailures >= STATUS_ATTEMPTS) break;\n } catch (error) {\n transportFailures += 1;\n lastError = new UploadTransferError(\n 0,\n `[upload] ${label} failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n if (attempt < TRANSPORT_ATTEMPTS) await sleep(backoffMs(attempt));\n }\n\n if (lastError instanceof UploadTransferError && lastError.status === 0) {\n lastError = lastError.withNote(\n \" (Bun fetch note: re-run with fetch verbose diagnostics for detail)\",\n );\n }\n throw lastError ?? new Error(`[upload] ${label} failed`);\n}\n\nconst parseJsonBody = <T>(label: string, schema: { parse: (value: unknown) => T }) => {\n return (response: unknown): T => {\n try {\n return schema.parse(response);\n } catch (error) {\n throw new UploadTransferError(\n 0,\n `[upload] ${label} returned an unexpected payload: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n };\n};\n\nexport function createUploadClient(config: UploadClientConfig): UploadClient {\n return {\n uploadBundle(input) {\n const payload = UploadRequestSchema.parse(input);\n return requestJson(\n config,\n `bundle upload for ${payload.workspace}`,\n \"/api/storage/bundles\",\n parseJsonBody(\"bundle upload\", UploadResponseSchema),\n { method: \"POST\", body: JSON.stringify(payload) },\n );\n },\n\n listBundles(input) {\n const query = new URLSearchParams({ account: input.account, gateway: input.gateway });\n if (input.workspace) query.set(\"workspace\", input.workspace);\n return requestJson(\n config,\n \"bundle listing\",\n `/api/storage/bundles?${query.toString()}`,\n parseJsonBody(\"bundle listing\", ListBundlesResultSchema),\n { method: \"GET\" },\n );\n },\n\n listVersions(input) {\n const query = new URLSearchParams({\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n });\n return requestJson(\n config,\n \"version listing\",\n `/api/storage/versions?${query.toString()}`,\n parseJsonBody(\"version listing\", ListVersionsResultSchema),\n { method: \"GET\" },\n );\n },\n\n integrityReport(input) {\n const query = new URLSearchParams({\n account: input.account,\n gateway: input.gateway,\n workspace: input.workspace,\n version: input.version,\n });\n return requestJson(\n config,\n \"integrity report\",\n `/api/storage/integrity?${query.toString()}`,\n parseJsonBody(\"integrity report\", IntegrityReportResultSchema),\n { method: \"GET\" },\n );\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/cdn-deploy.cjs
CHANGED
|
@@ -73,7 +73,13 @@ function resolveCdnDeployInputs(input) {
|
|
|
73
73
|
error: `The CLI session pins uploads to ${sessionSite} — a local site can never receive production bundles. Re-run \`bos login\` against the production site, or set BOS_STORAGE_ORIGIN explicitly.`
|
|
74
74
|
};
|
|
75
75
|
storageOrigin = sessionSite;
|
|
76
|
-
} else
|
|
76
|
+
} else return {
|
|
77
|
+
cdnOrigin,
|
|
78
|
+
storageOrigin: storageOriginDefault,
|
|
79
|
+
apiKey,
|
|
80
|
+
warning,
|
|
81
|
+
error: `Bundle uploads require an upload-service origin — the gateway (${input.gateway}) no longer serves uploads (ADR 0027: the upload service is standalone at upload.everything.dev). Set BOS_STORAGE_ORIGIN in the environment (CI) or re-run \`bos login\` against the production site so the session pins it.`
|
|
82
|
+
};
|
|
77
83
|
if (!apiKey) return {
|
|
78
84
|
cdnOrigin,
|
|
79
85
|
storageOrigin,
|
|
@@ -96,27 +102,39 @@ function resolveCdnDeployInputs(input) {
|
|
|
96
102
|
};
|
|
97
103
|
}
|
|
98
104
|
/**
|
|
99
|
-
* Pre-upload probe: the storage origin must actually be the
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
105
|
+
* Pre-upload probe: the storage origin must actually be the bundle-upload
|
|
106
|
+
* service.
|
|
107
|
+
* `/health` is the primary readiness check, with MCP kept as a compatibility
|
|
108
|
+
* fallback for older fixtures and legacy hosts.
|
|
103
109
|
*/
|
|
104
110
|
async function probeStorageOrigin(storageOrigin, fetchImpl = fetch) {
|
|
105
|
-
const
|
|
111
|
+
const base = storageOrigin.replace(/\/$/, "");
|
|
112
|
+
const healthUrl = `${base}/health`;
|
|
113
|
+
const mcpUrl = `${base}/.well-known/mcp.json`;
|
|
106
114
|
let response;
|
|
107
115
|
try {
|
|
108
|
-
response = await fetchImpl(
|
|
116
|
+
response = await fetchImpl(healthUrl, { headers: { accept: "text/plain" } });
|
|
109
117
|
} catch (error) {
|
|
110
118
|
return `unreachable (${String(error instanceof Error ? error.message : error)})`;
|
|
111
119
|
}
|
|
112
|
-
if (
|
|
113
|
-
|
|
120
|
+
if (response.ok) {
|
|
121
|
+
const body = await response.text().catch(() => "");
|
|
122
|
+
if (body.trim() === "OK" || body.trim().length === 0) return void 0;
|
|
123
|
+
}
|
|
124
|
+
let mcpResponse;
|
|
125
|
+
try {
|
|
126
|
+
mcpResponse = await fetchImpl(mcpUrl, { headers: { accept: "application/json" } });
|
|
127
|
+
} catch {
|
|
128
|
+
return `GET /health responded ${response.status}; fallback /.well-known/mcp.json unreachable`;
|
|
129
|
+
}
|
|
130
|
+
if (!mcpResponse.ok) return `GET /health responded ${response.status}; GET /.well-known/mcp.json responded ${mcpResponse.status}`;
|
|
114
131
|
try {
|
|
115
|
-
body = await
|
|
132
|
+
const body = await mcpResponse.json();
|
|
133
|
+
if (!body || typeof body !== "object") return "did not return the platform MCP descriptor";
|
|
134
|
+
return;
|
|
116
135
|
} catch {
|
|
117
136
|
return "did not return the platform MCP descriptor (non-JSON response)";
|
|
118
137
|
}
|
|
119
|
-
if (!body || typeof body !== "object") return "did not return the platform MCP descriptor";
|
|
120
138
|
}
|
|
121
139
|
|
|
122
140
|
//#endregion
|
package/dist/cdn-deploy.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdn-deploy.cjs","names":[],"sources":["../src/cdn-deploy.ts"],"sourcesContent":["interface SessionCredentialLike {\n apiKey?: string;\n siteUrl?: string;\n accountId?: string | null;\n}\n\nexport interface CdnDeployInputs {\n cdnOrigin: string | undefined;\n storageOrigin: string;\n apiKey: string | undefined;\n warning?: string;\n error?: string;\n}\n\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\", \"0.0.0.0\", \"local\"]);\n\n/** Local/private destinations can never serve production bundles. */\nexport function isLocalOrigin(url: string): boolean {\n try {\n const { hostname, protocol } = new URL(url);\n if (protocol !== \"http:\" && protocol !== \"https:\") return true;\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n return (\n LOCAL_HOSTNAMES.has(host) ||\n host.endsWith(\".localhost\") ||\n host.endsWith(\".local\") ||\n /^127\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n /^10\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n /^192\\.168\\.\\d+\\.\\d+$/.test(host) ||\n /^172\\.(1[6-9]|2\\d|3[01])\\.\\d+\\.\\d+$/.test(host) ||\n host.startsWith(\"169.254.\") ||\n (host.includes(\":\") &&\n (host.startsWith(\"fe80:\") || host.startsWith(\"fc\") || host.startsWith(\"fd\")))\n );\n } catch {\n return true;\n }\n}\n\n/**\n * Deploy origin resolution (ADR 0020, as amended): the CDN origin comes from\n * the env or the authored `cdn.origin` (inherited via extends) — a hard\n * requirement when uploads are planned, because the host never serves\n * `/bundles` itself. The upload origin is the env, the `bos login` session's\n * siteUrl, or the runtime's own gateway. A session pinned to a local site is\n * a hard error (the origin can only ever be a squatter's port); an env-pinned\n * local origin is deliberate local testing and only warns. Config-only\n * publishes (`uploadsPlanned: false`) ship the committed config verbatim —\n * no origins are resolved and nothing is required.\n */\nexport function resolveCdnDeployInputs(input: {\n env: Record<string, string | undefined>;\n bosConfig: { cdn?: { origin?: string } } | null;\n session: SessionCredentialLike | null;\n account: string;\n gateway: string;\n uploadsPlanned: boolean;\n}): CdnDeployInputs {\n const envCdnOrigin = input.env.BOS_BUNDLE_CDN_ORIGIN?.replace(/\\/$/, \"\");\n const configCdnOrigin = input.bosConfig?.cdn?.origin?.replace(/\\/$/, \"\");\n const storageEnvOrigin = input.env.BOS_STORAGE_ORIGIN?.replace(/\\/$/, \"\");\n const sessionSite = input.session?.siteUrl?.replace(/\\/$/, \"\");\n const apiKey = input.env.BOS_STORAGE_API_KEY ?? input.session?.apiKey;\n const uploadsPlanned = input.uploadsPlanned;\n\n const storageOriginDefault = storageEnvOrigin ?? sessionSite ?? `https://${input.gateway}`;\n\n if (!uploadsPlanned) {\n return { cdnOrigin: undefined, storageOrigin: storageOriginDefault, apiKey };\n }\n\n let cdnOrigin: string | undefined;\n let warning: string | undefined;\n if (envCdnOrigin) {\n cdnOrigin = envCdnOrigin;\n if (isLocalOrigin(envCdnOrigin)) {\n warning = `BOS_BUNDLE_CDN_ORIGIN is a local URL (${envCdnOrigin}) — deploying to a local stack, published bundle URLs will not resolve publicly.`;\n }\n } else if (configCdnOrigin) {\n if (isLocalOrigin(configCdnOrigin)) {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n error:\n `the authored config sets cdn.origin to the local URL ${configCdnOrigin} — deploy bundle ` +\n \"origins must be publicly reachable. Set BOS_BUNDLE_CDN_ORIGIN for a local deploy, \" +\n \"or fix cdn.origin (inherited via extends).\",\n };\n }\n cdnOrigin = configCdnOrigin;\n } else {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n error:\n \"CDN deploy requires a bundle origin — set `cdn.origin` in bos.config.json \" +\n \"(children inherit it from the base runtime via extends) or BOS_BUNDLE_CDN_ORIGIN \" +\n \"in the environment.\",\n };\n }\n\n let storageOrigin: string;\n if (storageEnvOrigin) {\n storageOrigin = storageEnvOrigin;\n if (isLocalOrigin(storageEnvOrigin) && !warning) {\n warning = `BOS_STORAGE_ORIGIN is a local URL (${storageEnvOrigin}) — uploading to a local stack.`;\n }\n } else if (sessionSite) {\n if (isLocalOrigin(sessionSite)) {\n return {\n cdnOrigin,\n storageOrigin: sessionSite,\n apiKey,\n error:\n `The CLI session pins uploads to ${sessionSite} — a local site can never receive ` +\n \"production bundles. Re-run `bos login` against the production site, or set \" +\n \"BOS_STORAGE_ORIGIN explicitly.\",\n };\n }\n storageOrigin = sessionSite;\n } else {\n storageOrigin = `https://${input.gateway}`;\n }\n\n if (!apiKey) {\n return {\n cdnOrigin,\n storageOrigin,\n apiKey,\n warning,\n error:\n \"CDN deploy requires bundle-upload credentials — run `bos login --key` and add the printed key \" +\n \"to GitHub secrets as BOS_STORAGE_API_KEY (or run `bos login` for interactive deploys)\",\n };\n }\n if (input.session?.accountId && input.session.accountId !== input.account) {\n return {\n cdnOrigin,\n storageOrigin,\n apiKey,\n warning,\n error: `The CLI session was created for ${input.session.accountId}, but the configured account is ${input.account}. Re-run bos login under the matching account.`,\n };\n }\n return { cdnOrigin, storageOrigin, apiKey, warning };\n}\n\n/**\n * Pre-upload probe: the storage origin must actually be the platform API.\n * `/.well-known/mcp.json` is the cheapest unauthenticated platform\n * fingerprint — a random service squatting the port (or a stale session\n * pointing at the wrong server) fails here before megabytes move.\n */\nexport async function probeStorageOrigin(\n storageOrigin: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<string | undefined> {\n const probeUrl = `${storageOrigin.replace(/\\/$/, \"\")}/.well-known/mcp.json`;\n let response: Response;\n try {\n response = await fetchImpl(probeUrl, { headers: { accept: \"application/json\" } });\n } catch (error) {\n return `unreachable (${String(error instanceof Error ? error.message : error)})`;\n }\n if (!response.ok) {\n return `GET /.well-known/mcp.json responded ${response.status}`;\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n return \"did not return the platform MCP descriptor (non-JSON response)\";\n }\n if (!body || typeof body !== \"object\") {\n return \"did not return the platform MCP descriptor\";\n }\n return undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cdn-deploy.cjs","names":[],"sources":["../src/cdn-deploy.ts"],"sourcesContent":["interface SessionCredentialLike {\n apiKey?: string;\n siteUrl?: string;\n accountId?: string | null;\n}\n\nexport interface CdnDeployInputs {\n cdnOrigin: string | undefined;\n storageOrigin: string;\n apiKey: string | undefined;\n warning?: string;\n error?: string;\n}\n\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\", \"0.0.0.0\", \"local\"]);\n\n/** Local/private destinations can never serve production bundles. */\nexport function isLocalOrigin(url: string): boolean {\n try {\n const { hostname, protocol } = new URL(url);\n if (protocol !== \"http:\" && protocol !== \"https:\") return true;\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n return (\n LOCAL_HOSTNAMES.has(host) ||\n host.endsWith(\".localhost\") ||\n host.endsWith(\".local\") ||\n /^127\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n /^10\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n /^192\\.168\\.\\d+\\.\\d+$/.test(host) ||\n /^172\\.(1[6-9]|2\\d|3[01])\\.\\d+\\.\\d+$/.test(host) ||\n host.startsWith(\"169.254.\") ||\n (host.includes(\":\") &&\n (host.startsWith(\"fe80:\") || host.startsWith(\"fc\") || host.startsWith(\"fd\")))\n );\n } catch {\n return true;\n }\n}\n\n/**\n * Deploy origin resolution (ADR 0020, as amended): the CDN origin comes from\n * the env or the authored `cdn.origin` (inherited via extends) — a hard\n * requirement when uploads are planned, because the host never serves\n * `/bundles` itself. The upload origin is the env, the `bos login` session's\n * siteUrl, or the runtime's own gateway. A session pinned to a local site is\n * a hard error (the origin can only ever be a squatter's port); an env-pinned\n * local origin is deliberate local testing and only warns. Config-only\n * publishes (`uploadsPlanned: false`) ship the committed config verbatim —\n * no origins are resolved and nothing is required.\n */\nexport function resolveCdnDeployInputs(input: {\n env: Record<string, string | undefined>;\n bosConfig: { cdn?: { origin?: string } } | null;\n session: SessionCredentialLike | null;\n account: string;\n gateway: string;\n uploadsPlanned: boolean;\n}): CdnDeployInputs {\n const envCdnOrigin = input.env.BOS_BUNDLE_CDN_ORIGIN?.replace(/\\/$/, \"\");\n const configCdnOrigin = input.bosConfig?.cdn?.origin?.replace(/\\/$/, \"\");\n const storageEnvOrigin = input.env.BOS_STORAGE_ORIGIN?.replace(/\\/$/, \"\");\n const sessionSite = input.session?.siteUrl?.replace(/\\/$/, \"\");\n const apiKey = input.env.BOS_STORAGE_API_KEY ?? input.session?.apiKey;\n const uploadsPlanned = input.uploadsPlanned;\n\n const storageOriginDefault = storageEnvOrigin ?? sessionSite ?? `https://${input.gateway}`;\n\n if (!uploadsPlanned) {\n return { cdnOrigin: undefined, storageOrigin: storageOriginDefault, apiKey };\n }\n\n let cdnOrigin: string | undefined;\n let warning: string | undefined;\n if (envCdnOrigin) {\n cdnOrigin = envCdnOrigin;\n if (isLocalOrigin(envCdnOrigin)) {\n warning = `BOS_BUNDLE_CDN_ORIGIN is a local URL (${envCdnOrigin}) — deploying to a local stack, published bundle URLs will not resolve publicly.`;\n }\n } else if (configCdnOrigin) {\n if (isLocalOrigin(configCdnOrigin)) {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n error:\n `the authored config sets cdn.origin to the local URL ${configCdnOrigin} — deploy bundle ` +\n \"origins must be publicly reachable. Set BOS_BUNDLE_CDN_ORIGIN for a local deploy, \" +\n \"or fix cdn.origin (inherited via extends).\",\n };\n }\n cdnOrigin = configCdnOrigin;\n } else {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n error:\n \"CDN deploy requires a bundle origin — set `cdn.origin` in bos.config.json \" +\n \"(children inherit it from the base runtime via extends) or BOS_BUNDLE_CDN_ORIGIN \" +\n \"in the environment.\",\n };\n }\n\n let storageOrigin: string;\n if (storageEnvOrigin) {\n storageOrigin = storageEnvOrigin;\n if (isLocalOrigin(storageEnvOrigin) && !warning) {\n warning = `BOS_STORAGE_ORIGIN is a local URL (${storageEnvOrigin}) — uploading to a local stack.`;\n }\n } else if (sessionSite) {\n if (isLocalOrigin(sessionSite)) {\n return {\n cdnOrigin,\n storageOrigin: sessionSite,\n apiKey,\n error:\n `The CLI session pins uploads to ${sessionSite} — a local site can never receive ` +\n \"production bundles. Re-run `bos login` against the production site, or set \" +\n \"BOS_STORAGE_ORIGIN explicitly.\",\n };\n }\n storageOrigin = sessionSite;\n } else {\n return {\n cdnOrigin,\n storageOrigin: storageOriginDefault,\n apiKey,\n warning,\n error:\n `Bundle uploads require an upload-service origin — the gateway (${input.gateway}) no longer serves ` +\n \"uploads (ADR 0027: the upload service is standalone at upload.everything.dev). Set BOS_STORAGE_ORIGIN \" +\n \"in the environment (CI) or re-run `bos login` against the production site so the session pins it.\",\n };\n }\n\n if (!apiKey) {\n return {\n cdnOrigin,\n storageOrigin,\n apiKey,\n warning,\n error:\n \"CDN deploy requires bundle-upload credentials — run `bos login --key` and add the printed key \" +\n \"to GitHub secrets as BOS_STORAGE_API_KEY (or run `bos login` for interactive deploys)\",\n };\n }\n if (input.session?.accountId && input.session.accountId !== input.account) {\n return {\n cdnOrigin,\n storageOrigin,\n apiKey,\n warning,\n error: `The CLI session was created for ${input.session.accountId}, but the configured account is ${input.account}. Re-run bos login under the matching account.`,\n };\n }\n return { cdnOrigin, storageOrigin, apiKey, warning };\n}\n\n/**\n * Pre-upload probe: the storage origin must actually be the bundle-upload\n * service.\n * `/health` is the primary readiness check, with MCP kept as a compatibility\n * fallback for older fixtures and legacy hosts.\n */\nexport async function probeStorageOrigin(\n storageOrigin: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<string | undefined> {\n const base = storageOrigin.replace(/\\/$/, \"\");\n const healthUrl = `${base}/health`;\n const mcpUrl = `${base}/.well-known/mcp.json`;\n let response: Response;\n try {\n response = await fetchImpl(healthUrl, { headers: { accept: \"text/plain\" } });\n } catch (error) {\n return `unreachable (${String(error instanceof Error ? error.message : error)})`;\n }\n if (response.ok) {\n const body = await response.text().catch(() => \"\");\n if (body.trim() === \"OK\" || body.trim().length === 0) return undefined;\n }\n let mcpResponse: Response;\n try {\n mcpResponse = await fetchImpl(mcpUrl, { headers: { accept: \"application/json\" } });\n } catch {\n return `GET /health responded ${response.status}; fallback /.well-known/mcp.json unreachable`;\n }\n if (!mcpResponse.ok) {\n return `GET /health responded ${response.status}; GET /.well-known/mcp.json responded ${mcpResponse.status}`;\n }\n try {\n const body = await mcpResponse.json();\n if (!body || typeof body !== \"object\") {\n return \"did not return the platform MCP descriptor\";\n }\n return undefined;\n } catch {\n return \"did not return the platform MCP descriptor (non-JSON response)\";\n }\n}\n"],"mappings":"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"}
|
package/dist/cdn-deploy.mjs
CHANGED
|
@@ -73,7 +73,13 @@ function resolveCdnDeployInputs(input) {
|
|
|
73
73
|
error: `The CLI session pins uploads to ${sessionSite} — a local site can never receive production bundles. Re-run \`bos login\` against the production site, or set BOS_STORAGE_ORIGIN explicitly.`
|
|
74
74
|
};
|
|
75
75
|
storageOrigin = sessionSite;
|
|
76
|
-
} else
|
|
76
|
+
} else return {
|
|
77
|
+
cdnOrigin,
|
|
78
|
+
storageOrigin: storageOriginDefault,
|
|
79
|
+
apiKey,
|
|
80
|
+
warning,
|
|
81
|
+
error: `Bundle uploads require an upload-service origin — the gateway (${input.gateway}) no longer serves uploads (ADR 0027: the upload service is standalone at upload.everything.dev). Set BOS_STORAGE_ORIGIN in the environment (CI) or re-run \`bos login\` against the production site so the session pins it.`
|
|
82
|
+
};
|
|
77
83
|
if (!apiKey) return {
|
|
78
84
|
cdnOrigin,
|
|
79
85
|
storageOrigin,
|
|
@@ -96,27 +102,39 @@ function resolveCdnDeployInputs(input) {
|
|
|
96
102
|
};
|
|
97
103
|
}
|
|
98
104
|
/**
|
|
99
|
-
* Pre-upload probe: the storage origin must actually be the
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
105
|
+
* Pre-upload probe: the storage origin must actually be the bundle-upload
|
|
106
|
+
* service.
|
|
107
|
+
* `/health` is the primary readiness check, with MCP kept as a compatibility
|
|
108
|
+
* fallback for older fixtures and legacy hosts.
|
|
103
109
|
*/
|
|
104
110
|
async function probeStorageOrigin(storageOrigin, fetchImpl = fetch) {
|
|
105
|
-
const
|
|
111
|
+
const base = storageOrigin.replace(/\/$/, "");
|
|
112
|
+
const healthUrl = `${base}/health`;
|
|
113
|
+
const mcpUrl = `${base}/.well-known/mcp.json`;
|
|
106
114
|
let response;
|
|
107
115
|
try {
|
|
108
|
-
response = await fetchImpl(
|
|
116
|
+
response = await fetchImpl(healthUrl, { headers: { accept: "text/plain" } });
|
|
109
117
|
} catch (error) {
|
|
110
118
|
return `unreachable (${String(error instanceof Error ? error.message : error)})`;
|
|
111
119
|
}
|
|
112
|
-
if (
|
|
113
|
-
|
|
120
|
+
if (response.ok) {
|
|
121
|
+
const body = await response.text().catch(() => "");
|
|
122
|
+
if (body.trim() === "OK" || body.trim().length === 0) return void 0;
|
|
123
|
+
}
|
|
124
|
+
let mcpResponse;
|
|
125
|
+
try {
|
|
126
|
+
mcpResponse = await fetchImpl(mcpUrl, { headers: { accept: "application/json" } });
|
|
127
|
+
} catch {
|
|
128
|
+
return `GET /health responded ${response.status}; fallback /.well-known/mcp.json unreachable`;
|
|
129
|
+
}
|
|
130
|
+
if (!mcpResponse.ok) return `GET /health responded ${response.status}; GET /.well-known/mcp.json responded ${mcpResponse.status}`;
|
|
114
131
|
try {
|
|
115
|
-
body = await
|
|
132
|
+
const body = await mcpResponse.json();
|
|
133
|
+
if (!body || typeof body !== "object") return "did not return the platform MCP descriptor";
|
|
134
|
+
return;
|
|
116
135
|
} catch {
|
|
117
136
|
return "did not return the platform MCP descriptor (non-JSON response)";
|
|
118
137
|
}
|
|
119
|
-
if (!body || typeof body !== "object") return "did not return the platform MCP descriptor";
|
|
120
138
|
}
|
|
121
139
|
|
|
122
140
|
//#endregion
|