@dsh-enhanced/dsh-rsi-cli 0.1.48

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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +231 -0
  3. package/bin/dsh-rsi.js +11 -0
  4. package/lib/backup.d.ts +16 -0
  5. package/lib/backup.d.ts.map +1 -0
  6. package/lib/backup.js +27 -0
  7. package/lib/backup.js.map +1 -0
  8. package/lib/diagnose.d.ts +33 -0
  9. package/lib/diagnose.d.ts.map +1 -0
  10. package/lib/diagnose.js +145 -0
  11. package/lib/diagnose.js.map +1 -0
  12. package/lib/index.d.ts +46 -0
  13. package/lib/index.d.ts.map +1 -0
  14. package/lib/index.js +592 -0
  15. package/lib/index.js.map +1 -0
  16. package/lib/install.d.ts +33 -0
  17. package/lib/install.d.ts.map +1 -0
  18. package/lib/install.js +64 -0
  19. package/lib/install.js.map +1 -0
  20. package/lib/paths.d.ts +61 -0
  21. package/lib/paths.d.ts.map +1 -0
  22. package/lib/paths.js +168 -0
  23. package/lib/paths.js.map +1 -0
  24. package/lib/process-guard.d.ts +51 -0
  25. package/lib/process-guard.d.ts.map +1 -0
  26. package/lib/process-guard.js +143 -0
  27. package/lib/process-guard.js.map +1 -0
  28. package/lib/purge.d.ts +58 -0
  29. package/lib/purge.d.ts.map +1 -0
  30. package/lib/purge.js +204 -0
  31. package/lib/purge.js.map +1 -0
  32. package/lib/run.d.ts +27 -0
  33. package/lib/run.d.ts.map +1 -0
  34. package/lib/run.js +53 -0
  35. package/lib/run.js.map +1 -0
  36. package/lib/secrets.d.ts +22 -0
  37. package/lib/secrets.d.ts.map +1 -0
  38. package/lib/secrets.js +107 -0
  39. package/lib/secrets.js.map +1 -0
  40. package/lib/services.d.ts +89 -0
  41. package/lib/services.d.ts.map +1 -0
  42. package/lib/services.js +339 -0
  43. package/lib/services.js.map +1 -0
  44. package/lib/update.d.ts +59 -0
  45. package/lib/update.d.ts.map +1 -0
  46. package/lib/update.js +160 -0
  47. package/lib/update.js.map +1 -0
  48. package/lib/version.d.ts +2 -0
  49. package/lib/version.d.ts.map +1 -0
  50. package/lib/version.js +2 -0
  51. package/lib/version.js.map +1 -0
  52. package/lib/weburl.d.ts +36 -0
  53. package/lib/weburl.d.ts.map +1 -0
  54. package/lib/weburl.js +106 -0
  55. package/lib/weburl.js.map +1 -0
  56. package/package.json +54 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC1C,YAAY,EAAE,CACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;KAAE,KAChC,OAAO,CAAC,MAAM,CAAC,CAAA;CACrB;AAED,eAAO,MAAM,eAAe,EAAE,eAa7B,CAAA;AAED,MAAM,WAAW,iBAAiB;IAChC,8FAA8F;IAC9F,IAAI,EAAE,KAAK,GAAG,OAAO,CAAA;IACrB,mEAAmE;IACnE,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAA;IAClB,mBAAmB;IACnB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,eAAe,CAAA;CAC3B;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoC5E"}
package/lib/install.js ADDED
@@ -0,0 +1,64 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { PurgeError } from "./purge.js";
6
+ const INSTALL_BASE_URL = 'https://raw.githubusercontent.com/22-ai-00/dsh-enhanced';
7
+ export const defaultExecutor = {
8
+ async download(url) {
9
+ const response = await fetch(url, { redirect: 'follow' });
10
+ if (!response.ok)
11
+ throw new Error(`HTTP ${response.status}`);
12
+ return await response.text();
13
+ },
14
+ runInherited(command, args, options) {
15
+ return new Promise((resolve, reject) => {
16
+ const child = spawn(command, [...args], { stdio: 'inherit', env: options.env });
17
+ child.on('error', reject);
18
+ child.on('close', code => resolve(code ?? 1));
19
+ });
20
+ },
21
+ };
22
+ /**
23
+ * 薄委托:dsh-rsi 不复制任何安装逻辑。
24
+ * npm 形态只负责把与自身版本同 tag 的 install-npm.sh 拉到临时目录执行,
25
+ * common.sh 的 SHA-256 自校验由脚本自身完成;local 形态直接执行 checkout 内脚本。
26
+ * 安装器 stdio 与当前终端直连,交互提示/输出不经缓冲。
27
+ * 返回安装器退出码。
28
+ */
29
+ export async function runInstall(options) {
30
+ const executor = options.executor ?? defaultExecutor;
31
+ const env = { ...process.env };
32
+ if (options.dshHome !== undefined)
33
+ env.DSH_HOME = options.dshHome;
34
+ if (options.mode === 'local') {
35
+ const root = options.localRepositoryRoot;
36
+ if (root === undefined || root.length === 0) {
37
+ throw new PurgeError('local 安装形态需要 --local <checkout 目录>');
38
+ }
39
+ const script = join(root, 'scripts', 'install', 'install-local.sh');
40
+ return await executor.runInherited('bash', [script, ...options.passthrough], { env });
41
+ }
42
+ if (!/^v\d+\.\d+\.\d+$/u.test(options.releaseRef)) {
43
+ throw new PurgeError(`非法的 release ref:${options.releaseRef}(应为 vX.Y.Z)`);
44
+ }
45
+ const url = `${INSTALL_BASE_URL}/${options.releaseRef}/scripts/install/install-npm.sh`;
46
+ let scriptBody;
47
+ try {
48
+ scriptBody = await executor.download(url);
49
+ }
50
+ catch (error) {
51
+ throw new PurgeError(`下载 install-npm.sh 失败(${url}):${error.message}。请检查网络,或改用 local 形态。`);
52
+ }
53
+ const staging = await mkdtemp(join(tmpdir(), 'dsh-rsi-install-'));
54
+ const scriptPath = join(staging, 'install-npm.sh');
55
+ try {
56
+ await writeFile(scriptPath, scriptBody, { mode: 0o700 });
57
+ await chmod(scriptPath, 0o700);
58
+ return await executor.runInherited('bash', [scriptPath, ...options.passthrough], { env });
59
+ }
60
+ finally {
61
+ await rm(staging, { recursive: true, force: true });
62
+ }
63
+ }
64
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
package/lib/paths.d.ts ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * dsh 受管 profile 在 macOS LaunchAgent 中使用的 label 前缀。
3
+ * 与 plugins/lark-channel/src/launchd.ts 的 launchAgentPaths 保持一致。
4
+ */
5
+ export declare const LAUNCHD_LABEL_PREFIX = "ai.deepseek.dsh.profile.";
6
+ /** Linux systemd --user 受管 unit 命名(lifecycle-profile.mjs SYSTEMD_UNIT)。 */
7
+ export declare const SYSTEMD_UNIT_PATTERN: RegExp;
8
+ /** 生命周期事务目录后缀(lifecycle-profile.mjs:17)。 */
9
+ export declare const TRANSACTION_SUFFIX = ".dsh-enhanced-transaction";
10
+ /** 生命周期 home 级锁后缀(lifecycle-profile.mjs:504)。 */
11
+ export declare const HOME_LOCK_SUFFIX = ".dsh-enhanced-lifecycle.lock";
12
+ /** Keychain / Secret Service 中受管凭据的 service 前缀。 */
13
+ export declare const CREDENTIAL_SERVICE_PREFIX = "dsh/";
14
+ export declare function resolveDshHome(env?: NodeJS.ProcessEnv): string;
15
+ export declare function profilesDirectory(dshHome: string): string;
16
+ export declare function logsDirectory(dshHome: string): string;
17
+ /**
18
+ * 列出 DSH home 下的全部 profile 名。
19
+ * profiles/ 不存在时返回空数组(机器上尚未安装任何 profile)。
20
+ */
21
+ export declare function listProfiles(dshHome: string): Promise<string[]>;
22
+ export interface ProfilePlugin {
23
+ /** node_modules/@dsh-enhanced 下的包名,含 scope。 */
24
+ name: string;
25
+ /** local = 指向 checkout 的符号链接;npm = 实体副本。 */
26
+ kind: 'npm' | 'local';
27
+ /** local 形态下符号链接指向的绝对路径(checkout 内的包目录)。 */
28
+ linkTarget?: string;
29
+ }
30
+ /**
31
+ * 枚举一个 profile 中安装的 @dsh-enhanced 插件及其安装形态。
32
+ * 符号链接(pnpm link / dsh plugin add <checkout>)判定为 local;
33
+ * 实体目录判定为 npm cohort 副本。
34
+ */
35
+ export declare function listProfilePlugins(dshHome: string, profile: string): Promise<ProfilePlugin[]>;
36
+ /**
37
+ * 汇总一批 profile 中 local 形态解析出的 checkout 包路径。
38
+ * purge 绝不删除这些路径,只在报告中提示用户自行处理 checkout 源码。
39
+ */
40
+ export declare function collectLocalCheckouts(plugins: readonly ProfilePlugin[]): string[];
41
+ /**
42
+ * home 之外/同级的生命周期残留:
43
+ * - `${home}.dsh-enhanced-transaction` 及 `.failed-<profile>-*` 失败诊断目录
44
+ * - `${home}.dsh-enhanced-lifecycle.lock`
45
+ * - `/tmp/.dsh-enhanced-lifecycle-<uid>-<sha256(home)>.lock` rendezvous 锁
46
+ * 后两者按精确名直接判断;事务/诊断目录在父目录中按前缀枚举。
47
+ */
48
+ export declare function listLifecycleResiduals(dshHome: string, parentEntries: readonly string[], tmpEntries?: readonly string[], uid?: number): Promise<string[]>;
49
+ /** 一个 profile 对应的受管日志文件(stdout/stderr)。 */
50
+ export declare function profileLogPaths(dshHome: string, profile: string): string[];
51
+ /** macOS LaunchAgent label / plist 路径(与 launchd.ts launchAgentPaths 对齐)。 */
52
+ export declare function launchAgentTargets(home: string, profile: string): {
53
+ label: string;
54
+ plistPath: string;
55
+ };
56
+ /** Linux systemd --user unit 名。 */
57
+ export declare function systemdUnitName(profile: string): string;
58
+ export declare function systemdUserDirectory(home: string): string;
59
+ /** 备份包默认落点:~(HOME)下 dsh-purge-backup-<UTC 时间戳>.tar.gz。 */
60
+ export declare function backupArchivePath(home: string, stamp: string): string;
61
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,6BAA6B,CAAA;AAC9D,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB,QAA8D,CAAA;AAC/F,4CAA4C;AAC5C,eAAO,MAAM,kBAAkB,8BAA8B,CAAA;AAC7D,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,iCAAiC,CAAA;AAC9D,mDAAmD;AACnD,eAAO,MAAM,yBAAyB,SAAS,CAAA;AAE/C,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG3E;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAuBrE;AAED,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAA;IACZ,4CAA4C;IAC5C,IAAI,EAAE,KAAK,GAAG,OAAO,CAAA;IACrB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA2BnG;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,EAAE,CAMjF;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,UAAU,GAAE,SAAS,MAAM,EAAO,EAClC,GAAG,GAAE,MAAyC,GAC7C,OAAO,CAAC,MAAM,EAAE,CAAC,CAiBnB;AAOD,2CAA2C;AAC3C,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAK1E;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAGtG;AAED,mCAAmC;AACnC,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAErE"}
package/lib/paths.js ADDED
@@ -0,0 +1,168 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readdir, readlink, lstat } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { join, basename } from 'node:path';
5
+ /**
6
+ * dsh 受管 profile 在 macOS LaunchAgent 中使用的 label 前缀。
7
+ * 与 plugins/lark-channel/src/launchd.ts 的 launchAgentPaths 保持一致。
8
+ */
9
+ export const LAUNCHD_LABEL_PREFIX = 'ai.deepseek.dsh.profile.';
10
+ /** Linux systemd --user 受管 unit 命名(lifecycle-profile.mjs SYSTEMD_UNIT)。 */
11
+ export const SYSTEMD_UNIT_PATTERN = /^dsh-profile-([A-Za-z0-9][A-Za-z0-9._-]{0,63})\.service$/u;
12
+ /** 生命周期事务目录后缀(lifecycle-profile.mjs:17)。 */
13
+ export const TRANSACTION_SUFFIX = '.dsh-enhanced-transaction';
14
+ /** 生命周期 home 级锁后缀(lifecycle-profile.mjs:504)。 */
15
+ export const HOME_LOCK_SUFFIX = '.dsh-enhanced-lifecycle.lock';
16
+ /** Keychain / Secret Service 中受管凭据的 service 前缀。 */
17
+ export const CREDENTIAL_SERVICE_PREFIX = 'dsh/';
18
+ export function resolveDshHome(env = process.env) {
19
+ const explicit = env.DSH_HOME;
20
+ return explicit && explicit.length > 0 ? explicit : join(homedir(), '.dsh');
21
+ }
22
+ export function profilesDirectory(dshHome) {
23
+ return join(dshHome, 'profiles');
24
+ }
25
+ export function logsDirectory(dshHome) {
26
+ return join(dshHome, 'logs');
27
+ }
28
+ /**
29
+ * 列出 DSH home 下的全部 profile 名。
30
+ * profiles/ 不存在时返回空数组(机器上尚未安装任何 profile)。
31
+ */
32
+ export async function listProfiles(dshHome) {
33
+ const directory = profilesDirectory(dshHome);
34
+ let entries;
35
+ try {
36
+ entries = await readdir(directory);
37
+ }
38
+ catch (error) {
39
+ if (error.code === 'ENOENT')
40
+ return [];
41
+ throw error;
42
+ }
43
+ const profiles = [];
44
+ for (const entry of entries) {
45
+ if (entry.startsWith('.'))
46
+ continue;
47
+ const profilePath = join(directory, entry);
48
+ const metadata = await lstat(profilePath);
49
+ if (!metadata.isDirectory() || metadata.isSymbolicLink())
50
+ continue;
51
+ // profiles/node_modules is DSH's shared Host peer fallback, not a profile.
52
+ // A real DSH profile is anchored by its package.json; requiring the marker
53
+ // also avoids treating arbitrary/cache directories as service targets.
54
+ let manifest;
55
+ try {
56
+ manifest = await lstat(join(profilePath, 'package.json'));
57
+ }
58
+ catch {
59
+ continue;
60
+ }
61
+ if (manifest.isFile() && !manifest.isSymbolicLink())
62
+ profiles.push(entry);
63
+ }
64
+ return profiles.sort();
65
+ }
66
+ /**
67
+ * 枚举一个 profile 中安装的 @dsh-enhanced 插件及其安装形态。
68
+ * 符号链接(pnpm link / dsh plugin add <checkout>)判定为 local;
69
+ * 实体目录判定为 npm cohort 副本。
70
+ */
71
+ export async function listProfilePlugins(dshHome, profile) {
72
+ const scopeDirectory = join(profilesDirectory(dshHome), profile, 'node_modules', '@dsh-enhanced');
73
+ let entries;
74
+ try {
75
+ entries = await readdir(scopeDirectory);
76
+ }
77
+ catch (error) {
78
+ if (error.code === 'ENOENT')
79
+ return [];
80
+ throw error;
81
+ }
82
+ const plugins = [];
83
+ for (const entry of entries) {
84
+ if (entry.startsWith('.') || entry.startsWith('@'))
85
+ continue;
86
+ const pluginPath = join(scopeDirectory, entry);
87
+ const metadata = await lstat(pluginPath);
88
+ const name = `@dsh-enhanced/${entry}`;
89
+ if (metadata.isSymbolicLink()) {
90
+ const target = await readlink(pluginPath);
91
+ plugins.push({
92
+ name,
93
+ kind: 'local',
94
+ linkTarget: target.startsWith('/') ? target : join(scopeDirectory, target),
95
+ });
96
+ }
97
+ else if (metadata.isDirectory()) {
98
+ plugins.push({ name, kind: 'npm' });
99
+ }
100
+ }
101
+ return plugins.sort((a, b) => a.name.localeCompare(b.name));
102
+ }
103
+ /**
104
+ * 汇总一批 profile 中 local 形态解析出的 checkout 包路径。
105
+ * purge 绝不删除这些路径,只在报告中提示用户自行处理 checkout 源码。
106
+ */
107
+ export function collectLocalCheckouts(plugins) {
108
+ const targets = new Set();
109
+ for (const plugin of plugins) {
110
+ if (plugin.kind === 'local' && plugin.linkTarget !== undefined)
111
+ targets.add(plugin.linkTarget);
112
+ }
113
+ return [...targets].sort();
114
+ }
115
+ /**
116
+ * home 之外/同级的生命周期残留:
117
+ * - `${home}.dsh-enhanced-transaction` 及 `.failed-<profile>-*` 失败诊断目录
118
+ * - `${home}.dsh-enhanced-lifecycle.lock`
119
+ * - `/tmp/.dsh-enhanced-lifecycle-<uid>-<sha256(home)>.lock` rendezvous 锁
120
+ * 后两者按精确名直接判断;事务/诊断目录在父目录中按前缀枚举。
121
+ */
122
+ export async function listLifecycleResiduals(dshHome, parentEntries, tmpEntries = [], uid = process.getuid?.() ?? Number.NaN) {
123
+ const residuals = new Set();
124
+ const homeBase = basename(dshHome);
125
+ const transactionPrefix = `${homeBase}${TRANSACTION_SUFFIX}`;
126
+ for (const entry of parentEntries) {
127
+ if (entry === transactionPrefix || entry.startsWith(`${transactionPrefix}.failed-`)) {
128
+ residuals.add(join(parentOf(dshHome), entry));
129
+ }
130
+ if (entry === `${homeBase}${HOME_LOCK_SUFFIX}`) {
131
+ residuals.add(join(parentOf(dshHome), entry));
132
+ }
133
+ }
134
+ if (Number.isFinite(uid)) {
135
+ const rendezvous = `.dsh-enhanced-lifecycle-${uid}-${createHash('sha256').update(dshHome).digest('hex')}.lock`;
136
+ if (tmpEntries.includes(rendezvous))
137
+ residuals.add(join('/tmp', rendezvous));
138
+ }
139
+ return [...residuals].sort();
140
+ }
141
+ function parentOf(path) {
142
+ const index = path.lastIndexOf('/');
143
+ return index <= 0 ? '/' : path.slice(0, index);
144
+ }
145
+ /** 一个 profile 对应的受管日志文件(stdout/stderr)。 */
146
+ export function profileLogPaths(dshHome, profile) {
147
+ return [
148
+ join(logsDirectory(dshHome), `${profile}-host.log`),
149
+ join(logsDirectory(dshHome), `${profile}-host.error.log`),
150
+ ];
151
+ }
152
+ /** macOS LaunchAgent label / plist 路径(与 launchd.ts launchAgentPaths 对齐)。 */
153
+ export function launchAgentTargets(home, profile) {
154
+ const label = `${LAUNCHD_LABEL_PREFIX}${profile}`;
155
+ return { label, plistPath: join(home, 'Library', 'LaunchAgents', `${label}.plist`) };
156
+ }
157
+ /** Linux systemd --user unit 名。 */
158
+ export function systemdUnitName(profile) {
159
+ return `dsh-profile-${profile}.service`;
160
+ }
161
+ export function systemdUserDirectory(home) {
162
+ return join(home, '.config', 'systemd', 'user');
163
+ }
164
+ /** 备份包默认落点:~(HOME)下 dsh-purge-backup-<UTC 时间戳>.tar.gz。 */
165
+ export function backupArchivePath(home, stamp) {
166
+ return join(home, `dsh-purge-backup-${stamp}.tar.gz`);
167
+ }
168
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { CommandRunner } from './run.ts';
2
+ /**
3
+ * Purge 前的进程守卫。
4
+ *
5
+ * 旧逻辑在停服之前做进程静止检查:只要有 `--profile <p>` 进程在跑就直接 fail,
6
+ * 连「本应受管、本可自动停掉」的 host 也不放过,导致用户卡在 purge 入口。
7
+ * 这里把进程分成三类:
8
+ * - self :dsh-rsi / purge.sh 自身命令行,永远跳过。
9
+ * - proven :非 self,但命令行指向目标 profile,且归属证据(DSH_HOME 环境变量 /
10
+ * cwd / 命令行)能证明它属于本次要清除的 DSH home。可安全终止。
11
+ * - foreign :命令行带 `--profile <p>` 但证据指向别的 home / 无法证明归属;fail-closed,
12
+ * 绝不自动杀。
13
+ *
14
+ * 终止顺序:先 SIGTERM,轮询最多 5 秒;仍在则 SIGKILL。每次复检都重新读取命令行,
15
+ * 确认 PID 仍是目标进程(防 PID reuse)。
16
+ */
17
+ export interface GuardedProcess {
18
+ pid: number;
19
+ commandLine: string;
20
+ /** 归属证据(人读),用于交互确认列出。 */
21
+ evidence: string[];
22
+ /** proven = 可安全终止;foreign = 无法证明归属,fail-closed。 */
23
+ owner: 'proven' | 'foreign';
24
+ }
25
+ export interface ProcessScan {
26
+ processes: GuardedProcess[];
27
+ /** 非零即环境探测失败(pgrep 不可用等),调用方应 fail-closed。 */
28
+ error?: string;
29
+ }
30
+ /**
31
+ * 扫描目标 profile 的运行中进程,分类为 proven / foreign。
32
+ * 已停掉的受管服务不会再出现(调用方先 stopManagedService);这里只处理残留进程。
33
+ */
34
+ export declare function scanRunningProfiles(dshHome: string, profiles: readonly string[], runner?: CommandRunner): Promise<ProcessScan>;
35
+ export interface TerminationReport {
36
+ terminated: number;
37
+ /** 仍在运行或身份已变的 PID(复检失败)。 */
38
+ failed: number[];
39
+ }
40
+ /**
41
+ * 终止一组已证明归属的 PID。
42
+ * SIGTERM → 每 200ms 复检一次,最多 5s → 仍在则 SIGKILL。
43
+ * 复检时重新读命令行:若 PID 已被复用成别的进程,立即放弃该 PID(不杀)。
44
+ */
45
+ export declare function terminatePids(pids: readonly number[], runner?: CommandRunner, options?: {
46
+ timeoutMs?: number;
47
+ intervalMs?: number;
48
+ }): Promise<TerminationReport>;
49
+ /** 供外部判断一个相对路径是否落在 dshHome 内(预留 cwd 证据扩展)。 */
50
+ export declare function pathInside(path: string, base: string): boolean;
51
+ //# sourceMappingURL=process-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-guard.d.ts","sourceRoot":"","sources":["../src/process-guard.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAG7C;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,yBAAyB;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,mDAAmD;IACnD,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAA;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,cAAc,EAAE,CAAA;IAC3B,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAgCD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,WAAW,CAAC,CAmCtB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,4BAA4B;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,MAAM,GAAE,aAA6B,EACrC,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,iBAAiB,CAAC,CAoD5B;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAG9D"}
@@ -0,0 +1,143 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { relative } from 'node:path';
3
+ import { defaultRunner } from "./run.js";
4
+ /** 这些命令行属于 purge/rsi 自身,即使带 --profile 也绝不视为待终止目标。 */
5
+ function isSelfCommand(commandLine) {
6
+ return commandLine.includes('dsh-rsi')
7
+ || commandLine.includes('rsi-cli')
8
+ || commandLine.includes('purge.sh');
9
+ }
10
+ function escapeRegex(value) {
11
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/gu, '\\$&');
12
+ }
13
+ /** 读取 PID 的命令行(ps);失败返回占位。 */
14
+ function readCommandLine(pid, runner) {
15
+ const shown = runner('ps', ['-p', String(pid), '-o', 'command=']);
16
+ return shown.stdout.trim() || `PID ${pid}`;
17
+ }
18
+ /** Linux:从 /proc/<pid>/environ 读 DSH_HOME;失败返回 undefined。 */
19
+ async function procDshHome(pid) {
20
+ try {
21
+ const raw = await readFile(`/proc/${pid}/environ`, { encoding: 'utf8' });
22
+ for (const entry of raw.split('\0')) {
23
+ if (entry.startsWith('DSH_HOME='))
24
+ return entry.slice('DSH_HOME='.length) || undefined;
25
+ }
26
+ return undefined;
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ /**
33
+ * 扫描目标 profile 的运行中进程,分类为 proven / foreign。
34
+ * 已停掉的受管服务不会再出现(调用方先 stopManagedService);这里只处理残留进程。
35
+ */
36
+ export async function scanRunningProfiles(dshHome, profiles, runner = defaultRunner) {
37
+ const processes = [];
38
+ for (const profile of profiles) {
39
+ const pattern = `[-]-profile ${escapeRegex(profile)}`;
40
+ const result = runner('pgrep', ['-f', pattern]);
41
+ if (result.status !== 0 && result.status !== 1) {
42
+ return { processes, error: result.stderr.trim() || `pgrep 退出码 ${result.status}` };
43
+ }
44
+ for (const line of result.stdout.split('\n')) {
45
+ const pid = Number(line.trim());
46
+ if (!Number.isInteger(pid) || pid <= 0)
47
+ continue;
48
+ const commandLine = readCommandLine(pid, runner);
49
+ if (isSelfCommand(commandLine))
50
+ continue;
51
+ const evidence = [`命令行:${commandLine}`];
52
+ const envHome = await procDshHome(pid);
53
+ let owner;
54
+ if (envHome !== undefined) {
55
+ // 有 DSH_HOME 环境变量:直接比较,最强证据。
56
+ evidence.push(`DSH_HOME=${envHome}`);
57
+ owner = envHome === dshHome ? 'proven' : 'foreign';
58
+ }
59
+ else {
60
+ // 无 /proc(macOS)或无 DSH_HOME 变量:命令行启发式。
61
+ // 命令行带目标 --profile 且属于 dsh/dsh-rsi/dsh plugin 操作 → proven;
62
+ // 否则无法证明归属 → foreign(fail-closed)。
63
+ const looksLikeDsh = /(^|[\s/])(dsh|dsh-rsi)([\s.]|$)/u.test(commandLine)
64
+ || commandLine.includes('dsh plugin');
65
+ owner = looksLikeDsh ? 'proven' : 'foreign';
66
+ if (!looksLikeDsh)
67
+ evidence.push('命令行不含 dsh 操作,无法证明归属');
68
+ }
69
+ processes.push({ pid, commandLine, evidence, owner });
70
+ }
71
+ }
72
+ return { processes };
73
+ }
74
+ /**
75
+ * 终止一组已证明归属的 PID。
76
+ * SIGTERM → 每 200ms 复检一次,最多 5s → 仍在则 SIGKILL。
77
+ * 复检时重新读命令行:若 PID 已被复用成别的进程,立即放弃该 PID(不杀)。
78
+ */
79
+ export async function terminatePids(pids, runner = defaultRunner, options = {}) {
80
+ const timeoutMs = options.timeoutMs ?? 5_000;
81
+ const intervalMs = options.intervalMs ?? 200;
82
+ const report = { terminated: 0, failed: [] };
83
+ if (pids.length === 0)
84
+ return report;
85
+ runner('kill', ['-TERM', ...pids.map(String)]);
86
+ const deadline = Date.now() + timeoutMs;
87
+ const stillAlive = [];
88
+ for (const pid of pids) {
89
+ const cmd = readCommandLine(pid, runner);
90
+ if (cmd.startsWith('PID ')) {
91
+ // 已经退出
92
+ report.terminated += 1;
93
+ continue;
94
+ }
95
+ stillAlive.push(pid);
96
+ }
97
+ if (stillAlive.length === 0)
98
+ return report;
99
+ while (Date.now() < deadline && stillAlive.length > 0) {
100
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
101
+ const gone = [];
102
+ for (const pid of stillAlive) {
103
+ const cmd = readCommandLine(pid, runner);
104
+ if (cmd.startsWith('PID ') || !cmd.includes('dsh')) {
105
+ // 退出了;或 PID 已被复用成非 dsh 进程(身份变化)→ 不杀,记 failed。
106
+ if (!cmd.startsWith('PID '))
107
+ report.failed.push(pid);
108
+ else
109
+ report.terminated += 1;
110
+ gone.push(pid);
111
+ }
112
+ }
113
+ for (const pid of gone) {
114
+ const idx = stillAlive.indexOf(pid);
115
+ if (idx >= 0)
116
+ stillAlive.splice(idx, 1);
117
+ }
118
+ }
119
+ if (stillAlive.length > 0) {
120
+ runner('kill', ['-KILL', ...stillAlive.map(String)]);
121
+ // 给 KILL 一点时间回收
122
+ await new Promise(resolve => setTimeout(resolve, 200));
123
+ for (const pid of stillAlive) {
124
+ const cmd = readCommandLine(pid, runner);
125
+ if (cmd.startsWith('PID ') || !cmd.includes('dsh')) {
126
+ if (cmd.startsWith('PID '))
127
+ report.terminated += 1;
128
+ else
129
+ report.failed.push(pid);
130
+ }
131
+ else {
132
+ report.failed.push(pid);
133
+ }
134
+ }
135
+ }
136
+ return report;
137
+ }
138
+ /** 供外部判断一个相对路径是否落在 dshHome 内(预留 cwd 证据扩展)。 */
139
+ export function pathInside(path, base) {
140
+ const rel = relative(base, path);
141
+ return rel === '' || (!rel.startsWith('..') && !rel.startsWith('/'));
142
+ }
143
+ //# sourceMappingURL=process-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-guard.js","sourceRoot":"","sources":["../src/process-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAEpC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAiCxC,qDAAqD;AACrD,SAAS,aAAa,CAAC,WAAmB;IACxC,OAAO,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC;WACjC,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC;WAC/B,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;AACvC,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,KAAK,CAAC,UAAU,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAA;AACzD,CAAC;AAED,8BAA8B;AAC9B,SAAS,eAAe,CAAC,GAAW,EAAE,MAAqB;IACzD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAA;IACjE,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,OAAO,GAAG,EAAE,CAAA;AAC5C,CAAC;AAED,6DAA6D;AAC7D,KAAK,UAAU,WAAW,CAAC,GAAW;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,SAAS,GAAG,UAAU,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;QACxE,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,IAAI,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;gBAAE,OAAO,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,SAAS,CAAA;QACxF,CAAC;QACD,OAAO,SAAS,CAAA;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAe,EACf,QAA2B,EAC3B,SAAwB,aAAa;IAErC,MAAM,SAAS,GAAqB,EAAE,CAAA;IACtC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,eAAe,WAAW,CAAC,OAAO,CAAC,EAAE,CAAA;QACrD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;QAC/C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,aAAa,MAAM,CAAC,MAAM,EAAE,EAAE,CAAA;QACnF,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;YAC/B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;gBAAE,SAAQ;YAChD,MAAM,WAAW,GAAG,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;YAChD,IAAI,aAAa,CAAC,WAAW,CAAC;gBAAE,SAAQ;YAExC,MAAM,QAAQ,GAAa,CAAC,OAAO,WAAW,EAAE,CAAC,CAAA;YACjD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAA;YACtC,IAAI,KAA2B,CAAA;YAE/B,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,6BAA6B;gBAC7B,QAAQ,CAAC,IAAI,CAAC,YAAY,OAAO,EAAE,CAAC,CAAA;gBACpC,KAAK,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;YACpD,CAAC;iBAAM,CAAC;gBACN,uCAAuC;gBACvC,2DAA2D;gBAC3D,mCAAmC;gBACnC,MAAM,YAAY,GAAG,kCAAkC,CAAC,IAAI,CAAC,WAAW,CAAC;uBACpE,WAAW,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;gBACvC,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;gBAC3C,IAAI,CAAC,YAAY;oBAAE,QAAQ,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAA;YACzD,CAAC;YACD,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;QACvD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,CAAA;AACtB,CAAC;AAQD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAuB,EACvB,SAAwB,aAAa,EACrC,UAAuD,EAAE;IAEzD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAA;IAC5C,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAA;IAC5C,MAAM,MAAM,GAAsB,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;IAC/D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAA;IAEpC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAA;IACvC,MAAM,UAAU,GAAa,EAAE,CAAA;IAC/B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QACxC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,OAAO;YACP,MAAM,CAAC,UAAU,IAAI,CAAC,CAAA;YACtB,SAAQ;QACV,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACtB,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAA;IAE1C,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAA;QAC7D,MAAM,IAAI,GAAa,EAAE,CAAA;QACzB,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;YACxC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACnD,8CAA8C;gBAC9C,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;oBAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;;oBAC/C,MAAM,CAAC,UAAU,IAAI,CAAC,CAAA;gBAC3B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YAChB,CAAC;QACH,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YACnC,IAAI,GAAG,IAAI,CAAC;gBAAE,UAAU,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACzC,CAAC;IACH,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;QACpD,gBAAgB;QAChB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAA;QACtD,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;YACxC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACnD,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;oBAAE,MAAM,CAAC,UAAU,IAAI,CAAC,CAAA;;oBAC7C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YAC9B,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YACzB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,IAAY;IACnD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAChC,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;AACtE,CAAC"}
package/lib/purge.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { HOME_LOCK_SUFFIX, TRANSACTION_SUFFIX } from './paths.ts';
2
+ import { type CommandRunner } from './run.ts';
3
+ export declare class PurgeError extends Error {
4
+ }
5
+ export interface PurgeOptions {
6
+ dshHome: string;
7
+ /** 用户 HOME:LaunchAgent/systemd unit、备份归档的落点基准。 */
8
+ home: string;
9
+ platform?: NodeJS.Platform;
10
+ /** 指定单 profile;省略表示全量清除整个 DSH home。 */
11
+ profile?: string;
12
+ backup: boolean;
13
+ keepKeychain: boolean;
14
+ removeHost: boolean;
15
+ dryRun: boolean;
16
+ /** UTC 时间戳生成器(测试可注入)。 */
17
+ stamp?: () => string;
18
+ runner?: CommandRunner;
19
+ /** --yes:自动终止已证明归属本 DSH home 的残留进程;否则仅报告并要求显式确认。 */
20
+ assumeYes?: boolean;
21
+ }
22
+ export interface PurgeReport {
23
+ scope: 'full' | 'profile';
24
+ profiles: string[];
25
+ serviceProfiles: string[];
26
+ backup?: {
27
+ archivePath: string;
28
+ bytes: number;
29
+ };
30
+ removedPaths: string[];
31
+ removedCredentials: string[];
32
+ credentialErrors: string[];
33
+ skippedServiceFiles: string[];
34
+ serviceErrors: string[];
35
+ keptCheckouts: string[];
36
+ hostRemoved: boolean;
37
+ hostPrefix?: string;
38
+ dryRun: boolean;
39
+ /** 停服后残留进程的处理结果(proven 终止 / foreign 拒绝)。 */
40
+ terminatedPids: number[];
41
+ failedPids: number[];
42
+ foreignProcesses: string[];
43
+ }
44
+ /**
45
+ * 彻底卸载(purge)编排:
46
+ * 1. 进程静止检查(活动 host 拒绝执行,不代用户 kill)
47
+ * 2. 停用并注销受管常驻服务(fail-closed:停服失败则不进入删除阶段)
48
+ * 3. tar.gz 备份整个 DSH home(默认开启)
49
+ * 4. 删除 profile/整个 home 及生命周期残留
50
+ * 5. 清理外部凭据库条目(--keep-keychain 保留)
51
+ * 6. 可选卸载全局 @deepseek-ai/dsh host(仅全量模式允许)
52
+ *
53
+ * local checkout 形态:只随 profile 目录移除其中的符号链接,checkout 源码绝不动。
54
+ */
55
+ export declare function runPurge(options: PurgeOptions): Promise<PurgeReport>;
56
+ /** 仅用于外部模块需要时引用(保持路径常量单一来源)。 */
57
+ export { HOME_LOCK_SUFFIX, TRANSACTION_SUFFIX };
58
+ //# sourceMappingURL=purge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"purge.d.ts","sourceRoot":"","sources":["../src/purge.ts"],"names":[],"mappings":"AAGA,OAAO,EAOL,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAsC,KAAK,aAAa,EAAE,MAAM,UAAU,CAAA;AAIjF,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAExC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;IAC1B,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;IACf,YAAY,EAAE,OAAO,CAAA;IACrB,UAAU,EAAE,OAAO,CAAA;IACnB,MAAM,EAAE,OAAO,CAAA;IACf,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,MAAM,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/C,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,kBAAkB,EAAE,MAAM,EAAE,CAAA;IAC5B,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,mBAAmB,EAAE,MAAM,EAAE,CAAA;IAC7B,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,WAAW,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,OAAO,CAAA;IACf,4CAA4C;IAC5C,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAC3B;AAeD;;;;;;;;;;GAUG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA0K1E;AAED,gCAAgC;AAChC,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,CAAA"}