UNPKG

everything-dev

Version:

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

1 lines • 7.88 kB
{"version":3,"file":"deploy-diff.cjs","names":["createHash","join","existsSync","readFileSync","dirname","collectSlotPinRefs"],"sources":["../src/deploy-diff.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { collectSlotPinRefs } from \"./rollback\";\nimport type { DistFile } from \"./storage-upload\";\nimport type { BosConfigInput } from \"./types\";\n\n/**\n * Differential bundle uploads (skip-unchanged): the previous deploy's version\n * manifest carries the dist's full per-file SRI map (`files`), so the next\n * deploy hashes its local dist, diffs against that map, and re-uploads only\n * new or changed files. Content-hashed dist filenames make an unchanged chunk\n * byte-identical, so a typical redeploy ships a handful of files instead of\n * the whole tree.\n *\n * Correctness: a skip requires (path, sha384) to match the previous\n * **server-computed** SRI map — client hashes decide only what to skip; the\n * published manifest keeps carrying the server-computed values. Any failure\n * to resolve the previous map (no pointer, fetch failed, SRI mismatch, manifest\n * predates `files`) degrades to a full upload.\n */\n\nexport interface PreviousPin {\n manifest: string;\n integrity: string;\n}\n\nexport interface DeployStateFile {\n version: 1;\n /** key: `<account>/<gateway>`, then workspace key → pin */\n apps: Record<string, Record<string, PreviousPin>>;\n}\n\nexport const BUNDLE_BASE_PATTERN = /\\/bundles\\/([^/]+)\\/([^/]+)\\/([^/]+)\\/$/;\n\nexport function workspaceKeyOfBase(base: string): string | null {\n const match = BUNDLE_BASE_PATTERN.exec(`${base.replace(/\\/$/, \"\")}/`);\n return match ? match[3]! : null;\n}\n\n/** The sha384 SRI the storage route would compute for these bytes. */\nexport function localObjectIntegrity(bytes: Uint8Array): string {\n return `sha384-${createHash(\"sha384\").update(bytes).digest(\"base64\")}`;\n}\n\nexport function localSriMap(files: DistFile[]): Record<string, string> {\n return Object.fromEntries(files.map((file) => [file.path, localObjectIntegrity(file.bytes)]));\n}\n\nconst deployStatePath = (configDir: string) => join(configDir, \".bos\", \"deploy-state.json\");\n\nexport function readDeployState(configDir: string): DeployStateFile {\n const path = deployStatePath(configDir);\n if (!existsSync(path)) return { version: 1, apps: {} };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as Partial<DeployStateFile>;\n if (parsed.version !== 1 || typeof parsed.apps !== \"object\" || parsed.apps === null) {\n return { version: 1, apps: {} };\n }\n return { version: 1, apps: parsed.apps as DeployStateFile[\"apps\"] };\n } catch {\n return { version: 1, apps: {} };\n }\n}\n\nexport function writeDeployState(configDir: string, state: DeployStateFile): void {\n const path = deployStatePath(configDir);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(state, null, 2)}\\n`);\n}\n\nexport function upsertDeployStatePins(\n configDir: string,\n account: string,\n gateway: string,\n pins: Record<string, PreviousPin>,\n): void {\n const state = readDeployState(configDir);\n const appKey = `${account}/${gateway}`;\n state.apps[appKey] = { ...state.apps[appKey], ...pins };\n writeDeployState(configDir, state);\n}\n\n/** Previous pins by workspace key, from the local deploy-state pointer. */\nexport function pinsFromDeployState(\n configDir: string,\n account: string,\n gateway: string,\n): Record<string, PreviousPin> {\n return readDeployState(configDir).apps[`${account}/${gateway}`] ?? {};\n}\n\n/** Previous pins by workspace key, from the published config's slot pins. */\nexport function pinsFromPublishedConfig(\n config: BosConfigInput,\n account: string,\n gateway: string,\n): Record<string, PreviousPin> {\n const pins: Record<string, PreviousPin> = {};\n for (const ref of collectSlotPinRefs(config)) {\n const workspace = workspaceKeyOfBase(ref.base);\n const nsMatch = BUNDLE_BASE_PATTERN.exec(`${ref.base.replace(/\\/$/, \"\")}/`);\n if (!workspace || !nsMatch || nsMatch[1] !== account || nsMatch[2] !== gateway) continue;\n pins[workspace] = ref.pin;\n }\n return pins;\n}\n\nexport interface WorkspaceDiff {\n /** files that must be uploaded (new or changed) */\n upload: DistFile[];\n /** number of files skipped because the previous manifest holds identical bytes */\n skipped: number;\n}\n\nexport function diffWorkspaceFiles(\n files: DistFile[],\n localSri: Record<string, string>,\n prevFiles: Record<string, string>,\n): WorkspaceDiff {\n const upload: DistFile[] = [];\n let skipped = 0;\n for (const file of files) {\n if (prevFiles[file.path] !== undefined && prevFiles[file.path] === localSri[file.path]) {\n skipped += 1;\n continue;\n }\n upload.push(file);\n }\n return { upload, skipped };\n}\n\n/**\n * The manifest's merged per-file map for this deploy: uploaded files carry the\n * server-computed SRI from the upload response; skipped files carry the\n * previous manifest's server-computed SRI (byte-identical to the local hash by\n * the skip decision). Files that left the dist carry nothing.\n */\nexport function mergedFileIntegrity(\n files: DistFile[],\n uploadedIntegrity: Record<string, string>,\n prevFiles: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const file of files) {\n const uploaded = uploadedIntegrity[file.path];\n if (uploaded) {\n merged[file.path] = uploaded;\n continue;\n }\n const carried = prevFiles?.[file.path];\n if (carried) merged[file.path] = carried;\n }\n return merged;\n}\n"],"mappings":"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"}