@osdlc/repo-cleaner 0.9.2 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +89 -62
- package/dist/apply.d.ts +45 -7
- package/dist/apply.js +118 -14
- package/dist/branches.d.ts +12 -2
- package/dist/branches.js +34 -8
- package/dist/cli.d.ts +26 -7
- package/dist/cli.js +472 -184
- package/dist/devlink.d.ts +5 -0
- package/dist/devlink.js +3 -1
- package/dist/index.d.ts +5 -6
- package/dist/index.js +3 -4
- package/dist/plan.d.ts +7 -3
- package/dist/plan.js +24 -11
- package/dist/receipt.d.ts +6 -0
- package/dist/receipt.js +11 -2
- package/dist/render.d.ts +33 -1
- package/dist/render.js +134 -1
- package/dist/status-line.d.ts +24 -0
- package/dist/status-line.js +54 -0
- package/dist/sweep.d.ts +8 -1
- package/dist/sweep.js +17 -4
- package/dist/types.d.ts +15 -0
- package/dist/types.js +3 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/worktrees.d.ts +1 -1
- package/dist/worktrees.js +21 -5
- package/package.json +7 -7
- package/dist/config.d.ts +0 -53
- package/dist/config.js +0 -67
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This package's version, from its package.json when it was built. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.10.1";
|
package/dist/worktrees.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export declare function hasBlockers(item: Pick<SweepItem, 'facts'>): boolean;
|
|
|
26
26
|
* The branch set is returned because a local branch checked out in a worktree
|
|
27
27
|
* must not be judged on its own: the worktree is what a person would act on.
|
|
28
28
|
*/
|
|
29
|
-
export declare function gatherWorktrees(repo: RepoInfo, prs: Map<string, PRInfo>, cwd: string, git: Git, only: string | undefined, probe: ProcessProbe): [SweepItem[], Set<string>];
|
|
29
|
+
export declare function gatherWorktrees(repo: RepoInfo, prs: Map<string, PRInfo>, cwd: string, git: Git, only: string | undefined, probe: ProcessProbe, tick?: (done: number, total: number) => void): [SweepItem[], Set<string>];
|
|
30
30
|
export declare function classifyWorktree(opts: {
|
|
31
31
|
path: string;
|
|
32
32
|
branch: string;
|
package/dist/worktrees.js
CHANGED
|
@@ -9,7 +9,7 @@ import { existsSync, statSync } from 'node:fs';
|
|
|
9
9
|
import { isAbsolute, join, sep } from 'node:path';
|
|
10
10
|
import { shellJoin } from '@osdlc/command-seam';
|
|
11
11
|
import { nodeGuard } from '@osdlc/path-guard/node';
|
|
12
|
-
import { gapSentence, recordPr } from "./branches.js";
|
|
12
|
+
import { gapSentence, mergedFact, recordPr } from "./branches.js";
|
|
13
13
|
import { confirmationNote, judgeLanding, landingConfirmed, recordLanding, unmatchedSentence, } from "./landing.js";
|
|
14
14
|
import { busyHolders, isIdleHolder, parseLockOwner } from "./processes.js";
|
|
15
15
|
import { authorizationsFor, landedRef, makeItem, } from "./types.js";
|
|
@@ -141,7 +141,7 @@ function readBlockers(opts) {
|
|
|
141
141
|
* The branch set is returned because a local branch checked out in a worktree
|
|
142
142
|
* must not be judged on its own: the worktree is what a person would act on.
|
|
143
143
|
*/
|
|
144
|
-
export function gatherWorktrees(repo, prs, cwd, git, only, probe) {
|
|
144
|
+
export function gatherWorktrees(repo, prs, cwd, git, only, probe, tick) {
|
|
145
145
|
const all = git.at(repo.root).worktrees();
|
|
146
146
|
const pathsByBranch = new Map();
|
|
147
147
|
for (const entry of all) {
|
|
@@ -151,7 +151,8 @@ export function gatherWorktrees(repo, prs, cwd, git, only, probe) {
|
|
|
151
151
|
}
|
|
152
152
|
const entries = only === undefined ? all : all.filter((entry) => entry.branch === only);
|
|
153
153
|
const items = [];
|
|
154
|
-
for (const entry of entries) {
|
|
154
|
+
for (const [i, entry] of entries.entries()) {
|
|
155
|
+
tick?.(i, entries.length);
|
|
155
156
|
const branch = entry.branch ?? '';
|
|
156
157
|
items.push(classifyWorktree({
|
|
157
158
|
path: entry.path,
|
|
@@ -202,8 +203,13 @@ export function classifyWorktree(opts) {
|
|
|
202
203
|
if (nodeGuard.contains(path, absCwd)) {
|
|
203
204
|
return pinned("current session's worktree");
|
|
204
205
|
}
|
|
205
|
-
|
|
206
|
+
// A directory whose `.git` is gone is no longer a checkout: git lists it as
|
|
207
|
+
// prunable and `git status` inside it fails. Treat it as gone; the prune
|
|
208
|
+
// that drops its registration leaves the directory alone.
|
|
209
|
+
const dirPresent = isDir(path);
|
|
210
|
+
const onDisk = dirPresent && existsSync(join(path, '.git'));
|
|
206
211
|
facts['on_disk'] = onDisk;
|
|
212
|
+
facts['directory_left'] = dirPresent && !onDisk;
|
|
207
213
|
let dirtyFiles = [];
|
|
208
214
|
let unpushed = [];
|
|
209
215
|
if (onDisk) {
|
|
@@ -244,6 +250,14 @@ export function classifyWorktree(opts) {
|
|
|
244
250
|
recordLanding(facts, patch);
|
|
245
251
|
const patchLanded = patch !== null && patch.check !== null;
|
|
246
252
|
const unlanded = unpushed.length > 0 && !landed && !landingConfirmed(patch);
|
|
253
|
+
if (onDisk && head !== null) {
|
|
254
|
+
// `soft`: a missing default ref reads as not merged.
|
|
255
|
+
facts['merged'] = mergedFact({
|
|
256
|
+
landed,
|
|
257
|
+
patchLanded,
|
|
258
|
+
ancestor: git.at(repo.root).soft.isAncestor(head, landedRef(repo)),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
247
261
|
const requires = authorizationsFor({
|
|
248
262
|
'abandon-unmerged': unlanded,
|
|
249
263
|
'discard-changes': dirtyFiles.length > 0,
|
|
@@ -280,7 +294,9 @@ export function classifyWorktree(opts) {
|
|
|
280
294
|
id: path,
|
|
281
295
|
classification: 'safe-to-delete',
|
|
282
296
|
action: 'remove',
|
|
283
|
-
reason:
|
|
297
|
+
reason: dirPresent
|
|
298
|
+
? 'directory is no longer a checkout (no .git); prune the stale link, the directory stays'
|
|
299
|
+
: 'worktree directory no longer on disk; prune the stale link',
|
|
284
300
|
facts,
|
|
285
301
|
commands: pruneOnly,
|
|
286
302
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@osdlc/repo-cleaner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "What has been merged into the default branch, and what can go away. Inventories local branches, worktrees and remote branches across one repository or many, classifying each with the evidence behind the verdict and the command that would act on it. Plans and applies approved deletes, compare-and-swap.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"author": "sksizer",
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"zod": "4.4.3",
|
|
52
52
|
"@osdlc/cli-tool": "^0.12.0",
|
|
53
|
-
"@osdlc/command-seam": "^0.14.0",
|
|
54
|
-
"@osdlc/config-layers": "^0.8.0",
|
|
55
53
|
"@osdlc/path-guard": "^0.3.1",
|
|
56
|
-
"@osdlc/
|
|
57
|
-
"@osdlc/typed-git": "^0.
|
|
54
|
+
"@osdlc/command-seam": "^0.14.0",
|
|
55
|
+
"@osdlc/typed-git": "^0.12.0",
|
|
56
|
+
"@osdlc/typed-gh": "^0.14.1",
|
|
57
|
+
"@osdlc/ux": "^0.1.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^26.1.0",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"prettier": "3.9.6",
|
|
64
64
|
"typescript": "~6.0.3",
|
|
65
65
|
"vitest": "^5.0.5",
|
|
66
|
-
"@osdlc/
|
|
67
|
-
"@osdlc/
|
|
66
|
+
"@osdlc/tsconfig": "^0.4.1",
|
|
67
|
+
"@osdlc/prettier-config": "^0.3.1"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"build": "cli-tool-stamp-version && tsc -p tsconfig.build.json",
|
package/dist/config.d.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The standalone tool's own configuration file.
|
|
3
|
-
*
|
|
4
|
-
* Only the CLI reads it. The library takes repositories as an argument, so a
|
|
5
|
-
* host that already knows where its repositories are passes them in and never touches this file.
|
|
6
|
-
*
|
|
7
|
-
* Shape, location and loading are `@osdlc/config-layers`'s machine file:
|
|
8
|
-
* `<config-home>/repo-cleaner/config.yaml` over `@osdlc/tiered-dirs`'
|
|
9
|
-
* standalone tier, Zod as the single source of truth, never throws, degrades
|
|
10
|
-
* to the defaults, memoised. Standalone, not suite or vendor, because a
|
|
11
|
-
* general tool must not write a company namespace onto someone else's
|
|
12
|
-
* machine.
|
|
13
|
-
*/
|
|
14
|
-
import { z } from 'zod';
|
|
15
|
-
/** The tool's name, and so its config directory segment. */
|
|
16
|
-
export declare const TOOL = "repo-cleaner";
|
|
17
|
-
/** Env var that relocates the config file, for tests and one-off runs. */
|
|
18
|
-
export declare const CONFIG_ENV = "REPO_CLEANER_MACHINE_CONFIG";
|
|
19
|
-
export declare const RepoCleanerConfigSchema: z.ZodObject<{
|
|
20
|
-
paths: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
21
|
-
repos: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
22
|
-
}, z.core.$strict>;
|
|
23
|
-
export type RepoCleanerConfig = z.infer<typeof RepoCleanerConfigSchema>;
|
|
24
|
-
/**
|
|
25
|
-
* Whose directories the config file, plans and receipts live under: vendor
|
|
26
|
-
* `repo-cleaner`, moved by the `REPO_CLEANER_*` override variables.
|
|
27
|
-
*/
|
|
28
|
-
export declare const VENDOR_DIRS: {
|
|
29
|
-
vendor: string;
|
|
30
|
-
envVars: import("@osdlc/tiered-dirs").EnvVars;
|
|
31
|
-
};
|
|
32
|
-
export declare const DEFAULT_CONFIG: RepoCleanerConfig;
|
|
33
|
-
/** `<config-home>/repo-cleaner/config.yaml`, unless {@link CONFIG_ENV} says otherwise. */
|
|
34
|
-
export declare const configPath: () => string;
|
|
35
|
-
/**
|
|
36
|
-
* The config, defaults filled.
|
|
37
|
-
*
|
|
38
|
-
* Never throws. A missing file, unreadable file, malformed YAML or wrong
|
|
39
|
-
* shape all degrade to the defaults: a person running the tool in a
|
|
40
|
-
* repository should not be stopped by a config file they never wrote.
|
|
41
|
-
*/
|
|
42
|
-
export declare const loadConfig: () => RepoCleanerConfig;
|
|
43
|
-
/** Drop the memoised value. Call from tests between runs. */
|
|
44
|
-
export declare const clearConfigCache: () => void;
|
|
45
|
-
/**
|
|
46
|
-
* The configured entries with their `{name}` path names substituted against
|
|
47
|
-
* `paths:`, relative entries resolved against `baseDir`.
|
|
48
|
-
*
|
|
49
|
-
* A name nothing defines is an error rather than an empty string — a
|
|
50
|
-
* `repos:` entry that quietly becomes `/dev` would sweep whatever happens to
|
|
51
|
-
* be there.
|
|
52
|
-
*/
|
|
53
|
-
export declare function configuredRepos(config?: RepoCleanerConfig, baseDir?: string): string[];
|
package/dist/config.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The standalone tool's own configuration file.
|
|
3
|
-
*
|
|
4
|
-
* Only the CLI reads it. The library takes repositories as an argument, so a
|
|
5
|
-
* host that already knows where its repositories are passes them in and never touches this file.
|
|
6
|
-
*
|
|
7
|
-
* Shape, location and loading are `@osdlc/config-layers`'s machine file:
|
|
8
|
-
* `<config-home>/repo-cleaner/config.yaml` over `@osdlc/tiered-dirs`'
|
|
9
|
-
* standalone tier, Zod as the single source of truth, never throws, degrades
|
|
10
|
-
* to the defaults, memoised. Standalone, not suite or vendor, because a
|
|
11
|
-
* general tool must not write a company namespace onto someone else's
|
|
12
|
-
* machine.
|
|
13
|
-
*/
|
|
14
|
-
import { envVars } from '@osdlc/cli-tool';
|
|
15
|
-
import { machineConfigFile, pathNames, pathsSchema, substitutePathNames, } from '@osdlc/config-layers';
|
|
16
|
-
import { z } from 'zod';
|
|
17
|
-
/** The tool's name, and so its config directory segment. */
|
|
18
|
-
export const TOOL = 'repo-cleaner';
|
|
19
|
-
/** Env var that relocates the config file, for tests and one-off runs. */
|
|
20
|
-
export const CONFIG_ENV = 'REPO_CLEANER_MACHINE_CONFIG';
|
|
21
|
-
export const RepoCleanerConfigSchema = z
|
|
22
|
-
.object({
|
|
23
|
-
paths: pathsSchema()
|
|
24
|
-
.default({})
|
|
25
|
-
.describe('Named directories; a `repos:` entry refers to one as `{name}`.'),
|
|
26
|
-
repos: z
|
|
27
|
-
.array(z.string().trim().min(1))
|
|
28
|
-
.default([])
|
|
29
|
-
.describe('Checkouts, or directories containing checkouts at any depth.'),
|
|
30
|
-
})
|
|
31
|
-
.strict();
|
|
32
|
-
/**
|
|
33
|
-
* Whose directories the config file, plans and receipts live under: vendor
|
|
34
|
-
* `repo-cleaner`, moved by the `REPO_CLEANER_*` override variables.
|
|
35
|
-
*/
|
|
36
|
-
export const VENDOR_DIRS = { vendor: TOOL, envVars: envVars('REPO_CLEANER') };
|
|
37
|
-
const file = machineConfigFile({
|
|
38
|
-
tool: TOOL,
|
|
39
|
-
schema: RepoCleanerConfigSchema,
|
|
40
|
-
vendorDirs: VENDOR_DIRS,
|
|
41
|
-
env: CONFIG_ENV,
|
|
42
|
-
});
|
|
43
|
-
export const DEFAULT_CONFIG = RepoCleanerConfigSchema.parse({});
|
|
44
|
-
/** `<config-home>/repo-cleaner/config.yaml`, unless {@link CONFIG_ENV} says otherwise. */
|
|
45
|
-
export const configPath = () => file.path();
|
|
46
|
-
/**
|
|
47
|
-
* The config, defaults filled.
|
|
48
|
-
*
|
|
49
|
-
* Never throws. A missing file, unreadable file, malformed YAML or wrong
|
|
50
|
-
* shape all degrade to the defaults: a person running the tool in a
|
|
51
|
-
* repository should not be stopped by a config file they never wrote.
|
|
52
|
-
*/
|
|
53
|
-
export const loadConfig = () => file.load();
|
|
54
|
-
/** Drop the memoised value. Call from tests between runs. */
|
|
55
|
-
export const clearConfigCache = () => file.clearCache();
|
|
56
|
-
/**
|
|
57
|
-
* The configured entries with their `{name}` path names substituted against
|
|
58
|
-
* `paths:`, relative entries resolved against `baseDir`.
|
|
59
|
-
*
|
|
60
|
-
* A name nothing defines is an error rather than an empty string — a
|
|
61
|
-
* `repos:` entry that quietly becomes `/dev` would sweep whatever happens to
|
|
62
|
-
* be there.
|
|
63
|
-
*/
|
|
64
|
-
export function configuredRepos(config = loadConfig(), baseDir = process.cwd()) {
|
|
65
|
-
const names = pathNames(baseDir, config.paths, {});
|
|
66
|
-
return config.repos.map((entry) => substitutePathNames(entry, names, 'repos:'));
|
|
67
|
-
}
|