@mison/wecom-cleaner 1.2.1 → 1.3.2
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 +68 -13
- package/docs/IMPLEMENTATION_PLAN.md +6 -6
- package/docs/NON_INTERACTIVE_SPEC.md +63 -0
- package/docs/TEST_MATRIX.md +14 -6
- package/docs/releases/v1.3.0.md +45 -0
- package/docs/releases/v1.3.1.md +31 -0
- package/docs/releases/v1.3.2.md +38 -0
- package/native/bin/darwin-arm64/wecom-cleaner-core +0 -0
- package/native/bin/darwin-x64/wecom-cleaner-core +0 -0
- package/native/manifest.json +5 -5
- package/native/zig/build.sh +3 -0
- package/native/zig/src/main.zig +2 -1
- package/native/zig/src/version.zig +1 -0
- package/package.json +1 -1
- package/skills/wecom-cleaner-agent/SKILL.md +14 -5
- package/skills/wecom-cleaner-agent/agents/openai.yaml +1 -1
- package/skills/wecom-cleaner-agent/references/commands.md +48 -9
- package/skills/wecom-cleaner-agent/scripts/check_update_report.sh +141 -0
- package/skills/wecom-cleaner-agent/scripts/cleanup_monthly_report.sh +37 -53
- package/skills/wecom-cleaner-agent/scripts/recycle_maintain_report.sh +39 -53
- package/skills/wecom-cleaner-agent/scripts/restore_batch_report.sh +34 -39
- package/skills/wecom-cleaner-agent/scripts/space_governance_report.sh +34 -49
- package/skills/wecom-cleaner-agent/scripts/upgrade_report.sh +240 -0
- package/src/cli.js +1274 -2
- package/src/config.js +65 -0
- package/src/constants.js +2 -0
- package/src/doctor.js +24 -0
- package/src/updater.js +503 -0
package/src/cli.js
CHANGED
|
@@ -33,6 +33,17 @@ import { runDoctor } from './doctor.js';
|
|
|
33
33
|
import { acquireLock, breakLock, LockHeldError } from './lock.js';
|
|
34
34
|
import { classifyErrorType, errorTypeToLabel } from './error-taxonomy.js';
|
|
35
35
|
import { collectRecycleStats, maintainRecycleBin, normalizeRecycleRetention } from './recycle-maintenance.js';
|
|
36
|
+
import {
|
|
37
|
+
applyUpdateCheckResult,
|
|
38
|
+
channelLabel,
|
|
39
|
+
checkLatestVersion,
|
|
40
|
+
normalizeSelfUpdateConfig,
|
|
41
|
+
normalizeUpgradeChannel,
|
|
42
|
+
runUpgrade,
|
|
43
|
+
shouldCheckForUpdate,
|
|
44
|
+
shouldSkipVersion,
|
|
45
|
+
updateWarningMessage,
|
|
46
|
+
} from './updater.js';
|
|
36
47
|
import {
|
|
37
48
|
compareMonthKey,
|
|
38
49
|
expandHome,
|
|
@@ -91,6 +102,8 @@ const NON_INTERACTIVE_ACTIONS = new Set([
|
|
|
91
102
|
MODES.RESTORE,
|
|
92
103
|
MODES.RECYCLE_MAINTAIN,
|
|
93
104
|
MODES.DOCTOR,
|
|
105
|
+
MODES.CHECK_UPDATE,
|
|
106
|
+
MODES.UPGRADE,
|
|
94
107
|
]);
|
|
95
108
|
const INTERACTIVE_MODE_ALIASES = new Map([
|
|
96
109
|
['start', MODES.START],
|
|
@@ -104,10 +117,36 @@ const INTERACTIVE_MODE_ALIASES = new Map([
|
|
|
104
117
|
['recycle_maintain', MODES.RECYCLE_MAINTAIN],
|
|
105
118
|
['recycle-maintain', MODES.RECYCLE_MAINTAIN],
|
|
106
119
|
['doctor', MODES.DOCTOR],
|
|
120
|
+
['check_update', MODES.CHECK_UPDATE],
|
|
121
|
+
['check-update', MODES.CHECK_UPDATE],
|
|
107
122
|
['settings', MODES.SETTINGS],
|
|
108
123
|
]);
|
|
109
124
|
const OUTPUT_JSON = 'json';
|
|
110
125
|
const OUTPUT_TEXT = 'text';
|
|
126
|
+
const RUN_TASK_PREVIEW = 'preview';
|
|
127
|
+
const RUN_TASK_EXECUTE = 'execute';
|
|
128
|
+
const RUN_TASK_PREVIEW_EXECUTE_VERIFY = 'preview-execute-verify';
|
|
129
|
+
const RUN_TASK_MODES = new Set([RUN_TASK_PREVIEW, RUN_TASK_EXECUTE, RUN_TASK_PREVIEW_EXECUTE_VERIFY]);
|
|
130
|
+
const SCAN_DEBUG_OFF = 'off';
|
|
131
|
+
const SCAN_DEBUG_SUMMARY = 'summary';
|
|
132
|
+
const SCAN_DEBUG_FULL = 'full';
|
|
133
|
+
const SCAN_DEBUG_LEVELS = new Set([SCAN_DEBUG_OFF, SCAN_DEBUG_SUMMARY, SCAN_DEBUG_FULL]);
|
|
134
|
+
const DESTRUCTIVE_ACTIONS = new Set([
|
|
135
|
+
MODES.CLEANUP_MONTHLY,
|
|
136
|
+
MODES.SPACE_GOVERNANCE,
|
|
137
|
+
MODES.RESTORE,
|
|
138
|
+
MODES.RECYCLE_MAINTAIN,
|
|
139
|
+
]);
|
|
140
|
+
const UPDATE_REPO_OWNER = 'MisonL';
|
|
141
|
+
const UPDATE_REPO_NAME = 'wecom-cleaner';
|
|
142
|
+
const UPDATE_TIMEOUT_MS = 2500;
|
|
143
|
+
|
|
144
|
+
function allowAutoUpdateByEnv() {
|
|
145
|
+
const raw = String(process.env.WECOM_CLEANER_AUTO_UPDATE || 'true')
|
|
146
|
+
.trim()
|
|
147
|
+
.toLowerCase();
|
|
148
|
+
return !['0', 'false', 'no', 'off'].includes(raw);
|
|
149
|
+
}
|
|
111
150
|
|
|
112
151
|
function isBackCommand(inputValue) {
|
|
113
152
|
const normalized = String(inputValue || '')
|
|
@@ -1772,6 +1811,58 @@ function uniqueStrings(values = []) {
|
|
|
1772
1811
|
return [...new Set(values.map((item) => String(item || '').trim()).filter(Boolean))];
|
|
1773
1812
|
}
|
|
1774
1813
|
|
|
1814
|
+
function normalizeRunTaskMode(rawMode) {
|
|
1815
|
+
const value = String(rawMode || '')
|
|
1816
|
+
.trim()
|
|
1817
|
+
.toLowerCase();
|
|
1818
|
+
if (!value) {
|
|
1819
|
+
return null;
|
|
1820
|
+
}
|
|
1821
|
+
if (!RUN_TASK_MODES.has(value)) {
|
|
1822
|
+
throw new UsageError(`参数 --run-task 的值无效: ${rawMode}`);
|
|
1823
|
+
}
|
|
1824
|
+
return value;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
function normalizeScanDebugLevel(rawLevel) {
|
|
1828
|
+
const value = String(rawLevel || SCAN_DEBUG_OFF)
|
|
1829
|
+
.trim()
|
|
1830
|
+
.toLowerCase();
|
|
1831
|
+
if (!value) {
|
|
1832
|
+
return SCAN_DEBUG_OFF;
|
|
1833
|
+
}
|
|
1834
|
+
if (!SCAN_DEBUG_LEVELS.has(value)) {
|
|
1835
|
+
throw new UsageError(`参数 --scan-debug 的值无效: ${rawLevel}`);
|
|
1836
|
+
}
|
|
1837
|
+
return value;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
function isDestructiveAction(action) {
|
|
1841
|
+
return DESTRUCTIVE_ACTIONS.has(action);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
function shouldAttachScanDebug(cliArgs) {
|
|
1845
|
+
return normalizeScanDebugLevel(cliArgs?.scanDebug) !== SCAN_DEBUG_OFF;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
function attachScanDebugData(baseData, cliArgs, summaryPayload, fullPayload = {}) {
|
|
1849
|
+
const level = normalizeScanDebugLevel(cliArgs?.scanDebug);
|
|
1850
|
+
if (level === SCAN_DEBUG_OFF) {
|
|
1851
|
+
return baseData || {};
|
|
1852
|
+
}
|
|
1853
|
+
const summary = summaryPayload && typeof summaryPayload === 'object' ? summaryPayload : {};
|
|
1854
|
+
const full = fullPayload && typeof fullPayload === 'object' ? fullPayload : {};
|
|
1855
|
+
return {
|
|
1856
|
+
...(baseData || {}),
|
|
1857
|
+
scanDebug: {
|
|
1858
|
+
level,
|
|
1859
|
+
summary,
|
|
1860
|
+
...(level === SCAN_DEBUG_FULL ? { full } : {}),
|
|
1861
|
+
generatedAt: Date.now(),
|
|
1862
|
+
},
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1775
1866
|
function normalizeActionOutputMode(cliArgs) {
|
|
1776
1867
|
if (cliArgs.output === OUTPUT_TEXT || cliArgs.output === OUTPUT_JSON) {
|
|
1777
1868
|
return cliArgs.output;
|
|
@@ -1798,6 +1889,12 @@ function actionFlagName(action) {
|
|
|
1798
1889
|
if (action === MODES.DOCTOR) {
|
|
1799
1890
|
return '--doctor';
|
|
1800
1891
|
}
|
|
1892
|
+
if (action === MODES.CHECK_UPDATE) {
|
|
1893
|
+
return '--check-update';
|
|
1894
|
+
}
|
|
1895
|
+
if (action === MODES.UPGRADE) {
|
|
1896
|
+
return '--upgrade <npm|github-script>';
|
|
1897
|
+
}
|
|
1801
1898
|
return `--${String(action || '').replace(/_/g, '-')}`;
|
|
1802
1899
|
}
|
|
1803
1900
|
|
|
@@ -1809,7 +1906,7 @@ function resolveActionFromCli(cliArgs, hasAnyArgs) {
|
|
|
1809
1906
|
throw new UsageError(
|
|
1810
1907
|
[
|
|
1811
1908
|
'无交互模式必须指定一个动作参数:',
|
|
1812
|
-
'--cleanup-monthly | --analysis-only | --space-governance | --restore-batch <batchId> | --recycle-maintain | --doctor',
|
|
1909
|
+
'--cleanup-monthly | --analysis-only | --space-governance | --restore-batch <batchId> | --recycle-maintain | --doctor | --check-update | --upgrade <npm|github-script>',
|
|
1813
1910
|
].join('\n')
|
|
1814
1911
|
);
|
|
1815
1912
|
}
|
|
@@ -1832,15 +1929,22 @@ function printCliUsage(appMeta) {
|
|
|
1832
1929
|
' --restore-batch <batchId>',
|
|
1833
1930
|
' --recycle-maintain',
|
|
1834
1931
|
' --doctor',
|
|
1932
|
+
' --check-update',
|
|
1933
|
+
' --upgrade <npm|github-script>',
|
|
1835
1934
|
'',
|
|
1836
1935
|
'常用选项:',
|
|
1837
1936
|
' --output json|text',
|
|
1838
1937
|
' --dry-run true|false',
|
|
1839
1938
|
' --yes',
|
|
1939
|
+
' --run-task preview|execute|preview-execute-verify',
|
|
1940
|
+
' --scan-debug off|summary|full',
|
|
1840
1941
|
' --accounts all|current|id1,id2',
|
|
1841
1942
|
' --months YYYY-MM,YYYY-MM',
|
|
1842
1943
|
' --cutoff-month YYYY-MM',
|
|
1843
1944
|
' --categories key1,key2',
|
|
1945
|
+
' --upgrade-version x.y.z',
|
|
1946
|
+
' --upgrade-channel stable|pre',
|
|
1947
|
+
' --upgrade-yes',
|
|
1844
1948
|
' --root <path>',
|
|
1845
1949
|
' --state-root <path>',
|
|
1846
1950
|
'',
|
|
@@ -1851,6 +1955,7 @@ function printCliUsage(appMeta) {
|
|
|
1851
1955
|
'示例:',
|
|
1852
1956
|
' wecom-cleaner --doctor',
|
|
1853
1957
|
' wecom-cleaner --cleanup-monthly --accounts all --cutoff-month 2024-04',
|
|
1958
|
+
' wecom-cleaner --cleanup-monthly --cutoff-month 2024-04 --accounts all --run-task preview-execute-verify --yes',
|
|
1854
1959
|
' wecom-cleaner --cleanup-monthly --accounts all --cutoff-month 2024-04 --dry-run false --yes',
|
|
1855
1960
|
];
|
|
1856
1961
|
console.log(lines.join('\n'));
|
|
@@ -1869,6 +1974,9 @@ function resolveInteractiveStartMode(cliArgs) {
|
|
|
1869
1974
|
}
|
|
1870
1975
|
|
|
1871
1976
|
if (NON_INTERACTIVE_ACTIONS.has(cliArgs.action)) {
|
|
1977
|
+
if (cliArgs.action === MODES.UPGRADE) {
|
|
1978
|
+
throw new UsageError('参数 --upgrade 仅支持无交互模式,请移除 --interactive 后重试。');
|
|
1979
|
+
}
|
|
1872
1980
|
return cliArgs.action;
|
|
1873
1981
|
}
|
|
1874
1982
|
return MODES.START;
|
|
@@ -2120,6 +2228,180 @@ function toStructuredError(item = {}, fallbackCode = 'E_ACTION_FAILED') {
|
|
|
2120
2228
|
};
|
|
2121
2229
|
}
|
|
2122
2230
|
|
|
2231
|
+
function summarizeDimensionRows(rows, { labelKey = 'label', countKey = 'targetCount' } = {}, limit = 20) {
|
|
2232
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
2233
|
+
return list.slice(0, limit).map((row) => ({
|
|
2234
|
+
label:
|
|
2235
|
+
row?.[labelKey] ||
|
|
2236
|
+
row?.categoryLabel ||
|
|
2237
|
+
row?.targetLabel ||
|
|
2238
|
+
row?.monthKey ||
|
|
2239
|
+
row?.rootPath ||
|
|
2240
|
+
row?.accountShortId ||
|
|
2241
|
+
'-',
|
|
2242
|
+
count: Number(row?.[countKey] || row?.count || 0),
|
|
2243
|
+
sizeBytes: Number(row?.sizeBytes || 0),
|
|
2244
|
+
}));
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
function buildUserFacingSummary(action, result) {
|
|
2248
|
+
const summary = result?.summary || {};
|
|
2249
|
+
const report = result?.data?.report || {};
|
|
2250
|
+
const matched = report?.matched || {};
|
|
2251
|
+
|
|
2252
|
+
if (action === MODES.CLEANUP_MONTHLY) {
|
|
2253
|
+
return {
|
|
2254
|
+
scope: {
|
|
2255
|
+
accountCount: Number(summary.accountCount || 0),
|
|
2256
|
+
monthCount: Number(summary.monthCount || 0),
|
|
2257
|
+
categoryCount: Number(summary.categoryCount || 0),
|
|
2258
|
+
rootPathCount: Number(summary.rootPathCount || 0),
|
|
2259
|
+
cutoffMonth: summary.cutoffMonth || null,
|
|
2260
|
+
monthRange: {
|
|
2261
|
+
from: summary.matchedMonthStart || matched?.monthRange?.from || null,
|
|
2262
|
+
to: summary.matchedMonthEnd || matched?.monthRange?.to || null,
|
|
2263
|
+
},
|
|
2264
|
+
},
|
|
2265
|
+
result: {
|
|
2266
|
+
noTarget: Boolean(summary.noTarget),
|
|
2267
|
+
matchedTargets: Number(summary.matchedTargets || 0),
|
|
2268
|
+
matchedBytes: Number(summary.matchedBytes || 0),
|
|
2269
|
+
reclaimedBytes: Number(summary.reclaimedBytes || 0),
|
|
2270
|
+
successCount: Number(summary.successCount || 0),
|
|
2271
|
+
skippedCount: Number(summary.skippedCount || 0),
|
|
2272
|
+
failedCount: Number(summary.failedCount || 0),
|
|
2273
|
+
batchId: summary.batchId || null,
|
|
2274
|
+
},
|
|
2275
|
+
byMonth: summarizeDimensionRows(matched.monthStats, { labelKey: 'monthKey' }),
|
|
2276
|
+
byCategory: summarizeDimensionRows(matched.categoryStats, { labelKey: 'categoryLabel' }),
|
|
2277
|
+
byRoot: summarizeDimensionRows(matched.rootStats, { labelKey: 'rootPath' }),
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
if (action === MODES.ANALYSIS_ONLY) {
|
|
2282
|
+
return {
|
|
2283
|
+
scope: {
|
|
2284
|
+
accountCount: Number(summary.accountCount || 0),
|
|
2285
|
+
matchedAccountCount: Number(summary.matchedAccountCount || 0),
|
|
2286
|
+
categoryCount: Number(summary.categoryCount || 0),
|
|
2287
|
+
monthBucketCount: Number(summary.monthBucketCount || 0),
|
|
2288
|
+
},
|
|
2289
|
+
result: {
|
|
2290
|
+
targetCount: Number(summary.targetCount || 0),
|
|
2291
|
+
totalBytes: Number(summary.totalBytes || 0),
|
|
2292
|
+
},
|
|
2293
|
+
byMonth: summarizeDimensionRows(matched.monthStats, { labelKey: 'monthKey' }),
|
|
2294
|
+
byCategory: summarizeDimensionRows(matched.categoryStats, { labelKey: 'categoryLabel' }),
|
|
2295
|
+
byRoot: summarizeDimensionRows(matched.rootStats, { labelKey: 'rootPath' }),
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
if (action === MODES.SPACE_GOVERNANCE) {
|
|
2300
|
+
return {
|
|
2301
|
+
scope: {
|
|
2302
|
+
accountCount: Number(summary.accountCount || 0),
|
|
2303
|
+
tierCount: Number(summary.tierCount || 0),
|
|
2304
|
+
targetTypeCount: Number(summary.targetTypeCount || 0),
|
|
2305
|
+
rootPathCount: Number(summary.rootPathCount || 0),
|
|
2306
|
+
},
|
|
2307
|
+
result: {
|
|
2308
|
+
noTarget: Boolean(summary.noTarget),
|
|
2309
|
+
matchedTargets: Number(summary.matchedTargets || 0),
|
|
2310
|
+
matchedBytes: Number(summary.matchedBytes || 0),
|
|
2311
|
+
reclaimedBytes: Number(summary.reclaimedBytes || 0),
|
|
2312
|
+
successCount: Number(summary.successCount || 0),
|
|
2313
|
+
skippedCount: Number(summary.skippedCount || 0),
|
|
2314
|
+
failedCount: Number(summary.failedCount || 0),
|
|
2315
|
+
batchId: summary.batchId || null,
|
|
2316
|
+
},
|
|
2317
|
+
byTier: summarizeDimensionRows(matched.byTier, { labelKey: 'tierLabel' }),
|
|
2318
|
+
byCategory: summarizeDimensionRows(matched.byTargetType, { labelKey: 'targetLabel' }),
|
|
2319
|
+
byRoot: summarizeDimensionRows(matched.byRoot, { labelKey: 'rootPath' }),
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
if (action === MODES.RESTORE) {
|
|
2324
|
+
return {
|
|
2325
|
+
scope: {
|
|
2326
|
+
entryCount: Number(summary.entryCount || 0),
|
|
2327
|
+
conflictStrategy: summary.conflictStrategy || null,
|
|
2328
|
+
rootPathCount: Number(summary.rootPathCount || 0),
|
|
2329
|
+
},
|
|
2330
|
+
result: {
|
|
2331
|
+
batchId: summary.batchId || null,
|
|
2332
|
+
matchedBytes: Number(summary.matchedBytes || 0),
|
|
2333
|
+
restoredBytes: Number(summary.restoredBytes || 0),
|
|
2334
|
+
successCount: Number(summary.successCount || 0),
|
|
2335
|
+
skippedCount: Number(summary.skippedCount || 0),
|
|
2336
|
+
failedCount: Number(summary.failedCount || 0),
|
|
2337
|
+
},
|
|
2338
|
+
byMonth: summarizeDimensionRows(matched.byMonth, { labelKey: 'monthKey' }),
|
|
2339
|
+
byCategory: summarizeDimensionRows(matched.byCategory, { labelKey: 'categoryLabel' }),
|
|
2340
|
+
byRoot: summarizeDimensionRows(matched.byRoot, { labelKey: 'rootPath' }),
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
if (action === MODES.RECYCLE_MAINTAIN) {
|
|
2345
|
+
return {
|
|
2346
|
+
scope: {
|
|
2347
|
+
candidateCount: Number(summary.candidateCount || 0),
|
|
2348
|
+
selectedByAge: Number(summary.selectedByAge || 0),
|
|
2349
|
+
selectedBySize: Number(summary.selectedBySize || 0),
|
|
2350
|
+
},
|
|
2351
|
+
result: {
|
|
2352
|
+
status: summary.status || null,
|
|
2353
|
+
deletedBatches: Number(summary.deletedBatches || 0),
|
|
2354
|
+
deletedBytes: Number(summary.deletedBytes || 0),
|
|
2355
|
+
failedBatches: Number(summary.failedBatches || 0),
|
|
2356
|
+
remainingBatches: Number(summary.remainingBatches || 0),
|
|
2357
|
+
remainingBytes: Number(summary.remainingBytes || 0),
|
|
2358
|
+
},
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
if (action === MODES.DOCTOR) {
|
|
2363
|
+
return {
|
|
2364
|
+
scope: {
|
|
2365
|
+
platform: result?.data?.runtime?.targetTag || null,
|
|
2366
|
+
},
|
|
2367
|
+
result: {
|
|
2368
|
+
overall: summary.overall || null,
|
|
2369
|
+
pass: Number(summary.pass || 0),
|
|
2370
|
+
warn: Number(summary.warn || 0),
|
|
2371
|
+
fail: Number(summary.fail || 0),
|
|
2372
|
+
},
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
if (action === MODES.CHECK_UPDATE) {
|
|
2377
|
+
return {
|
|
2378
|
+
result: {
|
|
2379
|
+
checked: Boolean(summary.checked),
|
|
2380
|
+
hasUpdate: Boolean(summary.hasUpdate),
|
|
2381
|
+
currentVersion: summary.currentVersion || null,
|
|
2382
|
+
latestVersion: summary.latestVersion || null,
|
|
2383
|
+
source: summary.source || null,
|
|
2384
|
+
channel: summary.channel || null,
|
|
2385
|
+
},
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
if (action === MODES.UPGRADE) {
|
|
2390
|
+
return {
|
|
2391
|
+
result: {
|
|
2392
|
+
executed: Boolean(summary.executed),
|
|
2393
|
+
method: summary.method || null,
|
|
2394
|
+
targetVersion: summary.targetVersion || null,
|
|
2395
|
+
status: hasDisplayValue(summary.status) ? Number(summary.status) : null,
|
|
2396
|
+
},
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
return {
|
|
2401
|
+
result: summary,
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2123
2405
|
const ACTION_DISPLAY_NAMES = new Map([
|
|
2124
2406
|
[MODES.CLEANUP_MONTHLY, '年月清理'],
|
|
2125
2407
|
[MODES.ANALYSIS_ONLY, '会话分析(只读)'],
|
|
@@ -2127,6 +2409,8 @@ const ACTION_DISPLAY_NAMES = new Map([
|
|
|
2127
2409
|
[MODES.RESTORE, '恢复已删除批次'],
|
|
2128
2410
|
[MODES.RECYCLE_MAINTAIN, '回收区治理'],
|
|
2129
2411
|
[MODES.DOCTOR, '系统自检'],
|
|
2412
|
+
[MODES.CHECK_UPDATE, '检查更新'],
|
|
2413
|
+
[MODES.UPGRADE, '程序升级'],
|
|
2130
2414
|
]);
|
|
2131
2415
|
|
|
2132
2416
|
const CONFLICT_STRATEGY_DISPLAY = new Map([
|
|
@@ -2744,6 +3028,61 @@ function printDoctorTextResult(payload) {
|
|
|
2744
3028
|
printRuntimeAndRisk(payload);
|
|
2745
3029
|
}
|
|
2746
3030
|
|
|
3031
|
+
function printCheckUpdateTextResult(payload) {
|
|
3032
|
+
const summary = payload.summary || {};
|
|
3033
|
+
const update = payload.data?.update || {};
|
|
3034
|
+
const conclusion = summary.hasUpdate
|
|
3035
|
+
? `检测到新版本 v${summary.latestVersion},可选择 npm 或 GitHub 脚本升级。`
|
|
3036
|
+
: summary.checked
|
|
3037
|
+
? '当前已是最新版本。'
|
|
3038
|
+
: '本次更新检查失败,请稍后重试。';
|
|
3039
|
+
|
|
3040
|
+
printTextRows('任务结论', [
|
|
3041
|
+
{ label: '动作', value: actionDisplayName(payload.action) },
|
|
3042
|
+
{ label: '结论', value: conclusion },
|
|
3043
|
+
{ label: '状态', value: summary.checked ? '检查完成' : '检查失败' },
|
|
3044
|
+
]);
|
|
3045
|
+
|
|
3046
|
+
printTextRows('检查结果', [
|
|
3047
|
+
{ label: '当前版本', value: summary.currentVersion || '-' },
|
|
3048
|
+
{ label: '最新版本', value: summary.latestVersion || '-' },
|
|
3049
|
+
{ label: '来源', value: summary.source || '-' },
|
|
3050
|
+
{ label: '通道', value: channelLabel(summary.channel) },
|
|
3051
|
+
{ label: '跳过提醒', value: formatYesNo(Boolean(summary.skippedByUser)) },
|
|
3052
|
+
{ label: '检查时间', value: formatLocalDate(update.checkedAt || Date.now()) },
|
|
3053
|
+
]);
|
|
3054
|
+
|
|
3055
|
+
printRuntimeAndRisk(payload);
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
function printUpgradeTextResult(payload) {
|
|
3059
|
+
const summary = payload.summary || {};
|
|
3060
|
+
const upgrade = payload.data?.upgrade || {};
|
|
3061
|
+
const executed = Boolean(summary.executed);
|
|
3062
|
+
const conclusion = !executed
|
|
3063
|
+
? summary.reason === 'already_latest'
|
|
3064
|
+
? '当前已是最新版本,无需执行升级。'
|
|
3065
|
+
: '升级前置检查失败,未执行升级。'
|
|
3066
|
+
: payload.ok
|
|
3067
|
+
? '升级已执行成功,建议重启程序后继续使用。'
|
|
3068
|
+
: '升级执行失败,请按错误提示排查。';
|
|
3069
|
+
|
|
3070
|
+
printTextRows('任务结论', [
|
|
3071
|
+
{ label: '动作', value: actionDisplayName(payload.action) },
|
|
3072
|
+
{ label: '结论', value: conclusion },
|
|
3073
|
+
{ label: '升级方式', value: summary.method || '-' },
|
|
3074
|
+
{ label: '目标版本', value: summary.targetVersion || '-' },
|
|
3075
|
+
]);
|
|
3076
|
+
|
|
3077
|
+
printTextRows('执行明细', [
|
|
3078
|
+
{ label: '已执行升级', value: formatYesNo(executed) },
|
|
3079
|
+
{ label: '退出码', value: hasDisplayValue(summary.status) ? summary.status : '-' },
|
|
3080
|
+
{ label: '命令', value: summary.command || upgrade.command || '-' },
|
|
3081
|
+
]);
|
|
3082
|
+
|
|
3083
|
+
printRuntimeAndRisk(payload);
|
|
3084
|
+
}
|
|
3085
|
+
|
|
2747
3086
|
function printGenericTextResult(payload) {
|
|
2748
3087
|
printTextRows('任务结论', [
|
|
2749
3088
|
{ label: '动作', value: actionDisplayName(payload.action) },
|
|
@@ -2757,7 +3096,37 @@ function printGenericTextResult(payload) {
|
|
|
2757
3096
|
printRuntimeAndRisk(payload);
|
|
2758
3097
|
}
|
|
2759
3098
|
|
|
3099
|
+
function printTaskPhasesText(payload) {
|
|
3100
|
+
const phases = Array.isArray(payload?.data?.taskPhases) ? payload.data.taskPhases : [];
|
|
3101
|
+
if (phases.length === 0) {
|
|
3102
|
+
return;
|
|
3103
|
+
}
|
|
3104
|
+
printTextRows(
|
|
3105
|
+
'任务流程',
|
|
3106
|
+
phases.map((phase) => {
|
|
3107
|
+
const summaryText =
|
|
3108
|
+
phase.status === 'completed'
|
|
3109
|
+
? `命中 ${formatCount(phase?.stats?.matchedTargets)} 项,成功 ${formatCount(phase?.stats?.successCount)} 项,失败 ${formatCount(phase?.stats?.failedCount)} 项,释放 ${formatBytesSafe(phase?.stats?.reclaimedBytes)}`
|
|
3110
|
+
: `已跳过(${phase.reason || '无'})`;
|
|
3111
|
+
return {
|
|
3112
|
+
label: phase.name,
|
|
3113
|
+
value: `${phase.status}${phase.ok === false ? '(失败)' : ''}`,
|
|
3114
|
+
note: summaryText,
|
|
3115
|
+
};
|
|
3116
|
+
})
|
|
3117
|
+
);
|
|
3118
|
+
const taskCard = payload?.data?.taskCard;
|
|
3119
|
+
if (taskCard && typeof taskCard === 'object') {
|
|
3120
|
+
printTextRows('流程结论', [
|
|
3121
|
+
{ label: '模式', value: taskCard.mode || '-' },
|
|
3122
|
+
{ label: '决策', value: taskCard.decision || '-' },
|
|
3123
|
+
{ label: '结论', value: taskCard.conclusion || '-' },
|
|
3124
|
+
]);
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
|
|
2760
3128
|
function printNonInteractiveTextResult(payload) {
|
|
3129
|
+
printTaskPhasesText(payload);
|
|
2761
3130
|
if (payload.action === MODES.CLEANUP_MONTHLY) {
|
|
2762
3131
|
printCleanupTextResult(payload);
|
|
2763
3132
|
return;
|
|
@@ -2782,6 +3151,14 @@ function printNonInteractiveTextResult(payload) {
|
|
|
2782
3151
|
printDoctorTextResult(payload);
|
|
2783
3152
|
return;
|
|
2784
3153
|
}
|
|
3154
|
+
if (payload.action === MODES.CHECK_UPDATE) {
|
|
3155
|
+
printCheckUpdateTextResult(payload);
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
if (payload.action === MODES.UPGRADE) {
|
|
3159
|
+
printUpgradeTextResult(payload);
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
2785
3162
|
printGenericTextResult(payload);
|
|
2786
3163
|
}
|
|
2787
3164
|
|
|
@@ -2846,6 +3223,32 @@ async function runCleanupModeNonInteractive(context, cliArgs, warnings = []) {
|
|
|
2846
3223
|
const targets = scan.targets || [];
|
|
2847
3224
|
const matchedBytes = targets.reduce((total, item) => total + Number(item?.sizeBytes || 0), 0);
|
|
2848
3225
|
const matchedReport = buildCleanupTargetReport(targets, { topPathLimit: 20 });
|
|
3226
|
+
const scanDebugSummary = {
|
|
3227
|
+
action: MODES.CLEANUP_MONTHLY,
|
|
3228
|
+
engineReady: context.nativeCorePath ? 'zig' : 'node',
|
|
3229
|
+
engineUsed: scan.engineUsed || 'node',
|
|
3230
|
+
nativeFallbackReason: scan.nativeFallbackReason || null,
|
|
3231
|
+
selectedAccountCount: accountResolved.selectedAccountIds.length,
|
|
3232
|
+
availableMonthCount: availableMonths.length,
|
|
3233
|
+
selectedMonthCount: monthFilters.length,
|
|
3234
|
+
selectedCategoryCount: categoryKeys.length,
|
|
3235
|
+
selectedExternalRootCount: externalResolved.roots.length,
|
|
3236
|
+
includeNonMonthDirs,
|
|
3237
|
+
matchedTargets: targets.length,
|
|
3238
|
+
matchedBytes,
|
|
3239
|
+
};
|
|
3240
|
+
const scanDebugFull = {
|
|
3241
|
+
selectedAccounts: accountResolved.selectedAccountIds,
|
|
3242
|
+
selectedMonths: monthFilters,
|
|
3243
|
+
selectedCategories: categoryKeys,
|
|
3244
|
+
availableMonths,
|
|
3245
|
+
selectedExternalRoots: externalResolved.roots,
|
|
3246
|
+
externalDetectionMeta: detectedExternalStorage.meta || null,
|
|
3247
|
+
matchedTopPaths: matchedReport.topPaths || [],
|
|
3248
|
+
matchedCategoryStats: matchedReport.categoryStats || [],
|
|
3249
|
+
matchedMonthStats: matchedReport.monthStats || [],
|
|
3250
|
+
matchedRootStats: matchedReport.rootStats || [],
|
|
3251
|
+
};
|
|
2849
3252
|
if (targets.length === 0) {
|
|
2850
3253
|
return {
|
|
2851
3254
|
ok: true,
|
|
@@ -2879,6 +3282,7 @@ async function runCleanupModeNonInteractive(context, cliArgs, warnings = []) {
|
|
|
2879
3282
|
matched: matchedReport,
|
|
2880
3283
|
executed: null,
|
|
2881
3284
|
},
|
|
3285
|
+
...attachScanDebugData({}, cliArgs, scanDebugSummary, scanDebugFull),
|
|
2882
3286
|
},
|
|
2883
3287
|
};
|
|
2884
3288
|
}
|
|
@@ -2918,6 +3322,7 @@ async function runCleanupModeNonInteractive(context, cliArgs, warnings = []) {
|
|
|
2918
3322
|
matched: matchedReport,
|
|
2919
3323
|
executed: result.breakdown || null,
|
|
2920
3324
|
},
|
|
3325
|
+
...attachScanDebugData({}, cliArgs, scanDebugSummary, scanDebugFull),
|
|
2921
3326
|
},
|
|
2922
3327
|
};
|
|
2923
3328
|
}
|
|
@@ -2952,6 +3357,29 @@ async function runAnalysisModeNonInteractive(context, cliArgs, warnings = []) {
|
|
|
2952
3357
|
warnings.push(result.nativeFallbackReason);
|
|
2953
3358
|
}
|
|
2954
3359
|
const analysisReport = buildCleanupTargetReport(result.targets, { topPathLimit: 20 });
|
|
3360
|
+
const scanDebugSummary = {
|
|
3361
|
+
action: MODES.ANALYSIS_ONLY,
|
|
3362
|
+
engineReady: context.nativeCorePath ? 'zig' : 'node',
|
|
3363
|
+
engineUsed: result.engineUsed || 'node',
|
|
3364
|
+
nativeFallbackReason: result.nativeFallbackReason || null,
|
|
3365
|
+
selectedAccountCount: accountResolved.selectedAccountIds.length,
|
|
3366
|
+
selectedCategoryCount: categoryKeys.length,
|
|
3367
|
+
selectedExternalRootCount: externalResolved.roots.length,
|
|
3368
|
+
matchedTargets: result.targets.length,
|
|
3369
|
+
matchedBytes: result.totalBytes,
|
|
3370
|
+
matchedAccountCount: result.accountsSummary.length,
|
|
3371
|
+
monthBucketCount: result.monthsSummary.length,
|
|
3372
|
+
};
|
|
3373
|
+
const scanDebugFull = {
|
|
3374
|
+
selectedAccounts: accountResolved.selectedAccountIds,
|
|
3375
|
+
selectedCategories: categoryKeys,
|
|
3376
|
+
selectedExternalRoots: externalResolved.roots,
|
|
3377
|
+
externalDetectionMeta: detectedExternalStorage.meta || null,
|
|
3378
|
+
accountsSummary: result.accountsSummary,
|
|
3379
|
+
categoriesSummary: result.categoriesSummary,
|
|
3380
|
+
monthsSummary: result.monthsSummary,
|
|
3381
|
+
matchedTopPaths: analysisReport.topPaths || [],
|
|
3382
|
+
};
|
|
2955
3383
|
|
|
2956
3384
|
return {
|
|
2957
3385
|
ok: true,
|
|
@@ -2978,6 +3406,7 @@ async function runAnalysisModeNonInteractive(context, cliArgs, warnings = []) {
|
|
|
2978
3406
|
report: {
|
|
2979
3407
|
matched: analysisReport,
|
|
2980
3408
|
},
|
|
3409
|
+
...attachScanDebugData({}, cliArgs, scanDebugSummary, scanDebugFull),
|
|
2981
3410
|
},
|
|
2982
3411
|
};
|
|
2983
3412
|
}
|
|
@@ -3036,6 +3465,33 @@ async function runSpaceGovernanceModeNonInteractive(context, cliArgs, warnings =
|
|
|
3036
3465
|
const allowRecentActive = cliArgs.allowRecentActive === true;
|
|
3037
3466
|
const dryRun = resolveDestructiveDryRun(cliArgs);
|
|
3038
3467
|
const matchedReport = buildGovernanceTargetReport(selectedTargets, { topPathLimit: 20 });
|
|
3468
|
+
const scanDebugSummary = {
|
|
3469
|
+
action: MODES.SPACE_GOVERNANCE,
|
|
3470
|
+
engineReady: context.nativeCorePath ? 'zig' : 'node',
|
|
3471
|
+
engineUsed: scan.engineUsed || 'node',
|
|
3472
|
+
nativeFallbackReason: scan.nativeFallbackReason || null,
|
|
3473
|
+
selectedAccountCount: accountResolved.selectedAccountIds.length,
|
|
3474
|
+
selectedExternalRootCount: externalResolved.roots.length,
|
|
3475
|
+
scannedTargets: scan.targets.length,
|
|
3476
|
+
selectableTargets: selectableTargets.length,
|
|
3477
|
+
selectedTargets: selectedTargets.length,
|
|
3478
|
+
selectedByCliTargets: selectedById.length,
|
|
3479
|
+
allowRecentActive,
|
|
3480
|
+
matchedBytes: matchedReport.totalBytes,
|
|
3481
|
+
};
|
|
3482
|
+
const scanDebugFull = {
|
|
3483
|
+
selectedAccounts: accountResolved.selectedAccountIds,
|
|
3484
|
+
selectedExternalRoots: externalResolved.roots,
|
|
3485
|
+
externalDetectionMeta: detectedExternalStorage.meta || null,
|
|
3486
|
+
tierFilters: tierFilterSet ? [...tierFilterSet] : [],
|
|
3487
|
+
cliSelectedTargets: selectedById,
|
|
3488
|
+
selectedTargetIds: selectedTargets.map((item) => item.id),
|
|
3489
|
+
matchedByTier: matchedReport.byTier || [],
|
|
3490
|
+
matchedByTargetType: matchedReport.byTargetType || [],
|
|
3491
|
+
matchedByRoot: matchedReport.byRoot || [],
|
|
3492
|
+
matchedTopPaths: matchedReport.topPaths || [],
|
|
3493
|
+
scanByTier: scan.byTier || [],
|
|
3494
|
+
};
|
|
3039
3495
|
const governanceRoot = inferDataRootFromProfilesRoot(config.rootDir);
|
|
3040
3496
|
const governanceAllowedRoots = governanceRoot
|
|
3041
3497
|
? [governanceRoot, ...externalResolved.roots]
|
|
@@ -3068,6 +3524,7 @@ async function runSpaceGovernanceModeNonInteractive(context, cliArgs, warnings =
|
|
|
3068
3524
|
matched: matchedReport,
|
|
3069
3525
|
executed: null,
|
|
3070
3526
|
},
|
|
3527
|
+
...attachScanDebugData({}, cliArgs, scanDebugSummary, scanDebugFull),
|
|
3071
3528
|
},
|
|
3072
3529
|
};
|
|
3073
3530
|
}
|
|
@@ -3113,6 +3570,7 @@ async function runSpaceGovernanceModeNonInteractive(context, cliArgs, warnings =
|
|
|
3113
3570
|
matched: matchedReport,
|
|
3114
3571
|
executed: result.breakdown || null,
|
|
3115
3572
|
},
|
|
3573
|
+
...attachScanDebugData({}, cliArgs, scanDebugSummary, scanDebugFull),
|
|
3116
3574
|
},
|
|
3117
3575
|
};
|
|
3118
3576
|
}
|
|
@@ -3158,6 +3616,25 @@ async function runRestoreModeNonInteractive(context, cliArgs, warnings = []) {
|
|
|
3158
3616
|
onConflict: async () => ({ action: conflictStrategy, applyToAll: true }),
|
|
3159
3617
|
});
|
|
3160
3618
|
const matchedReport = buildRestoreBatchTargetReport(batch.entries, { topPathLimit: 20 });
|
|
3619
|
+
const scanDebugSummary = {
|
|
3620
|
+
action: MODES.RESTORE,
|
|
3621
|
+
selectedExternalRootCount: externalResolved.roots.length,
|
|
3622
|
+
governanceRoot: governanceRoot || null,
|
|
3623
|
+
batchEntryCount: batch.entries.length,
|
|
3624
|
+
matchedBytes: matchedReport.totalBytes,
|
|
3625
|
+
dryRun,
|
|
3626
|
+
};
|
|
3627
|
+
const scanDebugFull = {
|
|
3628
|
+
batchId: batch.batchId,
|
|
3629
|
+
conflictStrategy,
|
|
3630
|
+
selectedExternalRoots: externalResolved.roots,
|
|
3631
|
+
governanceAllowRoots: governanceAllowRoots,
|
|
3632
|
+
matchedByScope: matchedReport.byScope || [],
|
|
3633
|
+
matchedByCategory: matchedReport.byCategory || [],
|
|
3634
|
+
matchedByMonth: matchedReport.byMonth || [],
|
|
3635
|
+
matchedByRoot: matchedReport.byRoot || [],
|
|
3636
|
+
topEntries: matchedReport.topEntries || [],
|
|
3637
|
+
};
|
|
3161
3638
|
|
|
3162
3639
|
return {
|
|
3163
3640
|
ok: result.failCount === 0,
|
|
@@ -3185,6 +3662,7 @@ async function runRestoreModeNonInteractive(context, cliArgs, warnings = []) {
|
|
|
3185
3662
|
matched: matchedReport,
|
|
3186
3663
|
executed: result.breakdown || null,
|
|
3187
3664
|
},
|
|
3665
|
+
...attachScanDebugData({}, cliArgs, scanDebugSummary, scanDebugFull),
|
|
3188
3666
|
},
|
|
3189
3667
|
};
|
|
3190
3668
|
}
|
|
@@ -3226,6 +3704,25 @@ async function runRecycleMaintainModeNonInteractive(context, cliArgs, warnings =
|
|
|
3226
3704
|
};
|
|
3227
3705
|
await saveConfig(config);
|
|
3228
3706
|
}
|
|
3707
|
+
const scanDebugSummary = {
|
|
3708
|
+
action: MODES.RECYCLE_MAINTAIN,
|
|
3709
|
+
dryRun,
|
|
3710
|
+
candidateCount: result.candidateCount,
|
|
3711
|
+
selectedByAge: result.selectedByAge,
|
|
3712
|
+
selectedBySize: result.selectedBySize,
|
|
3713
|
+
deletedBatches: result.deletedBatches,
|
|
3714
|
+
deletedBytes: result.deletedBytes,
|
|
3715
|
+
failedBatches: result.failBatches,
|
|
3716
|
+
};
|
|
3717
|
+
const scanDebugFull = {
|
|
3718
|
+
policy,
|
|
3719
|
+
before: result.before || null,
|
|
3720
|
+
after: result.after || null,
|
|
3721
|
+
thresholdBytes: result.thresholdBytes,
|
|
3722
|
+
overThreshold: result.overThreshold,
|
|
3723
|
+
selectedCandidates: result.selectedCandidates || [],
|
|
3724
|
+
operations: result.operations || [],
|
|
3725
|
+
};
|
|
3229
3726
|
|
|
3230
3727
|
return {
|
|
3231
3728
|
ok: result.failBatches === 0,
|
|
@@ -3259,6 +3756,7 @@ async function runRecycleMaintainModeNonInteractive(context, cliArgs, warnings =
|
|
|
3259
3756
|
selectedCandidates: result.selectedCandidates || [],
|
|
3260
3757
|
operations: result.operations || [],
|
|
3261
3758
|
},
|
|
3759
|
+
...attachScanDebugData({}, cliArgs, scanDebugSummary, scanDebugFull),
|
|
3262
3760
|
},
|
|
3263
3761
|
};
|
|
3264
3762
|
}
|
|
@@ -3287,6 +3785,337 @@ async function runDoctorModeNonInteractive(context, _cliArgs, warnings = []) {
|
|
|
3287
3785
|
};
|
|
3288
3786
|
}
|
|
3289
3787
|
|
|
3788
|
+
function resolveUpdateChannel(cliArgs, config) {
|
|
3789
|
+
const fallback = normalizeSelfUpdateConfig(config?.selfUpdate).channel;
|
|
3790
|
+
return normalizeUpgradeChannel(cliArgs.upgradeChannel, fallback);
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
function buildUpdateData(result, skipVersion = '') {
|
|
3794
|
+
const data = result && typeof result === 'object' ? result : {};
|
|
3795
|
+
return {
|
|
3796
|
+
checked: Boolean(data.checked),
|
|
3797
|
+
currentVersion: data.currentVersion || '',
|
|
3798
|
+
latestVersion: data.latestVersion || null,
|
|
3799
|
+
hasUpdate: Boolean(data.hasUpdate),
|
|
3800
|
+
sourceUsed: data.sourceUsed || 'none',
|
|
3801
|
+
channel: data.channel || 'stable',
|
|
3802
|
+
checkReason: data.checkReason || 'manual',
|
|
3803
|
+
checkedAt: Number(data.checkedAt || Date.now()),
|
|
3804
|
+
skippedByUser: shouldSkipVersion(data, skipVersion),
|
|
3805
|
+
errors: Array.isArray(data.errors) ? data.errors : [],
|
|
3806
|
+
upgradeMethods: Array.isArray(data.upgradeMethods) ? data.upgradeMethods : ['npm', 'github-script'],
|
|
3807
|
+
};
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
async function persistSelfUpdateState(context) {
|
|
3811
|
+
if (context.readOnlyConfig) {
|
|
3812
|
+
return;
|
|
3813
|
+
}
|
|
3814
|
+
await saveConfig(context.config).catch(() => {});
|
|
3815
|
+
}
|
|
3816
|
+
|
|
3817
|
+
async function runCheckUpdateModeNonInteractive(context, cliArgs, warnings = []) {
|
|
3818
|
+
const channel = resolveUpdateChannel(cliArgs, context.config);
|
|
3819
|
+
const checkResult = await checkLatestVersion({
|
|
3820
|
+
currentVersion: context.appMeta?.version || '0.0.0',
|
|
3821
|
+
packageName: PACKAGE_NAME,
|
|
3822
|
+
githubOwner: UPDATE_REPO_OWNER,
|
|
3823
|
+
githubRepo: UPDATE_REPO_NAME,
|
|
3824
|
+
channel,
|
|
3825
|
+
timeoutMs: UPDATE_TIMEOUT_MS,
|
|
3826
|
+
reason: 'manual',
|
|
3827
|
+
});
|
|
3828
|
+
const normalizedSelfUpdate = normalizeSelfUpdateConfig({
|
|
3829
|
+
...context.config.selfUpdate,
|
|
3830
|
+
channel,
|
|
3831
|
+
});
|
|
3832
|
+
context.config.selfUpdate = applyUpdateCheckResult(normalizedSelfUpdate, checkResult, '');
|
|
3833
|
+
await persistSelfUpdateState(context);
|
|
3834
|
+
const updateData = buildUpdateData(checkResult, context.config.selfUpdate.skipVersion);
|
|
3835
|
+
|
|
3836
|
+
if (updateData.hasUpdate && !updateData.skippedByUser) {
|
|
3837
|
+
warnings.push(updateWarningMessage(updateData, context.config.selfUpdate.skipVersion));
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
return {
|
|
3841
|
+
ok: updateData.checked,
|
|
3842
|
+
action: MODES.CHECK_UPDATE,
|
|
3843
|
+
dryRun: null,
|
|
3844
|
+
summary: {
|
|
3845
|
+
checked: updateData.checked,
|
|
3846
|
+
hasUpdate: updateData.hasUpdate,
|
|
3847
|
+
currentVersion: updateData.currentVersion || '-',
|
|
3848
|
+
latestVersion: updateData.latestVersion || '-',
|
|
3849
|
+
source: updateData.sourceUsed,
|
|
3850
|
+
channel: updateData.channel,
|
|
3851
|
+
skippedByUser: updateData.skippedByUser,
|
|
3852
|
+
},
|
|
3853
|
+
warnings: uniqueStrings(warnings),
|
|
3854
|
+
errors: updateData.errors.map((message) => ({
|
|
3855
|
+
code: 'E_UPDATE_CHECK_FAILED',
|
|
3856
|
+
message,
|
|
3857
|
+
})),
|
|
3858
|
+
data: {
|
|
3859
|
+
update: updateData,
|
|
3860
|
+
},
|
|
3861
|
+
};
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
async function runUpgradeModeNonInteractive(context, cliArgs, warnings = []) {
|
|
3865
|
+
const method = String(cliArgs.upgradeMethod || '').trim();
|
|
3866
|
+
if (!method) {
|
|
3867
|
+
throw new UsageError('参数 --upgrade 缺少升级方式(npm|github-script)');
|
|
3868
|
+
}
|
|
3869
|
+
if (!cliArgs.upgradeYes) {
|
|
3870
|
+
throw new ConfirmationRequiredError('检测到升级请求,但未提供 --upgrade-yes 确认参数。');
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
const channel = resolveUpdateChannel(cliArgs, context.config);
|
|
3874
|
+
let targetVersion = String(cliArgs.upgradeVersion || '').trim();
|
|
3875
|
+
let checkResult = null;
|
|
3876
|
+
|
|
3877
|
+
if (!targetVersion) {
|
|
3878
|
+
checkResult = await checkLatestVersion({
|
|
3879
|
+
currentVersion: context.appMeta?.version || '0.0.0',
|
|
3880
|
+
packageName: PACKAGE_NAME,
|
|
3881
|
+
githubOwner: UPDATE_REPO_OWNER,
|
|
3882
|
+
githubRepo: UPDATE_REPO_NAME,
|
|
3883
|
+
channel,
|
|
3884
|
+
timeoutMs: UPDATE_TIMEOUT_MS,
|
|
3885
|
+
reason: 'manual',
|
|
3886
|
+
});
|
|
3887
|
+
|
|
3888
|
+
if (!checkResult.checked) {
|
|
3889
|
+
return {
|
|
3890
|
+
ok: false,
|
|
3891
|
+
action: MODES.UPGRADE,
|
|
3892
|
+
dryRun: null,
|
|
3893
|
+
summary: {
|
|
3894
|
+
executed: false,
|
|
3895
|
+
method,
|
|
3896
|
+
targetVersion: '-',
|
|
3897
|
+
reason: 'check_failed',
|
|
3898
|
+
},
|
|
3899
|
+
warnings: uniqueStrings(warnings),
|
|
3900
|
+
errors: (checkResult.errors || []).map((message) => ({
|
|
3901
|
+
code: 'E_UPGRADE_CHECK_FAILED',
|
|
3902
|
+
message,
|
|
3903
|
+
})),
|
|
3904
|
+
data: {
|
|
3905
|
+
update: buildUpdateData(checkResult, context.config.selfUpdate.skipVersion),
|
|
3906
|
+
},
|
|
3907
|
+
};
|
|
3908
|
+
}
|
|
3909
|
+
if (!checkResult.hasUpdate) {
|
|
3910
|
+
return {
|
|
3911
|
+
ok: true,
|
|
3912
|
+
action: MODES.UPGRADE,
|
|
3913
|
+
dryRun: null,
|
|
3914
|
+
summary: {
|
|
3915
|
+
executed: false,
|
|
3916
|
+
method,
|
|
3917
|
+
targetVersion: checkResult.currentVersion || '-',
|
|
3918
|
+
reason: 'already_latest',
|
|
3919
|
+
},
|
|
3920
|
+
warnings: uniqueStrings(warnings),
|
|
3921
|
+
errors: [],
|
|
3922
|
+
data: {
|
|
3923
|
+
update: buildUpdateData(checkResult, context.config.selfUpdate.skipVersion),
|
|
3924
|
+
},
|
|
3925
|
+
};
|
|
3926
|
+
}
|
|
3927
|
+
targetVersion = checkResult.latestVersion;
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
const upgrade = runUpgrade({
|
|
3931
|
+
method,
|
|
3932
|
+
packageName: PACKAGE_NAME,
|
|
3933
|
+
targetVersion,
|
|
3934
|
+
githubOwner: UPDATE_REPO_OWNER,
|
|
3935
|
+
githubRepo: UPDATE_REPO_NAME,
|
|
3936
|
+
});
|
|
3937
|
+
|
|
3938
|
+
if (upgrade.ok) {
|
|
3939
|
+
context.config.selfUpdate = normalizeSelfUpdateConfig({
|
|
3940
|
+
...context.config.selfUpdate,
|
|
3941
|
+
skipVersion: '',
|
|
3942
|
+
lastKnownLatest: targetVersion,
|
|
3943
|
+
lastKnownSource: upgrade.method,
|
|
3944
|
+
});
|
|
3945
|
+
await persistSelfUpdateState(context);
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
return {
|
|
3949
|
+
ok: upgrade.ok,
|
|
3950
|
+
action: MODES.UPGRADE,
|
|
3951
|
+
dryRun: null,
|
|
3952
|
+
summary: {
|
|
3953
|
+
executed: true,
|
|
3954
|
+
method: upgrade.method,
|
|
3955
|
+
targetVersion: upgrade.targetVersion || targetVersion || '-',
|
|
3956
|
+
command: upgrade.command,
|
|
3957
|
+
status: upgrade.status,
|
|
3958
|
+
},
|
|
3959
|
+
warnings: uniqueStrings(warnings),
|
|
3960
|
+
errors: upgrade.ok
|
|
3961
|
+
? []
|
|
3962
|
+
: [
|
|
3963
|
+
{
|
|
3964
|
+
code: 'E_UPGRADE_FAILED',
|
|
3965
|
+
message: upgrade.error || upgrade.stderr || 'upgrade_failed',
|
|
3966
|
+
},
|
|
3967
|
+
],
|
|
3968
|
+
data: {
|
|
3969
|
+
update: checkResult ? buildUpdateData(checkResult, context.config.selfUpdate.skipVersion) : null,
|
|
3970
|
+
upgrade,
|
|
3971
|
+
},
|
|
3972
|
+
};
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
function attachStartupUpdateToResult(result, startupUpdate, skipVersion) {
|
|
3976
|
+
if (!startupUpdate) {
|
|
3977
|
+
return result;
|
|
3978
|
+
}
|
|
3979
|
+
const output = result && typeof result === 'object' ? result : {};
|
|
3980
|
+
const warnings = uniqueStrings(Array.isArray(output.warnings) ? output.warnings : []);
|
|
3981
|
+
const updateData = buildUpdateData(startupUpdate, skipVersion);
|
|
3982
|
+
if (updateData.hasUpdate && !updateData.skippedByUser) {
|
|
3983
|
+
warnings.push(updateWarningMessage(updateData, skipVersion));
|
|
3984
|
+
}
|
|
3985
|
+
return {
|
|
3986
|
+
...output,
|
|
3987
|
+
warnings: uniqueStrings(warnings),
|
|
3988
|
+
data: {
|
|
3989
|
+
...(output.data || {}),
|
|
3990
|
+
update: updateData,
|
|
3991
|
+
},
|
|
3992
|
+
};
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3995
|
+
async function maybeRunStartupUpdateCheck(context, cliArgs, action, interactiveMode) {
|
|
3996
|
+
if (!allowAutoUpdateByEnv()) {
|
|
3997
|
+
return null;
|
|
3998
|
+
}
|
|
3999
|
+
const selfUpdate = normalizeSelfUpdateConfig(context.config.selfUpdate);
|
|
4000
|
+
context.config.selfUpdate = selfUpdate;
|
|
4001
|
+
|
|
4002
|
+
if (!selfUpdate.enabled) {
|
|
4003
|
+
return null;
|
|
4004
|
+
}
|
|
4005
|
+
if (action === MODES.CHECK_UPDATE || action === MODES.UPGRADE) {
|
|
4006
|
+
return null;
|
|
4007
|
+
}
|
|
4008
|
+
if (!interactiveMode && action === MODES.DOCTOR) {
|
|
4009
|
+
return null;
|
|
4010
|
+
}
|
|
4011
|
+
|
|
4012
|
+
const decision = shouldCheckForUpdate(selfUpdate, Date.now());
|
|
4013
|
+
if (!decision.shouldCheck) {
|
|
4014
|
+
return null;
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
const channel = resolveUpdateChannel(cliArgs, context.config);
|
|
4018
|
+
const checkResult = await checkLatestVersion({
|
|
4019
|
+
currentVersion: context.appMeta?.version || '0.0.0',
|
|
4020
|
+
packageName: PACKAGE_NAME,
|
|
4021
|
+
githubOwner: UPDATE_REPO_OWNER,
|
|
4022
|
+
githubRepo: UPDATE_REPO_NAME,
|
|
4023
|
+
channel,
|
|
4024
|
+
timeoutMs: UPDATE_TIMEOUT_MS,
|
|
4025
|
+
reason: 'startup_slot',
|
|
4026
|
+
});
|
|
4027
|
+
context.config.selfUpdate = applyUpdateCheckResult(
|
|
4028
|
+
normalizeSelfUpdateConfig({
|
|
4029
|
+
...context.config.selfUpdate,
|
|
4030
|
+
channel,
|
|
4031
|
+
}),
|
|
4032
|
+
checkResult,
|
|
4033
|
+
decision.slot || ''
|
|
4034
|
+
);
|
|
4035
|
+
await persistSelfUpdateState(context);
|
|
4036
|
+
return buildUpdateData(checkResult, context.config.selfUpdate.skipVersion);
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
async function maybePromptInteractiveUpgrade(context, startupUpdate) {
|
|
4040
|
+
if (!startupUpdate || !startupUpdate.hasUpdate || startupUpdate.skippedByUser) {
|
|
4041
|
+
return false;
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
printSection('可用更新');
|
|
4045
|
+
printTextRows('更新提示', [
|
|
4046
|
+
{
|
|
4047
|
+
label: '版本',
|
|
4048
|
+
value: `当前 v${startupUpdate.currentVersion || '-'} -> 最新 v${startupUpdate.latestVersion || '-'}`,
|
|
4049
|
+
},
|
|
4050
|
+
{ label: '来源', value: startupUpdate.sourceUsed || '-' },
|
|
4051
|
+
{ label: '通道', value: channelLabel(startupUpdate.channel) },
|
|
4052
|
+
]);
|
|
4053
|
+
|
|
4054
|
+
const choice = await askSelect({
|
|
4055
|
+
message: '检测到新版本,是否升级?',
|
|
4056
|
+
default: 'npm',
|
|
4057
|
+
choices: [
|
|
4058
|
+
{ name: '通过 npm 升级(默认)', value: 'npm' },
|
|
4059
|
+
{ name: '通过 GitHub 脚本升级', value: 'github-script' },
|
|
4060
|
+
{ name: '稍后提醒', value: 'later' },
|
|
4061
|
+
{ name: '跳过该版本(不再提醒)', value: 'skip-version' },
|
|
4062
|
+
],
|
|
4063
|
+
});
|
|
4064
|
+
|
|
4065
|
+
if (choice === 'later') {
|
|
4066
|
+
return false;
|
|
4067
|
+
}
|
|
4068
|
+
if (choice === 'skip-version') {
|
|
4069
|
+
context.config.selfUpdate = normalizeSelfUpdateConfig({
|
|
4070
|
+
...context.config.selfUpdate,
|
|
4071
|
+
skipVersion: startupUpdate.latestVersion || '',
|
|
4072
|
+
});
|
|
4073
|
+
await persistSelfUpdateState(context);
|
|
4074
|
+
console.log(`已记录:v${startupUpdate.latestVersion} 将不再自动提醒。`);
|
|
4075
|
+
return false;
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
const confirmed = await askConfirm({
|
|
4079
|
+
message: `确认升级到 v${startupUpdate.latestVersion}?`,
|
|
4080
|
+
default: true,
|
|
4081
|
+
});
|
|
4082
|
+
if (!confirmed) {
|
|
4083
|
+
return false;
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
const upgrade = runUpgrade({
|
|
4087
|
+
method: choice,
|
|
4088
|
+
packageName: PACKAGE_NAME,
|
|
4089
|
+
targetVersion: startupUpdate.latestVersion,
|
|
4090
|
+
githubOwner: UPDATE_REPO_OWNER,
|
|
4091
|
+
githubRepo: UPDATE_REPO_NAME,
|
|
4092
|
+
});
|
|
4093
|
+
if (!upgrade.ok) {
|
|
4094
|
+
printTextRows('升级结果', [
|
|
4095
|
+
{ label: '状态', value: '失败' },
|
|
4096
|
+
{ label: '方式', value: choice },
|
|
4097
|
+
{ label: '命令', value: upgrade.command },
|
|
4098
|
+
{ label: '错误', value: upgrade.error || upgrade.stderr || 'unknown_error' },
|
|
4099
|
+
]);
|
|
4100
|
+
return false;
|
|
4101
|
+
}
|
|
4102
|
+
|
|
4103
|
+
context.config.selfUpdate = normalizeSelfUpdateConfig({
|
|
4104
|
+
...context.config.selfUpdate,
|
|
4105
|
+
skipVersion: '',
|
|
4106
|
+
lastKnownLatest: startupUpdate.latestVersion || '',
|
|
4107
|
+
lastKnownSource: choice,
|
|
4108
|
+
});
|
|
4109
|
+
await persistSelfUpdateState(context);
|
|
4110
|
+
printTextRows('升级结果', [
|
|
4111
|
+
{ label: '状态', value: '成功' },
|
|
4112
|
+
{ label: '方式', value: choice },
|
|
4113
|
+
{ label: '版本', value: `v${startupUpdate.latestVersion}` },
|
|
4114
|
+
]);
|
|
4115
|
+
console.log('升级已完成,建议重新启动 wecom-cleaner 后继续。');
|
|
4116
|
+
return true;
|
|
4117
|
+
}
|
|
4118
|
+
|
|
3290
4119
|
async function runNonInteractiveAction(action, context, cliArgs) {
|
|
3291
4120
|
const warnings = [];
|
|
3292
4121
|
if (cliArgs.actionFromMode) {
|
|
@@ -3311,9 +4140,360 @@ async function runNonInteractiveAction(action, context, cliArgs) {
|
|
|
3311
4140
|
if (action === MODES.DOCTOR) {
|
|
3312
4141
|
return runDoctorModeNonInteractive(context, cliArgs, warnings);
|
|
3313
4142
|
}
|
|
4143
|
+
if (action === MODES.CHECK_UPDATE) {
|
|
4144
|
+
return runCheckUpdateModeNonInteractive(context, cliArgs, warnings);
|
|
4145
|
+
}
|
|
4146
|
+
if (action === MODES.UPGRADE) {
|
|
4147
|
+
return runUpgradeModeNonInteractive(context, cliArgs, warnings);
|
|
4148
|
+
}
|
|
3314
4149
|
throw new UsageError(`不支持的无交互动作: ${action}`);
|
|
3315
4150
|
}
|
|
3316
4151
|
|
|
4152
|
+
function phaseMatchedTargets(action, result) {
|
|
4153
|
+
const summary = result?.summary || {};
|
|
4154
|
+
if (action === MODES.CLEANUP_MONTHLY || action === MODES.SPACE_GOVERNANCE) {
|
|
4155
|
+
return Number(summary.matchedTargets || 0);
|
|
4156
|
+
}
|
|
4157
|
+
if (action === MODES.RESTORE) {
|
|
4158
|
+
return Number(summary.entryCount || 0);
|
|
4159
|
+
}
|
|
4160
|
+
if (action === MODES.RECYCLE_MAINTAIN) {
|
|
4161
|
+
return Number(summary.candidateCount || 0);
|
|
4162
|
+
}
|
|
4163
|
+
if (action === MODES.ANALYSIS_ONLY) {
|
|
4164
|
+
return Number(summary.targetCount || 0);
|
|
4165
|
+
}
|
|
4166
|
+
return 0;
|
|
4167
|
+
}
|
|
4168
|
+
|
|
4169
|
+
function phaseMatchedBytes(action, result) {
|
|
4170
|
+
const summary = result?.summary || {};
|
|
4171
|
+
if (action === MODES.CLEANUP_MONTHLY || action === MODES.SPACE_GOVERNANCE || action === MODES.RESTORE) {
|
|
4172
|
+
return Number(summary.matchedBytes || 0);
|
|
4173
|
+
}
|
|
4174
|
+
if (action === MODES.ANALYSIS_ONLY) {
|
|
4175
|
+
return Number(summary.totalBytes || 0);
|
|
4176
|
+
}
|
|
4177
|
+
if (action === MODES.RECYCLE_MAINTAIN) {
|
|
4178
|
+
return Number(summary.deletedBytes || 0);
|
|
4179
|
+
}
|
|
4180
|
+
return 0;
|
|
4181
|
+
}
|
|
4182
|
+
|
|
4183
|
+
function phaseReclaimedBytes(action, result) {
|
|
4184
|
+
const summary = result?.summary || {};
|
|
4185
|
+
if (action === MODES.RESTORE) {
|
|
4186
|
+
return Number(summary.restoredBytes || 0);
|
|
4187
|
+
}
|
|
4188
|
+
if (action === MODES.RECYCLE_MAINTAIN) {
|
|
4189
|
+
return Number(summary.deletedBytes || 0);
|
|
4190
|
+
}
|
|
4191
|
+
return Number(summary.reclaimedBytes || 0);
|
|
4192
|
+
}
|
|
4193
|
+
|
|
4194
|
+
function buildTaskPhaseEntry(action, phaseName, result, durationMs) {
|
|
4195
|
+
const summary = result?.summary || {};
|
|
4196
|
+
const warnings = Array.isArray(result?.warnings) ? result.warnings : [];
|
|
4197
|
+
const errors = Array.isArray(result?.errors) ? result.errors : [];
|
|
4198
|
+
return {
|
|
4199
|
+
name: phaseName,
|
|
4200
|
+
status: 'completed',
|
|
4201
|
+
ok: Boolean(result?.ok),
|
|
4202
|
+
dryRun: result?.dryRun ?? null,
|
|
4203
|
+
durationMs: Math.max(0, Number(durationMs || 0)),
|
|
4204
|
+
summary,
|
|
4205
|
+
warningCount: warnings.length,
|
|
4206
|
+
errorCount: errors.length,
|
|
4207
|
+
warnings,
|
|
4208
|
+
errors,
|
|
4209
|
+
stats: {
|
|
4210
|
+
matchedTargets: phaseMatchedTargets(action, result),
|
|
4211
|
+
matchedBytes: phaseMatchedBytes(action, result),
|
|
4212
|
+
reclaimedBytes: phaseReclaimedBytes(action, result),
|
|
4213
|
+
successCount: Number(summary.successCount || 0),
|
|
4214
|
+
skippedCount: Number(summary.skippedCount || 0),
|
|
4215
|
+
failedCount: Number(summary.failedCount || summary.failedBatches || 0),
|
|
4216
|
+
batchId: summary.batchId || null,
|
|
4217
|
+
},
|
|
4218
|
+
userFacingSummary: buildUserFacingSummary(action, result),
|
|
4219
|
+
};
|
|
4220
|
+
}
|
|
4221
|
+
|
|
4222
|
+
function buildSkippedTaskPhase(phaseName, reason) {
|
|
4223
|
+
return {
|
|
4224
|
+
name: phaseName,
|
|
4225
|
+
status: 'skipped',
|
|
4226
|
+
reason,
|
|
4227
|
+
ok: true,
|
|
4228
|
+
dryRun: null,
|
|
4229
|
+
durationMs: 0,
|
|
4230
|
+
summary: {},
|
|
4231
|
+
warningCount: 0,
|
|
4232
|
+
errorCount: 0,
|
|
4233
|
+
warnings: [],
|
|
4234
|
+
errors: [],
|
|
4235
|
+
stats: {
|
|
4236
|
+
matchedTargets: 0,
|
|
4237
|
+
matchedBytes: 0,
|
|
4238
|
+
reclaimedBytes: 0,
|
|
4239
|
+
successCount: 0,
|
|
4240
|
+
skippedCount: 0,
|
|
4241
|
+
failedCount: 0,
|
|
4242
|
+
batchId: null,
|
|
4243
|
+
},
|
|
4244
|
+
userFacingSummary: {},
|
|
4245
|
+
};
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4248
|
+
function buildTaskCardBreakdown(action, report) {
|
|
4249
|
+
const matched = report?.matched || {};
|
|
4250
|
+
if (action === MODES.CLEANUP_MONTHLY || action === MODES.ANALYSIS_ONLY) {
|
|
4251
|
+
return {
|
|
4252
|
+
byCategory: summarizeDimensionRows(matched.categoryStats, { labelKey: 'categoryLabel' }, 16),
|
|
4253
|
+
byMonth: summarizeDimensionRows(matched.monthStats, { labelKey: 'monthKey' }, 16),
|
|
4254
|
+
byRoot: summarizeDimensionRows(matched.rootStats, { labelKey: 'rootPath' }, 12),
|
|
4255
|
+
topPaths: Array.isArray(matched.topPaths)
|
|
4256
|
+
? matched.topPaths.slice(0, 12).map((item) => ({
|
|
4257
|
+
path: item.path || '-',
|
|
4258
|
+
category: item.categoryLabel || item.categoryKey || '-',
|
|
4259
|
+
month: item.monthKey || '非月份目录',
|
|
4260
|
+
sizeBytes: Number(item.sizeBytes || 0),
|
|
4261
|
+
}))
|
|
4262
|
+
: [],
|
|
4263
|
+
};
|
|
4264
|
+
}
|
|
4265
|
+
if (action === MODES.SPACE_GOVERNANCE) {
|
|
4266
|
+
return {
|
|
4267
|
+
byCategory: summarizeDimensionRows(matched.byTargetType, { labelKey: 'targetLabel' }, 16),
|
|
4268
|
+
byMonth: [],
|
|
4269
|
+
byRoot: summarizeDimensionRows(matched.byRoot, { labelKey: 'rootPath' }, 12),
|
|
4270
|
+
byTier: summarizeDimensionRows(matched.byTier, { labelKey: 'tierLabel' }, 8),
|
|
4271
|
+
topPaths: Array.isArray(matched.topPaths)
|
|
4272
|
+
? matched.topPaths.slice(0, 12).map((item) => ({
|
|
4273
|
+
path: item.path || '-',
|
|
4274
|
+
category: item.targetLabel || item.targetKey || '-',
|
|
4275
|
+
month: '-',
|
|
4276
|
+
sizeBytes: Number(item.sizeBytes || 0),
|
|
4277
|
+
}))
|
|
4278
|
+
: [],
|
|
4279
|
+
};
|
|
4280
|
+
}
|
|
4281
|
+
if (action === MODES.RESTORE) {
|
|
4282
|
+
return {
|
|
4283
|
+
byCategory: summarizeDimensionRows(matched.byCategory, { labelKey: 'categoryLabel' }, 16),
|
|
4284
|
+
byMonth: summarizeDimensionRows(matched.byMonth, { labelKey: 'monthKey' }, 16),
|
|
4285
|
+
byRoot: summarizeDimensionRows(matched.byRoot, { labelKey: 'rootPath' }, 12),
|
|
4286
|
+
topPaths: Array.isArray(matched.topEntries)
|
|
4287
|
+
? matched.topEntries.slice(0, 12).map((item) => ({
|
|
4288
|
+
path: item.originalPath || '-',
|
|
4289
|
+
category: item.categoryLabel || item.categoryKey || '-',
|
|
4290
|
+
month: item.monthKey || '非月份目录',
|
|
4291
|
+
sizeBytes: Number(item.sizeBytes || 0),
|
|
4292
|
+
}))
|
|
4293
|
+
: [],
|
|
4294
|
+
};
|
|
4295
|
+
}
|
|
4296
|
+
if (action === MODES.RECYCLE_MAINTAIN) {
|
|
4297
|
+
const operations = Array.isArray(report?.operations) ? report.operations : [];
|
|
4298
|
+
const byStatus = operations.reduce((acc, item) => {
|
|
4299
|
+
const key = String(item?.status || 'unknown');
|
|
4300
|
+
acc.set(key, (acc.get(key) || 0) + 1);
|
|
4301
|
+
return acc;
|
|
4302
|
+
}, new Map());
|
|
4303
|
+
return {
|
|
4304
|
+
byCategory: [...byStatus.entries()].map(([label, count]) => ({
|
|
4305
|
+
label,
|
|
4306
|
+
count: Number(count || 0),
|
|
4307
|
+
sizeBytes: 0,
|
|
4308
|
+
})),
|
|
4309
|
+
byMonth: [],
|
|
4310
|
+
byRoot: [],
|
|
4311
|
+
topPaths: [],
|
|
4312
|
+
};
|
|
4313
|
+
}
|
|
4314
|
+
return {
|
|
4315
|
+
byCategory: [],
|
|
4316
|
+
byMonth: [],
|
|
4317
|
+
byRoot: [],
|
|
4318
|
+
topPaths: [],
|
|
4319
|
+
};
|
|
4320
|
+
}
|
|
4321
|
+
|
|
4322
|
+
function buildRunTaskCard(action, runTaskMode, taskDecision, phases, finalResult) {
|
|
4323
|
+
const previewPhase = phases.find((item) => item.name === 'preview' && item.status === 'completed') || null;
|
|
4324
|
+
const executePhase = phases.find((item) => item.name === 'execute' && item.status === 'completed') || null;
|
|
4325
|
+
const verifyPhase = phases.find((item) => item.name === 'verify' && item.status === 'completed') || null;
|
|
4326
|
+
const report = finalResult?.data?.report || {};
|
|
4327
|
+
const breakdown = buildTaskCardBreakdown(action, report);
|
|
4328
|
+
|
|
4329
|
+
let conclusion = '任务已完成。';
|
|
4330
|
+
if (taskDecision === 'skipped_no_target') {
|
|
4331
|
+
conclusion = '预演命中为 0,已按安全策略跳过真实执行。';
|
|
4332
|
+
} else if (taskDecision === 'preview_only') {
|
|
4333
|
+
conclusion = '已完成预演,本次未执行真实操作。';
|
|
4334
|
+
} else if (taskDecision === 'execute_only' && executePhase) {
|
|
4335
|
+
conclusion = executePhase.ok ? '已完成真实执行。' : '已尝试真实执行,但存在失败项。';
|
|
4336
|
+
} else if (taskDecision === 'executed_and_verified') {
|
|
4337
|
+
conclusion =
|
|
4338
|
+
verifyPhase && Number(verifyPhase.stats.matchedTargets || 0) === 0
|
|
4339
|
+
? '已完成真实执行并通过复核,范围内无剩余目标。'
|
|
4340
|
+
: '已完成真实执行与复核。';
|
|
4341
|
+
} else if (taskDecision === 'preview_failed') {
|
|
4342
|
+
conclusion = '预演阶段失败,后续阶段未执行。';
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
return {
|
|
4346
|
+
action,
|
|
4347
|
+
actionLabel: actionDisplayName(action),
|
|
4348
|
+
mode: runTaskMode,
|
|
4349
|
+
decision: taskDecision,
|
|
4350
|
+
conclusion,
|
|
4351
|
+
phases: phases.map((item) => ({
|
|
4352
|
+
name: item.name,
|
|
4353
|
+
status: item.status,
|
|
4354
|
+
reason: item.reason || null,
|
|
4355
|
+
dryRun: item.dryRun,
|
|
4356
|
+
ok: item.ok,
|
|
4357
|
+
durationMs: item.durationMs,
|
|
4358
|
+
matchedTargets: Number(item?.stats?.matchedTargets || 0),
|
|
4359
|
+
matchedBytes: Number(item?.stats?.matchedBytes || 0),
|
|
4360
|
+
reclaimedBytes: Number(item?.stats?.reclaimedBytes || 0),
|
|
4361
|
+
successCount: Number(item?.stats?.successCount || 0),
|
|
4362
|
+
skippedCount: Number(item?.stats?.skippedCount || 0),
|
|
4363
|
+
failedCount: Number(item?.stats?.failedCount || 0),
|
|
4364
|
+
batchId: item?.stats?.batchId || null,
|
|
4365
|
+
})),
|
|
4366
|
+
scope: {
|
|
4367
|
+
accountCount: Number(finalResult?.summary?.accountCount || 0),
|
|
4368
|
+
monthCount: Number(finalResult?.summary?.monthCount || 0),
|
|
4369
|
+
categoryCount: Number(finalResult?.summary?.categoryCount || 0),
|
|
4370
|
+
rootPathCount: Number(finalResult?.summary?.rootPathCount || 0),
|
|
4371
|
+
cutoffMonth: finalResult?.summary?.cutoffMonth || null,
|
|
4372
|
+
selectedAccounts: uniqueStrings(finalResult?.data?.selectedAccounts || []),
|
|
4373
|
+
selectedMonths: uniqueStrings(finalResult?.data?.selectedMonths || []),
|
|
4374
|
+
selectedCategories: uniqueStrings(finalResult?.data?.selectedCategories || []),
|
|
4375
|
+
selectedExternalRoots: uniqueStrings(finalResult?.data?.selectedExternalRoots || []),
|
|
4376
|
+
},
|
|
4377
|
+
preview: previewPhase
|
|
4378
|
+
? {
|
|
4379
|
+
matchedTargets: Number(previewPhase.stats.matchedTargets || 0),
|
|
4380
|
+
matchedBytes: Number(previewPhase.stats.matchedBytes || 0),
|
|
4381
|
+
reclaimedBytes: Number(previewPhase.stats.reclaimedBytes || 0),
|
|
4382
|
+
failedCount: Number(previewPhase.stats.failedCount || 0),
|
|
4383
|
+
}
|
|
4384
|
+
: null,
|
|
4385
|
+
execute: executePhase
|
|
4386
|
+
? {
|
|
4387
|
+
successCount: Number(executePhase.stats.successCount || 0),
|
|
4388
|
+
skippedCount: Number(executePhase.stats.skippedCount || 0),
|
|
4389
|
+
failedCount: Number(executePhase.stats.failedCount || 0),
|
|
4390
|
+
reclaimedBytes: Number(executePhase.stats.reclaimedBytes || 0),
|
|
4391
|
+
batchId: executePhase.stats.batchId || null,
|
|
4392
|
+
}
|
|
4393
|
+
: null,
|
|
4394
|
+
verify: verifyPhase
|
|
4395
|
+
? {
|
|
4396
|
+
matchedTargets: Number(verifyPhase.stats.matchedTargets || 0),
|
|
4397
|
+
matchedBytes: Number(verifyPhase.stats.matchedBytes || 0),
|
|
4398
|
+
failedCount: Number(verifyPhase.stats.failedCount || 0),
|
|
4399
|
+
}
|
|
4400
|
+
: null,
|
|
4401
|
+
breakdown,
|
|
4402
|
+
};
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4405
|
+
function withRunTaskResult(baseResult, action, runTaskMode, taskDecision, phases) {
|
|
4406
|
+
const output = baseResult && typeof baseResult === 'object' ? baseResult : {};
|
|
4407
|
+
const data = output.data && typeof output.data === 'object' ? output.data : {};
|
|
4408
|
+
const summary = output.summary && typeof output.summary === 'object' ? output.summary : {};
|
|
4409
|
+
return {
|
|
4410
|
+
...output,
|
|
4411
|
+
summary: {
|
|
4412
|
+
...summary,
|
|
4413
|
+
runTaskMode,
|
|
4414
|
+
taskDecision,
|
|
4415
|
+
phaseCount: phases.length,
|
|
4416
|
+
},
|
|
4417
|
+
data: {
|
|
4418
|
+
...data,
|
|
4419
|
+
taskPhases: phases,
|
|
4420
|
+
taskCard: buildRunTaskCard(action, runTaskMode, taskDecision, phases, output),
|
|
4421
|
+
},
|
|
4422
|
+
};
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
async function runNonInteractiveTask(action, context, cliArgs) {
|
|
4426
|
+
const runTaskMode = normalizeRunTaskMode(cliArgs.runTask);
|
|
4427
|
+
if (!runTaskMode) {
|
|
4428
|
+
return runNonInteractiveAction(action, context, cliArgs);
|
|
4429
|
+
}
|
|
4430
|
+
|
|
4431
|
+
if (!isDestructiveAction(action) && runTaskMode !== RUN_TASK_PREVIEW) {
|
|
4432
|
+
throw new UsageError(`动作 ${actionDisplayName(action)} 仅支持 --run-task preview`);
|
|
4433
|
+
}
|
|
4434
|
+
if (
|
|
4435
|
+
isDestructiveAction(action) &&
|
|
4436
|
+
(runTaskMode === RUN_TASK_EXECUTE || runTaskMode === RUN_TASK_PREVIEW_EXECUTE_VERIFY) &&
|
|
4437
|
+
!cliArgs.yes
|
|
4438
|
+
) {
|
|
4439
|
+
throw new ConfirmationRequiredError('检测到真实执行任务流程,但未提供 --yes 确认参数。');
|
|
4440
|
+
}
|
|
4441
|
+
|
|
4442
|
+
const execPhase = async (phaseName, phaseArgs) => {
|
|
4443
|
+
const startedAt = Date.now();
|
|
4444
|
+
const result = await runNonInteractiveAction(action, context, phaseArgs);
|
|
4445
|
+
return {
|
|
4446
|
+
result,
|
|
4447
|
+
phase: buildTaskPhaseEntry(action, phaseName, result, Date.now() - startedAt),
|
|
4448
|
+
};
|
|
4449
|
+
};
|
|
4450
|
+
|
|
4451
|
+
if (runTaskMode === RUN_TASK_PREVIEW) {
|
|
4452
|
+
const previewArgs = isDestructiveAction(action)
|
|
4453
|
+
? { ...cliArgs, dryRun: true, yes: false, runTask: null }
|
|
4454
|
+
: { ...cliArgs, runTask: null };
|
|
4455
|
+
const preview = await execPhase('preview', previewArgs);
|
|
4456
|
+
return withRunTaskResult(preview.result, action, runTaskMode, 'preview_only', [preview.phase]);
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4459
|
+
if (runTaskMode === RUN_TASK_EXECUTE) {
|
|
4460
|
+
const executeArgs = isDestructiveAction(action)
|
|
4461
|
+
? { ...cliArgs, dryRun: false, yes: true, runTask: null }
|
|
4462
|
+
: { ...cliArgs, runTask: null };
|
|
4463
|
+
const execute = await execPhase('execute', executeArgs);
|
|
4464
|
+
return withRunTaskResult(execute.result, action, runTaskMode, 'execute_only', [execute.phase]);
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
const previewArgs = { ...cliArgs, dryRun: true, yes: false, runTask: null };
|
|
4468
|
+
const preview = await execPhase('preview', previewArgs);
|
|
4469
|
+
if (!preview.result.ok) {
|
|
4470
|
+
const phases = [
|
|
4471
|
+
preview.phase,
|
|
4472
|
+
buildSkippedTaskPhase('execute', 'preview_failed'),
|
|
4473
|
+
buildSkippedTaskPhase('verify', 'preview_failed'),
|
|
4474
|
+
];
|
|
4475
|
+
return withRunTaskResult(preview.result, action, runTaskMode, 'preview_failed', phases);
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4478
|
+
if (phaseMatchedTargets(action, preview.result) <= 0) {
|
|
4479
|
+
const phases = [
|
|
4480
|
+
preview.phase,
|
|
4481
|
+
buildSkippedTaskPhase('execute', 'no_target'),
|
|
4482
|
+
buildSkippedTaskPhase('verify', 'no_execute'),
|
|
4483
|
+
];
|
|
4484
|
+
return withRunTaskResult(preview.result, action, runTaskMode, 'skipped_no_target', phases);
|
|
4485
|
+
}
|
|
4486
|
+
|
|
4487
|
+
const executeArgs = { ...cliArgs, dryRun: false, yes: true, runTask: null };
|
|
4488
|
+
const execute = await execPhase('execute', executeArgs);
|
|
4489
|
+
const verify = await execPhase('verify', previewArgs);
|
|
4490
|
+
return withRunTaskResult(execute.result, action, runTaskMode, 'executed_and_verified', [
|
|
4491
|
+
preview.phase,
|
|
4492
|
+
execute.phase,
|
|
4493
|
+
verify.phase,
|
|
4494
|
+
]);
|
|
4495
|
+
}
|
|
4496
|
+
|
|
3317
4497
|
async function runCleanupMode(context) {
|
|
3318
4498
|
const { config, aliases, nativeCorePath } = context;
|
|
3319
4499
|
|
|
@@ -4078,6 +5258,74 @@ async function runDoctorMode(context, options = {}) {
|
|
|
4078
5258
|
await printDoctorReport(report, Boolean(options.jsonOutput));
|
|
4079
5259
|
}
|
|
4080
5260
|
|
|
5261
|
+
async function runCheckUpdateMode(context, cliArgs = {}) {
|
|
5262
|
+
const channel = resolveUpdateChannel(cliArgs, context.config);
|
|
5263
|
+
const startedAt = Date.now();
|
|
5264
|
+
const checkResult = await checkLatestVersion({
|
|
5265
|
+
currentVersion: context.appMeta?.version || '0.0.0',
|
|
5266
|
+
packageName: PACKAGE_NAME,
|
|
5267
|
+
githubOwner: UPDATE_REPO_OWNER,
|
|
5268
|
+
githubRepo: UPDATE_REPO_NAME,
|
|
5269
|
+
channel,
|
|
5270
|
+
timeoutMs: UPDATE_TIMEOUT_MS,
|
|
5271
|
+
reason: 'manual',
|
|
5272
|
+
});
|
|
5273
|
+
|
|
5274
|
+
context.config.selfUpdate = applyUpdateCheckResult(
|
|
5275
|
+
normalizeSelfUpdateConfig({
|
|
5276
|
+
...context.config.selfUpdate,
|
|
5277
|
+
channel,
|
|
5278
|
+
}),
|
|
5279
|
+
checkResult,
|
|
5280
|
+
''
|
|
5281
|
+
);
|
|
5282
|
+
await persistSelfUpdateState(context);
|
|
5283
|
+
|
|
5284
|
+
const payload = {
|
|
5285
|
+
ok: checkResult.checked,
|
|
5286
|
+
action: MODES.CHECK_UPDATE,
|
|
5287
|
+
dryRun: null,
|
|
5288
|
+
summary: {
|
|
5289
|
+
checked: Boolean(checkResult.checked),
|
|
5290
|
+
hasUpdate: Boolean(checkResult.hasUpdate),
|
|
5291
|
+
currentVersion: checkResult.currentVersion || '-',
|
|
5292
|
+
latestVersion: checkResult.latestVersion || '-',
|
|
5293
|
+
source: checkResult.sourceUsed || 'none',
|
|
5294
|
+
channel: checkResult.channel || channel,
|
|
5295
|
+
skippedByUser: shouldSkipVersion(checkResult, context.config.selfUpdate.skipVersion),
|
|
5296
|
+
},
|
|
5297
|
+
warnings: [],
|
|
5298
|
+
errors: Array.isArray(checkResult.errors)
|
|
5299
|
+
? checkResult.errors.map((message) => ({
|
|
5300
|
+
code: 'E_UPDATE_CHECK_FAILED',
|
|
5301
|
+
message,
|
|
5302
|
+
}))
|
|
5303
|
+
: [],
|
|
5304
|
+
data: {
|
|
5305
|
+
update: buildUpdateData(checkResult, context.config.selfUpdate.skipVersion),
|
|
5306
|
+
},
|
|
5307
|
+
meta: {
|
|
5308
|
+
app: APP_NAME,
|
|
5309
|
+
package: PACKAGE_NAME,
|
|
5310
|
+
version: context.appMeta?.version || '0.0.0',
|
|
5311
|
+
timestamp: Date.now(),
|
|
5312
|
+
durationMs: Date.now() - startedAt,
|
|
5313
|
+
output: OUTPUT_TEXT,
|
|
5314
|
+
engine: context.lastRunEngineUsed || (context.nativeCorePath ? 'zig_ready' : 'node'),
|
|
5315
|
+
},
|
|
5316
|
+
};
|
|
5317
|
+
|
|
5318
|
+
if (payload.summary.hasUpdate && !payload.summary.skippedByUser) {
|
|
5319
|
+
payload.warnings.push(updateWarningMessage(payload.data.update, context.config.selfUpdate.skipVersion));
|
|
5320
|
+
}
|
|
5321
|
+
printCheckUpdateTextResult(payload);
|
|
5322
|
+
|
|
5323
|
+
const upgraded = await maybePromptInteractiveUpgrade(context, payload.data.update);
|
|
5324
|
+
if (upgraded) {
|
|
5325
|
+
return;
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
|
|
4081
5329
|
async function runRecycleMaintainMode(context, options = {}) {
|
|
4082
5330
|
const { config } = context;
|
|
4083
5331
|
const policy = normalizeRecycleRetention(config.recycleRetention);
|
|
@@ -4438,6 +5686,10 @@ async function runMode(mode, context, options = {}) {
|
|
|
4438
5686
|
await runDoctorMode(context, options);
|
|
4439
5687
|
return;
|
|
4440
5688
|
}
|
|
5689
|
+
if (mode === MODES.CHECK_UPDATE) {
|
|
5690
|
+
await runCheckUpdateMode(context, options.cliArgs || {});
|
|
5691
|
+
return;
|
|
5692
|
+
}
|
|
4441
5693
|
if (mode === MODES.RECYCLE_MAINTAIN) {
|
|
4442
5694
|
await runRecycleMaintainMode(context, options);
|
|
4443
5695
|
return;
|
|
@@ -4528,6 +5780,7 @@ async function runInteractiveLoop(context) {
|
|
|
4528
5780
|
{ name: '恢复已删除批次', value: MODES.RESTORE },
|
|
4529
5781
|
{ name: '回收区治理(保留策略)', value: MODES.RECYCLE_MAINTAIN },
|
|
4530
5782
|
{ name: '系统自检(doctor)', value: MODES.DOCTOR },
|
|
5783
|
+
{ name: '检查更新与升级', value: MODES.CHECK_UPDATE },
|
|
4531
5784
|
{ name: '交互配置', value: MODES.SETTINGS },
|
|
4532
5785
|
{ name: '退出', value: 'exit' },
|
|
4533
5786
|
],
|
|
@@ -4540,6 +5793,7 @@ async function runInteractiveLoop(context) {
|
|
|
4540
5793
|
await runMode(mode, context, {
|
|
4541
5794
|
jsonOutput: false,
|
|
4542
5795
|
force: false,
|
|
5796
|
+
cliArgs: {},
|
|
4543
5797
|
});
|
|
4544
5798
|
|
|
4545
5799
|
const back = await askConfirm({
|
|
@@ -4602,6 +5856,7 @@ async function main() {
|
|
|
4602
5856
|
nativeRepairNote: nativeProbe.repairNote || null,
|
|
4603
5857
|
appMeta,
|
|
4604
5858
|
projectRoot,
|
|
5859
|
+
readOnlyConfig,
|
|
4605
5860
|
};
|
|
4606
5861
|
|
|
4607
5862
|
let lockHandle = null;
|
|
@@ -4610,11 +5865,18 @@ async function main() {
|
|
|
4610
5865
|
}
|
|
4611
5866
|
|
|
4612
5867
|
try {
|
|
5868
|
+
const startupUpdate = await maybeRunStartupUpdateCheck(context, cliArgs, action, interactiveMode);
|
|
5869
|
+
|
|
4613
5870
|
if (interactiveMode) {
|
|
5871
|
+
const upgraded = await maybePromptInteractiveUpgrade(context, startupUpdate);
|
|
5872
|
+
if (upgraded) {
|
|
5873
|
+
return;
|
|
5874
|
+
}
|
|
4614
5875
|
if (interactiveStartMode !== MODES.START) {
|
|
4615
5876
|
await runMode(interactiveStartMode, context, {
|
|
4616
5877
|
jsonOutput: false,
|
|
4617
5878
|
force: cliArgs.force,
|
|
5879
|
+
cliArgs,
|
|
4618
5880
|
});
|
|
4619
5881
|
return;
|
|
4620
5882
|
}
|
|
@@ -4623,7 +5885,17 @@ async function main() {
|
|
|
4623
5885
|
}
|
|
4624
5886
|
|
|
4625
5887
|
const startedAt = Date.now();
|
|
4626
|
-
|
|
5888
|
+
let result = await runNonInteractiveTask(action, context, cliArgs);
|
|
5889
|
+
if (action !== MODES.CHECK_UPDATE && action !== MODES.UPGRADE) {
|
|
5890
|
+
result = attachStartupUpdateToResult(result, startupUpdate, context.config.selfUpdate.skipVersion);
|
|
5891
|
+
}
|
|
5892
|
+
result = {
|
|
5893
|
+
...(result || {}),
|
|
5894
|
+
data: {
|
|
5895
|
+
...((result && result.data) || {}),
|
|
5896
|
+
userFacingSummary: buildUserFacingSummary(action, result),
|
|
5897
|
+
},
|
|
5898
|
+
};
|
|
4627
5899
|
if (cliArgs.saveConfig) {
|
|
4628
5900
|
await saveConfig(config);
|
|
4629
5901
|
}
|