@narumitw/pi-sync 0.50.3 → 0.51.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 +4 -1
- package/dist/chunks/{chunk-6LP3MCR2.ts → chunk-2JZFHNLM.ts} +3 -5
- package/dist/chunks/chunk-2JZFHNLM.ts.map +7 -0
- package/dist/chunks/{chunk-LCRI3ABW.ts → chunk-2RZSQWBH.ts} +8 -19
- package/dist/chunks/chunk-2RZSQWBH.ts.map +7 -0
- package/dist/chunks/{chunk-IJ6FJFA5.ts → chunk-4AIHXRKE.ts} +11 -17
- package/dist/chunks/chunk-4AIHXRKE.ts.map +7 -0
- package/dist/chunks/{chunk-C7XY5NF5.ts → chunk-4MPIH46N.ts} +29 -51
- package/dist/chunks/chunk-4MPIH46N.ts.map +7 -0
- package/dist/chunks/{chunk-5B3FXBLE.ts → chunk-6546TS54.ts} +16 -20
- package/dist/chunks/chunk-6546TS54.ts.map +7 -0
- package/dist/chunks/{chunk-Q2TUM5X6.ts → chunk-6NT3YFGY.ts} +2 -2
- package/dist/chunks/chunk-6NT3YFGY.ts.map +7 -0
- package/dist/chunks/{chunk-2B2ELUF5.ts → chunk-7D5SF7P3.ts} +1 -1
- package/dist/chunks/chunk-7D5SF7P3.ts.map +7 -0
- package/dist/chunks/{chunk-YDWUZVPN.ts → chunk-7KHKWL3Q.ts} +28 -47
- package/dist/chunks/chunk-7KHKWL3Q.ts.map +7 -0
- package/dist/chunks/{chunk-ZI5MXMEX.ts → chunk-BVVP33U5.ts} +2 -4
- package/dist/chunks/chunk-BVVP33U5.ts.map +7 -0
- package/dist/chunks/{chunk-SZWBGPID.ts → chunk-DM4X4FIA.ts} +8 -13
- package/dist/chunks/chunk-DM4X4FIA.ts.map +7 -0
- package/dist/chunks/{chunk-SL64RLXR.ts → chunk-DZUSTGQV.ts} +8 -16
- package/dist/chunks/chunk-DZUSTGQV.ts.map +7 -0
- package/dist/chunks/{chunk-YHEMENKV.ts → chunk-E42GXD6D.ts} +13 -19
- package/dist/chunks/chunk-E42GXD6D.ts.map +7 -0
- package/dist/chunks/{chunk-Z4DQF6L3.ts → chunk-ECYCNZOE.ts} +1 -1
- package/dist/chunks/chunk-ECYCNZOE.ts.map +7 -0
- package/dist/chunks/{chunk-OEX4ABY6.ts → chunk-EEJPYTWE.ts} +11 -21
- package/dist/chunks/chunk-EEJPYTWE.ts.map +7 -0
- package/dist/chunks/{chunk-CPEHGXJV.ts → chunk-G4RUXRWZ.ts} +3 -5
- package/dist/chunks/chunk-G4RUXRWZ.ts.map +7 -0
- package/dist/chunks/{chunk-T2B5IU74.ts → chunk-HGJH56KR.ts} +4 -4
- package/dist/chunks/chunk-HGJH56KR.ts.map +7 -0
- package/dist/chunks/{chunk-JAAKLL6M.ts → chunk-IVXEDPAD.ts} +10 -34
- package/dist/chunks/chunk-IVXEDPAD.ts.map +7 -0
- package/dist/chunks/chunk-K3RIEL6J.ts +20 -0
- package/dist/chunks/chunk-K3RIEL6J.ts.map +7 -0
- package/dist/chunks/{chunk-LTBWGSBV.ts → chunk-MSSW6F3R.ts} +1 -1
- package/dist/chunks/chunk-MSSW6F3R.ts.map +7 -0
- package/dist/chunks/{chunk-T3HH3UEJ.ts → chunk-OVDTF5CH.ts} +4 -10
- package/dist/chunks/chunk-OVDTF5CH.ts.map +7 -0
- package/dist/chunks/{chunk-NJFUIVYL.ts → chunk-PWMWOJP4.ts} +1 -1
- package/dist/chunks/chunk-PWMWOJP4.ts.map +7 -0
- package/dist/chunks/{chunk-HUDQATCL.ts → chunk-Q32CQWPM.ts} +6 -8
- package/dist/chunks/chunk-Q32CQWPM.ts.map +7 -0
- package/dist/chunks/{chunk-ETTRJDNP.ts → chunk-QLSN7AGX.ts} +6 -13
- package/dist/chunks/chunk-QLSN7AGX.ts.map +7 -0
- package/dist/chunks/{chunk-THMF4BVL.ts → chunk-SWUXA3MH.ts} +1 -1
- package/dist/chunks/chunk-SWUXA3MH.ts.map +7 -0
- package/dist/chunks/{chunk-TEN2ZBJV.ts → chunk-T2SJD3N7.ts} +2 -4
- package/dist/chunks/chunk-T2SJD3N7.ts.map +7 -0
- package/dist/chunks/{chunk-AUSDLD6F.ts → chunk-T56RNRZJ.ts} +10 -26
- package/dist/chunks/chunk-T56RNRZJ.ts.map +7 -0
- package/dist/chunks/{chunk-DHVFS4QK.ts → chunk-UA7RXOIT.ts} +37 -90
- package/dist/chunks/chunk-UA7RXOIT.ts.map +7 -0
- package/dist/chunks/{chunk-D5X27LCA.ts → chunk-UETBKTDI.ts} +1 -1
- package/dist/chunks/chunk-UETBKTDI.ts.map +7 -0
- package/dist/chunks/{chunk-CHH26D4F.ts → chunk-YFADF5ZQ.ts} +7 -6
- package/dist/chunks/chunk-YFADF5ZQ.ts.map +7 -0
- package/dist/chunks/{chunk-CCSN3F47.ts → chunk-YPXGU5OR.ts} +4 -4
- package/dist/chunks/chunk-YPXGU5OR.ts.map +7 -0
- package/dist/chunks/{file-selection-FTTKAOYX.ts → file-selection-RAVHRYFG.ts} +14 -26
- package/dist/chunks/file-selection-RAVHRYFG.ts.map +7 -0
- package/dist/chunks/{git-backend-GMVS4PD4.ts → git-backend-IAANDL3K.ts} +41 -104
- package/dist/chunks/git-backend-IAANDL3K.ts.map +7 -0
- package/dist/chunks/manager-result-dispatcher-OTUMQM26.ts +23 -0
- package/dist/chunks/{manager-ui-IPAOAWZA.ts → manager-ui-763NEKJY.ts} +124 -251
- package/dist/chunks/manager-ui-763NEKJY.ts.map +7 -0
- package/dist/chunks/remote-selection-ui-TZTJF32Y.ts +22 -0
- package/dist/chunks/{s3-backend-66O4FGBH.ts → s3-backend-XGXUOPUN.ts} +23 -54
- package/dist/chunks/s3-backend-XGXUOPUN.ts.map +7 -0
- package/dist/chunks/{setup-switch-IS2PEZ3J.ts → setup-switch-5G2VNJHL.ts} +9 -9
- package/dist/chunks/{setup-wizard-JUJZYNNM.ts → setup-wizard-YIEWGX32.ts} +12 -12
- package/dist/chunks/setup-wizard-YIEWGX32.ts.map +7 -0
- package/dist/chunks/{snapshot-HKEWCVK6.ts → snapshot-JL5DFSXI.ts} +5 -5
- package/dist/chunks/sync-inspection-P3ENJLNF.ts +20 -0
- package/dist/chunks/{sync-operations-LBX3GKCM.ts → sync-operations-AR76EB6C.ts} +85 -201
- package/dist/chunks/sync-operations-AR76EB6C.ts.map +7 -0
- package/dist/chunks/{sync-state-Q2FTXTW6.ts → sync-state-VHJUZ4YG.ts} +6 -6
- package/dist/chunks/{webdav-backend-ECIKD7SO.ts → webdav-backend-3EGUONAT.ts} +20 -51
- package/dist/chunks/webdav-backend-3EGUONAT.ts.map +7 -0
- package/dist/index.ts +49 -74
- package/dist/index.ts.map +3 -3
- package/docs/settings.md +9 -0
- package/package.json +61 -62
- package/src/backends/backend-factory.ts +15 -15
- package/src/backends/backend-identity.ts +31 -31
- package/src/backends/backend-types.ts +42 -42
- package/src/backends/git/git-backend.ts +714 -788
- package/src/backends/git/git-config.ts +104 -111
- package/src/backends/git/git-runner.ts +230 -247
- package/src/backends/git/git-storage.ts +228 -245
- package/src/backends/s3/s3-backend.ts +408 -441
- package/src/backends/s3/s3-client.ts +288 -333
- package/src/backends/sync-backend.ts +65 -65
- package/src/backends/webdav/webdav-backend.ts +542 -589
- package/src/backends/webdav/webdav-client.ts +292 -316
- package/src/backends/webdav/webdav-config.ts +57 -68
- package/src/commands/command-execution.ts +270 -289
- package/src/commands/command-handler.ts +160 -163
- package/src/commands/command-types.ts +13 -13
- package/src/commands/command.ts +190 -196
- package/src/commands/setup-completions.ts +1 -1
- package/src/paths.ts +47 -47
- package/src/settings/config-errors.ts +4 -5
- package/src/settings/config-file.ts +473 -506
- package/src/settings/config.ts +171 -174
- package/src/settings/settings-management.ts +191 -216
- package/src/settings/settings-store.ts +63 -77
- package/src/settings/settings-types.ts +78 -75
- package/src/settings/settings-validation.ts +328 -364
- package/src/snapshot/session-paths.ts +41 -55
- package/src/snapshot/snapshot-apply.ts +197 -229
- package/src/snapshot/snapshot-codec.ts +29 -31
- package/src/snapshot/snapshot-paths.ts +4 -6
- package/src/snapshot/snapshot-transaction.ts +161 -170
- package/src/snapshot/snapshot-types.ts +24 -24
- package/src/snapshot/snapshot.ts +310 -343
- package/src/state/json-file.ts +11 -11
- package/src/state/lock.ts +226 -242
- package/src/state/lockfile-fs.ts +20 -20
- package/src/state/operation-availability.ts +36 -41
- package/src/state/state-directory.ts +171 -173
- package/src/state/state-types.ts +15 -15
- package/src/state/sync-state-store.ts +23 -23
- package/src/sync/automatic-sync.ts +75 -80
- package/src/sync/remote-snapshot.ts +52 -60
- package/src/sync/setup-switch.ts +99 -114
- package/src/sync/signals.ts +3 -5
- package/src/sync/startup-check.ts +97 -111
- package/src/sync/sync-decision.ts +76 -80
- package/src/sync/sync-error-guidance.ts +41 -48
- package/src/sync/sync-errors.ts +10 -10
- package/src/sync/sync-inspection.ts +29 -33
- package/src/sync/sync-loaders.ts +28 -36
- package/src/sync/sync-mutations.ts +510 -622
- package/src/sync/sync-operations.ts +7 -7
- package/src/sync/sync-policy.ts +296 -326
- package/src/sync/sync-queries.ts +175 -194
- package/src/sync/sync-state.ts +74 -100
- package/src/sync-extension.ts +89 -101
- package/src/sync.ts +26 -26
- package/src/types.ts +37 -37
- package/src/ui/automatic-sync-summary.ts +1 -3
- package/src/ui/cancellable-operation.ts +89 -101
- package/src/ui/file-selection.ts +250 -274
- package/src/ui/manager-attention.ts +51 -63
- package/src/ui/manager-recovery.ts +48 -57
- package/src/ui/manager-result-dispatcher.ts +62 -76
- package/src/ui/manager-state.ts +200 -232
- package/src/ui/manager-ui.ts +264 -362
- package/src/ui/remote-selection-ui.ts +547 -588
- package/src/ui/secret-input.ts +197 -205
- package/src/ui/settings-ui.ts +175 -172
- package/src/ui/setup/git-ui.ts +232 -261
- package/src/ui/setup/s3-credentials-ui.ts +58 -65
- package/src/ui/setup/s3-ui.ts +171 -195
- package/src/ui/setup/setup-actions.ts +91 -101
- package/src/ui/setup/setup-location-ui.ts +121 -137
- package/src/ui/setup/setup-name-ui.ts +27 -27
- package/src/ui/setup/setup-prompts.ts +65 -65
- package/src/ui/setup/setup-review.ts +58 -59
- package/src/ui/setup/setup-switcher.ts +156 -160
- package/src/ui/setup/setup-wizard.ts +28 -28
- package/src/ui/setup/text-input.ts +54 -58
- package/src/ui/setup/webdav-ui.ts +246 -293
- package/src/ui/storage-connections-ui.ts +317 -369
- package/src/ui/sync-attention.ts +148 -164
- package/src/ui/sync-format.ts +110 -121
- package/src/ui/sync-resolution-ui.ts +147 -153
- package/src/ui/sync-setups-ui.ts +218 -238
- package/src/ui/sync-status.ts +17 -0
- package/src/ui/terminal-text.ts +2 -2
- package/dist/chunks/chunk-2B2ELUF5.ts.map +0 -7
- package/dist/chunks/chunk-5B3FXBLE.ts.map +0 -7
- package/dist/chunks/chunk-6LP3MCR2.ts.map +0 -7
- package/dist/chunks/chunk-AUSDLD6F.ts.map +0 -7
- package/dist/chunks/chunk-C7XY5NF5.ts.map +0 -7
- package/dist/chunks/chunk-CCSN3F47.ts.map +0 -7
- package/dist/chunks/chunk-CHH26D4F.ts.map +0 -7
- package/dist/chunks/chunk-CPEHGXJV.ts.map +0 -7
- package/dist/chunks/chunk-D5X27LCA.ts.map +0 -7
- package/dist/chunks/chunk-DHVFS4QK.ts.map +0 -7
- package/dist/chunks/chunk-ETTRJDNP.ts.map +0 -7
- package/dist/chunks/chunk-HUDQATCL.ts.map +0 -7
- package/dist/chunks/chunk-IJ6FJFA5.ts.map +0 -7
- package/dist/chunks/chunk-JAAKLL6M.ts.map +0 -7
- package/dist/chunks/chunk-LCRI3ABW.ts.map +0 -7
- package/dist/chunks/chunk-LTBWGSBV.ts.map +0 -7
- package/dist/chunks/chunk-NJFUIVYL.ts.map +0 -7
- package/dist/chunks/chunk-OEX4ABY6.ts.map +0 -7
- package/dist/chunks/chunk-Q2TUM5X6.ts.map +0 -7
- package/dist/chunks/chunk-SL64RLXR.ts.map +0 -7
- package/dist/chunks/chunk-SZWBGPID.ts.map +0 -7
- package/dist/chunks/chunk-T2B5IU74.ts.map +0 -7
- package/dist/chunks/chunk-T3HH3UEJ.ts.map +0 -7
- package/dist/chunks/chunk-TEN2ZBJV.ts.map +0 -7
- package/dist/chunks/chunk-THMF4BVL.ts.map +0 -7
- package/dist/chunks/chunk-YDWUZVPN.ts.map +0 -7
- package/dist/chunks/chunk-YHEMENKV.ts.map +0 -7
- package/dist/chunks/chunk-Z4DQF6L3.ts.map +0 -7
- package/dist/chunks/chunk-ZI5MXMEX.ts.map +0 -7
- package/dist/chunks/file-selection-FTTKAOYX.ts.map +0 -7
- package/dist/chunks/git-backend-GMVS4PD4.ts.map +0 -7
- package/dist/chunks/manager-result-dispatcher-ASKGOSW5.ts +0 -22
- package/dist/chunks/manager-ui-IPAOAWZA.ts.map +0 -7
- package/dist/chunks/remote-selection-ui-QDNR7WEI.ts +0 -21
- package/dist/chunks/s3-backend-66O4FGBH.ts.map +0 -7
- package/dist/chunks/setup-wizard-JUJZYNNM.ts.map +0 -7
- package/dist/chunks/sync-inspection-CST2KL65.ts +0 -20
- package/dist/chunks/sync-operations-LBX3GKCM.ts.map +0 -7
- package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +0 -7
- /package/dist/chunks/{manager-result-dispatcher-ASKGOSW5.ts.map → manager-result-dispatcher-OTUMQM26.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-QDNR7WEI.ts.map → remote-selection-ui-TZTJF32Y.ts.map} +0 -0
- /package/dist/chunks/{setup-switch-IS2PEZ3J.ts.map → setup-switch-5G2VNJHL.ts.map} +0 -0
- /package/dist/chunks/{snapshot-HKEWCVK6.ts.map → snapshot-JL5DFSXI.ts.map} +0 -0
- /package/dist/chunks/{sync-inspection-CST2KL65.ts.map → sync-inspection-P3ENJLNF.ts.map} +0 -0
- /package/dist/chunks/{sync-state-Q2FTXTW6.ts.map → sync-state-VHJUZ4YG.ts.map} +0 -0
package/README.md
CHANGED
|
@@ -131,7 +131,7 @@ Then choose one reviewed direction:
|
|
|
131
131
|
Cancelling a preparation or confirmation returns to conflict resolution with no side effects.
|
|
132
132
|
Back returns to the sync manager, and Ctrl+C closes the complete flow.
|
|
133
133
|
|
|
134
|
-
Startup checks never open a dialog. With a sync baseline and no content-list mismatch,
|
|
134
|
+
Startup checks never open a dialog. While checking, TUI and RPC status show **sync ...**. With a sync baseline and no content-list mismatch, they use **sync ⇡** for local changes to push and **sync ⇣** for remote changes to pull. Review conditions use **sync ⇕**; in TUI, a persistent widget above the editor also identifies both sides changing, first sync with an existing remote snapshot, a differing content list (including order-only differences), or a missing remote snapshot despite an existing baseline. Both sides changing is a reason to review, not a confirmed file conflict.
|
|
135
135
|
No selected content and first sync with an empty remote stay quiet outside `/sync`, which provides setup or initialization guidance. Legacy remote metadata without an authoritative content list does not independently trigger a widget; the baseline and change conditions still apply. An included-content mismatch puts **Review synced content (recommended)** in the manager, where a fresh review verifies the remote snapshot before offering changes.
|
|
136
136
|
Check results are advisory observations against the last sync baseline, not proof of a file conflict or current equality. Opening the manager uses local information and shows when the check completed; it does not contact remote storage. Transfer actions recheck current content.
|
|
137
137
|
The widget has no expiry timer. Attention stays in memory and is invalidated by relevant settings/state changes or a foreground transfer's commit boundary, and cleared on session replacement or shutdown. A newer observation replaces the presentation, clearing the widget when only status or no reminder is needed. Cancelling a review or a failure before commit preserves a still-valid observation and its appropriate presentation.
|
|
@@ -158,6 +158,8 @@ Checks run once per session start, including `/reload`, new, resumed, and forked
|
|
|
158
158
|
|
|
159
159
|
Shutdown behavior is unchanged: when **Automatic sync** is On and sessions are included, pi-sync can automatically push the selected content, not only session files. This also applies to headless modes; shutdown never opens a dialog, and `/reload` skips this push. Turning the setting Off disables both future startup checks and automatic shutdown pushes.
|
|
160
160
|
|
|
161
|
+
**Settings → Show status (all setups)** defaults to **On**. Turning it Off immediately clears and suppresses pi-sync status text for background checks, transfers, and review attention; widgets and notifications remain available.
|
|
162
|
+
|
|
161
163
|
**Settings → Skip secret scan (all setups)** defaults to **Off**; enable it only after reviewing the destination and selected content because it disables push scanning for every setup.
|
|
162
164
|
See [Secret scanning](./docs/settings.md#secret-scanning) for the setting and diagnostic behavior.
|
|
163
165
|
|
|
@@ -169,6 +171,7 @@ A minimal Git setup uses an existing private remote and keeps automatic sync off
|
|
|
169
171
|
"activeSyncSetup": "home",
|
|
170
172
|
"onSwitch": "ask-before-pull",
|
|
171
173
|
"skipSecretScan": false,
|
|
174
|
+
"showStatus": true,
|
|
172
175
|
"storageConnections": {
|
|
173
176
|
"github": {
|
|
174
177
|
"type": "git",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
3
|
import {
|
|
4
4
|
normalizeGitRemoteIdentity
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-T2SJD3N7.ts";
|
|
6
6
|
|
|
7
7
|
// src/backends/webdav/webdav-config.ts
|
|
8
8
|
var DEFAULT_PATH = "pi-sync";
|
|
@@ -29,9 +29,7 @@ function normalizeWebDavUrl(value) {
|
|
|
29
29
|
throw new Error("Invalid pi-sync WebDAV URL.");
|
|
30
30
|
}
|
|
31
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
|
-
);
|
|
32
|
+
throw new Error("Invalid pi-sync WebDAV URL: credentials, query, and fragment are not allowed.");
|
|
35
33
|
}
|
|
36
34
|
const loopback = url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "[::1]" || url.hostname === "::1";
|
|
37
35
|
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
@@ -112,4 +110,4 @@ export {
|
|
|
112
110
|
backendIdentityCoordinates,
|
|
113
111
|
normalizeEndpointIdentity
|
|
114
112
|
};
|
|
115
|
-
//# sourceMappingURL=chunk-
|
|
113
|
+
//# sourceMappingURL=chunk-2JZFHNLM.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 try {\n const url = new URL(value.trim());\n url.username = \"\";\n url.password = \"\";\n url.search = \"\";\n url.hash = \"\";\n url.pathname = `${url.pathname.replace(/\\/+$/u, \"\")}/`;\n return url.toString();\n } catch {\n return value.trim();\n }\n}\n\nexport function normalizeWebDavUrl(value: string | undefined) {\n const normalized = normalizeOptionalString(value);\n if (!normalized) return undefined;\n let url: URL;\n try {\n url = new URL(normalized);\n } catch {\n throw new Error(\"Invalid pi-sync WebDAV URL.\");\n }\n if (url.username || url.password || url.search || url.hash) {\n throw new Error(\"Invalid pi-sync WebDAV URL: credentials, query, and fragment are not allowed.\");\n }\n const loopback =\n url.hostname === \"127.0.0.1\" || url.hostname === \"localhost\" || url.hostname === \"[::1]\" || url.hostname === \"::1\";\n if (url.protocol !== \"https:\" && !(url.protocol === \"http:\" && loopback)) {\n throw new Error(\"Invalid pi-sync WebDAV URL: HTTPS is required except for loopback.\");\n }\n url.pathname = `${url.pathname.replace(/\\/+$/u, \"\")}/`;\n return url.toString();\n}\n\nexport function normalizeWebDavPath(value: string | undefined) {\n if (value?.trim() === \".\" || value?.trim() === \"./\") return \"./\";\n const normalized = trimSlashes(normalizeOptionalString(value) ?? DEFAULT_PATH);\n if (\n !normalized ||\n normalized.includes(\"\\\\\") ||\n hasControlCharacter(normalized) ||\n normalized.split(\"/\").some((segment) => !segment || segment === \".\" || segment === \"..\")\n ) {\n throw new Error(\"Invalid pi-sync WebDAV path.\");\n }\n return normalized;\n}\n\nexport function validateWebDavNamespace(value: string) {\n if (value === \".\" || value === \"..\" || value.includes(\"/\") || value.includes(\"\\\\\") || hasControlCharacter(value)) {\n throw new Error(\"Invalid pi-sync WebDAV namespace.\");\n }\n}\n\nexport function validateWebDavCredentials(username: string, password?: string) {\n if (\n username.includes(\":\") ||\n hasControlCharacter(username) ||\n (password !== undefined && hasControlCharacter(password))\n ) {\n throw new Error(\"Invalid pi-sync WebDAV credentials.\");\n }\n}\n\nfunction normalizeOptionalString(value: string | undefined) {\n const normalized = value?.trim();\n return normalized || undefined;\n}\n\nfunction trimSlashes(value: string) {\n return value.replace(/^\\/+|\\/+$/gu, \"\");\n}\n\nfunction hasControlCharacter(value: string) {\n return [...value].some((character) => {\n const code = character.codePointAt(0) ?? 0;\n return code < 0x20 || (code >= 0x7f && code <= 0x9f);\n });\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 switch (config.backend.type) {\n case \"s3\":\n return JSON.stringify([\n \"s3\",\n normalizeEndpointIdentity(config.backend.profile.endpoint),\n config.backend.destination.bucket,\n config.storagePath,\n ]);\n case \"git\":\n return JSON.stringify([\n \"git\",\n normalizeGitRemoteIdentity(config.backend.profile.remote),\n config.backend.destination.branch,\n config.storagePath,\n ]);\n case \"webdav\":\n return JSON.stringify([\n \"webdav\",\n normalizeWebDavIdentityUrl(config.backend.profile.url),\n config.backend.profile.username,\n config.storagePath,\n ]);\n }\n}\n\nexport function normalizeEndpointIdentity(endpoint: string) {\n try {\n const url = new URL(endpoint.trim());\n url.hostname = url.hostname.toLowerCase();\n url.pathname = url.pathname.replace(/\\/+$/gu, \"\");\n return url.toString().replace(/\\/$/u, \"\");\n } catch {\n return endpoint.trim();\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
syncSetupStorageReview,
|
|
7
7
|
updateLocalConfig,
|
|
8
8
|
validateConfigName
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-4MPIH46N.ts";
|
|
10
10
|
import {
|
|
11
11
|
normalizeSyncInclude
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IVXEDPAD.ts";
|
|
13
13
|
|
|
14
14
|
// src/settings/settings-management.ts
|
|
15
15
|
import { isDeepStrictEqual } from "node:util";
|
|
@@ -77,15 +77,12 @@ async function updateStorageConnection(name, update, expectedSetups, signal) {
|
|
|
77
77
|
async function addSyncSetup(name, setup, signal, expectedConnection) {
|
|
78
78
|
validateConfigName(name, "sync setup");
|
|
79
79
|
await updateSettings((settings) => {
|
|
80
|
-
if (Object.hasOwn(settings.syncSetups, name))
|
|
81
|
-
throw new Error(`Sync setup already exists: ${name}`);
|
|
80
|
+
if (Object.hasOwn(settings.syncSetups, name)) throw new Error(`Sync setup already exists: ${name}`);
|
|
82
81
|
if (!Object.hasOwn(settings.storageConnections, setup.storage.connection)) {
|
|
83
82
|
throw new Error(`Storage connection not found: ${setup.storage.connection}`);
|
|
84
83
|
}
|
|
85
84
|
if (expectedConnection && !isDeepStrictEqual(settings.storageConnections[setup.storage.connection], expectedConnection)) {
|
|
86
|
-
throw new SyncSetupReviewChangedError(
|
|
87
|
-
"Storage connection changed while the setup review was open; reopen it."
|
|
88
|
-
);
|
|
85
|
+
throw new SyncSetupReviewChangedError("Storage connection changed while the setup review was open; reopen it.");
|
|
89
86
|
}
|
|
90
87
|
const nextSetups = { ...settings.syncSetups, [name]: structuredClone(setup) };
|
|
91
88
|
assertUniqueLocations(nextSetups, settings.storageConnections);
|
|
@@ -109,22 +106,14 @@ async function updateSyncSetup(name, update, options = {}) {
|
|
|
109
106
|
if (options.expectedStorage) {
|
|
110
107
|
const connectionName = setup.storage.connection;
|
|
111
108
|
const connection = settings.storageConnections[connectionName];
|
|
112
|
-
if (!connection || !sameStorageReview(
|
|
113
|
-
syncSetupStorageReview(name, setup, connectionName, connection),
|
|
114
|
-
options.expectedStorage
|
|
115
|
-
)) {
|
|
109
|
+
if (!connection || !sameStorageReview(syncSetupStorageReview(name, setup, connectionName, connection), options.expectedStorage)) {
|
|
116
110
|
throw new SyncSetupReviewChangedError(
|
|
117
111
|
`Sync setup \u201C${name}\u201D storage changed while it was open; reopen it and review the current storage location.`
|
|
118
112
|
);
|
|
119
113
|
}
|
|
120
114
|
}
|
|
121
|
-
if (options.expectedConnection && !isDeepStrictEqual(
|
|
122
|
-
|
|
123
|
-
options.expectedConnection
|
|
124
|
-
)) {
|
|
125
|
-
throw new SyncSetupReviewChangedError(
|
|
126
|
-
"Storage connection changed while the setup review was open; reopen it."
|
|
127
|
-
);
|
|
115
|
+
if (options.expectedConnection && !isDeepStrictEqual(settings.storageConnections[setup.storage.connection], options.expectedConnection)) {
|
|
116
|
+
throw new SyncSetupReviewChangedError("Storage connection changed while the setup review was open; reopen it.");
|
|
128
117
|
}
|
|
129
118
|
const nextSetup = update(structuredClone(setup));
|
|
130
119
|
if (!Object.hasOwn(settings.storageConnections, nextSetup.storage.connection)) {
|
|
@@ -206,4 +195,4 @@ export {
|
|
|
206
195
|
removeSyncSetup,
|
|
207
196
|
removeStorageConnection
|
|
208
197
|
};
|
|
209
|
-
//# sourceMappingURL=chunk-
|
|
198
|
+
//# sourceMappingURL=chunk-2RZSQWBH.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/settings/settings-management.ts"],
|
|
4
|
+
"sourcesContent": ["import { isDeepStrictEqual } from \"node:util\";\nimport { normalizeSyncInclude } from \"../sync/sync-policy.js\";\nimport { type SyncSetupStorageReview, syncSetupStorageReview } from \"./config.js\";\nimport { localConfigPath } from \"./config-file.js\";\nimport { updateLocalConfig } from \"./settings-store.js\";\nimport type { PiSyncSettingsV3, StorageConnectionSettings, SyncSetupSettings } from \"./settings-types.js\";\nimport { effectiveSyncSetupRemoteIdentity, validateConfigName } from \"./settings-validation.js\";\n\nexport class SyncSetupReviewChangedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SyncSetupReviewChangedError\";\n }\n}\n\nexport async function saveNewV3Settings(\n input: {\n setupName: string;\n connectionName: string;\n connection: StorageConnectionSettings;\n setup: SyncSetupSettings;\n },\n signal?: AbortSignal,\n) {\n validateConfigName(input.setupName, \"sync setup\");\n validateConfigName(input.connectionName, \"storage connection\");\n const settings: PiSyncSettingsV3 = {\n version: 3,\n activeSyncSetup: input.setupName,\n onSwitch: \"ask-before-pull\",\n skipSecretScan: false,\n storageConnections: { [input.connectionName]: structuredClone(input.connection) },\n syncSetups: {\n [input.setupName]: {\n ...structuredClone(input.setup),\n storage: { ...input.setup.storage, connection: input.connectionName },\n },\n },\n };\n return updateLocalConfig((current) => {\n if (Object.keys(current.storageConnections).length || Object.keys(current.syncSetups).length) {\n throw new Error(`Settings already exist: ${localConfigPath()}`);\n }\n return { ...current, ...settings };\n }, signal);\n}\n\nexport async function addStorageConnection(name: string, connection: StorageConnectionSettings, signal?: AbortSignal) {\n validateConfigName(name, \"storage connection\");\n await updateSettings((settings) => {\n if (Object.hasOwn(settings.storageConnections, name)) {\n throw new Error(`Storage connection already exists: ${name}`);\n }\n return {\n ...settings,\n storageConnections: {\n ...settings.storageConnections,\n [name]: structuredClone(connection),\n },\n };\n }, signal);\n}\n\nexport async function updateStorageConnection(\n name: string,\n update: (connection: StorageConnectionSettings) => StorageConnectionSettings,\n expectedSetups?: readonly string[],\n signal?: AbortSignal,\n) {\n validateConfigName(name, \"storage connection\");\n await updateSettings((settings) => {\n const connection = settings.storageConnections[name];\n if (!connection) throw new Error(`Storage connection not found: ${name}`);\n const currentSetups = referencingSetupNames(settings.syncSetups, name);\n if (expectedSetups && !sameNames(currentSetups, expectedSetups)) {\n throw new Error(\n `Storage connection \u201C${name}\u201D usage changed while it was open; reopen it and review the affected sync setups.`,\n );\n }\n const nextConnection = update(structuredClone(connection));\n const nextConnections = { ...settings.storageConnections, [name]: nextConnection };\n assertUniqueLocations(settings.syncSetups, nextConnections);\n return { ...settings, storageConnections: nextConnections };\n }, signal);\n}\n\nexport async function addSyncSetup(\n name: string,\n setup: SyncSetupSettings,\n signal?: AbortSignal,\n expectedConnection?: StorageConnectionSettings,\n) {\n validateConfigName(name, \"sync setup\");\n await updateSettings((settings) => {\n if (Object.hasOwn(settings.syncSetups, name)) throw new Error(`Sync setup already exists: ${name}`);\n if (!Object.hasOwn(settings.storageConnections, setup.storage.connection)) {\n throw new Error(`Storage connection not found: ${setup.storage.connection}`);\n }\n if (\n expectedConnection &&\n !isDeepStrictEqual(settings.storageConnections[setup.storage.connection], expectedConnection)\n ) {\n throw new SyncSetupReviewChangedError(\"Storage connection changed while the setup review was open; reopen it.\");\n }\n const nextSetups = { ...settings.syncSetups, [name]: structuredClone(setup) };\n assertUniqueLocations(nextSetups, settings.storageConnections);\n return {\n ...settings,\n syncSetups: nextSetups,\n ...(settings.activeSyncSetup ? {} : { activeSyncSetup: name }),\n };\n }, signal);\n}\n\nexport async function updateSyncSetup(\n name: string,\n update: (setup: SyncSetupSettings) => SyncSetupSettings,\n options: {\n expectedStorage?: SyncSetupStorageReview;\n expectedInclude?: readonly string[];\n expectedConnection?: StorageConnectionSettings;\n signal?: AbortSignal;\n } = {},\n) {\n validateConfigName(name, \"sync setup\");\n await updateSettings((settings) => {\n const setup = settings.syncSetups[name];\n if (!setup) throw new Error(`Sync setup not found: ${name}`);\n if (options.expectedInclude && !sameNames(normalizeSyncInclude(setup.sync.include), options.expectedInclude)) {\n throw new SyncSetupReviewChangedError(\n `Sync setup \u201C${name}\u201D included content changed while it was open; reopen it and review the current selection.`,\n );\n }\n if (options.expectedStorage) {\n const connectionName = setup.storage.connection;\n const connection = settings.storageConnections[connectionName];\n if (\n !connection ||\n !sameStorageReview(syncSetupStorageReview(name, setup, connectionName, connection), options.expectedStorage)\n ) {\n throw new SyncSetupReviewChangedError(\n `Sync setup \u201C${name}\u201D storage changed while it was open; reopen it and review the current storage location.`,\n );\n }\n }\n if (\n options.expectedConnection &&\n !isDeepStrictEqual(settings.storageConnections[setup.storage.connection], options.expectedConnection)\n ) {\n throw new SyncSetupReviewChangedError(\"Storage connection changed while the setup review was open; reopen it.\");\n }\n const nextSetup = update(structuredClone(setup));\n if (!Object.hasOwn(settings.storageConnections, nextSetup.storage.connection)) {\n throw new Error(`Storage connection not found: ${nextSetup.storage.connection}`);\n }\n const nextSetups = { ...settings.syncSetups, [name]: nextSetup };\n assertUniqueLocations(nextSetups, settings.storageConnections);\n return { ...settings, syncSetups: nextSetups };\n }, options.signal);\n}\n\nexport async function removeSyncSetup(name: string, signal?: AbortSignal) {\n validateConfigName(name, \"sync setup\");\n await updateSettings((settings) => {\n if (!Object.hasOwn(settings.syncSetups, name)) throw new Error(`Sync setup not found: ${name}`);\n const isCurrent = settings.activeSyncSetup === name;\n if (isCurrent && Object.keys(settings.syncSetups).length > 1) {\n throw new Error(\"Switch to another sync setup before removing the current setup.\");\n }\n const syncSetups = { ...settings.syncSetups };\n delete syncSetups[name];\n const next = { ...settings, syncSetups };\n if (isCurrent) delete next.activeSyncSetup;\n return next;\n }, signal);\n}\n\nexport async function removeStorageConnection(name: string, signal?: AbortSignal) {\n validateConfigName(name, \"storage connection\");\n await updateSettings((settings) => {\n const referenced = referencingSetupNames(settings.syncSetups, name)[0];\n if (referenced) {\n throw new Error(`Storage connection \u201C${name}\u201D is used by sync setup \u201C${referenced}\u201D.`);\n }\n if (!Object.hasOwn(settings.storageConnections, name)) {\n throw new Error(`Storage connection not found: ${name}`);\n }\n const storageConnections = { ...settings.storageConnections };\n delete storageConnections[name];\n return { ...settings, storageConnections };\n }, signal);\n}\n\nasync function updateSettings(update: (settings: PiSyncSettingsV3) => PiSyncSettingsV3, signal?: AbortSignal) {\n return updateLocalConfig((settings) => {\n if (settings.version !== 3) {\n throw new Error(\"Storage connections and sync setups require version 3 pi-sync settings.\");\n }\n return update(settings);\n }, signal);\n}\n\nfunction referencingSetupNames(setups: Record<string, SyncSetupSettings>, connection: string) {\n return Object.entries(setups)\n .filter(([, setup]) => setup.storage.connection === connection)\n .map(([name]) => name)\n .sort((left, right) => left.localeCompare(right));\n}\n\nfunction assertUniqueLocations(\n setups: Record<string, SyncSetupSettings>,\n connections: Record<string, StorageConnectionSettings>,\n) {\n const identities = new Map<string, string>();\n for (const [name, setup] of Object.entries(setups)) {\n const connection = connections[setup.storage.connection];\n if (!connection) throw new Error(`Storage connection not found: ${setup.storage.connection}`);\n const identity = effectiveSyncSetupRemoteIdentity(setup, connection);\n const previous = identities.get(identity);\n if (previous) {\n throw new Error(`Sync setup \u201C${name}\u201D duplicates the storage location of \u201C${previous}\u201D.`);\n }\n identities.set(identity, name);\n }\n}\n\nfunction sameNames(left: readonly string[], right: readonly string[]) {\n return left.length === right.length && left.every((name, index) => name === right[index]);\n}\n\nfunction sameStorageReview(left: SyncSetupStorageReview, right: SyncSetupStorageReview) {\n return (\n left.connectionName === right.connectionName &&\n left.storageKind === right.storageKind &&\n left.storagePath === right.storagePath &&\n left.bucket === right.bucket &&\n left.branch === right.branch\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -2,20 +2,20 @@
|
|
|
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-QLSN7AGX.ts";
|
|
6
6
|
import {
|
|
7
7
|
stateDir
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-BVVP33U5.ts";
|
|
9
9
|
import {
|
|
10
10
|
sessionStorageRoot
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-SWUXA3MH.ts";
|
|
12
12
|
import {
|
|
13
13
|
agentDir
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-OVDTF5CH.ts";
|
|
15
15
|
import {
|
|
16
16
|
assertWithinRoot,
|
|
17
17
|
isPathInside
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-IVXEDPAD.ts";
|
|
19
19
|
|
|
20
20
|
// src/snapshot/snapshot-transaction.ts
|
|
21
21
|
import { randomUUID } from "node:crypto";
|
|
@@ -129,21 +129,15 @@ async function prepareTransaction(plan, sessionDir) {
|
|
|
129
129
|
sessionRoot,
|
|
130
130
|
entries
|
|
131
131
|
};
|
|
132
|
-
await fs.writeFile(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
{
|
|
137
|
-
mode: 384
|
|
138
|
-
}
|
|
139
|
-
);
|
|
132
|
+
await fs.writeFile(path.join(directory, "journal.json"), `${JSON.stringify(journal, null, " ")}
|
|
133
|
+
`, {
|
|
134
|
+
mode: 384
|
|
135
|
+
});
|
|
140
136
|
return { directory, journal };
|
|
141
137
|
}
|
|
142
138
|
async function restoreTransaction(directory, journal) {
|
|
143
139
|
validateJournal(directory, journal);
|
|
144
|
-
for (const entry of [...journal.entries].sort(
|
|
145
|
-
(left, right) => right.target.length - left.target.length
|
|
146
|
-
)) {
|
|
140
|
+
for (const entry of [...journal.entries].sort((left, right) => right.target.length - left.target.length)) {
|
|
147
141
|
await fs.rm(entry.target, { recursive: true, force: true });
|
|
148
142
|
}
|
|
149
143
|
for (const entry of journal.entries) {
|
|
@@ -200,4 +194,4 @@ export {
|
|
|
200
194
|
recoverSnapshotTransactionsOnStartup,
|
|
201
195
|
recoverPendingSnapshotTransactions
|
|
202
196
|
};
|
|
203
|
-
//# sourceMappingURL=chunk-
|
|
197
|
+
//# sourceMappingURL=chunk-4AIHXRKE.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 target: string;\n backupName: string;\n kind: \"missing\" | \"file\" | \"directory\" | \"symlink\";\n linkTarget?: string;\n}\n\ninterface TransactionJournal {\n version: number;\n root: string;\n sessionRoot?: string;\n entries: TransactionEntry[];\n}\n\nexport async function applySnapshotTransaction(plan: SnapshotApplyPlan, options: { sessionDir?: string } = {}) {\n await recoverPendingSnapshotTransactions();\n const transaction = await prepareTransaction(plan, options.sessionDir);\n try {\n for (const target of plan.deletes) {\n await fs.rm(target, { force: true, recursive: true });\n }\n for (const item of plan.writes) {\n await fs.mkdir(path.dirname(item.target), { recursive: true });\n await fs.writeFile(item.target, item.content);\n }\n await fs.rm(transaction.directory, { recursive: true, force: true });\n } catch (error) {\n try {\n await restoreTransaction(transaction.directory, transaction.journal);\n } catch (recoveryError) {\n throw new AggregateError(\n [error, recoveryError],\n `Snapshot apply failed and automatic recovery also failed. Transaction retained at ${transaction.directory}.`,\n );\n }\n throw error;\n }\n}\n\nexport async function recoverSnapshotTransactionsOnStartup() {\n if (!(await pendingTransactionEntries()).some((entry) => entry.isDirectory())) return;\n await withLock(\"recovery\", recoverPendingSnapshotTransactions, { reclaimStale: true });\n}\n\nexport async function recoverPendingSnapshotTransactions() {\n const directory = transactionRoot();\n const entries = await pendingTransactionEntries();\n for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {\n if (!entry.isDirectory()) continue;\n const transactionDirectory = path.join(directory, entry.name);\n const journalPath = path.join(transactionDirectory, \"journal.json\");\n let journal: TransactionJournal;\n try {\n journal = JSON.parse(await fs.readFile(journalPath, \"utf8\")) as TransactionJournal;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n await fs.rm(transactionDirectory, { recursive: true, force: true });\n continue;\n }\n throw new Error(`Cannot recover malformed pi-sync transaction: ${journalPath}`, {\n cause: error,\n });\n }\n await restoreTransaction(transactionDirectory, journal);\n }\n}\n\nasync function pendingTransactionEntries() {\n try {\n return await fs.readdir(transactionRoot(), { withFileTypes: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw error;\n }\n}\n\nasync function prepareTransaction(plan: SnapshotApplyPlan, sessionDir?: string) {\n const root = path.resolve(agentDir());\n const sessionRoot = sessionDir ? path.resolve(sessionStorageRoot(root, sessionDir)) : undefined;\n const directory = path.join(transactionRoot(), randomUUID());\n const backupDirectory = path.join(directory, \"before\");\n await fs.mkdir(backupDirectory, { recursive: true, mode: 0o700 });\n const targets = [...new Set([...plan.deletes, ...plan.writes.map((item) => item.target)])].sort();\n const entries: TransactionEntry[] = [];\n for (let index = 0; index < targets.length; index += 1) {\n const target = targets[index];\n if (!target) continue;\n assertAllowedTarget(root, sessionRoot, target);\n const backupName = `${index}`;\n const backupPath = path.join(backupDirectory, backupName);\n try {\n const stat = await fs.lstat(target);\n if (stat.isSymbolicLink()) {\n entries.push({\n target,\n backupName,\n kind: \"symlink\",\n linkTarget: await fs.readlink(target),\n });\n } else if (stat.isDirectory()) {\n await fs.cp(target, backupPath, {\n recursive: true,\n dereference: false,\n preserveTimestamps: true,\n });\n entries.push({ target, backupName, kind: \"directory\" });\n } else if (stat.isFile()) {\n await fs.copyFile(target, backupPath);\n await fs.chmod(backupPath, stat.mode);\n entries.push({ target, backupName, kind: \"file\" });\n } else {\n throw new Error(`Unsupported existing snapshot target: ${target}`);\n }\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\" && code !== \"ENOTDIR\") throw error;\n entries.push({ target, backupName, kind: \"missing\" });\n }\n }\n const journal: TransactionJournal = {\n version: JOURNAL_VERSION,\n root,\n sessionRoot,\n entries,\n };\n await fs.writeFile(path.join(directory, \"journal.json\"), `${JSON.stringify(journal, null, \"\\t\")}\\n`, {\n mode: 0o600,\n });\n return { directory, journal };\n}\n\nasync function restoreTransaction(directory: string, journal: TransactionJournal) {\n validateJournal(directory, journal);\n for (const entry of [...journal.entries].sort((left, right) => right.target.length - left.target.length)) {\n await fs.rm(entry.target, { recursive: true, force: true });\n }\n for (const entry of journal.entries) {\n if (entry.kind === \"missing\") continue;\n await fs.mkdir(path.dirname(entry.target), { recursive: true });\n const backupPath = path.join(directory, \"before\", entry.backupName);\n assertWithinRoot(directory, backupPath);\n if (entry.kind === \"file\") await fs.copyFile(backupPath, entry.target);\n else if (entry.kind === \"directory\") {\n await fs.cp(backupPath, entry.target, {\n recursive: true,\n dereference: false,\n preserveTimestamps: true,\n });\n } else if (entry.kind === \"symlink\" && entry.linkTarget !== undefined) {\n await fs.symlink(entry.linkTarget, entry.target);\n }\n }\n await fs.rm(directory, { recursive: true, force: true });\n}\n\nfunction validateJournal(directory: string, journal: TransactionJournal) {\n if (journal.version !== JOURNAL_VERSION || !Array.isArray(journal.entries)) {\n throw new Error(`Unsupported pi-sync transaction journal: ${directory}`);\n }\n const expectedRoot = path.resolve(agentDir());\n if (path.resolve(journal.root) !== expectedRoot) {\n throw new Error(`Transaction root no longer matches the Pi agent directory: ${directory}`);\n }\n for (const entry of journal.entries) {\n if (\n !entry ||\n typeof entry.target !== \"string\" ||\n typeof entry.backupName !== \"string\" ||\n !/^\\d+$/u.test(entry.backupName)\n ) {\n throw new Error(`Invalid pi-sync transaction entry: ${directory}`);\n }\n assertAllowedTarget(journal.root, journal.sessionRoot, entry.target);\n }\n}\n\nfunction assertAllowedTarget(root: string, sessionRoot: string | undefined, target: string) {\n const resolved = path.resolve(target);\n if (isPathInside(root, resolved)) {\n assertWithinRoot(root, resolved);\n return;\n }\n if (sessionRoot && isPathInside(sessionRoot, resolved)) {\n assertWithinRoot(sessionRoot, resolved);\n return;\n }\n throw new Error(`Transaction target is outside configured roots: ${target}`);\n}\n\nfunction transactionRoot() {\n return path.join(stateDir(), \"transactions\");\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -7,16 +7,16 @@ import {
|
|
|
7
7
|
normalizeWebDavPath,
|
|
8
8
|
normalizeWebDavUrl,
|
|
9
9
|
validateWebDavCredentials
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2JZFHNLM.ts";
|
|
11
11
|
import {
|
|
12
12
|
normalizeGitBranch,
|
|
13
13
|
normalizeGitDirectory,
|
|
14
14
|
normalizeGitRemote,
|
|
15
15
|
normalizeGitRemoteIdentity
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-T2SJD3N7.ts";
|
|
17
17
|
import {
|
|
18
18
|
normalizeSyncInclude
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-IVXEDPAD.ts";
|
|
20
20
|
|
|
21
21
|
// src/settings/config-file.ts
|
|
22
22
|
import { randomUUID } from "node:crypto";
|
|
@@ -212,11 +212,7 @@ async function prepareLocalConfigPath(validateForMigration) {
|
|
|
212
212
|
return legacyPath;
|
|
213
213
|
}
|
|
214
214
|
if (!await configSnapshotStillMatches(legacyPath, legacy)) {
|
|
215
|
-
const removed = await quarantineAndRemoveConfigIfMatchesUnlocked(
|
|
216
|
-
canonicalPath,
|
|
217
|
-
installedIdentity,
|
|
218
|
-
legacy.bytes
|
|
219
|
-
);
|
|
215
|
+
const removed = await quarantineAndRemoveConfigIfMatchesUnlocked(canonicalPath, installedIdentity, legacy.bytes);
|
|
220
216
|
recordConfigMigrationNotice(
|
|
221
217
|
canonicalPath,
|
|
222
218
|
removed ? `${LEGACY_CONFIG_FILE_NAME} changed during migration; the stale ${CONFIG_FILE_NAME} copy was removed and the legacy settings were used for this session.` : `${LEGACY_CONFIG_FILE_NAME} changed during migration, but ${CONFIG_FILE_NAME} was replaced concurrently and takes precedence.`
|
|
@@ -339,11 +335,7 @@ async function installPrivateConfigExclusively(filePath, bytes) {
|
|
|
339
335
|
await publishedHandle?.close().catch(() => void 0);
|
|
340
336
|
}
|
|
341
337
|
if (publicationError) {
|
|
342
|
-
await quarantineAndRemoveConfigIfMatchesUnlocked(
|
|
343
|
-
filePath,
|
|
344
|
-
{ dev: installed.dev, ino: installed.ino },
|
|
345
|
-
bytes
|
|
346
|
-
);
|
|
338
|
+
await quarantineAndRemoveConfigIfMatchesUnlocked(filePath, { dev: installed.dev, ino: installed.ino }, bytes);
|
|
347
339
|
throw publicationError;
|
|
348
340
|
}
|
|
349
341
|
return { dev: installed.dev, ino: installed.ino };
|
|
@@ -541,6 +533,9 @@ function validateSettingsDocument(value) {
|
|
|
541
533
|
if (value.skipSecretScan !== void 0 && typeof value.skipSecretScan !== "boolean") {
|
|
542
534
|
throw new Error("Invalid pi-sync settings: skipSecretScan must be boolean.");
|
|
543
535
|
}
|
|
536
|
+
if (value.showStatus !== void 0 && typeof value.showStatus !== "boolean") {
|
|
537
|
+
throw new Error("Invalid pi-sync settings: showStatus must be boolean.");
|
|
538
|
+
}
|
|
544
539
|
const storageConnections = requireNamedObjectMap(
|
|
545
540
|
value.storageConnections,
|
|
546
541
|
"storageConnections",
|
|
@@ -567,9 +562,7 @@ function validateSettingsDocument(value) {
|
|
|
567
562
|
throw new Error("Invalid pi-sync settings: empty syncSetups cannot have activeSyncSetup.");
|
|
568
563
|
}
|
|
569
564
|
} else if (!activeSyncSetup || !Object.hasOwn(syncSetups, activeSyncSetup)) {
|
|
570
|
-
throw new Error(
|
|
571
|
-
"Invalid pi-sync settings: activeSyncSetup must reference an existing own-property sync setup."
|
|
572
|
-
);
|
|
565
|
+
throw new Error("Invalid pi-sync settings: activeSyncSetup must reference an existing own-property sync setup.");
|
|
573
566
|
}
|
|
574
567
|
validateUniqueRemoteSyncSetups(syncSetups, storageConnections);
|
|
575
568
|
return value;
|
|
@@ -597,17 +590,12 @@ function validateStorageConnection(name, value) {
|
|
|
597
590
|
}
|
|
598
591
|
return;
|
|
599
592
|
}
|
|
600
|
-
const credentials = requireRecord(
|
|
601
|
-
value.credentials,
|
|
602
|
-
`credentials for storage connection \u201C${name}\u201D`
|
|
603
|
-
);
|
|
593
|
+
const credentials = requireRecord(value.credentials, `credentials for storage connection \u201C${name}\u201D`);
|
|
604
594
|
if (type === "webdav") {
|
|
605
595
|
normalizeWebDavUrl(requiredString(value.url, `WebDAV URL for \u201C${name}\u201D`));
|
|
606
596
|
const username = requiredString(credentials.username, `WebDAV username for \u201C${name}\u201D`);
|
|
607
597
|
const password = requiredSecret(credentials.password, `WebDAV password for \u201C${name}\u201D`);
|
|
608
|
-
if (["accessKeyId", "secretAccessKey", "sessionToken"].some(
|
|
609
|
-
(field) => Object.hasOwn(credentials, field)
|
|
610
|
-
)) {
|
|
598
|
+
if (["accessKeyId", "secretAccessKey", "sessionToken"].some((field) => Object.hasOwn(credentials, field))) {
|
|
611
599
|
throw new Error(`Invalid pi-sync settings: WebDAV credentials for \u201C${name}\u201D mix fields.`);
|
|
612
600
|
}
|
|
613
601
|
validateWebDavCredentials(username, password);
|
|
@@ -642,11 +630,7 @@ function validateSyncSetup(name, value, connections) {
|
|
|
642
630
|
);
|
|
643
631
|
const storage = requireRecord(value.storage, `storage for sync setup \u201C${name}\u201D`);
|
|
644
632
|
const sync = requireRecord(value.sync, `sync policy for sync setup \u201C${name}\u201D`);
|
|
645
|
-
rejectLegacyFields(
|
|
646
|
-
storage,
|
|
647
|
-
["profile", "prefix", "directory", "namespace"],
|
|
648
|
-
`storage for sync setup \u201C${name}\u201D`
|
|
649
|
-
);
|
|
633
|
+
rejectLegacyFields(storage, ["profile", "prefix", "directory", "namespace"], `storage for sync setup \u201C${name}\u201D`);
|
|
650
634
|
rejectLegacyFields(
|
|
651
635
|
sync,
|
|
652
636
|
["autoSync", "syncFiles", "syncSessions", "extraFiles"],
|
|
@@ -681,9 +665,7 @@ function validateSyncSetup(name, value, connections) {
|
|
|
681
665
|
}
|
|
682
666
|
normalizeSyncInclude(sync.include);
|
|
683
667
|
if (typeof sync.automatic !== "boolean") {
|
|
684
|
-
throw new Error(
|
|
685
|
-
`Invalid pi-sync settings: sync setup \u201C${name}\u201D sync.automatic must be boolean.`
|
|
686
|
-
);
|
|
668
|
+
throw new Error(`Invalid pi-sync settings: sync setup \u201C${name}\u201D sync.automatic must be boolean.`);
|
|
687
669
|
}
|
|
688
670
|
}
|
|
689
671
|
function mixedSetupError(type, name) {
|
|
@@ -736,9 +718,7 @@ function effectiveSyncSetupRemoteIdentity(setup, connection) {
|
|
|
736
718
|
function rejectLegacyFields(value, fields, context) {
|
|
737
719
|
const field = fields.find((candidate) => Object.hasOwn(value, candidate));
|
|
738
720
|
if (field) {
|
|
739
|
-
throw new Error(
|
|
740
|
-
`Invalid pi-sync settings: ${context} contains unsupported version 1/2 field \u201C${field}\u201D.`
|
|
741
|
-
);
|
|
721
|
+
throw new Error(`Invalid pi-sync settings: ${context} contains unsupported version 1/2 field \u201C${field}\u201D.`);
|
|
742
722
|
}
|
|
743
723
|
}
|
|
744
724
|
function requireNamedObjectMap(value, field, itemLabel) {
|
|
@@ -851,9 +831,7 @@ var configUpdateQueue = Promise.resolve();
|
|
|
851
831
|
async function requireSettings() {
|
|
852
832
|
const settings = await readLocalConfigObject();
|
|
853
833
|
if (!settings) {
|
|
854
|
-
throw new Error(
|
|
855
|
-
`Missing pi-sync settings. Open /sync and choose Set up sync, or create ${localConfigPath()}.`
|
|
856
|
-
);
|
|
834
|
+
throw new Error(`Missing pi-sync settings. Open /sync and choose Set up sync, or create ${localConfigPath()}.`);
|
|
857
835
|
}
|
|
858
836
|
return settings;
|
|
859
837
|
}
|
|
@@ -869,6 +847,7 @@ function localConfigTemplate() {
|
|
|
869
847
|
version: 3,
|
|
870
848
|
onSwitch: DEFAULT_ON_SWITCH,
|
|
871
849
|
skipSecretScan: false,
|
|
850
|
+
showStatus: true,
|
|
872
851
|
storageConnections: {},
|
|
873
852
|
syncSetups: {}
|
|
874
853
|
};
|
|
@@ -914,13 +893,9 @@ function configFromSettings(settings, setupName) {
|
|
|
914
893
|
if (!selectedName) throw new Error("No sync setups are configured.");
|
|
915
894
|
validateConfigName(selectedName, "sync setup");
|
|
916
895
|
const setup = ownObject(settings.syncSetups, selectedName);
|
|
917
|
-
if (!setup)
|
|
918
|
-
throw new Error(`Invalid pi-sync settings: sync setup \u201C${selectedName}\u201D was not found.`);
|
|
896
|
+
if (!setup) throw new Error(`Invalid pi-sync settings: sync setup \u201C${selectedName}\u201D was not found.`);
|
|
919
897
|
const connectionName = setup.storage.connection;
|
|
920
|
-
const connection = ownObject(
|
|
921
|
-
settings.storageConnections,
|
|
922
|
-
connectionName
|
|
923
|
-
);
|
|
898
|
+
const connection = ownObject(settings.storageConnections, connectionName);
|
|
924
899
|
if (!connection) {
|
|
925
900
|
throw new Error(
|
|
926
901
|
`Invalid pi-sync settings: sync setup \u201C${selectedName}\u201D references missing storage connection \u201C${connectionName}\u201D.`
|
|
@@ -932,7 +907,8 @@ function configFromSettings(settings, setupName) {
|
|
|
932
907
|
connectionName,
|
|
933
908
|
connection,
|
|
934
909
|
settings.onSwitch,
|
|
935
|
-
settings.skipSecretScan ?? false
|
|
910
|
+
settings.skipSecretScan ?? false,
|
|
911
|
+
settings.showStatus ?? true
|
|
936
912
|
);
|
|
937
913
|
}
|
|
938
914
|
async function loadPartialConfig(setupName) {
|
|
@@ -942,17 +918,18 @@ async function loadPartialConfig(setupName) {
|
|
|
942
918
|
...storageReviewFromConfig(config),
|
|
943
919
|
include: [...config.include],
|
|
944
920
|
automatic: config.automatic,
|
|
945
|
-
onSwitch: config.onSwitch
|
|
921
|
+
onSwitch: config.onSwitch,
|
|
922
|
+
showStatus: config.showStatus
|
|
946
923
|
};
|
|
947
924
|
}
|
|
948
925
|
function syncSetupStorageReview(setupName, setup, connectionName, connection) {
|
|
949
926
|
return storageReviewFromConfig(
|
|
950
|
-
resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false)
|
|
927
|
+
resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false, true)
|
|
951
928
|
);
|
|
952
929
|
}
|
|
953
930
|
function syncSetupReviewIdentity(setupName, setup, connectionName, connection) {
|
|
954
931
|
return syncConfigReviewIdentity(
|
|
955
|
-
resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false)
|
|
932
|
+
resolveSyncConfig(setupName, setup, connectionName, connection, DEFAULT_ON_SWITCH, false, true)
|
|
956
933
|
);
|
|
957
934
|
}
|
|
958
935
|
function syncConfigReviewIdentity(config) {
|
|
@@ -968,7 +945,7 @@ function syncConfigReviewFingerprint(config) {
|
|
|
968
945
|
return createHash("sha256").update(syncConfigReviewIdentity(config)).digest("hex");
|
|
969
946
|
}
|
|
970
947
|
function syncCheckConfigFingerprint(config) {
|
|
971
|
-
return createHash("sha256").update(JSON.stringify(config)).digest("hex");
|
|
948
|
+
return createHash("sha256").update(JSON.stringify({ ...config, showStatus: void 0 })).digest("hex");
|
|
972
949
|
}
|
|
973
950
|
function storageReviewFromConfig(config) {
|
|
974
951
|
return {
|
|
@@ -978,7 +955,7 @@ function storageReviewFromConfig(config) {
|
|
|
978
955
|
...config.backend.type === "s3" ? { bucket: config.backend.destination.bucket } : config.backend.type === "git" ? { branch: config.backend.destination.branch } : {}
|
|
979
956
|
};
|
|
980
957
|
}
|
|
981
|
-
function resolveSyncConfig(setupName, setup, connectionName, connection, onSwitch, skipSecretScan) {
|
|
958
|
+
function resolveSyncConfig(setupName, setup, connectionName, connection, onSwitch, skipSecretScan, showStatus) {
|
|
982
959
|
const storagePath = normalizeStoragePath(setup.storage.path);
|
|
983
960
|
const namespace = storagePath === "./" ? "root" : storagePath.slice(storagePath.lastIndexOf("/") + 1);
|
|
984
961
|
const include = normalizeSyncInclude(setup.sync.include);
|
|
@@ -990,7 +967,8 @@ function resolveSyncConfig(setupName, setup, connectionName, connection, onSwitc
|
|
|
990
967
|
include,
|
|
991
968
|
automatic: setup.sync.automatic,
|
|
992
969
|
onSwitch,
|
|
993
|
-
skipSecretScan
|
|
970
|
+
skipSecretScan,
|
|
971
|
+
showStatus
|
|
994
972
|
};
|
|
995
973
|
if (connection.type === "git") {
|
|
996
974
|
return {
|
|
@@ -1084,4 +1062,4 @@ export {
|
|
|
1084
1062
|
sessionTokenWarnings,
|
|
1085
1063
|
syncSessionsWarnings
|
|
1086
1064
|
};
|
|
1087
|
-
//# sourceMappingURL=chunk-
|
|
1065
|
+
//# sourceMappingURL=chunk-4MPIH46N.ts.map
|