This commit is contained in:
+22
-2
@@ -1,5 +1,5 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { lstat, mkdir, mkdtemp, readFile, realpath, rm } from "node:fs/promises";
|
||||
import { cp, lstat, mkdir, mkdtemp, readFile, realpath, rm } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import type Database from "better-sqlite3";
|
||||
@@ -10,6 +10,7 @@ import { writeAudit } from "../audit.js";
|
||||
import {
|
||||
atomicSwitchDirectory,
|
||||
atomicSwitchRelease,
|
||||
applicationUpdateRuntimeHash,
|
||||
compareSemver,
|
||||
createSafeArchive,
|
||||
detectPlatform,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
isNewerVersion,
|
||||
normalizeReleasePermissions,
|
||||
parseSemver,
|
||||
runtimeHashFromLockfile,
|
||||
selectReleaseAsset,
|
||||
sanitizeAssetName,
|
||||
validateHttpsUrl,
|
||||
@@ -212,9 +214,16 @@ async function resolveRelease(options: UpdateRunOptions, platform: ReturnType<ty
|
||||
if (options.metadataUrl) {
|
||||
const metadataUrl = validateHttpsUrl(options.metadataUrl, options);
|
||||
const release = await fetchReleaseMetadata(metadataUrl, options);
|
||||
let runtimeHash: string | undefined;
|
||||
try {
|
||||
runtimeHash = runtimeHashFromLockfile(await readFile(path.join(options.currentDir, "pnpm-lock.yaml")));
|
||||
} catch {
|
||||
// Fall back to the full archive when the current installation predates
|
||||
// runtime fingerprints or is missing deployment provenance.
|
||||
}
|
||||
let asset = options.assetUrl && !options.requireSignature
|
||||
? { name: sanitizeAssetName(options.assetName ?? path.basename(new URL(options.assetUrl).pathname)), url: validateHttpsUrl(options.assetUrl, { ...options, baseUrl: metadataUrl }).toString(), ...(options.expectedSha256 ? { sha256: options.expectedSha256 } : {}) }
|
||||
: selectReleaseAsset(release, platform);
|
||||
: selectReleaseAsset(release, platform, runtimeHash);
|
||||
if (!asset) throw new Error("没有匹配当前平台的更新文件");
|
||||
const integrity = await attachSidecarHash(release, asset, {
|
||||
allowedHosts: options.allowedHosts ?? [],
|
||||
@@ -314,6 +323,17 @@ export async function runUpdate(options: UpdateRunOptions): Promise<UpdateRunRes
|
||||
if (!archivePath.endsWith(".tar.gz") && !archivePath.endsWith(".tgz") && !archivePath.endsWith(".tar") && !archivePath.endsWith(".zip")) throw new Error("更新文件格式仅支持 tar.gz、tar 或 zip");
|
||||
const stagedDir = path.join(workspace, "payload");
|
||||
await extractSafeArchive(archivePath, stagedDir, options.maxBytes === undefined ? {} : { maxBytes: options.maxBytes });
|
||||
if (applicationUpdateRuntimeHash(resolved.asset.name)) {
|
||||
const currentRelease = await realpath(options.currentDir).catch(() => { throw new Error("当前安装目录无效"); });
|
||||
const currentInfo = await lstat(currentRelease).catch(() => null);
|
||||
if (!currentInfo?.isDirectory() || currentInfo.isSymbolicLink()) throw new Error("当前安装目录无效");
|
||||
for (const entry of ["node_modules", "runtime", "pnpm-lock.yaml"] as const) {
|
||||
const source = path.join(currentRelease, entry);
|
||||
const sourceInfo = await lstat(source).catch(() => null);
|
||||
if (!sourceInfo || sourceInfo.isSymbolicLink()) throw new Error("当前运行时不完整,无法应用轻量更新");
|
||||
await cp(source, path.join(stagedDir, entry), { recursive: sourceInfo.isDirectory(), errorOnExist: true, force: false });
|
||||
}
|
||||
}
|
||||
await normalizeReleasePermissions(stagedDir);
|
||||
const payloadInfo = await lstat(path.join(stagedDir, "dist")).catch(() => null);
|
||||
if (!payloadInfo?.isDirectory() || payloadInfo.isSymbolicLink()) throw new Error("发布包缺少 dist 目录");
|
||||
|
||||
Reference in New Issue
Block a user