@cedarjs/pg 0.1.0-alpha.0 → 0.2.0-alpha.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 +281 -30
- package/dist/cli.cjs +62 -16
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +61 -15
- package/dist/cli.mjs.map +1 -1
- package/dist/{constants-B7sCf4fg.cjs → constants-Ct8myrEn.cjs} +4 -4
- package/dist/constants-Ct8myrEn.cjs.map +1 -0
- package/dist/{constants-YnHDwShD.mjs → constants-NLR0U4NR.mjs} +4 -4
- package/dist/constants-NLR0U4NR.mjs.map +1 -0
- package/dist/dev-env.cjs +12 -0
- package/dist/dev-env.cjs.map +1 -0
- package/dist/dev-env.d.cts +1 -0
- package/dist/dev-env.d.mts +1 -0
- package/dist/dev-env.mjs +14 -0
- package/dist/dev-env.mjs.map +1 -0
- package/dist/index.cjs +50 -10
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +168 -82
- package/dist/index.d.mts +168 -82
- package/dist/index.mjs +32 -2
- package/dist/index.mjs.map +1 -0
- package/dist/jest-teardown.cjs +18 -0
- package/dist/jest-teardown.cjs.map +1 -0
- package/dist/jest-teardown.d.cts +13 -0
- package/dist/jest-teardown.d.mts +14 -0
- package/dist/jest-teardown.mjs +18 -0
- package/dist/jest-teardown.mjs.map +1 -0
- package/dist/jest-template.cjs +41 -0
- package/dist/jest-template.cjs.map +1 -0
- package/dist/jest-template.d.cts +28 -0
- package/dist/jest-template.d.mts +28 -0
- package/dist/jest-template.mjs +35 -0
- package/dist/jest-template.mjs.map +1 -0
- package/dist/jest.cjs +12 -15
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +10 -7
- package/dist/jest.d.mts +10 -6
- package/dist/jest.mjs +12 -10
- package/dist/jest.mjs.map +1 -1
- package/dist/lease-B3TuX92y.d.mts +24 -0
- package/dist/lease-BSBBpaR4.mjs +201 -0
- package/dist/lease-BSBBpaR4.mjs.map +1 -0
- package/dist/lease-Bq9wKwQa.cjs +272 -0
- package/dist/lease-Bq9wKwQa.cjs.map +1 -0
- package/dist/lease-t4I9JahV.d.cts +24 -0
- package/dist/lifecycle-BOo6xBjD.mjs +684 -0
- package/dist/lifecycle-BOo6xBjD.mjs.map +1 -0
- package/dist/lifecycle-DEJ4GgWV.cjs +856 -0
- package/dist/lifecycle-DEJ4GgWV.cjs.map +1 -0
- package/dist/load-dev-env-7UjMHhw7.mjs +10 -0
- package/dist/load-dev-env-7UjMHhw7.mjs.map +1 -0
- package/dist/load-dev-env-cII6N4ZP.cjs +15 -0
- package/dist/load-dev-env-cII6N4ZP.cjs.map +1 -0
- package/dist/load-mode-env-B0qO9DZx.cjs +39 -0
- package/dist/load-mode-env-B0qO9DZx.cjs.map +1 -0
- package/dist/load-mode-env-C8gy4v9V.mjs +28 -0
- package/dist/load-mode-env-C8gy4v9V.mjs.map +1 -0
- package/dist/load-test-env-C34wpkIX.mjs +18 -0
- package/dist/load-test-env-C34wpkIX.mjs.map +1 -0
- package/dist/load-test-env-CkdUjTpV.cjs +23 -0
- package/dist/load-test-env-CkdUjTpV.cjs.map +1 -0
- package/dist/naming-C2nGVxPk.d.cts +39 -0
- package/dist/naming-C2nGVxPk.d.mts +39 -0
- package/dist/nx.cjs +26 -17
- package/dist/nx.cjs.map +1 -1
- package/dist/nx.d.cts +9 -20
- package/dist/nx.d.mts +9 -20
- package/dist/nx.mjs +20 -18
- package/dist/nx.mjs.map +1 -1
- package/dist/tasks-Caud9yHr.cjs +67 -0
- package/dist/tasks-Caud9yHr.cjs.map +1 -0
- package/dist/tasks-CjaZRi_G.d.mts +19 -0
- package/dist/tasks-DdQoP9We.d.cts +19 -0
- package/dist/tasks-KherHLac.mjs +38 -0
- package/dist/tasks-KherHLac.mjs.map +1 -0
- package/dist/template-CGw3C1Ob.mjs +88 -0
- package/dist/template-CGw3C1Ob.mjs.map +1 -0
- package/dist/template-D0gJ_MS2.cjs +105 -0
- package/dist/template-D0gJ_MS2.cjs.map +1 -0
- package/dist/template-mode-BkEs9LnY.cjs +91 -0
- package/dist/template-mode-BkEs9LnY.cjs.map +1 -0
- package/dist/template-mode-CcxV_Iju.d.cts +28 -0
- package/dist/template-mode-CcxV_Iju.d.mts +28 -0
- package/dist/template-mode-IjlFOG4O.mjs +80 -0
- package/dist/template-mode-IjlFOG4O.mjs.map +1 -0
- package/dist/test-env.cjs +16 -0
- package/dist/test-env.cjs.map +1 -0
- package/dist/test-env.d.cts +1 -0
- package/dist/test-env.d.mts +1 -0
- package/dist/test-env.mjs +18 -0
- package/dist/test-env.mjs.map +1 -0
- package/dist/vite-plus.cjs +8 -33
- package/dist/vite-plus.cjs.map +1 -1
- package/dist/vite-plus.d.cts +4 -48
- package/dist/vite-plus.d.mts +4 -48
- package/dist/vite-plus.mjs +5 -30
- package/dist/vite-plus.mjs.map +1 -1
- package/dist/vitest-template.cjs +48 -0
- package/dist/vitest-template.cjs.map +1 -0
- package/dist/vitest-template.d.cts +31 -0
- package/dist/vitest-template.d.mts +31 -0
- package/dist/vitest-template.mjs +42 -0
- package/dist/vitest-template.mjs.map +1 -0
- package/dist/vitest.cjs +10 -10
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +7 -3
- package/dist/vitest.d.mts +7 -2
- package/dist/vitest.mjs +10 -5
- package/dist/vitest.mjs.map +1 -1
- package/package.json +37 -13
- package/scripts/autopg-version +1 -0
- package/scripts/ci-install-autopg.sh +73 -0
- package/scripts/postinstall.js +39 -13
- package/dist/constants-B7sCf4fg.cjs.map +0 -1
- package/dist/constants-YnHDwShD.mjs.map +0 -1
- package/dist/lifecycle-D10usEjM.cjs +0 -743
- package/dist/lifecycle-D10usEjM.cjs.map +0 -1
- package/dist/lifecycle-PbTXNC2Z.mjs +0 -577
- package/dist/lifecycle-PbTXNC2Z.mjs.map +0 -1
- package/dist/tasks-CW2D0n46.mjs +0 -16
- package/dist/tasks-CW2D0n46.mjs.map +0 -1
- package/dist/tasks-DZ7hMmGJ.cjs +0 -21
- package/dist/tasks-DZ7hMmGJ.cjs.map +0 -1
package/dist/jest.cjs
CHANGED
|
@@ -1,28 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
__esModule: { value: true },
|
|
3
|
-
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
-
});
|
|
5
|
-
const require_lifecycle = require("./lifecycle-D10usEjM.cjs");
|
|
1
|
+
const require_lifecycle = require("./lifecycle-DEJ4GgWV.cjs");
|
|
6
2
|
//#region src/adapters/jest.ts
|
|
7
3
|
/**
|
|
8
|
-
* Jest globalSetup
|
|
4
|
+
* Jest globalSetup for a single shared test DB (`acquireIfNeeded` + dispose).
|
|
5
|
+
* Not a migrate-once / per-worker TEMPLATE runner — use `@cedarjs/pg/jest/template`.
|
|
6
|
+
*
|
|
9
7
|
* ```js
|
|
10
|
-
* globalSetup: require.resolve('@cedarjs/pg/jest')
|
|
8
|
+
* globalSetup: require.resolve('@cedarjs/pg/jest'),
|
|
9
|
+
* globalTeardown: require.resolve('@cedarjs/pg/jest-teardown'),
|
|
10
|
+
* setupFiles: [require.resolve('@cedarjs/pg/test-env')],
|
|
11
11
|
* ```
|
|
12
|
-
*
|
|
12
|
+
*
|
|
13
|
+
* `setupFiles` is required: Jest runs globalSetup in a separate process, so
|
|
14
|
+
* `DATABASE_URL` reaches workers via `.cedarpg/test.env`.
|
|
13
15
|
*/
|
|
14
16
|
async function globalSetup() {
|
|
15
|
-
await require_lifecycle.
|
|
17
|
+
await require_lifecycle.acquireIfNeeded({
|
|
16
18
|
mode: "test",
|
|
17
19
|
setEnv: true
|
|
18
20
|
});
|
|
19
21
|
}
|
|
20
|
-
/** Lease-gated dispose — no skip re-check (env may flip mid-suite). */
|
|
21
|
-
async function teardown() {
|
|
22
|
-
await require_lifecycle.dispose({ mode: "test" });
|
|
23
|
-
}
|
|
24
22
|
//#endregion
|
|
25
|
-
exports
|
|
26
|
-
exports.teardown = teardown;
|
|
23
|
+
module.exports = globalSetup;
|
|
27
24
|
|
|
28
25
|
//# sourceMappingURL=jest.cjs.map
|
package/dist/jest.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jest.cjs","names":["
|
|
1
|
+
{"version":3,"file":"jest.cjs","names":["acquireIfNeeded"],"sources":["../src/adapters/jest.ts"],"sourcesContent":["import { acquireIfNeeded } from \"../core/lifecycle.ts\";\n\n/**\n * Jest globalSetup for a single shared test DB (`acquireIfNeeded` + dispose).\n * Not a migrate-once / per-worker TEMPLATE runner — use `@cedarjs/pg/jest/template`.\n *\n * ```js\n * globalSetup: require.resolve('@cedarjs/pg/jest'),\n * globalTeardown: require.resolve('@cedarjs/pg/jest-teardown'),\n * setupFiles: [require.resolve('@cedarjs/pg/test-env')],\n * ```\n *\n * `setupFiles` is required: Jest runs globalSetup in a separate process, so\n * `DATABASE_URL` reaches workers via `.cedarpg/test.env`.\n */\nexport default async function globalSetup(): Promise<void> {\n await acquireIfNeeded({\n mode: \"test\",\n setEnv: true,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,eAA8B,cAA6B;CACzD,MAAMA,kBAAAA,gBAAgB;EACpB,MAAM;EACN,QAAQ;CACV,CAAC;AACH"}
|
package/dist/jest.d.cts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
//#region src/adapters/jest.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Jest globalSetup
|
|
3
|
+
* Jest globalSetup for a single shared test DB (`acquireIfNeeded` + dispose).
|
|
4
|
+
* Not a migrate-once / per-worker TEMPLATE runner — use `@cedarjs/pg/jest/template`.
|
|
5
|
+
*
|
|
4
6
|
* ```js
|
|
5
|
-
* globalSetup: require.resolve('@cedarjs/pg/jest')
|
|
7
|
+
* globalSetup: require.resolve('@cedarjs/pg/jest'),
|
|
8
|
+
* globalTeardown: require.resolve('@cedarjs/pg/jest-teardown'),
|
|
9
|
+
* setupFiles: [require.resolve('@cedarjs/pg/test-env')],
|
|
6
10
|
* ```
|
|
7
|
-
*
|
|
11
|
+
*
|
|
12
|
+
* `setupFiles` is required: Jest runs globalSetup in a separate process, so
|
|
13
|
+
* `DATABASE_URL` reaches workers via `.cedarpg/test.env`.
|
|
8
14
|
*/
|
|
9
15
|
declare function globalSetup(): Promise<void>;
|
|
10
|
-
|
|
11
|
-
declare function teardown(): Promise<void>;
|
|
12
|
-
//#endregion
|
|
13
|
-
export { globalSetup as default, teardown };
|
|
16
|
+
export = globalSetup;
|
|
14
17
|
//# sourceMappingURL=jest.d.cts.map
|
package/dist/jest.d.mts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
//#region src/adapters/jest.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Jest globalSetup
|
|
3
|
+
* Jest globalSetup for a single shared test DB (`acquireIfNeeded` + dispose).
|
|
4
|
+
* Not a migrate-once / per-worker TEMPLATE runner — use `@cedarjs/pg/jest/template`.
|
|
5
|
+
*
|
|
4
6
|
* ```js
|
|
5
|
-
* globalSetup: require.resolve('@cedarjs/pg/jest')
|
|
7
|
+
* globalSetup: require.resolve('@cedarjs/pg/jest'),
|
|
8
|
+
* globalTeardown: require.resolve('@cedarjs/pg/jest-teardown'),
|
|
9
|
+
* setupFiles: [require.resolve('@cedarjs/pg/test-env')],
|
|
6
10
|
* ```
|
|
7
|
-
*
|
|
11
|
+
*
|
|
12
|
+
* `setupFiles` is required: Jest runs globalSetup in a separate process, so
|
|
13
|
+
* `DATABASE_URL` reaches workers via `.cedarpg/test.env`.
|
|
8
14
|
*/
|
|
9
15
|
declare function globalSetup(): Promise<void>;
|
|
10
|
-
/** Lease-gated dispose — no skip re-check (env may flip mid-suite). */
|
|
11
|
-
declare function teardown(): Promise<void>;
|
|
12
16
|
//#endregion
|
|
13
|
-
export { globalSetup as default
|
|
17
|
+
export { globalSetup as default };
|
|
14
18
|
//# sourceMappingURL=jest.d.mts.map
|
package/dist/jest.mjs
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as acquireIfNeeded } from "./lifecycle-BOo6xBjD.mjs";
|
|
2
2
|
//#region src/adapters/jest.ts
|
|
3
3
|
/**
|
|
4
|
-
* Jest globalSetup
|
|
4
|
+
* Jest globalSetup for a single shared test DB (`acquireIfNeeded` + dispose).
|
|
5
|
+
* Not a migrate-once / per-worker TEMPLATE runner — use `@cedarjs/pg/jest/template`.
|
|
6
|
+
*
|
|
5
7
|
* ```js
|
|
6
|
-
* globalSetup: require.resolve('@cedarjs/pg/jest')
|
|
8
|
+
* globalSetup: require.resolve('@cedarjs/pg/jest'),
|
|
9
|
+
* globalTeardown: require.resolve('@cedarjs/pg/jest-teardown'),
|
|
10
|
+
* setupFiles: [require.resolve('@cedarjs/pg/test-env')],
|
|
7
11
|
* ```
|
|
8
|
-
*
|
|
12
|
+
*
|
|
13
|
+
* `setupFiles` is required: Jest runs globalSetup in a separate process, so
|
|
14
|
+
* `DATABASE_URL` reaches workers via `.cedarpg/test.env`.
|
|
9
15
|
*/
|
|
10
16
|
async function globalSetup() {
|
|
11
|
-
await
|
|
17
|
+
await acquireIfNeeded({
|
|
12
18
|
mode: "test",
|
|
13
19
|
setEnv: true
|
|
14
20
|
});
|
|
15
21
|
}
|
|
16
|
-
/** Lease-gated dispose — no skip re-check (env may flip mid-suite). */
|
|
17
|
-
async function teardown() {
|
|
18
|
-
await dispose({ mode: "test" });
|
|
19
|
-
}
|
|
20
22
|
//#endregion
|
|
21
|
-
export { globalSetup as default
|
|
23
|
+
export { globalSetup as default };
|
|
22
24
|
|
|
23
25
|
//# sourceMappingURL=jest.mjs.map
|
package/dist/jest.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jest.mjs","names":[],"sources":["../src/adapters/jest.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"jest.mjs","names":[],"sources":["../src/adapters/jest.ts"],"sourcesContent":["import { acquireIfNeeded } from \"../core/lifecycle.ts\";\n\n/**\n * Jest globalSetup for a single shared test DB (`acquireIfNeeded` + dispose).\n * Not a migrate-once / per-worker TEMPLATE runner — use `@cedarjs/pg/jest/template`.\n *\n * ```js\n * globalSetup: require.resolve('@cedarjs/pg/jest'),\n * globalTeardown: require.resolve('@cedarjs/pg/jest-teardown'),\n * setupFiles: [require.resolve('@cedarjs/pg/test-env')],\n * ```\n *\n * `setupFiles` is required: Jest runs globalSetup in a separate process, so\n * `DATABASE_URL` reaches workers via `.cedarpg/test.env`.\n */\nexport default async function globalSetup(): Promise<void> {\n await acquireIfNeeded({\n mode: \"test\",\n setEnv: true,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,eAA8B,cAA6B;CACzD,MAAM,gBAAgB;EACpB,MAAM;EACN,QAAQ;CACV,CAAC;AACH"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { t as DbMode } from "./naming-C2nGVxPk.mjs";
|
|
2
|
+
//#region src/core/lease.d.ts
|
|
3
|
+
type Lease = {
|
|
4
|
+
schemaVersion: 1;
|
|
5
|
+
mode: DbMode;
|
|
6
|
+
root: string;
|
|
7
|
+
repoSlug: string;
|
|
8
|
+
worktreeSlug: string;
|
|
9
|
+
pathHash: string;
|
|
10
|
+
databaseName: string;
|
|
11
|
+
roleName: string;
|
|
12
|
+
port: number;
|
|
13
|
+
pid: number;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
};
|
|
16
|
+
/** Narrow-parse a lease; reject incomplete or wrong-version records. */
|
|
17
|
+
declare function parseLease(raw: unknown): Lease | null;
|
|
18
|
+
/** Absolute `.cedarpg/<mode>.env` path (resolves root when omitted). */
|
|
19
|
+
declare function envFilePath(root: string | undefined, mode: DbMode): string;
|
|
20
|
+
declare function readLease(root: string, mode: DbMode): Lease | null;
|
|
21
|
+
declare function isOrphanLease(lease: Lease): boolean;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { readLease as a, parseLease as i, envFilePath as n, isOrphanLease as r, Lease as t };
|
|
24
|
+
//# sourceMappingURL=lease-B3TuX92y.d.mts.map
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { r as STATE_DIRNAME } from "./constants-NLR0U4NR.mjs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
//#region src/core/worktree.ts
|
|
8
|
+
function git(args, cwd) {
|
|
9
|
+
try {
|
|
10
|
+
return execFileSync("git", args, {
|
|
11
|
+
cwd,
|
|
12
|
+
encoding: "utf8",
|
|
13
|
+
stdio: [
|
|
14
|
+
"ignore",
|
|
15
|
+
"pipe",
|
|
16
|
+
"ignore"
|
|
17
|
+
]
|
|
18
|
+
}).trim();
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function sanitizeSlug(input) {
|
|
24
|
+
return input.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the project / worktree root.
|
|
28
|
+
* Explicit `root` wins (app project path). Otherwise prefer git toplevel, then cwd.
|
|
29
|
+
*/
|
|
30
|
+
function resolveRoot(root) {
|
|
31
|
+
if (root) return resolve(root);
|
|
32
|
+
const start = resolve(process.cwd());
|
|
33
|
+
const toplevel = git(["rev-parse", "--show-toplevel"], start);
|
|
34
|
+
if (toplevel) return toplevel;
|
|
35
|
+
return start;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Identify repo + worktree for observable DB naming.
|
|
39
|
+
*/
|
|
40
|
+
function resolveWorktreeIdentity(root) {
|
|
41
|
+
const absRoot = resolveRoot(root);
|
|
42
|
+
const pathHash = createHash("sha256").update(absRoot).digest("hex").slice(0, 8);
|
|
43
|
+
const commonDir = git(["rev-parse", "--git-common-dir"], absRoot);
|
|
44
|
+
let repoSlug = sanitizeSlug(basename(absRoot));
|
|
45
|
+
if (commonDir) {
|
|
46
|
+
const commonAbs = resolve(absRoot, commonDir);
|
|
47
|
+
const mainRoot = basename(commonAbs) === ".git" ? dirname(commonAbs) : commonAbs;
|
|
48
|
+
const fromGit = sanitizeSlug(basename(basename(mainRoot) === ".git" ? dirname(mainRoot) : mainRoot));
|
|
49
|
+
if (fromGit) repoSlug = fromGit;
|
|
50
|
+
}
|
|
51
|
+
let worktreeSlug = sanitizeSlug(basename(absRoot));
|
|
52
|
+
const gitFile = resolve(absRoot, ".git");
|
|
53
|
+
if (existsSync(gitFile)) try {
|
|
54
|
+
const match = readFileSync(gitFile, "utf8").trim().match(/gitdir:\s*(.+)$/m);
|
|
55
|
+
if (match?.[1]) {
|
|
56
|
+
const parts = resolve(absRoot, match[1].trim()).split(/[/\\]/);
|
|
57
|
+
const wtIdx = parts.lastIndexOf("worktrees");
|
|
58
|
+
if (wtIdx >= 0 && parts[wtIdx + 1]) {
|
|
59
|
+
const fromWt = sanitizeSlug(parts[wtIdx + 1]);
|
|
60
|
+
if (fromWt) worktreeSlug = fromWt;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch {}
|
|
64
|
+
if (!repoSlug) repoSlug = "repo";
|
|
65
|
+
if (!worktreeSlug) worktreeSlug = "wt";
|
|
66
|
+
return {
|
|
67
|
+
root: absRoot,
|
|
68
|
+
repoSlug,
|
|
69
|
+
worktreeSlug,
|
|
70
|
+
pathHash
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/core/lease.ts
|
|
75
|
+
function isDbMode(value) {
|
|
76
|
+
return value === "dev" || value === "test";
|
|
77
|
+
}
|
|
78
|
+
function isNonEmptyString(value) {
|
|
79
|
+
return typeof value === "string" && value.length > 0;
|
|
80
|
+
}
|
|
81
|
+
/** Narrow-parse a lease; reject incomplete or wrong-version records. */
|
|
82
|
+
function parseLease(raw) {
|
|
83
|
+
if (!raw || typeof raw !== "object") return null;
|
|
84
|
+
const o = raw;
|
|
85
|
+
if (o.schemaVersion !== 1) return null;
|
|
86
|
+
if (!isDbMode(o.mode)) return null;
|
|
87
|
+
if (!isNonEmptyString(o.root)) return null;
|
|
88
|
+
if (!isNonEmptyString(o.repoSlug)) return null;
|
|
89
|
+
if (!isNonEmptyString(o.worktreeSlug)) return null;
|
|
90
|
+
if (!isNonEmptyString(o.pathHash)) return null;
|
|
91
|
+
if (!isNonEmptyString(o.databaseName)) return null;
|
|
92
|
+
if (!isNonEmptyString(o.roleName)) return null;
|
|
93
|
+
if (typeof o.port !== "number") return null;
|
|
94
|
+
if (typeof o.pid !== "number") return null;
|
|
95
|
+
if (!isNonEmptyString(o.createdAt)) return null;
|
|
96
|
+
return {
|
|
97
|
+
schemaVersion: 1,
|
|
98
|
+
mode: o.mode,
|
|
99
|
+
root: o.root,
|
|
100
|
+
repoSlug: o.repoSlug,
|
|
101
|
+
worktreeSlug: o.worktreeSlug,
|
|
102
|
+
pathHash: o.pathHash,
|
|
103
|
+
databaseName: o.databaseName,
|
|
104
|
+
roleName: o.roleName,
|
|
105
|
+
port: o.port,
|
|
106
|
+
pid: o.pid,
|
|
107
|
+
createdAt: o.createdAt
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function leaseDir(root) {
|
|
111
|
+
return join(root, STATE_DIRNAME);
|
|
112
|
+
}
|
|
113
|
+
function leasePath(root, mode) {
|
|
114
|
+
return join(leaseDir(root), `${mode}.json`);
|
|
115
|
+
}
|
|
116
|
+
function envPath(root, mode) {
|
|
117
|
+
return join(leaseDir(root), `${mode}.env`);
|
|
118
|
+
}
|
|
119
|
+
/** Absolute `.cedarpg/<mode>.env` path (resolves root when omitted). */
|
|
120
|
+
function envFilePath(root, mode) {
|
|
121
|
+
return envPath(resolveRoot(root), mode);
|
|
122
|
+
}
|
|
123
|
+
/** Best-effort delete worktree lease JSON + mode env (missing is fine). */
|
|
124
|
+
function unlinkWorktreeArtifacts(root, mode) {
|
|
125
|
+
for (const file of [leasePath(root, mode), envPath(root, mode)]) try {
|
|
126
|
+
unlinkSync(file);
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
/** Durable registry outside worktrees so GC can find orphans after a checkout is deleted. */
|
|
130
|
+
function registryDir() {
|
|
131
|
+
return process.env.CEDAR_PG_REGISTRY_DIR || join(homedir(), ".cedarpg", "registry");
|
|
132
|
+
}
|
|
133
|
+
function registryPath(databaseName) {
|
|
134
|
+
return join(registryDir(), `${databaseName}.json`);
|
|
135
|
+
}
|
|
136
|
+
function writeJsonAtomic(file, value) {
|
|
137
|
+
mkdirSync(dirname(file), {
|
|
138
|
+
recursive: true,
|
|
139
|
+
mode: 448
|
|
140
|
+
});
|
|
141
|
+
const tmp = `${file}.tmp.${process.pid}`;
|
|
142
|
+
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
|
|
143
|
+
renameSync(tmp, file);
|
|
144
|
+
}
|
|
145
|
+
function registerLease(lease) {
|
|
146
|
+
writeJsonAtomic(registryPath(lease.databaseName), lease);
|
|
147
|
+
}
|
|
148
|
+
function unregisterLease(databaseName) {
|
|
149
|
+
try {
|
|
150
|
+
unlinkSync(registryPath(databaseName));
|
|
151
|
+
} catch {}
|
|
152
|
+
}
|
|
153
|
+
function readLease(root, mode) {
|
|
154
|
+
const file = leasePath(root, mode);
|
|
155
|
+
if (!existsSync(file)) return null;
|
|
156
|
+
try {
|
|
157
|
+
return parseLease(JSON.parse(readFileSync(file, "utf8")));
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Persist lease. Registry is written first (GC source of truth), then the worktree cache.
|
|
164
|
+
*/
|
|
165
|
+
function writeLease(lease) {
|
|
166
|
+
registerLease(lease);
|
|
167
|
+
mkdirSync(leaseDir(lease.root), {
|
|
168
|
+
recursive: true,
|
|
169
|
+
mode: 448
|
|
170
|
+
});
|
|
171
|
+
writeJsonAtomic(leasePath(lease.root, lease.mode), lease);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Remove registry entry by databaseName and best-effort delete worktree lease + env.
|
|
175
|
+
* Call only after a successful DROP (or when intentionally abandoning tracking).
|
|
176
|
+
*/
|
|
177
|
+
function forgetLease(lease) {
|
|
178
|
+
unregisterLease(lease.databaseName);
|
|
179
|
+
unlinkWorktreeArtifacts(lease.root, lease.mode);
|
|
180
|
+
}
|
|
181
|
+
/** All registry entries (used by gc). */
|
|
182
|
+
function listRegistryLeases() {
|
|
183
|
+
const dir = registryDir();
|
|
184
|
+
if (!existsSync(dir)) return [];
|
|
185
|
+
const out = [];
|
|
186
|
+
for (const name of readdirSync(dir)) {
|
|
187
|
+
if (!name.endsWith(".json")) continue;
|
|
188
|
+
try {
|
|
189
|
+
const lease = parseLease(JSON.parse(readFileSync(join(dir, name), "utf8")));
|
|
190
|
+
if (lease) out.push(lease);
|
|
191
|
+
} catch {}
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
function isOrphanLease(lease) {
|
|
196
|
+
return !existsSync(lease.root);
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
export { leaseDir as a, parseLease as c, resolveRoot as d, resolveWorktreeIdentity as f, isOrphanLease as i, readLease as l, envPath as n, leasePath as o, forgetLease as r, listRegistryLeases as s, envFilePath as t, writeLease as u };
|
|
200
|
+
|
|
201
|
+
//# sourceMappingURL=lease-BSBBpaR4.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease-BSBBpaR4.mjs","names":[],"sources":["../src/core/worktree.ts","../src/core/lease.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\nimport { execFileSync } from \"node:child_process\";\n\nexport type WorktreeIdentity = {\n /** Absolute path to the worktree / project root. */\n root: string;\n /** Basename of the git common dir / main checkout (e.g. `cedar`). */\n repoSlug: string;\n /** Basename of this worktree directory (e.g. `feat-auth`). */\n worktreeSlug: string;\n /** First 8 hex chars of sha256(absolute root). */\n pathHash: string;\n};\n\nfunction git(args: string[], cwd: string): string | null {\n try {\n return execFileSync(\"git\", args, {\n cwd,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n } catch {\n return null;\n }\n}\n\nfunction sanitizeSlug(input: string): string {\n return input\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/_+/g, \"_\")\n .replace(/^_|_$/g, \"\");\n}\n\n/**\n * Resolve the project / worktree root.\n * Explicit `root` wins (app project path). Otherwise prefer git toplevel, then cwd.\n */\nexport function resolveRoot(root?: string): string {\n if (root) return resolve(root);\n const start = resolve(process.cwd());\n const toplevel = git([\"rev-parse\", \"--show-toplevel\"], start);\n if (toplevel) return toplevel;\n return start;\n}\n\n/**\n * Identify repo + worktree for observable DB naming.\n */\nexport function resolveWorktreeIdentity(root?: string): WorktreeIdentity {\n const absRoot = resolveRoot(root);\n const pathHash = createHash(\"sha256\").update(absRoot).digest(\"hex\").slice(0, 8);\n\n const commonDir = git([\"rev-parse\", \"--git-common-dir\"], absRoot);\n let repoSlug = sanitizeSlug(basename(absRoot));\n if (commonDir) {\n // common-dir is often `<main>/.git` or an absolute path to the main .git\n const commonAbs = resolve(absRoot, commonDir);\n // If commonAbs ends with `.git`, parent is main worktree root\n const mainRoot = basename(commonAbs) === \".git\" ? dirname(commonAbs) : commonAbs;\n // For linked worktrees, common-dir may be `.../main/.git`\n const candidate = basename(mainRoot) === \".git\" ? dirname(mainRoot) : mainRoot;\n const fromGit = sanitizeSlug(basename(candidate));\n if (fromGit) repoSlug = fromGit;\n }\n\n // Prefer gitdir worktree name when present (`.git` file pointing at worktrees/<name>)\n let worktreeSlug = sanitizeSlug(basename(absRoot));\n const gitFile = resolve(absRoot, \".git\");\n if (existsSync(gitFile)) {\n try {\n const content = readFileSync(gitFile, \"utf8\").trim();\n const match = content.match(/gitdir:\\s*(.+)$/m);\n if (match?.[1]) {\n const gitdir = resolve(absRoot, match[1].trim());\n // .../.git/worktrees/<name>\n const parts = gitdir.split(/[/\\\\]/);\n const wtIdx = parts.lastIndexOf(\"worktrees\");\n if (wtIdx >= 0 && parts[wtIdx + 1]) {\n const fromWt = sanitizeSlug(parts[wtIdx + 1]!);\n if (fromWt) worktreeSlug = fromWt;\n }\n }\n } catch {\n // keep basename slug\n }\n }\n\n if (!repoSlug) repoSlug = \"repo\";\n if (!worktreeSlug) worktreeSlug = \"wt\";\n\n return { root: absRoot, repoSlug, worktreeSlug, pathHash };\n}\n","import {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { STATE_DIRNAME } from \"./constants.ts\";\nimport type { DbMode } from \"./naming.ts\";\nimport { resolveRoot } from \"./worktree.ts\";\n\nexport type Lease = {\n schemaVersion: 1;\n mode: DbMode;\n root: string;\n repoSlug: string;\n worktreeSlug: string;\n pathHash: string;\n databaseName: string;\n roleName: string;\n port: number;\n pid: number;\n createdAt: string;\n};\n\nfunction isDbMode(value: unknown): value is DbMode {\n return value === \"dev\" || value === \"test\";\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0;\n}\n\n/** Narrow-parse a lease; reject incomplete or wrong-version records. */\nexport function parseLease(raw: unknown): Lease | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n if (o.schemaVersion !== 1) return null;\n if (!isDbMode(o.mode)) return null;\n if (!isNonEmptyString(o.root)) return null;\n if (!isNonEmptyString(o.repoSlug)) return null;\n if (!isNonEmptyString(o.worktreeSlug)) return null;\n if (!isNonEmptyString(o.pathHash)) return null;\n if (!isNonEmptyString(o.databaseName)) return null;\n if (!isNonEmptyString(o.roleName)) return null;\n if (typeof o.port !== \"number\") return null;\n if (typeof o.pid !== \"number\") return null;\n if (!isNonEmptyString(o.createdAt)) return null;\n return {\n schemaVersion: 1,\n mode: o.mode,\n root: o.root,\n repoSlug: o.repoSlug,\n worktreeSlug: o.worktreeSlug,\n pathHash: o.pathHash,\n databaseName: o.databaseName,\n roleName: o.roleName,\n port: o.port,\n pid: o.pid,\n createdAt: o.createdAt,\n };\n}\n\nexport function leaseDir(root: string): string {\n return join(root, STATE_DIRNAME);\n}\n\nexport function leasePath(root: string, mode: DbMode): string {\n return join(leaseDir(root), `${mode}.json`);\n}\n\nexport function envPath(root: string, mode: DbMode): string {\n return join(leaseDir(root), `${mode}.env`);\n}\n\n/** Absolute `.cedarpg/<mode>.env` path (resolves root when omitted). */\nexport function envFilePath(root: string | undefined, mode: DbMode): string {\n return envPath(resolveRoot(root), mode);\n}\n\n/** Best-effort delete worktree lease JSON + mode env (missing is fine). */\nfunction unlinkWorktreeArtifacts(root: string, mode: DbMode): void {\n for (const file of [leasePath(root, mode), envPath(root, mode)]) {\n try {\n unlinkSync(file);\n } catch {\n // root or file may already be gone\n }\n }\n}\n\n/** Durable registry outside worktrees so GC can find orphans after a checkout is deleted. */\nexport function registryDir(): string {\n return process.env.CEDAR_PG_REGISTRY_DIR || join(homedir(), STATE_DIRNAME, \"registry\");\n}\n\nfunction registryPath(databaseName: string): string {\n return join(registryDir(), `${databaseName}.json`);\n}\n\nfunction writeJsonAtomic(file: string, value: unknown): void {\n mkdirSync(dirname(file), { recursive: true, mode: 0o700 });\n const tmp = `${file}.tmp.${process.pid}`;\n writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\\n`, { mode: 0o600 });\n renameSync(tmp, file);\n}\n\nfunction registerLease(lease: Lease): void {\n writeJsonAtomic(registryPath(lease.databaseName), lease);\n}\n\nfunction unregisterLease(databaseName: string): void {\n try {\n unlinkSync(registryPath(databaseName));\n } catch {\n // missing is fine\n }\n}\n\nfunction peekDatabaseName(root: string, mode: DbMode): string | undefined {\n const file = leasePath(root, mode);\n if (!existsSync(file)) return undefined;\n try {\n const raw = JSON.parse(readFileSync(file, \"utf8\")) as { databaseName?: unknown };\n return typeof raw.databaseName === \"string\" && raw.databaseName ? raw.databaseName : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function readLease(root: string, mode: DbMode): Lease | null {\n const file = leasePath(root, mode);\n if (!existsSync(file)) return null;\n try {\n return parseLease(JSON.parse(readFileSync(file, \"utf8\")));\n } catch {\n return null;\n }\n}\n\n/**\n * Persist lease. Registry is written first (GC source of truth), then the worktree cache.\n */\nexport function writeLease(lease: Lease): void {\n registerLease(lease);\n const dir = leaseDir(lease.root);\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n writeJsonAtomic(leasePath(lease.root, lease.mode), lease);\n}\n\n/**\n * Remove registry entry by databaseName and best-effort delete worktree lease + env.\n * Call only after a successful DROP (or when intentionally abandoning tracking).\n */\nexport function forgetLease(lease: Pick<Lease, \"root\" | \"mode\" | \"databaseName\">): void {\n unregisterLease(lease.databaseName);\n unlinkWorktreeArtifacts(lease.root, lease.mode);\n}\n\n/**\n * Clear worktree lease + env + registry. Unregisters by databaseName even when the local\n * file fails full parse (peeks databaseName from raw JSON).\n */\nexport function clearLease(root: string, mode: DbMode): void {\n const existing = readLease(root, mode);\n if (existing) {\n forgetLease(existing);\n return;\n }\n const databaseName = peekDatabaseName(root, mode);\n unlinkWorktreeArtifacts(root, mode);\n if (databaseName) {\n unregisterLease(databaseName);\n }\n}\n\n/** All registry entries (used by gc). */\nexport function listRegistryLeases(): Lease[] {\n const dir = registryDir();\n if (!existsSync(dir)) return [];\n const out: Lease[] = [];\n for (const name of readdirSync(dir)) {\n if (!name.endsWith(\".json\")) continue;\n try {\n const lease = parseLease(JSON.parse(readFileSync(join(dir, name), \"utf8\")));\n if (lease) out.push(lease);\n } catch {\n // skip corrupt\n }\n }\n return out;\n}\n\nexport function isOrphanLease(lease: Lease): boolean {\n return !existsSync(lease.root);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
const require_constants = require("./constants-Ct8myrEn.cjs");
|
|
2
|
+
let node_fs = require("node:fs");
|
|
3
|
+
let node_os = require("node:os");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
let node_crypto = require("node:crypto");
|
|
6
|
+
let node_child_process = require("node:child_process");
|
|
7
|
+
//#region src/core/worktree.ts
|
|
8
|
+
function git(args, cwd) {
|
|
9
|
+
try {
|
|
10
|
+
return (0, node_child_process.execFileSync)("git", args, {
|
|
11
|
+
cwd,
|
|
12
|
+
encoding: "utf8",
|
|
13
|
+
stdio: [
|
|
14
|
+
"ignore",
|
|
15
|
+
"pipe",
|
|
16
|
+
"ignore"
|
|
17
|
+
]
|
|
18
|
+
}).trim();
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function sanitizeSlug(input) {
|
|
24
|
+
return input.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the project / worktree root.
|
|
28
|
+
* Explicit `root` wins (app project path). Otherwise prefer git toplevel, then cwd.
|
|
29
|
+
*/
|
|
30
|
+
function resolveRoot(root) {
|
|
31
|
+
if (root) return (0, node_path.resolve)(root);
|
|
32
|
+
const start = (0, node_path.resolve)(process.cwd());
|
|
33
|
+
const toplevel = git(["rev-parse", "--show-toplevel"], start);
|
|
34
|
+
if (toplevel) return toplevel;
|
|
35
|
+
return start;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Identify repo + worktree for observable DB naming.
|
|
39
|
+
*/
|
|
40
|
+
function resolveWorktreeIdentity(root) {
|
|
41
|
+
const absRoot = resolveRoot(root);
|
|
42
|
+
const pathHash = (0, node_crypto.createHash)("sha256").update(absRoot).digest("hex").slice(0, 8);
|
|
43
|
+
const commonDir = git(["rev-parse", "--git-common-dir"], absRoot);
|
|
44
|
+
let repoSlug = sanitizeSlug((0, node_path.basename)(absRoot));
|
|
45
|
+
if (commonDir) {
|
|
46
|
+
const commonAbs = (0, node_path.resolve)(absRoot, commonDir);
|
|
47
|
+
const mainRoot = (0, node_path.basename)(commonAbs) === ".git" ? (0, node_path.dirname)(commonAbs) : commonAbs;
|
|
48
|
+
const fromGit = sanitizeSlug((0, node_path.basename)((0, node_path.basename)(mainRoot) === ".git" ? (0, node_path.dirname)(mainRoot) : mainRoot));
|
|
49
|
+
if (fromGit) repoSlug = fromGit;
|
|
50
|
+
}
|
|
51
|
+
let worktreeSlug = sanitizeSlug((0, node_path.basename)(absRoot));
|
|
52
|
+
const gitFile = (0, node_path.resolve)(absRoot, ".git");
|
|
53
|
+
if ((0, node_fs.existsSync)(gitFile)) try {
|
|
54
|
+
const match = (0, node_fs.readFileSync)(gitFile, "utf8").trim().match(/gitdir:\s*(.+)$/m);
|
|
55
|
+
if (match?.[1]) {
|
|
56
|
+
const parts = (0, node_path.resolve)(absRoot, match[1].trim()).split(/[/\\]/);
|
|
57
|
+
const wtIdx = parts.lastIndexOf("worktrees");
|
|
58
|
+
if (wtIdx >= 0 && parts[wtIdx + 1]) {
|
|
59
|
+
const fromWt = sanitizeSlug(parts[wtIdx + 1]);
|
|
60
|
+
if (fromWt) worktreeSlug = fromWt;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch {}
|
|
64
|
+
if (!repoSlug) repoSlug = "repo";
|
|
65
|
+
if (!worktreeSlug) worktreeSlug = "wt";
|
|
66
|
+
return {
|
|
67
|
+
root: absRoot,
|
|
68
|
+
repoSlug,
|
|
69
|
+
worktreeSlug,
|
|
70
|
+
pathHash
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/core/lease.ts
|
|
75
|
+
function isDbMode(value) {
|
|
76
|
+
return value === "dev" || value === "test";
|
|
77
|
+
}
|
|
78
|
+
function isNonEmptyString(value) {
|
|
79
|
+
return typeof value === "string" && value.length > 0;
|
|
80
|
+
}
|
|
81
|
+
/** Narrow-parse a lease; reject incomplete or wrong-version records. */
|
|
82
|
+
function parseLease(raw) {
|
|
83
|
+
if (!raw || typeof raw !== "object") return null;
|
|
84
|
+
const o = raw;
|
|
85
|
+
if (o.schemaVersion !== 1) return null;
|
|
86
|
+
if (!isDbMode(o.mode)) return null;
|
|
87
|
+
if (!isNonEmptyString(o.root)) return null;
|
|
88
|
+
if (!isNonEmptyString(o.repoSlug)) return null;
|
|
89
|
+
if (!isNonEmptyString(o.worktreeSlug)) return null;
|
|
90
|
+
if (!isNonEmptyString(o.pathHash)) return null;
|
|
91
|
+
if (!isNonEmptyString(o.databaseName)) return null;
|
|
92
|
+
if (!isNonEmptyString(o.roleName)) return null;
|
|
93
|
+
if (typeof o.port !== "number") return null;
|
|
94
|
+
if (typeof o.pid !== "number") return null;
|
|
95
|
+
if (!isNonEmptyString(o.createdAt)) return null;
|
|
96
|
+
return {
|
|
97
|
+
schemaVersion: 1,
|
|
98
|
+
mode: o.mode,
|
|
99
|
+
root: o.root,
|
|
100
|
+
repoSlug: o.repoSlug,
|
|
101
|
+
worktreeSlug: o.worktreeSlug,
|
|
102
|
+
pathHash: o.pathHash,
|
|
103
|
+
databaseName: o.databaseName,
|
|
104
|
+
roleName: o.roleName,
|
|
105
|
+
port: o.port,
|
|
106
|
+
pid: o.pid,
|
|
107
|
+
createdAt: o.createdAt
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function leaseDir(root) {
|
|
111
|
+
return (0, node_path.join)(root, require_constants.STATE_DIRNAME);
|
|
112
|
+
}
|
|
113
|
+
function leasePath(root, mode) {
|
|
114
|
+
return (0, node_path.join)(leaseDir(root), `${mode}.json`);
|
|
115
|
+
}
|
|
116
|
+
function envPath(root, mode) {
|
|
117
|
+
return (0, node_path.join)(leaseDir(root), `${mode}.env`);
|
|
118
|
+
}
|
|
119
|
+
/** Absolute `.cedarpg/<mode>.env` path (resolves root when omitted). */
|
|
120
|
+
function envFilePath(root, mode) {
|
|
121
|
+
return envPath(resolveRoot(root), mode);
|
|
122
|
+
}
|
|
123
|
+
/** Best-effort delete worktree lease JSON + mode env (missing is fine). */
|
|
124
|
+
function unlinkWorktreeArtifacts(root, mode) {
|
|
125
|
+
for (const file of [leasePath(root, mode), envPath(root, mode)]) try {
|
|
126
|
+
(0, node_fs.unlinkSync)(file);
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
/** Durable registry outside worktrees so GC can find orphans after a checkout is deleted. */
|
|
130
|
+
function registryDir() {
|
|
131
|
+
return process.env.CEDAR_PG_REGISTRY_DIR || (0, node_path.join)((0, node_os.homedir)(), ".cedarpg", "registry");
|
|
132
|
+
}
|
|
133
|
+
function registryPath(databaseName) {
|
|
134
|
+
return (0, node_path.join)(registryDir(), `${databaseName}.json`);
|
|
135
|
+
}
|
|
136
|
+
function writeJsonAtomic(file, value) {
|
|
137
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(file), {
|
|
138
|
+
recursive: true,
|
|
139
|
+
mode: 448
|
|
140
|
+
});
|
|
141
|
+
const tmp = `${file}.tmp.${process.pid}`;
|
|
142
|
+
(0, node_fs.writeFileSync)(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
|
|
143
|
+
(0, node_fs.renameSync)(tmp, file);
|
|
144
|
+
}
|
|
145
|
+
function registerLease(lease) {
|
|
146
|
+
writeJsonAtomic(registryPath(lease.databaseName), lease);
|
|
147
|
+
}
|
|
148
|
+
function unregisterLease(databaseName) {
|
|
149
|
+
try {
|
|
150
|
+
(0, node_fs.unlinkSync)(registryPath(databaseName));
|
|
151
|
+
} catch {}
|
|
152
|
+
}
|
|
153
|
+
function readLease(root, mode) {
|
|
154
|
+
const file = leasePath(root, mode);
|
|
155
|
+
if (!(0, node_fs.existsSync)(file)) return null;
|
|
156
|
+
try {
|
|
157
|
+
return parseLease(JSON.parse((0, node_fs.readFileSync)(file, "utf8")));
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Persist lease. Registry is written first (GC source of truth), then the worktree cache.
|
|
164
|
+
*/
|
|
165
|
+
function writeLease(lease) {
|
|
166
|
+
registerLease(lease);
|
|
167
|
+
(0, node_fs.mkdirSync)(leaseDir(lease.root), {
|
|
168
|
+
recursive: true,
|
|
169
|
+
mode: 448
|
|
170
|
+
});
|
|
171
|
+
writeJsonAtomic(leasePath(lease.root, lease.mode), lease);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Remove registry entry by databaseName and best-effort delete worktree lease + env.
|
|
175
|
+
* Call only after a successful DROP (or when intentionally abandoning tracking).
|
|
176
|
+
*/
|
|
177
|
+
function forgetLease(lease) {
|
|
178
|
+
unregisterLease(lease.databaseName);
|
|
179
|
+
unlinkWorktreeArtifacts(lease.root, lease.mode);
|
|
180
|
+
}
|
|
181
|
+
/** All registry entries (used by gc). */
|
|
182
|
+
function listRegistryLeases() {
|
|
183
|
+
const dir = registryDir();
|
|
184
|
+
if (!(0, node_fs.existsSync)(dir)) return [];
|
|
185
|
+
const out = [];
|
|
186
|
+
for (const name of (0, node_fs.readdirSync)(dir)) {
|
|
187
|
+
if (!name.endsWith(".json")) continue;
|
|
188
|
+
try {
|
|
189
|
+
const lease = parseLease(JSON.parse((0, node_fs.readFileSync)((0, node_path.join)(dir, name), "utf8")));
|
|
190
|
+
if (lease) out.push(lease);
|
|
191
|
+
} catch {}
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
function isOrphanLease(lease) {
|
|
196
|
+
return !(0, node_fs.existsSync)(lease.root);
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
Object.defineProperty(exports, "envFilePath", {
|
|
200
|
+
enumerable: true,
|
|
201
|
+
get: function() {
|
|
202
|
+
return envFilePath;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
Object.defineProperty(exports, "envPath", {
|
|
206
|
+
enumerable: true,
|
|
207
|
+
get: function() {
|
|
208
|
+
return envPath;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
Object.defineProperty(exports, "forgetLease", {
|
|
212
|
+
enumerable: true,
|
|
213
|
+
get: function() {
|
|
214
|
+
return forgetLease;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
Object.defineProperty(exports, "isOrphanLease", {
|
|
218
|
+
enumerable: true,
|
|
219
|
+
get: function() {
|
|
220
|
+
return isOrphanLease;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
Object.defineProperty(exports, "leaseDir", {
|
|
224
|
+
enumerable: true,
|
|
225
|
+
get: function() {
|
|
226
|
+
return leaseDir;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
Object.defineProperty(exports, "leasePath", {
|
|
230
|
+
enumerable: true,
|
|
231
|
+
get: function() {
|
|
232
|
+
return leasePath;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
Object.defineProperty(exports, "listRegistryLeases", {
|
|
236
|
+
enumerable: true,
|
|
237
|
+
get: function() {
|
|
238
|
+
return listRegistryLeases;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
Object.defineProperty(exports, "parseLease", {
|
|
242
|
+
enumerable: true,
|
|
243
|
+
get: function() {
|
|
244
|
+
return parseLease;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
Object.defineProperty(exports, "readLease", {
|
|
248
|
+
enumerable: true,
|
|
249
|
+
get: function() {
|
|
250
|
+
return readLease;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
Object.defineProperty(exports, "resolveRoot", {
|
|
254
|
+
enumerable: true,
|
|
255
|
+
get: function() {
|
|
256
|
+
return resolveRoot;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
Object.defineProperty(exports, "resolveWorktreeIdentity", {
|
|
260
|
+
enumerable: true,
|
|
261
|
+
get: function() {
|
|
262
|
+
return resolveWorktreeIdentity;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
Object.defineProperty(exports, "writeLease", {
|
|
266
|
+
enumerable: true,
|
|
267
|
+
get: function() {
|
|
268
|
+
return writeLease;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
//# sourceMappingURL=lease-Bq9wKwQa.cjs.map
|