@narumitw/pi-sync 0.49.14 → 0.50.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 +134 -241
- package/dist/chunks/chunk-2B2ELUF5.ts +29 -0
- package/dist/chunks/chunk-2B2ELUF5.ts.map +7 -0
- package/dist/chunks/chunk-6LP3MCR2.ts +115 -0
- package/dist/chunks/chunk-6LP3MCR2.ts.map +7 -0
- package/dist/chunks/chunk-6UHKGALE.ts +134 -0
- package/dist/chunks/chunk-6UHKGALE.ts.map +7 -0
- package/dist/chunks/{chunk-RGJ6SOYR.ts → chunk-AUSDLD6F.ts} +4 -4
- package/dist/chunks/chunk-AUSDLD6F.ts.map +7 -0
- package/dist/chunks/{chunk-3NDD5QPM.ts → chunk-AXXS2BPA.ts} +9 -9
- package/dist/chunks/chunk-AXXS2BPA.ts.map +7 -0
- package/dist/chunks/{chunk-APZVBSM6.ts → chunk-C7XY5NF5.ts} +214 -365
- package/dist/chunks/chunk-C7XY5NF5.ts.map +7 -0
- package/dist/chunks/{chunk-4U6AFLJB.ts → chunk-CHH26D4F.ts} +3 -2
- package/dist/chunks/chunk-CHH26D4F.ts.map +7 -0
- package/dist/chunks/chunk-CPEHGXJV.ts +50 -0
- package/dist/chunks/chunk-CPEHGXJV.ts.map +7 -0
- package/dist/chunks/{chunk-5MQ76BK5.ts → chunk-D5X27LCA.ts} +2 -2
- package/dist/chunks/chunk-D5X27LCA.ts.map +7 -0
- package/dist/chunks/{chunk-LJDSHNS3.ts → chunk-GWMHAQG4.ts} +4 -4
- package/dist/chunks/chunk-GWMHAQG4.ts.map +7 -0
- package/dist/chunks/{chunk-FR6CVXAL.ts → chunk-HUDQATCL.ts} +25 -25
- package/dist/chunks/chunk-HUDQATCL.ts.map +7 -0
- package/dist/chunks/{chunk-YQ6UW7IF.ts → chunk-JAAKLL6M.ts} +2 -2
- package/dist/chunks/chunk-JAAKLL6M.ts.map +7 -0
- package/dist/chunks/chunk-JSYMGJQQ.ts +51 -0
- package/dist/chunks/chunk-JSYMGJQQ.ts.map +7 -0
- package/dist/chunks/chunk-L7TBO32M.ts +25 -0
- package/dist/chunks/chunk-L7TBO32M.ts.map +7 -0
- package/dist/chunks/{chunk-4F3W4CSC.ts → chunk-LCRI3ABW.ts} +22 -8
- package/dist/chunks/chunk-LCRI3ABW.ts.map +7 -0
- package/dist/chunks/chunk-LTBWGSBV.ts +12 -0
- package/dist/chunks/chunk-LTBWGSBV.ts.map +7 -0
- package/dist/chunks/chunk-NJFUIVYL.ts +17 -0
- package/dist/chunks/chunk-NJFUIVYL.ts.map +7 -0
- package/dist/chunks/{chunk-EWX3TPLQ.ts → chunk-OEX4ABY6.ts} +39 -37
- package/dist/chunks/chunk-OEX4ABY6.ts.map +7 -0
- package/dist/chunks/chunk-OUHGBFYF.ts +1255 -0
- package/dist/chunks/chunk-OUHGBFYF.ts.map +7 -0
- package/dist/chunks/{chunk-HGV24P4T.ts → chunk-Q2TUM5X6.ts} +3 -3
- package/dist/chunks/chunk-Q2TUM5X6.ts.map +7 -0
- package/dist/chunks/{chunk-BHJUISB4.ts → chunk-SL64RLXR.ts} +8 -6
- package/dist/chunks/chunk-SL64RLXR.ts.map +7 -0
- package/dist/chunks/chunk-T3HH3UEJ.ts +96 -0
- package/dist/chunks/chunk-T3HH3UEJ.ts.map +7 -0
- package/dist/chunks/chunk-TEN2ZBJV.ts +101 -0
- package/dist/chunks/chunk-TEN2ZBJV.ts.map +7 -0
- package/dist/chunks/chunk-THMF4BVL.ts +18 -0
- package/dist/chunks/chunk-THMF4BVL.ts.map +7 -0
- package/dist/chunks/{chunk-LONPFFNP.ts → chunk-X5OM6GXT.ts} +9 -7
- package/dist/chunks/chunk-X5OM6GXT.ts.map +7 -0
- package/dist/chunks/chunk-XBX4HBLG.ts +57 -0
- package/dist/chunks/chunk-XBX4HBLG.ts.map +7 -0
- package/dist/chunks/{chunk-5WJNWFTG.ts → chunk-YKJHPTGO.ts} +23 -55
- package/dist/chunks/chunk-YKJHPTGO.ts.map +7 -0
- package/dist/chunks/chunk-Z4DQF6L3.ts +12 -0
- package/dist/chunks/chunk-Z4DQF6L3.ts.map +7 -0
- package/dist/chunks/{chunk-F5QL2GPY.ts → chunk-ZI5MXMEX.ts} +5 -99
- package/dist/chunks/chunk-ZI5MXMEX.ts.map +7 -0
- package/dist/chunks/{file-selection-4UABOP44.ts → file-selection-FTTKAOYX.ts} +10 -7
- package/dist/chunks/file-selection-FTTKAOYX.ts.map +7 -0
- package/dist/chunks/{git-backend-E3UOUUOM.ts → git-backend-OQW46G2X.ts} +42 -17
- package/dist/chunks/git-backend-OQW46G2X.ts.map +7 -0
- package/dist/chunks/manager-result-dispatcher-U23ODD32.ts +22 -0
- package/dist/chunks/manager-ui-ABATHC36.ts +1680 -0
- package/dist/chunks/manager-ui-ABATHC36.ts.map +7 -0
- package/dist/chunks/remote-selection-ui-ZE3TY67S.ts +21 -0
- package/dist/chunks/{s3-backend-3QZGO2EF.ts → s3-backend-3P33LQHU.ts} +96 -18
- package/dist/chunks/s3-backend-3P33LQHU.ts.map +7 -0
- package/dist/chunks/{setup-switch-X3W3FALZ.ts → setup-switch-IS2PEZ3J.ts} +9 -9
- package/dist/chunks/setup-wizard-VB3VDOV5.ts +89 -0
- package/dist/chunks/setup-wizard-VB3VDOV5.ts.map +7 -0
- package/dist/chunks/{snapshot-WBSV4MQG.ts → snapshot-HKEWCVK6.ts} +7 -6
- package/dist/chunks/sync-inspection-FEP4L5TR.ts +20 -0
- package/dist/chunks/{sync-operations-MPAFVVHA.ts → sync-operations-ONJ27QRL.ts} +381 -299
- package/dist/chunks/sync-operations-ONJ27QRL.ts.map +7 -0
- package/dist/chunks/{sync-state-36H7ULG2.ts → sync-state-Q2FTXTW6.ts} +6 -6
- package/dist/chunks/sync-state-Q2FTXTW6.ts.map +7 -0
- package/dist/chunks/{webdav-backend-ZD5RHRIU.ts → webdav-backend-ECIKD7SO.ts} +26 -13
- package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +7 -0
- package/dist/index.ts +477 -342
- package/dist/index.ts.map +4 -4
- package/docs/settings.md +211 -0
- package/package.json +6 -8
- package/src/{backend-factory.ts → backends/backend-factory.ts} +4 -4
- package/src/backends/backend-identity.ts +40 -0
- package/src/backends/backend-types.ts +81 -0
- package/src/{git-backend.ts → backends/git/git-backend.ts} +43 -19
- package/src/{git-config.ts → backends/git/git-config.ts} +1 -0
- package/src/{git-storage.ts → backends/git/git-storage.ts} +2 -2
- package/src/{s3-backend.ts → backends/s3/s3-backend.ts} +45 -9
- package/src/{s3-client.ts → backends/s3/s3-client.ts} +56 -8
- package/src/{sync-backend.ts → backends/sync-backend.ts} +1 -1
- package/src/{webdav-backend.ts → backends/webdav/webdav-backend.ts} +22 -10
- package/src/{webdav-client.ts → backends/webdav/webdav-client.ts} +1 -1
- package/src/{webdav-config.ts → backends/webdav/webdav-config.ts} +1 -0
- package/src/commands/command-execution.ts +330 -0
- package/src/commands/command-handler.ts +201 -0
- package/src/commands/command-types.ts +18 -0
- package/src/{command.ts → commands/command.ts} +2 -1
- package/src/commands/setup-completions.ts +6 -0
- package/src/settings/config.ts +238 -0
- package/src/{settings-management.ts → settings/settings-management.ts} +40 -14
- package/src/settings/settings-store.ts +109 -0
- package/src/settings/settings-types.ts +128 -0
- package/src/settings/settings-validation.ts +443 -0
- package/src/snapshot/session-paths.ts +84 -0
- package/src/{snapshot-apply.ts → snapshot/snapshot-apply.ts} +3 -3
- package/src/{snapshot-codec.ts → snapshot/snapshot-codec.ts} +2 -2
- package/src/{snapshot-transaction.ts → snapshot/snapshot-transaction.ts} +5 -4
- package/src/snapshot/snapshot-types.ts +38 -0
- package/src/{snapshot.ts → snapshot/snapshot.ts} +39 -20
- package/src/state/json-file.ts +20 -0
- package/src/{lock.ts → state/lock.ts} +3 -2
- package/src/{operation-availability.ts → state/operation-availability.ts} +2 -2
- package/src/state/state-types.ts +20 -0
- package/src/state/sync-state-store.ts +62 -0
- package/src/sync/automatic-sync.ts +103 -0
- package/src/{remote-snapshot.ts → sync/remote-snapshot.ts} +27 -4
- package/src/{setup-switch.ts → sync/setup-switch.ts} +6 -7
- package/src/sync/signals.ts +10 -0
- package/src/sync/startup-check.ts +128 -0
- package/src/{sync-decision.ts → sync/sync-decision.ts} +6 -3
- package/src/sync/sync-error-guidance.ts +53 -0
- package/src/sync/sync-inspection.ts +51 -0
- package/src/sync/sync-loaders.ts +54 -0
- package/src/{sync-operations.ts → sync/sync-mutations.ts} +41 -330
- package/src/sync/sync-operations.ts +12 -0
- package/src/{sync-policy.ts → sync/sync-policy.ts} +2 -2
- package/src/sync/sync-queries.ts +235 -0
- package/src/{sync-state.ts → sync/sync-state.ts} +5 -4
- package/src/sync-extension.ts +64 -708
- package/src/sync.ts +8 -8
- package/src/types.ts +47 -284
- package/src/ui/automatic-sync-summary.ts +6 -0
- package/src/{cancellable-operation.ts → ui/cancellable-operation.ts} +4 -4
- package/src/{file-selection.ts → ui/file-selection.ts} +5 -3
- package/src/{manager-attention.ts → ui/manager-attention.ts} +5 -1
- package/src/{manager-recovery.ts → ui/manager-recovery.ts} +6 -2
- package/src/{manager-state.ts → ui/manager-state.ts} +35 -14
- package/src/ui/manager-ui.ts +386 -0
- package/src/{remote-selection-ui.ts → ui/remote-selection-ui.ts} +21 -13
- package/src/{secret-input.ts → ui/secret-input.ts} +22 -4
- package/src/{settings-ui.ts → ui/settings-ui.ts} +42 -11
- package/src/ui/setup/git-ui.ts +293 -0
- package/src/{s3-credentials-ui.ts → ui/setup/s3-credentials-ui.ts} +13 -21
- package/src/ui/setup/s3-ui.ts +217 -0
- package/src/ui/setup/setup-actions.ts +127 -0
- package/src/ui/setup/setup-location-ui.ts +167 -0
- package/src/ui/setup/setup-name-ui.ts +34 -0
- package/src/ui/setup/setup-prompts.ts +92 -0
- package/src/ui/setup/setup-review.ts +70 -0
- package/src/ui/setup/setup-switcher.ts +181 -0
- package/src/ui/setup/setup-wizard.ts +39 -0
- package/src/ui/setup/text-input.ts +78 -0
- package/src/ui/setup/webdav-ui.ts +330 -0
- package/src/{storage-connections-ui.ts → ui/storage-connections-ui.ts} +99 -69
- package/src/{sync-attention.ts → ui/sync-attention.ts} +75 -6
- package/src/{sync-format.ts → ui/sync-format.ts} +7 -13
- package/src/{sync-resolution-ui.ts → ui/sync-resolution-ui.ts} +4 -3
- package/src/{sync-setups-ui.ts → ui/sync-setups-ui.ts} +23 -10
- package/src/ui/terminal-text.ts +4 -0
- package/dist/chunks/chunk-3NDD5QPM.ts.map +0 -7
- package/dist/chunks/chunk-4F3W4CSC.ts.map +0 -7
- package/dist/chunks/chunk-4U6AFLJB.ts.map +0 -7
- package/dist/chunks/chunk-5MQ76BK5.ts.map +0 -7
- package/dist/chunks/chunk-5WJNWFTG.ts.map +0 -7
- package/dist/chunks/chunk-APZVBSM6.ts.map +0 -7
- package/dist/chunks/chunk-BHJUISB4.ts.map +0 -7
- package/dist/chunks/chunk-EWX3TPLQ.ts.map +0 -7
- package/dist/chunks/chunk-F5QL2GPY.ts.map +0 -7
- package/dist/chunks/chunk-FR6CVXAL.ts.map +0 -7
- package/dist/chunks/chunk-HGV24P4T.ts.map +0 -7
- package/dist/chunks/chunk-JNOOSPEW.ts +0 -82
- package/dist/chunks/chunk-JNOOSPEW.ts.map +0 -7
- package/dist/chunks/chunk-LJDSHNS3.ts.map +0 -7
- package/dist/chunks/chunk-LONPFFNP.ts.map +0 -7
- package/dist/chunks/chunk-RGJ6SOYR.ts.map +0 -7
- package/dist/chunks/chunk-W64NEHXT.ts +0 -167
- package/dist/chunks/chunk-W64NEHXT.ts.map +0 -7
- package/dist/chunks/chunk-YQ6UW7IF.ts.map +0 -7
- package/dist/chunks/file-selection-4UABOP44.ts.map +0 -7
- package/dist/chunks/git-backend-E3UOUUOM.ts.map +0 -7
- package/dist/chunks/manager-result-dispatcher-RNRQA3PW.ts +0 -18
- package/dist/chunks/manager-ui-QO2P4V3Z.ts +0 -2757
- package/dist/chunks/manager-ui-QO2P4V3Z.ts.map +0 -7
- package/dist/chunks/remote-selection-ui-LHSAGKGA.ts +0 -17
- package/dist/chunks/s3-backend-3QZGO2EF.ts.map +0 -7
- package/dist/chunks/sync-operations-MPAFVVHA.ts.map +0 -7
- package/dist/chunks/webdav-backend-ZD5RHRIU.ts.map +0 -7
- package/src/config.ts +0 -910
- package/src/git-ui.ts +0 -307
- package/src/manager-helpers.ts +0 -64
- package/src/manager-ui.ts +0 -997
- package/src/webdav-ui.ts +0 -421
- /package/dist/chunks/{manager-result-dispatcher-RNRQA3PW.ts.map → manager-result-dispatcher-U23ODD32.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-LHSAGKGA.ts.map → remote-selection-ui-ZE3TY67S.ts.map} +0 -0
- /package/dist/chunks/{setup-switch-X3W3FALZ.ts.map → setup-switch-IS2PEZ3J.ts.map} +0 -0
- /package/dist/chunks/{snapshot-WBSV4MQG.ts.map → snapshot-HKEWCVK6.ts.map} +0 -0
- /package/dist/chunks/{sync-state-36H7ULG2.ts.map → sync-inspection-FEP4L5TR.ts.map} +0 -0
- /package/src/{git-runner.ts → backends/git/git-runner.ts} +0 -0
- /package/src/{config-errors.ts → settings/config-errors.ts} +0 -0
- /package/src/{config-file.ts → settings/config-file.ts} +0 -0
- /package/src/{snapshot-paths.ts → snapshot/snapshot-paths.ts} +0 -0
- /package/src/{lock-policy.ts → state/lock-policy.ts} +0 -0
- /package/src/{lockfile-fs.ts → state/lockfile-fs.ts} +0 -0
- /package/src/{state-directory.ts → state/state-directory.ts} +0 -0
- /package/src/{sync-errors.ts → sync/sync-errors.ts} +0 -0
- /package/src/{manager-result-dispatcher.ts → ui/manager-result-dispatcher.ts} +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
normalizeGitRemoteIdentity
|
|
5
|
+
} from "./chunk-TEN2ZBJV.ts";
|
|
6
|
+
|
|
7
|
+
// src/backends/webdav/webdav-config.ts
|
|
8
|
+
var DEFAULT_PATH = "pi-sync";
|
|
9
|
+
function normalizeWebDavIdentityUrl(value) {
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(value.trim());
|
|
12
|
+
url.username = "";
|
|
13
|
+
url.password = "";
|
|
14
|
+
url.search = "";
|
|
15
|
+
url.hash = "";
|
|
16
|
+
url.pathname = `${url.pathname.replace(/\/+$/u, "")}/`;
|
|
17
|
+
return url.toString();
|
|
18
|
+
} catch {
|
|
19
|
+
return value.trim();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function normalizeWebDavUrl(value) {
|
|
23
|
+
const normalized = normalizeOptionalString(value);
|
|
24
|
+
if (!normalized) return void 0;
|
|
25
|
+
let url;
|
|
26
|
+
try {
|
|
27
|
+
url = new URL(normalized);
|
|
28
|
+
} catch {
|
|
29
|
+
throw new Error("Invalid pi-sync WebDAV URL.");
|
|
30
|
+
}
|
|
31
|
+
if (url.username || url.password || url.search || url.hash) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
"Invalid pi-sync WebDAV URL: credentials, query, and fragment are not allowed."
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
const loopback = url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "[::1]" || url.hostname === "::1";
|
|
37
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
38
|
+
throw new Error("Invalid pi-sync WebDAV URL: HTTPS is required except for loopback.");
|
|
39
|
+
}
|
|
40
|
+
url.pathname = `${url.pathname.replace(/\/+$/u, "")}/`;
|
|
41
|
+
return url.toString();
|
|
42
|
+
}
|
|
43
|
+
function normalizeWebDavPath(value) {
|
|
44
|
+
if (value?.trim() === "." || value?.trim() === "./") return "./";
|
|
45
|
+
const normalized = trimSlashes(normalizeOptionalString(value) ?? DEFAULT_PATH);
|
|
46
|
+
if (!normalized || normalized.includes("\\") || hasControlCharacter(normalized) || normalized.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
|
|
47
|
+
throw new Error("Invalid pi-sync WebDAV path.");
|
|
48
|
+
}
|
|
49
|
+
return normalized;
|
|
50
|
+
}
|
|
51
|
+
function validateWebDavCredentials(username, password) {
|
|
52
|
+
if (username.includes(":") || hasControlCharacter(username) || password !== void 0 && hasControlCharacter(password)) {
|
|
53
|
+
throw new Error("Invalid pi-sync WebDAV credentials.");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function normalizeOptionalString(value) {
|
|
57
|
+
const normalized = value?.trim();
|
|
58
|
+
return normalized || void 0;
|
|
59
|
+
}
|
|
60
|
+
function trimSlashes(value) {
|
|
61
|
+
return value.replace(/^\/+|\/+$/gu, "");
|
|
62
|
+
}
|
|
63
|
+
function hasControlCharacter(value) {
|
|
64
|
+
return [...value].some((character) => {
|
|
65
|
+
const code = character.codePointAt(0) ?? 0;
|
|
66
|
+
return code < 32 || code >= 127 && code <= 159;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/backends/backend-identity.ts
|
|
71
|
+
function backendIdentityCoordinates(config) {
|
|
72
|
+
switch (config.backend.type) {
|
|
73
|
+
case "s3":
|
|
74
|
+
return JSON.stringify([
|
|
75
|
+
"s3",
|
|
76
|
+
normalizeEndpointIdentity(config.backend.profile.endpoint),
|
|
77
|
+
config.backend.destination.bucket,
|
|
78
|
+
config.storagePath
|
|
79
|
+
]);
|
|
80
|
+
case "git":
|
|
81
|
+
return JSON.stringify([
|
|
82
|
+
"git",
|
|
83
|
+
normalizeGitRemoteIdentity(config.backend.profile.remote),
|
|
84
|
+
config.backend.destination.branch,
|
|
85
|
+
config.storagePath
|
|
86
|
+
]);
|
|
87
|
+
case "webdav":
|
|
88
|
+
return JSON.stringify([
|
|
89
|
+
"webdav",
|
|
90
|
+
normalizeWebDavIdentityUrl(config.backend.profile.url),
|
|
91
|
+
config.backend.profile.username,
|
|
92
|
+
config.storagePath
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function normalizeEndpointIdentity(endpoint) {
|
|
97
|
+
try {
|
|
98
|
+
const url = new URL(endpoint.trim());
|
|
99
|
+
url.hostname = url.hostname.toLowerCase();
|
|
100
|
+
url.pathname = url.pathname.replace(/\/+$/gu, "");
|
|
101
|
+
return url.toString().replace(/\/$/u, "");
|
|
102
|
+
} catch {
|
|
103
|
+
return endpoint.trim();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
normalizeWebDavIdentityUrl,
|
|
109
|
+
normalizeWebDavUrl,
|
|
110
|
+
normalizeWebDavPath,
|
|
111
|
+
validateWebDavCredentials,
|
|
112
|
+
backendIdentityCoordinates,
|
|
113
|
+
normalizeEndpointIdentity
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=chunk-6LP3MCR2.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/backends/webdav/webdav-config.ts", "../../src/backends/backend-identity.ts"],
|
|
4
|
+
"sourcesContent": ["const DEFAULT_PATH = \"pi-sync\";\n\nexport function normalizeWebDavIdentityUrl(value: string) {\n\ttry {\n\t\tconst url = new URL(value.trim());\n\t\turl.username = \"\";\n\t\turl.password = \"\";\n\t\turl.search = \"\";\n\t\turl.hash = \"\";\n\t\turl.pathname = `${url.pathname.replace(/\\/+$/u, \"\")}/`;\n\t\treturn url.toString();\n\t} catch {\n\t\treturn value.trim();\n\t}\n}\n\nexport function normalizeWebDavUrl(value: string | undefined) {\n\tconst normalized = normalizeOptionalString(value);\n\tif (!normalized) return undefined;\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(normalized);\n\t} catch {\n\t\tthrow new Error(\"Invalid pi-sync WebDAV URL.\");\n\t}\n\tif (url.username || url.password || url.search || url.hash) {\n\t\tthrow new Error(\n\t\t\t\"Invalid pi-sync WebDAV URL: credentials, query, and fragment are not allowed.\",\n\t\t);\n\t}\n\tconst loopback =\n\t\turl.hostname === \"127.0.0.1\" ||\n\t\turl.hostname === \"localhost\" ||\n\t\turl.hostname === \"[::1]\" ||\n\t\turl.hostname === \"::1\";\n\tif (url.protocol !== \"https:\" && !(url.protocol === \"http:\" && loopback)) {\n\t\tthrow new Error(\"Invalid pi-sync WebDAV URL: HTTPS is required except for loopback.\");\n\t}\n\turl.pathname = `${url.pathname.replace(/\\/+$/u, \"\")}/`;\n\treturn url.toString();\n}\n\nexport function normalizeWebDavPath(value: string | undefined) {\n\tif (value?.trim() === \".\" || value?.trim() === \"./\") return \"./\";\n\tconst normalized = trimSlashes(normalizeOptionalString(value) ?? DEFAULT_PATH);\n\tif (\n\t\t!normalized ||\n\t\tnormalized.includes(\"\\\\\") ||\n\t\thasControlCharacter(normalized) ||\n\t\tnormalized.split(\"/\").some((segment) => !segment || segment === \".\" || segment === \"..\")\n\t) {\n\t\tthrow new Error(\"Invalid pi-sync WebDAV path.\");\n\t}\n\treturn normalized;\n}\n\nexport function validateWebDavNamespace(value: string) {\n\tif (\n\t\tvalue === \".\" ||\n\t\tvalue === \"..\" ||\n\t\tvalue.includes(\"/\") ||\n\t\tvalue.includes(\"\\\\\") ||\n\t\thasControlCharacter(value)\n\t) {\n\t\tthrow new Error(\"Invalid pi-sync WebDAV namespace.\");\n\t}\n}\n\nexport function validateWebDavCredentials(username: string, password?: string) {\n\tif (\n\t\tusername.includes(\":\") ||\n\t\thasControlCharacter(username) ||\n\t\t(password !== undefined && hasControlCharacter(password))\n\t) {\n\t\tthrow new Error(\"Invalid pi-sync WebDAV credentials.\");\n\t}\n}\n\nfunction normalizeOptionalString(value: string | undefined) {\n\tconst normalized = value?.trim();\n\treturn normalized || undefined;\n}\n\nfunction trimSlashes(value: string) {\n\treturn value.replace(/^\\/+|\\/+$/gu, \"\");\n}\n\nfunction hasControlCharacter(value: string) {\n\treturn [...value].some((character) => {\n\t\tconst code = character.codePointAt(0) ?? 0;\n\t\treturn code < 0x20 || (code >= 0x7f && code <= 0x9f);\n\t});\n}\n", "import type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { normalizeGitRemoteIdentity } from \"./git/git-config.js\";\nimport { normalizeWebDavIdentityUrl } from \"./webdav/webdav-config.js\";\n\nexport function backendIdentityCoordinates(config: AnySyncConfig) {\n\tswitch (config.backend.type) {\n\t\tcase \"s3\":\n\t\t\treturn JSON.stringify([\n\t\t\t\t\"s3\",\n\t\t\t\tnormalizeEndpointIdentity(config.backend.profile.endpoint),\n\t\t\t\tconfig.backend.destination.bucket,\n\t\t\t\tconfig.storagePath,\n\t\t\t]);\n\t\tcase \"git\":\n\t\t\treturn JSON.stringify([\n\t\t\t\t\"git\",\n\t\t\t\tnormalizeGitRemoteIdentity(config.backend.profile.remote),\n\t\t\t\tconfig.backend.destination.branch,\n\t\t\t\tconfig.storagePath,\n\t\t\t]);\n\t\tcase \"webdav\":\n\t\t\treturn JSON.stringify([\n\t\t\t\t\"webdav\",\n\t\t\t\tnormalizeWebDavIdentityUrl(config.backend.profile.url),\n\t\t\t\tconfig.backend.profile.username,\n\t\t\t\tconfig.storagePath,\n\t\t\t]);\n\t}\n}\n\nexport function normalizeEndpointIdentity(endpoint: string) {\n\ttry {\n\t\tconst url = new URL(endpoint.trim());\n\t\turl.hostname = url.hostname.toLowerCase();\n\t\turl.pathname = url.pathname.replace(/\\/+$/gu, \"\");\n\t\treturn url.toString().replace(/\\/$/u, \"\");\n\t} catch {\n\t\treturn endpoint.trim();\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
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
|
+
safeTerminalText
|
|
5
|
+
} from "./chunk-LTBWGSBV.ts";
|
|
6
|
+
import {
|
|
7
|
+
syncCheckConfigFingerprint,
|
|
8
|
+
syncConfigReviewFingerprint
|
|
9
|
+
} from "./chunk-C7XY5NF5.ts";
|
|
10
|
+
import {
|
|
11
|
+
compareSyncInclude,
|
|
12
|
+
sameSyncInclude
|
|
13
|
+
} from "./chunk-JAAKLL6M.ts";
|
|
14
|
+
|
|
15
|
+
// src/ui/sync-attention.ts
|
|
16
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
17
|
+
var STATUS_KEY = "sync";
|
|
18
|
+
var WIDGET_KEY = "sync:attention";
|
|
19
|
+
function syncAttentionMatchesConfig(attention, config) {
|
|
20
|
+
return attention.decision.setupName === config.setupName && attention.decision.configIdentity === syncConfigReviewFingerprint(config) && sameSyncInclude(attention.decision.localInclude, config.include);
|
|
21
|
+
}
|
|
22
|
+
function createSyncAttentionController() {
|
|
23
|
+
let state;
|
|
24
|
+
let observation;
|
|
25
|
+
return {
|
|
26
|
+
observe(value) {
|
|
27
|
+
observation = value;
|
|
28
|
+
},
|
|
29
|
+
observation() {
|
|
30
|
+
return observation;
|
|
31
|
+
},
|
|
32
|
+
clearObservation() {
|
|
33
|
+
observation = void 0;
|
|
34
|
+
},
|
|
35
|
+
notifyObservation(ctx) {
|
|
36
|
+
if (observation && observationNeedsAttention(observation)) {
|
|
37
|
+
ctx.ui.notify(observationLines(observation).join("\n"), "warning");
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
set(decision, origin) {
|
|
41
|
+
state = { decision, origin, offered: false };
|
|
42
|
+
},
|
|
43
|
+
current() {
|
|
44
|
+
return state;
|
|
45
|
+
},
|
|
46
|
+
markOffered() {
|
|
47
|
+
if (!state || state.offered) return false;
|
|
48
|
+
state = { ...state, offered: true };
|
|
49
|
+
return true;
|
|
50
|
+
},
|
|
51
|
+
clear(ctx) {
|
|
52
|
+
state = void 0;
|
|
53
|
+
clearAttentionPresentation(ctx);
|
|
54
|
+
},
|
|
55
|
+
reset(ctx) {
|
|
56
|
+
state = void 0;
|
|
57
|
+
observation = void 0;
|
|
58
|
+
clearAttentionPresentation(ctx);
|
|
59
|
+
},
|
|
60
|
+
publish(ctx) {
|
|
61
|
+
if (!state && (!observation || !observationNeedsAttention(observation))) {
|
|
62
|
+
clearAttentionPresentation(ctx);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const presentation = state ? attentionPresentation(state.decision) : {
|
|
66
|
+
status: "changes to review",
|
|
67
|
+
lines: observationLines(observation)
|
|
68
|
+
};
|
|
69
|
+
ctx.ui.setStatus(STATUS_KEY, presentation.status);
|
|
70
|
+
if (ctx.mode !== "tui") return;
|
|
71
|
+
ctx.ui.setWidget(WIDGET_KEY, (_tui, theme) => ({
|
|
72
|
+
render(width) {
|
|
73
|
+
const safeWidth = Math.max(1, width);
|
|
74
|
+
return presentation.lines.map(
|
|
75
|
+
(line, index) => truncateToWidth(theme.fg(index === 0 ? "warning" : "muted", line), safeWidth, "\u2026")
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
invalidate() {
|
|
79
|
+
}
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function attentionPresentation(decision) {
|
|
85
|
+
const setupName = safeTerminalText(decision.setupName);
|
|
86
|
+
const comparison = compareSyncInclude(decision.localInclude, decision.remoteInclude);
|
|
87
|
+
const difference = comparison.remoteOnly.length === 0 && comparison.localOnly.length === 0 ? "Only list order differs" : `Remote ${comparison.remoteOnly.length} \xB7 Device ${comparison.localOnly.length}`;
|
|
88
|
+
return {
|
|
89
|
+
status: "review needed",
|
|
90
|
+
lines: [`Pi Sync needs review \xB7 ${setupName}`, difference, "No changes \xB7 Run /sync to review"]
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function observationMatchesConfig(observation, config) {
|
|
94
|
+
return observation.configIdentity === syncCheckConfigFingerprint(config);
|
|
95
|
+
}
|
|
96
|
+
function observationSummary(observation) {
|
|
97
|
+
const result = observation.inspection;
|
|
98
|
+
if (result.emptyInclude) return "No included content selected";
|
|
99
|
+
if (result.selectionState?.kind === "different")
|
|
100
|
+
return "Synced-content list differs; review needed";
|
|
101
|
+
if (result.firstSync)
|
|
102
|
+
return result.head ? "No sync baseline; review remote and local content" : "Remote empty; no sync baseline";
|
|
103
|
+
if (!result.head) return "Remote snapshot missing; review needed";
|
|
104
|
+
if (result.localChanged && result.remoteChanged)
|
|
105
|
+
return "Local and remote changed since last sync; review needed";
|
|
106
|
+
if (result.remoteChanged) return "Remote changed since last sync";
|
|
107
|
+
if (result.localChanged) return "Local content changed since last sync";
|
|
108
|
+
if (result.selectionState?.kind === "legacy")
|
|
109
|
+
return "Legacy remote has no authoritative content list";
|
|
110
|
+
return "No changes detected since last sync";
|
|
111
|
+
}
|
|
112
|
+
function observationNeedsAttention(observation) {
|
|
113
|
+
const result = observation.inspection;
|
|
114
|
+
return result.emptyInclude || result.firstSync || !result.head || result.localChanged || result.remoteChanged || result.selectionState?.kind !== "same";
|
|
115
|
+
}
|
|
116
|
+
function observationLines(observation) {
|
|
117
|
+
return [
|
|
118
|
+
`Pi Sync check \xB7 ${safeTerminalText(observation.setupName)}`,
|
|
119
|
+
observationSummary(observation),
|
|
120
|
+
"No startup transfer \xB7 Run /sync to review"
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
function clearAttentionPresentation(ctx) {
|
|
124
|
+
ctx.ui.setStatus(STATUS_KEY, void 0);
|
|
125
|
+
ctx.ui.setWidget(WIDGET_KEY, void 0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
syncAttentionMatchesConfig,
|
|
130
|
+
createSyncAttentionController,
|
|
131
|
+
observationMatchesConfig,
|
|
132
|
+
observationSummary
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=chunk-6UHKGALE.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/ui/sync-attention.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport { truncateToWidth } from \"@earendil-works/pi-tui\";\nimport { syncCheckConfigFingerprint, syncConfigReviewFingerprint } from \"../settings/config.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport type { StartupObservation } from \"../sync/sync-inspection.js\";\nimport {\n\tcompareSyncInclude,\n\ttype RemoteSelectionDecision,\n\tsameSyncInclude,\n} from \"../sync/sync-policy.js\";\nimport { safeTerminalText } from \"./terminal-text.js\";\n\nconst STATUS_KEY = \"sync\";\nconst WIDGET_KEY = \"sync:attention\";\n\nexport type SyncAttentionOrigin = \"sync\" | \"pull\" | \"push\";\n\nexport interface SyncAttentionState {\n\tdecision: RemoteSelectionDecision;\n\torigin: SyncAttentionOrigin;\n\toffered: boolean;\n}\n\nexport function syncAttentionMatchesConfig(attention: SyncAttentionState, config: AnySyncConfig) {\n\treturn (\n\t\tattention.decision.setupName === config.setupName &&\n\t\tattention.decision.configIdentity === syncConfigReviewFingerprint(config) &&\n\t\tsameSyncInclude(attention.decision.localInclude, config.include)\n\t);\n}\n\nexport interface SyncAttentionController {\n\tobserve(observation: StartupObservation): void;\n\tobservation(): StartupObservation | undefined;\n\tclearObservation(): void;\n\tnotifyObservation(ctx: ExtensionContext): void;\n\tset(decision: RemoteSelectionDecision, origin: SyncAttentionOrigin): void;\n\tcurrent(): SyncAttentionState | undefined;\n\tmarkOffered(): boolean;\n\tclear(ctx: ExtensionContext): void;\n\treset(ctx: ExtensionContext): void;\n\tpublish(ctx: ExtensionContext): void;\n}\n\nexport function createSyncAttentionController(): SyncAttentionController {\n\tlet state: SyncAttentionState | undefined;\n\tlet observation: StartupObservation | undefined;\n\n\treturn {\n\t\tobserve(value) {\n\t\t\tobservation = value;\n\t\t},\n\t\tobservation() {\n\t\t\treturn observation;\n\t\t},\n\t\tclearObservation() {\n\t\t\tobservation = undefined;\n\t\t},\n\t\tnotifyObservation(ctx) {\n\t\t\tif (observation && observationNeedsAttention(observation)) {\n\t\t\t\tctx.ui.notify(observationLines(observation).join(\"\\n\"), \"warning\");\n\t\t\t}\n\t\t},\n\t\tset(decision, origin) {\n\t\t\tstate = { decision, origin, offered: false };\n\t\t},\n\t\tcurrent() {\n\t\t\treturn state;\n\t\t},\n\t\tmarkOffered() {\n\t\t\tif (!state || state.offered) return false;\n\t\t\tstate = { ...state, offered: true };\n\t\t\treturn true;\n\t\t},\n\t\tclear(ctx) {\n\t\t\tstate = undefined;\n\t\t\tclearAttentionPresentation(ctx);\n\t\t},\n\t\treset(ctx) {\n\t\t\tstate = undefined;\n\t\t\tobservation = undefined;\n\t\t\tclearAttentionPresentation(ctx);\n\t\t},\n\t\tpublish(ctx) {\n\t\t\tif (!state && (!observation || !observationNeedsAttention(observation))) {\n\t\t\t\tclearAttentionPresentation(ctx);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst presentation = state\n\t\t\t\t? attentionPresentation(state.decision)\n\t\t\t\t: {\n\t\t\t\t\t\tstatus: \"changes to review\",\n\t\t\t\t\t\tlines: observationLines(observation as StartupObservation),\n\t\t\t\t\t};\n\t\t\tctx.ui.setStatus(STATUS_KEY, presentation.status);\n\t\t\tif (ctx.mode !== \"tui\") return;\n\t\t\tctx.ui.setWidget(WIDGET_KEY, (_tui, theme) => ({\n\t\t\t\trender(width: number) {\n\t\t\t\t\tconst safeWidth = Math.max(1, width);\n\t\t\t\t\treturn presentation.lines.map((line, index) =>\n\t\t\t\t\t\ttruncateToWidth(theme.fg(index === 0 ? \"warning\" : \"muted\", line), safeWidth, \"\u2026\"),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\tinvalidate() {},\n\t\t\t}));\n\t\t},\n\t};\n}\n\nfunction attentionPresentation(decision: RemoteSelectionDecision) {\n\tconst setupName = safeTerminalText(decision.setupName);\n\tconst comparison = compareSyncInclude(decision.localInclude, decision.remoteInclude);\n\tconst difference =\n\t\tcomparison.remoteOnly.length === 0 && comparison.localOnly.length === 0\n\t\t\t? \"Only list order differs\"\n\t\t\t: `Remote ${comparison.remoteOnly.length} \u00B7 Device ${comparison.localOnly.length}`;\n\treturn {\n\t\tstatus: \"review needed\",\n\t\tlines: [`Pi Sync needs review \u00B7 ${setupName}`, difference, \"No changes \u00B7 Run /sync to review\"],\n\t};\n}\n\nexport function observationMatchesConfig(observation: StartupObservation, config: AnySyncConfig) {\n\treturn observation.configIdentity === syncCheckConfigFingerprint(config);\n}\n\nexport function observationSummary(observation: StartupObservation) {\n\tconst result = observation.inspection;\n\tif (result.emptyInclude) return \"No included content selected\";\n\tif (result.selectionState?.kind === \"different\")\n\t\treturn \"Synced-content list differs; review needed\";\n\tif (result.firstSync)\n\t\treturn result.head\n\t\t\t? \"No sync baseline; review remote and local content\"\n\t\t\t: \"Remote empty; no sync baseline\";\n\tif (!result.head) return \"Remote snapshot missing; review needed\";\n\tif (result.localChanged && result.remoteChanged)\n\t\treturn \"Local and remote changed since last sync; review needed\";\n\tif (result.remoteChanged) return \"Remote changed since last sync\";\n\tif (result.localChanged) return \"Local content changed since last sync\";\n\tif (result.selectionState?.kind === \"legacy\")\n\t\treturn \"Legacy remote has no authoritative content list\";\n\treturn \"No changes detected since last sync\";\n}\n\nexport function observationNeedsAttention(observation: StartupObservation) {\n\tconst result = observation.inspection;\n\treturn (\n\t\tresult.emptyInclude ||\n\t\tresult.firstSync ||\n\t\t!result.head ||\n\t\tresult.localChanged ||\n\t\tresult.remoteChanged ||\n\t\tresult.selectionState?.kind !== \"same\"\n\t);\n}\n\nfunction observationLines(observation: StartupObservation) {\n\treturn [\n\t\t`Pi Sync check \u00B7 ${safeTerminalText(observation.setupName)}`,\n\t\tobservationSummary(observation),\n\t\t\"No startup transfer \u00B7 Run /sync to review\",\n\t];\n}\n\nfunction clearAttentionPresentation(ctx: ExtensionContext) {\n\tctx.ui.setStatus(STATUS_KEY, undefined);\n\tctx.ui.setWidget(WIDGET_KEY, undefined);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
filterSnapshotForConfigPolicy,
|
|
5
5
|
isSessionPath
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-OEX4ABY6.ts";
|
|
7
7
|
import {
|
|
8
8
|
canonicalSnapshotPathForConfig,
|
|
9
9
|
customIncludePathsByLower,
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
isConfiguredSnapshotPath,
|
|
12
12
|
normalizeSyncInclude,
|
|
13
13
|
toPosix
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-JAAKLL6M.ts";
|
|
15
15
|
|
|
16
|
-
// src/sync-state.ts
|
|
16
|
+
// src/sync/sync-state.ts
|
|
17
17
|
function hasLocalChanges(local, state, config) {
|
|
18
18
|
return !sameHashes(fileHashMap(local), stateHashMapForConfig(state, config));
|
|
19
19
|
}
|
|
@@ -125,4 +125,4 @@ export {
|
|
|
125
125
|
canPullRemoteSettingsOnFirstSync,
|
|
126
126
|
canPullRemoteSessionsOnFirstSync
|
|
127
127
|
};
|
|
128
|
-
//# sourceMappingURL=chunk-
|
|
128
|
+
//# sourceMappingURL=chunk-AUSDLD6F.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/sync/sync-state.ts"],
|
|
4
|
+
"sourcesContent": ["import type { RemoteHead } from \"../backends/sync-backend.js\";\nimport { toPosix } from \"../paths.js\";\nimport {\n\tcanonicalSnapshotPathForConfig,\n\tfilterSnapshotForConfigPolicy,\n\tisConfiguredSnapshotPath,\n\tisSessionPath,\n} from \"../snapshot/snapshot.js\";\nimport type { Snapshot } from \"../snapshot/snapshot-types.js\";\nimport type { SyncState } from \"../state/state-types.js\";\nimport {\n\tcustomIncludePathsByLower,\n\tincludeFromSelectionConfig,\n\tnormalizeSyncInclude,\n\ttype SyncSelectionConfig,\n} from \"./sync-policy.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
|
+
}
|
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
3
|
import {
|
|
4
4
|
withLock
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-GWMHAQG4.ts";
|
|
6
|
+
import {
|
|
7
|
+
stateDir
|
|
8
|
+
} from "./chunk-ZI5MXMEX.ts";
|
|
6
9
|
import {
|
|
7
10
|
sessionStorageRoot
|
|
8
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-THMF4BVL.ts";
|
|
9
12
|
import {
|
|
10
13
|
agentDir
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import {
|
|
13
|
-
stateDir
|
|
14
|
-
} from "./chunk-F5QL2GPY.ts";
|
|
14
|
+
} from "./chunk-T3HH3UEJ.ts";
|
|
15
15
|
import {
|
|
16
16
|
assertWithinRoot,
|
|
17
17
|
isPathInside
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-JAAKLL6M.ts";
|
|
19
19
|
|
|
20
|
-
// src/snapshot-transaction.ts
|
|
20
|
+
// src/snapshot/snapshot-transaction.ts
|
|
21
21
|
import { randomUUID } from "node:crypto";
|
|
22
22
|
import fs from "node:fs/promises";
|
|
23
23
|
import path from "node:path";
|
|
@@ -200,4 +200,4 @@ export {
|
|
|
200
200
|
recoverSnapshotTransactionsOnStartup,
|
|
201
201
|
recoverPendingSnapshotTransactions
|
|
202
202
|
};
|
|
203
|
-
//# sourceMappingURL=chunk-
|
|
203
|
+
//# sourceMappingURL=chunk-AXXS2BPA.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/snapshot/snapshot-transaction.ts"],
|
|
4
|
+
"sourcesContent": ["import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { assertWithinRoot, isPathInside } from \"../paths.js\";\nimport { withLock } from \"../state/lock.js\";\nimport { stateDir } from \"../state/state-directory.js\";\nimport { agentDir } from \"./session-paths.js\";\nimport { sessionStorageRoot } from \"./snapshot-paths.js\";\nimport type { SnapshotApplyPlan } from \"./snapshot-types.js\";\n\nconst JOURNAL_VERSION = 1;\n\ninterface TransactionEntry {\n\ttarget: string;\n\tbackupName: string;\n\tkind: \"missing\" | \"file\" | \"directory\" | \"symlink\";\n\tlinkTarget?: string;\n}\n\ninterface TransactionJournal {\n\tversion: number;\n\troot: string;\n\tsessionRoot?: string;\n\tentries: TransactionEntry[];\n}\n\nexport async function applySnapshotTransaction(\n\tplan: SnapshotApplyPlan,\n\toptions: { sessionDir?: string } = {},\n) {\n\tawait recoverPendingSnapshotTransactions();\n\tconst transaction = await prepareTransaction(plan, options.sessionDir);\n\ttry {\n\t\tfor (const target of plan.deletes) {\n\t\t\tawait fs.rm(target, { force: true, recursive: true });\n\t\t}\n\t\tfor (const item of plan.writes) {\n\t\t\tawait fs.mkdir(path.dirname(item.target), { recursive: true });\n\t\t\tawait fs.writeFile(item.target, item.content);\n\t\t}\n\t\tawait fs.rm(transaction.directory, { recursive: true, force: true });\n\t} catch (error) {\n\t\ttry {\n\t\t\tawait restoreTransaction(transaction.directory, transaction.journal);\n\t\t} catch (recoveryError) {\n\t\t\tthrow new AggregateError(\n\t\t\t\t[error, recoveryError],\n\t\t\t\t`Snapshot apply failed and automatic recovery also failed. Transaction retained at ${transaction.directory}.`,\n\t\t\t);\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nexport async function recoverSnapshotTransactionsOnStartup() {\n\tif (!(await pendingTransactionEntries()).some((entry) => entry.isDirectory())) return;\n\tawait withLock(\"recovery\", recoverPendingSnapshotTransactions, { reclaimStale: true });\n}\n\nexport async function recoverPendingSnapshotTransactions() {\n\tconst directory = transactionRoot();\n\tconst entries = await pendingTransactionEntries();\n\tfor (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {\n\t\tif (!entry.isDirectory()) continue;\n\t\tconst transactionDirectory = path.join(directory, entry.name);\n\t\tconst journalPath = path.join(transactionDirectory, \"journal.json\");\n\t\tlet journal: TransactionJournal;\n\t\ttry {\n\t\t\tjournal = JSON.parse(await fs.readFile(journalPath, \"utf8\")) as TransactionJournal;\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\t\tawait fs.rm(transactionDirectory, { recursive: true, force: true });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthrow new Error(`Cannot recover malformed pi-sync transaction: ${journalPath}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\tawait restoreTransaction(transactionDirectory, journal);\n\t}\n}\n\nasync function pendingTransactionEntries() {\n\ttry {\n\t\treturn await fs.readdir(transactionRoot(), { withFileTypes: true });\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n\t\tthrow error;\n\t}\n}\n\nasync function prepareTransaction(plan: SnapshotApplyPlan, sessionDir?: string) {\n\tconst root = path.resolve(agentDir());\n\tconst sessionRoot = sessionDir ? path.resolve(sessionStorageRoot(root, sessionDir)) : undefined;\n\tconst directory = path.join(transactionRoot(), randomUUID());\n\tconst backupDirectory = path.join(directory, \"before\");\n\tawait fs.mkdir(backupDirectory, { recursive: true, mode: 0o700 });\n\tconst targets = [...new Set([...plan.deletes, ...plan.writes.map((item) => item.target)])].sort();\n\tconst entries: TransactionEntry[] = [];\n\tfor (let index = 0; index < targets.length; index += 1) {\n\t\tconst target = targets[index];\n\t\tif (!target) continue;\n\t\tassertAllowedTarget(root, sessionRoot, target);\n\t\tconst backupName = `${index}`;\n\t\tconst backupPath = path.join(backupDirectory, backupName);\n\t\ttry {\n\t\t\tconst stat = await fs.lstat(target);\n\t\t\tif (stat.isSymbolicLink()) {\n\t\t\t\tentries.push({\n\t\t\t\t\ttarget,\n\t\t\t\t\tbackupName,\n\t\t\t\t\tkind: \"symlink\",\n\t\t\t\t\tlinkTarget: await fs.readlink(target),\n\t\t\t\t});\n\t\t\t} else if (stat.isDirectory()) {\n\t\t\t\tawait fs.cp(target, backupPath, {\n\t\t\t\t\trecursive: true,\n\t\t\t\t\tdereference: false,\n\t\t\t\t\tpreserveTimestamps: true,\n\t\t\t\t});\n\t\t\t\tentries.push({ target, backupName, kind: \"directory\" });\n\t\t\t} else if (stat.isFile()) {\n\t\t\t\tawait fs.copyFile(target, backupPath);\n\t\t\t\tawait fs.chmod(backupPath, stat.mode);\n\t\t\t\tentries.push({ target, backupName, kind: \"file\" });\n\t\t\t} else {\n\t\t\t\tthrow new Error(`Unsupported existing snapshot target: ${target}`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst code = (error as NodeJS.ErrnoException).code;\n\t\t\tif (code !== \"ENOENT\" && code !== \"ENOTDIR\") throw error;\n\t\t\tentries.push({ target, backupName, kind: \"missing\" });\n\t\t}\n\t}\n\tconst journal: TransactionJournal = {\n\t\tversion: JOURNAL_VERSION,\n\t\troot,\n\t\tsessionRoot,\n\t\tentries,\n\t};\n\tawait fs.writeFile(\n\t\tpath.join(directory, \"journal.json\"),\n\t\t`${JSON.stringify(journal, null, \"\\t\")}\\n`,\n\t\t{\n\t\t\tmode: 0o600,\n\t\t},\n\t);\n\treturn { directory, journal };\n}\n\nasync function restoreTransaction(directory: string, journal: TransactionJournal) {\n\tvalidateJournal(directory, journal);\n\tfor (const entry of [...journal.entries].sort(\n\t\t(left, right) => right.target.length - left.target.length,\n\t)) {\n\t\tawait fs.rm(entry.target, { recursive: true, force: true });\n\t}\n\tfor (const entry of journal.entries) {\n\t\tif (entry.kind === \"missing\") continue;\n\t\tawait fs.mkdir(path.dirname(entry.target), { recursive: true });\n\t\tconst backupPath = path.join(directory, \"before\", entry.backupName);\n\t\tassertWithinRoot(directory, backupPath);\n\t\tif (entry.kind === \"file\") await fs.copyFile(backupPath, entry.target);\n\t\telse if (entry.kind === \"directory\") {\n\t\t\tawait fs.cp(backupPath, entry.target, {\n\t\t\t\trecursive: true,\n\t\t\t\tdereference: false,\n\t\t\t\tpreserveTimestamps: true,\n\t\t\t});\n\t\t} else if (entry.kind === \"symlink\" && entry.linkTarget !== undefined) {\n\t\t\tawait fs.symlink(entry.linkTarget, entry.target);\n\t\t}\n\t}\n\tawait fs.rm(directory, { recursive: true, force: true });\n}\n\nfunction validateJournal(directory: string, journal: TransactionJournal) {\n\tif (journal.version !== JOURNAL_VERSION || !Array.isArray(journal.entries)) {\n\t\tthrow new Error(`Unsupported pi-sync transaction journal: ${directory}`);\n\t}\n\tconst expectedRoot = path.resolve(agentDir());\n\tif (path.resolve(journal.root) !== expectedRoot) {\n\t\tthrow new Error(`Transaction root no longer matches the Pi agent directory: ${directory}`);\n\t}\n\tfor (const entry of journal.entries) {\n\t\tif (\n\t\t\t!entry ||\n\t\t\ttypeof entry.target !== \"string\" ||\n\t\t\ttypeof entry.backupName !== \"string\" ||\n\t\t\t!/^\\d+$/u.test(entry.backupName)\n\t\t) {\n\t\t\tthrow new Error(`Invalid pi-sync transaction entry: ${directory}`);\n\t\t}\n\t\tassertAllowedTarget(journal.root, journal.sessionRoot, entry.target);\n\t}\n}\n\nfunction assertAllowedTarget(root: string, sessionRoot: string | undefined, target: string) {\n\tconst resolved = path.resolve(target);\n\tif (isPathInside(root, resolved)) {\n\t\tassertWithinRoot(root, resolved);\n\t\treturn;\n\t}\n\tif (sessionRoot && isPathInside(sessionRoot, resolved)) {\n\t\tassertWithinRoot(sessionRoot, resolved);\n\t\treturn;\n\t}\n\tthrow new Error(`Transaction target is outside configured roots: ${target}`);\n}\n\nfunction transactionRoot() {\n\treturn path.join(stateDir(), \"transactions\");\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|