@linxin666/dsh-remote-web-ui 0.1.15 → 0.1.16
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/README.i18n.yaml +2 -2
- package/README.md +9 -5
- package/README.zh.md +1 -1
- package/lib/client.js +58 -10
- package/lib/client.js.map +1 -1
- package/lib/index.js +110 -11
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +3 -1
- package/lib/types/client/UpdatePanel.js +2 -0
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +4 -0
- package/lib/types/client/settings-form.d.ts +62 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +61 -12
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +36 -10
- package/lib/types/update.d.ts +44 -4
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +118 -8
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +7 -1
- package/src/client/UpdatePanel.tsx +2 -0
- package/src/client/locales.ts +4 -0
- package/src/client/settings-card.module.css +3 -0
- package/src/client/settings-form.ts +114 -12
- package/src/index.ts +34 -10
- package/src/update.ts +128 -9
package/lib/index.js
CHANGED
|
@@ -1633,13 +1633,16 @@ function isLinkedSpec(spec) {
|
|
|
1633
1633
|
}
|
|
1634
1634
|
/**
|
|
1635
1635
|
* Resolve the anchor package's manifest path. The aggregate package is the
|
|
1636
|
-
* canonical entry point; this plugin's own package is the fallback.
|
|
1636
|
+
* canonical entry point; this plugin's own package is the fallback. Both a
|
|
1637
|
+
* throwing resolve and an undefined return mean "not installed" and move on
|
|
1638
|
+
* to the next candidate.
|
|
1637
1639
|
* @param resolve - a Node resolve implementation scoped to the host process.
|
|
1638
1640
|
* @returns the absolute manifest path, or undefined when neither is installed.
|
|
1639
1641
|
*/
|
|
1640
1642
|
function resolveAnchorManifest(resolve) {
|
|
1641
1643
|
for (const name of [AGGREGATE_PACKAGE, SELF_PACKAGE]) try {
|
|
1642
|
-
|
|
1644
|
+
const path = resolve(name + "/package.json");
|
|
1645
|
+
if (path !== void 0) return path;
|
|
1643
1646
|
} catch {}
|
|
1644
1647
|
}
|
|
1645
1648
|
/**
|
|
@@ -1701,14 +1704,21 @@ async function fetchLatestVersion(name, fetchImpl, timeoutMs = 1e4) {
|
|
|
1701
1704
|
return;
|
|
1702
1705
|
}
|
|
1703
1706
|
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Sentinel for an installed version that could not be read (missing manifest
|
|
1709
|
+
* or resolve failure). It is a real-looking version so `checkUpdates` can
|
|
1710
|
+
* render the affected row, but the verified-update comparison must never
|
|
1711
|
+
* treat it as evidence — a read failure is not a version that "moved".
|
|
1712
|
+
*/
|
|
1713
|
+
const VERSION_UNKNOWN = "0.0.0";
|
|
1704
1714
|
/** The resolved current version of one family package (probe failure tolerated). */
|
|
1705
1715
|
function readInstalledVersion(resolve, name) {
|
|
1706
1716
|
try {
|
|
1707
1717
|
const path = resolve(name + "/package.json");
|
|
1708
1718
|
const version = path === void 0 ? void 0 : readManifest(path)?.version;
|
|
1709
|
-
return typeof version === "string" ? version :
|
|
1719
|
+
return typeof version === "string" ? version : VERSION_UNKNOWN;
|
|
1710
1720
|
} catch {
|
|
1711
|
-
return
|
|
1721
|
+
return VERSION_UNKNOWN;
|
|
1712
1722
|
}
|
|
1713
1723
|
}
|
|
1714
1724
|
/**
|
|
@@ -1800,13 +1810,18 @@ function runUpdate(deps) {
|
|
|
1800
1810
|
const candidates = [
|
|
1801
1811
|
{
|
|
1802
1812
|
command: "pnpm",
|
|
1803
|
-
args: [
|
|
1813
|
+
args: [
|
|
1814
|
+
"update",
|
|
1815
|
+
"--latest",
|
|
1816
|
+
...packages
|
|
1817
|
+
]
|
|
1804
1818
|
},
|
|
1805
1819
|
{
|
|
1806
1820
|
command: "corepack",
|
|
1807
1821
|
args: [
|
|
1808
1822
|
"pnpm",
|
|
1809
1823
|
"update",
|
|
1824
|
+
"--latest",
|
|
1810
1825
|
...packages
|
|
1811
1826
|
]
|
|
1812
1827
|
},
|
|
@@ -1816,6 +1831,7 @@ function runUpdate(deps) {
|
|
|
1816
1831
|
"--yes",
|
|
1817
1832
|
"pnpm",
|
|
1818
1833
|
"update",
|
|
1834
|
+
"--latest",
|
|
1819
1835
|
...packages
|
|
1820
1836
|
]
|
|
1821
1837
|
}
|
|
@@ -1912,6 +1928,70 @@ function runUpdate(deps) {
|
|
|
1912
1928
|
runCandidate(0);
|
|
1913
1929
|
});
|
|
1914
1930
|
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Run the update, then verify the installed versions actually moved. pnpm
|
|
1933
|
+
* exits 0 even when it silently kept the installed versions — the pnpm 11
|
|
1934
|
+
* `minimumReleaseAge` gate refuses same-day releases by default — so a green
|
|
1935
|
+
* exit alone would report a misleading "update complete". Re-read the
|
|
1936
|
+
* installed versions afterwards and surface a `stale` failure (with the
|
|
1937
|
+
* captured pnpm output) when nothing moved, so the panel can tell the user
|
|
1938
|
+
* how to unblock the gate instead of claiming success.
|
|
1939
|
+
*
|
|
1940
|
+
* The stale decision anchors on the pre-run installed versions, not on the
|
|
1941
|
+
* registry latest: under a lenient gate pnpm may move 0.1.12 -> 0.1.13 while
|
|
1942
|
+
* latest stays 0.1.15 — that is a real update, not stale. The post-run anchor
|
|
1943
|
+
* path is re-resolved rather than reused from boot time: pnpm removes the old
|
|
1944
|
+
* version's .pnpm directory on update, so a boot-time captured path would
|
|
1945
|
+
* fail to read and collapse a successful update into a bogus 'missing'.
|
|
1946
|
+
*
|
|
1947
|
+
* A green exit whose verification has nothing to compare (registry probe
|
|
1948
|
+
* outage, missing anchor, non-npm mode) is reported as `verify-failed` — it
|
|
1949
|
+
* must never read as success. A green exit where no package moved but the
|
|
1950
|
+
* post-run check could not prove the install is fully current (a partial
|
|
1951
|
+
* probe failure hides whether a gate kept something back) is also
|
|
1952
|
+
* `verify-failed`: it must not collapse into a success either.
|
|
1953
|
+
* @param deps - the run and check seams.
|
|
1954
|
+
* @returns the run result; `stale` when exit 0 left every version in place,
|
|
1955
|
+
* `verify-failed` when the post-run check could not verify anything.
|
|
1956
|
+
*/
|
|
1957
|
+
async function runUpdateVerified(deps) {
|
|
1958
|
+
const before = /* @__PURE__ */ new Map();
|
|
1959
|
+
for (const name of deps.run.packages) {
|
|
1960
|
+
const version = readInstalledVersion(deps.check.resolve, name);
|
|
1961
|
+
if (version !== VERSION_UNKNOWN) before.set(name, version);
|
|
1962
|
+
}
|
|
1963
|
+
const result = await runUpdate(deps.run);
|
|
1964
|
+
if (!result.ok) return result;
|
|
1965
|
+
const anchorManifestPath = resolveAnchorManifest(deps.check.resolve) ?? deps.check.anchorManifestPath;
|
|
1966
|
+
const status = await checkUpdates({
|
|
1967
|
+
...deps.check,
|
|
1968
|
+
anchorManifestPath
|
|
1969
|
+
});
|
|
1970
|
+
if (status.error !== void 0 || status.mode !== "npm") return {
|
|
1971
|
+
...result,
|
|
1972
|
+
ok: false,
|
|
1973
|
+
errorCode: "verify-failed",
|
|
1974
|
+
error: "pnpm exited 0 but the post-run version check could not verify the install"
|
|
1975
|
+
};
|
|
1976
|
+
if (status.packages.some((packageStatus) => {
|
|
1977
|
+
const beforeVersion = before.get(packageStatus.name);
|
|
1978
|
+
if (beforeVersion === void 0 || packageStatus.current === VERSION_UNKNOWN) return false;
|
|
1979
|
+
return packageStatus.current !== beforeVersion;
|
|
1980
|
+
})) return result;
|
|
1981
|
+
if (status.outdated) return {
|
|
1982
|
+
...result,
|
|
1983
|
+
ok: false,
|
|
1984
|
+
errorCode: "stale",
|
|
1985
|
+
error: "pnpm exited 0 but the installed versions did not change"
|
|
1986
|
+
};
|
|
1987
|
+
if (status.packages.some((packageStatus) => packageStatus.latest === void 0)) return {
|
|
1988
|
+
...result,
|
|
1989
|
+
ok: false,
|
|
1990
|
+
errorCode: "verify-failed",
|
|
1991
|
+
error: "pnpm exited 0 but the post-run version check could not verify the install"
|
|
1992
|
+
};
|
|
1993
|
+
return result;
|
|
1994
|
+
}
|
|
1915
1995
|
//#endregion
|
|
1916
1996
|
//#region src/update-routes.ts
|
|
1917
1997
|
/** Route paths (exact matches under /api). */
|
|
@@ -2093,11 +2173,17 @@ function apply(ctx, config) {
|
|
|
2093
2173
|
const apiProxy = ctx.get("apiProxy");
|
|
2094
2174
|
if (apiProxy === void 0) console.warn("remote-web-ui: apiProxy service unavailable — the mobile data channel is disabled");
|
|
2095
2175
|
const requireFromHost = createRequire(import.meta.url);
|
|
2096
|
-
const
|
|
2176
|
+
const resolveAnchorPath = () => resolveAnchorManifest((specifier) => {
|
|
2177
|
+
try {
|
|
2178
|
+
return requireFromHost.resolve(specifier);
|
|
2179
|
+
} catch {
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2097
2183
|
const updateRoutes = makeUpdateRoutes({
|
|
2098
2184
|
fence: (request) => isTrustedApiRequest(request, []),
|
|
2099
2185
|
check: () => checkUpdates({
|
|
2100
|
-
anchorManifestPath,
|
|
2186
|
+
anchorManifestPath: resolveAnchorPath(),
|
|
2101
2187
|
resolve: (specifier) => {
|
|
2102
2188
|
try {
|
|
2103
2189
|
return requireFromHost.resolve(specifier);
|
|
@@ -2108,7 +2194,7 @@ function apply(ctx, config) {
|
|
|
2108
2194
|
fetchLatest: (name) => fetchLatestVersion(name, fetch)
|
|
2109
2195
|
}),
|
|
2110
2196
|
run: async () => {
|
|
2111
|
-
const target = resolveUpdateTarget({ anchorManifestPath });
|
|
2197
|
+
const target = resolveUpdateTarget({ anchorManifestPath: resolveAnchorPath() });
|
|
2112
2198
|
if ("error" in target) {
|
|
2113
2199
|
const code = target.error;
|
|
2114
2200
|
return {
|
|
@@ -2119,9 +2205,22 @@ function apply(ctx, config) {
|
|
|
2119
2205
|
errorCode: code
|
|
2120
2206
|
};
|
|
2121
2207
|
}
|
|
2122
|
-
return
|
|
2123
|
-
|
|
2124
|
-
|
|
2208
|
+
return runUpdateVerified({
|
|
2209
|
+
run: {
|
|
2210
|
+
profileDir: target.profileDir,
|
|
2211
|
+
packages: target.packages
|
|
2212
|
+
},
|
|
2213
|
+
check: {
|
|
2214
|
+
anchorManifestPath: resolveAnchorPath(),
|
|
2215
|
+
resolve: (specifier) => {
|
|
2216
|
+
try {
|
|
2217
|
+
return requireFromHost.resolve(specifier);
|
|
2218
|
+
} catch {
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
},
|
|
2222
|
+
fetchLatest: (name) => fetchLatestVersion(name, fetch)
|
|
2223
|
+
}
|
|
2125
2224
|
});
|
|
2126
2225
|
}
|
|
2127
2226
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAGnD,oGAAoG;AACpG,eAAO,MAAM,cAAc,kMAUjB,CAAA;AAEV,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzD,oEAAoE;AACpE,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,GAAG,WAAW,CAAA;AAE9E,wDAAwD;AACxD,MAAM,WAAW,uBAAuB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACnE,0CAA0C;IAC1C,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAA;IACnF,uCAAuC;IACvC,QAAQ,EAAE,IAAI,CAAA;IACd,4EAA4E;IAC5E,cAAc,EAAE,IAAI,CAAA;IACpB,kFAAkF;IAClF,KAAK,EAAE,SAAS,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,uBAAuB,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAGnD,oGAAoG;AACpG,eAAO,MAAM,cAAc,kMAUjB,CAAA;AAEV,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzD,oEAAoE;AACpE,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,GAAG,WAAW,CAAA;AAE9E,wDAAwD;AACxD,MAAM,WAAW,uBAAuB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACnE,0CAA0C;IAC1C,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAA;IACnF,uCAAuC;IACvC,QAAQ,EAAE,IAAI,CAAA;IACd,4EAA4E;IAC5E,cAAc,EAAE,IAAI,CAAA;IACpB,kFAAkF;IAClF,KAAK,EAAE,SAAS,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,6BAA6B;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,uBAAuB,CAAC,IAAI,CAAC,sCAiHpG;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAA;IACV,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,UAAU,EAAE,OAAO,CAAA;IACnB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAA;IACvB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAA;IACjB,wBAAwB;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,oEAAoE;IACpE,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,kHAAkH;AAClH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG;IAC7C,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,+BAqCA;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG;IAC/C,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAA;IACpB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;CACjB,+BAmCA;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG;IAC9C,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,OAAO,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACzD,+BAsCA"}
|
|
@@ -46,7 +46,9 @@ export function PluginSettingsCard(props) {
|
|
|
46
46
|
: null] }));
|
|
47
47
|
}
|
|
48
48
|
return (_jsxs("li", { className: cardClass, children: [_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null, _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }), open
|
|
49
|
-
? (_jsxs("div", { className: css.body, children: [!state.writable ? _jsx("p", { className: css.readOnly, role: "status", children: props.t('settings.readOnly') }) : null, props.children, _jsxs("div", { className: css.footer, children: [state.failed
|
|
49
|
+
? (_jsxs("div", { className: css.body, children: [!state.writable ? _jsx("p", { className: css.readOnly, role: "status", children: props.t('settings.readOnly') }) : null, props.children, _jsxs("div", { className: css.footer, children: [state.failed
|
|
50
|
+
? (_jsxs("p", { className: css.failed, role: "status", children: [props.t('settings.saveFailed'), state.failedReason ? ' - ' + state.failedReason : ''] }))
|
|
51
|
+
: null, _jsx("button", { type: "button", className: css.discard, disabled: !state.dirty || state.saving, onClick: props.onDiscard, children: props.t('settings.discard') }), _jsx("button", { type: "button", className: css.save, disabled: blocked, onClick: props.onSave, children: props.t(!state.saving ? 'settings.save' : 'settings.saving') })] })] }))
|
|
50
52
|
: null] }));
|
|
51
53
|
}
|
|
52
54
|
/** A staged value field. `numeric` only hints the keypad: which drafts a field accepts is decided by its spec. */
|
|
@@ -77,6 +77,8 @@ function errorMessageOf(t, result) {
|
|
|
77
77
|
case "not-found": return t("update.error.notFound");
|
|
78
78
|
case "link": return t("update.error.link");
|
|
79
79
|
case "pnpm-failed": return t("update.error.pnpmFailed", { code: String(result.exitCode ?? "?") });
|
|
80
|
+
case "stale": return t("update.error.stale");
|
|
81
|
+
case "verify-failed": return t("update.error.verifyFailed");
|
|
80
82
|
default: return result.error ?? t("update.error.unknown");
|
|
81
83
|
}
|
|
82
84
|
}
|
|
@@ -99,6 +99,8 @@ export declare const zh: {
|
|
|
99
99
|
'update.error.notFound': string;
|
|
100
100
|
'update.error.link': string;
|
|
101
101
|
'update.error.pnpmFailed': string;
|
|
102
|
+
'update.error.stale': string;
|
|
103
|
+
'update.error.verifyFailed': string;
|
|
102
104
|
'update.error.unknown': string;
|
|
103
105
|
};
|
|
104
106
|
/** The remote namespace key union. */
|
|
@@ -205,6 +207,8 @@ export declare const en: {
|
|
|
205
207
|
'update.error.notFound': string;
|
|
206
208
|
'update.error.link': string;
|
|
207
209
|
'update.error.pnpmFailed': string;
|
|
210
|
+
'update.error.stale': string;
|
|
211
|
+
'update.error.verifyFailed': string;
|
|
208
212
|
'update.error.unknown': string;
|
|
209
213
|
};
|
|
210
214
|
//# sourceMappingURL=locales.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,mEAAmE;AACnE,eAAO,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwGmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwGsB,CAAA"}
|
|
@@ -101,6 +101,8 @@ export const zh = {
|
|
|
101
101
|
'update.error.notFound': '未找到 dsh-web-ui 聚合包安装,无法更新。',
|
|
102
102
|
'update.error.link': '当前为本地开发模式(链接安装),无法自动更新。',
|
|
103
103
|
'update.error.pnpmFailed': '更新执行失败(pnpm 退出码 {code}),详见下方输出。',
|
|
104
|
+
'update.error.stale': '更新命令已执行,但安装版本未变化。常见原因:pnpm 11 的 minimumReleaseAge 门禁会静默跳过发布不足 24 小时的新版本。请在 profile 目录(~/.dsh/profiles/<profile>)的 pnpm-workspace.yaml 中配置 minimumReleaseAgeExclude(例如 "@linxin666/*")或 minimumReleaseAge: 0 后重新点击更新。',
|
|
105
|
+
'update.error.verifyFailed': '更新命令已执行,但无法核对安装后的版本(可能是 registry 探测失败或安装路径变化)。请查看下方输出,或稍后重试。',
|
|
104
106
|
'update.error.unknown': '更新失败,请重试。',
|
|
105
107
|
};
|
|
106
108
|
/** English dictionary, checked complete against the zh key set. */
|
|
@@ -205,5 +207,7 @@ export const en = {
|
|
|
205
207
|
'update.error.notFound': 'The dsh-web-ui aggregate package is not installed; nothing to update.',
|
|
206
208
|
'update.error.link': 'Local development mode (link install) — automatic update is unavailable.',
|
|
207
209
|
'update.error.pnpmFailed': 'The update failed (pnpm exited with code {code}); see the output below.',
|
|
210
|
+
'update.error.stale': 'The update command ran but the installed versions did not change. Likely cause: the pnpm 11 minimumReleaseAge gate silently skips releases published less than 24 hours ago. Add minimumReleaseAgeExclude (e.g. "@linxin666/*") or set minimumReleaseAge: 0 in pnpm-workspace.yaml under the profile directory (~/.dsh/profiles/<profile>), then run the update again.',
|
|
211
|
+
'update.error.verifyFailed': 'The update command ran but the installed versions could not be verified afterwards (registry probe failed or the install path changed). Inspect the output below, or retry later.',
|
|
208
212
|
'update.error.unknown': 'Update failed; try again.',
|
|
209
213
|
};
|
|
@@ -18,6 +18,14 @@ export type FieldWrite = {
|
|
|
18
18
|
export interface FieldSpec {
|
|
19
19
|
/** Field name inside the namespace section. */
|
|
20
20
|
field: string;
|
|
21
|
+
/**
|
|
22
|
+
* Whether the Host treats this field as a secret and redacts its value from
|
|
23
|
+
* the read-back (role('secret') in the section schema). Redacted secrets are
|
|
24
|
+
* never compared against the draft on save; the field lands when the scope
|
|
25
|
+
* reports the write succeeded (its secret-set marker under the bridge), so
|
|
26
|
+
* a successful secret save is not misreported as failed.
|
|
27
|
+
*/
|
|
28
|
+
secret?: boolean;
|
|
21
29
|
/** Render a stored value as draft text; the empty string when the section carries none. */
|
|
22
30
|
format: (value: unknown) => string;
|
|
23
31
|
/**
|
|
@@ -57,6 +65,12 @@ export interface CardShell {
|
|
|
57
65
|
saving: boolean;
|
|
58
66
|
/** Whether the last save did not land as staged; cleared by the next edit or save. */
|
|
59
67
|
failed: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The rejection code/message the Host returned for the last failed save,
|
|
70
|
+
* surfaced next to the generic failure text. Undefined while no save has
|
|
71
|
+
* failed (or the failure carried no server reason).
|
|
72
|
+
*/
|
|
73
|
+
failedReason?: string;
|
|
60
74
|
}
|
|
61
75
|
/** The write actions the card's slot entry injects. */
|
|
62
76
|
export interface CardActions {
|
|
@@ -69,6 +83,37 @@ export interface CardActions {
|
|
|
69
83
|
/** Drop every staged edit. */
|
|
70
84
|
discard: () => void;
|
|
71
85
|
}
|
|
86
|
+
/** One durable write a batched settings scope performs. */
|
|
87
|
+
export interface BatchedWrite {
|
|
88
|
+
/** Field this entry writes. */
|
|
89
|
+
field: string;
|
|
90
|
+
/** set stores a value; unset drops the leaf. */
|
|
91
|
+
op: 'set' | 'unset';
|
|
92
|
+
/** Value for op set (absent for unset). */
|
|
93
|
+
value?: unknown;
|
|
94
|
+
}
|
|
95
|
+
/** Per-field outcome of one batched scope write. */
|
|
96
|
+
export interface BatchedFieldResult {
|
|
97
|
+
/** Field this entry writes. */
|
|
98
|
+
field: string;
|
|
99
|
+
/** Whether the Host accepted this field's write (per the read-back view). */
|
|
100
|
+
landed: boolean;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Result of a batched scope write. The bridge scope posts every planned write
|
|
104
|
+
* in one /mutate so the Host validate hook judges baseURL+model together; a
|
|
105
|
+
* batched refusal fails the whole save rather than per-field.
|
|
106
|
+
*/
|
|
107
|
+
export interface BatchResult {
|
|
108
|
+
/** Whether the whole mutate was accepted. */
|
|
109
|
+
ok: boolean;
|
|
110
|
+
/** Per-field success, in the request order (always present when ok). */
|
|
111
|
+
fields: BatchedFieldResult[];
|
|
112
|
+
/** Host rejection code (mutate refused). */
|
|
113
|
+
code?: string;
|
|
114
|
+
/** Host rejection message (mutate refused). */
|
|
115
|
+
message?: string;
|
|
116
|
+
}
|
|
72
117
|
/** Constraints a numeric field's accepted drafts must satisfy, mirroring the host schema. */
|
|
73
118
|
export interface NumberConstraints {
|
|
74
119
|
/** The accepted value must be a whole number. */
|
|
@@ -80,6 +125,13 @@ export interface NumberConstraints {
|
|
|
80
125
|
export declare function numberField(field: string, constraints?: NumberConstraints): FieldSpec;
|
|
81
126
|
/** A free-text field. An empty draft clears the field. */
|
|
82
127
|
export declare function textField(field: string): FieldSpec;
|
|
128
|
+
/**
|
|
129
|
+
* A free-text field the Host treats as a secret and redacts from the read-back
|
|
130
|
+
* (role('secret') in the section schema). The card still edits it like text,
|
|
131
|
+
* but a save never compares the redacted value back and relies on the scope
|
|
132
|
+
* reporting the write landed.
|
|
133
|
+
*/
|
|
134
|
+
export declare function secretField(field: string): FieldSpec;
|
|
83
135
|
/** A boolean field, edited through true/false draft text. */
|
|
84
136
|
export declare function booleanField(field: string): FieldSpec;
|
|
85
137
|
/** An enumerated string field; only the listed choices are accepted. An empty draft clears the field. */
|
|
@@ -99,6 +151,7 @@ export declare class CardForm<T> {
|
|
|
99
151
|
private readonly listeners;
|
|
100
152
|
private saving;
|
|
101
153
|
private failed;
|
|
154
|
+
private failedReason;
|
|
102
155
|
/** @param scope - the bound settings scope for this card's namespace. */
|
|
103
156
|
constructor(scope: SettingsScope<T>, specs: FieldSpec[]);
|
|
104
157
|
/** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
|
|
@@ -111,9 +164,18 @@ export declare class CardForm<T> {
|
|
|
111
164
|
actions(): CardActions;
|
|
112
165
|
/**
|
|
113
166
|
* Write every staged edit, then re-seed from what the Host accepted.
|
|
167
|
+
*
|
|
168
|
+
* When the scope carries the optional batch surface (the dsh-web-ui
|
|
169
|
+
* bridge scope), every planned write rides one mutation so cross-field
|
|
170
|
+
* validate hooks (baseURL+model) judge the batch as a unit instead of
|
|
171
|
+
* deadlocking on per-field writes. Otherwise the per-field loop runs.
|
|
172
|
+
* A field lands only when the Host reports it held the staged value; a
|
|
173
|
+
* landed field's draft is dropped, a failed one stays staged for the user.
|
|
114
174
|
* @returns settlement after every write and the read-back.
|
|
115
175
|
*/
|
|
116
176
|
save(): Promise<void>;
|
|
177
|
+
/** The scope's batch surface when it supports one; undefined conservatively otherwise. */
|
|
178
|
+
private batchedScope;
|
|
117
179
|
/**
|
|
118
180
|
* Every staged edit a save would write. An entry whose draft is not a value
|
|
119
181
|
* its field accepts carries no write: the form is still dirty, and the save
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAyB,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAGjH,yEAAyE;AACzE,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAErB,0EAA0E;AAC1E,MAAM,WAAW,SAAS;IACxB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,2FAA2F;IAC3F,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;IAClC;;;OAGG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAA;CAChD;AAED,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,UAAU,EAAE,OAAO,CAAA;IACnB,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,oDAAoD;AACpD,MAAM,WAAW,SAAS;IACxB,4EAA4E;IAC5E,SAAS,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB,gDAAgD;IAChD,QAAQ,EAAE,OAAO,CAAA;IACjB,4DAA4D;IAC5D,KAAK,EAAE,OAAO,CAAA;IACd,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAA;IAChB,2CAA2C;IAC3C,MAAM,EAAE,OAAO,CAAA;IACf,sFAAsF;IACtF,MAAM,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
|
|
@@ -40,6 +40,15 @@ export function textField(field) {
|
|
|
40
40
|
},
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* A free-text field the Host treats as a secret and redacts from the read-back
|
|
45
|
+
* (role('secret') in the section schema). The card still edits it like text,
|
|
46
|
+
* but a save never compares the redacted value back and relies on the scope
|
|
47
|
+
* reporting the write landed.
|
|
48
|
+
*/
|
|
49
|
+
export function secretField(field) {
|
|
50
|
+
return { ...textField(field), secret: true };
|
|
51
|
+
}
|
|
43
52
|
/** A boolean field, edited through true/false draft text. */
|
|
44
53
|
export function booleanField(field) {
|
|
45
54
|
return {
|
|
@@ -84,6 +93,7 @@ export class CardForm {
|
|
|
84
93
|
listeners = new Set();
|
|
85
94
|
saving = false;
|
|
86
95
|
failed = false;
|
|
96
|
+
failedReason;
|
|
87
97
|
/** @param scope - the bound settings scope for this card's namespace. */
|
|
88
98
|
constructor(scope, specs) {
|
|
89
99
|
this.scope = scope;
|
|
@@ -108,6 +118,7 @@ export class CardForm {
|
|
|
108
118
|
invalid: plan.some(item => item.run === undefined),
|
|
109
119
|
saving: this.saving,
|
|
110
120
|
failed: this.failed,
|
|
121
|
+
...this.failedReason === undefined ? {} : { failedReason: this.failedReason },
|
|
111
122
|
};
|
|
112
123
|
}
|
|
113
124
|
/** Read one field's state from the effective section and its staged draft. */
|
|
@@ -137,37 +148,68 @@ export class CardForm {
|
|
|
137
148
|
return;
|
|
138
149
|
this.staged.clear();
|
|
139
150
|
this.failed = false;
|
|
151
|
+
this.failedReason = undefined;
|
|
140
152
|
this.publish();
|
|
141
153
|
},
|
|
142
154
|
};
|
|
143
155
|
}
|
|
144
156
|
/**
|
|
145
157
|
* Write every staged edit, then re-seed from what the Host accepted.
|
|
158
|
+
*
|
|
159
|
+
* When the scope carries the optional batch surface (the dsh-web-ui
|
|
160
|
+
* bridge scope), every planned write rides one mutation so cross-field
|
|
161
|
+
* validate hooks (baseURL+model) judge the batch as a unit instead of
|
|
162
|
+
* deadlocking on per-field writes. Otherwise the per-field loop runs.
|
|
163
|
+
* A field lands only when the Host reports it held the staged value; a
|
|
164
|
+
* landed field's draft is dropped, a failed one stays staged for the user.
|
|
146
165
|
* @returns settlement after every write and the read-back.
|
|
147
166
|
*/
|
|
148
167
|
async save() {
|
|
149
168
|
const plan = this.plan();
|
|
150
|
-
const
|
|
151
|
-
if (plan.length === 0 || this.saving ||
|
|
169
|
+
const valid = plan.filter(item => item.run !== undefined);
|
|
170
|
+
if (plan.length === 0 || this.saving || valid.length !== plan.length)
|
|
152
171
|
return;
|
|
172
|
+
const plannedWrites = valid.map(item => item.op);
|
|
153
173
|
// Snapshot the fields this save writes, so edits staged while it is in
|
|
154
174
|
// flight survive: only the staged keys this save actually wrote are cleared.
|
|
155
175
|
const fields = new Set(plan.map(item => item.field));
|
|
156
176
|
this.saving = true;
|
|
157
177
|
this.failed = false;
|
|
178
|
+
this.failedReason = undefined;
|
|
158
179
|
this.publish();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
180
|
+
const landed = new Set();
|
|
181
|
+
const batch = this.batchedScope();
|
|
182
|
+
if (batch !== undefined) {
|
|
183
|
+
const result = await batch.mutate(plannedWrites);
|
|
184
|
+
if (result.ok) {
|
|
185
|
+
for (const field of result.fields) {
|
|
186
|
+
if (field.landed)
|
|
187
|
+
landed.add(field.field);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
this.failedReason = result.message;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
for (const item of valid) {
|
|
196
|
+
if (await item.run())
|
|
197
|
+
landed.add(item.field);
|
|
198
|
+
}
|
|
162
199
|
}
|
|
163
|
-
|
|
164
|
-
|
|
200
|
+
for (const field of fields) {
|
|
201
|
+
if (landed.has(field))
|
|
165
202
|
this.staged.delete(field);
|
|
166
203
|
}
|
|
167
204
|
this.saving = false;
|
|
168
|
-
this.failed =
|
|
205
|
+
this.failed = landed.size !== fields.size;
|
|
169
206
|
this.publish();
|
|
170
207
|
}
|
|
208
|
+
/** The scope's batch surface when it supports one; undefined conservatively otherwise. */
|
|
209
|
+
batchedScope() {
|
|
210
|
+
const candidate = this.scope;
|
|
211
|
+
return typeof candidate?.mutate === 'function' ? candidate : undefined;
|
|
212
|
+
}
|
|
171
213
|
/**
|
|
172
214
|
* Every staged edit a save would write. An entry whose draft is not a value
|
|
173
215
|
* its field accepts carries no write: the form is still dirty, and the save
|
|
@@ -181,18 +223,18 @@ export class CardForm {
|
|
|
181
223
|
const spec = this.specOf(field);
|
|
182
224
|
if (staged.clear) {
|
|
183
225
|
if (this.stored(field))
|
|
184
|
-
plan.push({ field, run: () => this.clear(field) });
|
|
226
|
+
plan.push({ field, op: { field, op: 'unset' }, run: () => this.clear(field) });
|
|
185
227
|
continue;
|
|
186
228
|
}
|
|
187
229
|
if (staged.text === spec.format(this.sectionValue(field)))
|
|
188
230
|
continue;
|
|
189
231
|
const write = spec.parse(staged.text);
|
|
190
232
|
if (write === undefined)
|
|
191
|
-
plan.push({ field, run: undefined });
|
|
233
|
+
plan.push({ field, op: { field, op: 'unset' }, run: undefined });
|
|
192
234
|
else if (write.kind === 'clear')
|
|
193
|
-
plan.push({ field, run: () => this.clear(field) });
|
|
235
|
+
plan.push({ field, op: { field, op: 'unset' }, run: () => this.clear(field) });
|
|
194
236
|
else
|
|
195
|
-
plan.push({ field, run: () => this.store(field, write.value) });
|
|
237
|
+
plan.push({ field, op: { field, op: 'set', value: write.value }, run: () => this.store(field, write.value) });
|
|
196
238
|
}
|
|
197
239
|
return plan;
|
|
198
240
|
}
|
|
@@ -202,11 +244,18 @@ export class CardForm {
|
|
|
202
244
|
}
|
|
203
245
|
async store(field, value) {
|
|
204
246
|
await this.scope.set(field, value);
|
|
247
|
+
// A redacted secret never appears in the user layer read-back; judging it
|
|
248
|
+
// by value would misreport a successful secret save as failed. The bridge
|
|
249
|
+
// reports secret writes through its secret-set markers (batch path); on
|
|
250
|
+
// the per-field path the scope resolved, so the write is landed.
|
|
251
|
+
if (this.specOf(field).secret)
|
|
252
|
+
return true;
|
|
205
253
|
return this.userLayer()?.[field] === value;
|
|
206
254
|
}
|
|
207
255
|
stage(field, edit) {
|
|
208
256
|
this.staged.set(field, edit);
|
|
209
257
|
this.failed = false;
|
|
258
|
+
this.failedReason = undefined;
|
|
210
259
|
this.publish();
|
|
211
260
|
}
|
|
212
261
|
specOf(field) {
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,CAAC,MAAM,aAAa,CAAA;AAmB3B,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;WAKG;QACH,UAAU,CACR,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GACrC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAC9B;CACF;AAED,iCAAiC;AACjC,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAA4B,CAAA;AAE/C;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,uDAAqC,CAAA;AAElF,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAU3B,CAAA;AAyBF;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,CAAC,MAAM,aAAa,CAAA;AAmB3B,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;WAKG;QACH,UAAU,CACR,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GACrC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAC9B;CACF;AAED,iCAAiC;AACjC,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAA4B,CAAA;AAE/C;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,uDAAqC,CAAA;AAElF,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAU3B,CAAA;AAyBF;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAkOzD"}
|
package/lib/types/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { makeMobileRoutes } from "./mobile-routes.js";
|
|
|
18
18
|
import { makeMobileApiRoutes } from "./mobile-api.js";
|
|
19
19
|
import { lanIPv4Addresses } from "./lan.js";
|
|
20
20
|
import { TunnelManager } from "./tunnel.js";
|
|
21
|
-
import { checkUpdates, fetchLatestVersion, resolveAnchorManifest, resolveUpdateTarget,
|
|
21
|
+
import { checkUpdates, fetchLatestVersion, resolveAnchorManifest, resolveUpdateTarget, runUpdateVerified, } from "./update.js";
|
|
22
22
|
import { makeUpdateRoutes } from "./update-routes.js";
|
|
23
23
|
/** Stable cordis plugin name. */
|
|
24
24
|
export const name = 'remote-web-ui';
|
|
@@ -152,19 +152,27 @@ export function apply(ctx, config) {
|
|
|
152
152
|
}
|
|
153
153
|
// ── remote update ────────────────────────────────────────────────────────
|
|
154
154
|
// The dsh-web-ui self-update surface: probe the npm registry for family
|
|
155
|
-
// releases and run `pnpm update` in the owning profile. Resolutions
|
|
156
|
-
// on the host process's own module graph, so the update always
|
|
157
|
-
// profile the running web GUI was booted from. The
|
|
158
|
-
//
|
|
159
|
-
//
|
|
155
|
+
// releases and run `pnpm update --latest` in the owning profile. Resolutions
|
|
156
|
+
// anchor on the host process's own module graph, so the update always
|
|
157
|
+
// targets the profile the running web GUI was booted from. The anchor path
|
|
158
|
+
// is re-resolved per operation: pnpm removes the old version's .pnpm
|
|
159
|
+
// directory on update, so a boot-time captured path would fail to read
|
|
160
|
+
// after a successful update; versions are re-read from disk per check.
|
|
160
161
|
const requireFromHost = createRequire(import.meta.url);
|
|
161
|
-
const
|
|
162
|
+
const resolveAnchorPath = () => resolveAnchorManifest(specifier => {
|
|
163
|
+
try {
|
|
164
|
+
return requireFromHost.resolve(specifier);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
162
170
|
const updateRoutes = makeUpdateRoutes({
|
|
163
171
|
// Control endpoints are host-surface only: a LAN/phone origin must never
|
|
164
172
|
// trigger a real install on this machine.
|
|
165
173
|
fence: request => isTrustedApiRequest(request, []),
|
|
166
174
|
check: () => checkUpdates({
|
|
167
|
-
anchorManifestPath,
|
|
175
|
+
anchorManifestPath: resolveAnchorPath(),
|
|
168
176
|
resolve: specifier => {
|
|
169
177
|
try {
|
|
170
178
|
return requireFromHost.resolve(specifier);
|
|
@@ -176,7 +184,7 @@ export function apply(ctx, config) {
|
|
|
176
184
|
fetchLatest: name => fetchLatestVersion(name, fetch),
|
|
177
185
|
}),
|
|
178
186
|
run: async () => {
|
|
179
|
-
const target = resolveUpdateTarget({ anchorManifestPath });
|
|
187
|
+
const target = resolveUpdateTarget({ anchorManifestPath: resolveAnchorPath() });
|
|
180
188
|
if ('error' in target) {
|
|
181
189
|
const code = target.error;
|
|
182
190
|
return {
|
|
@@ -187,7 +195,25 @@ export function apply(ctx, config) {
|
|
|
187
195
|
errorCode: code,
|
|
188
196
|
};
|
|
189
197
|
}
|
|
190
|
-
|
|
198
|
+
// Verify the versions actually moved after a green pnpm exit: the pnpm
|
|
199
|
+
// 11 minimumReleaseAge gate can silently keep the installed versions
|
|
200
|
+
// (same-day releases), which a plain exit-0 check would report as
|
|
201
|
+
// success — the user then restarts and nothing changed.
|
|
202
|
+
return runUpdateVerified({
|
|
203
|
+
run: { profileDir: target.profileDir, packages: target.packages },
|
|
204
|
+
check: {
|
|
205
|
+
anchorManifestPath: resolveAnchorPath(),
|
|
206
|
+
resolve: specifier => {
|
|
207
|
+
try {
|
|
208
|
+
return requireFromHost.resolve(specifier);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
fetchLatest: name => fetchLatestVersion(name, fetch),
|
|
215
|
+
},
|
|
216
|
+
});
|
|
191
217
|
},
|
|
192
218
|
});
|
|
193
219
|
const routes = [
|