ghostrail 0.13.0 → 0.15.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 +2 -2
- package/dist/board/index.d.ts +1 -1
- package/dist/board/index.d.ts.map +1 -1
- package/dist/board/index.js.map +1 -1
- package/dist/board/page.d.ts +1 -1
- package/dist/board/page.d.ts.map +1 -1
- package/dist/board/page.js +169 -11
- package/dist/board/page.js.map +1 -1
- package/dist/board/server.d.ts +19 -2
- package/dist/board/server.d.ts.map +1 -1
- package/dist/board/server.js +38 -3
- package/dist/board/server.js.map +1 -1
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +164 -14
- package/dist/cli/commands.js.map +1 -1
- package/dist/commands/help.js +3 -3
- package/dist/commands/help.js.map +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +7 -5
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +15 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +5 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/dashboard/server.d.ts +2 -2
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +8 -4
- package/dist/dashboard/server.js.map +1 -1
- package/dist/factory/build.d.ts +22 -2
- package/dist/factory/build.d.ts.map +1 -1
- package/dist/factory/build.js +11 -6
- package/dist/factory/build.js.map +1 -1
- package/dist/factory/checkout.d.ts +41 -0
- package/dist/factory/checkout.d.ts.map +1 -0
- package/dist/factory/checkout.js +90 -0
- package/dist/factory/checkout.js.map +1 -0
- package/dist/factory/state.d.ts +85 -0
- package/dist/factory/state.d.ts.map +1 -0
- package/dist/factory/state.js +157 -0
- package/dist/factory/state.js.map +1 -0
- package/dist/global/index.d.ts +3 -1
- package/dist/global/index.d.ts.map +1 -1
- package/dist/global/index.js +3 -1
- package/dist/global/index.js.map +1 -1
- package/dist/global/registry.d.ts +38 -5
- package/dist/global/registry.d.ts.map +1 -1
- package/dist/global/registry.js +40 -14
- package/dist/global/registry.js.map +1 -1
- package/dist/global/repo-id.d.ts +50 -0
- package/dist/global/repo-id.d.ts.map +1 -0
- package/dist/global/repo-id.js +149 -0
- package/dist/global/repo-id.js.map +1 -0
- package/dist/global/state-dir.d.ts +32 -0
- package/dist/global/state-dir.d.ts.map +1 -0
- package/dist/global/state-dir.js +43 -0
- package/dist/global/state-dir.js.map +1 -0
- package/dist/global/store.d.ts +2 -0
- package/dist/global/store.d.ts.map +1 -1
- package/dist/global/store.js.map +1 -1
- package/dist/loop/advice.d.ts +11 -0
- package/dist/loop/advice.d.ts.map +1 -0
- package/dist/loop/advice.js +48 -0
- package/dist/loop/advice.js.map +1 -0
- package/dist/loop/control.d.ts +15 -1
- package/dist/loop/control.d.ts.map +1 -1
- package/dist/loop/control.js +8 -1
- package/dist/loop/control.js.map +1 -1
- package/dist/loop/engine.d.ts.map +1 -1
- package/dist/loop/engine.js +1 -0
- package/dist/loop/engine.js.map +1 -1
- package/dist/loop/index.d.ts +1 -0
- package/dist/loop/index.d.ts.map +1 -1
- package/dist/loop/index.js +1 -0
- package/dist/loop/index.js.map +1 -1
- package/dist/publish/gh.d.ts +4 -1
- package/dist/publish/gh.d.ts.map +1 -1
- package/dist/publish/gh.js +58 -0
- package/dist/publish/gh.js.map +1 -1
- package/dist/publish/githost.d.ts +19 -0
- package/dist/publish/githost.d.ts.map +1 -1
- package/dist/publish/githost.js.map +1 -1
- package/dist/publish/index.d.ts +2 -2
- package/dist/publish/index.d.ts.map +1 -1
- package/dist/publish/index.js +1 -1
- package/dist/publish/index.js.map +1 -1
- package/dist/service/manager.d.ts +2 -0
- package/dist/service/manager.d.ts.map +1 -1
- package/dist/service/manager.js +2 -1
- package/dist/service/manager.js.map +1 -1
- package/dist/service/unit.d.ts +16 -7
- package/dist/service/unit.d.ts.map +1 -1
- package/dist/service/unit.js +11 -7
- package/dist/service/unit.js.map +1 -1
- package/dist/store/events.d.ts +3 -0
- package/dist/store/events.d.ts.map +1 -1
- package/dist/store/events.js +2 -0
- package/dist/store/events.js.map +1 -1
- package/dist/store/file-store.d.ts +5 -18
- package/dist/store/file-store.d.ts.map +1 -1
- package/dist/store/file-store.js +1 -1
- package/dist/store/file-store.js.map +1 -1
- package/dist/store/index.d.ts +2 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js.map +1 -1
- package/dist/store/port.d.ts +53 -0
- package/dist/store/port.d.ts.map +1 -0
- package/dist/store/port.js +2 -0
- package/dist/store/port.js.map +1 -0
- package/package.json +1 -1
- package/skill/ghostrail/SKILL.md +2 -2
- package/templates/{code-local → code}/README.md +2 -2
- package/templates/{code-local → code}/ghostrail.toml +2 -2
- package/templates/{content-loop → content}/README.md +2 -2
- package/templates/{content-loop → content}/ghostrail.toml +1 -1
- /package/templates/{code-local → code}/prompts/resolve-issue.md +0 -0
- /package/templates/{code-local → code}/prompts/respond.md +0 -0
- /package/templates/{code-local → code}/prompts/triage.md +0 -0
- /package/templates/{content-loop → content}/prompts/draft.md +0 -0
- /package/templates/{content-loop → content}/prompts/respond.md +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { GhostrailConfig } from "../config/schema.js";
|
|
2
|
+
import type { GlobalEnv } from "../global/store.js";
|
|
3
|
+
/** The store directory a pre-TJ-1585 ghostrail kept inside its checkout. */
|
|
4
|
+
export declare const LEGACY_STORE_DIR = ".factory";
|
|
5
|
+
/**
|
|
6
|
+
* The identity of the repo a checkout belongs to.
|
|
7
|
+
*
|
|
8
|
+
* `[factory].source` wins when set, matching `resolveRepoSlug`: a factory that
|
|
9
|
+
* clones from a URL is that repo's factory wherever the command was typed. The
|
|
10
|
+
* checkout's `origin` is the fallback, and a repo with neither is keyed on its
|
|
11
|
+
* path, marked so it is visibly path-keyed.
|
|
12
|
+
*/
|
|
13
|
+
export declare function repoIdFor(repoPath: string, config?: GhostrailConfig): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Where this checkout's ghostrail keeps its state, and the legacy directory it
|
|
16
|
+
* used to keep it in.
|
|
17
|
+
*
|
|
18
|
+
* Both, because every caller that needs the new path also needs to know whether
|
|
19
|
+
* there is something at the old one to carry over.
|
|
20
|
+
*/
|
|
21
|
+
export declare function stateDirFor(repoPath: string, config?: GhostrailConfig, env?: GlobalEnv, home?: string): Promise<{
|
|
22
|
+
dir: string;
|
|
23
|
+
legacy: string;
|
|
24
|
+
repoId: string;
|
|
25
|
+
}>;
|
|
26
|
+
/** What {@link migrateLegacyStore} did, so the caller can report it honestly. */
|
|
27
|
+
export type MigrationOutcome = {
|
|
28
|
+
kind: "nothing-to-do";
|
|
29
|
+
} | {
|
|
30
|
+
kind: "migrated";
|
|
31
|
+
from: string;
|
|
32
|
+
to: string;
|
|
33
|
+
} | {
|
|
34
|
+
kind: "already-populated";
|
|
35
|
+
from: string;
|
|
36
|
+
to: string;
|
|
37
|
+
} | {
|
|
38
|
+
kind: "failed";
|
|
39
|
+
from: string;
|
|
40
|
+
to: string;
|
|
41
|
+
error: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Carry a checkout's `.factory` over to the repo's state directory.
|
|
45
|
+
*
|
|
46
|
+
* Copies rather than moves, and never deletes the original. A move that half
|
|
47
|
+
* succeeds loses a run history that exists nowhere else, and the cost of
|
|
48
|
+
* leaving a stale directory behind is one line of output telling the operator
|
|
49
|
+
* they can remove it. They can see it; the tool cannot see what it destroyed.
|
|
50
|
+
*
|
|
51
|
+
* Skipped when the destination already holds anything. That is what makes this
|
|
52
|
+
* idempotent, and it is also the answer to two checkouts of one repo each
|
|
53
|
+
* having their own `.factory`: the first is carried over and the second is
|
|
54
|
+
* reported rather than silently merged into or clobbering it. Merging two event
|
|
55
|
+
* logs is possible, but choosing which history is the real one is not a
|
|
56
|
+
* decision to make on someone's behalf.
|
|
57
|
+
*/
|
|
58
|
+
export declare function migrateLegacyStore(legacy: string, dir: string): Promise<MigrationOutcome>;
|
|
59
|
+
/** One line describing an outcome, or undefined when there is nothing to say. */
|
|
60
|
+
export declare function describeMigration(outcome: MigrationOutcome): string | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* The clone URL for this repo: `[factory].source` when set, else the checkout's
|
|
63
|
+
* own origin. Undefined for a repo with no remote, which cannot be cloned and
|
|
64
|
+
* must keep using the checkout it lives in.
|
|
65
|
+
*/
|
|
66
|
+
export declare function cloneSourceFor(repoPath: string, config?: GhostrailConfig): Promise<string | undefined>;
|
|
67
|
+
/**
|
|
68
|
+
* Where worktrees should be made from: ghostrail's own clone when it can have
|
|
69
|
+
* one, else the operator's checkout.
|
|
70
|
+
*
|
|
71
|
+
* Falls back rather than failing, in every case. A repo with no remote has
|
|
72
|
+
* nothing to clone; a clone that cannot be made or fetched leaves the old
|
|
73
|
+
* borrow-your-checkout behavior in place. Neither is worth refusing to run
|
|
74
|
+
* over, and both are worth saying out loud.
|
|
75
|
+
*/
|
|
76
|
+
export declare function resolveWorkRepo(options: {
|
|
77
|
+
repoPath: string;
|
|
78
|
+
stateDir: string;
|
|
79
|
+
config: GhostrailConfig;
|
|
80
|
+
env?: NodeJS.ProcessEnv;
|
|
81
|
+
}): Promise<{
|
|
82
|
+
path?: string;
|
|
83
|
+
note?: string;
|
|
84
|
+
}>;
|
|
85
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/factory/state.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,aAAa,CAAC;AAE3C;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAS3F;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,eAAe,EACxB,GAAG,GAAE,SAAuB,EAC5B,IAAI,GAAE,MAAkB,GACvB,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAO1D;AAED,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,GACzB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAYhE;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAoB/F;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAW/E;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAO7B;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,GAAG,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAwB5C"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { cp, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { spawnCollect } from "../backend/proc.js";
|
|
6
|
+
import { canonicalRepoId, localRepoId } from "../global/repo-id.js";
|
|
7
|
+
import { repoStateDir } from "../global/state-dir.js";
|
|
8
|
+
import { ensureManagedCheckout } from "./checkout.js";
|
|
9
|
+
/** The store directory a pre-TJ-1585 ghostrail kept inside its checkout. */
|
|
10
|
+
export const LEGACY_STORE_DIR = ".factory";
|
|
11
|
+
/**
|
|
12
|
+
* The identity of the repo a checkout belongs to.
|
|
13
|
+
*
|
|
14
|
+
* `[factory].source` wins when set, matching `resolveRepoSlug`: a factory that
|
|
15
|
+
* clones from a URL is that repo's factory wherever the command was typed. The
|
|
16
|
+
* checkout's `origin` is the fallback, and a repo with neither is keyed on its
|
|
17
|
+
* path, marked so it is visibly path-keyed.
|
|
18
|
+
*/
|
|
19
|
+
export async function repoIdFor(repoPath, config) {
|
|
20
|
+
const source = config?.factory.source;
|
|
21
|
+
if (source !== undefined) {
|
|
22
|
+
const fromSource = canonicalRepoId(source);
|
|
23
|
+
if (fromSource !== undefined)
|
|
24
|
+
return fromSource;
|
|
25
|
+
}
|
|
26
|
+
const origin = await spawnCollect(["git", "-C", repoPath, "remote", "get-url", "origin"]);
|
|
27
|
+
const fromOrigin = origin.exitCode === 0 ? canonicalRepoId(origin.stdout) : undefined;
|
|
28
|
+
return fromOrigin ?? localRepoId(repoPath);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Where this checkout's ghostrail keeps its state, and the legacy directory it
|
|
32
|
+
* used to keep it in.
|
|
33
|
+
*
|
|
34
|
+
* Both, because every caller that needs the new path also needs to know whether
|
|
35
|
+
* there is something at the old one to carry over.
|
|
36
|
+
*/
|
|
37
|
+
export async function stateDirFor(repoPath, config, env = process.env, home = homedir()) {
|
|
38
|
+
const repoId = await repoIdFor(repoPath, config);
|
|
39
|
+
return {
|
|
40
|
+
repoId,
|
|
41
|
+
dir: repoStateDir(repoId, env, home),
|
|
42
|
+
legacy: join(repoPath, LEGACY_STORE_DIR),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** True when `dir` exists and holds at least one entry. */
|
|
46
|
+
async function hasContent(dir) {
|
|
47
|
+
try {
|
|
48
|
+
if (!(await stat(dir)).isDirectory())
|
|
49
|
+
return false;
|
|
50
|
+
return (await readdir(dir)).length > 0;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Carry a checkout's `.factory` over to the repo's state directory.
|
|
58
|
+
*
|
|
59
|
+
* Copies rather than moves, and never deletes the original. A move that half
|
|
60
|
+
* succeeds loses a run history that exists nowhere else, and the cost of
|
|
61
|
+
* leaving a stale directory behind is one line of output telling the operator
|
|
62
|
+
* they can remove it. They can see it; the tool cannot see what it destroyed.
|
|
63
|
+
*
|
|
64
|
+
* Skipped when the destination already holds anything. That is what makes this
|
|
65
|
+
* idempotent, and it is also the answer to two checkouts of one repo each
|
|
66
|
+
* having their own `.factory`: the first is carried over and the second is
|
|
67
|
+
* reported rather than silently merged into or clobbering it. Merging two event
|
|
68
|
+
* logs is possible, but choosing which history is the real one is not a
|
|
69
|
+
* decision to make on someone's behalf.
|
|
70
|
+
*/
|
|
71
|
+
export async function migrateLegacyStore(legacy, dir) {
|
|
72
|
+
if (!(await hasContent(legacy)))
|
|
73
|
+
return { kind: "nothing-to-do" };
|
|
74
|
+
if (await hasContent(dir))
|
|
75
|
+
return { kind: "already-populated", from: legacy, to: dir };
|
|
76
|
+
try {
|
|
77
|
+
await cp(legacy, dir, { recursive: true });
|
|
78
|
+
// Verified before it is reported as done: `cp` resolving is not proof the
|
|
79
|
+
// destination is readable, and this is the only claim the operator has that
|
|
80
|
+
// their history survived.
|
|
81
|
+
if (!(await hasContent(dir))) {
|
|
82
|
+
return { kind: "failed", from: legacy, to: dir, error: "the copy left nothing behind" };
|
|
83
|
+
}
|
|
84
|
+
return { kind: "migrated", from: legacy, to: dir };
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
kind: "failed",
|
|
89
|
+
from: legacy,
|
|
90
|
+
to: dir,
|
|
91
|
+
error: error instanceof Error ? error.message : String(error),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** One line describing an outcome, or undefined when there is nothing to say. */
|
|
96
|
+
export function describeMigration(outcome) {
|
|
97
|
+
switch (outcome.kind) {
|
|
98
|
+
case "nothing-to-do":
|
|
99
|
+
return undefined;
|
|
100
|
+
case "migrated":
|
|
101
|
+
return `carried this repo's run history over to ${outcome.to}; ${outcome.from} is now unused and safe to delete`;
|
|
102
|
+
case "already-populated":
|
|
103
|
+
return `left ${outcome.from} alone: ${outcome.to} already holds history for this repo, and merging two histories is not a choice to make for you`;
|
|
104
|
+
case "failed":
|
|
105
|
+
return `could not carry ${outcome.from} over to ${outcome.to} (${outcome.error}); the original is untouched`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The clone URL for this repo: `[factory].source` when set, else the checkout's
|
|
110
|
+
* own origin. Undefined for a repo with no remote, which cannot be cloned and
|
|
111
|
+
* must keep using the checkout it lives in.
|
|
112
|
+
*/
|
|
113
|
+
export async function cloneSourceFor(repoPath, config) {
|
|
114
|
+
const source = config?.factory.source;
|
|
115
|
+
if (source !== undefined && source.length > 0)
|
|
116
|
+
return source;
|
|
117
|
+
const origin = await spawnCollect(["git", "-C", repoPath, "remote", "get-url", "origin"]);
|
|
118
|
+
if (origin.exitCode !== 0)
|
|
119
|
+
return undefined;
|
|
120
|
+
const url = origin.stdout.trim();
|
|
121
|
+
return url.length === 0 ? undefined : url;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Where worktrees should be made from: ghostrail's own clone when it can have
|
|
125
|
+
* one, else the operator's checkout.
|
|
126
|
+
*
|
|
127
|
+
* Falls back rather than failing, in every case. A repo with no remote has
|
|
128
|
+
* nothing to clone; a clone that cannot be made or fetched leaves the old
|
|
129
|
+
* borrow-your-checkout behavior in place. Neither is worth refusing to run
|
|
130
|
+
* over, and both are worth saying out loud.
|
|
131
|
+
*/
|
|
132
|
+
export async function resolveWorkRepo(options) {
|
|
133
|
+
const { repoPath, stateDir, config } = options;
|
|
134
|
+
if (!config.backend.managedCheckout)
|
|
135
|
+
return {};
|
|
136
|
+
// A backend that clones per run has no use for one kept between runs.
|
|
137
|
+
if (config.backend.kind !== "local-worktree")
|
|
138
|
+
return {};
|
|
139
|
+
const source = await cloneSourceFor(repoPath, config);
|
|
140
|
+
if (source === undefined) {
|
|
141
|
+
return { note: `${repoPath} has no remote, so runs use this checkout directly` };
|
|
142
|
+
}
|
|
143
|
+
const result = await ensureManagedCheckout({
|
|
144
|
+
stateDir,
|
|
145
|
+
source,
|
|
146
|
+
baseBranch: config.factory.baseBranch,
|
|
147
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
148
|
+
});
|
|
149
|
+
if (result.kind === "unavailable") {
|
|
150
|
+
return { note: `using ${repoPath} for worktrees: ${result.reason}` };
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
path: result.path,
|
|
154
|
+
...(result.fetched ? {} : { note: `could not fetch ${source}; runs branch from a stale base` }),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/factory/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD,OAAO,EAAuB,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAE3E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAgB,EAAE,MAAwB;IACxE,MAAM,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC;IACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO,UAAU,CAAC;IAClD,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC1F,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACtF,OAAO,UAAU,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,QAAgB,EAChB,MAAwB,EACxB,MAAiB,OAAO,CAAC,GAAG,EAC5B,OAAe,OAAO,EAAE;IAExB,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACjD,OAAO;QACL,MAAM;QACN,GAAG,EAAE,YAAY,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;QACpC,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,gBAAgB,CAAC;KACzC,CAAC;AACJ,CAAC;AASD,2DAA2D;AAC3D,KAAK,UAAU,UAAU,CAAC,GAAW;IACnC,IAAI,CAAC;QACH,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE;YAAE,OAAO,KAAK,CAAC;QACnD,OAAO,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAc,EAAE,GAAW;IAClE,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;IAClE,IAAI,MAAM,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC;IACvF,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,0EAA0E;QAC1E,4EAA4E;QAC5E,0BAA0B;QAC1B,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC;QAC1F,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC;IACrD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,MAAM;YACZ,EAAE,EAAE,GAAG;YACP,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC9D,CAAC;IACJ,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,OAAyB;IACzD,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,eAAe;YAClB,OAAO,SAAS,CAAC;QACnB,KAAK,UAAU;YACb,OAAO,2CAA2C,OAAO,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,mCAAmC,CAAC;QACnH,KAAK,mBAAmB;YACtB,OAAO,QAAQ,OAAO,CAAC,IAAI,WAAW,OAAO,CAAC,EAAE,iGAAiG,CAAC;QACpJ,KAAK,QAAQ;YACX,OAAO,mBAAmB,OAAO,CAAC,IAAI,YAAY,OAAO,CAAC,EAAE,KAAK,OAAO,CAAC,KAAK,8BAA8B,CAAC;IACjH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAgB,EAChB,MAAwB;IAExB,MAAM,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC;IACtC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAC7D,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC1F,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACjC,OAAO,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC;AAC5C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAKrC;IACC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC/C,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,eAAe;QAAE,OAAO,EAAE,CAAC;IAC/C,sEAAsE;IACtE,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,gBAAgB;QAAE,OAAO,EAAE,CAAC;IAExD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACtD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,GAAG,QAAQ,oDAAoD,EAAE,CAAC;IACnF,CAAC;IAED,MAAM,MAAM,GAAmB,MAAM,qBAAqB,CAAC;QACzD,QAAQ;QACR,MAAM;QACN,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU;QACrC,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;KAC3D,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QAClC,OAAO,EAAE,IAAI,EAAE,SAAS,QAAQ,mBAAmB,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;IACvE,CAAC;IACD,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,mBAAmB,MAAM,iCAAiC,EAAE,CAAC;KAChG,CAAC;AACJ,CAAC"}
|
package/dist/global/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { type Ghostrail, REGISTRY_FILE, findByPath, parseRegistry, renderRegistry, upsert, } from "./registry.js";
|
|
1
|
+
export { type Ghostrail, REGISTRY_FILE, entryKey, findByKey, findByPath, parseRegistry, renderRegistry, upsert, } from "./registry.js";
|
|
2
|
+
export { canonicalRepoId, isLocalRepoId, localRepoId, repoKey } from "./repo-id.js";
|
|
3
|
+
export { repoStateDir, stateHome } from "./state-dir.js";
|
|
2
4
|
export { artifactFileName, type GlobalEnv, GlobalStore, resolveGlobalDir } from "./store.js";
|
|
3
5
|
export { STAGED_ARTIFACTS_DIR, stageArtifacts } from "./stage.js";
|
|
4
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/global/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,aAAa,EACb,UAAU,EACV,aAAa,EACb,cAAc,EACd,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,KAAK,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/global/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,aAAa,EACb,QAAQ,EACR,SAAS,EACT,UAAU,EACV,aAAa,EACb,cAAc,EACd,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,KAAK,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/global/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { REGISTRY_FILE, findByPath, parseRegistry, renderRegistry, upsert, } from "./registry.js";
|
|
1
|
+
export { REGISTRY_FILE, entryKey, findByKey, findByPath, parseRegistry, renderRegistry, upsert, } from "./registry.js";
|
|
2
|
+
export { canonicalRepoId, isLocalRepoId, localRepoId, repoKey } from "./repo-id.js";
|
|
3
|
+
export { repoStateDir, stateHome } from "./state-dir.js";
|
|
2
4
|
export { artifactFileName, GlobalStore, resolveGlobalDir } from "./store.js";
|
|
3
5
|
export { STAGED_ARTIFACTS_DIR, stageArtifacts } from "./stage.js";
|
|
4
6
|
//# sourceMappingURL=index.js.map
|
package/dist/global/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/global/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EACb,UAAU,EACV,aAAa,EACb,cAAc,EACd,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAkB,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/global/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EACb,QAAQ,EACR,SAAS,EACT,UAAU,EACV,aAAa,EACb,cAAc,EACd,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAkB,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -10,7 +10,24 @@
|
|
|
10
10
|
export interface Ghostrail {
|
|
11
11
|
/** Display name, from `[factory].name` or the directory. Never an identity. */
|
|
12
12
|
name: string;
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* The repo this ghostrail is for, canonical (`host/owner/name`), or
|
|
15
|
+
* `local:<path>` for a repo with no usable remote. **This is the identity.**
|
|
16
|
+
*
|
|
17
|
+
* Optional only for backward compatibility: a registry written before repo
|
|
18
|
+
* identities existed has none, and an entry without one falls back to being
|
|
19
|
+
* keyed on its `path`, exactly as it was. This file is advertised as
|
|
20
|
+
* hand-editable, and refusing to read yesterday's one would be the worst
|
|
21
|
+
* possible reading of that promise.
|
|
22
|
+
*/
|
|
23
|
+
repo?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Absolute path to the checkout most recently used for this repo.
|
|
26
|
+
*
|
|
27
|
+
* A hint, for a human reading the file and for a local-worktree run that
|
|
28
|
+
* needs somewhere to make worktrees from. Not the identity: two checkouts of
|
|
29
|
+
* one repo are one ghostrail, which is the whole point of `repo`.
|
|
30
|
+
*/
|
|
14
31
|
path: string;
|
|
15
32
|
/** ISO date the entry was added, for the operator. Nothing reads it. */
|
|
16
33
|
added: string;
|
|
@@ -36,9 +53,10 @@ export declare const REGISTRY_FILE = "ghostrails.toml";
|
|
|
36
53
|
* than fatal, so a registry written by a later ghostrail still reads here.
|
|
37
54
|
* Order is preserved exactly, and the array is fresh on every call.
|
|
38
55
|
*
|
|
39
|
-
* Two entries
|
|
40
|
-
* registry never contains duplicate
|
|
41
|
-
* `
|
|
56
|
+
* Two entries for the same ghostrail are malformed for the same reason: a
|
|
57
|
+
* parsed registry never contains duplicate keys, which is what lets `upsert`
|
|
58
|
+
* and `findByKey` stay as simple as they are. The key is the repo identity, or
|
|
59
|
+
* the path for an entry written before identities existed.
|
|
42
60
|
*/
|
|
43
61
|
export declare function parseRegistry(toml: string): Ghostrail[] | undefined;
|
|
44
62
|
/**
|
|
@@ -51,10 +69,25 @@ export declare function parseRegistry(toml: string): Ghostrail[] | undefined;
|
|
|
51
69
|
* the header invites them to do.
|
|
52
70
|
*/
|
|
53
71
|
export declare function renderRegistry(entries: readonly Ghostrail[]): string;
|
|
72
|
+
/**
|
|
73
|
+
* What an entry is keyed on: its repo identity, or its path when it has none.
|
|
74
|
+
*
|
|
75
|
+
* One function so the parser, `findByKey`, and `upsert` cannot disagree about
|
|
76
|
+
* what "the same ghostrail" means, which is exactly the kind of drift that let
|
|
77
|
+
* two checkouts become two ghostrails in the first place.
|
|
78
|
+
*/
|
|
79
|
+
export declare function entryKey(entry: Ghostrail): string;
|
|
54
80
|
/** The entry at `path`, or undefined. Exact string equality; callers resolve first. */
|
|
55
81
|
export declare function findByPath(entries: readonly Ghostrail[], path: string): Ghostrail | undefined;
|
|
82
|
+
/** The entry for a repo identity (or a path, for an entry that has none). */
|
|
83
|
+
export declare function findByKey(entries: readonly Ghostrail[], key: string): Ghostrail | undefined;
|
|
56
84
|
/**
|
|
57
|
-
* Add `entry`, or replace the one
|
|
85
|
+
* Add `entry`, or replace the one with the same identity.
|
|
86
|
+
*
|
|
87
|
+
* Keyed on the repo, so `init` in a second checkout of a known repo updates
|
|
88
|
+
* that ghostrail (adopting the new checkout as its `path`) instead of adding a
|
|
89
|
+
* second one. An entry with no identity still matches on path, so a
|
|
90
|
+
* pre-identity registry keeps working.
|
|
58
91
|
*
|
|
59
92
|
* A replacement keeps its position so a diff of the file shows a changed block
|
|
60
93
|
* rather than a move; a new entry appends. Neither argument is mutated.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/global/registry.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,sCAAsC;AACtC,MAAM,WAAW,SAAS;IACxB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/global/registry.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,sCAAsC;AACtC,MAAM,WAAW,SAAS;IACxB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,uCAAuC;AACvC,eAAO,MAAM,aAAa,oBAAoB,CAAC;AA2B/C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,SAAS,CA4BnE;AAOD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAcpE;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAEjD;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAE7F;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAE3F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,EAAE,CAOnF"}
|
package/dist/global/registry.js
CHANGED
|
@@ -17,12 +17,15 @@ function isRecord(value) {
|
|
|
17
17
|
function parseEntry(raw) {
|
|
18
18
|
if (!isRecord(raw))
|
|
19
19
|
return undefined;
|
|
20
|
-
const { name, path, added, watch } = raw;
|
|
20
|
+
const { name, repo, path, added, watch } = raw;
|
|
21
21
|
if (typeof name !== "string" || typeof path !== "string")
|
|
22
22
|
return undefined;
|
|
23
23
|
if (typeof added !== "string" || typeof watch !== "boolean")
|
|
24
24
|
return undefined;
|
|
25
|
-
|
|
25
|
+
// Absent is legal (a pre-identity registry); present but not a string is not.
|
|
26
|
+
if (repo !== undefined && typeof repo !== "string")
|
|
27
|
+
return undefined;
|
|
28
|
+
return { name, ...(repo === undefined ? {} : { repo }), path, added, watch };
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
31
|
* Parse the registry, or undefined when it cannot be trusted.
|
|
@@ -38,9 +41,10 @@ function parseEntry(raw) {
|
|
|
38
41
|
* than fatal, so a registry written by a later ghostrail still reads here.
|
|
39
42
|
* Order is preserved exactly, and the array is fresh on every call.
|
|
40
43
|
*
|
|
41
|
-
* Two entries
|
|
42
|
-
* registry never contains duplicate
|
|
43
|
-
* `
|
|
44
|
+
* Two entries for the same ghostrail are malformed for the same reason: a
|
|
45
|
+
* parsed registry never contains duplicate keys, which is what lets `upsert`
|
|
46
|
+
* and `findByKey` stay as simple as they are. The key is the repo identity, or
|
|
47
|
+
* the path for an entry written before identities existed.
|
|
44
48
|
*/
|
|
45
49
|
export function parseRegistry(toml) {
|
|
46
50
|
let root;
|
|
@@ -58,18 +62,19 @@ export function parseRegistry(toml) {
|
|
|
58
62
|
if (!Array.isArray(raw))
|
|
59
63
|
return undefined;
|
|
60
64
|
const entries = [];
|
|
61
|
-
const
|
|
65
|
+
const keys = new Set();
|
|
62
66
|
for (const item of raw) {
|
|
63
67
|
const entry = parseEntry(item);
|
|
64
68
|
if (entry === undefined)
|
|
65
69
|
return undefined;
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
70
|
+
// Two blocks claiming one ghostrail is a contradiction rather than a
|
|
71
|
+
// preference to resolve. Copying a block and forgetting to change the repo
|
|
72
|
+
// is a plausible hand-edit, and honouring either one of them would silently
|
|
73
|
+
// ignore an edit the operator clearly meant.
|
|
74
|
+
const key = entryKey(entry);
|
|
75
|
+
if (keys.has(key))
|
|
71
76
|
return undefined;
|
|
72
|
-
|
|
77
|
+
keys.add(key);
|
|
73
78
|
entries.push(entry);
|
|
74
79
|
}
|
|
75
80
|
return entries;
|
|
@@ -91,6 +96,7 @@ export function renderRegistry(entries) {
|
|
|
91
96
|
const blocks = entries.map((entry) => [
|
|
92
97
|
"[[ghostrail]]",
|
|
93
98
|
`name = ${quote(entry.name)}`,
|
|
99
|
+
...(entry.repo === undefined ? [] : [`repo = ${quote(entry.repo)}`]),
|
|
94
100
|
`path = ${quote(entry.path)}`,
|
|
95
101
|
`added = ${quote(entry.added)}`,
|
|
96
102
|
`watch = ${entry.watch}`,
|
|
@@ -99,18 +105,38 @@ export function renderRegistry(entries) {
|
|
|
99
105
|
// run together the blocks are hard to read and give noisier diffs.
|
|
100
106
|
return `${[HEADER, ...blocks].join("\n\n")}\n`;
|
|
101
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* What an entry is keyed on: its repo identity, or its path when it has none.
|
|
110
|
+
*
|
|
111
|
+
* One function so the parser, `findByKey`, and `upsert` cannot disagree about
|
|
112
|
+
* what "the same ghostrail" means, which is exactly the kind of drift that let
|
|
113
|
+
* two checkouts become two ghostrails in the first place.
|
|
114
|
+
*/
|
|
115
|
+
export function entryKey(entry) {
|
|
116
|
+
return entry.repo ?? entry.path;
|
|
117
|
+
}
|
|
102
118
|
/** The entry at `path`, or undefined. Exact string equality; callers resolve first. */
|
|
103
119
|
export function findByPath(entries, path) {
|
|
104
120
|
return entries.find((entry) => entry.path === path);
|
|
105
121
|
}
|
|
122
|
+
/** The entry for a repo identity (or a path, for an entry that has none). */
|
|
123
|
+
export function findByKey(entries, key) {
|
|
124
|
+
return entries.find((entry) => entryKey(entry) === key);
|
|
125
|
+
}
|
|
106
126
|
/**
|
|
107
|
-
* Add `entry`, or replace the one
|
|
127
|
+
* Add `entry`, or replace the one with the same identity.
|
|
128
|
+
*
|
|
129
|
+
* Keyed on the repo, so `init` in a second checkout of a known repo updates
|
|
130
|
+
* that ghostrail (adopting the new checkout as its `path`) instead of adding a
|
|
131
|
+
* second one. An entry with no identity still matches on path, so a
|
|
132
|
+
* pre-identity registry keeps working.
|
|
108
133
|
*
|
|
109
134
|
* A replacement keeps its position so a diff of the file shows a changed block
|
|
110
135
|
* rather than a move; a new entry appends. Neither argument is mutated.
|
|
111
136
|
*/
|
|
112
137
|
export function upsert(entries, entry) {
|
|
113
|
-
const
|
|
138
|
+
const key = entryKey(entry);
|
|
139
|
+
const index = entries.findIndex((existing) => entryKey(existing) === key);
|
|
114
140
|
if (index === -1)
|
|
115
141
|
return [...entries, entry];
|
|
116
142
|
const next = [...entries];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/global/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/global/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical identity of the repo a git remote points at, as
|
|
3
|
+
* `host[:port]/path/to/repo`, or undefined when the remote is not one.
|
|
4
|
+
*
|
|
5
|
+
* The point is that every spelling of one repo collapses to one string:
|
|
6
|
+
* scp-like and URL forms, with or without `.git`, with or without a default
|
|
7
|
+
* port, are the same repo and must key the same ghostrail.
|
|
8
|
+
*
|
|
9
|
+
* Strict everywhere except case. Anything unrecognised is undefined rather than
|
|
10
|
+
* guessed, because the two failure directions are not symmetric: wrongly
|
|
11
|
+
* merging two repos pools their history and cannot be undone, while wrongly
|
|
12
|
+
* separating two clones only leaves today's behavior in place. The exception is
|
|
13
|
+
* lowercasing, which is deliberately lenient: hosts are case-insensitive and so
|
|
14
|
+
* are repos on the forges people use, so `Owner/Repo` and `owner/repo` are one
|
|
15
|
+
* repo. On a case-sensitive host that could merge two repos differing only in
|
|
16
|
+
* case, which is judged far less likely than someone typing a clone URL with
|
|
17
|
+
* different capitalisation.
|
|
18
|
+
*
|
|
19
|
+
* Never throws. A remote that cannot be read is a missing identity, and the
|
|
20
|
+
* caller falls back to {@link localRepoId}.
|
|
21
|
+
*/
|
|
22
|
+
export declare function canonicalRepoId(remote: string): string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* The identity of a repo with no usable remote, from its absolute path.
|
|
25
|
+
*
|
|
26
|
+
* The `local:` prefix means it can never collide with a `host/...` identity,
|
|
27
|
+
* and makes a path-keyed entry visibly path-keyed rather than silently so. The
|
|
28
|
+
* path is kept verbatim: it is already absolute, and case-folding it would be
|
|
29
|
+
* wrong on a case-sensitive filesystem.
|
|
30
|
+
*/
|
|
31
|
+
export declare function localRepoId(path: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Whether an identity is path-keyed rather than derived from a remote.
|
|
34
|
+
*
|
|
35
|
+
* Checks `local:/`, not `local:`. The shorter prefix looks like the marker and
|
|
36
|
+
* is not one: a host genuinely named `local` on a non-default port produces
|
|
37
|
+
* `local:22/owner/repo`, which is an ordinary canonical identity.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isLocalRepoId(id: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* A filesystem-safe directory name for an identity.
|
|
42
|
+
*
|
|
43
|
+
* The hash is not decoration. Folding characters and truncating both lose
|
|
44
|
+
* information, so two different identities can reduce to the same readable
|
|
45
|
+
* stem; the hash of the *full* identity is what keeps the key unique. The stem
|
|
46
|
+
* is there so a human browsing the state directory can tell what they are
|
|
47
|
+
* looking at.
|
|
48
|
+
*/
|
|
49
|
+
export declare function repoKey(id: string): string;
|
|
50
|
+
//# sourceMappingURL=repo-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo-id.d.ts","sourceRoot":"","sources":["../../src/global/repo-id.ts"],"names":[],"mappings":"AAmBA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgBlE;AAqDD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQhD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAU1C"}
|