@dsh-workbench/core 0.3.0 → 0.3.1
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.md +6 -2
- package/bin/dsh-workbench +17 -15
- package/client.js +359 -64
- package/dist/client/client.body.js +359 -64
- package/dist/client/index.js +359 -64
- package/dist/host/index.js +443 -385
- package/dist/types/app/client.d.ts +3 -0
- package/dist/types/app/layouts/rightbar/BoardGrid.d.ts +23 -0
- package/dist/types/app/layouts/rightbar/board-columns.d.ts +17 -4
- package/dist/types/app/layouts/rightbar/rightbar-width.d.ts +16 -0
- package/dist/types/app/layouts/rightbar/rightbar-width.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/use-board-split.d.ts +8 -0
- package/dist/types/modules/panels/model/diy-report.d.ts +13 -2
- package/dist/types/shared/api/host/diy-workshop.d.ts +13 -0
- package/dist/types/shared/api/host/runtime-seed.d.ts +0 -2
- package/docs/HANDOFF.md +11 -1
- package/docs/OPERATIONS.md +43 -0
- package/package.json +2 -2
- package/scripts/seed-workshop.sh +461 -13
- package/skills/workbench-module-diy/BUILD.md +6 -1
package/dist/host/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import fs, { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync,
|
|
2
|
+
import fs, { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path, { basename, delimiter, dirname, join, resolve, sep } from "node:path";
|
|
4
4
|
import { deflateSync, inflateSync } from "node:zlib";
|
|
5
|
-
import os from "node:os";
|
|
6
5
|
import { spawn } from "node:child_process";
|
|
7
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import os from "node:os";
|
|
8
8
|
//#region ../shared/dist/config/constants.js
|
|
9
9
|
var PLUGIN_ID = "@dsh-workbench/core";
|
|
10
10
|
var RELEASE_SIBLINGS = ["@dsh-workbench/shared", "@dsh-workbench/module-tools"];
|
|
@@ -2965,6 +2965,43 @@ function inWorkshopSession(ctx, workspace) {
|
|
|
2965
2965
|
const root = workshopPathOf(ctx);
|
|
2966
2966
|
return root !== null && workspace !== null && insideWorkshop(root, workspace);
|
|
2967
2967
|
}
|
|
2968
|
+
function workshopSyncOf(workshopRoot) {
|
|
2969
|
+
let parsed;
|
|
2970
|
+
try {
|
|
2971
|
+
parsed = JSON.parse(readFileSync(join(workshopRoot, ".sync.json"), "utf8"));
|
|
2972
|
+
} catch {
|
|
2973
|
+
return;
|
|
2974
|
+
}
|
|
2975
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
2976
|
+
const record = parsed;
|
|
2977
|
+
const state = asText$2(record["state"]);
|
|
2978
|
+
if (state !== "ok" && state !== "syncing" && state !== "failed") return void 0;
|
|
2979
|
+
const reason = asText$2(record["reason"]);
|
|
2980
|
+
return {
|
|
2981
|
+
state,
|
|
2982
|
+
version: asText$2(record["version"]),
|
|
2983
|
+
at: asText$2(record["at"]),
|
|
2984
|
+
...reason === "" ? {} : { reason }
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2987
|
+
function workshopToolchainOf(workshopRoot) {
|
|
2988
|
+
const versionOf = (name) => {
|
|
2989
|
+
try {
|
|
2990
|
+
return asText$2(JSON.parse(readFileSync(join(workshopRoot, "node_modules", "@dsh-workbench", name, "package.json"), "utf8")).version);
|
|
2991
|
+
} catch {
|
|
2992
|
+
return "";
|
|
2993
|
+
}
|
|
2994
|
+
};
|
|
2995
|
+
const core = versionOf("core");
|
|
2996
|
+
const moduleTools = versionOf("module-tools");
|
|
2997
|
+
const shared = versionOf("shared");
|
|
2998
|
+
if (core === "" || moduleTools === "" || shared === "") return void 0;
|
|
2999
|
+
return {
|
|
3000
|
+
core,
|
|
3001
|
+
moduleTools,
|
|
3002
|
+
shared
|
|
3003
|
+
};
|
|
3004
|
+
}
|
|
2968
3005
|
//#endregion
|
|
2969
3006
|
//#region src/modules/panels/unit.ts
|
|
2970
3007
|
var PANELS_LIST_TOOL = "workbench_panels_list";
|
|
@@ -5761,134 +5798,425 @@ function moduleBuildFacts(dir) {
|
|
|
5761
5798
|
};
|
|
5762
5799
|
}
|
|
5763
5800
|
//#endregion
|
|
5764
|
-
//#region src/
|
|
5765
|
-
function
|
|
5766
|
-
const
|
|
5767
|
-
const
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5801
|
+
//#region src/shared/api/host/runtime-seed.ts
|
|
5802
|
+
function runtimeSeedPaths(root) {
|
|
5803
|
+
const opencliRoot = join(root, "opencli");
|
|
5804
|
+
const opencliBinDir = join(opencliRoot, "bin");
|
|
5805
|
+
const workshop = join(root, "diy");
|
|
5806
|
+
return {
|
|
5807
|
+
root,
|
|
5808
|
+
entryDir: join(root, "bin"),
|
|
5809
|
+
opencliRoot,
|
|
5810
|
+
opencliBinDir,
|
|
5811
|
+
opencliBin: join(opencliBinDir, "opencli"),
|
|
5812
|
+
opencliHome: join(opencliRoot, "home"),
|
|
5813
|
+
workshop,
|
|
5814
|
+
workshopPackages: join(workshop, "packages"),
|
|
5815
|
+
workshopManifest: join(workshop, "package.json")
|
|
5771
5816
|
};
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5817
|
+
}
|
|
5818
|
+
function runtimeSeedRootOf(ctx) {
|
|
5819
|
+
const homePath = ctx.get("dshHomePath");
|
|
5820
|
+
if (typeof homePath !== "function") return null;
|
|
5821
|
+
try {
|
|
5822
|
+
return homePath("workbench");
|
|
5823
|
+
} catch {
|
|
5824
|
+
return null;
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
function packageRootOf(moduleUrl) {
|
|
5828
|
+
return dirname(dirname(dirname(fileURLToPath(moduleUrl))));
|
|
5829
|
+
}
|
|
5830
|
+
function isInstalledCopy(packageRoot) {
|
|
5831
|
+
return packageRoot.split(sep).includes("node_modules");
|
|
5832
|
+
}
|
|
5833
|
+
function opencliWrapperScript(packageRoot, home) {
|
|
5834
|
+
const script = join(packageRoot, "bin", "opencli-isolated");
|
|
5835
|
+
return `#!/bin/sh\nexec env DSH_HOME=${quoteShellArg(home)} bash ${quoteShellArg(script)} "$@"\n`;
|
|
5836
|
+
}
|
|
5837
|
+
function workshopSeedPlan(packageRoot, home, root) {
|
|
5838
|
+
const script = join(packageRoot, "scripts", "seed-workshop.sh");
|
|
5839
|
+
const env = {
|
|
5840
|
+
DSH_HOME: home,
|
|
5841
|
+
DSH_WB_SHELL_ROOT: packageRoot
|
|
5776
5842
|
};
|
|
5777
|
-
|
|
5843
|
+
const log = join(root, "seed.log");
|
|
5778
5844
|
return {
|
|
5779
|
-
|
|
5780
|
-
|
|
5845
|
+
entry: {
|
|
5846
|
+
file: "bash",
|
|
5847
|
+
args: [script, "entry"],
|
|
5848
|
+
env,
|
|
5849
|
+
log
|
|
5850
|
+
},
|
|
5851
|
+
full: {
|
|
5852
|
+
file: "bash",
|
|
5853
|
+
args: [script],
|
|
5854
|
+
env,
|
|
5855
|
+
log
|
|
5856
|
+
}
|
|
5781
5857
|
};
|
|
5782
5858
|
}
|
|
5783
|
-
function
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5859
|
+
function runDetached(command, onError, spawnImpl = spawn) {
|
|
5860
|
+
let fd = null;
|
|
5861
|
+
try {
|
|
5862
|
+
fd = openSync(command.log, "a");
|
|
5863
|
+
const child = spawnImpl(command.file, [...command.args], {
|
|
5864
|
+
env: {
|
|
5865
|
+
...process.env,
|
|
5866
|
+
...command.env
|
|
5867
|
+
},
|
|
5868
|
+
detached: true,
|
|
5869
|
+
stdio: [
|
|
5870
|
+
"ignore",
|
|
5871
|
+
fd,
|
|
5872
|
+
fd
|
|
5873
|
+
]
|
|
5874
|
+
});
|
|
5875
|
+
child.on("error", (error) => onError(`自举脚本没跑起来(${command.file}):${String(error)}`));
|
|
5876
|
+
child.unref();
|
|
5877
|
+
} catch (error) {
|
|
5878
|
+
onError(`自举脚本没跑起来(${command.file}):${String(error)}`);
|
|
5879
|
+
} finally {
|
|
5880
|
+
if (fd !== null) closeQuietly(fd);
|
|
5881
|
+
}
|
|
5787
5882
|
}
|
|
5788
|
-
function
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
return `${String(Math.round(minutes / 60))} 小时前`;
|
|
5883
|
+
function closeQuietly(fd) {
|
|
5884
|
+
try {
|
|
5885
|
+
closeSync(fd);
|
|
5886
|
+
} catch {
|
|
5887
|
+
return;
|
|
5888
|
+
}
|
|
5795
5889
|
}
|
|
5796
|
-
function
|
|
5797
|
-
|
|
5890
|
+
function prependPath(entryDir, env = process.env) {
|
|
5891
|
+
const parts = (env.PATH ?? "").split(delimiter).filter((part) => part !== "");
|
|
5892
|
+
if (parts.includes(entryDir)) return false;
|
|
5893
|
+
env.PATH = [entryDir, ...parts].join(delimiter);
|
|
5894
|
+
return true;
|
|
5798
5895
|
}
|
|
5799
|
-
function
|
|
5800
|
-
const
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5896
|
+
function seedRuntimeFiles(options) {
|
|
5897
|
+
const paths = runtimeSeedPaths(options.root);
|
|
5898
|
+
const done = [];
|
|
5899
|
+
try {
|
|
5900
|
+
for (const dir of [
|
|
5901
|
+
paths.entryDir,
|
|
5902
|
+
paths.opencliBinDir,
|
|
5903
|
+
paths.opencliHome,
|
|
5904
|
+
paths.workshopPackages
|
|
5905
|
+
]) mkdirSync(dir, { recursive: true });
|
|
5906
|
+
const wanted = opencliWrapperScript(options.packageRoot, options.home);
|
|
5907
|
+
if (readText$1(paths.opencliBin) !== wanted) {
|
|
5908
|
+
rmSync(paths.opencliBin, { force: true });
|
|
5909
|
+
writeFileSync(paths.opencliBin, wanted, "utf8");
|
|
5910
|
+
chmodSync(paths.opencliBin, 493);
|
|
5911
|
+
done.push(paths.opencliBin);
|
|
5912
|
+
}
|
|
5913
|
+
} catch (error) {
|
|
5914
|
+
options.log?.(`自举没做完:${String(error)}`);
|
|
5915
|
+
}
|
|
5916
|
+
return done;
|
|
5807
5917
|
}
|
|
5808
|
-
function
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
const report = row.report;
|
|
5815
|
-
if (report === void 0) return [title, ` 没上报:卡片没渲染出来,或者用户那一页还没刷新到带这份补丁的版本`].join("\n");
|
|
5816
|
-
const reads = report.cards.slice(0, 6).map((one) => ` 数据 · ${readText$1(one, now)}`);
|
|
5817
|
-
const more = report.cards.length > 6 ? [` 数据 · …还有 ${String(report.cards.length - 6)} 条`] : [];
|
|
5818
|
-
const crash = report.crash === void 0 ? " 崩过:没有" : ` 崩过:是 —— ${report.crash.reason}(${stampOf(report.crash.at)} · 共 ${String(report.crash.count)} 次)`;
|
|
5819
|
-
return [
|
|
5820
|
-
title,
|
|
5821
|
-
...reads,
|
|
5822
|
-
...more,
|
|
5823
|
-
crash
|
|
5824
|
-
].join("\n");
|
|
5825
|
-
});
|
|
5826
|
-
return body.length === 0 ? `${head}${note}` : `${head}${note}\n${body.join("\n")}`;
|
|
5918
|
+
function readText$1(file) {
|
|
5919
|
+
try {
|
|
5920
|
+
return readFileSync(file, "utf8");
|
|
5921
|
+
} catch {
|
|
5922
|
+
return null;
|
|
5923
|
+
}
|
|
5827
5924
|
}
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5925
|
+
//#endregion
|
|
5926
|
+
//#region src/shared/api/host/self-update.ts
|
|
5927
|
+
function runningVersionOf(moduleUrl) {
|
|
5928
|
+
try {
|
|
5929
|
+
const read = JSON.parse(readFileSync(new URL("../../package.json", moduleUrl), "utf8"));
|
|
5930
|
+
return typeof read.version === "string" && read.version !== "" ? read.version : void 0;
|
|
5931
|
+
} catch {
|
|
5932
|
+
return;
|
|
5933
|
+
}
|
|
5934
|
+
}
|
|
5935
|
+
function selfUpdatePaths(dshHome) {
|
|
5936
|
+
const dir = join(dshHome, "workbench");
|
|
5832
5937
|
return {
|
|
5833
|
-
unit,
|
|
5834
|
-
name,
|
|
5835
5938
|
dir,
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
artifacts: facts.artifacts,
|
|
5840
|
-
stale: facts.stale,
|
|
5841
|
-
health,
|
|
5842
|
-
next
|
|
5939
|
+
script: join(dir, "self-update.sh"),
|
|
5940
|
+
marker: join(dir, "self-update.json"),
|
|
5941
|
+
log: join(dir, "self-update.log")
|
|
5843
5942
|
};
|
|
5844
5943
|
}
|
|
5845
|
-
function
|
|
5846
|
-
|
|
5847
|
-
return `工坊:${packagesDir}\n${lines.map((one) => {
|
|
5848
|
-
const head = `—— ${one.unit}(${one.name})`;
|
|
5849
|
-
const facts = ` 装:${one.installed ? "是" : "没有"} · 面板认得:${one.known ? "是" : "不是"} · 这个项目上开着:${one.open ? "是" : "没有"}`;
|
|
5850
|
-
const artifacts = one.artifacts.length === 0 ? " 产物:没有" : ` 产物:${one.artifacts.map((item) => `${item.path}${item.at === null ? "(缺)" : ` ${item.at.slice(11, 19)}Z`}`).join(" · ")} · 源码比产物新:${one.stale ? "是" : "没有"}`;
|
|
5851
|
-
return [
|
|
5852
|
-
head,
|
|
5853
|
-
facts,
|
|
5854
|
-
` 起得来:${healthText(one.health)}`,
|
|
5855
|
-
artifacts,
|
|
5856
|
-
` 下一步:${one.next}`
|
|
5857
|
-
].join("\n");
|
|
5858
|
-
}).join("\n")}`;
|
|
5859
|
-
}
|
|
5860
|
-
function renderList(packagesDir, modules) {
|
|
5861
|
-
if (modules.length === 0) return `工坊里还没有模块(${packagesDir})—— 在里面造一个:pnpm exec dsh-wb-module init <名字> --dir <工坊>/packages/module-<名字> --template diy`;
|
|
5862
|
-
const lines = modules.map((one) => `${one.unit} · ${one.installed ? "已装" : "没装"} · ${one.name}` + (one.description === "" ? "" : `\n${" ".repeat(one.unit.length)} ${one.description}`));
|
|
5863
|
-
const footer = modules.filter((one) => !one.installed).length === 0 ? "都已装过 —— **不要再装**:改源码 + dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新一下页面)。" : "装:workbench_diy_install(unit=<上面那个 unit>)";
|
|
5864
|
-
return `工坊:${packagesDir}\n${lines.join("\n")}\n\n${footer}`;
|
|
5944
|
+
function recoveryCommand(input) {
|
|
5945
|
+
return `dsh plugin --profile ${input.profile} add ${input.pkg}@${input.version}`;
|
|
5865
5946
|
}
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5947
|
+
function selfUpdateMarker(request, state, version, at) {
|
|
5948
|
+
return {
|
|
5949
|
+
...request,
|
|
5950
|
+
state,
|
|
5951
|
+
version,
|
|
5952
|
+
at
|
|
5953
|
+
};
|
|
5954
|
+
}
|
|
5955
|
+
function quoted(value) {
|
|
5956
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
5957
|
+
}
|
|
5958
|
+
function selfUpdateScript(request, paths) {
|
|
5959
|
+
const rescue = recoveryCommand({
|
|
5960
|
+
profile: request.profile,
|
|
5961
|
+
pkg: request.pkg,
|
|
5962
|
+
version: request.from
|
|
5963
|
+
});
|
|
5964
|
+
const lastsOne = recoveryCommand({
|
|
5965
|
+
profile: request.profile,
|
|
5966
|
+
pkg: request.pkg,
|
|
5967
|
+
version: request.to === "" ? "latest" : request.to
|
|
5968
|
+
});
|
|
5969
|
+
const target = request.to === "" ? "latest" : request.to;
|
|
5970
|
+
const landed = request.to === "" ? "is_other" : "is_to";
|
|
5971
|
+
return `#!/usr/bin/env bash
|
|
5972
|
+
set -uo pipefail
|
|
5973
|
+
DSH_HOME=${quoted(request.dshHome)}
|
|
5974
|
+
PKG=${quoted(request.pkg)}
|
|
5975
|
+
PROFILE=${quoted(request.profile)}
|
|
5976
|
+
FROM=${quoted(request.from)}
|
|
5977
|
+
TO=${quoted(target)}
|
|
5978
|
+
MARKER=${quoted(paths.marker)}
|
|
5979
|
+
LOG=${quoted(paths.log)}
|
|
5980
|
+
PROFILE_DIR="$DSH_HOME/profiles/$PROFILE"
|
|
5981
|
+
say() { printf '%s %s\\n' "$(date '+%F %T')" "$*" >>"$LOG"; }
|
|
5982
|
+
on_disk() {
|
|
5983
|
+
node -e 'const p=process.argv[1];try{process.stdout.write(require(p).version)}catch(e){}' \\
|
|
5984
|
+
"$PROFILE_DIR/node_modules/$PKG/package.json" 2>/dev/null
|
|
5985
|
+
}
|
|
5986
|
+
write_marker() {
|
|
5987
|
+
printf '{"pkg":"%s","profile":"%s","from":"%s","to":"%s","state":"%s","version":"%s","at":"%s"}\\n' \\
|
|
5988
|
+
"$PKG" "$PROFILE" "$FROM" "$TO" "$1" "$2" "$(date -u '+%FT%TZ')" >"$MARKER"
|
|
5989
|
+
}
|
|
5990
|
+
install() { dsh plugin --profile "$PROFILE" add "$PKG@$1" >>"$LOG" 2>&1; }
|
|
5991
|
+
is_to() { [ "$(on_disk)" = "$TO" ]; }
|
|
5992
|
+
is_other() { [ -n "$(on_disk)" ] && [ "$(on_disk)" != "$FROM" ]; }
|
|
5993
|
+
if ! command -v dsh >/dev/null 2>&1; then
|
|
5994
|
+
say "PATH 上没有 dsh —— 这一跳没跑。救法:${rescue}"
|
|
5995
|
+
write_marker failed "$FROM"
|
|
5996
|
+
exit 1
|
|
5997
|
+
fi
|
|
5998
|
+
say "开始 \${FROM} -> \${TO}(不卸任何一行)"
|
|
5999
|
+
write_marker running "$FROM"
|
|
6000
|
+
if install "$TO" && ${landed}; then
|
|
6001
|
+
say "换好了:$(on_disk) 在盘上(目标 \${TO}),重启一次 dsh 才用得上"
|
|
6002
|
+
write_marker ok "$(on_disk)"
|
|
6003
|
+
exit 0
|
|
6004
|
+
fi
|
|
6005
|
+
say "换版没成(盘上 $(on_disk))—— 把 \${FROM} 装回去"
|
|
6006
|
+
if install "$FROM" && [ "$(on_disk)" = "$FROM" ]; then
|
|
6007
|
+
say "已回滚:\${FROM} 还在盘上,工作台这一行没被动过"
|
|
6008
|
+
write_marker rolled-back "$FROM"
|
|
6009
|
+
exit 0
|
|
6010
|
+
fi
|
|
6011
|
+
say "回滚也没成。救法:${rescue}(或 ${lastsOne})"
|
|
6012
|
+
write_marker failed "$(on_disk)"
|
|
6013
|
+
exit 1
|
|
6014
|
+
`;
|
|
6015
|
+
}
|
|
6016
|
+
function planSelfUpdate(request, now = /* @__PURE__ */ new Date()) {
|
|
6017
|
+
const paths = selfUpdatePaths(request.dshHome);
|
|
6018
|
+
return {
|
|
6019
|
+
paths,
|
|
6020
|
+
script: selfUpdateScript(request, paths),
|
|
6021
|
+
marker: selfUpdateMarker(request, "running", request.from, now.toISOString()),
|
|
6022
|
+
command: {
|
|
6023
|
+
file: "bash",
|
|
6024
|
+
args: [paths.script],
|
|
6025
|
+
env: {},
|
|
6026
|
+
log: paths.log
|
|
6027
|
+
}
|
|
6028
|
+
};
|
|
6029
|
+
}
|
|
6030
|
+
function startSelfUpdate(request, onError, spawnImpl = spawn) {
|
|
6031
|
+
const plan = planSelfUpdate(request);
|
|
6032
|
+
mkdirSync(plan.paths.dir, { recursive: true });
|
|
6033
|
+
writeFileSync(plan.paths.script, plan.script, { mode: 493 });
|
|
6034
|
+
chmodSync(plan.paths.script, 493);
|
|
6035
|
+
writeFileSync(plan.paths.marker, `${JSON.stringify(plan.marker)}\n`);
|
|
6036
|
+
runDetached(plan.command, onError, spawnImpl);
|
|
6037
|
+
const forward = recoveryCommand({
|
|
6038
|
+
profile: request.profile,
|
|
6039
|
+
pkg: request.pkg,
|
|
6040
|
+
version: request.to === "" ? "latest" : request.to
|
|
6041
|
+
});
|
|
6042
|
+
return {
|
|
6043
|
+
started: true,
|
|
6044
|
+
paths: plan.paths,
|
|
6045
|
+
forward
|
|
6046
|
+
};
|
|
6047
|
+
}
|
|
6048
|
+
function fixText() {
|
|
6049
|
+
return " 修法:cd \"$DSH_HOME/workbench/diy\" && pnpm install;还不行就把这个工坊目录删掉再重启一次工作台(骨架会自己重建;`packages/` 下你造的模块先留着或另存)。";
|
|
6050
|
+
}
|
|
6051
|
+
function syncingStillRunning(at, now, windowMs) {
|
|
6052
|
+
const started = Date.parse(at);
|
|
6053
|
+
if (!Number.isFinite(started)) return true;
|
|
6054
|
+
return now - started <= windowMs;
|
|
6055
|
+
}
|
|
6056
|
+
function toolchainGap(toolchain, shellVersion) {
|
|
6057
|
+
if (toolchain === void 0) return void 0;
|
|
6058
|
+
const gaps = [
|
|
6059
|
+
["core", toolchain.core],
|
|
6060
|
+
["module-tools", toolchain.moduleTools],
|
|
6061
|
+
["shared", toolchain.shared]
|
|
6062
|
+
].filter((one) => one[1] !== shellVersion);
|
|
6063
|
+
return gaps.length === 0 ? void 0 : gaps.map((one) => `${one[0]} ${one[1]}`).join(" / ");
|
|
6064
|
+
}
|
|
6065
|
+
function syncNotice(input) {
|
|
6066
|
+
const { sync, shellVersion, toolchain } = input;
|
|
6067
|
+
const now = input.now ?? Date.now();
|
|
6068
|
+
const windowMs = input.windowMs ?? 9e5;
|
|
6069
|
+
if (shellVersion === void 0) return void 0;
|
|
6070
|
+
const gap = toolchainGap(toolchain, shellVersion);
|
|
6071
|
+
if (toolchain !== void 0 && gap === void 0) return void 0;
|
|
6072
|
+
const about = sync !== void 0 && sync.version === shellVersion ? sync : void 0;
|
|
6073
|
+
if (about === void 0 && gap === void 0) return void 0;
|
|
6074
|
+
const where = gap === void 0 ? "工坊里读不到那三个包的版本" : `工坊里 ${gap}`;
|
|
6075
|
+
if (about !== void 0 && about.state === "failed") return [`⚠️ 工坊工具链还是上一代(${where};外壳已经是 ${shellVersion}):${about.reason ?? "(没给原因)"}`, fixText()].join("\n");
|
|
6076
|
+
if (about !== void 0 && about.state === "syncing") {
|
|
6077
|
+
if (syncingStillRunning(about.at, now, windowMs)) return `⏳ 工坊工具链正在后台同步到 ${shellVersion} —— 这段期间按上一代在跑,不用等它。`;
|
|
6078
|
+
return [`⚠️ 工坊工具链上一次同步没走完(这条停在 ${about.at},超过 ${String(Math.round(windowMs / 6e4))} 分钟了):${where},外壳已经是 ${shellVersion}。`, fixText()].join("\n");
|
|
6079
|
+
}
|
|
6080
|
+
if (gap === void 0) return void 0;
|
|
6081
|
+
return `⏳ 工坊工具链还是上一代(工坊里 ${gap},外壳 ${shellVersion})—— 下一轮工作台启动时会对齐到 ${shellVersion};一直这样就看 $DSH_HOME/workbench/seed.log。`;
|
|
6082
|
+
}
|
|
6083
|
+
function healthOf(options) {
|
|
6084
|
+
const { registry, unit, installed } = options;
|
|
6085
|
+
const refused = registry?.refusals().find((one) => one.unit === unit);
|
|
6086
|
+
if (refused !== void 0) return {
|
|
6087
|
+
state: "failed",
|
|
6088
|
+
reason: refused.reasons.join(";")
|
|
6089
|
+
};
|
|
6090
|
+
const failed = registry?.failureOf(unit);
|
|
6091
|
+
if (failed !== void 0) return {
|
|
6092
|
+
state: "failed",
|
|
6093
|
+
reason: failed
|
|
6094
|
+
};
|
|
6095
|
+
if (registry?.all().some((one) => one.unit === unit) === true) return { state: "ok" };
|
|
6096
|
+
return {
|
|
6097
|
+
state: "absent",
|
|
6098
|
+
reason: installed ? "工作台还没读到它的声明 —— 新装的模块要重启一次工作台" : "还没装"
|
|
6099
|
+
};
|
|
6100
|
+
}
|
|
6101
|
+
function healthText(health) {
|
|
6102
|
+
if (health.state === "ok") return "是";
|
|
6103
|
+
if (health.state === "failed") return `不是 —— ${health.reason ?? "(没给原因)"}`;
|
|
6104
|
+
return `还没有(${health.reason ?? "还不知道"})`;
|
|
6105
|
+
}
|
|
6106
|
+
function ageText(at, now) {
|
|
6107
|
+
const seconds = Math.max(0, Math.round((now - at) / 1e3));
|
|
6108
|
+
if (seconds < 10) return "刚刚";
|
|
6109
|
+
if (seconds < 60) return `${String(seconds)} 秒前`;
|
|
6110
|
+
const minutes = Math.round(seconds / 60);
|
|
6111
|
+
if (minutes < 60) return `${String(minutes)} 分钟前`;
|
|
6112
|
+
return `${String(Math.round(minutes / 60))} 小时前`;
|
|
6113
|
+
}
|
|
6114
|
+
function stampOf(at) {
|
|
6115
|
+
return `${new Date(at).toISOString().slice(11, 19)}Z`;
|
|
6116
|
+
}
|
|
6117
|
+
function readText(read, now) {
|
|
6118
|
+
const when = `${stampOf(read.at)} · ${ageText(read.at, now)}`;
|
|
6119
|
+
if (read.phase === "ready") return `${read.route} 读到了(${when})`;
|
|
6120
|
+
if (read.phase === "loading") return `${read.route} 正在读(${when})`;
|
|
6121
|
+
if (read.phase === "idle") return `${read.route} 没在取数(${when})`;
|
|
6122
|
+
const code = read.error?.code ?? "unknown";
|
|
6123
|
+
const message = read.error?.message ?? "";
|
|
6124
|
+
return `${read.route} 读不到(${code})${message === "" ? "" : `:${message}`}(${when})`;
|
|
6125
|
+
}
|
|
6126
|
+
function renderPanel(options) {
|
|
6127
|
+
const { packagesDir, workspace, rows, board, at, now } = options;
|
|
6128
|
+
const head = `工坊:${packagesDir}\n这个工作区:${workspace ?? "(会话不在任何工作区上)"}`;
|
|
6129
|
+
const note = at === 0 ? "\n还没有一条面板运行态上报 —— 面板上一个取数 / 渲染都没发生过:要么这个工作区的面板上没开卡片,要么用户那一页还是刷新前的旧版本(上报要页面刷一次才带上)。" : `\n面板上有 ${String(board.length)} 张卡|运行态来自用户那一页(最后一次上报 ${stampOf(at)} · ${ageText(at, now)})`;
|
|
6130
|
+
const body = rows.map((row) => {
|
|
6131
|
+
const title = `—— ${row.unit}${row.name === "" ? "" : `(${row.name})`} · ${healthText(row.health)}`;
|
|
6132
|
+
const report = row.report;
|
|
6133
|
+
if (report === void 0) return [title, ` 没上报:卡片没渲染出来,或者用户那一页还没刷新到带这份补丁的版本`].join("\n");
|
|
6134
|
+
const reads = report.cards.slice(0, 6).map((one) => ` 数据 · ${readText(one, now)}`);
|
|
6135
|
+
const more = report.cards.length > 6 ? [` 数据 · …还有 ${String(report.cards.length - 6)} 条`] : [];
|
|
6136
|
+
const crash = report.crash === void 0 ? " 崩过:没有" : ` 崩过:是 —— ${report.crash.reason}(${stampOf(report.crash.at)} · 共 ${String(report.crash.count)} 次)`;
|
|
6137
|
+
return [
|
|
6138
|
+
title,
|
|
6139
|
+
...reads,
|
|
6140
|
+
...more,
|
|
6141
|
+
crash
|
|
6142
|
+
].join("\n");
|
|
6143
|
+
});
|
|
6144
|
+
return body.length === 0 ? `${head}${note}` : `${head}${note}\n${body.join("\n")}`;
|
|
6145
|
+
}
|
|
6146
|
+
function statusOf(options) {
|
|
6147
|
+
const { dir, unit, name, installed, known, open, health } = options;
|
|
6148
|
+
const facts = moduleBuildFacts(dir);
|
|
6149
|
+
const next = health.state === "failed" ? `它起不来:${health.reason ?? "(没给原因)"} —— 先把这一条修掉(改模块源码 → build;改了 package.json / cordis.patch.yml 要重启工作台),别去重装` : !installed ? "装:workbench_diy_install(第一次,会让用户点头一次)" : !known ? "面板还不认得这个单元 —— 重启一次工作台(新装的模块要重启才注册进面板),重启后再 workbench_diy_show" : !open ? "上板:workbench_diy_show(只上板,不重装)" : !facts.builtAll ? "还没构建:pnpm exec dsh-wb-module build" : facts.stale ? "源码比产物新:pnpm exec dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新页面)" : "都在生效:改源码 → build(宿主侧秒级热换;改了 client.js 刷新页面),不要再装一遍";
|
|
6150
|
+
return {
|
|
6151
|
+
unit,
|
|
6152
|
+
name,
|
|
6153
|
+
dir,
|
|
6154
|
+
installed,
|
|
6155
|
+
known,
|
|
6156
|
+
open,
|
|
6157
|
+
artifacts: facts.artifacts,
|
|
6158
|
+
stale: facts.stale,
|
|
6159
|
+
health,
|
|
6160
|
+
next
|
|
6161
|
+
};
|
|
6162
|
+
}
|
|
6163
|
+
function renderStatus(packagesDir, lines, options = {}) {
|
|
6164
|
+
const root = dirname(packagesDir);
|
|
6165
|
+
const notice = syncNotice({
|
|
6166
|
+
sync: workshopSyncOf(root),
|
|
6167
|
+
shellVersion: options.shellVersion ?? runningVersionOf(import.meta.url),
|
|
6168
|
+
toolchain: workshopToolchainOf(root)
|
|
6169
|
+
});
|
|
6170
|
+
if (lines.length === 0) {
|
|
6171
|
+
const list = renderList(packagesDir, []);
|
|
6172
|
+
return notice === void 0 ? list : `${notice}\n${list}`;
|
|
6173
|
+
}
|
|
6174
|
+
const block = lines.map((one) => {
|
|
6175
|
+
const head = `—— ${one.unit}(${one.name})`;
|
|
6176
|
+
const facts = ` 装:${one.installed ? "是" : "没有"} · 面板认得:${one.known ? "是" : "不是"} · 这个项目上开着:${one.open ? "是" : "没有"}`;
|
|
6177
|
+
const artifacts = one.artifacts.length === 0 ? " 产物:没有" : ` 产物:${one.artifacts.map((item) => `${item.path}${item.at === null ? "(缺)" : ` ${item.at.slice(11, 19)}Z`}`).join(" · ")} · 源码比产物新:${one.stale ? "是" : "没有"}`;
|
|
6178
|
+
return [
|
|
6179
|
+
head,
|
|
6180
|
+
facts,
|
|
6181
|
+
` 起得来:${healthText(one.health)}`,
|
|
6182
|
+
artifacts,
|
|
6183
|
+
` 下一步:${one.next}`
|
|
6184
|
+
].join("\n");
|
|
6185
|
+
});
|
|
6186
|
+
return `${notice === void 0 ? `工坊:${packagesDir}` : `工坊:${packagesDir}\n${notice}`}\n${block.join("\n")}`;
|
|
6187
|
+
}
|
|
6188
|
+
function renderList(packagesDir, modules) {
|
|
6189
|
+
if (modules.length === 0) return `工坊里还没有模块(${packagesDir})—— 在里面造一个:pnpm exec dsh-wb-module init <名字> --dir <工坊>/packages/module-<名字> --template diy`;
|
|
6190
|
+
const lines = modules.map((one) => `${one.unit} · ${one.installed ? "已装" : "没装"} · ${one.name}` + (one.description === "" ? "" : `\n${" ".repeat(one.unit.length)} ${one.description}`));
|
|
6191
|
+
const footer = modules.filter((one) => !one.installed).length === 0 ? "都已装过 —— **不要再装**:改源码 + dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新一下页面)。" : "装:workbench_diy_install(unit=<上面那个 unit>)";
|
|
6192
|
+
return `工坊:${packagesDir}\n${lines.join("\n")}\n\n${footer}`;
|
|
6193
|
+
}
|
|
6194
|
+
//#endregion
|
|
6195
|
+
//#region src/modules/panels/model/diy-tools.ts
|
|
6196
|
+
function registerDiyTools(ctx, market, panels, debug) {
|
|
6197
|
+
const tools = ctx.get("tools");
|
|
6198
|
+
if (tools === void 0) {
|
|
6199
|
+
ctx.logger?.info?.("[dsh-workbench] 没有 tools 服务,跳过 DIY 工坊工具注册");
|
|
6200
|
+
return [];
|
|
6201
|
+
}
|
|
6202
|
+
const listTool = {
|
|
6203
|
+
name: DIY_LIST_TOOL,
|
|
6204
|
+
description: "DIY 工坊里有哪些模块、装没装 —— 先跑它拿到 unit。普通项目里装模块用 workbench_modules_install。",
|
|
6205
|
+
parameters: {
|
|
6206
|
+
type: "object",
|
|
6207
|
+
properties: {},
|
|
6208
|
+
additionalProperties: false
|
|
6209
|
+
},
|
|
6210
|
+
output: OUTPUT$2,
|
|
6211
|
+
async execute(_raw, exec) {
|
|
6212
|
+
const { packagesDir } = workshopOf(ctx, exec);
|
|
6213
|
+
const { modules } = modulesOf(ctx, packagesDir);
|
|
6214
|
+
return plainJson({
|
|
6215
|
+
workshop: packagesDir,
|
|
6216
|
+
modules,
|
|
6217
|
+
text: renderList(packagesDir, modules)
|
|
6218
|
+
});
|
|
6219
|
+
}
|
|
5892
6220
|
};
|
|
5893
6221
|
const statusTool = {
|
|
5894
6222
|
name: DIY_STATUS_TOOL,
|
|
@@ -6857,276 +7185,6 @@ async function installRouted(deps) {
|
|
|
6857
7185
|
});
|
|
6858
7186
|
}
|
|
6859
7187
|
//#endregion
|
|
6860
|
-
//#region src/shared/api/host/runtime-seed.ts
|
|
6861
|
-
var WORKSHOP_SKILLS_DIR = "workbench-module-diy";
|
|
6862
|
-
function runtimeSeedPaths(root) {
|
|
6863
|
-
const opencliRoot = join(root, "opencli");
|
|
6864
|
-
const opencliBinDir = join(opencliRoot, "bin");
|
|
6865
|
-
const workshop = join(root, "diy");
|
|
6866
|
-
return {
|
|
6867
|
-
root,
|
|
6868
|
-
entryDir: join(root, "bin"),
|
|
6869
|
-
opencliRoot,
|
|
6870
|
-
opencliBinDir,
|
|
6871
|
-
opencliBin: join(opencliBinDir, "opencli"),
|
|
6872
|
-
opencliHome: join(opencliRoot, "home"),
|
|
6873
|
-
workshop,
|
|
6874
|
-
workshopPackages: join(workshop, "packages"),
|
|
6875
|
-
workshopManifest: join(workshop, "package.json"),
|
|
6876
|
-
skillsLink: join(workshop, ".agents", "skills", WORKSHOP_SKILLS_DIR)
|
|
6877
|
-
};
|
|
6878
|
-
}
|
|
6879
|
-
function runtimeSeedRootOf(ctx) {
|
|
6880
|
-
const homePath = ctx.get("dshHomePath");
|
|
6881
|
-
if (typeof homePath !== "function") return null;
|
|
6882
|
-
try {
|
|
6883
|
-
return homePath("workbench");
|
|
6884
|
-
} catch {
|
|
6885
|
-
return null;
|
|
6886
|
-
}
|
|
6887
|
-
}
|
|
6888
|
-
function packageRootOf(moduleUrl) {
|
|
6889
|
-
return dirname(dirname(dirname(fileURLToPath(moduleUrl))));
|
|
6890
|
-
}
|
|
6891
|
-
function isInstalledCopy(packageRoot) {
|
|
6892
|
-
return packageRoot.split(sep).includes("node_modules");
|
|
6893
|
-
}
|
|
6894
|
-
function opencliWrapperScript(packageRoot, home) {
|
|
6895
|
-
const script = join(packageRoot, "bin", "opencli-isolated");
|
|
6896
|
-
return `#!/bin/sh\nexec env DSH_HOME=${quoteShellArg(home)} bash ${quoteShellArg(script)} "$@"\n`;
|
|
6897
|
-
}
|
|
6898
|
-
function workshopSeedPlan(packageRoot, home, root) {
|
|
6899
|
-
const script = join(packageRoot, "scripts", "seed-workshop.sh");
|
|
6900
|
-
const env = { DSH_HOME: home };
|
|
6901
|
-
const log = join(root, "seed.log");
|
|
6902
|
-
return {
|
|
6903
|
-
entry: {
|
|
6904
|
-
file: "bash",
|
|
6905
|
-
args: [script, "entry"],
|
|
6906
|
-
env,
|
|
6907
|
-
log
|
|
6908
|
-
},
|
|
6909
|
-
full: {
|
|
6910
|
-
file: "bash",
|
|
6911
|
-
args: [script],
|
|
6912
|
-
env,
|
|
6913
|
-
log
|
|
6914
|
-
}
|
|
6915
|
-
};
|
|
6916
|
-
}
|
|
6917
|
-
function runDetached(command, onError, spawnImpl = spawn) {
|
|
6918
|
-
let fd = null;
|
|
6919
|
-
try {
|
|
6920
|
-
fd = openSync(command.log, "a");
|
|
6921
|
-
const child = spawnImpl(command.file, [...command.args], {
|
|
6922
|
-
env: {
|
|
6923
|
-
...process.env,
|
|
6924
|
-
...command.env
|
|
6925
|
-
},
|
|
6926
|
-
detached: true,
|
|
6927
|
-
stdio: [
|
|
6928
|
-
"ignore",
|
|
6929
|
-
fd,
|
|
6930
|
-
fd
|
|
6931
|
-
]
|
|
6932
|
-
});
|
|
6933
|
-
child.on("error", (error) => onError(`自举脚本没跑起来(${command.file}):${String(error)}`));
|
|
6934
|
-
child.unref();
|
|
6935
|
-
} catch (error) {
|
|
6936
|
-
onError(`自举脚本没跑起来(${command.file}):${String(error)}`);
|
|
6937
|
-
} finally {
|
|
6938
|
-
if (fd !== null) closeQuietly(fd);
|
|
6939
|
-
}
|
|
6940
|
-
}
|
|
6941
|
-
function closeQuietly(fd) {
|
|
6942
|
-
try {
|
|
6943
|
-
closeSync(fd);
|
|
6944
|
-
} catch {
|
|
6945
|
-
return;
|
|
6946
|
-
}
|
|
6947
|
-
}
|
|
6948
|
-
function prependPath(entryDir, env = process.env) {
|
|
6949
|
-
const parts = (env.PATH ?? "").split(delimiter).filter((part) => part !== "");
|
|
6950
|
-
if (parts.includes(entryDir)) return false;
|
|
6951
|
-
env.PATH = [entryDir, ...parts].join(delimiter);
|
|
6952
|
-
return true;
|
|
6953
|
-
}
|
|
6954
|
-
function seedRuntimeFiles(options) {
|
|
6955
|
-
const paths = runtimeSeedPaths(options.root);
|
|
6956
|
-
const done = [];
|
|
6957
|
-
try {
|
|
6958
|
-
for (const dir of [
|
|
6959
|
-
paths.entryDir,
|
|
6960
|
-
paths.opencliBinDir,
|
|
6961
|
-
paths.opencliHome,
|
|
6962
|
-
paths.workshopPackages
|
|
6963
|
-
]) mkdirSync(dir, { recursive: true });
|
|
6964
|
-
const wanted = opencliWrapperScript(options.packageRoot, options.home);
|
|
6965
|
-
if (readText(paths.opencliBin) !== wanted) {
|
|
6966
|
-
rmSync(paths.opencliBin, { force: true });
|
|
6967
|
-
writeFileSync(paths.opencliBin, wanted, "utf8");
|
|
6968
|
-
chmodSync(paths.opencliBin, 493);
|
|
6969
|
-
done.push(paths.opencliBin);
|
|
6970
|
-
}
|
|
6971
|
-
if (linkSkills(paths.skillsLink, join(options.packageRoot, "skills", "workbench-module-diy"), options.log)) done.push(paths.skillsLink);
|
|
6972
|
-
} catch (error) {
|
|
6973
|
-
options.log?.(`自举没做完:${String(error)}`);
|
|
6974
|
-
}
|
|
6975
|
-
return done;
|
|
6976
|
-
}
|
|
6977
|
-
function readText(file) {
|
|
6978
|
-
try {
|
|
6979
|
-
return readFileSync(file, "utf8");
|
|
6980
|
-
} catch {
|
|
6981
|
-
return null;
|
|
6982
|
-
}
|
|
6983
|
-
}
|
|
6984
|
-
function readLink(link) {
|
|
6985
|
-
try {
|
|
6986
|
-
return readlinkSync(link);
|
|
6987
|
-
} catch {
|
|
6988
|
-
return null;
|
|
6989
|
-
}
|
|
6990
|
-
}
|
|
6991
|
-
function linkSkills(link, source, log) {
|
|
6992
|
-
if (!existsSync(source) || readLink(link) === source) return false;
|
|
6993
|
-
try {
|
|
6994
|
-
mkdirSync(dirname(link), { recursive: true });
|
|
6995
|
-
rmSync(link, {
|
|
6996
|
-
force: true,
|
|
6997
|
-
recursive: true
|
|
6998
|
-
});
|
|
6999
|
-
symlinkSync(source, link, "dir");
|
|
7000
|
-
return true;
|
|
7001
|
-
} catch (error) {
|
|
7002
|
-
log?.(`DIY 指引没挂上(${link}):${String(error)}`);
|
|
7003
|
-
return false;
|
|
7004
|
-
}
|
|
7005
|
-
}
|
|
7006
|
-
//#endregion
|
|
7007
|
-
//#region src/shared/api/host/self-update.ts
|
|
7008
|
-
function runningVersionOf(moduleUrl) {
|
|
7009
|
-
try {
|
|
7010
|
-
const read = JSON.parse(readFileSync(new URL("../../package.json", moduleUrl), "utf8"));
|
|
7011
|
-
return typeof read.version === "string" && read.version !== "" ? read.version : void 0;
|
|
7012
|
-
} catch {
|
|
7013
|
-
return;
|
|
7014
|
-
}
|
|
7015
|
-
}
|
|
7016
|
-
function selfUpdatePaths(dshHome) {
|
|
7017
|
-
const dir = join(dshHome, "workbench");
|
|
7018
|
-
return {
|
|
7019
|
-
dir,
|
|
7020
|
-
script: join(dir, "self-update.sh"),
|
|
7021
|
-
marker: join(dir, "self-update.json"),
|
|
7022
|
-
log: join(dir, "self-update.log")
|
|
7023
|
-
};
|
|
7024
|
-
}
|
|
7025
|
-
function recoveryCommand(input) {
|
|
7026
|
-
return `dsh plugin --profile ${input.profile} add ${input.pkg}@${input.version}`;
|
|
7027
|
-
}
|
|
7028
|
-
function selfUpdateMarker(request, state, version, at) {
|
|
7029
|
-
return {
|
|
7030
|
-
...request,
|
|
7031
|
-
state,
|
|
7032
|
-
version,
|
|
7033
|
-
at
|
|
7034
|
-
};
|
|
7035
|
-
}
|
|
7036
|
-
function quoted(value) {
|
|
7037
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
7038
|
-
}
|
|
7039
|
-
function selfUpdateScript(request, paths) {
|
|
7040
|
-
const rescue = recoveryCommand({
|
|
7041
|
-
profile: request.profile,
|
|
7042
|
-
pkg: request.pkg,
|
|
7043
|
-
version: request.from
|
|
7044
|
-
});
|
|
7045
|
-
const lastsOne = recoveryCommand({
|
|
7046
|
-
profile: request.profile,
|
|
7047
|
-
pkg: request.pkg,
|
|
7048
|
-
version: request.to === "" ? "latest" : request.to
|
|
7049
|
-
});
|
|
7050
|
-
const target = request.to === "" ? "latest" : request.to;
|
|
7051
|
-
const landed = request.to === "" ? "is_other" : "is_to";
|
|
7052
|
-
return `#!/usr/bin/env bash
|
|
7053
|
-
set -uo pipefail
|
|
7054
|
-
DSH_HOME=${quoted(request.dshHome)}
|
|
7055
|
-
PKG=${quoted(request.pkg)}
|
|
7056
|
-
PROFILE=${quoted(request.profile)}
|
|
7057
|
-
FROM=${quoted(request.from)}
|
|
7058
|
-
TO=${quoted(target)}
|
|
7059
|
-
MARKER=${quoted(paths.marker)}
|
|
7060
|
-
LOG=${quoted(paths.log)}
|
|
7061
|
-
PROFILE_DIR="$DSH_HOME/profiles/$PROFILE"
|
|
7062
|
-
say() { printf '%s %s\\n' "$(date '+%F %T')" "$*" >>"$LOG"; }
|
|
7063
|
-
on_disk() {
|
|
7064
|
-
node -e 'const p=process.argv[1];try{process.stdout.write(require(p).version)}catch(e){}' \\
|
|
7065
|
-
"$PROFILE_DIR/node_modules/$PKG/package.json" 2>/dev/null
|
|
7066
|
-
}
|
|
7067
|
-
write_marker() {
|
|
7068
|
-
printf '{"pkg":"%s","profile":"%s","from":"%s","to":"%s","state":"%s","version":"%s","at":"%s"}\\n' \\
|
|
7069
|
-
"$PKG" "$PROFILE" "$FROM" "$TO" "$1" "$2" "$(date -u '+%FT%TZ')" >"$MARKER"
|
|
7070
|
-
}
|
|
7071
|
-
install() { dsh plugin --profile "$PROFILE" add "$PKG@$1" >>"$LOG" 2>&1; }
|
|
7072
|
-
is_to() { [ "$(on_disk)" = "$TO" ]; }
|
|
7073
|
-
is_other() { [ -n "$(on_disk)" ] && [ "$(on_disk)" != "$FROM" ]; }
|
|
7074
|
-
if ! command -v dsh >/dev/null 2>&1; then
|
|
7075
|
-
say "PATH 上没有 dsh —— 这一跳没跑。救法:${rescue}"
|
|
7076
|
-
write_marker failed "$FROM"
|
|
7077
|
-
exit 1
|
|
7078
|
-
fi
|
|
7079
|
-
say "开始 \${FROM} -> \${TO}(不卸任何一行)"
|
|
7080
|
-
write_marker running "$FROM"
|
|
7081
|
-
if install "$TO" && ${landed}; then
|
|
7082
|
-
say "换好了:$(on_disk) 在盘上(目标 \${TO}),重启一次 dsh 才用得上"
|
|
7083
|
-
write_marker ok "$(on_disk)"
|
|
7084
|
-
exit 0
|
|
7085
|
-
fi
|
|
7086
|
-
say "换版没成(盘上 $(on_disk))—— 把 \${FROM} 装回去"
|
|
7087
|
-
if install "$FROM" && [ "$(on_disk)" = "$FROM" ]; then
|
|
7088
|
-
say "已回滚:\${FROM} 还在盘上,工作台这一行没被动过"
|
|
7089
|
-
write_marker rolled-back "$FROM"
|
|
7090
|
-
exit 0
|
|
7091
|
-
fi
|
|
7092
|
-
say "回滚也没成。救法:${rescue}(或 ${lastsOne})"
|
|
7093
|
-
write_marker failed "$(on_disk)"
|
|
7094
|
-
exit 1
|
|
7095
|
-
`;
|
|
7096
|
-
}
|
|
7097
|
-
function planSelfUpdate(request, now = /* @__PURE__ */ new Date()) {
|
|
7098
|
-
const paths = selfUpdatePaths(request.dshHome);
|
|
7099
|
-
return {
|
|
7100
|
-
paths,
|
|
7101
|
-
script: selfUpdateScript(request, paths),
|
|
7102
|
-
marker: selfUpdateMarker(request, "running", request.from, now.toISOString()),
|
|
7103
|
-
command: {
|
|
7104
|
-
file: "bash",
|
|
7105
|
-
args: [paths.script],
|
|
7106
|
-
env: {},
|
|
7107
|
-
log: paths.log
|
|
7108
|
-
}
|
|
7109
|
-
};
|
|
7110
|
-
}
|
|
7111
|
-
function startSelfUpdate(request, onError, spawnImpl = spawn) {
|
|
7112
|
-
const plan = planSelfUpdate(request);
|
|
7113
|
-
mkdirSync(plan.paths.dir, { recursive: true });
|
|
7114
|
-
writeFileSync(plan.paths.script, plan.script, { mode: 493 });
|
|
7115
|
-
chmodSync(plan.paths.script, 493);
|
|
7116
|
-
writeFileSync(plan.paths.marker, `${JSON.stringify(plan.marker)}\n`);
|
|
7117
|
-
runDetached(plan.command, onError, spawnImpl);
|
|
7118
|
-
const forward = recoveryCommand({
|
|
7119
|
-
profile: request.profile,
|
|
7120
|
-
pkg: request.pkg,
|
|
7121
|
-
version: request.to === "" ? "latest" : request.to
|
|
7122
|
-
});
|
|
7123
|
-
return {
|
|
7124
|
-
started: true,
|
|
7125
|
-
paths: plan.paths,
|
|
7126
|
-
forward
|
|
7127
|
-
};
|
|
7128
|
-
}
|
|
7129
|
-
//#endregion
|
|
7130
7188
|
//#region src/modules/panels/model/market-workbench-update.ts
|
|
7131
7189
|
async function updateWorkbenchOnce(deps) {
|
|
7132
7190
|
const asked = deps.asked.trim();
|