@narumitw/pi-sync 0.49.15 → 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 +49 -16
- 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-JF6MZCT4.ts → chunk-AUSDLD6F.ts} +4 -4
- package/dist/chunks/chunk-AUSDLD6F.ts.map +7 -0
- package/dist/chunks/{chunk-X7SQ2EQM.ts → chunk-AXXS2BPA.ts} +9 -9
- package/dist/chunks/chunk-AXXS2BPA.ts.map +7 -0
- package/dist/chunks/{chunk-5T442OP3.ts → chunk-C7XY5NF5.ts} +210 -373
- 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-WSOBYHIZ.ts → chunk-GWMHAQG4.ts} +4 -4
- package/dist/chunks/chunk-GWMHAQG4.ts.map +7 -0
- package/dist/chunks/{chunk-5LZALPCD.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-DFXBKHGN.ts → chunk-LCRI3ABW.ts} +21 -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-HVUSHMVL.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-GLQLN5GV.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-44LPGQTI.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-ARN2672F.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-RKEHALNQ.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-552LXHE4.ts → s3-backend-3P33LQHU.ts} +96 -18
- package/dist/chunks/s3-backend-3P33LQHU.ts.map +7 -0
- package/dist/chunks/{setup-switch-5YDG5YGJ.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-2RYBUDRA.ts → snapshot-HKEWCVK6.ts} +7 -6
- package/dist/chunks/sync-inspection-FEP4L5TR.ts +20 -0
- package/dist/chunks/{sync-operations-3LLIHWZO.ts → sync-operations-ONJ27QRL.ts} +374 -294
- package/dist/chunks/sync-operations-ONJ27QRL.ts.map +7 -0
- package/dist/chunks/{sync-state-SRN6YRBK.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 +30 -6
- package/package.json +1 -1
- 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} +39 -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} +34 -325
- 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 -286
- 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} +17 -13
- 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-44LPGQTI.ts.map +0 -7
- package/dist/chunks/chunk-4U6AFLJB.ts.map +0 -7
- package/dist/chunks/chunk-5LZALPCD.ts.map +0 -7
- package/dist/chunks/chunk-5MQ76BK5.ts.map +0 -7
- package/dist/chunks/chunk-5T442OP3.ts.map +0 -7
- package/dist/chunks/chunk-ARN2672F.ts.map +0 -7
- package/dist/chunks/chunk-C7LXZ3UH.ts +0 -167
- package/dist/chunks/chunk-C7LXZ3UH.ts.map +0 -7
- package/dist/chunks/chunk-DFXBKHGN.ts.map +0 -7
- package/dist/chunks/chunk-E4COSV32.ts +0 -82
- package/dist/chunks/chunk-E4COSV32.ts.map +0 -7
- package/dist/chunks/chunk-F5QL2GPY.ts.map +0 -7
- package/dist/chunks/chunk-GLQLN5GV.ts.map +0 -7
- package/dist/chunks/chunk-HGV24P4T.ts.map +0 -7
- package/dist/chunks/chunk-HVUSHMVL.ts.map +0 -7
- package/dist/chunks/chunk-JF6MZCT4.ts.map +0 -7
- package/dist/chunks/chunk-WSOBYHIZ.ts.map +0 -7
- package/dist/chunks/chunk-X7SQ2EQM.ts.map +0 -7
- package/dist/chunks/chunk-YQ6UW7IF.ts.map +0 -7
- package/dist/chunks/file-selection-RKEHALNQ.ts.map +0 -7
- package/dist/chunks/git-backend-E3UOUUOM.ts.map +0 -7
- package/dist/chunks/manager-result-dispatcher-LWPYJAD5.ts +0 -18
- package/dist/chunks/manager-ui-NF4RUHBY.ts +0 -2785
- package/dist/chunks/manager-ui-NF4RUHBY.ts.map +0 -7
- package/dist/chunks/remote-selection-ui-NUF5P4ZJ.ts +0 -17
- package/dist/chunks/s3-backend-552LXHE4.ts.map +0 -7
- package/dist/chunks/sync-operations-3LLIHWZO.ts.map +0 -7
- package/dist/chunks/webdav-backend-ZD5RHRIU.ts.map +0 -7
- package/src/config.ts +0 -923
- 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-LWPYJAD5.ts.map → manager-result-dispatcher-U23ODD32.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-NUF5P4ZJ.ts.map → remote-selection-ui-ZE3TY67S.ts.map} +0 -0
- /package/dist/chunks/{setup-switch-5YDG5YGJ.ts.map → setup-switch-IS2PEZ3J.ts.map} +0 -0
- /package/dist/chunks/{snapshot-2RYBUDRA.ts.map → snapshot-HKEWCVK6.ts.map} +0 -0
- /package/dist/chunks/{sync-state-SRN6YRBK.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,53 @@
|
|
|
1
|
+
import { errorMessage } from "./sync-errors.js";
|
|
2
|
+
|
|
3
|
+
/** Add recovery advice without changing backend outcome semantics or printing error causes. */
|
|
4
|
+
export function syncErrorGuidance(error: unknown): string {
|
|
5
|
+
const detail = errorMessage(error);
|
|
6
|
+
const message = detail.length > 1200 ? `${detail.slice(0, 1200)}… (details truncated)` : detail;
|
|
7
|
+
const code = (error as NodeJS.ErrnoException | undefined)?.code;
|
|
8
|
+
const status = (error as { status?: number } | undefined)?.status;
|
|
9
|
+
let next: string;
|
|
10
|
+
if (code === "ENOSPC") {
|
|
11
|
+
next = "Free disk space for the private pi-sync settings/state directory, then retry.";
|
|
12
|
+
} else if (code && ["EACCES", "EPERM", "EROFS"].includes(code)) {
|
|
13
|
+
next = "Check write access to the private pi-sync settings/state directory, then retry.";
|
|
14
|
+
} else if (code && ["EBUSY", "EIO", "EMFILE", "ENFILE"].includes(code)) {
|
|
15
|
+
next =
|
|
16
|
+
"Check local disk availability and other processes using the private settings file, then retry.";
|
|
17
|
+
} else if (/changed while|type changed|reopen/iu.test(message)) {
|
|
18
|
+
next = "Reopen the item in /sync and review its current settings before saving again.";
|
|
19
|
+
} else if (
|
|
20
|
+
status === 401 ||
|
|
21
|
+
status === 403 ||
|
|
22
|
+
/Permission denied|Authentication failed|HTTP (401|403)|failed \((401|403)\)/iu.test(message)
|
|
23
|
+
) {
|
|
24
|
+
next =
|
|
25
|
+
"Check the storage connection's credentials and remote permissions in /sync → More → Storage connections.";
|
|
26
|
+
} else if (status === 404 || /HTTP 404|failed \(404\)/iu.test(message)) {
|
|
27
|
+
next =
|
|
28
|
+
"Verify the connection address and setup's bucket, branch, or path with your provider; this response alone does not prove which resource is missing.";
|
|
29
|
+
} else if (status !== undefined && status >= 500) {
|
|
30
|
+
next = "Check your storage provider's service status, then retry Check setup before syncing.";
|
|
31
|
+
} else if (
|
|
32
|
+
/timed out|timeout|request failed|resolve host|Could not resolve|ENOTFOUND|ECONNREFUSED/iu.test(
|
|
33
|
+
message,
|
|
34
|
+
)
|
|
35
|
+
) {
|
|
36
|
+
next =
|
|
37
|
+
"Check the server address and network connection, then run /sync doctor for the affected setup.";
|
|
38
|
+
} else if (/strong ETag|If-Match|If-None-Match|precondition|conditional header/iu.test(message)) {
|
|
39
|
+
next =
|
|
40
|
+
"Ask your provider to check strong ETags and conditional writes, including proxy behavior, or choose compatible storage. Do not bypass these safety checks.";
|
|
41
|
+
} else if (/spawn git ENOENT/iu.test(message)) {
|
|
42
|
+
next = "Install Git 2.30 or newer and make sure Pi can find git on PATH, then retry.";
|
|
43
|
+
} else if (/response exceeds.*byte limit/iu.test(message)) {
|
|
44
|
+
next =
|
|
45
|
+
"Verify the storage endpoint; the server returned an unexpectedly large response. Check your provider's logs before retrying.";
|
|
46
|
+
} else if (/settings|config|already exists|duplicates|same normalized/iu.test(message)) {
|
|
47
|
+
next =
|
|
48
|
+
"Correct the named field or item in /sync. If the settings file is invalid, repair it before retrying; do not replace it with defaults.";
|
|
49
|
+
} else {
|
|
50
|
+
next = "Open /sync → More → Check setup and review the reported checks before retrying.";
|
|
51
|
+
}
|
|
52
|
+
return `${message}\n${next}`;
|
|
53
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createSyncBackend, type SyncBackendFactory } from "../backends/backend-factory.js";
|
|
2
|
+
import type { AnySyncConfig } from "../settings/settings-types.js";
|
|
3
|
+
import { createSnapshot } from "../snapshot/snapshot.js";
|
|
4
|
+
import type { SnapshotOptions } from "../snapshot/snapshot-types.js";
|
|
5
|
+
import { readStateForConfig, syncStateFingerprint } from "../state/sync-state-store.js";
|
|
6
|
+
import { throwIfAborted } from "./signals.js";
|
|
7
|
+
import { inspectRemoteSelection } from "./sync-policy.js";
|
|
8
|
+
import { hasLocalChanges, remoteChangedSinceState } from "./sync-state.js";
|
|
9
|
+
|
|
10
|
+
/** Advisory baseline-relative observation, never an authorization to transfer files. */
|
|
11
|
+
export async function inspectSync(
|
|
12
|
+
config: AnySyncConfig,
|
|
13
|
+
options: SnapshotOptions,
|
|
14
|
+
signal?: AbortSignal,
|
|
15
|
+
factory: SyncBackendFactory = createSyncBackend,
|
|
16
|
+
) {
|
|
17
|
+
throwIfAborted(signal);
|
|
18
|
+
const backend = await factory(config);
|
|
19
|
+
throwIfAborted(signal);
|
|
20
|
+
const local = await createSnapshot(config.snapshotIdentity, { ...options, signal });
|
|
21
|
+
throwIfAborted(signal);
|
|
22
|
+
const state = await readStateForConfig(config);
|
|
23
|
+
throwIfAborted(signal);
|
|
24
|
+
const head = await backend.readHead(signal);
|
|
25
|
+
throwIfAborted(signal);
|
|
26
|
+
return {
|
|
27
|
+
head,
|
|
28
|
+
selectionState: head
|
|
29
|
+
? inspectRemoteSelection(config.include, { selection: head.selection, files: [] })
|
|
30
|
+
: undefined,
|
|
31
|
+
localFiles: local.files.length,
|
|
32
|
+
localChanged: hasLocalChanges(local, state, config),
|
|
33
|
+
remoteChanged: remoteChangedSinceState(head, state, config, (left, right) =>
|
|
34
|
+
backend.sameRevision(left, right),
|
|
35
|
+
),
|
|
36
|
+
firstSync: !state.lastAppliedSnapshot,
|
|
37
|
+
emptyInclude: config.include.length === 0,
|
|
38
|
+
stateIdentity: syncStateFingerprint(state),
|
|
39
|
+
destination: backend.destination,
|
|
40
|
+
capability: backend.capability,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type SyncInspection = Awaited<ReturnType<typeof inspectSync>>;
|
|
45
|
+
|
|
46
|
+
export interface StartupObservation {
|
|
47
|
+
setupName: string;
|
|
48
|
+
configIdentity: string;
|
|
49
|
+
checkedAt: string;
|
|
50
|
+
inspection: SyncInspection;
|
|
51
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
type SetupSwitchModule = Pick<typeof import("./setup-switch.js"), "useSyncSetup">;
|
|
2
|
+
type SnapshotModule = Pick<typeof import("../snapshot/snapshot.js"), "createSnapshot">;
|
|
3
|
+
type SyncStateModule = Pick<typeof import("./sync-state.js"), "hasLocalChanges">;
|
|
4
|
+
type SyncOperations = typeof import("./sync-operations.js");
|
|
5
|
+
|
|
6
|
+
type SyncInspectionModule = Pick<typeof import("./sync-inspection.js"), "inspectSync">;
|
|
7
|
+
|
|
8
|
+
export interface SyncDependencies {
|
|
9
|
+
loadSyncInspection(): Promise<SyncInspectionModule>;
|
|
10
|
+
loadSetupSwitch(): Promise<SetupSwitchModule>;
|
|
11
|
+
loadSnapshot(): Promise<SnapshotModule>;
|
|
12
|
+
loadSyncState(): Promise<SyncStateModule>;
|
|
13
|
+
loadSyncOperations(): Promise<SyncOperations>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SyncLoaders {
|
|
17
|
+
inspection(): Promise<SyncInspectionModule>;
|
|
18
|
+
setupSwitch(): Promise<SetupSwitchModule>;
|
|
19
|
+
snapshot(): Promise<SnapshotModule>;
|
|
20
|
+
syncState(): Promise<SyncStateModule>;
|
|
21
|
+
operations(): Promise<SyncOperations>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createSyncLoaders(dependencies: Partial<SyncDependencies>): SyncLoaders {
|
|
25
|
+
const loaders: SyncLoaders = {
|
|
26
|
+
inspection: cachedModuleLoader(
|
|
27
|
+
dependencies.loadSyncInspection ?? (() => import("./sync-inspection.js")),
|
|
28
|
+
),
|
|
29
|
+
setupSwitch: cachedModuleLoader(
|
|
30
|
+
dependencies.loadSetupSwitch ?? (() => import("./setup-switch.js")),
|
|
31
|
+
),
|
|
32
|
+
snapshot: cachedModuleLoader(
|
|
33
|
+
dependencies.loadSnapshot ?? (() => import("../snapshot/snapshot.js")),
|
|
34
|
+
),
|
|
35
|
+
syncState: cachedModuleLoader(dependencies.loadSyncState ?? (() => import("./sync-state.js"))),
|
|
36
|
+
operations: cachedModuleLoader(
|
|
37
|
+
dependencies.loadSyncOperations ?? (() => import("./sync-operations.js")),
|
|
38
|
+
),
|
|
39
|
+
};
|
|
40
|
+
return loaders;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function cachedModuleLoader<Module>(load: () => Promise<Module>): () => Promise<Module> {
|
|
44
|
+
let pending: Promise<Module> | undefined;
|
|
45
|
+
return () => {
|
|
46
|
+
if (!pending) {
|
|
47
|
+
pending = load().catch((error) => {
|
|
48
|
+
pending = undefined;
|
|
49
|
+
throw error;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return pending;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import type { ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { createSyncBackend, type SyncBackendFactory } from "
|
|
4
|
+
import { createSyncBackend, type SyncBackendFactory } from "../backends/backend-factory.js";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from "
|
|
14
|
-
import { inspectLock, isLockGuardHeld, isStaleLock, withLock } from "./lock.js";
|
|
6
|
+
expectedRemoteHead,
|
|
7
|
+
type PublishSnapshotResult,
|
|
8
|
+
type RemoteHead,
|
|
9
|
+
type SyncBackend,
|
|
10
|
+
} from "../backends/sync-backend.js";
|
|
11
|
+
import type { CommandOptions } from "../commands/command-types.js";
|
|
12
|
+
import { loadConfig } from "../settings/config.js";
|
|
13
|
+
import type { AnySyncConfig } from "../settings/settings-types.js";
|
|
15
14
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from "
|
|
15
|
+
sessionDirForApply,
|
|
16
|
+
sessionDirFromContext,
|
|
17
|
+
snapshotOptionsForContext,
|
|
18
|
+
} from "../snapshot/session-paths.js";
|
|
20
19
|
import {
|
|
21
20
|
createSnapshot,
|
|
22
21
|
filterSnapshotForConfigPolicy,
|
|
@@ -26,28 +25,28 @@ import {
|
|
|
26
25
|
sessionSnapshotPathFromAbsolute,
|
|
27
26
|
snapshotIncludesSessions,
|
|
28
27
|
snapshotWithoutSessions,
|
|
29
|
-
} from "
|
|
30
|
-
import { applySnapshot } from "
|
|
31
|
-
import { encodeSnapshot } from "
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
type SyncBackend,
|
|
37
|
-
} from "./sync-backend.js";
|
|
38
|
-
import { createSyncDecision } from "./sync-decision.js";
|
|
28
|
+
} from "../snapshot/snapshot.js";
|
|
29
|
+
import { applySnapshot } from "../snapshot/snapshot-apply.js";
|
|
30
|
+
import { encodeSnapshot } from "../snapshot/snapshot-codec.js";
|
|
31
|
+
import type { Snapshot, SnapshotOptions } from "../snapshot/snapshot-types.js";
|
|
32
|
+
import { stateDir } from "../state/state-directory.js";
|
|
33
|
+
import type { SyncState } from "../state/state-types.js";
|
|
34
|
+
import { readStateForConfig, writeStateForConfig } from "../state/sync-state-store.js";
|
|
39
35
|
import {
|
|
40
36
|
countPreservedRemoteFiles,
|
|
41
|
-
errorMessage,
|
|
42
|
-
formatDiff,
|
|
43
37
|
formatPullSummary,
|
|
44
38
|
formatPushSummary,
|
|
45
39
|
formatRollbackSummary,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
} from "../ui/sync-format.js";
|
|
41
|
+
import {
|
|
42
|
+
readRemoteSnapshot,
|
|
43
|
+
readSnapshotForHead,
|
|
44
|
+
requireCompatibleRemoteSelection,
|
|
45
|
+
} from "./remote-snapshot.js";
|
|
46
|
+
import { throwIfAborted } from "./signals.js";
|
|
47
|
+
import { createSyncDecision } from "./sync-decision.js";
|
|
48
|
+
import { errorMessage } from "./sync-errors.js";
|
|
49
|
+
import { inspectRemoteSelection } from "./sync-policy.js";
|
|
51
50
|
import {
|
|
52
51
|
canPullRemoteSessionsOnFirstSync,
|
|
53
52
|
canPullRemoteSettingsOnFirstSync,
|
|
@@ -61,17 +60,9 @@ import {
|
|
|
61
60
|
snapshotsMatch,
|
|
62
61
|
syncPolicyChanged,
|
|
63
62
|
} from "./sync-state.js";
|
|
64
|
-
import type {
|
|
65
|
-
AnySyncConfig,
|
|
66
|
-
CommandOptions,
|
|
67
|
-
Snapshot,
|
|
68
|
-
SnapshotOptions,
|
|
69
|
-
SyncState,
|
|
70
|
-
} from "./types.js";
|
|
71
63
|
|
|
72
64
|
const STATUS_KEY = "sync";
|
|
73
65
|
const VERSION = 1;
|
|
74
|
-
const DEFAULT_PROFILE = "default";
|
|
75
66
|
const POST_LOCAL_COMMIT_TIMEOUT_MS = 30_000;
|
|
76
67
|
|
|
77
68
|
export class PublicationStatePersistenceError extends Error {
|
|
@@ -109,190 +100,6 @@ interface PushInput {
|
|
|
109
100
|
backend?: SyncBackend;
|
|
110
101
|
}
|
|
111
102
|
|
|
112
|
-
function backendFor(config: AnySyncConfig, factory: SyncBackendFactory) {
|
|
113
|
-
return factory(config);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export async function status(
|
|
117
|
-
ctx: ExtensionCommandContext,
|
|
118
|
-
options: CommandOptions,
|
|
119
|
-
factory: SyncBackendFactory = createSyncBackend,
|
|
120
|
-
) {
|
|
121
|
-
const config = await loadConfig(options.setup);
|
|
122
|
-
throwIfAborted(options.signal);
|
|
123
|
-
ctx.ui.setStatus(STATUS_KEY, `checking ${config.setupName}`);
|
|
124
|
-
const backend = await backendFor(config, factory);
|
|
125
|
-
const local = await createSnapshot(
|
|
126
|
-
config.snapshotIdentity,
|
|
127
|
-
snapshotOptionsForContext(ctx, config),
|
|
128
|
-
);
|
|
129
|
-
throwIfAborted(options.signal);
|
|
130
|
-
const state = await readStateForConfig(config);
|
|
131
|
-
throwIfAborted(options.signal);
|
|
132
|
-
const head = await backend.readHead(options.signal);
|
|
133
|
-
throwIfAborted(options.signal);
|
|
134
|
-
const selectionState = head
|
|
135
|
-
? inspectRemoteSelection(config.include, { selection: head.selection, files: [] })
|
|
136
|
-
: undefined;
|
|
137
|
-
const localChanged = hasLocalChanges(local, state, config);
|
|
138
|
-
|
|
139
|
-
const remoteText = head
|
|
140
|
-
? `remote: ${head.snapshotId} from ${head.machine} at ${head.createdAt}`
|
|
141
|
-
: "remote: empty";
|
|
142
|
-
const remoteChanged = remoteChangedSinceState(head, state, config, (left, right) =>
|
|
143
|
-
backend.sameRevision(left, right),
|
|
144
|
-
);
|
|
145
|
-
const warnings = syncSessionsWarnings(config);
|
|
146
|
-
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
147
|
-
ctx.ui.notify(
|
|
148
|
-
[
|
|
149
|
-
`sync setup: ${config.setupName}`,
|
|
150
|
-
`storage connection: ${config.connectionName}`,
|
|
151
|
-
`storage location: ${safeTerminalText(backend.destination)}`,
|
|
152
|
-
`publication safety: ${publicationCapabilityDescription(backend.capability)}`,
|
|
153
|
-
`included content: ${config.include.join(", ") || "none"}`,
|
|
154
|
-
`sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
|
|
155
|
-
remoteText,
|
|
156
|
-
formatRemoteSelectionStatus(selectionState),
|
|
157
|
-
`local files: ${local.files.length}`,
|
|
158
|
-
`local changed since last sync: ${localChanged ? "yes" : "no"}`,
|
|
159
|
-
`remote changed since last sync: ${remoteChanged ? "yes" : "no"}`,
|
|
160
|
-
...warnings,
|
|
161
|
-
].join("\n"),
|
|
162
|
-
localChanged || remoteChanged || selectionState?.kind === "different" || warnings.length > 0
|
|
163
|
-
? "warning"
|
|
164
|
-
: "info",
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export async function diff(
|
|
169
|
-
ctx: ExtensionCommandContext,
|
|
170
|
-
options: CommandOptions,
|
|
171
|
-
factory: SyncBackendFactory = createSyncBackend,
|
|
172
|
-
) {
|
|
173
|
-
const config = await loadConfig(options.setup);
|
|
174
|
-
throwIfAborted(options.signal);
|
|
175
|
-
ctx.ui.setStatus(STATUS_KEY, `checking ${config.setupName}`);
|
|
176
|
-
const backend = await backendFor(config, factory);
|
|
177
|
-
const local = await createSnapshot(
|
|
178
|
-
config.snapshotIdentity,
|
|
179
|
-
snapshotOptionsForContext(ctx, config),
|
|
180
|
-
);
|
|
181
|
-
throwIfAborted(options.signal);
|
|
182
|
-
const { snapshot: remote, selectionState } = await readRemoteSnapshot(
|
|
183
|
-
backend,
|
|
184
|
-
config,
|
|
185
|
-
options.signal,
|
|
186
|
-
{ allowSelectionDifference: true },
|
|
187
|
-
);
|
|
188
|
-
throwIfAborted(options.signal);
|
|
189
|
-
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
190
|
-
|
|
191
|
-
const warnings = syncSessionsWarnings(config);
|
|
192
|
-
const header = [
|
|
193
|
-
`sync setup: ${config.setupName}`,
|
|
194
|
-
`storage connection: ${config.connectionName}`,
|
|
195
|
-
`storage location: ${safeTerminalText(backend.destination)}`,
|
|
196
|
-
`included content: ${config.include.join(", ") || "none"}`,
|
|
197
|
-
`sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
|
|
198
|
-
formatRemoteSelectionStatus(selectionState),
|
|
199
|
-
...warnings,
|
|
200
|
-
].join("\n");
|
|
201
|
-
const level = warnings.length > 0 || selectionState?.kind === "different" ? "warning" : "info";
|
|
202
|
-
if (!remote) {
|
|
203
|
-
ctx.ui.notify(
|
|
204
|
-
`${header}\n\n${formatSnapshotOnlyDiff("Remote is empty. Local push would upload", local)}`,
|
|
205
|
-
level,
|
|
206
|
-
);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
ctx.ui.notify(`${header}\n\n${formatDiff(local, remote)}`, level);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export async function doctor(
|
|
214
|
-
ctx: ExtensionCommandContext,
|
|
215
|
-
options: CommandOptions,
|
|
216
|
-
factory: SyncBackendFactory = createSyncBackend,
|
|
217
|
-
) {
|
|
218
|
-
const messages: string[] = [];
|
|
219
|
-
let level: "info" | "warning" = "info";
|
|
220
|
-
let snapshotOptions: SnapshotOptions = {};
|
|
221
|
-
let profile = DEFAULT_PROFILE;
|
|
222
|
-
let backend: SyncBackend | undefined;
|
|
223
|
-
let backendSummary: string[] = [];
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
const config = await loadConfig(options.setup);
|
|
227
|
-
throwIfAborted(options.signal);
|
|
228
|
-
backend = await backendFor(config, factory);
|
|
229
|
-
profile = config.snapshotIdentity;
|
|
230
|
-
snapshotOptions = snapshotOptionsForContext(ctx, config);
|
|
231
|
-
messages.push(
|
|
232
|
-
`config: ok (sync setup ${config.setupName})`,
|
|
233
|
-
`included content: ${config.include.join(", ") || "none"}`,
|
|
234
|
-
`sessions: ${config.include.includes("sessions") ? "included" : "excluded"}`,
|
|
235
|
-
);
|
|
236
|
-
backendSummary = [
|
|
237
|
-
`storage location: ${safeTerminalText(backend.destination)}`,
|
|
238
|
-
`publication safety: ${publicationCapabilityDescription(backend.capability)}`,
|
|
239
|
-
];
|
|
240
|
-
const warnings = syncSessionsWarnings(config);
|
|
241
|
-
if (warnings.length > 0) {
|
|
242
|
-
level = "warning";
|
|
243
|
-
messages.push(...warnings);
|
|
244
|
-
}
|
|
245
|
-
} catch (error) {
|
|
246
|
-
throwIfAborted(options.signal);
|
|
247
|
-
level = "warning";
|
|
248
|
-
messages.push(`config: ${errorMessage(error)}`);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const local = await createSnapshot(profile, snapshotOptions);
|
|
252
|
-
throwIfAborted(options.signal);
|
|
253
|
-
const secrets = scanSnapshot(local);
|
|
254
|
-
if (secrets.length > 0) {
|
|
255
|
-
level = "warning";
|
|
256
|
-
messages.push("secret scan: possible secrets found:");
|
|
257
|
-
messages.push(...secrets.map((secret) => `- ${secret}`));
|
|
258
|
-
} else {
|
|
259
|
-
messages.push(`secret scan: ok (${local.files.length} files checked)`);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const lock = await inspectLock();
|
|
263
|
-
throwIfAborted(options.signal);
|
|
264
|
-
if (lock.status === "valid" && isStaleLock(lock.lock)) {
|
|
265
|
-
level = "warning";
|
|
266
|
-
messages.push(
|
|
267
|
-
`lock: stale (pid ${lock.lock.pid}); run /sync unlock after verifying no sync is running`,
|
|
268
|
-
);
|
|
269
|
-
} else if (lock.status === "valid") {
|
|
270
|
-
messages.push(`lock: held by pid ${lock.lock.pid} since ${lock.lock.startedAt}`);
|
|
271
|
-
} else if (lock.status === "unreadable") {
|
|
272
|
-
level = "warning";
|
|
273
|
-
messages.push(
|
|
274
|
-
"lock: unreadable; use /sync unlock --stale only after verifying no sync is running",
|
|
275
|
-
);
|
|
276
|
-
} else if (await isLockGuardHeld()) {
|
|
277
|
-
throwIfAborted(options.signal);
|
|
278
|
-
level = "warning";
|
|
279
|
-
messages.push("lock: guard active while metadata is missing or still being initialized");
|
|
280
|
-
} else {
|
|
281
|
-
messages.push("lock: free");
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (backend) {
|
|
285
|
-
messages.push(...backendSummary);
|
|
286
|
-
const diagnostics = await backend.diagnose(options.signal);
|
|
287
|
-
throwIfAborted(options.signal);
|
|
288
|
-
for (const diagnostic of diagnostics) {
|
|
289
|
-
messages.push(diagnostic.message);
|
|
290
|
-
if (diagnostic.level !== "info") level = "warning";
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
ctx.ui.notify(messages.join("\n"), level);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
103
|
export async function push(
|
|
297
104
|
ctx: ExtensionCommandContext | ExtensionContext,
|
|
298
105
|
options: CommandOptions,
|
|
@@ -302,7 +109,7 @@ export async function push(
|
|
|
302
109
|
const config = input?.config ?? (await loadConfig(options.setup));
|
|
303
110
|
throwIfAborted(options.signal);
|
|
304
111
|
ctx.ui.setStatus(STATUS_KEY, `pushing ${config.setupName}`);
|
|
305
|
-
const backend = input?.backend ?? (await
|
|
112
|
+
const backend = input?.backend ?? (await factory(config));
|
|
306
113
|
const state = input?.state ?? (await readStateForConfig(config));
|
|
307
114
|
throwIfAborted(options.signal);
|
|
308
115
|
const local =
|
|
@@ -448,7 +255,7 @@ export async function pull(
|
|
|
448
255
|
const config = await loadConfig(options.setup);
|
|
449
256
|
throwIfAborted(options.signal);
|
|
450
257
|
ctx.ui.setStatus(STATUS_KEY, `pulling ${config.setupName}`);
|
|
451
|
-
const backend = await
|
|
258
|
+
const backend = await factory(config);
|
|
452
259
|
const state = await readStateForConfig(config);
|
|
453
260
|
throwIfAborted(options.signal);
|
|
454
261
|
const local = await createSnapshot(
|
|
@@ -553,7 +360,7 @@ export async function syncBoth(
|
|
|
553
360
|
) {
|
|
554
361
|
const config = await loadConfig(options.setup);
|
|
555
362
|
throwIfAborted(options.signal);
|
|
556
|
-
const backend = await
|
|
363
|
+
const backend = await factory(config);
|
|
557
364
|
const state = await readStateForConfig(config);
|
|
558
365
|
throwIfAborted(options.signal);
|
|
559
366
|
const local = await createSnapshot(
|
|
@@ -671,52 +478,6 @@ export async function syncBoth(
|
|
|
671
478
|
if (!options.silent) ctx.ui.notify("pi-sync is already up to date.", "info");
|
|
672
479
|
}
|
|
673
480
|
|
|
674
|
-
export async function history(
|
|
675
|
-
ctx: ExtensionCommandContext,
|
|
676
|
-
options: CommandOptions,
|
|
677
|
-
factory: SyncBackendFactory = createSyncBackend,
|
|
678
|
-
) {
|
|
679
|
-
const config = await loadConfig(options.setup);
|
|
680
|
-
throwIfAborted(options.signal);
|
|
681
|
-
const backend = await backendFor(config, factory);
|
|
682
|
-
const snapshots = (await backend.listHistory(options.signal)).slice(-20).reverse();
|
|
683
|
-
throwIfAborted(options.signal);
|
|
684
|
-
if (snapshots.length === 0) {
|
|
685
|
-
ctx.ui.notify("No remote pi-sync history found.", "info");
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
const currentSnapshot = snapshots[0]?.snapshotId;
|
|
690
|
-
if (ctx.mode === "tui") {
|
|
691
|
-
const labels = snapshots.map(
|
|
692
|
-
(item, index) =>
|
|
693
|
-
`${index + 1}. ${item.createdAt} · ${safeTerminalText(item.machine)} · ${item.snapshotId}${item.snapshotId === currentSnapshot ? " (current)" : ""}${item.syncSessions ? " · sessions" : ""}`,
|
|
694
|
-
);
|
|
695
|
-
const selected = await ctx.ui.select(
|
|
696
|
-
`History for sync setup “${safeTerminalText(config.setupName)}”\n\nChoose a snapshot to preview rollback.`,
|
|
697
|
-
[...labels, "Back"],
|
|
698
|
-
);
|
|
699
|
-
if (!selected || selected === "Back") return;
|
|
700
|
-
throwIfAborted(options.signal);
|
|
701
|
-
const index = labels.indexOf(selected);
|
|
702
|
-
const snapshot = snapshots[index];
|
|
703
|
-
if (!snapshot) return;
|
|
704
|
-
await withLock("rollback", () =>
|
|
705
|
-
rollback(ctx, { ...options, args: [snapshot.snapshotRef], yes: false }, factory, {
|
|
706
|
-
backendIdentity: backend.identity,
|
|
707
|
-
setup: config.setupName,
|
|
708
|
-
}),
|
|
709
|
-
);
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
ctx.ui.notify(
|
|
713
|
-
snapshots
|
|
714
|
-
.map((item) => `${item.snapshotRef} ${item.createdAt} ${safeTerminalText(item.machine)}`)
|
|
715
|
-
.join("\n"),
|
|
716
|
-
"info",
|
|
717
|
-
);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
481
|
export async function rollback(
|
|
721
482
|
ctx: ExtensionCommandContext,
|
|
722
483
|
options: CommandOptions,
|
|
@@ -728,7 +489,7 @@ export async function rollback(
|
|
|
728
489
|
|
|
729
490
|
const config = await loadConfig(options.setup);
|
|
730
491
|
throwIfAborted(options.signal);
|
|
731
|
-
const backend = await
|
|
492
|
+
const backend = await factory(config);
|
|
732
493
|
if (
|
|
733
494
|
expectedSelection &&
|
|
734
495
|
(backend.identity !== expectedSelection.backendIdentity ||
|
|
@@ -837,30 +598,6 @@ function protectedSessionPaths(ctx: ExtensionCommandContext | ExtensionContext)
|
|
|
837
598
|
return snapshotPath ? new Set([snapshotPath]) : new Set<string>();
|
|
838
599
|
}
|
|
839
600
|
|
|
840
|
-
function snapshotOptionsForContext(
|
|
841
|
-
ctx: ExtensionCommandContext | ExtensionContext,
|
|
842
|
-
config: AnySyncConfig,
|
|
843
|
-
): SnapshotOptions {
|
|
844
|
-
return {
|
|
845
|
-
include: config.include,
|
|
846
|
-
sessionDir: sessionDirFromContext(ctx),
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
function sessionDirFromContext(ctx: ExtensionCommandContext | ExtensionContext) {
|
|
851
|
-
const manager = ctx.sessionManager as typeof ctx.sessionManager & {
|
|
852
|
-
usesDefaultSessionDir?: () => boolean;
|
|
853
|
-
};
|
|
854
|
-
const usesDefaultSessionDir = manager.usesDefaultSessionDir;
|
|
855
|
-
if (typeof usesDefaultSessionDir === "function" && usesDefaultSessionDir.call(manager)) {
|
|
856
|
-
return undefined;
|
|
857
|
-
}
|
|
858
|
-
const getSessionDir = manager.getSessionDir;
|
|
859
|
-
return typeof getSessionDir === "function"
|
|
860
|
-
? (getSessionDir.call(manager) as string | undefined)
|
|
861
|
-
: undefined;
|
|
862
|
-
}
|
|
863
|
-
|
|
864
601
|
async function maybeReload(ctx: ExtensionCommandContext | ExtensionContext, signal?: AbortSignal) {
|
|
865
602
|
if (signal?.aborted || !("reload" in ctx)) return;
|
|
866
603
|
if (
|
|
@@ -915,27 +652,6 @@ async function snapshotForUpload(
|
|
|
915
652
|
return mergeRemotePreservedFiles(local, snapshot, config);
|
|
916
653
|
}
|
|
917
654
|
|
|
918
|
-
async function readRemoteSnapshot(
|
|
919
|
-
backend: SyncBackend,
|
|
920
|
-
config: AnySyncConfig,
|
|
921
|
-
signal?: AbortSignal,
|
|
922
|
-
options: { allowSelectionDifference?: boolean } = {},
|
|
923
|
-
) {
|
|
924
|
-
const head = await backend.readHead(signal);
|
|
925
|
-
if (!head) return { head: undefined, snapshot: undefined, selectionState: undefined };
|
|
926
|
-
const snapshot = await readSnapshotForHead(backend, head, signal);
|
|
927
|
-
const selectionState = inspectRemoteSelection(config.include, snapshot);
|
|
928
|
-
if (!options.allowSelectionDifference && selectionState.kind === "different") {
|
|
929
|
-
const configIdentity = syncConfigReviewFingerprint(config);
|
|
930
|
-
throw remoteSelectionMismatch(config, selectionState.include, configIdentity);
|
|
931
|
-
}
|
|
932
|
-
return {
|
|
933
|
-
head,
|
|
934
|
-
snapshot: filterSnapshotForConfigPolicy(snapshot, config),
|
|
935
|
-
selectionState,
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
|
|
939
655
|
async function confirmPush(
|
|
940
656
|
ctx: ExtensionCommandContext | ExtensionContext,
|
|
941
657
|
options: CommandOptions,
|
|
@@ -967,13 +683,6 @@ async function confirmPush(
|
|
|
967
683
|
return false;
|
|
968
684
|
}
|
|
969
685
|
|
|
970
|
-
function throwIfAborted(signal?: AbortSignal) {
|
|
971
|
-
if (!signal?.aborted) return;
|
|
972
|
-
throw signal.reason instanceof Error
|
|
973
|
-
? signal.reason
|
|
974
|
-
: new DOMException("The operation was aborted", "AbortError");
|
|
975
|
-
}
|
|
976
|
-
|
|
977
686
|
function sameRemoteHead(
|
|
978
687
|
backend: SyncBackend,
|
|
979
688
|
left: RemoteHead | undefined,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// First-use operation surface for the cached loader; implementations stay lazy.
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
backupLocal,
|
|
5
|
+
PublicationStatePersistenceError,
|
|
6
|
+
pull,
|
|
7
|
+
push,
|
|
8
|
+
RollbackPublicationError,
|
|
9
|
+
rollback,
|
|
10
|
+
syncBoth,
|
|
11
|
+
} from "./sync-mutations.js";
|
|
12
|
+
export { diff, doctor, history, status } from "./sync-queries.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { isDeniedPath, toPosix } from "
|
|
3
|
-
import type { Snapshot, SnapshotSelection } from "
|
|
2
|
+
import { isDeniedPath, toPosix } from "../paths.js";
|
|
3
|
+
import type { Snapshot, SnapshotSelection } from "../snapshot/snapshot-types.js";
|
|
4
4
|
|
|
5
5
|
export const BUILT_IN_SYNC_ROOTS = [
|
|
6
6
|
"settings.json",
|