nawabari 0.1.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/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +298 -0
- package/dist/cli.js.map +1 -0
- package/dist/domain/doctor.d.ts +20 -0
- package/dist/domain/doctor.js +167 -0
- package/dist/domain/doctor.js.map +1 -0
- package/dist/domain/errors.d.ts +29 -0
- package/dist/domain/errors.js +42 -0
- package/dist/domain/errors.js.map +1 -0
- package/dist/domain/session-backend.d.ts +24 -0
- package/dist/domain/session-backend.js +206 -0
- package/dist/domain/session-backend.js.map +1 -0
- package/dist/domain/session.d.ts +89 -0
- package/dist/domain/session.js +47 -0
- package/dist/domain/session.js.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +14 -0
- package/dist/errors.js.map +1 -0
- package/dist/git.d.ts +33 -0
- package/dist/git.js +157 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/presentation.d.ts +9 -0
- package/dist/presentation.js +61 -0
- package/dist/presentation.js.map +1 -0
- package/dist/registry/atomic.d.ts +20 -0
- package/dist/registry/atomic.js +128 -0
- package/dist/registry/atomic.js.map +1 -0
- package/dist/registry/errors.d.ts +8 -0
- package/dist/registry/errors.js +30 -0
- package/dist/registry/errors.js.map +1 -0
- package/dist/registry/lock.d.ts +60 -0
- package/dist/registry/lock.js +727 -0
- package/dist/registry/lock.js.map +1 -0
- package/dist/registry/store.d.ts +38 -0
- package/dist/registry/store.js +156 -0
- package/dist/registry/store.js.map +1 -0
- package/dist/registry/types.d.ts +18 -0
- package/dist/registry/types.js +3 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/registry.d.ts +5 -0
- package/dist/registry.js +6 -0
- package/dist/registry.js.map +1 -0
- package/dist/session-id.d.ts +7 -0
- package/dist/session-id.js +32 -0
- package/dist/session-id.js.map +1 -0
- package/dist/session-registry.d.ts +169 -0
- package/dist/session-registry.js +1190 -0
- package/dist/session-registry.js.map +1 -0
- package/package.json +57 -0
package/dist/git.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { SessionRegistryError } from "./errors.js";
|
|
5
|
+
export const defaultGit = {
|
|
6
|
+
run(args, cwd) {
|
|
7
|
+
try {
|
|
8
|
+
return execFileSync("git", [...args], {
|
|
9
|
+
cwd,
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
12
|
+
timeout: 10_000,
|
|
13
|
+
env: {
|
|
14
|
+
...process.env,
|
|
15
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
16
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
17
|
+
},
|
|
18
|
+
}).trim();
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new SessionRegistryError("GIT_COMMAND_FAILED", `git ${args.join(" ")} failed in ${cwd}`, { command: args.join(" "), cwd }, error);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export function resolveRepositoryContext(options = {}) {
|
|
26
|
+
const cwd = canonicalDirectory(options.cwd ?? process.cwd(), "REPOSITORY_IDENTITY_AMBIGUOUS");
|
|
27
|
+
const git = options.git ?? defaultGit;
|
|
28
|
+
let worktreePath;
|
|
29
|
+
let commonGitDirectory;
|
|
30
|
+
try {
|
|
31
|
+
worktreePath = canonicalDirectory(git.run(["rev-parse", "--show-toplevel"], cwd), "REPOSITORY_IDENTITY_AMBIGUOUS");
|
|
32
|
+
const commonGitDirectoryOutput = git.run(["rev-parse", "--git-common-dir"], cwd);
|
|
33
|
+
const commonGitDirectoryPath = path.isAbsolute(commonGitDirectoryOutput)
|
|
34
|
+
? commonGitDirectoryOutput
|
|
35
|
+
: path.resolve(cwd, commonGitDirectoryOutput);
|
|
36
|
+
commonGitDirectory = canonicalDirectory(commonGitDirectoryPath, "REPOSITORY_IDENTITY_AMBIGUOUS");
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error instanceof SessionRegistryError && error.code === "REPOSITORY_IDENTITY_AMBIGUOUS") {
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
throw new SessionRegistryError("NOT_A_GIT_REPOSITORY", `Could not resolve a repository from ${cwd}`, { cwd }, error);
|
|
43
|
+
}
|
|
44
|
+
return Object.freeze({
|
|
45
|
+
repositoryId: commonGitDirectory,
|
|
46
|
+
commonGitDirectory,
|
|
47
|
+
worktreePath,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export function resolveWorktreeIdentity(options = {}) {
|
|
51
|
+
const repository = options.repository ?? resolveRepositoryContext(options);
|
|
52
|
+
const git = options.git ?? defaultGit;
|
|
53
|
+
const branchName = options.branchName ?? readCurrentBranch(git, repository.worktreePath);
|
|
54
|
+
const branchId = normalizeBranchId(branchName);
|
|
55
|
+
return Object.freeze({
|
|
56
|
+
worktreeId: repository.worktreePath,
|
|
57
|
+
worktreePath: repository.worktreePath,
|
|
58
|
+
branchId,
|
|
59
|
+
branchName: branchId.slice("refs/heads/".length),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/** Read the repository's local worktree inventory without consulting a remote. */
|
|
63
|
+
export function listGitWorktrees(git, cwd) {
|
|
64
|
+
const output = git.run(["worktree", "list", "--porcelain"], cwd);
|
|
65
|
+
const entries = [];
|
|
66
|
+
let currentPath;
|
|
67
|
+
let currentBranch = null;
|
|
68
|
+
const flush = () => {
|
|
69
|
+
if (currentPath === undefined)
|
|
70
|
+
return;
|
|
71
|
+
entries.push(Object.freeze({ worktreePath: canonicalListedPath(currentPath), branchName: currentBranch }));
|
|
72
|
+
currentPath = undefined;
|
|
73
|
+
currentBranch = null;
|
|
74
|
+
};
|
|
75
|
+
for (const line of output.split(/\r?\n/u)) {
|
|
76
|
+
if (line.startsWith("worktree ")) {
|
|
77
|
+
flush();
|
|
78
|
+
currentPath = line.slice("worktree ".length);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (line.startsWith("branch ")) {
|
|
82
|
+
const branchId = line.slice("branch ".length);
|
|
83
|
+
if (branchId.startsWith("refs/heads/")) {
|
|
84
|
+
currentBranch = branchId.slice("refs/heads/".length);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
flush();
|
|
89
|
+
return entries;
|
|
90
|
+
}
|
|
91
|
+
export function normalizeBranchId(branchName) {
|
|
92
|
+
const trimmed = branchName.trim();
|
|
93
|
+
if (trimmed !== branchName) {
|
|
94
|
+
throw new SessionRegistryError("INVALID_BRANCH_ID", `Invalid branch identity: ${branchName}`, { branchName });
|
|
95
|
+
}
|
|
96
|
+
if (trimmed.startsWith("refs/") && !trimmed.startsWith("refs/heads/")) {
|
|
97
|
+
throw new SessionRegistryError("INVALID_BRANCH_ID", `Invalid local branch identity: ${branchName}`, { branchName });
|
|
98
|
+
}
|
|
99
|
+
const normalized = trimmed.startsWith("refs/heads/") ? trimmed : `refs/heads/${trimmed}`;
|
|
100
|
+
const shortName = normalized.slice("refs/heads/".length);
|
|
101
|
+
const components = shortName.split("/");
|
|
102
|
+
if (shortName.length === 0 ||
|
|
103
|
+
shortName.startsWith("/") ||
|
|
104
|
+
shortName.endsWith("/") ||
|
|
105
|
+
shortName.includes("..") ||
|
|
106
|
+
shortName === "@" ||
|
|
107
|
+
shortName.includes("@{") ||
|
|
108
|
+
shortName.endsWith(".") ||
|
|
109
|
+
shortName.endsWith(".lock") ||
|
|
110
|
+
components.some((component) => component.startsWith(".") || component.endsWith(".") || component.endsWith(".lock")) ||
|
|
111
|
+
shortName.includes("//") ||
|
|
112
|
+
/[\u0000-\u0020~^:?*[\\]/u.test(shortName)) {
|
|
113
|
+
throw new SessionRegistryError("INVALID_BRANCH_ID", `Invalid branch identity: ${branchName}`, { branchName });
|
|
114
|
+
}
|
|
115
|
+
return normalized;
|
|
116
|
+
}
|
|
117
|
+
function canonicalListedPath(candidate) {
|
|
118
|
+
const resolved = path.resolve(candidate);
|
|
119
|
+
try {
|
|
120
|
+
return fs.realpathSync.native(resolved);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return resolved;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export function readCurrentBranch(git, cwd) {
|
|
127
|
+
try {
|
|
128
|
+
const branchName = git.run(["symbolic-ref", "--quiet", "--short", "HEAD"], cwd);
|
|
129
|
+
if (branchName.length === 0) {
|
|
130
|
+
throw new SessionRegistryError("WORKTREE_IDENTITY_AMBIGUOUS", `The worktree at ${cwd} has no branch`, { cwd });
|
|
131
|
+
}
|
|
132
|
+
return branchName;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (error instanceof SessionRegistryError && error.code === "WORKTREE_IDENTITY_AMBIGUOUS") {
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
throw new SessionRegistryError("WORKTREE_IDENTITY_AMBIGUOUS", `Could not resolve the current branch for ${cwd}`, { cwd }, error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function canonicalDirectory(candidate, errorCode) {
|
|
142
|
+
if (candidate.trim().length === 0) {
|
|
143
|
+
throw new SessionRegistryError(errorCode, "Git returned an empty directory identity");
|
|
144
|
+
}
|
|
145
|
+
const resolved = path.resolve(candidate);
|
|
146
|
+
try {
|
|
147
|
+
const stat = fs.statSync(resolved);
|
|
148
|
+
if (!stat.isDirectory()) {
|
|
149
|
+
throw new Error("path is not a directory");
|
|
150
|
+
}
|
|
151
|
+
return fs.realpathSync.native(resolved);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
throw new SessionRegistryError(errorCode, `Could not resolve directory identity: ${resolved}`, { path: resolved }, error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=git.js.map
|
package/dist/git.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;IAC9C,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC9B,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DomainError, type JsonObject } from "./domain/errors.js";
|
|
2
|
+
export type CliMode = "human" | "json";
|
|
3
|
+
export type CliIO = {
|
|
4
|
+
stdout: (line: string) => void;
|
|
5
|
+
stderr: (line: string) => void;
|
|
6
|
+
};
|
|
7
|
+
export declare function defaultCliIO(): CliIO;
|
|
8
|
+
export declare function renderSuccess(mode: CliMode, command: string, payload: JsonObject): string;
|
|
9
|
+
export declare function renderFailure(mode: CliMode, command: string, error: DomainError): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function defaultCliIO() {
|
|
2
|
+
return {
|
|
3
|
+
stdout: (line) => console.log(line),
|
|
4
|
+
stderr: (line) => console.error(line),
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
function jsonSuccess(command, payload) {
|
|
8
|
+
return JSON.stringify({ ok: true, command, ...payload });
|
|
9
|
+
}
|
|
10
|
+
function jsonFailure(command, error) {
|
|
11
|
+
const response = {
|
|
12
|
+
ok: false,
|
|
13
|
+
command,
|
|
14
|
+
code: error.code,
|
|
15
|
+
message: error.message,
|
|
16
|
+
};
|
|
17
|
+
if (error.details !== null) {
|
|
18
|
+
if (typeof error.details.allowed === "boolean")
|
|
19
|
+
response.allowed = error.details.allowed;
|
|
20
|
+
response.details = error.details;
|
|
21
|
+
}
|
|
22
|
+
return JSON.stringify(response);
|
|
23
|
+
}
|
|
24
|
+
function formatValue(value) {
|
|
25
|
+
if (typeof value === "string")
|
|
26
|
+
return value;
|
|
27
|
+
return JSON.stringify(value);
|
|
28
|
+
}
|
|
29
|
+
function humanSuccess(command, payload) {
|
|
30
|
+
if (Object.keys(payload).length === 0)
|
|
31
|
+
return `${command}: ok`;
|
|
32
|
+
const lines = [`${command}: ok`];
|
|
33
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
34
|
+
if (key === "checks" && Array.isArray(value)) {
|
|
35
|
+
for (const item of value) {
|
|
36
|
+
if (typeof item === "object" && item !== null && !Array.isArray(item) && "name" in item && "status" in item) {
|
|
37
|
+
const name = item.name;
|
|
38
|
+
const status = item.status;
|
|
39
|
+
if (typeof name === "string" && typeof status === "string")
|
|
40
|
+
lines.push(` ${name}: ${status}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
lines.push(` ${key}: ${formatValue(value)}`);
|
|
46
|
+
}
|
|
47
|
+
return lines.join("\n");
|
|
48
|
+
}
|
|
49
|
+
function humanFailure(command, error) {
|
|
50
|
+
const lines = [`${command}: rejected`, ` code: ${error.code}`, ` message: ${error.message}`];
|
|
51
|
+
if (error.details !== null)
|
|
52
|
+
lines.push(` details: ${JSON.stringify(error.details)}`);
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|
|
55
|
+
export function renderSuccess(mode, command, payload) {
|
|
56
|
+
return mode === "json" ? jsonSuccess(command, payload) : humanSuccess(command, payload);
|
|
57
|
+
}
|
|
58
|
+
export function renderFailure(mode, command, error) {
|
|
59
|
+
return mode === "json" ? jsonFailure(command, error) : humanFailure(command, error);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=presentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation.js","sourceRoot":"","sources":["../src/presentation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface AtomicWritePaths {
|
|
2
|
+
temporaryPath: string;
|
|
3
|
+
targetPath: string;
|
|
4
|
+
}
|
|
5
|
+
export interface AtomicWriteHooks {
|
|
6
|
+
beforeRename?: (paths: AtomicWritePaths) => void | Promise<void>;
|
|
7
|
+
afterRename?: (paths: AtomicWritePaths) => void | Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export interface AtomicWriteOptions {
|
|
10
|
+
ensureParent?: boolean;
|
|
11
|
+
hooks?: AtomicWriteHooks;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Persist JSON by writing and syncing a same-directory temporary file, then
|
|
15
|
+
* replacing the target with rename. Readers therefore observe either the
|
|
16
|
+
* previous complete document or the next complete document.
|
|
17
|
+
*/
|
|
18
|
+
export declare function writeJsonAtomically(targetPath: string, value: unknown, options?: AtomicWriteOptions): Promise<void>;
|
|
19
|
+
/** Synchronous counterpart used by the legacy synchronous registry boundary. */
|
|
20
|
+
export declare function writeJsonAtomicallySync(targetPath: string, value: unknown, options?: Pick<AtomicWriteOptions, "ensureParent">): void;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { open, mkdir, rename, rm } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, join } from "node:path";
|
|
5
|
+
import { RegistryError } from "./errors.js";
|
|
6
|
+
function errorCode(error) {
|
|
7
|
+
if (!(error instanceof Error) || !("code" in error)) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const code = error.code;
|
|
11
|
+
return typeof code === "string" ? code : undefined;
|
|
12
|
+
}
|
|
13
|
+
function isUnsupportedDirectorySync(error) {
|
|
14
|
+
const code = errorCode(error);
|
|
15
|
+
return code === "EINVAL" || code === "ENOTSUP" || code === "EISDIR";
|
|
16
|
+
}
|
|
17
|
+
async function syncDirectory(directory) {
|
|
18
|
+
let handle;
|
|
19
|
+
try {
|
|
20
|
+
handle = await open(directory, "r");
|
|
21
|
+
await handle.sync();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (!isUnsupportedDirectorySync(error)) {
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
if (handle !== undefined) {
|
|
30
|
+
await handle.close();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function syncDirectorySync(directory) {
|
|
35
|
+
let descriptor;
|
|
36
|
+
try {
|
|
37
|
+
descriptor = fs.openSync(directory, "r");
|
|
38
|
+
fs.fsyncSync(descriptor);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (!isUnsupportedDirectorySync(error)) {
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
if (descriptor !== undefined) {
|
|
47
|
+
fs.closeSync(descriptor);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Persist JSON by writing and syncing a same-directory temporary file, then
|
|
53
|
+
* replacing the target with rename. Readers therefore observe either the
|
|
54
|
+
* previous complete document or the next complete document.
|
|
55
|
+
*/
|
|
56
|
+
export async function writeJsonAtomically(targetPath, value, options = {}) {
|
|
57
|
+
const directory = dirname(targetPath);
|
|
58
|
+
if (options.ensureParent !== false) {
|
|
59
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
60
|
+
}
|
|
61
|
+
const temporaryPath = join(directory, `.${basename(targetPath)}.${process.pid}.${randomUUID()}.tmp`);
|
|
62
|
+
const serialized = JSON.stringify(value, null, 2);
|
|
63
|
+
if (serialized === undefined) {
|
|
64
|
+
throw new RegistryError("REGISTRY_IO_ERROR", "Cannot serialize registry state", {
|
|
65
|
+
targetPath,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
let handle;
|
|
69
|
+
let renamed = false;
|
|
70
|
+
try {
|
|
71
|
+
handle = await open(temporaryPath, "wx", 0o600);
|
|
72
|
+
await handle.writeFile(`${serialized}\n`, "utf8");
|
|
73
|
+
await handle.sync();
|
|
74
|
+
await handle.close();
|
|
75
|
+
handle = undefined;
|
|
76
|
+
const paths = { temporaryPath, targetPath };
|
|
77
|
+
await options.hooks?.beforeRename?.(paths);
|
|
78
|
+
await rename(temporaryPath, targetPath);
|
|
79
|
+
renamed = true;
|
|
80
|
+
await syncDirectory(directory);
|
|
81
|
+
await options.hooks?.afterRename?.(paths);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (handle !== undefined) {
|
|
85
|
+
await handle.close();
|
|
86
|
+
}
|
|
87
|
+
if (!renamed) {
|
|
88
|
+
await rm(temporaryPath, { force: true });
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Synchronous counterpart used by the legacy synchronous registry boundary. */
|
|
94
|
+
export function writeJsonAtomicallySync(targetPath, value, options = {}) {
|
|
95
|
+
const directory = dirname(targetPath);
|
|
96
|
+
if (options.ensureParent !== false) {
|
|
97
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
98
|
+
}
|
|
99
|
+
const temporaryPath = join(directory, `.${basename(targetPath)}.${process.pid}.${randomUUID()}.tmp`);
|
|
100
|
+
const serialized = JSON.stringify(value, null, 2);
|
|
101
|
+
if (serialized === undefined) {
|
|
102
|
+
throw new RegistryError("REGISTRY_IO_ERROR", "Cannot serialize registry state", {
|
|
103
|
+
targetPath,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
let descriptor;
|
|
107
|
+
let renamed = false;
|
|
108
|
+
try {
|
|
109
|
+
descriptor = fs.openSync(temporaryPath, "wx", 0o600);
|
|
110
|
+
fs.writeFileSync(descriptor, `${serialized}\n`, "utf8");
|
|
111
|
+
fs.fsyncSync(descriptor);
|
|
112
|
+
fs.closeSync(descriptor);
|
|
113
|
+
descriptor = undefined;
|
|
114
|
+
fs.renameSync(temporaryPath, targetPath);
|
|
115
|
+
renamed = true;
|
|
116
|
+
syncDirectorySync(directory);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (descriptor !== undefined) {
|
|
120
|
+
fs.closeSync(descriptor);
|
|
121
|
+
}
|
|
122
|
+
if (!renamed) {
|
|
123
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
124
|
+
}
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=atomic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic.js","sourceRoot":"","sources":["../../src/registry/atomic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const REGISTRY_ERROR_CODES: readonly ["REGISTRY_NOT_FOUND", "REGISTRY_CORRUPT", "REGISTRY_UNSUPPORTED_SCHEMA", "REGISTRY_REPOSITORY_MISMATCH", "REGISTRY_DUPLICATE_SESSION", "REGISTRY_DUPLICATE_WORKTREE", "REGISTRY_DUPLICATE_BRANCH", "REGISTRY_IO_ERROR", "REGISTRY_MUTATION_FAILED", "LOCK_BUSY", "LOCK_STALE", "LOCK_INVALID", "LOCK_IO_ERROR", "LOCK_RELEASE_FAILED"];
|
|
2
|
+
export type RegistryErrorCode = (typeof REGISTRY_ERROR_CODES)[number];
|
|
3
|
+
export declare class RegistryError extends Error {
|
|
4
|
+
readonly code: RegistryErrorCode;
|
|
5
|
+
readonly details: Readonly<Record<string, unknown>>;
|
|
6
|
+
constructor(code: RegistryErrorCode, message: string, details?: Readonly<Record<string, unknown>>, options?: ErrorOptions);
|
|
7
|
+
}
|
|
8
|
+
export declare function isRegistryError(error: unknown): error is RegistryError;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const REGISTRY_ERROR_CODES = [
|
|
2
|
+
"REGISTRY_NOT_FOUND",
|
|
3
|
+
"REGISTRY_CORRUPT",
|
|
4
|
+
"REGISTRY_UNSUPPORTED_SCHEMA",
|
|
5
|
+
"REGISTRY_REPOSITORY_MISMATCH",
|
|
6
|
+
"REGISTRY_DUPLICATE_SESSION",
|
|
7
|
+
"REGISTRY_DUPLICATE_WORKTREE",
|
|
8
|
+
"REGISTRY_DUPLICATE_BRANCH",
|
|
9
|
+
"REGISTRY_IO_ERROR",
|
|
10
|
+
"REGISTRY_MUTATION_FAILED",
|
|
11
|
+
"LOCK_BUSY",
|
|
12
|
+
"LOCK_STALE",
|
|
13
|
+
"LOCK_INVALID",
|
|
14
|
+
"LOCK_IO_ERROR",
|
|
15
|
+
"LOCK_RELEASE_FAILED",
|
|
16
|
+
];
|
|
17
|
+
export class RegistryError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
details;
|
|
20
|
+
constructor(code, message, details = {}, options) {
|
|
21
|
+
super(message, options);
|
|
22
|
+
this.name = "RegistryError";
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.details = details;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function isRegistryError(error) {
|
|
28
|
+
return error instanceof RegistryError;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/registry/errors.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,oBAAoB;IACpB,kBAAkB;IAClB,6BAA6B;IAC7B,8BAA8B;IAC9B,4BAA4B;IAC5B,6BAA6B;IAC7B,2BAA2B;IAC3B,mBAAmB;IACnB,0BAA0B;IAC1B,WAAW;IACX,YAAY;IACZ,cAAc;IACd,eAAe;IACf,qBAAqB;CACb,CAAC;AAIX,MAAM,OAAO,aAAc,SAAQ,KAAK;IACtB,IAAI,CAAoB;IACxB,OAAO,CAAoC;IAE3D,YACE,IAAuB,EACvB,OAAe,EACf,UAA6C,EAAE,EAC/C,OAAsB;QAEtB,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,KAAK,YAAY,aAAa,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { RegistryError } from "./errors.js";
|
|
2
|
+
import { type LockOwnerRecord } from "./types.js";
|
|
3
|
+
export type LockFailureCode = "LOCK_BUSY" | "LOCK_STALE" | "LOCK_INVALID" | "LOCK_IO_ERROR" | "LOCK_RELEASE_FAILED";
|
|
4
|
+
export declare class RegistryLockError extends RegistryError {
|
|
5
|
+
readonly lockPath: string;
|
|
6
|
+
readonly owner?: LockOwnerRecord;
|
|
7
|
+
constructor(code: LockFailureCode, message: string, lockPath: string, details?: Readonly<Record<string, unknown>>, owner?: LockOwnerRecord);
|
|
8
|
+
}
|
|
9
|
+
export interface RepositoryLockOptions {
|
|
10
|
+
lockPath: string;
|
|
11
|
+
staleAfterMs?: number;
|
|
12
|
+
acquireTimeoutMs?: number;
|
|
13
|
+
retryDelayMs?: number;
|
|
14
|
+
metadataGraceMs?: number;
|
|
15
|
+
hostname?: string;
|
|
16
|
+
processStartTime?: string | null;
|
|
17
|
+
clock?: () => number;
|
|
18
|
+
/** Test seam used to exercise the reclaim/create race deterministically. */
|
|
19
|
+
beforeReclaimRemove?: () => void | Promise<void>;
|
|
20
|
+
/** Test seam used to confirm a contender observed an active reclaim marker. */
|
|
21
|
+
onReclaimMarkerObserved?: () => void;
|
|
22
|
+
}
|
|
23
|
+
export interface LockLease {
|
|
24
|
+
readonly token: string;
|
|
25
|
+
readonly owner: LockOwnerRecord;
|
|
26
|
+
release(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export interface SyncLockLease {
|
|
29
|
+
readonly token: string;
|
|
30
|
+
readonly owner: LockOwnerRecord;
|
|
31
|
+
release(): void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Read Linux's process start token. A PID alone is not sufficient for safe
|
|
35
|
+
* stale-lock recovery because the operating system can reuse a PID.
|
|
36
|
+
*/
|
|
37
|
+
export declare function readProcessStartTime(pid: number): Promise<string | null>;
|
|
38
|
+
export declare class RepositoryLock {
|
|
39
|
+
private readonly options;
|
|
40
|
+
private readonly reclaimPath;
|
|
41
|
+
constructor(options: RepositoryLockOptions);
|
|
42
|
+
get lockPath(): string;
|
|
43
|
+
acquire(): Promise<LockLease>;
|
|
44
|
+
/** Synchronous adapter for the legacy synchronous SessionRegistry API. */
|
|
45
|
+
acquireSync(): SyncLockLease;
|
|
46
|
+
private tryCreate;
|
|
47
|
+
private tryCreateSync;
|
|
48
|
+
private inspectExisting;
|
|
49
|
+
private inspectExistingSync;
|
|
50
|
+
private tryReclaim;
|
|
51
|
+
private tryReclaimSync;
|
|
52
|
+
private handleExistingReclaimer;
|
|
53
|
+
private handleExistingReclaimerSync;
|
|
54
|
+
private removeCreatedLock;
|
|
55
|
+
private removeCreatedLockSync;
|
|
56
|
+
private release;
|
|
57
|
+
private releaseSync;
|
|
58
|
+
private reclaimMarkerExists;
|
|
59
|
+
private reclaimMarkerExistsSync;
|
|
60
|
+
}
|