dshmarket 1.70.3 → 1.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/client.js +128 -75
- package/lib/install.js +11 -9
- package/lib/release-candidates.js +101 -0
- package/lib/routes.js +132 -24
- package/lib/types/install.d.ts +1 -0
- package/lib/types/release-candidates.d.ts +26 -0
- package/lib/types/updates.d.ts +18 -3
- package/lib/updates.js +189 -21
- package/package.json +1 -1
- package/src/client/MarketSection.tsx +69 -32
- package/src/client/locales.ts +4 -0
- package/src/client/market-data.ts +9 -5
- package/src/client/operations.ts +16 -5
- package/src/install.ts +12 -10
- package/src/release-candidates.ts +115 -0
- package/src/routes.ts +135 -21
- package/src/updates.ts +169 -26
package/client/client.js
CHANGED
|
@@ -398,6 +398,8 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
398
398
|
updateFail: "更新失败",
|
|
399
399
|
upToDate: "已是最新",
|
|
400
400
|
updateHeldTag: "新版等待中",
|
|
401
|
+
updateHeldTarget: "最新版 {0} 刚发布,还要约 {1} 小时。先更新到 {2}。",
|
|
402
|
+
updateHeldTargetUnknown: "最新版 {0} 的发布时间暂不确定,还需等待。先更新到 {2}。",
|
|
401
403
|
updateHeldHint: "这个 profile 要求新版本发布满一段时间才允许安装,大约还要 {1} 小时。",
|
|
402
404
|
updateHeldEstimatedHint: "新版本已经出现在 /latest,但镜像的完整信息还没同步,所以先不提供更新;大约 {1} 分钟后会重新检查。",
|
|
403
405
|
newerPrereleaseAvailable: "预发布版 {0} 已发布",
|
|
@@ -1159,6 +1161,8 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
1159
1161
|
updateFail: "Update failed",
|
|
1160
1162
|
upToDate: "Up to date",
|
|
1161
1163
|
updateHeldTag: "new version waiting",
|
|
1164
|
+
updateHeldTarget: "Latest {0} was just released; about {1} hours to wait. Update to {2} for now.",
|
|
1165
|
+
updateHeldTargetUnknown: "The release time of latest {0} is unknown, so it still needs to wait. Update to {2} for now.",
|
|
1162
1166
|
updateHeldHint: "This profile waits a while before it installs a fresh release; about {1} hour(s) left.",
|
|
1163
1167
|
updateHeldEstimatedHint: "The new version is listed, but the registry mirror has not caught up with its metadata, so the update is held; the market checks again in about {1} minute(s).",
|
|
1164
1168
|
newerPrereleaseAvailable: "prerelease {0} published",
|
|
@@ -1948,6 +1952,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
1948
1952
|
if (row.restore === true && world.updates[row.name]?.kind === "linked" && findCatalogEntryForLocal(world.plugins, row.name) !== null) return null;
|
|
1949
1953
|
return "no-update";
|
|
1950
1954
|
}
|
|
1955
|
+
if (row.kind === "update" && row.eligibleVersion !== void 0 && row.eligibleVersion !== world.updates[row.name]?.eligibleVersion) return "target-changed";
|
|
1951
1956
|
return null;
|
|
1952
1957
|
}
|
|
1953
1958
|
function installedForCatalog(installed, bundles) {
|
|
@@ -4156,9 +4161,9 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
4156
4161
|
* scripts awaiting approval, a held release. `detail` is kept for the same
|
|
4157
4162
|
* reason: it is the row's own report.
|
|
4158
4163
|
*
|
|
4159
|
-
*
|
|
4160
|
-
*
|
|
4161
|
-
*
|
|
4164
|
+
* Failed updates awaiting build approval also keep their original update
|
|
4165
|
+
* options so approving after a reload cannot select a different target.
|
|
4166
|
+
* `blockedBy` and `conflicts` are deliberately dropped. `needsRefresh` is written so the
|
|
4162
4167
|
* stored shape stays the record's own, but the restore drops it: only the page
|
|
4163
4168
|
* that wrote it can still be waiting for that reload.
|
|
4164
4169
|
*/
|
|
@@ -4172,6 +4177,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
4172
4177
|
...record.reason === void 0 ? {} : { reason: record.reason },
|
|
4173
4178
|
...record.detail === void 0 ? {} : { detail: record.detail },
|
|
4174
4179
|
...record.blockedBuilds === void 0 ? {} : { blockedBuilds: [...record.blockedBuilds] },
|
|
4180
|
+
...record.kind === "update" && record.state === "failed" && (record.blockedBuilds?.length ?? 0) > 0 && record.updateOptions !== void 0 ? { updateOptions: { ...record.updateOptions } } : {},
|
|
4175
4181
|
...record.needsRefresh === true ? { needsRefresh: true } : {},
|
|
4176
4182
|
...record.heldRelease === void 0 ? {} : { heldRelease: { ...record.heldRelease } }
|
|
4177
4183
|
}));
|
|
@@ -4222,6 +4228,13 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
4222
4228
|
const detail = asText(row.detail);
|
|
4223
4229
|
const blockedBuilds = asStringList(row.blockedBuilds);
|
|
4224
4230
|
const heldRelease = asHeldRelease(row.heldRelease);
|
|
4231
|
+
const options = row.updateOptions !== null && typeof row.updateOptions === "object" && !Array.isArray(row.updateOptions) ? row.updateOptions : void 0;
|
|
4232
|
+
const updateOptions = kind === "update" && state === "failed" && blockedBuilds !== void 0 && options !== void 0 ? {
|
|
4233
|
+
...typeof options.force === "boolean" ? { force: options.force } : {},
|
|
4234
|
+
...typeof options.restore === "boolean" ? { restore: options.restore } : {},
|
|
4235
|
+
...asText(options.compatVersion) === void 0 ? {} : { compatVersion: asText(options.compatVersion) },
|
|
4236
|
+
...asText(options.eligibleVersion) === void 0 ? {} : { eligibleVersion: asText(options.eligibleVersion) }
|
|
4237
|
+
} : void 0;
|
|
4225
4238
|
return {
|
|
4226
4239
|
id,
|
|
4227
4240
|
kind,
|
|
@@ -4231,6 +4244,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
4231
4244
|
...reason === void 0 ? {} : { reason },
|
|
4232
4245
|
...detail === void 0 ? {} : { detail },
|
|
4233
4246
|
...blockedBuilds === void 0 ? {} : { blockedBuilds },
|
|
4247
|
+
...updateOptions === void 0 ? {} : { updateOptions },
|
|
4234
4248
|
...heldRelease === void 0 ? {} : { heldRelease }
|
|
4235
4249
|
};
|
|
4236
4250
|
}
|
|
@@ -8987,18 +9001,20 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
8987
9001
|
kind,
|
|
8988
9002
|
name,
|
|
8989
9003
|
restore: options.restore === true,
|
|
9004
|
+
...typeof options.eligibleVersion === "string" ? { eligibleVersion: options.eligibleVersion } : {},
|
|
8990
9005
|
...url === void 0 ? {} : { url }
|
|
8991
9006
|
}, {
|
|
8992
9007
|
installed,
|
|
8993
9008
|
updates,
|
|
8994
9009
|
plugins: data.plugins
|
|
8995
9010
|
});
|
|
8996
|
-
if (verdict !== null) return stale(verdict === "gone" ? t("agentQueueStaleGone") : t("agentQueueStaleNoUpdate"));
|
|
9011
|
+
if (verdict !== null) return stale(verdict === "target-changed" ? localizeBilingual("更新目标已变化,已跳过。请重新选择更新。 / The update target changed, so this task was skipped. Choose the update again.", lang) : verdict === "gone" ? t("agentQueueStaleGone") : t("agentQueueStaleNoUpdate"));
|
|
8997
9012
|
if (kind === "install" && url === void 0) return [];
|
|
8998
9013
|
const updateOptions = kind === "update" ? {
|
|
8999
9014
|
force: options.force === true,
|
|
9000
9015
|
restore: options.restore === true,
|
|
9001
|
-
...typeof options.compatVersion === "string" ? { compatVersion: options.compatVersion } : {}
|
|
9016
|
+
...typeof options.compatVersion === "string" ? { compatVersion: options.compatVersion } : {},
|
|
9017
|
+
...typeof options.eligibleVersion === "string" ? { eligibleVersion: options.eligibleVersion } : {}
|
|
9002
9018
|
} : void 0;
|
|
9003
9019
|
return [{
|
|
9004
9020
|
ok: true,
|
|
@@ -9045,6 +9061,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
9045
9061
|
}, [
|
|
9046
9062
|
data,
|
|
9047
9063
|
t,
|
|
9064
|
+
lang,
|
|
9048
9065
|
queueSnapshotReady,
|
|
9049
9066
|
installed,
|
|
9050
9067
|
updates
|
|
@@ -10683,7 +10700,14 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
10683
10700
|
body: "{}"
|
|
10684
10701
|
}).catch(() => {});
|
|
10685
10702
|
}, []);
|
|
10686
|
-
const doUpdate = (0, react.useCallback)((name, force = false, restore = false, compatVersion, confirmAgentsRunning = false) => {
|
|
10703
|
+
const doUpdate = (0, react.useCallback)((name, force = false, restore = false, compatVersion, confirmAgentsRunning = false, eligibleVersion) => {
|
|
10704
|
+
const expectedVersion = compatVersion === void 0 && !restore ? eligibleVersion ?? updates[name]?.eligibleVersion : void 0;
|
|
10705
|
+
const updateOptions = {
|
|
10706
|
+
force,
|
|
10707
|
+
restore,
|
|
10708
|
+
compatVersion,
|
|
10709
|
+
eligibleVersion: expectedVersion
|
|
10710
|
+
};
|
|
10687
10711
|
setInstallError(null);
|
|
10688
10712
|
setActivationWarnings([]);
|
|
10689
10713
|
setStaleName((prev) => prev === name ? null : prev);
|
|
@@ -10698,11 +10722,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
10698
10722
|
kind: "update",
|
|
10699
10723
|
name,
|
|
10700
10724
|
state: "running",
|
|
10701
|
-
updateOptions
|
|
10702
|
-
force,
|
|
10703
|
-
restore,
|
|
10704
|
-
compatVersion
|
|
10705
|
-
}
|
|
10725
|
+
updateOptions
|
|
10706
10726
|
}));
|
|
10707
10727
|
return fetch(api("/dsh-market/update"), {
|
|
10708
10728
|
method: "POST",
|
|
@@ -10712,6 +10732,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
10712
10732
|
...force ? { force: true } : {},
|
|
10713
10733
|
...restore ? { restore: true } : {},
|
|
10714
10734
|
...compatVersion !== void 0 ? { compatVersion } : {},
|
|
10735
|
+
...expectedVersion !== void 0 ? { eligibleVersion: expectedVersion } : {},
|
|
10715
10736
|
...confirmAgentsRunning ? { confirmAgentsRunning: true } : {}
|
|
10716
10737
|
})
|
|
10717
10738
|
}).then((res) => res.json().then((body) => ({
|
|
@@ -10726,6 +10747,15 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
10726
10747
|
if (body.partial === true) setInstallError(t("partialNote"));
|
|
10727
10748
|
return;
|
|
10728
10749
|
}
|
|
10750
|
+
if (status === 409 && body.staleTarget === true) {
|
|
10751
|
+
setRecords((list) => patch(list, updateRecordId, {
|
|
10752
|
+
state: "failed",
|
|
10753
|
+
reason: String(body.error ?? "Update target changed")
|
|
10754
|
+
}));
|
|
10755
|
+
setInstallError(localizeBilingual(String(body.error ?? "Update target changed"), lang));
|
|
10756
|
+
refreshInstalled(true);
|
|
10757
|
+
return;
|
|
10758
|
+
}
|
|
10729
10759
|
if (status === 200 && body.ok && body.skipped === "current") {
|
|
10730
10760
|
setRecords((list) => drop(list, updateRecordId));
|
|
10731
10761
|
refreshInstalled(true);
|
|
@@ -10776,7 +10806,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
10776
10806
|
if (Array.isArray(body.ignoredBuilds) && body.ignoredBuilds.length > 0) setBuildsSkipped({
|
|
10777
10807
|
updateName: name,
|
|
10778
10808
|
names: body.ignoredBuilds.map(String),
|
|
10779
|
-
|
|
10809
|
+
updateOptions
|
|
10780
10810
|
});
|
|
10781
10811
|
const orphans = Array.isArray(body.orphanBundles) ? body.orphanBundles.map(String) : [];
|
|
10782
10812
|
const failure = failureText(body);
|
|
@@ -10788,7 +10818,8 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
10788
10818
|
].filter(Boolean).join("\n").trim().slice(0, 1200);
|
|
10789
10819
|
setRecords((list) => patch(list, updateRecordId, {
|
|
10790
10820
|
state: "failed",
|
|
10791
|
-
reason: clipped
|
|
10821
|
+
reason: clipped,
|
|
10822
|
+
...Array.isArray(body.ignoredBuilds) && body.ignoredBuilds.length > 0 ? { blockedBuilds: body.ignoredBuilds.map(String) } : {}
|
|
10792
10823
|
}));
|
|
10793
10824
|
setInstallError((restore ? t("restoreFail") : t("updateFail")) + ": " + name + " — " + localizeBilingual(clipped, lang));
|
|
10794
10825
|
}
|
|
@@ -10797,7 +10828,8 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
10797
10828
|
refreshInstalled,
|
|
10798
10829
|
installed,
|
|
10799
10830
|
t,
|
|
10800
|
-
lang
|
|
10831
|
+
lang,
|
|
10832
|
+
updates
|
|
10801
10833
|
]);
|
|
10802
10834
|
const doSourceMigration = (0, react.useCallback)((name, confirmAgentsRunning = false, renameTo) => {
|
|
10803
10835
|
setInstallError(null);
|
|
@@ -11397,7 +11429,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
11397
11429
|
if (task.kind === "install") {
|
|
11398
11430
|
const plugin = dataRef.current?.plugins.find((candidate) => candidate.url === task.url);
|
|
11399
11431
|
if (plugin !== void 0) doInstallRef.current?.(plugin);
|
|
11400
|
-
} else if (task.kind === "update") doUpdateRef.current?.(task.name, task.updateOptions?.force, task.updateOptions?.restore, task.updateOptions?.compatVersion);
|
|
11432
|
+
} else if (task.kind === "update") doUpdateRef.current?.(task.name, task.updateOptions?.force, task.updateOptions?.restore, task.updateOptions?.compatVersion, false, task.updateOptions?.eligibleVersion);
|
|
11401
11433
|
else doUninstallRef.current?.(task.name);
|
|
11402
11434
|
} finally {
|
|
11403
11435
|
drainingRef.current = false;
|
|
@@ -11437,7 +11469,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
11437
11469
|
doInstall(plugin);
|
|
11438
11470
|
} else if (record.kind === "update") {
|
|
11439
11471
|
setRecords((prev) => drop(prev, record.id));
|
|
11440
|
-
doUpdate(record.name, record.updateOptions?.force, record.updateOptions?.restore, record.updateOptions?.compatVersion, confirmAgentsRunning);
|
|
11472
|
+
doUpdate(record.name, record.updateOptions?.force, record.updateOptions?.restore, record.updateOptions?.compatVersion, confirmAgentsRunning, record.updateOptions?.eligibleVersion);
|
|
11441
11473
|
} else {
|
|
11442
11474
|
setRecords((prev) => drop(prev, record.id));
|
|
11443
11475
|
doUninstall(record.name, confirmAgentsRunning);
|
|
@@ -11704,22 +11736,26 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
11704
11736
|
});
|
|
11705
11737
|
}, [bootId]);
|
|
11706
11738
|
const doUpdateAll = (0, react.useCallback)(() => {
|
|
11707
|
-
const names = reminderBatchUpdatableNames.
|
|
11739
|
+
const names = reminderBatchUpdatableNames.map((name) => ({
|
|
11740
|
+
name,
|
|
11741
|
+
eligibleVersion: updates[name]?.eligibleVersion
|
|
11742
|
+
}));
|
|
11708
11743
|
setUpdatingAll(true);
|
|
11709
11744
|
const next = () => {
|
|
11710
|
-
const
|
|
11711
|
-
if (
|
|
11745
|
+
const item = names.shift();
|
|
11746
|
+
if (item === void 0) {
|
|
11712
11747
|
setUpdatingAll(false);
|
|
11713
11748
|
refreshInstalled(true);
|
|
11714
11749
|
return;
|
|
11715
11750
|
}
|
|
11716
|
-
doUpdate(name).then(next, next);
|
|
11751
|
+
doUpdate(item.name, false, false, void 0, false, item.eligibleVersion).then(next, next);
|
|
11717
11752
|
};
|
|
11718
11753
|
next();
|
|
11719
11754
|
}, [
|
|
11720
11755
|
reminderBatchUpdatableNames,
|
|
11721
11756
|
doUpdate,
|
|
11722
|
-
refreshInstalled
|
|
11757
|
+
refreshInstalled,
|
|
11758
|
+
updates
|
|
11723
11759
|
]);
|
|
11724
11760
|
const finishRestore = (0, react.useCallback)((body) => {
|
|
11725
11761
|
const errors = Array.isArray(body.errors) ? body.errors : [];
|
|
@@ -13123,7 +13159,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
13123
13159
|
const plugin = record.url === void 0 ? void 0 : data?.plugins.find((p) => p.url === record.url);
|
|
13124
13160
|
approveAndRetry(names, () => {
|
|
13125
13161
|
if (plugin !== void 0) doInstall(plugin);
|
|
13126
|
-
else doUpdate(record.name, false,
|
|
13162
|
+
else doUpdate(record.name, record.updateOptions?.force, record.updateOptions?.restore, record.updateOptions?.compatVersion, false, record.updateOptions?.eligibleVersion);
|
|
13127
13163
|
});
|
|
13128
13164
|
}
|
|
13129
13165
|
})
|
|
@@ -13374,11 +13410,11 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
13374
13410
|
size: "sm",
|
|
13375
13411
|
disabled: busyUrl !== null,
|
|
13376
13412
|
onClick: () => {
|
|
13377
|
-
const { plugin, updateName, names,
|
|
13413
|
+
const { plugin, updateName, names, updateOptions } = buildsSkipped;
|
|
13378
13414
|
setBuildsSkipped(null);
|
|
13379
13415
|
approveAndRetry(names, () => {
|
|
13380
13416
|
if (plugin !== void 0) doInstall(plugin);
|
|
13381
|
-
else if (updateName !== void 0) doUpdate(updateName,
|
|
13417
|
+
else if (updateName !== void 0) doUpdate(updateName, updateOptions?.force, updateOptions?.restore, updateOptions?.compatVersion, false, updateOptions?.eligibleVersion);
|
|
13382
13418
|
});
|
|
13383
13419
|
},
|
|
13384
13420
|
children: t("approveBuilds")
|
|
@@ -14594,56 +14630,70 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
14594
14630
|
"aria-hidden": "true",
|
|
14595
14631
|
children: "·"
|
|
14596
14632
|
});
|
|
14597
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
|
|
14606
|
-
|
|
14607
|
-
|
|
14608
|
-
|
|
14609
|
-
|
|
14610
|
-
|
|
14611
|
-
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14621
|
-
|
|
14622
|
-
|
|
14623
|
-
|
|
14624
|
-
|
|
14625
|
-
|
|
14626
|
-
|
|
14627
|
-
|
|
14628
|
-
|
|
14629
|
-
|
|
14630
|
-
|
|
14631
|
-
|
|
14632
|
-
className: Market_module_css_default.
|
|
14633
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
14634
|
+
updatable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14635
|
+
className: Market_module_css_default.irowStatus,
|
|
14636
|
+
children: [!generation && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
14637
|
+
className: Market_module_css_default.irowStatusPart,
|
|
14638
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14639
|
+
className: Market_module_css_default.irowStatusFact,
|
|
14640
|
+
children: status?.eligibleVersion != null ? t("hostUpdateReady").replace("{0}", displayLatest(status.eligibleVersion)) : status?.latest != null ? t("hostUpdateReady").replace("{0}", displayLatest(status.latest)) : t("updateAvailableShort")
|
|
14641
|
+
}), sep]
|
|
14642
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
14643
|
+
type: "button",
|
|
14644
|
+
className: Market_module_css_default.notesLink,
|
|
14645
|
+
onClick: () => openNotes(name, status?.current ?? null, status?.latest ?? null, repoUrl),
|
|
14646
|
+
children: `${t("notesLink")} ›`
|
|
14647
|
+
})]
|
|
14648
|
+
}),
|
|
14649
|
+
updatable && status?.eligibleVersion !== void 0 && status.restoreRequired !== true && status.heldUpdate !== void 0 && status.heldUpdate.latest !== status.eligibleVersion && (() => {
|
|
14650
|
+
const left = Date.parse(status.heldUpdate.until) - Date.now();
|
|
14651
|
+
const known = status.heldUpdate.estimated !== true && Number.isFinite(left) && left > 0;
|
|
14652
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
14653
|
+
className: Market_module_css_default.irowStatus,
|
|
14654
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14655
|
+
className: Market_module_css_default.metaInline,
|
|
14656
|
+
children: t(known ? "updateHeldTarget" : "updateHeldTargetUnknown").replace("{0}", displayLatest(status.heldUpdate.latest)).replace("{1}", String(Math.max(1, Math.ceil(left / 36e5)))).replace("{2}", displayLatest(status.eligibleVersion))
|
|
14657
|
+
})
|
|
14658
|
+
});
|
|
14659
|
+
})(),
|
|
14660
|
+
(exempt || ignored) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14661
|
+
className: Market_module_css_default.irowStatus,
|
|
14662
|
+
children: [
|
|
14663
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14664
|
+
className: Market_module_css_default.irowStatusIcon,
|
|
14665
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconBellOff, { size: 16 })
|
|
14666
|
+
}),
|
|
14667
|
+
exempt ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14668
|
+
className: Market_module_css_default.metaInline,
|
|
14633
14669
|
title: t("updateExemptHint"),
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
|
|
14670
|
+
role: "status",
|
|
14671
|
+
children: t("updateExemptMark")
|
|
14672
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14673
|
+
className: Market_module_css_default.metaInline,
|
|
14674
|
+
role: "status",
|
|
14675
|
+
children: t("updateNoticeIgnored")
|
|
14676
|
+
}),
|
|
14677
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14678
|
+
className: Market_module_css_default.irowStatusAction,
|
|
14679
|
+
children: exempt ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
14680
|
+
type: "button",
|
|
14681
|
+
className: Market_module_css_default.noteToggle,
|
|
14682
|
+
title: t("updateExemptHint"),
|
|
14683
|
+
"aria-label": `${t("updateExemptRestore")} ${name}`,
|
|
14684
|
+
onClick: () => toggleUpdateExempt(name),
|
|
14685
|
+
children: t("updateExemptRestore")
|
|
14686
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
14687
|
+
type: "button",
|
|
14688
|
+
className: Market_module_css_default.noteToggle,
|
|
14689
|
+
"aria-label": `${t("updateExemptRestore")} ${name}`,
|
|
14690
|
+
onClick: () => unignoreUpdateNotice(name),
|
|
14691
|
+
children: t("updateExemptRestore")
|
|
14692
|
+
})
|
|
14643
14693
|
})
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14694
|
+
]
|
|
14695
|
+
})
|
|
14696
|
+
] });
|
|
14647
14697
|
})(),
|
|
14648
14698
|
!off && act !== void 0 && meta !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14649
14699
|
className: Market_module_css_default.act,
|
|
@@ -14783,7 +14833,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
14783
14833
|
className: Market_module_css_default.metaTag,
|
|
14784
14834
|
title: t("hostUpdateHint"),
|
|
14785
14835
|
children: t("hostUpdateReady").replace("{0}", status.latest)
|
|
14786
|
-
}) : status && status.updateAvailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
14836
|
+
}) : status && status.updateAvailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
14787
14837
|
variant: "outline",
|
|
14788
14838
|
size: "sm",
|
|
14789
14839
|
className: Market_module_css_default.warnBtn,
|
|
@@ -14792,8 +14842,11 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
|
|
|
14792
14842
|
if (status.restoreRequired === true) askRestore(name);
|
|
14793
14843
|
else doUpdate(name);
|
|
14794
14844
|
},
|
|
14795
|
-
children: status.restoreRequired === true ? t("restoreOnline") : t("update")
|
|
14796
|
-
})
|
|
14845
|
+
children: status.restoreRequired === true ? t("restoreOnline") : status.eligibleVersion !== void 0 ? `${t("update")} ${displayLatest(status.eligibleVersion)}` : t("update")
|
|
14846
|
+
}), status.heldUpdate !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14847
|
+
className: Market_module_css_default.metaTag,
|
|
14848
|
+
children: t("updateHeldTag").replace("{0}", displayLatest(status.heldUpdate.latest))
|
|
14849
|
+
})] }) : status?.heldUpdate !== void 0 ? (() => {
|
|
14797
14850
|
const left = Date.parse(status.heldUpdate.until) - Date.now();
|
|
14798
14851
|
const estimated = status.heldUpdate.estimated === true;
|
|
14799
14852
|
const amount = String(Math.max(1, Math.ceil(left / (estimated ? 6e4 : 36e5))));
|
package/lib/install.js
CHANGED
|
@@ -119,6 +119,9 @@ export function isUnpublishedHostPeer(pkg, profile, explicitDir) {
|
|
|
119
119
|
*/
|
|
120
120
|
export async function withHoistRecovery(run, profile, pluginArgs, profileDirectory, options = {}) {
|
|
121
121
|
const marketFlags = options.marketFlags !== false;
|
|
122
|
+
// An already verified mature update must not rewrite the user's workspace
|
|
123
|
+
// policy as a side effect of market-level recovery (#991).
|
|
124
|
+
const repairProfilePolicy = options.preserveProfilePolicy !== true;
|
|
122
125
|
/** The option a recovery step needed and this host does not accept (#732). */
|
|
123
126
|
let unavailableOption = null;
|
|
124
127
|
// Before the FIRST run, not after a failure: the shadowed duplicate rule
|
|
@@ -127,7 +130,7 @@ export async function withHoistRecovery(run, profile, pluginArgs, profileDirecto
|
|
|
127
130
|
// small file read. Every verb that resolves the graph is covered, not just
|
|
128
131
|
// add and remove: an `install` or an in-place `update` reads the same key.
|
|
129
132
|
const verb = pluginArgs.find(argument => !argument.startsWith('-'));
|
|
130
|
-
if (verb === 'add' || verb === 'remove' || verb === 'install' || verb === 'update') {
|
|
133
|
+
if (repairProfilePolicy && (verb === 'add' || verb === 'remove' || verb === 'install' || verb === 'update')) {
|
|
131
134
|
const merged = mergeDuplicateReleaseAgeExcludes(profile, profileDirectory);
|
|
132
135
|
if (merged.length > 0) {
|
|
133
136
|
logEvent('warn', 'install', `minimumReleaseAgeExclude held several rules for ${merged.join(', ')}, and pnpm honours only the FIRST per name (#732) — merged each package's rules into one, before running`);
|
|
@@ -145,7 +148,7 @@ export async function withHoistRecovery(run, profile, pluginArgs, profileDirecto
|
|
|
145
148
|
// appended stayed dead (@Mlte0907, 1.66.7). The merge is a no-op when
|
|
146
149
|
// nothing is duplicated, so asking it costs one file read.
|
|
147
150
|
let repairedDuringRun = false;
|
|
148
|
-
if (!ok(result) && !result.cancelled && (pluginArgs[0] === 'add' || pluginArgs[0] === 'remove')) {
|
|
151
|
+
if (repairProfilePolicy && !ok(result) && !result.cancelled && (pluginArgs[0] === 'add' || pluginArgs[0] === 'remove')) {
|
|
149
152
|
const mergedNow = mergeDuplicateReleaseAgeExcludes(profile, profileDirectory);
|
|
150
153
|
if (mergedNow.length > 0) {
|
|
151
154
|
repairedDuringRun = true;
|
|
@@ -155,7 +158,7 @@ export async function withHoistRecovery(run, profile, pluginArgs, profileDirecto
|
|
|
155
158
|
}
|
|
156
159
|
if (!ok(result) && !result.cancelled && !repairedDuringRun) {
|
|
157
160
|
const failure = classifyPnpmFailure(`${result.stderr}\n${result.stdout}`, result.exitCode);
|
|
158
|
-
if (failure?.code === 'unparseable-build-key') {
|
|
161
|
+
if (failure?.code === 'unparseable-build-key' && repairProfilePolicy) {
|
|
159
162
|
// The profile's own allowBuilds block is what fails, so no retry of the
|
|
160
163
|
// same command can pass until it is repaired (#698). Drop only the
|
|
161
164
|
// source-form keys this pnpm cannot parse — bare names stay, and on
|
|
@@ -187,15 +190,14 @@ export async function withHoistRecovery(run, profile, pluginArgs, profileDirecto
|
|
|
187
190
|
// (#732) or extending a version union (#733). Either way the same
|
|
188
191
|
// rewrite fixes it, needs no option at all, and so also works on the
|
|
189
192
|
// desktop bridge that refuses options. Retry the SAME argv afterwards.
|
|
190
|
-
const mergedNow = mergeDuplicateReleaseAgeExcludes(profile, profileDirectory);
|
|
193
|
+
const mergedNow = repairProfilePolicy ? mergeDuplicateReleaseAgeExcludes(profile, profileDirectory) : [];
|
|
191
194
|
if (mergedNow.length > 0) {
|
|
192
195
|
logEvent('warn', 'install', `pnpm appended a second minimumReleaseAgeExclude rule for ${mergedNow.join(', ')} and honours only the first, shadowing its own entry (#732) — merged them into one and retrying once`);
|
|
193
196
|
result = await run(profile, pluginArgs);
|
|
194
197
|
}
|
|
195
|
-
else if (options.releaseAgeBypass === false) {
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
// job, so the command stands as it is.
|
|
198
|
+
else if (options.releaseAgeBypass === false || options.preserveProfilePolicy === true) {
|
|
199
|
+
// Fresh installs (#594) and mature updates/recovery (#991) never
|
|
200
|
+
// bypass age enforcement. Preserve the native failure and policy.
|
|
199
201
|
}
|
|
200
202
|
else if (!marketFlags) {
|
|
201
203
|
unavailableOption = RELEASE_AGE_OVERRIDE;
|
|
@@ -399,7 +401,7 @@ export async function withHoistRecovery(run, profile, pluginArgs, profileDirecto
|
|
|
399
401
|
// after a recovery retry or an official Desktop manager install. Repair
|
|
400
402
|
// the final file now, before another plugin operation reads it; a successful
|
|
401
403
|
// command must never be retried just because its exclusions needed merging.
|
|
402
|
-
if (ok(result) && (verb === 'add' || verb === 'remove' || verb === 'install' || verb === 'update')) {
|
|
404
|
+
if (repairProfilePolicy && ok(result) && (verb === 'add' || verb === 'remove' || verb === 'install' || verb === 'update')) {
|
|
403
405
|
try {
|
|
404
406
|
const merged = mergeDuplicateReleaseAgeExcludes(profile, profileDirectory);
|
|
405
407
|
if (merged.length > 0) {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
function semver(value) {
|
|
2
|
+
if (typeof value !== 'string')
|
|
3
|
+
return null;
|
|
4
|
+
const matched = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/.exec(value);
|
|
5
|
+
if (!matched)
|
|
6
|
+
return null;
|
|
7
|
+
const prerelease = matched[4]?.split('.') ?? [];
|
|
8
|
+
if (prerelease.some(part => /^\d+$/.test(part) && part.length > 1 && part.startsWith('0')))
|
|
9
|
+
return null;
|
|
10
|
+
return { core: [BigInt(matched[1]), BigInt(matched[2]), BigInt(matched[3])], prerelease };
|
|
11
|
+
}
|
|
12
|
+
/** Accept stable SemVer, including build metadata, at both API boundaries. */
|
|
13
|
+
export function isStableReleaseVersion(value) {
|
|
14
|
+
const parsed = semver(value);
|
|
15
|
+
return parsed !== null && parsed.prerelease.length === 0;
|
|
16
|
+
}
|
|
17
|
+
function compare(a, b) {
|
|
18
|
+
for (let i = 0; i < 3; i++) {
|
|
19
|
+
if (a.core[i] !== b.core[i])
|
|
20
|
+
return a.core[i] > b.core[i] ? 1 : -1;
|
|
21
|
+
}
|
|
22
|
+
if (a.prerelease.length === 0 || b.prerelease.length === 0)
|
|
23
|
+
return Math.sign(b.prerelease.length - a.prerelease.length);
|
|
24
|
+
for (let i = 0; i < Math.max(a.prerelease.length, b.prerelease.length); i++) {
|
|
25
|
+
const x = a.prerelease[i], y = b.prerelease[i];
|
|
26
|
+
if (x === undefined)
|
|
27
|
+
return -1;
|
|
28
|
+
if (y === undefined)
|
|
29
|
+
return 1;
|
|
30
|
+
if (x === y)
|
|
31
|
+
continue;
|
|
32
|
+
const xn = /^\d+$/.test(x), yn = /^\d+$/.test(y);
|
|
33
|
+
if (xn && yn)
|
|
34
|
+
return BigInt(x) > BigInt(y) ? 1 : -1;
|
|
35
|
+
if (xn !== yn)
|
|
36
|
+
return xn ? -1 : 1;
|
|
37
|
+
return x > y ? 1 : -1;
|
|
38
|
+
}
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
export function strictPublishTime(value) {
|
|
42
|
+
if (typeof value !== 'string' || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?Z$/.test(value))
|
|
43
|
+
return null;
|
|
44
|
+
const timestamp = Date.parse(value);
|
|
45
|
+
if (!Number.isFinite(timestamp))
|
|
46
|
+
return null;
|
|
47
|
+
return new Date(timestamp).toISOString().slice(0, 19) === value.slice(0, 19) ? timestamp : null;
|
|
48
|
+
}
|
|
49
|
+
export function selectMatureUpdate(packument, installed, now, policy) {
|
|
50
|
+
const latest = typeof packument?.['dist-tags']?.latest === 'string' ? packument['dist-tags'].latest : null;
|
|
51
|
+
const empty = (reason) => ({ candidate: null, latest, held: null, reason, nextMaturityAt: null, latestMaturityAt: null });
|
|
52
|
+
const top = semver(latest);
|
|
53
|
+
if (!top || top.prerelease.length || !packument.versions || !packument.time ||
|
|
54
|
+
typeof packument.versions !== 'object' || typeof packument.time !== 'object' ||
|
|
55
|
+
!Object.hasOwn(packument.versions, latest) ||
|
|
56
|
+
(policy.latestFromEndpoint != null && policy.latestFromEndpoint !== latest))
|
|
57
|
+
return empty('inconsistent');
|
|
58
|
+
const ceiling = policy.maxVersion === undefined ? top : semver(policy.maxVersion);
|
|
59
|
+
if (!ceiling || ceiling.prerelease.length ||
|
|
60
|
+
(policy.maxVersion !== undefined && !Object.hasOwn(packument.versions, policy.maxVersion)))
|
|
61
|
+
return empty('inconsistent');
|
|
62
|
+
const current = semver(installed);
|
|
63
|
+
if (!current || current.prerelease.length)
|
|
64
|
+
return empty('invalid-installed');
|
|
65
|
+
const ageMs = policy.minimumReleaseAgeMs;
|
|
66
|
+
if (!Number.isSafeInteger(ageMs) || ageMs < 0 || !Number.isFinite(now))
|
|
67
|
+
return empty('policy-unknown');
|
|
68
|
+
let candidate = null;
|
|
69
|
+
let candidateSemver = null;
|
|
70
|
+
let nextMaturityAt = null;
|
|
71
|
+
for (const version of Object.keys(packument.versions)) {
|
|
72
|
+
const parsed = semver(version);
|
|
73
|
+
if (!parsed || parsed.prerelease.length || compare(parsed, current) <= 0 || compare(parsed, top) > 0 || compare(parsed, ceiling) > 0)
|
|
74
|
+
continue;
|
|
75
|
+
const published = strictPublishTime(packument.time[version]);
|
|
76
|
+
const exempt = policy.exemptPackage === true || policy.exemptVersions?.has(version) === true;
|
|
77
|
+
// A declared exemption is not evidence for a fabricated or absent timestamp.
|
|
78
|
+
if (published === null || published > now || (!exempt && published > now - ageMs)) {
|
|
79
|
+
if (published !== null && published <= now && Number.isFinite(published + ageMs)) {
|
|
80
|
+
const at = published + ageMs;
|
|
81
|
+
if (nextMaturityAt === null || at < nextMaturityAt)
|
|
82
|
+
nextMaturityAt = at;
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (candidateSemver === null || compare(parsed, candidateSemver) > 0) {
|
|
87
|
+
candidate = version;
|
|
88
|
+
candidateSemver = parsed;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const latestPublished = strictPublishTime(packument.time[latest]);
|
|
92
|
+
const latestExempt = policy.exemptPackage === true || policy.exemptVersions?.has(latest) === true;
|
|
93
|
+
const latestMature = latestPublished !== null && latestPublished <= now &&
|
|
94
|
+
(latestExempt || latestPublished <= now - ageMs);
|
|
95
|
+
return {
|
|
96
|
+
candidate, latest, held: latestMature ? null : latest,
|
|
97
|
+
reason: candidate ? 'eligible' : latestMature ? 'current' : latestPublished === null ? 'unknown-age' : 'held',
|
|
98
|
+
nextMaturityAt,
|
|
99
|
+
latestMaturityAt: latestPublished === null || latestExempt ? null : latestPublished + ageMs,
|
|
100
|
+
};
|
|
101
|
+
}
|