@mison/wecom-cleaner 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -13
- package/docs/NON_INTERACTIVE_SPEC.md +126 -2
- package/docs/releases/v1.2.0.md +33 -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 -4
- package/native/zig/src/main.zig +3 -2
- package/package.json +3 -1
- package/skills/wecom-cleaner-agent/SKILL.md +49 -49
- package/skills/wecom-cleaner-agent/agents/openai.yaml +1 -1
- package/skills/wecom-cleaner-agent/references/commands.md +56 -96
- package/skills/wecom-cleaner-agent/scripts/analysis_report.sh +275 -0
- package/skills/wecom-cleaner-agent/scripts/cleanup_monthly_report.sh +450 -0
- package/skills/wecom-cleaner-agent/scripts/doctor_report.sh +167 -0
- package/skills/wecom-cleaner-agent/scripts/recycle_maintain_report.sh +281 -0
- package/skills/wecom-cleaner-agent/scripts/restore_batch_report.sh +349 -0
- package/skills/wecom-cleaner-agent/scripts/space_governance_report.sh +401 -0
- package/src/cleanup.js +180 -0
- package/src/cli.js +1220 -30
- package/src/config.js +10 -0
- package/src/recycle-maintenance.js +37 -0
- package/src/restore.js +174 -0
package/src/config.js
CHANGED
|
@@ -139,6 +139,8 @@ function normalizeSpaceGovernance(input, fallback) {
|
|
|
139
139
|
|
|
140
140
|
export function parseCliArgs(argv) {
|
|
141
141
|
const parsed = {
|
|
142
|
+
help: false,
|
|
143
|
+
version: false,
|
|
142
144
|
rootDir: null,
|
|
143
145
|
externalStorageRoots: null,
|
|
144
146
|
externalStorageAutoDetect: null,
|
|
@@ -197,6 +199,14 @@ export function parseCliArgs(argv) {
|
|
|
197
199
|
|
|
198
200
|
for (let i = 0; i < argv.length; i += 1) {
|
|
199
201
|
const token = argv[i];
|
|
202
|
+
if (token === '-h' || token === '--help') {
|
|
203
|
+
parsed.help = true;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (token === '-v' || token === '--version') {
|
|
207
|
+
parsed.version = true;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
200
210
|
if (ACTION_FLAG_MAP.has(token)) {
|
|
201
211
|
parsed.action = ACTION_FLAG_MAP.get(token);
|
|
202
212
|
parsed.actionFlagCount += 1;
|
|
@@ -218,9 +218,17 @@ export async function maintainRecycleBin({ indexPath, recycleRoot, policy, dryRu
|
|
|
218
218
|
candidateCount: selected.candidates.length,
|
|
219
219
|
selectedByAge: selected.candidates.filter((item) => item.selectedBy === 'age').length,
|
|
220
220
|
selectedBySize: selected.candidates.filter((item) => item.selectedBy === 'size').length,
|
|
221
|
+
selectedCandidates: selected.candidates.map((item) => ({
|
|
222
|
+
batchId: item.batchId,
|
|
223
|
+
firstTime: item.firstTime,
|
|
224
|
+
ageDays: ageDays(item.firstTime, now),
|
|
225
|
+
totalBytes: Number(item.totalBytes || 0),
|
|
226
|
+
selectedBy: item.selectedBy || 'unknown',
|
|
227
|
+
})),
|
|
221
228
|
deletedBatches: 0,
|
|
222
229
|
deletedBytes: 0,
|
|
223
230
|
failBatches: 0,
|
|
231
|
+
operations: [],
|
|
224
232
|
errors: [],
|
|
225
233
|
};
|
|
226
234
|
|
|
@@ -277,6 +285,13 @@ export async function maintainRecycleBin({ indexPath, recycleRoot, policy, dryRu
|
|
|
277
285
|
const resolvedBatchRoot = resolveBatchRootFromEntries(recycleRoot, batch);
|
|
278
286
|
if (!resolvedBatchRoot.ok) {
|
|
279
287
|
summary.failBatches += 1;
|
|
288
|
+
summary.operations.push({
|
|
289
|
+
batchId: batch.batchId,
|
|
290
|
+
status: 'failed_invalid_path',
|
|
291
|
+
selectedBy: batch.selectedBy || 'unknown',
|
|
292
|
+
totalBytes: Number(batch.totalBytes || 0),
|
|
293
|
+
invalidReason: resolvedBatchRoot.invalidReason,
|
|
294
|
+
});
|
|
280
295
|
summary.errors.push({
|
|
281
296
|
batchId: batch.batchId,
|
|
282
297
|
message: `批次路径校验失败: ${resolvedBatchRoot.invalidReason}`,
|
|
@@ -289,6 +304,13 @@ export async function maintainRecycleBin({ indexPath, recycleRoot, policy, dryRu
|
|
|
289
304
|
if (dryRun) {
|
|
290
305
|
summary.deletedBatches += 1;
|
|
291
306
|
summary.deletedBytes += Number(batch.totalBytes || 0);
|
|
307
|
+
summary.operations.push({
|
|
308
|
+
batchId: batch.batchId,
|
|
309
|
+
status: 'dry_run',
|
|
310
|
+
selectedBy: batch.selectedBy || 'unknown',
|
|
311
|
+
totalBytes: Number(batch.totalBytes || 0),
|
|
312
|
+
batchRoot: resolvedBatchRoot.batchRoot,
|
|
313
|
+
});
|
|
292
314
|
continue;
|
|
293
315
|
}
|
|
294
316
|
|
|
@@ -296,9 +318,24 @@ export async function maintainRecycleBin({ indexPath, recycleRoot, policy, dryRu
|
|
|
296
318
|
await fs.rm(resolvedBatchRoot.batchRoot, { recursive: true, force: true });
|
|
297
319
|
summary.deletedBatches += 1;
|
|
298
320
|
summary.deletedBytes += Number(batch.totalBytes || 0);
|
|
321
|
+
summary.operations.push({
|
|
322
|
+
batchId: batch.batchId,
|
|
323
|
+
status: 'deleted',
|
|
324
|
+
selectedBy: batch.selectedBy || 'unknown',
|
|
325
|
+
totalBytes: Number(batch.totalBytes || 0),
|
|
326
|
+
batchRoot: resolvedBatchRoot.batchRoot,
|
|
327
|
+
});
|
|
299
328
|
} catch (error) {
|
|
300
329
|
summary.failBatches += 1;
|
|
301
330
|
const message = error instanceof Error ? error.message : String(error);
|
|
331
|
+
summary.operations.push({
|
|
332
|
+
batchId: batch.batchId,
|
|
333
|
+
status: 'failed',
|
|
334
|
+
selectedBy: batch.selectedBy || 'unknown',
|
|
335
|
+
totalBytes: Number(batch.totalBytes || 0),
|
|
336
|
+
batchRoot: resolvedBatchRoot.batchRoot,
|
|
337
|
+
errorType: classifyErrorType(message),
|
|
338
|
+
});
|
|
302
339
|
summary.errors.push({
|
|
303
340
|
batchId: batch.batchId,
|
|
304
341
|
message,
|
package/src/restore.js
CHANGED
|
@@ -277,6 +277,172 @@ function buildRestoreAuditMeta(entry = {}) {
|
|
|
277
277
|
};
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
function createRestoreBreakdownRow(seed = {}) {
|
|
281
|
+
return {
|
|
282
|
+
...seed,
|
|
283
|
+
totalCount: 0,
|
|
284
|
+
totalBytes: 0,
|
|
285
|
+
successCount: 0,
|
|
286
|
+
successBytes: 0,
|
|
287
|
+
skippedCount: 0,
|
|
288
|
+
skippedBytes: 0,
|
|
289
|
+
failedCount: 0,
|
|
290
|
+
failedBytes: 0,
|
|
291
|
+
dryRunCount: 0,
|
|
292
|
+
dryRunBytes: 0,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function applyRestoreBreakdownStatus(row, statusKey, sizeBytes) {
|
|
297
|
+
const bytes = Number(sizeBytes || 0);
|
|
298
|
+
row.totalCount += 1;
|
|
299
|
+
row.totalBytes += bytes;
|
|
300
|
+
if (statusKey === 'success') {
|
|
301
|
+
row.successCount += 1;
|
|
302
|
+
row.successBytes += bytes;
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (statusKey === 'skipped') {
|
|
306
|
+
row.skippedCount += 1;
|
|
307
|
+
row.skippedBytes += bytes;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (statusKey === 'failed') {
|
|
311
|
+
row.failedCount += 1;
|
|
312
|
+
row.failedBytes += bytes;
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
row.dryRunCount += 1;
|
|
316
|
+
row.dryRunBytes += bytes;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function pushRestoreTopEntry(samples, sample, limit = 20) {
|
|
320
|
+
samples.push(sample);
|
|
321
|
+
samples.sort((a, b) => Number(b.sizeBytes || 0) - Number(a.sizeBytes || 0));
|
|
322
|
+
if (samples.length > limit) {
|
|
323
|
+
samples.length = limit;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function createRestoreBreakdownTracker(topEntryLimit = 20) {
|
|
328
|
+
return {
|
|
329
|
+
byScope: new Map(),
|
|
330
|
+
byCategory: new Map(),
|
|
331
|
+
byMonth: new Map(),
|
|
332
|
+
byRoot: new Map(),
|
|
333
|
+
status: {
|
|
334
|
+
success: { count: 0, bytes: 0 },
|
|
335
|
+
skipped: { count: 0, bytes: 0 },
|
|
336
|
+
failed: { count: 0, bytes: 0 },
|
|
337
|
+
dryRun: { count: 0, bytes: 0 },
|
|
338
|
+
},
|
|
339
|
+
topEntries: [],
|
|
340
|
+
topEntryLimit,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function updateRestoreBreakdown(tracker, entry, statusKey, statusLabel, restoredPath = null) {
|
|
345
|
+
const bytes = Number(entry?.sizeBytes || 0);
|
|
346
|
+
if (!tracker.status[statusKey]) {
|
|
347
|
+
tracker.status[statusKey] = { count: 0, bytes: 0 };
|
|
348
|
+
}
|
|
349
|
+
tracker.status[statusKey].count += 1;
|
|
350
|
+
tracker.status[statusKey].bytes += bytes;
|
|
351
|
+
|
|
352
|
+
const scopeKey = String(entry?.scope || 'cleanup_monthly');
|
|
353
|
+
if (!tracker.byScope.has(scopeKey)) {
|
|
354
|
+
tracker.byScope.set(scopeKey, createRestoreBreakdownRow({ scope: scopeKey }));
|
|
355
|
+
}
|
|
356
|
+
applyRestoreBreakdownStatus(tracker.byScope.get(scopeKey), statusKey, bytes);
|
|
357
|
+
|
|
358
|
+
const categoryKey = String(entry?.categoryKey || entry?.targetKey || 'unknown');
|
|
359
|
+
if (!tracker.byCategory.has(categoryKey)) {
|
|
360
|
+
tracker.byCategory.set(
|
|
361
|
+
categoryKey,
|
|
362
|
+
createRestoreBreakdownRow({
|
|
363
|
+
categoryKey,
|
|
364
|
+
categoryLabel: entry?.categoryLabel || categoryKey,
|
|
365
|
+
})
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
applyRestoreBreakdownStatus(tracker.byCategory.get(categoryKey), statusKey, bytes);
|
|
369
|
+
|
|
370
|
+
const monthKey = String(entry?.monthKey || '非月份目录');
|
|
371
|
+
if (!tracker.byMonth.has(monthKey)) {
|
|
372
|
+
tracker.byMonth.set(monthKey, createRestoreBreakdownRow({ monthKey }));
|
|
373
|
+
}
|
|
374
|
+
applyRestoreBreakdownStatus(tracker.byMonth.get(monthKey), statusKey, bytes);
|
|
375
|
+
|
|
376
|
+
const sourcePath = typeof entry?.sourcePath === 'string' ? entry.sourcePath : '';
|
|
377
|
+
const rootPath = sourcePath ? path.dirname(path.resolve(sourcePath)) : null;
|
|
378
|
+
const rootKey = rootPath || '(unknown)';
|
|
379
|
+
if (!tracker.byRoot.has(rootKey)) {
|
|
380
|
+
tracker.byRoot.set(
|
|
381
|
+
rootKey,
|
|
382
|
+
createRestoreBreakdownRow({
|
|
383
|
+
rootPath: rootPath || null,
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
applyRestoreBreakdownStatus(tracker.byRoot.get(rootKey), statusKey, bytes);
|
|
388
|
+
|
|
389
|
+
pushRestoreTopEntry(
|
|
390
|
+
tracker.topEntries,
|
|
391
|
+
{
|
|
392
|
+
sourcePath: entry?.sourcePath || null,
|
|
393
|
+
recyclePath: entry?.recyclePath || null,
|
|
394
|
+
restoredPath: restoredPath || null,
|
|
395
|
+
sizeBytes: bytes,
|
|
396
|
+
status: statusLabel,
|
|
397
|
+
scope: scopeKey,
|
|
398
|
+
categoryKey,
|
|
399
|
+
categoryLabel: entry?.categoryLabel || categoryKey,
|
|
400
|
+
monthKey: entry?.monthKey || null,
|
|
401
|
+
accountShortId: entry?.accountShortId || null,
|
|
402
|
+
},
|
|
403
|
+
tracker.topEntryLimit
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function sortRestoreBreakdownRowsByBytes(rows = []) {
|
|
408
|
+
return [...rows].sort((a, b) => {
|
|
409
|
+
const bytesDiff = Number(b.totalBytes || 0) - Number(a.totalBytes || 0);
|
|
410
|
+
if (bytesDiff !== 0) {
|
|
411
|
+
return bytesDiff;
|
|
412
|
+
}
|
|
413
|
+
return Number(b.totalCount || 0) - Number(a.totalCount || 0);
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function sortRestoreMonthRows(rows = []) {
|
|
418
|
+
const nonMonthKey = '非月份目录';
|
|
419
|
+
return [...rows].sort((a, b) => {
|
|
420
|
+
const aMonth = String(a.monthKey || nonMonthKey);
|
|
421
|
+
const bMonth = String(b.monthKey || nonMonthKey);
|
|
422
|
+
if (aMonth === nonMonthKey && bMonth !== nonMonthKey) {
|
|
423
|
+
return 1;
|
|
424
|
+
}
|
|
425
|
+
if (aMonth !== nonMonthKey && bMonth === nonMonthKey) {
|
|
426
|
+
return -1;
|
|
427
|
+
}
|
|
428
|
+
if (aMonth === bMonth) {
|
|
429
|
+
return Number(b.totalBytes || 0) - Number(a.totalBytes || 0);
|
|
430
|
+
}
|
|
431
|
+
return aMonth.localeCompare(bMonth);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function finalizeRestoreBreakdown(tracker) {
|
|
436
|
+
return {
|
|
437
|
+
byStatus: tracker.status,
|
|
438
|
+
byScope: sortRestoreBreakdownRowsByBytes([...tracker.byScope.values()]),
|
|
439
|
+
byCategory: sortRestoreBreakdownRowsByBytes([...tracker.byCategory.values()]),
|
|
440
|
+
byMonth: sortRestoreMonthRows([...tracker.byMonth.values()]),
|
|
441
|
+
byRoot: sortRestoreBreakdownRowsByBytes([...tracker.byRoot.values()]),
|
|
442
|
+
topEntries: [...tracker.topEntries],
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
280
446
|
export async function listRestorableBatches(indexPath, options = {}) {
|
|
281
447
|
const recycleRoot = typeof options.recycleRoot === 'string' ? options.recycleRoot : null;
|
|
282
448
|
const restoredSet = new Set();
|
|
@@ -348,6 +514,7 @@ export async function restoreBatch({
|
|
|
348
514
|
restoredBytes: 0,
|
|
349
515
|
errors: [],
|
|
350
516
|
};
|
|
517
|
+
const breakdownTracker = createRestoreBreakdownTracker();
|
|
351
518
|
|
|
352
519
|
const validationState = await buildRestoreValidationState({
|
|
353
520
|
profileRoot,
|
|
@@ -379,6 +546,7 @@ export async function restoreBatch({
|
|
|
379
546
|
|
|
380
547
|
if (invalidPathReason) {
|
|
381
548
|
summary.skipCount += 1;
|
|
549
|
+
updateRestoreBreakdown(breakdownTracker, entry, 'skipped', 'skipped_invalid_path');
|
|
382
550
|
await appendJsonLine(indexPath, {
|
|
383
551
|
action: 'restore',
|
|
384
552
|
time: Date.now(),
|
|
@@ -401,6 +569,7 @@ export async function restoreBatch({
|
|
|
401
569
|
|
|
402
570
|
if (!(await pathExists(recyclePath))) {
|
|
403
571
|
summary.skipCount += 1;
|
|
572
|
+
updateRestoreBreakdown(breakdownTracker, entry, 'skipped', 'skipped_missing_recycle');
|
|
404
573
|
await appendJsonLine(indexPath, {
|
|
405
574
|
action: 'restore',
|
|
406
575
|
time: Date.now(),
|
|
@@ -447,6 +616,7 @@ export async function restoreBatch({
|
|
|
447
616
|
|
|
448
617
|
if (strategy === 'skip') {
|
|
449
618
|
summary.skipCount += 1;
|
|
619
|
+
updateRestoreBreakdown(breakdownTracker, entry, 'skipped', 'skipped_conflict');
|
|
450
620
|
await appendJsonLine(indexPath, {
|
|
451
621
|
action: 'restore',
|
|
452
622
|
time: Date.now(),
|
|
@@ -474,6 +644,7 @@ export async function restoreBatch({
|
|
|
474
644
|
if (dryRun) {
|
|
475
645
|
summary.successCount += 1;
|
|
476
646
|
summary.restoredBytes += Number(entry.sizeBytes || 0);
|
|
647
|
+
updateRestoreBreakdown(breakdownTracker, entry, 'dryRun', 'dry_run', targetPath);
|
|
477
648
|
|
|
478
649
|
await appendJsonLine(indexPath, {
|
|
479
650
|
action: 'restore',
|
|
@@ -504,6 +675,7 @@ export async function restoreBatch({
|
|
|
504
675
|
await movePath(recyclePath, targetPath);
|
|
505
676
|
summary.successCount += 1;
|
|
506
677
|
summary.restoredBytes += Number(entry.sizeBytes || 0);
|
|
678
|
+
updateRestoreBreakdown(breakdownTracker, entry, 'success', 'success', targetPath);
|
|
507
679
|
|
|
508
680
|
await appendJsonLine(indexPath, {
|
|
509
681
|
action: 'restore',
|
|
@@ -524,6 +696,7 @@ export async function restoreBatch({
|
|
|
524
696
|
});
|
|
525
697
|
} catch (error) {
|
|
526
698
|
summary.failCount += 1;
|
|
699
|
+
updateRestoreBreakdown(breakdownTracker, entry, 'failed', 'failed', targetPath);
|
|
527
700
|
summary.errors.push({
|
|
528
701
|
recyclePath,
|
|
529
702
|
sourcePath: originalPath,
|
|
@@ -551,5 +724,6 @@ export async function restoreBatch({
|
|
|
551
724
|
}
|
|
552
725
|
}
|
|
553
726
|
|
|
727
|
+
summary.breakdown = finalizeRestoreBreakdown(breakdownTracker);
|
|
554
728
|
return summary;
|
|
555
729
|
}
|