@narumitw/pi-sync 0.49.9 → 0.49.11
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 +10 -1
- package/dist/chunks/chunk-3GRWMWUZ.ts +213 -0
- package/dist/chunks/chunk-3GRWMWUZ.ts.map +7 -0
- package/dist/chunks/chunk-3NDD5QPM.ts +203 -0
- package/dist/chunks/chunk-3NDD5QPM.ts.map +7 -0
- package/dist/chunks/chunk-4F3W4CSC.ts +195 -0
- package/dist/chunks/chunk-4F3W4CSC.ts.map +7 -0
- package/dist/chunks/chunk-4U6AFLJB.ts +221 -0
- package/dist/chunks/chunk-4U6AFLJB.ts.map +7 -0
- package/dist/chunks/chunk-5MQ76BK5.ts +34 -0
- package/dist/chunks/chunk-5MQ76BK5.ts.map +7 -0
- package/dist/chunks/chunk-APZVBSM6.ts +1238 -0
- package/dist/chunks/chunk-APZVBSM6.ts.map +7 -0
- package/dist/chunks/chunk-BHJUISB4.ts +134 -0
- package/dist/chunks/chunk-BHJUISB4.ts.map +7 -0
- package/dist/chunks/chunk-D3S54OXI.ts +653 -0
- package/dist/chunks/chunk-D3S54OXI.ts.map +7 -0
- package/dist/chunks/chunk-EWX3TPLQ.ts +356 -0
- package/dist/chunks/chunk-EWX3TPLQ.ts.map +7 -0
- package/dist/chunks/chunk-F5QL2GPY.ts +341 -0
- package/dist/chunks/chunk-F5QL2GPY.ts.map +7 -0
- package/dist/chunks/chunk-FR6CVXAL.ts +81 -0
- package/dist/chunks/chunk-FR6CVXAL.ts.map +7 -0
- package/dist/chunks/chunk-HGV24P4T.ts +53 -0
- package/dist/chunks/chunk-HGV24P4T.ts.map +7 -0
- package/dist/chunks/chunk-JNOOSPEW.ts +82 -0
- package/dist/chunks/chunk-JNOOSPEW.ts.map +7 -0
- package/dist/chunks/chunk-LJDSHNS3.ts +259 -0
- package/dist/chunks/chunk-LJDSHNS3.ts.map +7 -0
- package/dist/chunks/chunk-RGJ6SOYR.ts +128 -0
- package/dist/chunks/chunk-RGJ6SOYR.ts.map +7 -0
- package/dist/chunks/chunk-W64NEHXT.ts +167 -0
- package/dist/chunks/chunk-W64NEHXT.ts.map +7 -0
- package/dist/chunks/chunk-YQ6UW7IF.ts +394 -0
- package/dist/chunks/chunk-YQ6UW7IF.ts.map +7 -0
- package/dist/chunks/file-selection-4UABOP44.ts +290 -0
- package/dist/chunks/file-selection-4UABOP44.ts.map +7 -0
- package/dist/chunks/git-backend-E3UOUUOM.ts +1104 -0
- package/dist/chunks/git-backend-E3UOUUOM.ts.map +7 -0
- package/dist/chunks/manager-result-dispatcher-YKBLZ7KL.ts +18 -0
- package/dist/chunks/manager-result-dispatcher-YKBLZ7KL.ts.map +7 -0
- package/dist/chunks/manager-ui-7E25VWUP.ts +2754 -0
- package/dist/chunks/manager-ui-7E25VWUP.ts.map +7 -0
- package/dist/chunks/remote-selection-ui-J6OW5DKZ.ts +17 -0
- package/dist/chunks/remote-selection-ui-J6OW5DKZ.ts.map +7 -0
- package/dist/chunks/s3-backend-3QZGO2EF.ts +680 -0
- package/dist/chunks/s3-backend-3QZGO2EF.ts.map +7 -0
- package/dist/chunks/setup-switch-X3W3FALZ.ts +27 -0
- package/dist/chunks/setup-switch-X3W3FALZ.ts.map +7 -0
- package/dist/chunks/snapshot-WBSV4MQG.ts +45 -0
- package/dist/chunks/snapshot-WBSV4MQG.ts.map +7 -0
- package/dist/chunks/sync-operations-MPAFVVHA.ts +1112 -0
- package/dist/chunks/sync-operations-MPAFVVHA.ts.map +7 -0
- package/dist/chunks/sync-state-36H7ULG2.ts +41 -0
- package/dist/chunks/sync-state-36H7ULG2.ts.map +7 -0
- package/dist/chunks/webdav-backend-ZD5RHRIU.ts +869 -0
- package/dist/chunks/webdav-backend-ZD5RHRIU.ts.map +7 -0
- package/dist/index.ts +668 -0
- package/dist/index.ts.map +7 -0
- package/package.json +10 -6
- package/src/state-directory.ts +55 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
|
+
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
|
+
import {
|
|
4
|
+
ensureStateDir,
|
|
5
|
+
lockPath
|
|
6
|
+
} from "./chunk-APZVBSM6.js";
|
|
7
|
+
import {
|
|
8
|
+
LOCKFILE_FS_ADAPTER,
|
|
9
|
+
LOCK_GUARD_STALE_MS,
|
|
10
|
+
LOCK_GUARD_UPDATE_MS
|
|
11
|
+
} from "./chunk-F5QL2GPY.js";
|
|
12
|
+
|
|
13
|
+
// src/lock.ts
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
import fs from "node:fs/promises";
|
|
16
|
+
import lockfile from "proper-lockfile";
|
|
17
|
+
var LOCK_STALE_MS = 30 * 60 * 1e3;
|
|
18
|
+
var MAX_PROCESS_ID = 2147483647;
|
|
19
|
+
async function withLock(command, fn, options = {}) {
|
|
20
|
+
await ensureStateDir();
|
|
21
|
+
const lock = {
|
|
22
|
+
id: randomUUID(),
|
|
23
|
+
pid: process.pid,
|
|
24
|
+
command,
|
|
25
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
26
|
+
};
|
|
27
|
+
let guard;
|
|
28
|
+
let result;
|
|
29
|
+
let failed = false;
|
|
30
|
+
let failure;
|
|
31
|
+
try {
|
|
32
|
+
try {
|
|
33
|
+
guard = await acquireGuard();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (!isLockHeldError(error)) throw error;
|
|
36
|
+
throw await describeHeldLock();
|
|
37
|
+
}
|
|
38
|
+
let inspection = await inspectLock();
|
|
39
|
+
if (inspection.status === "valid" && isStaleLock(inspection.lock)) {
|
|
40
|
+
if (!options.reclaimStale) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`pi-sync lock is stale (pid ${inspection.lock.pid}). Run /sync unlock --stale, then retry.`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
guard.throwIfCompromised();
|
|
46
|
+
const rechecked = await inspectLock();
|
|
47
|
+
if (rechecked.status !== "valid" || rechecked.lock.id !== inspection.lock.id || !isStaleLock(rechecked.lock)) {
|
|
48
|
+
throw new Error("pi-sync lock changed while preparing transaction recovery; retry.");
|
|
49
|
+
}
|
|
50
|
+
await fs.rm(lockPath(), { force: true });
|
|
51
|
+
inspection = { status: "missing" };
|
|
52
|
+
}
|
|
53
|
+
if (inspection.status === "valid") {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`pi-sync is already running (${inspection.lock.command}, pid ${inspection.lock.pid}, started ${inspection.lock.startedAt}).`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (inspection.status === "unreadable") {
|
|
59
|
+
throw new Error(
|
|
60
|
+
"pi-sync lock metadata is unreadable. Run /sync unlock --stale after verifying no sync is running."
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
await fs.writeFile(lockPath(), JSON.stringify(lock, null, " "), { flag: "wx" });
|
|
64
|
+
guard.throwIfCompromised();
|
|
65
|
+
result = await fn();
|
|
66
|
+
guard.throwIfCompromised();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
failed = true;
|
|
69
|
+
failure = error;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const current = await readLock();
|
|
73
|
+
if (current?.id === lock.id) await fs.rm(lockPath(), { force: true });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (!failed) {
|
|
76
|
+
failed = true;
|
|
77
|
+
failure = error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (guard) {
|
|
81
|
+
const releaseError = await releaseGuard(guard);
|
|
82
|
+
if (releaseError && !failed) {
|
|
83
|
+
failed = true;
|
|
84
|
+
failure = releaseError;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (failed) throw failure;
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
async function inspectLock() {
|
|
91
|
+
try {
|
|
92
|
+
const text = await fs.readFile(lockPath(), "utf8");
|
|
93
|
+
if (text.trim().length === 0) return { status: "unreadable" };
|
|
94
|
+
const parsed = JSON.parse(text);
|
|
95
|
+
return isLockFile(parsed) ? { status: "valid", lock: parsed } : { status: "unreadable" };
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error.code === "ENOENT") return { status: "missing" };
|
|
98
|
+
if (error instanceof SyntaxError) return { status: "unreadable" };
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function readLock() {
|
|
103
|
+
const inspection = await inspectLock();
|
|
104
|
+
return inspection.status === "valid" ? inspection.lock : void 0;
|
|
105
|
+
}
|
|
106
|
+
function isLockGuardHeld() {
|
|
107
|
+
return lockfile.check(lockPath(), {
|
|
108
|
+
fs: LOCKFILE_FS_ADAPTER,
|
|
109
|
+
lockfilePath: `${lockPath()}.guard`,
|
|
110
|
+
realpath: false,
|
|
111
|
+
stale: LOCK_GUARD_STALE_MS
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function isStaleLock(lock) {
|
|
115
|
+
try {
|
|
116
|
+
process.kill(lock.pid, 0);
|
|
117
|
+
return false;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error.code === "ESRCH") return true;
|
|
120
|
+
return Date.now() - Date.parse(lock.startedAt) > LOCK_STALE_MS;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function unlock(ctx, options) {
|
|
124
|
+
throwIfAborted(options.signal);
|
|
125
|
+
await ensureStateDir();
|
|
126
|
+
throwIfAborted(options.signal);
|
|
127
|
+
let guard;
|
|
128
|
+
try {
|
|
129
|
+
guard = await acquireGuard();
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (!isLockHeldError(error)) throw error;
|
|
132
|
+
ctx.ui.notify((await describeHeldLock()).message, "warning");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
let failed = false;
|
|
136
|
+
let failure;
|
|
137
|
+
try {
|
|
138
|
+
await unlockGuarded(ctx, options);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
failed = true;
|
|
141
|
+
failure = error;
|
|
142
|
+
}
|
|
143
|
+
const releaseError = await releaseGuard(guard);
|
|
144
|
+
if (releaseError && !failed) {
|
|
145
|
+
failed = true;
|
|
146
|
+
failure = releaseError;
|
|
147
|
+
}
|
|
148
|
+
if (failed) throw failure;
|
|
149
|
+
}
|
|
150
|
+
async function unlockGuarded(ctx, options) {
|
|
151
|
+
throwIfAborted(options.signal);
|
|
152
|
+
let inspection = await inspectLock();
|
|
153
|
+
throwIfAborted(options.signal);
|
|
154
|
+
if (inspection.status === "missing") {
|
|
155
|
+
ctx.ui.notify("No pi-sync lock is present.", "info");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (inspection.status === "unreadable") {
|
|
159
|
+
if (!options.stale) {
|
|
160
|
+
ctx.ui.notify(
|
|
161
|
+
"Pi-sync lock metadata is unreadable. Use /sync unlock --stale only after verifying no sync is running.",
|
|
162
|
+
"warning"
|
|
163
|
+
);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
inspection = await inspectLock();
|
|
167
|
+
throwIfAborted(options.signal);
|
|
168
|
+
if (inspection.status === "unreadable") {
|
|
169
|
+
throwIfAborted(options.signal);
|
|
170
|
+
await fs.rm(lockPath(), { force: true });
|
|
171
|
+
if (!options.signal?.aborted) {
|
|
172
|
+
ctx.ui.notify(
|
|
173
|
+
"Removed unreadable pi-sync lock. No settings, files, sync state, or remote data were changed.",
|
|
174
|
+
"info"
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (inspection.status === "missing") {
|
|
180
|
+
ctx.ui.notify("No pi-sync lock is present.", "info");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (!isStaleLock(inspection.lock)) {
|
|
185
|
+
ctx.ui.notify("Lock owner is still live; refusing to remove it.", "warning");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
throwIfAborted(options.signal);
|
|
189
|
+
await fs.rm(lockPath(), { force: true });
|
|
190
|
+
if (!options.signal?.aborted) {
|
|
191
|
+
ctx.ui.notify(
|
|
192
|
+
"Removed stale pi-sync lock. No settings, files, sync state, or remote data were changed.",
|
|
193
|
+
"info"
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function throwIfAborted(signal) {
|
|
198
|
+
if (!signal?.aborted) return;
|
|
199
|
+
throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
|
|
200
|
+
}
|
|
201
|
+
async function releaseGuard(guard) {
|
|
202
|
+
try {
|
|
203
|
+
await guard.release();
|
|
204
|
+
return void 0;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
return guard.isCompromised() ? void 0 : error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function acquireGuard() {
|
|
210
|
+
let compromisedError;
|
|
211
|
+
const release = await lockfile.lock(lockPath(), {
|
|
212
|
+
fs: LOCKFILE_FS_ADAPTER,
|
|
213
|
+
lockfilePath: `${lockPath()}.guard`,
|
|
214
|
+
realpath: false,
|
|
215
|
+
stale: LOCK_GUARD_STALE_MS,
|
|
216
|
+
update: LOCK_GUARD_UPDATE_MS,
|
|
217
|
+
onCompromised: (error) => {
|
|
218
|
+
compromisedError = error;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
release,
|
|
223
|
+
throwIfCompromised: () => {
|
|
224
|
+
if (compromisedError) throw compromisedError;
|
|
225
|
+
},
|
|
226
|
+
isCompromised: () => compromisedError !== void 0
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
async function describeHeldLock() {
|
|
230
|
+
const current = await readLock();
|
|
231
|
+
if (current && isStaleLock(current)) {
|
|
232
|
+
return new Error("pi-sync lock owner exited; retry shortly while the lock guard expires.");
|
|
233
|
+
}
|
|
234
|
+
if (current) {
|
|
235
|
+
return new Error(
|
|
236
|
+
`Pi-sync is currently running (${current.command}, pid ${current.pid}, started ${current.startedAt}).`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
return new Error(
|
|
240
|
+
"Pi-sync is currently running (lock metadata is unreadable or still being written)."
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
function isLockHeldError(error) {
|
|
244
|
+
return error.code === "ELOCKED";
|
|
245
|
+
}
|
|
246
|
+
function isLockFile(value) {
|
|
247
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
248
|
+
const lock = value;
|
|
249
|
+
return typeof lock.id === "string" && lock.id.length > 0 && Number.isInteger(lock.pid) && (lock.pid ?? 0) > 0 && (lock.pid ?? 0) <= MAX_PROCESS_ID && typeof lock.command === "string" && lock.command.length > 0 && typeof lock.startedAt === "string" && Number.isFinite(Date.parse(lock.startedAt));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export {
|
|
253
|
+
withLock,
|
|
254
|
+
inspectLock,
|
|
255
|
+
isLockGuardHeld,
|
|
256
|
+
isStaleLock,
|
|
257
|
+
unlock
|
|
258
|
+
};
|
|
259
|
+
//# sourceMappingURL=chunk-LJDSHNS3.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lock.ts"],
|
|
4
|
+
"sourcesContent": ["import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport type { ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport lockfile from \"proper-lockfile\";\nimport { ensureStateDir, lockPath } from \"./config.js\";\nimport { LOCK_GUARD_STALE_MS, LOCK_GUARD_UPDATE_MS } from \"./lock-policy.js\";\nimport { LOCKFILE_FS_ADAPTER } from \"./lockfile-fs.js\";\nimport type { CommandOptions, LockFile } from \"./types.js\";\n\nconst LOCK_STALE_MS = 30 * 60 * 1000;\nconst MAX_PROCESS_ID = 2_147_483_647;\n\ninterface Guard {\n\trelease: () => Promise<void>;\n\tthrowIfCompromised: () => void;\n\tisCompromised: () => boolean;\n}\n\nexport type LockInspection =\n\t| { status: \"missing\" }\n\t| { status: \"unreadable\" }\n\t| { status: \"valid\"; lock: LockFile };\n\nexport async function withLock<T>(\n\tcommand: string,\n\tfn: () => Promise<T>,\n\toptions: { reclaimStale?: boolean } = {},\n): Promise<T> {\n\tawait ensureStateDir();\n\tconst lock: LockFile = {\n\t\tid: randomUUID(),\n\t\tpid: process.pid,\n\t\tcommand,\n\t\tstartedAt: new Date().toISOString(),\n\t};\n\tlet guard: Guard | undefined;\n\tlet result: T | undefined;\n\tlet failed = false;\n\tlet failure: unknown;\n\ttry {\n\t\ttry {\n\t\t\tguard = await acquireGuard();\n\t\t} catch (error) {\n\t\t\tif (!isLockHeldError(error)) throw error;\n\t\t\tthrow await describeHeldLock();\n\t\t}\n\n\t\tlet inspection = await inspectLock();\n\t\tif (inspection.status === \"valid\" && isStaleLock(inspection.lock)) {\n\t\t\tif (!options.reclaimStale) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`pi-sync lock is stale (pid ${inspection.lock.pid}). Run /sync unlock --stale, then retry.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tguard.throwIfCompromised();\n\t\t\tconst rechecked = await inspectLock();\n\t\t\tif (\n\t\t\t\trechecked.status !== \"valid\" ||\n\t\t\t\trechecked.lock.id !== inspection.lock.id ||\n\t\t\t\t!isStaleLock(rechecked.lock)\n\t\t\t) {\n\t\t\t\tthrow new Error(\"pi-sync lock changed while preparing transaction recovery; retry.\");\n\t\t\t}\n\t\t\tawait fs.rm(lockPath(), { force: true });\n\t\t\tinspection = { status: \"missing\" };\n\t\t}\n\t\tif (inspection.status === \"valid\") {\n\t\t\tthrow new Error(\n\t\t\t\t`pi-sync is already running (${inspection.lock.command}, pid ${inspection.lock.pid}, started ${inspection.lock.startedAt}).`,\n\t\t\t);\n\t\t}\n\t\tif (inspection.status === \"unreadable\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"pi-sync lock metadata is unreadable. Run /sync unlock --stale after verifying no sync is running.\",\n\t\t\t);\n\t\t}\n\n\t\tawait fs.writeFile(lockPath(), JSON.stringify(lock, null, \"\\t\"), { flag: \"wx\" });\n\t\tguard.throwIfCompromised();\n\t\tresult = await fn();\n\t\tguard.throwIfCompromised();\n\t} catch (error) {\n\t\tfailed = true;\n\t\tfailure = error;\n\t}\n\n\ttry {\n\t\tconst current = await readLock();\n\t\tif (current?.id === lock.id) await fs.rm(lockPath(), { force: true });\n\t} catch (error) {\n\t\tif (!failed) {\n\t\t\tfailed = true;\n\t\t\tfailure = error;\n\t\t}\n\t}\n\tif (guard) {\n\t\tconst releaseError = await releaseGuard(guard);\n\t\tif (releaseError && !failed) {\n\t\t\tfailed = true;\n\t\t\tfailure = releaseError;\n\t\t}\n\t}\n\tif (failed) throw failure;\n\treturn result as T;\n}\n\nexport async function inspectLock(): Promise<LockInspection> {\n\ttry {\n\t\tconst text = await fs.readFile(lockPath(), \"utf8\");\n\t\tif (text.trim().length === 0) return { status: \"unreadable\" };\n\t\tconst parsed = JSON.parse(text) as unknown;\n\t\treturn isLockFile(parsed) ? { status: \"valid\", lock: parsed } : { status: \"unreadable\" };\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { status: \"missing\" };\n\t\tif (error instanceof SyntaxError) return { status: \"unreadable\" };\n\t\tthrow error;\n\t}\n}\n\nexport async function readLock(): Promise<LockFile | undefined> {\n\tconst inspection = await inspectLock();\n\treturn inspection.status === \"valid\" ? inspection.lock : undefined;\n}\n\nexport async function lockFileExists(): Promise<boolean> {\n\ttry {\n\t\tawait fs.stat(lockPath());\n\t\treturn true;\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n\t\tthrow error;\n\t}\n}\n\nexport function isLockGuardHeld() {\n\treturn lockfile.check(lockPath(), {\n\t\tfs: LOCKFILE_FS_ADAPTER,\n\t\tlockfilePath: `${lockPath()}.guard`,\n\t\trealpath: false,\n\t\tstale: LOCK_GUARD_STALE_MS,\n\t});\n}\n\nexport function isStaleLock(lock: LockFile) {\n\ttry {\n\t\tprocess.kill(lock.pid, 0);\n\t\treturn false;\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code === \"ESRCH\") return true;\n\t\treturn Date.now() - Date.parse(lock.startedAt) > LOCK_STALE_MS;\n\t}\n}\n\nexport async function unlock(ctx: ExtensionCommandContext, options: CommandOptions) {\n\tthrowIfAborted(options.signal);\n\tawait ensureStateDir();\n\tthrowIfAborted(options.signal);\n\tlet guard: Guard;\n\ttry {\n\t\tguard = await acquireGuard();\n\t} catch (error) {\n\t\tif (!isLockHeldError(error)) throw error;\n\t\tctx.ui.notify((await describeHeldLock()).message, \"warning\");\n\t\treturn;\n\t}\n\n\tlet failed = false;\n\tlet failure: unknown;\n\ttry {\n\t\tawait unlockGuarded(ctx, options);\n\t} catch (error) {\n\t\tfailed = true;\n\t\tfailure = error;\n\t}\n\tconst releaseError = await releaseGuard(guard);\n\tif (releaseError && !failed) {\n\t\tfailed = true;\n\t\tfailure = releaseError;\n\t}\n\tif (failed) throw failure;\n}\n\nasync function unlockGuarded(ctx: ExtensionCommandContext, options: CommandOptions) {\n\tthrowIfAborted(options.signal);\n\tlet inspection = await inspectLock();\n\tthrowIfAborted(options.signal);\n\tif (inspection.status === \"missing\") {\n\t\tctx.ui.notify(\"No pi-sync lock is present.\", \"info\");\n\t\treturn;\n\t}\n\tif (inspection.status === \"unreadable\") {\n\t\tif (!options.stale) {\n\t\t\tctx.ui.notify(\n\t\t\t\t\"Pi-sync lock metadata is unreadable. Use /sync unlock --stale only after verifying no sync is running.\",\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tinspection = await inspectLock();\n\t\tthrowIfAborted(options.signal);\n\t\tif (inspection.status === \"unreadable\") {\n\t\t\t// Legacy writers expose an empty file before writing owner metadata, so no\n\t\t\t// automatic test can prove this file is abandoned. The explicit --stale\n\t\t\t// flag is the user's confirmation that no legacy sync is still running.\n\t\t\tthrowIfAborted(options.signal);\n\t\t\tawait fs.rm(lockPath(), { force: true });\n\t\t\tif (!options.signal?.aborted) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\"Removed unreadable pi-sync lock. No settings, files, sync state, or remote data were changed.\",\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (inspection.status === \"missing\") {\n\t\t\tctx.ui.notify(\"No pi-sync lock is present.\", \"info\");\n\t\t\treturn;\n\t\t}\n\t}\n\tif (!isStaleLock(inspection.lock)) {\n\t\tctx.ui.notify(\"Lock owner is still live; refusing to remove it.\", \"warning\");\n\t\treturn;\n\t}\n\tthrowIfAborted(options.signal);\n\tawait fs.rm(lockPath(), { force: true });\n\tif (!options.signal?.aborted) {\n\t\tctx.ui.notify(\n\t\t\t\"Removed stale pi-sync lock. No settings, files, sync state, or remote data were changed.\",\n\t\t\t\"info\",\n\t\t);\n\t}\n}\n\nfunction throwIfAborted(signal?: AbortSignal) {\n\tif (!signal?.aborted) return;\n\tthrow signal.reason instanceof Error\n\t\t? signal.reason\n\t\t: new DOMException(\"The operation was aborted\", \"AbortError\");\n}\n\nasync function releaseGuard(guard: Guard) {\n\ttry {\n\t\tawait guard.release();\n\t\treturn undefined;\n\t} catch (error) {\n\t\treturn guard.isCompromised() ? undefined : error;\n\t}\n}\n\nasync function acquireGuard(): Promise<Guard> {\n\tlet compromisedError: Error | undefined;\n\tconst release = await lockfile.lock(lockPath(), {\n\t\tfs: LOCKFILE_FS_ADAPTER,\n\t\tlockfilePath: `${lockPath()}.guard`,\n\t\trealpath: false,\n\t\tstale: LOCK_GUARD_STALE_MS,\n\t\tupdate: LOCK_GUARD_UPDATE_MS,\n\t\tonCompromised: (error) => {\n\t\t\tcompromisedError = error;\n\t\t},\n\t});\n\treturn {\n\t\trelease,\n\t\tthrowIfCompromised: () => {\n\t\t\tif (compromisedError) throw compromisedError;\n\t\t},\n\t\tisCompromised: () => compromisedError !== undefined,\n\t};\n}\n\nasync function describeHeldLock() {\n\tconst current = await readLock();\n\tif (current && isStaleLock(current)) {\n\t\treturn new Error(\"pi-sync lock owner exited; retry shortly while the lock guard expires.\");\n\t}\n\tif (current) {\n\t\treturn new Error(\n\t\t\t`Pi-sync is currently running (${current.command}, pid ${current.pid}, started ${current.startedAt}).`,\n\t\t);\n\t}\n\treturn new Error(\n\t\t\"Pi-sync is currently running (lock metadata is unreadable or still being written).\",\n\t);\n}\n\nfunction isLockHeldError(error: unknown) {\n\treturn (error as NodeJS.ErrnoException).code === \"ELOCKED\";\n}\n\nfunction isLockFile(value: unknown): value is LockFile {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst lock = value as Partial<LockFile>;\n\treturn (\n\t\ttypeof lock.id === \"string\" &&\n\t\tlock.id.length > 0 &&\n\t\tNumber.isInteger(lock.pid) &&\n\t\t(lock.pid ?? 0) > 0 &&\n\t\t(lock.pid ?? 0) <= MAX_PROCESS_ID &&\n\t\ttypeof lock.command === \"string\" &&\n\t\tlock.command.length > 0 &&\n\t\ttypeof lock.startedAt === \"string\" &&\n\t\tNumber.isFinite(Date.parse(lock.startedAt))\n\t);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
|
+
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
|
+
import {
|
|
4
|
+
filterSnapshotForConfigPolicy,
|
|
5
|
+
isSessionPath
|
|
6
|
+
} from "./chunk-EWX3TPLQ.js";
|
|
7
|
+
import {
|
|
8
|
+
canonicalSnapshotPathForConfig,
|
|
9
|
+
customIncludePathsByLower,
|
|
10
|
+
includeFromSelectionConfig,
|
|
11
|
+
isConfiguredSnapshotPath,
|
|
12
|
+
normalizeSyncInclude,
|
|
13
|
+
toPosix
|
|
14
|
+
} from "./chunk-YQ6UW7IF.js";
|
|
15
|
+
|
|
16
|
+
// src/sync-state.ts
|
|
17
|
+
function hasLocalChanges(local, state, config) {
|
|
18
|
+
return !sameHashes(fileHashMap(local), stateHashMapForConfig(state, config));
|
|
19
|
+
}
|
|
20
|
+
function remoteChangedSinceState(head, state, config, sameRevision) {
|
|
21
|
+
if (!head) return Boolean(state.lastAppliedSnapshot);
|
|
22
|
+
if (head.snapshotId !== state.lastAppliedSnapshot) return true;
|
|
23
|
+
if (state.lastRemoteRevision && !sameRevision(head.revision, state.lastRemoteRevision))
|
|
24
|
+
return true;
|
|
25
|
+
if (syncIncludeChanged(state, config)) return true;
|
|
26
|
+
return includeFromSelectionConfig(config).includes("sessions") && !state.include?.includes("sessions") && head.syncSessions;
|
|
27
|
+
}
|
|
28
|
+
function hasRemoteChanges(remote, state, config, ignoredPaths = /* @__PURE__ */ new Set()) {
|
|
29
|
+
if (remote.id === state.lastAppliedSnapshot && !syncPolicyChanged(state, config)) return false;
|
|
30
|
+
return !snapshotHashesMatchState(
|
|
31
|
+
filterSnapshotForConfigPolicy(remote, config),
|
|
32
|
+
state,
|
|
33
|
+
config,
|
|
34
|
+
ignoredPaths
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function sameHashes(left, right) {
|
|
38
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
39
|
+
for (const key of keys) if (left[key] !== right[key]) return false;
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
function fileHashMap(snapshot) {
|
|
43
|
+
return Object.fromEntries(snapshot.files.map((file) => [file.path, file.sha256]));
|
|
44
|
+
}
|
|
45
|
+
function stateHashMapForConfig(state, config) {
|
|
46
|
+
const includePaths = customIncludePathsByLower(includeFromSelectionConfig(config));
|
|
47
|
+
return Object.fromEntries(
|
|
48
|
+
Object.entries(state.lastFileHashes).filter(([filePath]) => isConfiguredSnapshotPath(filePath, config)).map(([filePath, hash]) => [canonicalSnapshotPathForConfig(filePath, includePaths), hash])
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function snapshotHashesMatchState(snapshot, state, config, ignoredPaths = /* @__PURE__ */ new Set()) {
|
|
52
|
+
return sameHashes(
|
|
53
|
+
withoutHashPaths(fileHashMap(snapshot), ignoredPaths),
|
|
54
|
+
withoutHashPaths(stateHashMapForConfig(state, config), ignoredPaths)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
function snapshotsMatch(left, right) {
|
|
58
|
+
return left.syncSessions === right.syncSessions && sameHashes(fileHashMap(left), fileHashMap(right));
|
|
59
|
+
}
|
|
60
|
+
function withoutHashPaths(hashes, ignoredPaths) {
|
|
61
|
+
if (ignoredPaths.size === 0) return hashes;
|
|
62
|
+
return Object.fromEntries(
|
|
63
|
+
Object.entries(hashes).filter(([filePath]) => !ignoredPaths.has(toPosix(filePath)))
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
function syncPolicyChanged(state, config) {
|
|
67
|
+
return syncIncludeChanged(state, config);
|
|
68
|
+
}
|
|
69
|
+
function shouldRefreshSyncedState(remote, head, state, config, sameRevision) {
|
|
70
|
+
return remote.id !== state.lastAppliedSnapshot || Boolean(
|
|
71
|
+
head && (!state.lastRemoteRevision || !sameRevision(head.revision, state.lastRemoteRevision))
|
|
72
|
+
) || syncPolicyChanged(state, config);
|
|
73
|
+
}
|
|
74
|
+
function syncIncludeChanged(state, config) {
|
|
75
|
+
const stored = state.include ? normalizeSyncInclude(state.include) : includeFromSelectionConfig(state);
|
|
76
|
+
const current = includeFromSelectionConfig(config);
|
|
77
|
+
return stored.length !== current.length || stored.some((item, index) => item !== current[index]);
|
|
78
|
+
}
|
|
79
|
+
function settingsHashMap(snapshot) {
|
|
80
|
+
return Object.fromEntries(
|
|
81
|
+
snapshot.files.filter((file) => !isSessionPath(file.path)).map((file) => [file.path, file.sha256])
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
function sessionHashMap(snapshot) {
|
|
85
|
+
return Object.fromEntries(
|
|
86
|
+
snapshot.files.filter((file) => isSessionPath(file.path)).map((file) => [file.path, file.sha256])
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
function settingsHashMapFromState(state) {
|
|
90
|
+
return Object.fromEntries(
|
|
91
|
+
Object.entries(state.lastFileHashes).filter(([filePath]) => !isSessionPath(filePath))
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
function settingsHashesMatchState(remote, state) {
|
|
95
|
+
return sameHashes(settingsHashMap(remote), settingsHashMapFromState(state));
|
|
96
|
+
}
|
|
97
|
+
function canPullRemoteSettingsOnFirstSync(local, remote) {
|
|
98
|
+
const remoteSettings = settingsHashMap(remote);
|
|
99
|
+
return Object.entries(settingsHashMap(local)).every(
|
|
100
|
+
([filePath, hash]) => remoteSettings[filePath] === hash
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
function canPullRemoteSessionsOnFirstSync(local, remote) {
|
|
104
|
+
const localSessions = sessionHashMap(local);
|
|
105
|
+
const remoteSessions = sessionHashMap(remote);
|
|
106
|
+
return Object.entries(localSessions).every(
|
|
107
|
+
([filePath, hash]) => remoteSessions[filePath] === hash
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export {
|
|
112
|
+
hasLocalChanges,
|
|
113
|
+
remoteChangedSinceState,
|
|
114
|
+
hasRemoteChanges,
|
|
115
|
+
sameHashes,
|
|
116
|
+
fileHashMap,
|
|
117
|
+
snapshotHashesMatchState,
|
|
118
|
+
snapshotsMatch,
|
|
119
|
+
syncPolicyChanged,
|
|
120
|
+
shouldRefreshSyncedState,
|
|
121
|
+
settingsHashMap,
|
|
122
|
+
sessionHashMap,
|
|
123
|
+
settingsHashMapFromState,
|
|
124
|
+
settingsHashesMatchState,
|
|
125
|
+
canPullRemoteSettingsOnFirstSync,
|
|
126
|
+
canPullRemoteSessionsOnFirstSync
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=chunk-RGJ6SOYR.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/sync-state.ts"],
|
|
4
|
+
"sourcesContent": ["import { toPosix } from \"./paths.js\";\nimport {\n\tcanonicalSnapshotPathForConfig,\n\tfilterSnapshotForConfigPolicy,\n\tisConfiguredSnapshotPath,\n\tisSessionPath,\n} from \"./snapshot.js\";\nimport type { RemoteHead } from \"./sync-backend.js\";\nimport {\n\tcustomIncludePathsByLower,\n\tincludeFromSelectionConfig,\n\tnormalizeSyncInclude,\n\ttype SyncSelectionConfig,\n} from \"./sync-policy.js\";\nimport type { Snapshot, SyncState } from \"./types.js\";\n\ntype SyncPolicyConfig = SyncSelectionConfig;\n\nexport function hasLocalChanges(local: Snapshot, state: SyncState, config: SyncPolicyConfig) {\n\treturn !sameHashes(fileHashMap(local), stateHashMapForConfig(state, config));\n}\n\nexport function remoteChangedSinceState(\n\thead: RemoteHead | undefined,\n\tstate: SyncState,\n\tconfig: SyncPolicyConfig,\n\tsameRevision: (left: string, right: string) => boolean,\n) {\n\tif (!head) return Boolean(state.lastAppliedSnapshot);\n\tif (head.snapshotId !== state.lastAppliedSnapshot) return true;\n\tif (state.lastRemoteRevision && !sameRevision(head.revision, state.lastRemoteRevision))\n\t\treturn true;\n\tif (syncIncludeChanged(state, config)) return true;\n\treturn (\n\t\tincludeFromSelectionConfig(config).includes(\"sessions\") &&\n\t\t!state.include?.includes(\"sessions\") &&\n\t\thead.syncSessions\n\t);\n}\n\nexport function hasRemoteChanges(\n\tremote: Snapshot,\n\tstate: SyncState,\n\tconfig: SyncPolicyConfig,\n\tignoredPaths = new Set<string>(),\n) {\n\tif (remote.id === state.lastAppliedSnapshot && !syncPolicyChanged(state, config)) return false;\n\treturn !snapshotHashesMatchState(\n\t\tfilterSnapshotForConfigPolicy(remote, config),\n\t\tstate,\n\t\tconfig,\n\t\tignoredPaths,\n\t);\n}\n\nexport function sameHashes(left: Record<string, string>, right: Record<string, string>) {\n\tconst keys = new Set([...Object.keys(left), ...Object.keys(right)]);\n\tfor (const key of keys) if (left[key] !== right[key]) return false;\n\treturn true;\n}\n\nexport function fileHashMap(snapshot: Snapshot) {\n\treturn Object.fromEntries(snapshot.files.map((file) => [file.path, file.sha256]));\n}\n\nfunction stateHashMapForConfig(state: SyncState, config: SyncPolicyConfig) {\n\tconst includePaths = customIncludePathsByLower(includeFromSelectionConfig(config));\n\treturn Object.fromEntries(\n\t\tObject.entries(state.lastFileHashes)\n\t\t\t.filter(([filePath]) => isConfiguredSnapshotPath(filePath, config))\n\t\t\t.map(([filePath, hash]) => [canonicalSnapshotPathForConfig(filePath, includePaths), hash]),\n\t);\n}\n\nexport function snapshotHashesMatchState(\n\tsnapshot: Snapshot,\n\tstate: SyncState,\n\tconfig: SyncPolicyConfig,\n\tignoredPaths = new Set<string>(),\n) {\n\treturn sameHashes(\n\t\twithoutHashPaths(fileHashMap(snapshot), ignoredPaths),\n\t\twithoutHashPaths(stateHashMapForConfig(state, config), ignoredPaths),\n\t);\n}\n\nexport function snapshotsMatch(left: Snapshot, right: Snapshot) {\n\treturn (\n\t\tleft.syncSessions === right.syncSessions && sameHashes(fileHashMap(left), fileHashMap(right))\n\t);\n}\n\nfunction withoutHashPaths(hashes: Record<string, string>, ignoredPaths: Set<string>) {\n\tif (ignoredPaths.size === 0) return hashes;\n\treturn Object.fromEntries(\n\t\tObject.entries(hashes).filter(([filePath]) => !ignoredPaths.has(toPosix(filePath))),\n\t);\n}\n\nexport function syncPolicyChanged(state: SyncState, config: SyncPolicyConfig) {\n\treturn syncIncludeChanged(state, config);\n}\n\nexport function shouldRefreshSyncedState(\n\tremote: Snapshot,\n\thead: RemoteHead | undefined,\n\tstate: SyncState,\n\tconfig: SyncPolicyConfig,\n\tsameRevision: (left: string, right: string) => boolean,\n) {\n\treturn (\n\t\tremote.id !== state.lastAppliedSnapshot ||\n\t\tBoolean(\n\t\t\thead && (!state.lastRemoteRevision || !sameRevision(head.revision, state.lastRemoteRevision)),\n\t\t) ||\n\t\tsyncPolicyChanged(state, config)\n\t);\n}\n\nfunction syncIncludeChanged(state: SyncState, config: SyncPolicyConfig) {\n\tconst stored = state.include\n\t\t? normalizeSyncInclude(state.include)\n\t\t: includeFromSelectionConfig(state);\n\tconst current = includeFromSelectionConfig(config);\n\treturn stored.length !== current.length || stored.some((item, index) => item !== current[index]);\n}\n\nexport function settingsHashMap(snapshot: Snapshot) {\n\treturn Object.fromEntries(\n\t\tsnapshot.files\n\t\t\t.filter((file) => !isSessionPath(file.path))\n\t\t\t.map((file) => [file.path, file.sha256]),\n\t);\n}\n\nexport function sessionHashMap(snapshot: Snapshot) {\n\treturn Object.fromEntries(\n\t\tsnapshot.files\n\t\t\t.filter((file) => isSessionPath(file.path))\n\t\t\t.map((file) => [file.path, file.sha256]),\n\t);\n}\n\nexport function settingsHashMapFromState(state: SyncState) {\n\treturn Object.fromEntries(\n\t\tObject.entries(state.lastFileHashes).filter(([filePath]) => !isSessionPath(filePath)),\n\t);\n}\n\nexport function settingsHashesMatchState(remote: Snapshot, state: SyncState) {\n\treturn sameHashes(settingsHashMap(remote), settingsHashMapFromState(state));\n}\n\nexport function canPullRemoteSettingsOnFirstSync(local: Snapshot, remote: Snapshot) {\n\tconst remoteSettings = settingsHashMap(remote);\n\treturn Object.entries(settingsHashMap(local)).every(\n\t\t([filePath, hash]) => remoteSettings[filePath] === hash,\n\t);\n}\n\nexport function canPullRemoteSessionsOnFirstSync(local: Snapshot, remote: Snapshot) {\n\tconst localSessions = sessionHashMap(local);\n\tconst remoteSessions = sessionHashMap(remote);\n\treturn Object.entries(localSessions).every(\n\t\t([filePath, hash]) => remoteSessions[filePath] === hash,\n\t);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
|
+
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
|
+
import {
|
|
4
|
+
fileHashMap
|
|
5
|
+
} from "./chunk-RGJ6SOYR.js";
|
|
6
|
+
import {
|
|
7
|
+
agentDir
|
|
8
|
+
} from "./chunk-APZVBSM6.js";
|
|
9
|
+
import {
|
|
10
|
+
inspectRemoteSelection
|
|
11
|
+
} from "./chunk-YQ6UW7IF.js";
|
|
12
|
+
|
|
13
|
+
// src/sync-errors.ts
|
|
14
|
+
var SetupPullRequiresUiError = class extends Error {
|
|
15
|
+
};
|
|
16
|
+
var SyncDecisionRequiredError = class extends Error {
|
|
17
|
+
decision;
|
|
18
|
+
constructor(decision) {
|
|
19
|
+
super(decision.directMessage);
|
|
20
|
+
this.name = "SyncDecisionRequiredError";
|
|
21
|
+
this.decision = decision;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
function isSyncDecisionRequiredError(error) {
|
|
25
|
+
return error instanceof SyncDecisionRequiredError;
|
|
26
|
+
}
|
|
27
|
+
function errorMessage(error) {
|
|
28
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
+
return message.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/sync-format.ts
|
|
33
|
+
function formatDiff(local, remote) {
|
|
34
|
+
const localMap = fileHashMap(local);
|
|
35
|
+
const remoteMap = fileHashMap(remote);
|
|
36
|
+
const allPaths = [.../* @__PURE__ */ new Set([...Object.keys(localMap), ...Object.keys(remoteMap)])].sort();
|
|
37
|
+
const lines = [
|
|
38
|
+
`local: ${local.files.length} files`,
|
|
39
|
+
`remote: ${remote.id} (${remote.files.length} files)`,
|
|
40
|
+
""
|
|
41
|
+
];
|
|
42
|
+
let changed = 0;
|
|
43
|
+
for (const filePath of allPaths) {
|
|
44
|
+
if (!localMap[filePath]) {
|
|
45
|
+
lines.push(`Remote only: ${filePath}`);
|
|
46
|
+
changed += 1;
|
|
47
|
+
} else if (!remoteMap[filePath]) {
|
|
48
|
+
lines.push(`Local only: ${filePath}`);
|
|
49
|
+
changed += 1;
|
|
50
|
+
} else if (localMap[filePath] !== remoteMap[filePath]) {
|
|
51
|
+
lines.push(`Different: ${filePath}`);
|
|
52
|
+
changed += 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (changed === 0) lines.push("No file differences.");
|
|
56
|
+
return lines.join("\n");
|
|
57
|
+
}
|
|
58
|
+
function formatSnapshotOnlyDiff(title, snapshot) {
|
|
59
|
+
return [`${title}: ${snapshot.id}`, ...snapshot.files.map((file) => `Add: ${file.path}`)].join(
|
|
60
|
+
"\n"
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
function formatPushSummary(config, destination, upload, head, preservedRemoteFileCount = 0, remote) {
|
|
64
|
+
return [
|
|
65
|
+
`Sync setup: ${safeTerminalText(config.setupName)}`,
|
|
66
|
+
`Storage location: ${safeTerminalText(destination)}`,
|
|
67
|
+
`Upload ${upload.files.length} files from ${safeTerminalText(agentDir())}.`,
|
|
68
|
+
`Sessions: ${upload.syncSessions ? "included \u2014 may contain private conversations" : "not included"}`,
|
|
69
|
+
head ? `Remote latest: ${head.snapshotId}` : "Remote latest: empty",
|
|
70
|
+
"Publication effect: the backend's active head will reference the new immutable snapshot.",
|
|
71
|
+
...remote && inspectRemoteSelection(config.include, remote).kind === "different" ? [
|
|
72
|
+
"Included-content policy effect: replace the differing remote selection with this setup's local selection."
|
|
73
|
+
] : [],
|
|
74
|
+
formatPublicationPreview(remote, upload),
|
|
75
|
+
preservedRemoteFileCount > 0 ? `Possible secrets in locally managed files were scanned before this prompt; ${preservedRemoteFileCount} preserved remote file(s) were not rescanned.` : "Possible secrets were scanned before this prompt."
|
|
76
|
+
].join("\n");
|
|
77
|
+
}
|
|
78
|
+
function formatApplyPreview(local, remote) {
|
|
79
|
+
return formatDirectionalChanges(local, remote, {
|
|
80
|
+
add: "Add locally",
|
|
81
|
+
update: "Update locally",
|
|
82
|
+
remove: "Remove locally"
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function formatPullSummary(config, destination, local, remote, protectedSessionCount) {
|
|
86
|
+
return [
|
|
87
|
+
`Sync setup: ${safeTerminalText(config.setupName)}`,
|
|
88
|
+
`Storage location: ${safeTerminalText(destination)}`,
|
|
89
|
+
`Snapshot: ${safeTerminalText(remote.id)}`,
|
|
90
|
+
`Sessions: ${remote.syncSessions ? "included \u2014 may contain private conversations" : "not included"}`,
|
|
91
|
+
`Protected live sessions: ${protectedSessionCount || "none"}`,
|
|
92
|
+
formatApplyPreview(local, remote),
|
|
93
|
+
"A local backup is created before these writes/deletes. The remote active snapshot is unchanged."
|
|
94
|
+
].join("\n");
|
|
95
|
+
}
|
|
96
|
+
function formatRollbackSummary(config, destination, local, remote, requestedSnapshot, protectedSessionCount) {
|
|
97
|
+
return [
|
|
98
|
+
`Sync setup: ${safeTerminalText(config.setupName)}`,
|
|
99
|
+
`Storage location: ${safeTerminalText(destination)}`,
|
|
100
|
+
`Snapshot: ${safeTerminalText(requestedSnapshot)}`,
|
|
101
|
+
`Sessions: ${remote.syncSessions ? "included \u2014 may contain private conversations" : "not included"}`,
|
|
102
|
+
`Protected live sessions: ${protectedSessionCount || "none"}`,
|
|
103
|
+
formatApplyPreview(local, remote),
|
|
104
|
+
"A local backup is created before applying; the backend's active head will change."
|
|
105
|
+
].join("\n");
|
|
106
|
+
}
|
|
107
|
+
function formatPublicationPreview(remote, upload) {
|
|
108
|
+
if (!remote) {
|
|
109
|
+
return ["Remote is empty.", ...upload.files.map((file) => `Add remotely: ${file.path}`)].join(
|
|
110
|
+
"\n"
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return formatDirectionalChanges(remote, upload, {
|
|
114
|
+
add: "Add remotely",
|
|
115
|
+
update: "Update remotely",
|
|
116
|
+
remove: "Remove remotely"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function formatDirectionalChanges(before, after, labels) {
|
|
120
|
+
const beforeMap = fileHashMap(before);
|
|
121
|
+
const afterMap = fileHashMap(after);
|
|
122
|
+
const paths = [.../* @__PURE__ */ new Set([...Object.keys(beforeMap), ...Object.keys(afterMap)])].sort();
|
|
123
|
+
const lines = [];
|
|
124
|
+
for (const filePath of paths) {
|
|
125
|
+
if (!beforeMap[filePath]) lines.push(`${labels.add}: ${filePath}`);
|
|
126
|
+
else if (!afterMap[filePath]) lines.push(`${labels.remove}: ${filePath}`);
|
|
127
|
+
else if (beforeMap[filePath] !== afterMap[filePath])
|
|
128
|
+
lines.push(`${labels.update}: ${filePath}`);
|
|
129
|
+
}
|
|
130
|
+
if (lines.length === 0) lines.push("No file changes.");
|
|
131
|
+
return lines.join("\n");
|
|
132
|
+
}
|
|
133
|
+
function countPreservedRemoteFiles(local, upload) {
|
|
134
|
+
const localPaths = new Set(local.files.map((file) => file.path));
|
|
135
|
+
return upload.files.filter((file) => !localPaths.has(file.path)).length;
|
|
136
|
+
}
|
|
137
|
+
function publicationCapabilityDescription(capability) {
|
|
138
|
+
switch (capability) {
|
|
139
|
+
case "lease-protected":
|
|
140
|
+
return "lease-protected (exact expected-ref update)";
|
|
141
|
+
case "atomic-conditional":
|
|
142
|
+
return "atomic-conditional (verified atomic precondition)";
|
|
143
|
+
case "conditional-required":
|
|
144
|
+
return "conditional-required (read-only until atomic preconditions are verified)";
|
|
145
|
+
case "read-check-write-verify":
|
|
146
|
+
return "read-check-write-verify (visible races rejected; simultaneous unseen races remain possible)";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function safeTerminalText(value) {
|
|
150
|
+
return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export {
|
|
154
|
+
SetupPullRequiresUiError,
|
|
155
|
+
SyncDecisionRequiredError,
|
|
156
|
+
isSyncDecisionRequiredError,
|
|
157
|
+
errorMessage,
|
|
158
|
+
formatDiff,
|
|
159
|
+
formatSnapshotOnlyDiff,
|
|
160
|
+
formatPushSummary,
|
|
161
|
+
formatPullSummary,
|
|
162
|
+
formatRollbackSummary,
|
|
163
|
+
countPreservedRemoteFiles,
|
|
164
|
+
publicationCapabilityDescription,
|
|
165
|
+
safeTerminalText
|
|
166
|
+
};
|
|
167
|
+
//# sourceMappingURL=chunk-W64NEHXT.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/sync-errors.ts", "../../src/sync-format.ts"],
|
|
4
|
+
"sourcesContent": ["import type { SyncDecision } from \"./sync-decision.js\";\n\nexport class SetupPullRequiresUiError extends Error {}\n\nexport class SyncDecisionRequiredError extends Error {\n\treadonly decision: SyncDecision;\n\n\tconstructor(decision: SyncDecision) {\n\t\tsuper(decision.directMessage);\n\t\tthis.name = \"SyncDecisionRequiredError\";\n\t\tthis.decision = decision;\n\t}\n}\n\nexport function isSyncDecisionRequiredError(error: unknown): error is SyncDecisionRequiredError {\n\treturn error instanceof SyncDecisionRequiredError;\n}\n\nexport function errorMessage(error: unknown) {\n\tconst message = error instanceof Error ? error.message : String(error);\n\t// biome-ignore lint/suspicious/noControlCharactersInRegex: Escape untrusted terminal controls.\n\treturn message.replace(/[\\u0000-\\u001f\\u007f-\\u009f]/gu, \"?\");\n}\n", "import { agentDir } from \"./config.js\";\n\nexport { errorMessage } from \"./sync-errors.js\";\n\nimport type { PublicationCapability, RemoteHead } from \"./sync-backend.js\";\nimport { inspectRemoteSelection } from \"./sync-policy.js\";\nimport { fileHashMap } from \"./sync-state.js\";\nimport type { CommonSyncConfig, Snapshot } from \"./types.js\";\n\nexport function formatDiff(local: Snapshot, remote: Snapshot) {\n\tconst localMap = fileHashMap(local);\n\tconst remoteMap = fileHashMap(remote);\n\tconst allPaths = [...new Set([...Object.keys(localMap), ...Object.keys(remoteMap)])].sort();\n\tconst lines = [\n\t\t`local: ${local.files.length} files`,\n\t\t`remote: ${remote.id} (${remote.files.length} files)`,\n\t\t\"\",\n\t];\n\tlet changed = 0;\n\tfor (const filePath of allPaths) {\n\t\tif (!localMap[filePath]) {\n\t\t\tlines.push(`Remote only: ${filePath}`);\n\t\t\tchanged += 1;\n\t\t} else if (!remoteMap[filePath]) {\n\t\t\tlines.push(`Local only: ${filePath}`);\n\t\t\tchanged += 1;\n\t\t} else if (localMap[filePath] !== remoteMap[filePath]) {\n\t\t\tlines.push(`Different: ${filePath}`);\n\t\t\tchanged += 1;\n\t\t}\n\t}\n\tif (changed === 0) lines.push(\"No file differences.\");\n\treturn lines.join(\"\\n\");\n}\n\nexport function formatSnapshotOnlyDiff(title: string, snapshot: Snapshot) {\n\treturn [`${title}: ${snapshot.id}`, ...snapshot.files.map((file) => `Add: ${file.path}`)].join(\n\t\t\"\\n\",\n\t);\n}\n\nexport function formatPushSummary(\n\tconfig: CommonSyncConfig,\n\tdestination: string,\n\tupload: Snapshot,\n\thead: RemoteHead | undefined,\n\tpreservedRemoteFileCount = 0,\n\tremote?: Snapshot,\n) {\n\treturn [\n\t\t`Sync setup: ${safeTerminalText(config.setupName)}`,\n\t\t`Storage location: ${safeTerminalText(destination)}`,\n\t\t`Upload ${upload.files.length} files from ${safeTerminalText(agentDir())}.`,\n\t\t`Sessions: ${upload.syncSessions ? \"included \u2014 may contain private conversations\" : \"not included\"}`,\n\t\thead ? `Remote latest: ${head.snapshotId}` : \"Remote latest: empty\",\n\t\t\"Publication effect: the backend's active head will reference the new immutable snapshot.\",\n\t\t...(remote && inspectRemoteSelection(config.include, remote).kind === \"different\"\n\t\t\t? [\n\t\t\t\t\t\"Included-content policy effect: replace the differing remote selection with this setup's local selection.\",\n\t\t\t\t]\n\t\t\t: []),\n\t\tformatPublicationPreview(remote, upload),\n\t\tpreservedRemoteFileCount > 0\n\t\t\t? `Possible secrets in locally managed files were scanned before this prompt; ${preservedRemoteFileCount} preserved remote file(s) were not rescanned.`\n\t\t\t: \"Possible secrets were scanned before this prompt.\",\n\t].join(\"\\n\");\n}\n\nexport function formatApplyPreview(local: Snapshot, remote: Snapshot) {\n\treturn formatDirectionalChanges(local, remote, {\n\t\tadd: \"Add locally\",\n\t\tupdate: \"Update locally\",\n\t\tremove: \"Remove locally\",\n\t});\n}\n\nexport function formatPullSummary(\n\tconfig: CommonSyncConfig,\n\tdestination: string,\n\tlocal: Snapshot,\n\tremote: Snapshot,\n\tprotectedSessionCount: number,\n) {\n\treturn [\n\t\t`Sync setup: ${safeTerminalText(config.setupName)}`,\n\t\t`Storage location: ${safeTerminalText(destination)}`,\n\t\t`Snapshot: ${safeTerminalText(remote.id)}`,\n\t\t`Sessions: ${remote.syncSessions ? \"included \u2014 may contain private conversations\" : \"not included\"}`,\n\t\t`Protected live sessions: ${protectedSessionCount || \"none\"}`,\n\t\tformatApplyPreview(local, remote),\n\t\t\"A local backup is created before these writes/deletes. The remote active snapshot is unchanged.\",\n\t].join(\"\\n\");\n}\n\nexport function formatRollbackSummary(\n\tconfig: CommonSyncConfig,\n\tdestination: string,\n\tlocal: Snapshot,\n\tremote: Snapshot,\n\trequestedSnapshot: string,\n\tprotectedSessionCount: number,\n) {\n\treturn [\n\t\t`Sync setup: ${safeTerminalText(config.setupName)}`,\n\t\t`Storage location: ${safeTerminalText(destination)}`,\n\t\t`Snapshot: ${safeTerminalText(requestedSnapshot)}`,\n\t\t`Sessions: ${remote.syncSessions ? \"included \u2014 may contain private conversations\" : \"not included\"}`,\n\t\t`Protected live sessions: ${protectedSessionCount || \"none\"}`,\n\t\tformatApplyPreview(local, remote),\n\t\t\"A local backup is created before applying; the backend's active head will change.\",\n\t].join(\"\\n\");\n}\n\nfunction formatPublicationPreview(remote: Snapshot | undefined, upload: Snapshot) {\n\tif (!remote) {\n\t\treturn [\"Remote is empty.\", ...upload.files.map((file) => `Add remotely: ${file.path}`)].join(\n\t\t\t\"\\n\",\n\t\t);\n\t}\n\treturn formatDirectionalChanges(remote, upload, {\n\t\tadd: \"Add remotely\",\n\t\tupdate: \"Update remotely\",\n\t\tremove: \"Remove remotely\",\n\t});\n}\n\nfunction formatDirectionalChanges(\n\tbefore: Snapshot,\n\tafter: Snapshot,\n\tlabels: { add: string; update: string; remove: string },\n) {\n\tconst beforeMap = fileHashMap(before);\n\tconst afterMap = fileHashMap(after);\n\tconst paths = [...new Set([...Object.keys(beforeMap), ...Object.keys(afterMap)])].sort();\n\tconst lines: string[] = [];\n\tfor (const filePath of paths) {\n\t\tif (!beforeMap[filePath]) lines.push(`${labels.add}: ${filePath}`);\n\t\telse if (!afterMap[filePath]) lines.push(`${labels.remove}: ${filePath}`);\n\t\telse if (beforeMap[filePath] !== afterMap[filePath])\n\t\t\tlines.push(`${labels.update}: ${filePath}`);\n\t}\n\tif (lines.length === 0) lines.push(\"No file changes.\");\n\treturn lines.join(\"\\n\");\n}\n\nexport function countPreservedRemoteFiles(local: Snapshot, upload: Snapshot) {\n\tconst localPaths = new Set(local.files.map((file) => file.path));\n\treturn upload.files.filter((file) => !localPaths.has(file.path)).length;\n}\n\nexport function publicationCapabilityDescription(capability: PublicationCapability) {\n\tswitch (capability) {\n\t\tcase \"lease-protected\":\n\t\t\treturn \"lease-protected (exact expected-ref update)\";\n\t\tcase \"atomic-conditional\":\n\t\t\treturn \"atomic-conditional (verified atomic precondition)\";\n\t\tcase \"conditional-required\":\n\t\t\treturn \"conditional-required (read-only until atomic preconditions are verified)\";\n\t\tcase \"read-check-write-verify\":\n\t\t\treturn \"read-check-write-verify (visible races rejected; simultaneous unseen races remain possible)\";\n\t}\n}\n\nexport function safeTerminalText(value: string) {\n\t// biome-ignore lint/suspicious/noControlCharactersInRegex: Escape untrusted terminal controls.\n\treturn value.replace(/[\\u0000-\\u001f\\u007f-\\u009f]/gu, \"?\");\n}\n\nexport function redact(value: string) {\n\treturn value.length <= 8 ? \"configured\" : `${value.slice(0, 4)}\u2026${value.slice(-4)}`;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|