@osdlc/repo-cleaner 0.17.2 → 0.18.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/CHANGELOG.md +18 -0
- package/README.md +497 -329
- package/dist/branches.d.ts +9 -36
- package/dist/branches.js +92 -172
- package/dist/clean.d.ts +162 -0
- package/dist/clean.js +755 -0
- package/dist/cli.d.ts +12 -16
- package/dist/cli.js +340 -479
- package/dist/hooks.d.ts +8 -12
- package/dist/hooks.js +2 -2
- package/dist/index.d.ts +18 -25
- package/dist/index.js +16 -23
- package/dist/landing.d.ts +3 -3
- package/dist/landing.js +7 -7
- package/dist/plan.d.ts +76 -70
- package/dist/plan.js +82 -77
- package/dist/receipt.d.ts +44 -57
- package/dist/receipt.js +46 -28
- package/dist/reference.d.ts +12 -0
- package/dist/reference.js +148 -0
- package/dist/remote-mirror.d.ts +8 -0
- package/dist/remote-mirror.js +18 -0
- package/dist/render.d.ts +61 -61
- package/dist/render.js +195 -254
- package/dist/resolve.js +1 -9
- package/dist/status-line.d.ts +1 -1
- package/dist/status-line.js +5 -5
- package/dist/step-board.d.ts +1 -1
- package/dist/step-board.js +2 -5
- package/dist/submodules.d.ts +120 -0
- package/dist/submodules.js +292 -0
- package/dist/sweep.d.ts +97 -67
- package/dist/sweep.js +272 -118
- package/dist/types.d.ts +276 -105
- package/dist/types.js +100 -54
- package/dist/util.d.ts +7 -0
- package/dist/util.js +20 -0
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/worktrees.d.ts +21 -37
- package/dist/worktrees.js +198 -165
- package/package.json +4 -4
- package/dist/apply.d.ts +0 -131
- package/dist/apply.js +0 -541
package/dist/receipt.d.ts
CHANGED
|
@@ -1,56 +1,53 @@
|
|
|
1
|
-
/** What
|
|
1
|
+
/** What a run did, step by step, rewritten after every step. */
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
/** The receipt format's version, written into every receipt. */
|
|
4
|
-
export declare const RECEIPT_VERSION =
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
export declare const RECEIPT_VERSION = 2;
|
|
5
|
+
/**
|
|
6
|
+
* Why a row did not run:
|
|
7
|
+
*
|
|
8
|
+
* `gone` no longer in the sweep
|
|
9
|
+
* `blocked` the sweep now finds a blocker
|
|
10
|
+
* `keep` the sweep keeps it for structure; it has no action
|
|
11
|
+
* `held` a branch whose worktree this run does not remove
|
|
12
|
+
* `needs-pick` not judged `delete`; it runs only when picked
|
|
13
|
+
* `changed` its evidence moved since the plan
|
|
14
|
+
* `not-allowed` it carries a risk that was not allowed
|
|
15
|
+
* `duplicate` another checkout of the same remote deletes the same branch
|
|
16
|
+
* `spared` the same remote branch is spared through another checkout
|
|
17
|
+
*/
|
|
18
|
+
export declare const DROP_REASONS: readonly ["gone", "blocked", "keep", "held", "needs-pick", "changed", "not-allowed", "duplicate", "spared"];
|
|
19
|
+
export type DropReason = (typeof DROP_REASONS)[number];
|
|
14
20
|
export declare const DroppedSchema: z.ZodObject<{
|
|
15
|
-
category: z.ZodEnum<{
|
|
16
|
-
local_branch: "local_branch";
|
|
17
|
-
worktree: "worktree";
|
|
18
|
-
remote_branch: "remote_branch";
|
|
19
|
-
}>;
|
|
20
|
-
id: z.ZodString;
|
|
21
21
|
repo: z.ZodString;
|
|
22
|
+
row: z.ZodCustom<`worktree:${string}` | `branch:${string}` | `remote:${string}`, `worktree:${string}` | `branch:${string}` | `remote:${string}`>;
|
|
22
23
|
reason: z.ZodEnum<{
|
|
24
|
+
keep: "keep";
|
|
25
|
+
blocked: "blocked";
|
|
23
26
|
gone: "gone";
|
|
24
|
-
|
|
25
|
-
"needs-
|
|
26
|
-
|
|
27
|
-
"not-
|
|
27
|
+
held: "held";
|
|
28
|
+
"needs-pick": "needs-pick";
|
|
29
|
+
changed: "changed";
|
|
30
|
+
"not-allowed": "not-allowed";
|
|
28
31
|
duplicate: "duplicate";
|
|
29
|
-
|
|
32
|
+
spared: "spared";
|
|
30
33
|
}>;
|
|
31
34
|
detail: z.ZodString;
|
|
32
35
|
}, z.core.$strip>;
|
|
33
36
|
export type Dropped = z.infer<typeof DroppedSchema>;
|
|
37
|
+
export declare const STEP_ACTIONS: readonly ["verify-remote", "unlock-worktree", "remove-worktree", "delete-branch", "delete-remote", "prune-worktrees"];
|
|
34
38
|
export declare const StepSchema: z.ZodObject<{
|
|
35
39
|
repo: z.ZodString;
|
|
36
|
-
row: z.
|
|
37
|
-
category: z.ZodEnum<{
|
|
38
|
-
local_branch: "local_branch";
|
|
39
|
-
worktree: "worktree";
|
|
40
|
-
remote_branch: "remote_branch";
|
|
41
|
-
}>;
|
|
42
|
-
id: z.ZodString;
|
|
43
|
-
}, z.core.$strip>>;
|
|
40
|
+
row: z.ZodCustom<`worktree:${string}` | `branch:${string}` | `remote:${string}`, `worktree:${string}` | `branch:${string}` | `remote:${string}`>;
|
|
44
41
|
action: z.ZodEnum<{
|
|
45
42
|
"verify-remote": "verify-remote";
|
|
46
43
|
"unlock-worktree": "unlock-worktree";
|
|
47
44
|
"remove-worktree": "remove-worktree";
|
|
48
|
-
"delete-
|
|
45
|
+
"delete-branch": "delete-branch";
|
|
49
46
|
"delete-remote": "delete-remote";
|
|
50
47
|
"prune-worktrees": "prune-worktrees";
|
|
51
48
|
}>;
|
|
52
49
|
command: z.ZodString;
|
|
53
|
-
|
|
50
|
+
expectedSha: z.ZodNullable<z.ZodString>;
|
|
54
51
|
result: z.ZodEnum<{
|
|
55
52
|
pending: "pending";
|
|
56
53
|
"would-run": "would-run";
|
|
@@ -62,55 +59,45 @@ export declare const StepSchema: z.ZodObject<{
|
|
|
62
59
|
}, z.core.$strip>;
|
|
63
60
|
export type Step = z.infer<typeof StepSchema>;
|
|
64
61
|
export declare const ReceiptSchema: z.ZodObject<{
|
|
65
|
-
version: z.ZodLiteral<
|
|
62
|
+
version: z.ZodLiteral<2>;
|
|
66
63
|
plan: z.ZodString;
|
|
67
64
|
status: z.ZodEnum<{
|
|
68
65
|
failed: "failed";
|
|
69
66
|
"dry-run": "dry-run";
|
|
70
|
-
|
|
67
|
+
running: "running";
|
|
71
68
|
complete: "complete";
|
|
72
69
|
}>;
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
startedAt: z.ZodString;
|
|
71
|
+
finishedAt: z.ZodNullable<z.ZodString>;
|
|
75
72
|
dropped: z.ZodArray<z.ZodObject<{
|
|
76
|
-
category: z.ZodEnum<{
|
|
77
|
-
local_branch: "local_branch";
|
|
78
|
-
worktree: "worktree";
|
|
79
|
-
remote_branch: "remote_branch";
|
|
80
|
-
}>;
|
|
81
|
-
id: z.ZodString;
|
|
82
73
|
repo: z.ZodString;
|
|
74
|
+
row: z.ZodCustom<`worktree:${string}` | `branch:${string}` | `remote:${string}`, `worktree:${string}` | `branch:${string}` | `remote:${string}`>;
|
|
83
75
|
reason: z.ZodEnum<{
|
|
76
|
+
keep: "keep";
|
|
77
|
+
blocked: "blocked";
|
|
84
78
|
gone: "gone";
|
|
85
|
-
|
|
86
|
-
"needs-
|
|
87
|
-
|
|
88
|
-
"not-
|
|
79
|
+
held: "held";
|
|
80
|
+
"needs-pick": "needs-pick";
|
|
81
|
+
changed: "changed";
|
|
82
|
+
"not-allowed": "not-allowed";
|
|
89
83
|
duplicate: "duplicate";
|
|
90
|
-
|
|
84
|
+
spared: "spared";
|
|
91
85
|
}>;
|
|
92
86
|
detail: z.ZodString;
|
|
93
87
|
}, z.core.$strip>>;
|
|
94
88
|
steps: z.ZodArray<z.ZodObject<{
|
|
95
89
|
repo: z.ZodString;
|
|
96
|
-
row: z.
|
|
97
|
-
category: z.ZodEnum<{
|
|
98
|
-
local_branch: "local_branch";
|
|
99
|
-
worktree: "worktree";
|
|
100
|
-
remote_branch: "remote_branch";
|
|
101
|
-
}>;
|
|
102
|
-
id: z.ZodString;
|
|
103
|
-
}, z.core.$strip>>;
|
|
90
|
+
row: z.ZodCustom<`worktree:${string}` | `branch:${string}` | `remote:${string}`, `worktree:${string}` | `branch:${string}` | `remote:${string}`>;
|
|
104
91
|
action: z.ZodEnum<{
|
|
105
92
|
"verify-remote": "verify-remote";
|
|
106
93
|
"unlock-worktree": "unlock-worktree";
|
|
107
94
|
"remove-worktree": "remove-worktree";
|
|
108
|
-
"delete-
|
|
95
|
+
"delete-branch": "delete-branch";
|
|
109
96
|
"delete-remote": "delete-remote";
|
|
110
97
|
"prune-worktrees": "prune-worktrees";
|
|
111
98
|
}>;
|
|
112
99
|
command: z.ZodString;
|
|
113
|
-
|
|
100
|
+
expectedSha: z.ZodNullable<z.ZodString>;
|
|
114
101
|
result: z.ZodEnum<{
|
|
115
102
|
pending: "pending";
|
|
116
103
|
"would-run": "would-run";
|
package/dist/receipt.js
CHANGED
|
@@ -1,46 +1,64 @@
|
|
|
1
|
-
/** What
|
|
1
|
+
/** What a run did, step by step, rewritten after every step. */
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import {
|
|
3
|
+
import { RowIdSchema } from "./plan.js";
|
|
4
4
|
/** The receipt format's version, written into every receipt. */
|
|
5
|
-
export const RECEIPT_VERSION =
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export const RECEIPT_VERSION = 2;
|
|
6
|
+
/**
|
|
7
|
+
* Why a row did not run:
|
|
8
|
+
*
|
|
9
|
+
* `gone` no longer in the sweep
|
|
10
|
+
* `blocked` the sweep now finds a blocker
|
|
11
|
+
* `keep` the sweep keeps it for structure; it has no action
|
|
12
|
+
* `held` a branch whose worktree this run does not remove
|
|
13
|
+
* `needs-pick` not judged `delete`; it runs only when picked
|
|
14
|
+
* `changed` its evidence moved since the plan
|
|
15
|
+
* `not-allowed` it carries a risk that was not allowed
|
|
16
|
+
* `duplicate` another checkout of the same remote deletes the same branch
|
|
17
|
+
* `spared` the same remote branch is spared through another checkout
|
|
18
|
+
*/
|
|
19
|
+
export const DROP_REASONS = [
|
|
20
|
+
'gone',
|
|
21
|
+
'blocked',
|
|
22
|
+
'keep',
|
|
23
|
+
'held',
|
|
24
|
+
'needs-pick',
|
|
25
|
+
'changed',
|
|
26
|
+
'not-allowed',
|
|
27
|
+
'duplicate',
|
|
28
|
+
'spared',
|
|
29
|
+
];
|
|
30
|
+
export const DroppedSchema = z.object({
|
|
8
31
|
repo: z.string(),
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
'not-deletable',
|
|
12
|
-
'needs-review',
|
|
13
|
-
'facts-changed',
|
|
14
|
-
'not-authorized',
|
|
15
|
-
'duplicate',
|
|
16
|
-
'kept',
|
|
17
|
-
]),
|
|
32
|
+
row: RowIdSchema,
|
|
33
|
+
reason: z.enum(DROP_REASONS),
|
|
18
34
|
detail: z.string(),
|
|
19
35
|
});
|
|
36
|
+
export const STEP_ACTIONS = [
|
|
37
|
+
'verify-remote',
|
|
38
|
+
'unlock-worktree',
|
|
39
|
+
'remove-worktree',
|
|
40
|
+
'delete-branch',
|
|
41
|
+
'delete-remote',
|
|
42
|
+
'prune-worktrees',
|
|
43
|
+
];
|
|
20
44
|
export const StepSchema = z.object({
|
|
21
45
|
repo: z.string(),
|
|
22
|
-
/**
|
|
23
|
-
row:
|
|
24
|
-
action: z.enum(
|
|
25
|
-
'verify-remote',
|
|
26
|
-
'unlock-worktree',
|
|
27
|
-
'remove-worktree',
|
|
28
|
-
'delete-local',
|
|
29
|
-
'delete-remote',
|
|
30
|
-
'prune-worktrees',
|
|
31
|
-
]),
|
|
46
|
+
/** The row the step acts on. */
|
|
47
|
+
row: RowIdSchema,
|
|
48
|
+
action: z.enum(STEP_ACTIONS),
|
|
32
49
|
command: z.string(),
|
|
33
|
-
|
|
50
|
+
expectedSha: z.string().nullable(),
|
|
34
51
|
result: z.enum(['pending', 'would-run', 'ok', 'failed']),
|
|
35
52
|
error: z.string().nullable(),
|
|
36
53
|
verification: z.string().nullable(),
|
|
37
54
|
});
|
|
38
55
|
export const ReceiptSchema = z.object({
|
|
39
56
|
version: z.literal(RECEIPT_VERSION),
|
|
57
|
+
/** Where the plan came from: a path, `(stdin)`, or `(swept)`. */
|
|
40
58
|
plan: z.string(),
|
|
41
|
-
status: z.enum(['dry-run', '
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
status: z.enum(['dry-run', 'running', 'complete', 'failed']),
|
|
60
|
+
startedAt: z.string(),
|
|
61
|
+
finishedAt: z.string().nullable(),
|
|
44
62
|
dropped: z.array(DroppedSchema),
|
|
45
63
|
steps: z.array(StepSchema),
|
|
46
64
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The full reference `--help --extended` prints: what every option means,
|
|
3
|
+
* the words the report uses, and which combinations are refused. The README
|
|
4
|
+
* says the same in its own layout; keep the two in step.
|
|
5
|
+
*/
|
|
6
|
+
/** The extended help for each command: its own options, then the shared vocabulary. */
|
|
7
|
+
export declare const REFERENCE: {
|
|
8
|
+
readonly report: string;
|
|
9
|
+
readonly plan: string;
|
|
10
|
+
readonly clean: string;
|
|
11
|
+
readonly cache: "repo-cleaner cache [list | remove <url>... | remove --all]\n Lists or removes the clones kept for remote URL targets. A removed clone is\n cloned again by the next sweep of its URL; a plan that names it cannot run.";
|
|
12
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The full reference `--help --extended` prints: what every option means,
|
|
3
|
+
* the words the report uses, and which combinations are refused. The README
|
|
4
|
+
* says the same in its own layout; keep the two in step.
|
|
5
|
+
*/
|
|
6
|
+
const TARGETS = `Targets
|
|
7
|
+
A target is what to sweep:
|
|
8
|
+
a checkout that repository; a linked worktree sweeps the repository it belongs to
|
|
9
|
+
a directory every checkout found under it, three levels deep at most
|
|
10
|
+
a remote URL its remote branches only, through a clone kept in the cache
|
|
11
|
+
directory (https://, ssh://, file://, or user@host:path)
|
|
12
|
+
No target sweeps the checkout you are in. A target never fans out further than
|
|
13
|
+
it says.`;
|
|
14
|
+
const SWEEP = `Sweep options (the report, plan and clean)
|
|
15
|
+
--remote Also judge remote branches. Turns on the network: fetches
|
|
16
|
+
each remote first and asks GitHub or GitLab about pull
|
|
17
|
+
requests. Slower.
|
|
18
|
+
--offline With --remote: do not fetch; judge against the
|
|
19
|
+
remote-tracking refs already on disk.
|
|
20
|
+
--only <kinds> Only these kinds of row: worktree, branch, remote,
|
|
21
|
+
comma-separated. Default: worktree,branch, plus remote with
|
|
22
|
+
--remote. Naming remote implies --remote.
|
|
23
|
+
--branch <name|HEAD> Only the rows of one branch: its worktree, its local branch,
|
|
24
|
+
and its remote branch with --remote. HEAD is the branch
|
|
25
|
+
checked out at the target. Never the default branch.
|
|
26
|
+
--against <remote> The remote whose default branch counts as "merged into".
|
|
27
|
+
Default: origin, then the first remote.
|
|
28
|
+
--provider <name> With --remote: where pull requests come from. auto (by the
|
|
29
|
+
remote's host), github, gitlab, or none to judge on git
|
|
30
|
+
evidence alone.
|
|
31
|
+
--no-progress No progress line on stderr.`;
|
|
32
|
+
const REPORT = `The report: repo-cleaner [target...]
|
|
33
|
+
Deletes nothing. Lists each row with its verdict, why, the risks it carries,
|
|
34
|
+
and the command that would delete it.
|
|
35
|
+
--all Also show keep rows. By default only delete, review and
|
|
36
|
+
blocked rows are listed. Only with --format text.
|
|
37
|
+
--format text The report. The default.
|
|
38
|
+
--format summary Counts per repository: total, merged, delete, prune,
|
|
39
|
+
review, blocked, keep.
|
|
40
|
+
--format commands Only the commands of delete rows that carry no risk.
|
|
41
|
+
--format json Everything, evidence included, for scripts.`;
|
|
42
|
+
const PLAN = `repo-cleaner plan [target...]
|
|
43
|
+
Sweeps and saves every row, each with a fingerprint of the evidence behind its
|
|
44
|
+
verdict, so it can be reviewed now and run later with clean --plan.
|
|
45
|
+
-o, --out <file> Where the plan goes; - (the default) is stdout.`;
|
|
46
|
+
const CLEAN = `repo-cleaner clean [target...]
|
|
47
|
+
Sweeps (or reads --plan), checks every row again, shows the steps, asks, then
|
|
48
|
+
deletes, compare-and-swap, verifying each step and stopping at the first failure.
|
|
49
|
+
Without --pick it runs every row judged delete.
|
|
50
|
+
--pick <row> Work on exactly these rows. Narrows the run to them, and opts
|
|
51
|
+
in review rows and kept rows with an action (an open pull
|
|
52
|
+
request), which never run otherwise. A picked row still
|
|
53
|
+
needs --allow for its risks. A blocked row is skipped.
|
|
54
|
+
Repeatable.
|
|
55
|
+
--spare <row> Leave this row alone, and make sure it is what you saw. A
|
|
56
|
+
spared row is not deleted, even if it is judged delete. A
|
|
57
|
+
spared remote branch is still checked: if it is gone or its
|
|
58
|
+
tip moved since the plan, the run stops before deleting
|
|
59
|
+
anything. Sparing a worktree also spares its branch.
|
|
60
|
+
Repeatable.
|
|
61
|
+
--allow <risk> Accept this risk for every row that carries it.
|
|
62
|
+
--allow <risk>=<row> Accept it for that one row only.
|
|
63
|
+
--plan <file|-> Run a saved plan instead of sweeping now (- reads stdin).
|
|
64
|
+
Rows whose evidence changed since the plan are dropped.
|
|
65
|
+
-y, --yes Do not ask. Without it, and with no terminal to ask on,
|
|
66
|
+
clean refuses.
|
|
67
|
+
--dry-run Check and show the steps, then stop.
|
|
68
|
+
--receipt <file> Write a record of every step, rewritten after each one.
|
|
69
|
+
Default: next to a --plan file; none otherwise.
|
|
70
|
+
--format text|json How the steps and the outcome are shown.`;
|
|
71
|
+
const CACHE = `repo-cleaner cache [list | remove <url>... | remove --all]
|
|
72
|
+
Lists or removes the clones kept for remote URL targets. A removed clone is
|
|
73
|
+
cloned again by the next sweep of its URL; a plan that names it cannot run.`;
|
|
74
|
+
const WORDS = `Rows
|
|
75
|
+
One thing that could be deleted, named <kind>:<name>:
|
|
76
|
+
worktree:<path> a linked worktree: removed with git worktree remove, or,
|
|
77
|
+
when its directory is already gone, its record pruned
|
|
78
|
+
branch:<name> a local branch, deleted with git update-ref -d <sha>
|
|
79
|
+
remote:<remote>/<name> a branch on a remote, deleted by a push with a lease
|
|
80
|
+
A bare name or path works when only one row has it.
|
|
81
|
+
|
|
82
|
+
Verdicts
|
|
83
|
+
delete Its work is on the default branch, or it is otherwise safe to lose.
|
|
84
|
+
clean deletes it, once its risks are allowed.
|
|
85
|
+
review The signals are mixed, or it holds work. Deleted only when picked and
|
|
86
|
+
its risks are allowed.
|
|
87
|
+
blocked Something rules removal out: a lock, a running process, a submodule
|
|
88
|
+
that cannot be read, hidden index flags, a nested repository, a branch
|
|
89
|
+
checked out twice. Never deleted; fix the cause and sweep again.
|
|
90
|
+
keep Structural (the default branch, the current checkout, the primary
|
|
91
|
+
worktree): never deleted. Or in use (an open pull request): deleted
|
|
92
|
+
only when picked and confirm-open-pr is allowed.
|
|
93
|
+
prune Shown for a delete row whose worktree directory is already gone: only
|
|
94
|
+
the stale record goes.
|
|
95
|
+
|
|
96
|
+
Risks (approve each by name with --allow)
|
|
97
|
+
abandon-unmerged Commits that are not on the default branch, or a worktree's
|
|
98
|
+
(or its submodules') commits and stashes that no remote has.
|
|
99
|
+
discard-changes Uncommitted or untracked files in the worktree or any of its
|
|
100
|
+
submodules.
|
|
101
|
+
confirm-open-pr The branch has an open pull request; deleting the remote branch
|
|
102
|
+
closes it.
|
|
103
|
+
A worktree's submodules are part of it: one --allow on the worktree row covers
|
|
104
|
+
the same risk in every submodule.`;
|
|
105
|
+
const CONFLICTS = `Refused before anything is deleted (exit 2)
|
|
106
|
+
A combination is refused when it contradicts itself, or when one of its options
|
|
107
|
+
would have no effect. Most are refused before anything is swept; a row named
|
|
108
|
+
two ways (a bare name and its id), and an --allow for a row, are checked
|
|
109
|
+
against the plan, after the sweep.
|
|
110
|
+
the same row in --pick and --spare
|
|
111
|
+
--allow <risk>=<row> for a spared row, or for a row outside --pick
|
|
112
|
+
a picked or spared row of a kind --only leaves out
|
|
113
|
+
a picked or spared row that is not one of --branch's rows
|
|
114
|
+
--branch with more than one target, or with a remote URL, or the default branch
|
|
115
|
+
--plan with targets or sweep options: the plan already fixed them
|
|
116
|
+
--plan - without --yes or --dry-run: stdin carries the plan, so nothing can ask
|
|
117
|
+
--yes with --dry-run
|
|
118
|
+
--receipt with --dry-run
|
|
119
|
+
--offline or --provider without --remote
|
|
120
|
+
--remote with an --only that leaves remote out
|
|
121
|
+
a remote URL target with --only worktree or --only branch, with --offline, or
|
|
122
|
+
mixed with checkout targets
|
|
123
|
+
--all with any --format but text
|
|
124
|
+
|
|
125
|
+
Refused after the sweep, before deleting (exit 1)
|
|
126
|
+
a picked, spared or allowed row the plan does not have
|
|
127
|
+
a row name that matches rows in more than one repository
|
|
128
|
+
a spared remote branch that is gone or moved since the plan
|
|
129
|
+
|
|
130
|
+
Allowed, because nothing contradicts
|
|
131
|
+
the same row picked twice: it runs once
|
|
132
|
+
--allow <risk> with --allow <risk>=<row>: the broader one covers it
|
|
133
|
+
--allow for a risk no row that runs carries: the preview lists it as unused
|
|
134
|
+
picking a blocked or keep row: it is skipped and shown with the reason
|
|
135
|
+
--only remote without --remote: asking for remote branches implies the network`;
|
|
136
|
+
const EXIT = `Exit codes
|
|
137
|
+
0 swept; or clean ran, was declined, or had nothing to do
|
|
138
|
+
1 failed against the state it found: an unreadable plan, a row not in it
|
|
139
|
+
2 cannot run as typed: an unknown flag, a malformed row, a refused combination
|
|
140
|
+
10 no target named a repository
|
|
141
|
+
11 clean stopped at a failed step; the receipt says which`;
|
|
142
|
+
/** The extended help for each command: its own options, then the shared vocabulary. */
|
|
143
|
+
export const REFERENCE = {
|
|
144
|
+
report: [TARGETS, SWEEP, REPORT, WORDS, CONFLICTS, EXIT].join('\n\n'),
|
|
145
|
+
plan: [TARGETS, SWEEP, PLAN, WORDS, EXIT].join('\n\n'),
|
|
146
|
+
clean: [TARGETS, SWEEP, CLEAN, WORDS, CONFLICTS, EXIT].join('\n\n'),
|
|
147
|
+
cache: CACHE,
|
|
148
|
+
};
|
package/dist/remote-mirror.d.ts
CHANGED
|
@@ -77,3 +77,11 @@ export declare function listMirrors(cacheDir: string, opts?: {
|
|
|
77
77
|
* given, credentials included.
|
|
78
78
|
*/
|
|
79
79
|
export declare function cachedMirrorOf(url: string, cacheDir: string): string | null;
|
|
80
|
+
/**
|
|
81
|
+
* True when `target` names a remote rather than a directory: a URL with a
|
|
82
|
+
* scheme (`https://`, `ssh://`, `file://`), or the scp form `user@host:path`
|
|
83
|
+
* that names no directory on disk. A Windows drive path is never one.
|
|
84
|
+
*/
|
|
85
|
+
export declare function isRemoteUrl(target: string, exists: (path: string) => boolean): boolean;
|
|
86
|
+
/** Where remote URL targets' clones are kept: `repo-cleaner/remotes` under the user's cache directory. */
|
|
87
|
+
export declare function defaultCacheDir(env?: Record<string, string | undefined>): string;
|
package/dist/remote-mirror.js
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
import { createHash } from 'node:crypto';
|
|
16
16
|
import { existsSync, lstatSync, mkdirSync, readdirSync, renameSync, rmSync } from 'node:fs';
|
|
17
17
|
import { dirname, join } from 'node:path';
|
|
18
|
+
import { systemDirs } from '@osdlc/cli-tool';
|
|
18
19
|
import { Git, parseRemoteRepo } from '@osdlc/typed-git';
|
|
20
|
+
import { VENDOR_DIRS } from "./devlink.js";
|
|
19
21
|
/** A remote URL that cannot be mirrored, or a mirror that cannot be made or reused. */
|
|
20
22
|
export class RemoteMirrorError extends Error {
|
|
21
23
|
constructor(message) {
|
|
@@ -163,3 +165,19 @@ export function cachedMirrorOf(url, cacheDir) {
|
|
|
163
165
|
const dir = join(cacheDir, mirrorName(url));
|
|
164
166
|
return existsSync(dir) ? dir : null;
|
|
165
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* True when `target` names a remote rather than a directory: a URL with a
|
|
170
|
+
* scheme (`https://`, `ssh://`, `file://`), or the scp form `user@host:path`
|
|
171
|
+
* that names no directory on disk. A Windows drive path is never one.
|
|
172
|
+
*/
|
|
173
|
+
export function isRemoteUrl(target, exists) {
|
|
174
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(target))
|
|
175
|
+
return true;
|
|
176
|
+
if (/^[a-z]:[\\/]/i.test(target))
|
|
177
|
+
return false;
|
|
178
|
+
return /^(?:[^@/:\\]+@)?[^/:\\]+:(?!\/\/).+$/.test(target) && !exists(target);
|
|
179
|
+
}
|
|
180
|
+
/** Where remote URL targets' clones are kept: `repo-cleaner/remotes` under the user's cache directory. */
|
|
181
|
+
export function defaultCacheDir(env = process.env) {
|
|
182
|
+
return systemDirs(VENDOR_DIRS, { env }).standalone('repo-cleaner').cache().app('remotes').path;
|
|
183
|
+
}
|
package/dist/render.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Rendering a sweep for a terminal
|
|
2
|
+
* Rendering a sweep and a run, for a terminal and for a program.
|
|
3
3
|
*
|
|
4
4
|
* The text report is columnar because it is read by eye across repositories:
|
|
5
|
-
* the eye finds a column, and a line-per-
|
|
6
|
-
* find. JSON carries everything the text report
|
|
7
|
-
* behind every verdict.
|
|
5
|
+
* the eye finds a column, and a line-per-row paragraph gives it nothing to
|
|
6
|
+
* find. JSON carries everything the text report leaves out, the evidence
|
|
7
|
+
* behind every verdict included.
|
|
8
8
|
*/
|
|
9
|
-
import type {
|
|
10
|
-
import {
|
|
9
|
+
import type { Preview } from './clean.ts';
|
|
10
|
+
import type { Dropped, Receipt, Step } from './receipt.ts';
|
|
11
|
+
import { type Kind, type RepoSweep, type Sweep } from './types.ts';
|
|
11
12
|
/**
|
|
12
13
|
* How a report colors its words. {@link PLAIN} leaves them as they are; the
|
|
13
14
|
* CLI passes {@link TERMINAL_STYLE} when the stream is a terminal that takes
|
|
@@ -31,57 +32,38 @@ export interface Style {
|
|
|
31
32
|
export declare const PLAIN: Style;
|
|
32
33
|
/** The basic terminal colors: red to delete, blue to prune, yellow to review, green to keep. */
|
|
33
34
|
export declare const TERMINAL_STYLE: Style;
|
|
34
|
-
/**
|
|
35
|
-
* A row's authorizations for a report, e.g. `requires abandon-unmerged (drops 3
|
|
36
|
-
* commits), confirm-open-pr`. Empty when the row needs none.
|
|
37
|
-
*/
|
|
38
|
-
export declare function requiresNote(item: Pick<SweepItem, 'requires' | 'facts'>): string;
|
|
39
|
-
/** One repository's rows, indented under its header. */
|
|
35
|
+
/** One repository's rows, indented under its header. `all` shows `keep` rows too. */
|
|
40
36
|
export declare function renderRepo(sweep: RepoSweep, opts?: {
|
|
41
|
-
|
|
37
|
+
all?: boolean;
|
|
42
38
|
style?: Style;
|
|
43
39
|
}): string;
|
|
44
40
|
/**
|
|
45
|
-
* What the search cost, or null when nothing was searched — every
|
|
41
|
+
* What the search cost, or null when nothing was searched — every target
|
|
46
42
|
* named a checkout outright.
|
|
47
43
|
*
|
|
48
|
-
* Worth a line because the numbers are how a person catches
|
|
44
|
+
* Worth a line because the numbers are how a person catches a target
|
|
49
45
|
* pointing at the wrong directory: thousands of directories read for three
|
|
50
|
-
* repositories says the
|
|
46
|
+
* repositories says the target is a home directory, not a workspace.
|
|
51
47
|
*/
|
|
52
|
-
export declare function renderScan(
|
|
48
|
+
export declare function renderScan(sweep: Pick<Sweep, 'scan'>): string | null;
|
|
53
49
|
/** The one-line verdict a person reads first, and often only. */
|
|
54
|
-
export declare function
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
/** The whole run as counts: a table per repository, then the one-line summary. */
|
|
61
|
-
export declare function renderCountsText(result: SweepResult, style?: Style): string;
|
|
62
|
-
/** The whole run's counts as JSON: no rows, no facts. */
|
|
63
|
-
export declare function renderCountsJson(result: SweepResult): string;
|
|
64
|
-
/** The whole run as text: duplicate checkouts, a section per repository, then the summary. */
|
|
65
|
-
export declare function renderText(result: SweepResult, opts?: {
|
|
66
|
-
keep?: boolean;
|
|
50
|
+
export declare function renderTotals(sweep: Sweep, style?: Style): string;
|
|
51
|
+
/** The sweep as counts (`--format summary`): a table per repository, then the totals line. */
|
|
52
|
+
export declare function renderSummary(sweep: Sweep, style?: Style): string;
|
|
53
|
+
/** The sweep as text: duplicate checkouts, a section per repository, then the totals. */
|
|
54
|
+
export declare function renderText(sweep: Sweep, opts?: {
|
|
55
|
+
all?: boolean;
|
|
67
56
|
style?: Style;
|
|
68
57
|
}): string;
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
/** Rows deleted (or, in a dry run, that would be), by category. Pruned worktrees are not counted here. */
|
|
79
|
-
deleted: {
|
|
80
|
-
worktree: number;
|
|
81
|
-
local_branch: number;
|
|
82
|
-
remote_branch: number;
|
|
83
|
-
};
|
|
84
|
-
/** Worktrees whose stale registration was pruned; their directories were gone or left alone. */
|
|
58
|
+
/** The sweep as JSON: every row with its evidence, the counts, and what was swept. */
|
|
59
|
+
export declare function renderJson(sweep: Sweep, extra?: Record<string, unknown>): string;
|
|
60
|
+
/** Every command of a `delete` row that carries no risk, in report order (`--format commands`). */
|
|
61
|
+
export declare function renderCommands(sweep: Sweep): string[];
|
|
62
|
+
/** What a run did, or would do, counted by kind. */
|
|
63
|
+
export interface RunSummary {
|
|
64
|
+
/** Rows deleted (or, in a preview, that would be), by kind. Pruned worktrees are counted apart. */
|
|
65
|
+
deleted: Record<Kind, number>;
|
|
66
|
+
/** Worktrees whose stale record was pruned; their directories were gone or left alone. */
|
|
85
67
|
pruned: number;
|
|
86
68
|
/** Dropped rows by reason. */
|
|
87
69
|
dropped: Record<string, number>;
|
|
@@ -90,29 +72,47 @@ export interface ApplySummary {
|
|
|
90
72
|
/** Row steps never reached because a step failed. */
|
|
91
73
|
notRun: number;
|
|
92
74
|
}
|
|
93
|
-
/** Count
|
|
94
|
-
export declare function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
75
|
+
/** Count what `steps` did and `dropped` left out. A preview's `would-run` steps count as done. */
|
|
76
|
+
export declare function summarizeRun(r: {
|
|
77
|
+
steps: readonly Step[];
|
|
78
|
+
dropped: readonly Dropped[];
|
|
79
|
+
}): RunSummary;
|
|
80
|
+
/** Rows a run deletes or prunes, which is what a person is asked to approve. */
|
|
81
|
+
export declare function rowsActedOn(summary: RunSummary): number;
|
|
82
|
+
/** The closing lines of a run or preview: what it deleted, skipped and could not finish. */
|
|
83
|
+
export declare function renderRunSummary(r: {
|
|
84
|
+
steps: readonly Step[];
|
|
85
|
+
dropped: readonly Dropped[];
|
|
86
|
+
}, opts?: {
|
|
87
|
+
preview?: boolean;
|
|
88
|
+
}): string[];
|
|
99
89
|
/** The word a preview shows for a step: what it does to the row. */
|
|
100
90
|
export declare const STEP_VERB: Record<Step['action'], string>;
|
|
101
91
|
/**
|
|
102
|
-
* What
|
|
103
|
-
* repository, each row and the command that deletes it, the rows it will
|
|
104
|
-
*
|
|
105
|
-
* shows, so a path or name cannot hide part of what is
|
|
92
|
+
* What a run is about to do, for the person asked to approve it: per
|
|
93
|
+
* repository, each row and the command that deletes it, the rows it will skip
|
|
94
|
+
* and why, the allows no row needed, then the counts. Terminal controls are
|
|
95
|
+
* stripped from what it shows, so a path or name cannot hide part of what is
|
|
96
|
+
* approved.
|
|
106
97
|
*/
|
|
107
|
-
export declare function renderPreview(
|
|
108
|
-
/** Rows dropped by a
|
|
109
|
-
export declare function
|
|
98
|
+
export declare function renderPreview(p: Pick<Preview, 'steps' | 'dropped'> & Partial<Pick<Preview, 'unusedAllows'>>, style?: Style): string;
|
|
99
|
+
/** Rows dropped by a run's own re-check after the preview, each with why. */
|
|
100
|
+
export declare function renderLateDrops(dropped: readonly Dropped[], style?: Style): string;
|
|
110
101
|
/**
|
|
111
102
|
* A failed step for a run shown live: `✗`, the row, and the error in full,
|
|
112
|
-
* which the step's
|
|
103
|
+
* which the step's line on the board is too narrow to hold.
|
|
113
104
|
*/
|
|
114
105
|
export declare function renderStepFailure(step: Step, style?: Style): string;
|
|
115
|
-
/**
|
|
106
|
+
/** A run as text: dropped rows, each step, a summary, then where the receipt is. */
|
|
116
107
|
export declare function renderReceipt(receipt: Receipt, receiptPath: string | null, style?: Style, opts?: {
|
|
117
108
|
steps?: boolean;
|
|
118
109
|
}): string;
|
|
110
|
+
/** The renderers a library user needs: the CLI's own formats. */
|
|
111
|
+
export declare const render: {
|
|
112
|
+
text: typeof renderText;
|
|
113
|
+
json: typeof renderJson;
|
|
114
|
+
summary: typeof renderSummary;
|
|
115
|
+
commands: typeof renderCommands;
|
|
116
|
+
preview: typeof renderPreview;
|
|
117
|
+
receipt: typeof renderReceipt;
|
|
118
|
+
};
|