Support delta package updates
This commit is contained in:
@@ -99,6 +99,14 @@
|
|||||||
<label for="package">安装包</label>
|
<label for="package">安装包</label>
|
||||||
<input id="package" name="package" type="file" />
|
<input id="package" name="package" type="file" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="delta-from-version">增量包来源版本</label>
|
||||||
|
<input id="delta-from-version" name="deltaFromVersion" maxlength="40" placeholder="1.1.7" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="delta-package">增量包</label>
|
||||||
|
<input id="delta-package" name="deltaPackage" type="file" accept=".zip,.smdelta,application/zip" />
|
||||||
|
</div>
|
||||||
<div class="field full">
|
<div class="field full">
|
||||||
<label for="version-manifest">版本清单 version.json</label>
|
<label for="version-manifest">版本清单 version.json</label>
|
||||||
<input id="version-manifest" name="versionManifest" type="file" accept="application/json,.json" />
|
<input id="version-manifest" name="versionManifest" type="file" accept="application/json,.json" />
|
||||||
|
|||||||
@@ -152,6 +152,8 @@ function fillForm(item) {
|
|||||||
els.form.elements.featured.checked = Boolean(item.featured);
|
els.form.elements.featured.checked = Boolean(item.featured);
|
||||||
els.form.elements.icon.value = "";
|
els.form.elements.icon.value = "";
|
||||||
els.form.elements.package.value = "";
|
els.form.elements.package.value = "";
|
||||||
|
els.form.elements.deltaFromVersion.value = "";
|
||||||
|
els.form.elements.deltaPackage.value = "";
|
||||||
els.form.elements.versionManifest.value = "";
|
els.form.elements.versionManifest.value = "";
|
||||||
els.form.elements.versionManifestContent.value = item.versionManifest ? JSON.stringify(item.versionManifest, null, 2) : "";
|
els.form.elements.versionManifestContent.value = item.versionManifest ? JSON.stringify(item.versionManifest, null, 2) : "";
|
||||||
els.formTitle.textContent = `更新 ${item.name}`;
|
els.formTitle.textContent = `更新 ${item.name}`;
|
||||||
|
|||||||
+190
-7
@@ -308,6 +308,7 @@ function publicItem(item, includePrivate = false) {
|
|||||||
delete copy.storagePath;
|
delete copy.storagePath;
|
||||||
delete copy.iconStoragePath;
|
delete copy.iconStoragePath;
|
||||||
delete copy.versionManifestStoragePath;
|
delete copy.versionManifestStoragePath;
|
||||||
|
delete copy.deltaPackages;
|
||||||
delete copy.externalDownloadUrl;
|
delete copy.externalDownloadUrl;
|
||||||
delete copy.versionManifest;
|
delete copy.versionManifest;
|
||||||
copy.hasVersionManifest = Boolean(item.versionManifest);
|
copy.hasVersionManifest = Boolean(item.versionManifest);
|
||||||
@@ -364,6 +365,70 @@ async function makeFilePatch(files = {}) {
|
|||||||
return patch;
|
return patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function makeDeltaPackage(file, fromVersion, toVersion) {
|
||||||
|
const sourceVersion = normalizeVersion(fromVersion);
|
||||||
|
const targetVersion = normalizeVersion(toVersion);
|
||||||
|
if (!file) return null;
|
||||||
|
if (!sourceVersion) {
|
||||||
|
const error = new Error("Delta package source version is required");
|
||||||
|
error.status = 400;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
fromVersion: sourceVersion,
|
||||||
|
toVersion: targetVersion,
|
||||||
|
storagePath: file.path,
|
||||||
|
originalFileName: file.originalname,
|
||||||
|
fileName: file.filename,
|
||||||
|
sizeBytes: file.size,
|
||||||
|
size: formatSize(file.size),
|
||||||
|
sha256: await hashFile(file.path),
|
||||||
|
uploadedAt: new Date().toISOString(),
|
||||||
|
type: "file-zip",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getManifestDeltaMap(manifest = {}) {
|
||||||
|
const map = manifest.delta_updates || manifest.deltaUpdates || manifest.delta || {};
|
||||||
|
return map && typeof map === "object" && !Array.isArray(map) ? map : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferDeltaFromVersion(manifest = {}) {
|
||||||
|
const keys = Object.keys(getManifestDeltaMap(manifest));
|
||||||
|
return keys.length === 1 ? keys[0] : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeDeltaPackageIntoManifest(sourceManifest = {}, deltaPackage, itemId) {
|
||||||
|
sourceManifest = sourceManifest && typeof sourceManifest === "object" ? sourceManifest : {};
|
||||||
|
if (!deltaPackage) return sourceManifest;
|
||||||
|
const fromVersion = deltaPackage.fromVersion;
|
||||||
|
const deltaUrl = `/api/software/${encodeURIComponent(itemId)}/delta-download?from=${encodeURIComponent(fromVersion)}`;
|
||||||
|
const next = {
|
||||||
|
...sourceManifest,
|
||||||
|
delta_updates: {
|
||||||
|
...getManifestDeltaMap(sourceManifest),
|
||||||
|
[fromVersion]: {
|
||||||
|
...(getManifestDeltaMap(sourceManifest)[fromVersion] || {}),
|
||||||
|
type: "file-zip",
|
||||||
|
format: "server-manager-file-delta-v1",
|
||||||
|
fromVersion,
|
||||||
|
from_version: fromVersion,
|
||||||
|
targetVersion: deltaPackage.toVersion || sourceManifest.version || "",
|
||||||
|
target_version: deltaPackage.toVersion || sourceManifest.version || "",
|
||||||
|
package_url: deltaUrl,
|
||||||
|
packageUrl: deltaUrl,
|
||||||
|
patch_url: deltaUrl,
|
||||||
|
patchUrl: deltaUrl,
|
||||||
|
sha256: deltaPackage.sha256,
|
||||||
|
sizeBytes: deltaPackage.sizeBytes,
|
||||||
|
size: deltaPackage.size,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
next.deltaUpdates = next.delta_updates;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
function getBodyPatch(body, existing = {}) {
|
function getBodyPatch(body, existing = {}) {
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
return {
|
return {
|
||||||
@@ -430,10 +495,57 @@ function getUpdateDownloadUrl(item) {
|
|||||||
return resolveDownloadUrl(item) ? `/api/software/${encodeURIComponent(item.id)}/update-download` : "";
|
return resolveDownloadUrl(item) ? `/api/software/${encodeURIComponent(item.id)}/update-download` : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDeltaDownloadUrl(item, fromVersion) {
|
||||||
|
const deltaPackage = item.deltaPackages?.[normalizeVersion(fromVersion)];
|
||||||
|
return deltaPackage ? `/api/software/${encodeURIComponent(item.id)}/delta-download?from=${encodeURIComponent(normalizeVersion(fromVersion))}` : "";
|
||||||
|
}
|
||||||
|
|
||||||
function getReleaseNotes(item, manifest) {
|
function getReleaseNotes(item, manifest) {
|
||||||
return manifest.release_notes || manifest.releaseNotes || manifest.changelog || item.changelog || "";
|
return manifest.release_notes || manifest.releaseNotes || manifest.changelog || item.changelog || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildPublicDeltaUpdates(item, req, sourceManifest = {}) {
|
||||||
|
const manifestDeltaMap = getManifestDeltaMap(sourceManifest);
|
||||||
|
const result = {};
|
||||||
|
const allFromVersions = new Set([
|
||||||
|
...Object.keys(manifestDeltaMap),
|
||||||
|
...Object.keys(item.deltaPackages || {}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (const fromVersion of allFromVersions) {
|
||||||
|
const normalizedFrom = normalizeVersion(fromVersion);
|
||||||
|
if (!normalizedFrom) continue;
|
||||||
|
const sourceEntry = manifestDeltaMap[fromVersion] || manifestDeltaMap[normalizedFrom] || {};
|
||||||
|
if (!sourceEntry || typeof sourceEntry !== "object" || Array.isArray(sourceEntry)) continue;
|
||||||
|
|
||||||
|
const deltaPackage = item.deltaPackages?.[normalizedFrom];
|
||||||
|
if (deltaPackage) {
|
||||||
|
const deltaUrl = absoluteUrl(req, getDeltaDownloadUrl(item, normalizedFrom));
|
||||||
|
result[normalizedFrom] = {
|
||||||
|
...sourceEntry,
|
||||||
|
type: sourceEntry.type || deltaPackage.type || "file-zip",
|
||||||
|
format: sourceEntry.format || "server-manager-file-delta-v1",
|
||||||
|
fromVersion: sourceEntry.fromVersion || sourceEntry.from_version || normalizedFrom,
|
||||||
|
from_version: sourceEntry.from_version || sourceEntry.fromVersion || normalizedFrom,
|
||||||
|
targetVersion: sourceEntry.targetVersion || sourceEntry.target_version || deltaPackage.toVersion || getLatestVersion(item),
|
||||||
|
target_version: sourceEntry.target_version || sourceEntry.targetVersion || deltaPackage.toVersion || getLatestVersion(item),
|
||||||
|
packageUrl: deltaUrl,
|
||||||
|
package_url: deltaUrl,
|
||||||
|
patchUrl: deltaUrl,
|
||||||
|
patch_url: deltaUrl,
|
||||||
|
sha256: sourceEntry.sha256 || deltaPackage.sha256,
|
||||||
|
sizeBytes: sourceEntry.sizeBytes ?? deltaPackage.sizeBytes,
|
||||||
|
size: sourceEntry.size || deltaPackage.size,
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result[normalizedFrom] = { ...sourceEntry };
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function createStoredVersionManifest(item, sourceManifest = {}) {
|
function createStoredVersionManifest(item, sourceManifest = {}) {
|
||||||
const versionManifestUrl = `/api/software/${encodeURIComponent(item.id)}/version.json`;
|
const versionManifestUrl = `/api/software/${encodeURIComponent(item.id)}/version.json`;
|
||||||
const downloadUrl = getProtectedDownloadUrl(item);
|
const downloadUrl = getProtectedDownloadUrl(item);
|
||||||
@@ -481,6 +593,7 @@ function buildVersionManifest(item, req) {
|
|||||||
const updateUrl = absoluteUrl(req, versionManifestUrl);
|
const updateUrl = absoluteUrl(req, versionManifestUrl);
|
||||||
const releaseNotes = getReleaseNotes(item, sourceManifest);
|
const releaseNotes = getReleaseNotes(item, sourceManifest);
|
||||||
const minimumVersion = sourceManifest.min_supported_version || sourceManifest.minSupportedVersion || sourceManifest.minimumVersion || sourceManifest.minVersion || "";
|
const minimumVersion = sourceManifest.min_supported_version || sourceManifest.minSupportedVersion || sourceManifest.minimumVersion || sourceManifest.minVersion || "";
|
||||||
|
const deltaUpdates = buildPublicDeltaUpdates(item, req, sourceManifest);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...sourceManifest,
|
...sourceManifest,
|
||||||
@@ -504,8 +617,8 @@ function buildVersionManifest(item, req) {
|
|||||||
force_update: Boolean(sourceManifest.forceUpdate || sourceManifest.force_update || sourceManifest.force),
|
force_update: Boolean(sourceManifest.forceUpdate || sourceManifest.force_update || sourceManifest.force),
|
||||||
minSupportedVersion: minimumVersion,
|
minSupportedVersion: minimumVersion,
|
||||||
min_supported_version: minimumVersion,
|
min_supported_version: minimumVersion,
|
||||||
deltaUpdates: sourceManifest.deltaUpdates || sourceManifest.delta_updates || {},
|
deltaUpdates,
|
||||||
delta_updates: sourceManifest.delta_updates || sourceManifest.deltaUpdates || {},
|
delta_updates: deltaUpdates,
|
||||||
checkUpdateUrl: absoluteUrl(req, `/api/software/${encodeURIComponent(item.id)}/check-update`),
|
checkUpdateUrl: absoluteUrl(req, `/api/software/${encodeURIComponent(item.id)}/check-update`),
|
||||||
versionManifestUrl: updateUrl,
|
versionManifestUrl: updateUrl,
|
||||||
};
|
};
|
||||||
@@ -576,6 +689,12 @@ function resolvePackagePath(item) {
|
|||||||
return publicPackagePath;
|
return publicPackagePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveDeltaPackagePath(item, fromVersion) {
|
||||||
|
const deltaPackage = item.deltaPackages?.[normalizeVersion(fromVersion)];
|
||||||
|
if (!deltaPackage?.storagePath) return "";
|
||||||
|
return path.resolve(deltaPackage.storagePath);
|
||||||
|
}
|
||||||
|
|
||||||
async function cleanupUploadedFiles(req) {
|
async function cleanupUploadedFiles(req) {
|
||||||
const uploadedFiles = Object.values(req.files || {}).flat();
|
const uploadedFiles = Object.values(req.files || {}).flat();
|
||||||
for (const file of uploadedFiles) {
|
for (const file of uploadedFiles) {
|
||||||
@@ -611,7 +730,7 @@ const upload = multer({
|
|||||||
storage,
|
storage,
|
||||||
limits: {
|
limits: {
|
||||||
fileSize: Number(process.env.MAX_UPLOAD_BYTES || 1024 * 1024 * 1024),
|
fileSize: Number(process.env.MAX_UPLOAD_BYTES || 1024 * 1024 * 1024),
|
||||||
files: 3,
|
files: 4,
|
||||||
},
|
},
|
||||||
fileFilter(req, file, callback) {
|
fileFilter(req, file, callback) {
|
||||||
if (file.fieldname === "icon" && !file.mimetype.startsWith("image/")) {
|
if (file.fieldname === "icon" && !file.mimetype.startsWith("image/")) {
|
||||||
@@ -833,7 +952,43 @@ async function sendSoftwareDownload(req, res, next) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendSoftwareDeltaDownload(req, res, next) {
|
||||||
|
try {
|
||||||
|
const fromVersion = normalizeVersion(req.query.from || req.query.version || req.query.currentVersion || "");
|
||||||
|
if (!fromVersion) {
|
||||||
|
res.status(400).json({ error: "Delta source version is required. Use ?from=1.0.0" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = await readStore();
|
||||||
|
const item = store.items.find((entry) => entry.id === req.params.id && entry.active !== false);
|
||||||
|
if (!item) {
|
||||||
|
res.status(404).json({ error: "Software not found" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deltaPackage = item.deltaPackages?.[fromVersion];
|
||||||
|
const deltaPath = resolveDeltaPackagePath(item, fromVersion);
|
||||||
|
if (!deltaPackage || !deltaPath) {
|
||||||
|
res.status(404).json({ error: "Delta package not found" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isPathInside(packageDir, deltaPath)) {
|
||||||
|
res.status(403).json({ error: "Invalid delta package path" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fsp.access(deltaPath);
|
||||||
|
item.deltaDownloadCount = (item.deltaDownloadCount || 0) + 1;
|
||||||
|
await writeStore(store);
|
||||||
|
res.download(deltaPath, deltaPackage.originalFileName || deltaPackage.fileName || path.basename(deltaPath));
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
app.get("/api/software/:id/update-download", sendSoftwareDownload);
|
app.get("/api/software/:id/update-download", sendSoftwareDownload);
|
||||||
|
app.get("/api/software/:id/delta-download", sendSoftwareDeltaDownload);
|
||||||
app.get("/api/software/:id/download", requirePlayer, sendSoftwareDownload);
|
app.get("/api/software/:id/download", requirePlayer, sendSoftwareDownload);
|
||||||
|
|
||||||
app.get("/api/admin/software", requirePlayer, requireAdmin, async (req, res, next) => {
|
app.get("/api/admin/software", requirePlayer, requireAdmin, async (req, res, next) => {
|
||||||
@@ -845,7 +1000,14 @@ app.get("/api/admin/software", requirePlayer, requireAdmin, async (req, res, nex
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/admin/software", requirePlayer, requireAdmin, upload.fields([{ name: "package", maxCount: 1 }, { name: "icon", maxCount: 1 }, { name: "versionManifest", maxCount: 1 }]), async (req, res, next) => {
|
const softwareUploadFields = [
|
||||||
|
{ name: "package", maxCount: 1 },
|
||||||
|
{ name: "deltaPackage", maxCount: 1 },
|
||||||
|
{ name: "icon", maxCount: 1 },
|
||||||
|
{ name: "versionManifest", maxCount: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
app.post("/api/admin/software", requirePlayer, requireAdmin, upload.fields(softwareUploadFields), async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const bodyPatch = getBodyPatch(req.body);
|
const bodyPatch = getBodyPatch(req.body);
|
||||||
if (!bodyPatch.name) {
|
if (!bodyPatch.name) {
|
||||||
@@ -862,10 +1024,15 @@ app.post("/api/admin/software", requirePlayer, requireAdmin, upload.fields([{ na
|
|||||||
|
|
||||||
const filePatch = await makeFilePatch(req.files);
|
const filePatch = await makeFilePatch(req.files);
|
||||||
const contentManifest = parseVersionManifestContent(req.body.versionManifestContent);
|
const contentManifest = parseVersionManifestContent(req.body.versionManifestContent);
|
||||||
const explicitManifest = filePatch.versionManifest || contentManifest;
|
let explicitManifest = filePatch.versionManifest || contentManifest;
|
||||||
if (explicitManifest?.version) {
|
if (explicitManifest?.version) {
|
||||||
bodyPatch.version = String(explicitManifest.version).trim();
|
bodyPatch.version = String(explicitManifest.version).trim();
|
||||||
}
|
}
|
||||||
|
const deltaPackage = await makeDeltaPackage(
|
||||||
|
req.files?.deltaPackage?.[0],
|
||||||
|
req.body.deltaFromVersion || inferDeltaFromVersion(explicitManifest || {}),
|
||||||
|
explicitManifest?.version || bodyPatch.version,
|
||||||
|
);
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const externalDownloadUrl = bodyPatch.externalDownloadUrl;
|
const externalDownloadUrl = bodyPatch.externalDownloadUrl;
|
||||||
const item = {
|
const item = {
|
||||||
@@ -874,10 +1041,14 @@ app.post("/api/admin/software", requirePlayer, requireAdmin, upload.fields([{ na
|
|||||||
downloadCount: 0,
|
downloadCount: 0,
|
||||||
...bodyPatch,
|
...bodyPatch,
|
||||||
...filePatch,
|
...filePatch,
|
||||||
|
deltaPackages: deltaPackage ? { [deltaPackage.fromVersion]: deltaPackage } : {},
|
||||||
downloadUrl: filePatch.downloadUrl || externalDownloadUrl,
|
downloadUrl: filePatch.downloadUrl || externalDownloadUrl,
|
||||||
externalDownloadUrl: filePatch.downloadUrl ? "" : externalDownloadUrl,
|
externalDownloadUrl: filePatch.downloadUrl ? "" : externalDownloadUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (deltaPackage) {
|
||||||
|
explicitManifest = mergeDeltaPackageIntoManifest(explicitManifest || {}, deltaPackage, item.id);
|
||||||
|
}
|
||||||
if (explicitManifest) {
|
if (explicitManifest) {
|
||||||
applyVersionManifest(item, explicitManifest, filePatch.versionManifest ? "file" : "manual");
|
applyVersionManifest(item, explicitManifest, filePatch.versionManifest ? "file" : "manual");
|
||||||
} else if (resolveDownloadUrl(item)) {
|
} else if (resolveDownloadUrl(item)) {
|
||||||
@@ -894,7 +1065,7 @@ app.post("/api/admin/software", requirePlayer, requireAdmin, upload.fields([{ na
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put("/api/admin/software/:id", requirePlayer, requireAdmin, upload.fields([{ name: "package", maxCount: 1 }, { name: "icon", maxCount: 1 }, { name: "versionManifest", maxCount: 1 }]), async (req, res, next) => {
|
app.put("/api/admin/software/:id", requirePlayer, requireAdmin, upload.fields(softwareUploadFields), async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const store = await readStore();
|
const store = await readStore();
|
||||||
const index = store.items.findIndex((item) => item.id === req.params.id);
|
const index = store.items.findIndex((item) => item.id === req.params.id);
|
||||||
@@ -912,18 +1083,30 @@ app.put("/api/admin/software/:id", requirePlayer, requireAdmin, upload.fields([{
|
|||||||
|
|
||||||
const filePatch = await makeFilePatch(req.files);
|
const filePatch = await makeFilePatch(req.files);
|
||||||
const contentManifest = parseVersionManifestContent(req.body.versionManifestContent);
|
const contentManifest = parseVersionManifestContent(req.body.versionManifestContent);
|
||||||
const explicitManifest = filePatch.versionManifest || contentManifest;
|
let explicitManifest = filePatch.versionManifest || contentManifest;
|
||||||
if (explicitManifest?.version) {
|
if (explicitManifest?.version) {
|
||||||
bodyPatch.version = String(explicitManifest.version).trim();
|
bodyPatch.version = String(explicitManifest.version).trim();
|
||||||
}
|
}
|
||||||
|
const deltaPackage = await makeDeltaPackage(
|
||||||
|
req.files?.deltaPackage?.[0],
|
||||||
|
req.body.deltaFromVersion || inferDeltaFromVersion(explicitManifest || {}),
|
||||||
|
explicitManifest?.version || bodyPatch.version || existing.version,
|
||||||
|
);
|
||||||
const downloadUrl = filePatch.downloadUrl || bodyPatch.externalDownloadUrl || existing.downloadUrl || "";
|
const downloadUrl = filePatch.downloadUrl || bodyPatch.externalDownloadUrl || existing.downloadUrl || "";
|
||||||
const item = {
|
const item = {
|
||||||
...existing,
|
...existing,
|
||||||
...bodyPatch,
|
...bodyPatch,
|
||||||
...filePatch,
|
...filePatch,
|
||||||
|
deltaPackages: {
|
||||||
|
...(existing.deltaPackages || {}),
|
||||||
|
...(deltaPackage ? { [deltaPackage.fromVersion]: deltaPackage } : {}),
|
||||||
|
},
|
||||||
downloadUrl,
|
downloadUrl,
|
||||||
externalDownloadUrl: filePatch.downloadUrl ? "" : bodyPatch.externalDownloadUrl,
|
externalDownloadUrl: filePatch.downloadUrl ? "" : bodyPatch.externalDownloadUrl,
|
||||||
};
|
};
|
||||||
|
if (deltaPackage) {
|
||||||
|
explicitManifest = mergeDeltaPackageIntoManifest(explicitManifest || {}, deltaPackage, item.id);
|
||||||
|
}
|
||||||
if (explicitManifest) {
|
if (explicitManifest) {
|
||||||
applyVersionManifest(item, explicitManifest, filePatch.versionManifest ? "file" : "manual");
|
applyVersionManifest(item, explicitManifest, filePatch.versionManifest ? "file" : "manual");
|
||||||
} else if (filePatch.downloadUrl || bodyPatch.externalDownloadUrl || !item.versionManifest) {
|
} else if (filePatch.downloadUrl || bodyPatch.externalDownloadUrl || !item.versionManifest) {
|
||||||
|
|||||||
Reference in New Issue
Block a user