@embeddables/deploy 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/deploy.ts","../src/errors.ts","../src/files.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/deploy.ts","../src/errors.ts","../src/files.ts"],"mappings":";cASa;UAKI;;EAEf;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;;EAEA;EACA;;EAEA;EACA,eAAe;;EAEf,SAAS,eAAe;;UAGT;EACf;GACC;;UAGc;EACf;EACA;EACA;EACA;EACA;EACA;;EAEA,WAAW;;EAEX;;EAEA;;EAEA;;;;;;;;iBAeoB,iBACpB,SAAS,0BACR,QAAQ;;;;cCvEE,oBAAoB;WACtB;WACA;EAEG,YAAA,iBAAiB;IAAW;IAAiB;IAAe;;;;;;cCE7D"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
1
2
|
import { readFile, readdir } from "node:fs/promises";
|
|
2
3
|
import { join, relative, sep } from "node:path";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
4
|
import { gzipSync } from "node:zlib";
|
|
5
5
|
//#region src/batches.ts
|
|
6
6
|
const PART_OVERHEAD_BYTES = 512;
|
|
@@ -143,7 +143,7 @@ function writeOctal(block, offset, length, value) {
|
|
|
143
143
|
//#region src/deploy.ts
|
|
144
144
|
const DEFAULT_API_ORIGIN = "https://backend.embeddables.com";
|
|
145
145
|
const DEFAULT_CONCURRENCY = 3;
|
|
146
|
-
const
|
|
146
|
+
const REQUEST_ATTEMPTS = 3;
|
|
147
147
|
/**
|
|
148
148
|
* Uploads a built app in three steps: a manifest of static files, the files
|
|
149
149
|
* the backend does not already have (in parallel batches), then the server
|
|
@@ -152,6 +152,7 @@ const BATCH_ATTEMPTS = 3;
|
|
|
152
152
|
*/
|
|
153
153
|
async function deployHostingApp(options) {
|
|
154
154
|
const api = createApi(options);
|
|
155
|
+
const idempotencyKey = randomUUID();
|
|
155
156
|
const files = options.clientDir ? await readStaticFiles(options.clientDir) : [];
|
|
156
157
|
const server = await readServerArchive(options.serverDir);
|
|
157
158
|
const manifest = {};
|
|
@@ -166,9 +167,12 @@ async function deployHostingApp(options) {
|
|
|
166
167
|
size: file.size
|
|
167
168
|
});
|
|
168
169
|
}
|
|
169
|
-
const session = await api.
|
|
170
|
+
const session = await api.retryingJson("/hosting/v1/deploys/sessions", {
|
|
170
171
|
method: "POST",
|
|
171
|
-
headers: {
|
|
172
|
+
headers: {
|
|
173
|
+
"content-type": "application/json",
|
|
174
|
+
"idempotency-key": idempotencyKey
|
|
175
|
+
},
|
|
172
176
|
body: JSON.stringify({
|
|
173
177
|
appSlug: options.appSlug,
|
|
174
178
|
branch: options.branch,
|
|
@@ -192,7 +196,7 @@ async function deployHostingApp(options) {
|
|
|
192
196
|
});
|
|
193
197
|
});
|
|
194
198
|
return {
|
|
195
|
-
...await api.
|
|
199
|
+
...await api.retryingJson(`/hosting/v1/deploys/sessions/${encodeURIComponent(session.sessionId)}/finalize`, {
|
|
196
200
|
method: "POST",
|
|
197
201
|
headers: { "content-type": "application/gzip" },
|
|
198
202
|
body: server
|
|
@@ -232,19 +236,18 @@ function createApi(options) {
|
|
|
232
236
|
return response;
|
|
233
237
|
}
|
|
234
238
|
return {
|
|
235
|
-
|
|
236
|
-
return await (await send(path, init)).json();
|
|
237
|
-
},
|
|
238
|
-
/** Retries network failures, 429, and 5xx. Batches are idempotent: bytes are keyed by their hash. */
|
|
239
|
+
/** Retries network failures, 429, and 5xx for retry-safe deploy operations. */
|
|
239
240
|
async retrying(path, init) {
|
|
240
241
|
for (let attempt = 1;; attempt++) try {
|
|
241
|
-
await send(path, init);
|
|
242
|
-
return;
|
|
242
|
+
return await send(path, init);
|
|
243
243
|
} catch (error) {
|
|
244
244
|
const status = error instanceof DeployError ? error.status : null;
|
|
245
|
-
if (!(status === null || status === 429 || status >= 500) || attempt >=
|
|
245
|
+
if (!(status === null || status === 429 || status >= 500) || attempt >= REQUEST_ATTEMPTS) throw error;
|
|
246
246
|
await sleep(500 * 3 ** (attempt - 1));
|
|
247
247
|
}
|
|
248
|
+
},
|
|
249
|
+
async retryingJson(path, init) {
|
|
250
|
+
return await (await this.retrying(path, init)).json();
|
|
248
251
|
}
|
|
249
252
|
};
|
|
250
253
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/batches.ts","../src/errors.ts","../src/files.ts","../src/tar.ts","../src/deploy.ts"],"sourcesContent":["/** The backend rejects an asset batch whose request body is over 30 MB. */\nexport const MAX_BATCH_BYTES = 30 * 1024 * 1024\n\n// * Multipart adds a boundary and part headers to every file; this leaves\n// room for them so a batch never trips the backend's Content-Length check.\nconst PART_OVERHEAD_BYTES = 512\nconst BATCH_HEADROOM_BYTES = 256 * 1024\n\n/**\n * Splits the backend's buckets of missing content hashes into requests that\n * each stay under the batch limit. A single file is at most 25 MB, so it\n * always fits in a batch of its own.\n */\nexport function planBatches(buckets: string[][], sizes: ReadonlyMap<string, number>): string[][] {\n const budget = MAX_BATCH_BYTES - BATCH_HEADROOM_BYTES\n const batches: string[][] = []\n for (const bucket of buckets) {\n let current: string[] = []\n let used = 0\n for (const hash of bucket) {\n const size = sizes.get(hash)\n if (size === undefined) throw new Error(`Backend asked for unknown file ${hash}.`)\n const cost = size + PART_OVERHEAD_BYTES\n if (current.length > 0 && used + cost > budget) {\n batches.push(current)\n current = []\n used = 0\n }\n current.push(hash)\n used += cost\n }\n if (current.length > 0) batches.push(current)\n }\n return batches\n}\n","/** A deploy step failed. `status` and `code` come from the backend's problem response when it sent one. */\nexport class DeployError extends Error {\n readonly status: number | null\n readonly code: string | null\n\n constructor(message: string, options: { status?: number; code?: string; cause?: unknown } = {}) {\n super(message, { cause: options.cause })\n this.name = 'DeployError'\n this.status = options.status ?? null\n this.code = options.code ?? null\n }\n}\n","import { createHash } from 'node:crypto'\nimport { readdir, readFile } from 'node:fs/promises'\nimport { join, relative, sep } from 'node:path'\n\nimport { DeployError } from './errors.js'\n\n/** Cloudflare's hard per-file limit for static assets. */\nexport const MAX_STATIC_ASSET_BYTES = 25 * 1024 * 1024\n\nexport interface StaticFile {\n /** Manifest path: posix, relative to the client directory, with a leading slash. */\n path: string\n absolutePath: string\n contentHash: string\n size: number\n}\n\n/** Every regular file under `root`, as posix paths relative to it, sorted. Symlinks are skipped. */\nexport async function listFiles(root: string): Promise<string[]> {\n const found: string[] = []\n async function walk(dir: string): Promise<void> {\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n const absolute = join(dir, entry.name)\n if (entry.isDirectory()) await walk(absolute)\n else if (entry.isFile()) found.push(relative(root, absolute).split(sep).join('/'))\n }\n }\n await walk(root)\n return found.sort()\n}\n\n/** Hashes one file at a time, so memory stays at the size of the largest file. */\nexport async function readStaticFiles(clientDir: string): Promise<StaticFile[]> {\n const files: StaticFile[] = []\n for (const path of await listFiles(clientDir)) {\n const absolutePath = join(clientDir, path)\n const bytes = await readFile(absolutePath)\n if (bytes.byteLength > MAX_STATIC_ASSET_BYTES) {\n throw new DeployError(\n `Static file ${path} is larger than 25 MB. Serve large media from R2 instead.`,\n { code: 'validation.file_too_large' },\n )\n }\n files.push({\n path: `/${path}`,\n absolutePath,\n contentHash: createHash('sha256').update(bytes).digest('hex'),\n size: bytes.byteLength,\n })\n }\n return files\n}\n","import { gzipSync } from 'node:zlib'\n\nimport { DeployError } from './errors.js'\n\nconst BLOCK = 512\nconst NAME_BYTES = 100\nconst PREFIX_BYTES = 155\nconst encoder = new TextEncoder()\n\nexport interface TarEntry {\n path: string\n bytes: Uint8Array\n}\n\n/** A gzipped ustar archive. Paths over 100 bytes are split into the ustar `prefix` field. */\nexport function gzipTar(entries: TarEntry[]): Uint8Array<ArrayBuffer> {\n return new Uint8Array(gzipSync(tar(entries)))\n}\n\nexport function tar(entries: TarEntry[]): Uint8Array {\n const size = entries.reduce(\n (total, entry) => total + BLOCK + Math.ceil(entry.bytes.byteLength / BLOCK) * BLOCK,\n 2 * BLOCK,\n )\n const out = new Uint8Array(size)\n let offset = 0\n for (const entry of entries) {\n out.set(header(entry.path, entry.bytes.byteLength), offset)\n offset += BLOCK\n out.set(entry.bytes, offset)\n offset += Math.ceil(entry.bytes.byteLength / BLOCK) * BLOCK\n }\n return out\n}\n\nfunction header(path: string, size: number): Uint8Array {\n const { name, prefix } = splitPath(path)\n const block = new Uint8Array(BLOCK)\n writeBytes(block, 0, name)\n writeOctal(block, 100, 8, 0o644)\n writeOctal(block, 108, 8, 0)\n writeOctal(block, 116, 8, 0)\n writeOctal(block, 124, 12, size)\n writeOctal(block, 136, 12, 0)\n block.fill(0x20, 148, 156)\n block[156] = 0x30\n writeBytes(block, 257, encoder.encode('ustar\\0'))\n writeBytes(block, 263, encoder.encode('00'))\n writeBytes(block, 345, prefix)\n const checksum = block.reduce((total, byte) => total + byte, 0)\n writeBytes(block, 148, encoder.encode(`${checksum.toString(8).padStart(6, '0')}\\0 `))\n return block\n}\n\nfunction splitPath(path: string): { name: Uint8Array; prefix: Uint8Array } {\n const whole = encoder.encode(path)\n if (whole.byteLength <= NAME_BYTES) return { name: whole, prefix: new Uint8Array() }\n // * Split at the last slash that still fits the prefix, leaving the longest prefix.\n for (let index = path.lastIndexOf('/'); index > 0; index = path.lastIndexOf('/', index - 1)) {\n const prefix = encoder.encode(path.slice(0, index))\n const name = encoder.encode(path.slice(index + 1))\n if (prefix.byteLength <= PREFIX_BYTES && name.byteLength <= NAME_BYTES && name.byteLength > 0) {\n return { name, prefix }\n }\n }\n throw new DeployError(`Path ${path} is too long for a tar archive.`, {\n code: 'validation.failed',\n })\n}\n\nfunction writeBytes(block: Uint8Array, offset: number, bytes: Uint8Array): void {\n block.set(bytes, offset)\n}\n\nfunction writeOctal(block: Uint8Array, offset: number, length: number, value: number): void {\n writeBytes(block, offset, encoder.encode(`${value.toString(8).padStart(length - 1, '0')}\\0`))\n}\n","import { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nimport { planBatches } from './batches.js'\nimport { DeployError } from './errors.js'\nimport { listFiles, readStaticFiles } from './files.js'\nimport { gzipTar } from './tar.js'\n\nexport const DEFAULT_API_ORIGIN = 'https://backend.embeddables.com'\n\nconst DEFAULT_CONCURRENCY = 3\nconst BATCH_ATTEMPTS = 3\n\nexport interface DeployHostingAppOptions {\n /** Project deploy key (`edk_…`). */\n deployKey: string\n appSlug: string\n branch: string\n sha: string\n /** ISO 8601 commit time. Keeps overlapping deploys from moving an alias back to an older commit. */\n committedAt?: string\n /** Worker modules. `wrangler.json` here sets the main module, compatibility date, flags, and vars. */\n serverDir: string\n /** Static files. Omit for an app with none. */\n clientDir?: string\n /** When set, the backend checks that it owns the deploy key. */\n projectId?: string\n apiOrigin?: string\n /** Asset batches uploaded at once. */\n concurrency?: number\n fetch?: typeof fetch\n /** Wait between batch retries; replaced in tests. */\n sleep?: (ms: number) => Promise<void>\n}\n\nexport interface DeployPromotion {\n status: string\n [key: string]: unknown\n}\n\nexport interface DeployHostingAppResult {\n scriptName: string\n branchUrl: string\n commitUrl: string\n appSlug: string\n projectSlug: string\n ref: string\n /** Null when the branch is not the app's production branch. */\n promotion: DeployPromotion | null\n /** True when this deploy registered the app. Surface it: a misspelled slug registers a new app. */\n appRegistered?: true\n /** Static files in the manifest. */\n totalFiles: number\n /** Distinct files sent; the rest were already stored from earlier deploys. */\n uploadedFiles: number\n}\n\ninterface StartSessionResponse {\n sessionId: string\n buckets: string[][]\n expiresAt: string\n}\n\n/**\n * Uploads a built app in three steps: a manifest of static files, the files\n * the backend does not already have (in parallel batches), then the server\n * modules. The same commit can be redeployed safely; unchanged files upload\n * nothing.\n */\nexport async function deployHostingApp(\n options: DeployHostingAppOptions,\n): Promise<DeployHostingAppResult> {\n const api = createApi(options)\n const files = options.clientDir ? await readStaticFiles(options.clientDir) : []\n const server = await readServerArchive(options.serverDir)\n\n const manifest: Record<string, { contentHash: string; size: number }> = {}\n const byHash = new Map<string, { absolutePath: string; size: number }>()\n for (const file of files) {\n manifest[file.path] = { contentHash: file.contentHash, size: file.size }\n if (!byHash.has(file.contentHash)) {\n byHash.set(file.contentHash, { absolutePath: file.absolutePath, size: file.size })\n }\n }\n\n const session = await api.json<StartSessionResponse>('/hosting/v1/deploys/sessions', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n appSlug: options.appSlug,\n branch: options.branch,\n sha: options.sha,\n committedAt: options.committedAt,\n manifest,\n }),\n })\n\n const sizes = new Map([...byHash].map(([hash, file]) => [hash, file.size]))\n const batches = planBatches(session.buckets, sizes)\n const batchPath = `/hosting/v1/deploys/sessions/${encodeURIComponent(session.sessionId)}/assets`\n await runPool(batches, options.concurrency ?? DEFAULT_CONCURRENCY, async (batch) => {\n const form = new FormData()\n for (const hash of batch) {\n const file = byHash.get(hash)!\n form.append(hash, new Blob([await readFile(file.absolutePath)]), hash)\n }\n await api.retrying(batchPath, { method: 'POST', body: form })\n })\n\n // ! Not retried: a finalize that landed ends the session, so a retry would\n // report a missing session for a deploy that succeeded.\n const result = await api.json<Omit<DeployHostingAppResult, 'totalFiles' | 'uploadedFiles'>>(\n `/hosting/v1/deploys/sessions/${encodeURIComponent(session.sessionId)}/finalize`,\n { method: 'POST', headers: { 'content-type': 'application/gzip' }, body: server },\n )\n return {\n ...result,\n totalFiles: files.length,\n uploadedFiles: batches.reduce((total, batch) => total + batch.length, 0),\n }\n}\n\nasync function readServerArchive(serverDir: string): Promise<Uint8Array<ArrayBuffer>> {\n const paths = await listFiles(serverDir)\n if (paths.length === 0) {\n throw new DeployError(`No server modules found in ${serverDir}.`, { code: 'validation.failed' })\n }\n const entries = await Promise.all(\n paths.map(async (path) => ({\n path: `server/${path}`,\n bytes: new Uint8Array(await readFile(join(serverDir, path))),\n })),\n )\n return gzipTar(entries)\n}\n\nfunction createApi(options: DeployHostingAppOptions) {\n const origin = (options.apiOrigin ?? DEFAULT_API_ORIGIN).replace(/\\/+$/, '')\n const fetchImpl = options.fetch ?? fetch\n const sleep = options.sleep ?? ((ms: number) => new Promise((r) => setTimeout(r, ms)))\n const auth: Record<string, string> = { authorization: `Bearer ${options.deployKey}` }\n if (options.projectId) auth['x-project-id'] = options.projectId\n\n async function send(path: string, init: RequestInit): Promise<Response> {\n let response: Response\n try {\n response = await fetchImpl(`${origin}${path}`, {\n ...init,\n headers: { ...auth, ...(init.headers as Record<string, string> | undefined) },\n })\n } catch (cause) {\n throw new DeployError(`Could not reach ${origin}.`, { cause })\n }\n if (!response.ok) throw await problem(response)\n return response\n }\n\n return {\n async json<T>(path: string, init: RequestInit): Promise<T> {\n return (await (await send(path, init)).json()) as T\n },\n /** Retries network failures, 429, and 5xx. Batches are idempotent: bytes are keyed by their hash. */\n async retrying(path: string, init: RequestInit): Promise<void> {\n for (let attempt = 1; ; attempt++) {\n try {\n await send(path, init)\n return\n } catch (error) {\n const status = error instanceof DeployError ? error.status : null\n const transient = status === null || status === 429 || status >= 500\n if (!transient || attempt >= BATCH_ATTEMPTS) throw error\n await sleep(500 * 3 ** (attempt - 1))\n }\n }\n },\n }\n}\n\nasync function problem(response: Response): Promise<DeployError> {\n let body: { detail?: unknown; title?: unknown; code?: unknown } = {}\n try {\n body = (await response.json()) as typeof body\n } catch {\n // * Not every failure (a proxy, a gateway timeout) returns problem JSON.\n }\n const detail = typeof body.detail === 'string' ? body.detail : null\n const title = typeof body.title === 'string' ? body.title : null\n return new DeployError(detail ?? title ?? `Deploy request failed with ${response.status}.`, {\n status: response.status,\n code: typeof body.code === 'string' ? body.code : undefined,\n })\n}\n\nasync function runPool<T>(\n items: T[],\n concurrency: number,\n task: (item: T) => Promise<void>,\n): Promise<void> {\n let next = 0\n let failure: { error: unknown } | null = null\n const workers = Array.from(\n { length: Math.max(1, Math.min(concurrency, items.length)) },\n async () => {\n while (!failure && next < items.length) {\n try {\n await task(items[next++]!)\n } catch (error) {\n failure ??= { error }\n }\n }\n },\n )\n // * Wait for in-flight batches so nothing uploads after the deploy rejects.\n await Promise.all(workers)\n if (failure) throw (failure as { error: unknown }).error\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/batches.ts","../src/errors.ts","../src/files.ts","../src/tar.ts","../src/deploy.ts"],"sourcesContent":["/** The backend rejects an asset batch whose request body is over 30 MB. */\nexport const MAX_BATCH_BYTES = 30 * 1024 * 1024\n\n// * Multipart adds a boundary and part headers to every file; this leaves\n// room for them so a batch never trips the backend's Content-Length check.\nconst PART_OVERHEAD_BYTES = 512\nconst BATCH_HEADROOM_BYTES = 256 * 1024\n\n/**\n * Splits the backend's buckets of missing content hashes into requests that\n * each stay under the batch limit. A single file is at most 25 MB, so it\n * always fits in a batch of its own.\n */\nexport function planBatches(buckets: string[][], sizes: ReadonlyMap<string, number>): string[][] {\n const budget = MAX_BATCH_BYTES - BATCH_HEADROOM_BYTES\n const batches: string[][] = []\n for (const bucket of buckets) {\n let current: string[] = []\n let used = 0\n for (const hash of bucket) {\n const size = sizes.get(hash)\n if (size === undefined) throw new Error(`Backend asked for unknown file ${hash}.`)\n const cost = size + PART_OVERHEAD_BYTES\n if (current.length > 0 && used + cost > budget) {\n batches.push(current)\n current = []\n used = 0\n }\n current.push(hash)\n used += cost\n }\n if (current.length > 0) batches.push(current)\n }\n return batches\n}\n","/** A deploy step failed. `status` and `code` come from the backend's problem response when it sent one. */\nexport class DeployError extends Error {\n readonly status: number | null\n readonly code: string | null\n\n constructor(message: string, options: { status?: number; code?: string; cause?: unknown } = {}) {\n super(message, { cause: options.cause })\n this.name = 'DeployError'\n this.status = options.status ?? null\n this.code = options.code ?? null\n }\n}\n","import { createHash } from 'node:crypto'\nimport { readdir, readFile } from 'node:fs/promises'\nimport { join, relative, sep } from 'node:path'\n\nimport { DeployError } from './errors.js'\n\n/** Cloudflare's hard per-file limit for static assets. */\nexport const MAX_STATIC_ASSET_BYTES = 25 * 1024 * 1024\n\nexport interface StaticFile {\n /** Manifest path: posix, relative to the client directory, with a leading slash. */\n path: string\n absolutePath: string\n contentHash: string\n size: number\n}\n\n/** Every regular file under `root`, as posix paths relative to it, sorted. Symlinks are skipped. */\nexport async function listFiles(root: string): Promise<string[]> {\n const found: string[] = []\n async function walk(dir: string): Promise<void> {\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n const absolute = join(dir, entry.name)\n if (entry.isDirectory()) await walk(absolute)\n else if (entry.isFile()) found.push(relative(root, absolute).split(sep).join('/'))\n }\n }\n await walk(root)\n return found.sort()\n}\n\n/** Hashes one file at a time, so memory stays at the size of the largest file. */\nexport async function readStaticFiles(clientDir: string): Promise<StaticFile[]> {\n const files: StaticFile[] = []\n for (const path of await listFiles(clientDir)) {\n const absolutePath = join(clientDir, path)\n const bytes = await readFile(absolutePath)\n if (bytes.byteLength > MAX_STATIC_ASSET_BYTES) {\n throw new DeployError(\n `Static file ${path} is larger than 25 MB. Serve large media from R2 instead.`,\n { code: 'validation.file_too_large' },\n )\n }\n files.push({\n path: `/${path}`,\n absolutePath,\n contentHash: createHash('sha256').update(bytes).digest('hex'),\n size: bytes.byteLength,\n })\n }\n return files\n}\n","import { gzipSync } from 'node:zlib'\n\nimport { DeployError } from './errors.js'\n\nconst BLOCK = 512\nconst NAME_BYTES = 100\nconst PREFIX_BYTES = 155\nconst encoder = new TextEncoder()\n\nexport interface TarEntry {\n path: string\n bytes: Uint8Array\n}\n\n/** A gzipped ustar archive. Paths over 100 bytes are split into the ustar `prefix` field. */\nexport function gzipTar(entries: TarEntry[]): Uint8Array<ArrayBuffer> {\n return new Uint8Array(gzipSync(tar(entries)))\n}\n\nexport function tar(entries: TarEntry[]): Uint8Array {\n const size = entries.reduce(\n (total, entry) => total + BLOCK + Math.ceil(entry.bytes.byteLength / BLOCK) * BLOCK,\n 2 * BLOCK,\n )\n const out = new Uint8Array(size)\n let offset = 0\n for (const entry of entries) {\n out.set(header(entry.path, entry.bytes.byteLength), offset)\n offset += BLOCK\n out.set(entry.bytes, offset)\n offset += Math.ceil(entry.bytes.byteLength / BLOCK) * BLOCK\n }\n return out\n}\n\nfunction header(path: string, size: number): Uint8Array {\n const { name, prefix } = splitPath(path)\n const block = new Uint8Array(BLOCK)\n writeBytes(block, 0, name)\n writeOctal(block, 100, 8, 0o644)\n writeOctal(block, 108, 8, 0)\n writeOctal(block, 116, 8, 0)\n writeOctal(block, 124, 12, size)\n writeOctal(block, 136, 12, 0)\n block.fill(0x20, 148, 156)\n block[156] = 0x30\n writeBytes(block, 257, encoder.encode('ustar\\0'))\n writeBytes(block, 263, encoder.encode('00'))\n writeBytes(block, 345, prefix)\n const checksum = block.reduce((total, byte) => total + byte, 0)\n writeBytes(block, 148, encoder.encode(`${checksum.toString(8).padStart(6, '0')}\\0 `))\n return block\n}\n\nfunction splitPath(path: string): { name: Uint8Array; prefix: Uint8Array } {\n const whole = encoder.encode(path)\n if (whole.byteLength <= NAME_BYTES) return { name: whole, prefix: new Uint8Array() }\n // * Split at the last slash that still fits the prefix, leaving the longest prefix.\n for (let index = path.lastIndexOf('/'); index > 0; index = path.lastIndexOf('/', index - 1)) {\n const prefix = encoder.encode(path.slice(0, index))\n const name = encoder.encode(path.slice(index + 1))\n if (prefix.byteLength <= PREFIX_BYTES && name.byteLength <= NAME_BYTES && name.byteLength > 0) {\n return { name, prefix }\n }\n }\n throw new DeployError(`Path ${path} is too long for a tar archive.`, {\n code: 'validation.failed',\n })\n}\n\nfunction writeBytes(block: Uint8Array, offset: number, bytes: Uint8Array): void {\n block.set(bytes, offset)\n}\n\nfunction writeOctal(block: Uint8Array, offset: number, length: number, value: number): void {\n writeBytes(block, offset, encoder.encode(`${value.toString(8).padStart(length - 1, '0')}\\0`))\n}\n","import { randomUUID } from 'node:crypto'\nimport { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nimport { planBatches } from './batches.js'\nimport { DeployError } from './errors.js'\nimport { listFiles, readStaticFiles } from './files.js'\nimport { gzipTar } from './tar.js'\n\nexport const DEFAULT_API_ORIGIN = 'https://backend.embeddables.com'\n\nconst DEFAULT_CONCURRENCY = 3\nconst REQUEST_ATTEMPTS = 3\n\nexport interface DeployHostingAppOptions {\n /** Project deploy key (`edk_…`). */\n deployKey: string\n appSlug: string\n branch: string\n sha: string\n /** ISO 8601 commit time. Keeps overlapping deploys from moving an alias back to an older commit. */\n committedAt?: string\n /** Worker modules. `wrangler.json` here sets the main module, compatibility date, flags, and vars. */\n serverDir: string\n /** Static files. Omit for an app with none. */\n clientDir?: string\n /** When set, the backend checks that it owns the deploy key. */\n projectId?: string\n apiOrigin?: string\n /** Asset batches uploaded at once. */\n concurrency?: number\n fetch?: typeof fetch\n /** Wait between batch retries; replaced in tests. */\n sleep?: (ms: number) => Promise<void>\n}\n\nexport interface DeployPromotion {\n status: string\n [key: string]: unknown\n}\n\nexport interface DeployHostingAppResult {\n scriptName: string\n branchUrl: string\n commitUrl: string\n appSlug: string\n projectSlug: string\n ref: string\n /** Null when the branch is not the app's production branch. */\n promotion: DeployPromotion | null\n /** True when this deploy registered the app. Surface it: a misspelled slug registers a new app. */\n appRegistered?: true\n /** Static files in the manifest. */\n totalFiles: number\n /** Distinct files sent; the rest were already stored from earlier deploys. */\n uploadedFiles: number\n}\n\ninterface StartSessionResponse {\n sessionId: string\n buckets: string[][]\n expiresAt: string\n}\n\n/**\n * Uploads a built app in three steps: a manifest of static files, the files\n * the backend does not already have (in parallel batches), then the server\n * modules. The same commit can be redeployed safely; unchanged files upload\n * nothing.\n */\nexport async function deployHostingApp(\n options: DeployHostingAppOptions,\n): Promise<DeployHostingAppResult> {\n const api = createApi(options)\n const idempotencyKey = randomUUID()\n const files = options.clientDir ? await readStaticFiles(options.clientDir) : []\n const server = await readServerArchive(options.serverDir)\n\n const manifest: Record<string, { contentHash: string; size: number }> = {}\n const byHash = new Map<string, { absolutePath: string; size: number }>()\n for (const file of files) {\n manifest[file.path] = { contentHash: file.contentHash, size: file.size }\n if (!byHash.has(file.contentHash)) {\n byHash.set(file.contentHash, { absolutePath: file.absolutePath, size: file.size })\n }\n }\n\n const session = await api.retryingJson<StartSessionResponse>('/hosting/v1/deploys/sessions', {\n method: 'POST',\n headers: { 'content-type': 'application/json', 'idempotency-key': idempotencyKey },\n body: JSON.stringify({\n appSlug: options.appSlug,\n branch: options.branch,\n sha: options.sha,\n committedAt: options.committedAt,\n manifest,\n }),\n })\n\n const sizes = new Map([...byHash].map(([hash, file]) => [hash, file.size]))\n const batches = planBatches(session.buckets, sizes)\n const batchPath = `/hosting/v1/deploys/sessions/${encodeURIComponent(session.sessionId)}/assets`\n await runPool(batches, options.concurrency ?? DEFAULT_CONCURRENCY, async (batch) => {\n const form = new FormData()\n for (const hash of batch) {\n const file = byHash.get(hash)!\n form.append(hash, new Blob([await readFile(file.absolutePath)]), hash)\n }\n await api.retrying(batchPath, { method: 'POST', body: form })\n })\n\n const result = await api.retryingJson<\n Omit<DeployHostingAppResult, 'totalFiles' | 'uploadedFiles'>\n >(`/hosting/v1/deploys/sessions/${encodeURIComponent(session.sessionId)}/finalize`, {\n method: 'POST',\n headers: { 'content-type': 'application/gzip' },\n body: server,\n })\n return {\n ...result,\n totalFiles: files.length,\n uploadedFiles: batches.reduce((total, batch) => total + batch.length, 0),\n }\n}\n\nasync function readServerArchive(serverDir: string): Promise<Uint8Array<ArrayBuffer>> {\n const paths = await listFiles(serverDir)\n if (paths.length === 0) {\n throw new DeployError(`No server modules found in ${serverDir}.`, { code: 'validation.failed' })\n }\n const entries = await Promise.all(\n paths.map(async (path) => ({\n path: `server/${path}`,\n bytes: new Uint8Array(await readFile(join(serverDir, path))),\n })),\n )\n return gzipTar(entries)\n}\n\nfunction createApi(options: DeployHostingAppOptions) {\n const origin = (options.apiOrigin ?? DEFAULT_API_ORIGIN).replace(/\\/+$/, '')\n const fetchImpl = options.fetch ?? fetch\n const sleep = options.sleep ?? ((ms: number) => new Promise((r) => setTimeout(r, ms)))\n const auth: Record<string, string> = { authorization: `Bearer ${options.deployKey}` }\n if (options.projectId) auth['x-project-id'] = options.projectId\n\n async function send(path: string, init: RequestInit): Promise<Response> {\n let response: Response\n try {\n response = await fetchImpl(`${origin}${path}`, {\n ...init,\n headers: { ...auth, ...(init.headers as Record<string, string> | undefined) },\n })\n } catch (cause) {\n throw new DeployError(`Could not reach ${origin}.`, { cause })\n }\n if (!response.ok) throw await problem(response)\n return response\n }\n\n return {\n /** Retries network failures, 429, and 5xx for retry-safe deploy operations. */\n async retrying(path: string, init: RequestInit): Promise<Response> {\n for (let attempt = 1; ; attempt++) {\n try {\n return await send(path, init)\n } catch (error) {\n const status = error instanceof DeployError ? error.status : null\n const transient = status === null || status === 429 || status >= 500\n if (!transient || attempt >= REQUEST_ATTEMPTS) throw error\n await sleep(500 * 3 ** (attempt - 1))\n }\n }\n },\n async retryingJson<T>(path: string, init: RequestInit): Promise<T> {\n return (await (await this.retrying(path, init)).json()) as T\n },\n }\n}\n\nasync function problem(response: Response): Promise<DeployError> {\n let body: { detail?: unknown; title?: unknown; code?: unknown } = {}\n try {\n body = (await response.json()) as typeof body\n } catch {\n // * Not every failure (a proxy, a gateway timeout) returns problem JSON.\n }\n const detail = typeof body.detail === 'string' ? body.detail : null\n const title = typeof body.title === 'string' ? body.title : null\n return new DeployError(detail ?? title ?? `Deploy request failed with ${response.status}.`, {\n status: response.status,\n code: typeof body.code === 'string' ? body.code : undefined,\n })\n}\n\nasync function runPool<T>(\n items: T[],\n concurrency: number,\n task: (item: T) => Promise<void>,\n): Promise<void> {\n let next = 0\n let failure: { error: unknown } | null = null\n const workers = Array.from(\n { length: Math.max(1, Math.min(concurrency, items.length)) },\n async () => {\n while (!failure && next < items.length) {\n try {\n await task(items[next++]!)\n } catch (error) {\n failure ??= { error }\n }\n }\n },\n )\n // * Wait for in-flight batches so nothing uploads after the deploy rejects.\n await Promise.all(workers)\n if (failure) throw (failure as { error: unknown }).error\n}\n"],"mappings":"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"}
|