clone

push a directory of html from the terminal and get a four-character url back. a deno cli on cloudflare

feat/site-feedback
nzip / cli / lib / publish.ts
publish.ts 2.7K
1// Transport-independent upload/commit operation. No printing, prompts, or local state.2import type { CommitRequest, CommitResponse } from "@nzip/shared";3import type { ApiClient } from "./api.ts";4import type { Bundle } from "./bundle.ts";56export interface PublishProgress {7 progress: number;8 total: number;9 message: string;10}1112export type PublishResult = CommitResponse & {13 files: number;14 newBlobs: number;15 dedupedBlobs: number;16 warnings: string[];17};1819export async function publishBundle(20 api: Pick<ApiClient, "prepare" | "uploadBlob" | "commit">,21 bundle: Bundle,22 policy: Omit<CommitRequest, "manifest">,23 options: {24 signal?: AbortSignal;25 onProgress?: (progress: PublishProgress) => void | Promise<void>;26 } = {},27): Promise<PublishResult> {28 options.signal?.throwIfAborted();29 const prep = await api.prepare(bundle.manifest);30 const total = prep.missing.length + 1; // commit is the final step31 const report = async (progress: number, message: string) => {32 // Progress is advisory; a disconnected observer must not turn an accepted commit into failure.33 try {34 await options.onProgress?.({ progress, total, message });35 } catch { /* best effort */ }36 };37 await report(0, `Prepared ${prep.missing.length} new blobs`);38 let done = 0;39 let failed = false;40 const queue = [...prep.missing];41 const workers = Array.from({ length: Math.min(6, queue.length) }, async () => {42 try {43 while (!failed && queue.length) {44 options.signal?.throwIfAborted();45 const hash = queue.shift()!;46 const bytes = bundle.blobs.get(hash);47 if (!bytes) throw new Error(`internal: missing blob bytes for ${hash}`);48 await api.uploadBlob(hash, bytes);49 await report(++done, `Uploaded ${done}/${prep.missing.length} blobs`);50 }51 } catch (error) {52 failed = true;53 throw error;54 }55 });56 // Drain in-flight uploads even when one fails; never commit a partial upload.57 const uploaded = await Promise.allSettled(workers);58 for (const result of uploaded) {59 if (result.status === "rejected") throw result.reason;60 }61 options.signal?.throwIfAborted();62 const result = await api.commit({ manifest: bundle.manifest, ...policy });63 if (policy.feedback !== undefined && result.feedback !== policy.feedback) {64 throw new Error(65 `published ${result.address}, but the server ignored feedback=${policy.feedback}; ` +66 "upgrade the nzip Worker to 0.12.0 or later",67 );68 }69 await report(total, "Published");70 return {71 ...result,72 files: Object.keys(bundle.manifest.files).length,73 newBlobs: prep.missing.length,74 dedupedBlobs: bundle.blobs.size - prep.missing.length,75 warnings: bundle.warnings,76 };77}