UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

245 lines (243 loc) • 8.38 kB
import { composeWorkspaceVersionManifest, readBuildReport, resolveSlotVersion } from "./version-manifest.mjs"; import { buildSourceArchive, makeSourceArchiveTempDir } from "./source-archive.mjs"; import { collectDistFiles, uploadBundle, uploadWorkspaceDist } from "./storage.mjs"; import { diffWorkspaceFiles, localSriMap, mergedFileIntegrity } from "./diff.mjs"; import { readFileSync, rmSync } from "node:fs"; import { basename, dirname, join } from "node:path"; //#region src/services/deploy/upload-leg.ts const realStorageLegDeps = { collectDistFiles, uploadWorkspaceDist, uploadBundle, resolveSlotVersion, readBuildReport, composeWorkspaceVersionManifest, buildSourceArchive }; function formatBundleMb(files) { return `${(files.reduce((sum, file) => sum + file.bytes.byteLength, 0) / 1024 / 1024).toFixed(1)} MB`; } /** One workspace's upload with skip-unchanged diffing; any failure to resolve * the previous manifest's file map degrades to a full upload. */ async function diffUploadWorkspace(deps, input) { const localSri = localSriMap(input.files); let uploadFiles = input.files; let prevFiles; if (input.previousPin) try { const resolved = await deps.resolveSlotVersion({ base: input.base, pin: input.previousPin }); if (resolved.files) { const diff = diffWorkspaceFiles(input.files, localSri, resolved.files); prevFiles = resolved.files; uploadFiles = diff.upload; if (diff.skipped > 0) input.notes.push(`${diff.skipped} unchanged file(s) — skipping (manifest diff)`); } } catch { input.notes.push("previous manifest unusable — uploading everything"); } input.notes.push(`uploading ${uploadFiles.length} of ${input.files.length} files (${formatBundleMb(uploadFiles)}) → ${input.base}`); const result = uploadFiles.length > 0 ? await deps.uploadWorkspaceDist({ origin: input.origin, apiKey: input.apiKey, account: input.account, gateway: input.gateway, workspace: input.workspace, files: uploadFiles }) : { stored: 0, totalBytes: 0, integrity: {} }; return { result, fileIntegrity: mergedFileIntegrity(input.files, result.integrity, prevFiles), uploaded: result.stored, skipped: input.files.length - uploadFiles.length, total: input.files.length }; } /** Compose + upload the workspace's version manifest — server-computed SRI of * the just-finished upload, uploaded additively at `versions/<version>.json`. * Returns undefined when the dist predates hashed entry names. The manifest's * `shared` record rides back for the publish train's truth gate (#546). */ async function pinWorkspaceVersionManifest(deps, input) { if (!input.report) return void 0; const versionManifest = deps.composeWorkspaceVersionManifest({ report: input.report, ssrReport: input.ssrReport, integrityMap: input.integrityMap, ...input.files ? { files: input.files } : {}, ...input.source ? { source: input.source } : {}, ...input.gitSha ? { gitSha: input.gitSha } : {} }); if (!versionManifest) return void 0; const manifestFile = `versions/${versionManifest.version}.json`; const manifestUpload = await deps.uploadBundle({ origin: input.origin, apiKey: input.apiKey, account: input.account, gateway: input.gateway, workspace: input.workspace, files: [{ path: manifestFile, bytes: new TextEncoder().encode(`${JSON.stringify(versionManifest, null, 2)}\n`) }] }); const manifestIntegrity = manifestUpload.integrity[manifestFile]; if (!manifestIntegrity) return void 0; input.notes.push(`version manifest ${manifestFile} (SRI pinned)`); return { pin: { file: manifestFile, integrity: manifestIntegrity, storage: manifestUpload.storage }, ...versionManifest.shared ? { shared: versionManifest.shared } : {} }; } /** Source-archive leg (publish.sourcePublish, #406): tar the workspace * subtree, dedupe against the previous pin by archive SRI, otherwise upload * and take the server-computed SRI. The temp dir is cleaned on every path. */ async function resolveSourceReference(deps, input) { const notes = input.notes; const outDir = makeSourceArchiveTempDir(); try { const archive = await deps.buildSourceArchive({ repoRoot: input.repoRoot, workspace: input.workspaceDir, outDir }); if (input.previousPin) try { const resolved = await deps.resolveSlotVersion({ base: input.base, pin: input.previousPin }); if (resolved.source?.integrity === archive.integrity) { notes.push(`source unchanged — reusing ${archive.file}`); return { source: { file: resolved.source.file, integrity: resolved.source.integrity } }; } } catch {} const upload = await deps.uploadBundle({ origin: input.origin, apiKey: input.apiKey, account: input.account, gateway: input.gateway, workspace: input.workspace, files: [{ path: archive.file, bytes: new Uint8Array(readFileSync(archive.path)) }] }); if (upload.storage === "memory") return { memory: true }; const integrity = upload.integrity[archive.file]; if (!integrity) throw new Error(`[publish] source archive upload for ${input.workspace}: missing server SRI for ${archive.file}`); notes.push(`source archive ${archive.file}`); return { source: { file: archive.file, integrity } }; } finally { rmSync(outDir, { recursive: true, force: true }); } } async function uploadWorkspaceBundle(input) { const deps = input.deps ?? realStorageLegDeps; const base = `${input.urlOrigin}/bundles/${input.account}/${input.gateway}/${input.workspaceKey}/`; const startedAt = Date.now(); let phase = "upload"; try { const distFiles = await deps.collectDistFiles(input.distDir); const notes = []; const outcome = await diffUploadWorkspace(deps, { origin: input.storageOrigin, apiKey: input.storageApiKey, account: input.account, gateway: input.gateway, workspace: input.workspaceKey, files: distFiles, base, previousPin: input.previousPin, notes }); const report = deps.readBuildReport(input.distDir); const ssrReport = deps.readBuildReport(join(input.distDir, "ssr")); const entryIntegrity = report ? outcome.fileIntegrity[report.entry] : void 0; const ssrEntryIntegrity = ssrReport?.entry ? outcome.fileIntegrity[`ssr/${ssrReport.entry}`] : void 0; let sourceRef; if (input.source) { if (outcome.result.storage === "memory") return { kind: "memory" }; phase = "source"; const sourceOutcome = await resolveSourceReference(deps, { origin: input.storageOrigin, apiKey: input.storageApiKey, account: input.account, gateway: input.gateway, workspace: input.workspaceKey, base, previousPin: input.previousPin, repoRoot: input.source.repoRoot, workspaceDir: input.source.workspaceDir, notes }); if ("memory" in sourceOutcome) return { kind: "memory" }; sourceRef = sourceOutcome.source; phase = "upload"; } const pinned = await pinWorkspaceVersionManifest(deps, { origin: input.storageOrigin, apiKey: input.storageApiKey, account: input.account, gateway: input.gateway, workspace: input.workspaceKey, report, ssrReport, integrityMap: outcome.fileIntegrity, files: outcome.fileIntegrity, ...sourceRef ? { source: sourceRef } : {}, ...input.gitSha ? { gitSha: input.gitSha } : {}, notes }); if (!pinned) return { kind: "missing-manifest" }; const pin = pinned.pin; if (outcome.result.storage === "memory" || pin.storage === "memory") return { kind: "memory" }; return { kind: "ok", pin, storage: outcome.result.storage, entryIntegrity, ssrEntryIntegrity, ...pinned.shared ? { shared: pinned.shared } : {}, uploaded: outcome.uploaded, skipped: outcome.skipped, durationMs: Date.now() - startedAt, detail: { base, manifestFile: pin.file, notes } }; } catch (error) { return { kind: "error", message: error instanceof Error ? error.message : phase === "source" ? `[publish] source archive for ${input.workspaceKey} failed` : `[publish] bundle upload for ${input.workspaceKey} failed` }; } } /** Directory names the source leg needs (main workspaces only). */ function sourceLegDirs(workspacePath) { return { repoRoot: dirname(workspacePath), workspaceDir: basename(workspacePath) }; } //#endregion export { realStorageLegDeps, sourceLegDirs, uploadWorkspaceBundle }; //# sourceMappingURL=upload-leg.mjs.map