@narumitw/pi-sync 0.49.15 → 0.50.1
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/attention-widget-Z7G3SYYJ.ts +9 -0
- package/dist/chunks/attention-widget-Z7G3SYYJ.ts.map +7 -0
- package/dist/chunks/chunk-2B2ELUF5.ts +29 -0
- package/dist/chunks/chunk-2B2ELUF5.ts.map +7 -0
- package/dist/chunks/{chunk-ARN2672F.ts → chunk-4CHTZMEO.ts} +23 -55
- package/dist/chunks/chunk-4CHTZMEO.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-JF6MZCT4.ts → chunk-AUSDLD6F.ts} +4 -4
- package/dist/chunks/chunk-AUSDLD6F.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-CCSN3F47.ts +51 -0
- package/dist/chunks/chunk-CCSN3F47.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-ETTRJDNP.ts} +4 -4
- package/dist/chunks/chunk-ETTRJDNP.ts.map +7 -0
- package/dist/chunks/{chunk-44LPGQTI.ts → chunk-G4RNQQZS.ts} +9 -7
- package/dist/chunks/chunk-G4RNQQZS.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-X7SQ2EQM.ts → chunk-IJ6FJFA5.ts} +9 -9
- package/dist/chunks/chunk-IJ6FJFA5.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-KW7YQEZN.ts +1255 -0
- package/dist/chunks/chunk-KW7YQEZN.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-HGV24P4T.ts → chunk-Q2TUM5X6.ts} +3 -3
- package/dist/chunks/chunk-Q2TUM5X6.ts.map +7 -0
- package/dist/chunks/chunk-S7YBZR42.ts +146 -0
- package/dist/chunks/chunk-S7YBZR42.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-SZWBGPID.ts +57 -0
- package/dist/chunks/chunk-SZWBGPID.ts.map +7 -0
- package/dist/chunks/chunk-T2B5IU74.ts +25 -0
- package/dist/chunks/chunk-T2B5IU74.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-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-GMVS4PD4.ts} +39 -14
- package/dist/chunks/git-backend-GMVS4PD4.ts.map +7 -0
- package/dist/chunks/manager-result-dispatcher-FKIBM3GO.ts +22 -0
- package/dist/chunks/manager-ui-R3XPUFFS.ts +1680 -0
- package/dist/chunks/manager-ui-R3XPUFFS.ts.map +7 -0
- package/dist/chunks/remote-selection-ui-D7RBRJNL.ts +21 -0
- package/dist/chunks/{s3-backend-552LXHE4.ts → s3-backend-66O4FGBH.ts} +94 -16
- package/dist/chunks/s3-backend-66O4FGBH.ts.map +7 -0
- package/dist/chunks/{setup-switch-5YDG5YGJ.ts → setup-switch-IS2PEZ3J.ts} +9 -9
- package/dist/chunks/setup-wizard-IUB6ESEU.ts +89 -0
- package/dist/chunks/setup-wizard-IUB6ESEU.ts.map +7 -0
- package/dist/chunks/{snapshot-2RYBUDRA.ts → snapshot-HKEWCVK6.ts} +7 -6
- package/dist/chunks/sync-inspection-CST2KL65.ts +20 -0
- package/dist/chunks/{sync-operations-3LLIHWZO.ts → sync-operations-LBX3GKCM.ts} +369 -289
- package/dist/chunks/sync-operations-LBX3GKCM.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 +476 -341
- 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/attention-widget.ts +1 -0
- 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/ui/sync-attention.ts +187 -0
- 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/sync-attention.ts +0 -100
- package/src/webdav-ui.ts +0 -421
- /package/dist/chunks/{manager-result-dispatcher-LWPYJAD5.ts.map → manager-result-dispatcher-FKIBM3GO.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-NUF5P4ZJ.ts.map → remote-selection-ui-D7RBRJNL.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-CST2KL65.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,330 @@
|
|
|
1
|
+
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
normalizeWebDavPath,
|
|
4
|
+
normalizeWebDavUrl,
|
|
5
|
+
validateWebDavCredentials,
|
|
6
|
+
} from "../../backends/webdav/webdav-config.js";
|
|
7
|
+
import {
|
|
8
|
+
addStorageConnection,
|
|
9
|
+
addSyncSetup,
|
|
10
|
+
saveNewV3Settings,
|
|
11
|
+
updateStorageConnection,
|
|
12
|
+
updateSyncSetup,
|
|
13
|
+
} from "../../settings/settings-management.js";
|
|
14
|
+
import type { PartialConfig } from "../../settings/settings-types.js";
|
|
15
|
+
import { automaticSyncSummary } from "../automatic-sync-summary.js";
|
|
16
|
+
import { promptSecret } from "../secret-input.js";
|
|
17
|
+
import { safeTerminalText as safe } from "../terminal-text.js";
|
|
18
|
+
import { promptAvailableSetupStorage } from "./setup-location-ui.js";
|
|
19
|
+
import {
|
|
20
|
+
chooseAutomaticSync,
|
|
21
|
+
chooseSessions,
|
|
22
|
+
chooseSetupContent,
|
|
23
|
+
includedContentLines,
|
|
24
|
+
promptResourceName,
|
|
25
|
+
readSetupConnection,
|
|
26
|
+
} from "./setup-prompts.js";
|
|
27
|
+
import { saveReviewedDraft } from "./setup-review.js";
|
|
28
|
+
import { promptTextInput } from "./text-input.js";
|
|
29
|
+
|
|
30
|
+
export async function showWebDavSetup(
|
|
31
|
+
ctx: ExtensionCommandContext,
|
|
32
|
+
targetName: string,
|
|
33
|
+
signal?: AbortSignal,
|
|
34
|
+
) {
|
|
35
|
+
const url = await promptWebDavUrl(ctx, signal);
|
|
36
|
+
if (!url) return false;
|
|
37
|
+
const username = await promptUsername(ctx, signal);
|
|
38
|
+
if (!username) return false;
|
|
39
|
+
const password = await promptSecret(ctx, "WebDAV password", { signal });
|
|
40
|
+
signal?.throwIfAborted();
|
|
41
|
+
if (password === undefined) return false;
|
|
42
|
+
const remotePath = await chooseDestination(ctx, signal);
|
|
43
|
+
if (!remotePath) return false;
|
|
44
|
+
const include = await chooseSetupContent(ctx, signal);
|
|
45
|
+
if (!include) return false;
|
|
46
|
+
const automatic = await chooseAutomaticSync(ctx, signal);
|
|
47
|
+
if (automatic === undefined) return false;
|
|
48
|
+
const sessions = await chooseSessions(ctx, signal);
|
|
49
|
+
if (sessions === undefined) return false;
|
|
50
|
+
const saved = await saveReviewedDraft(
|
|
51
|
+
ctx,
|
|
52
|
+
"Review WebDAV setup",
|
|
53
|
+
[
|
|
54
|
+
`Sync setup: ${safe(targetName)}`,
|
|
55
|
+
`Storage connection: ${safe(targetName)} (WebDAV)`,
|
|
56
|
+
`URL: ${safe(url)}`,
|
|
57
|
+
`Storage location: ${safe(remotePath)}`,
|
|
58
|
+
"Username and password: stored privately (values hidden)",
|
|
59
|
+
...includedContentLines(include, sessions),
|
|
60
|
+
`Automatic sync: ${automaticSyncSummary(automatic)}`,
|
|
61
|
+
"Check setup tests whether this server can safely save sync updates using a temporary probe.",
|
|
62
|
+
"Saving does not contact remote storage or start syncing.",
|
|
63
|
+
],
|
|
64
|
+
"Save setup",
|
|
65
|
+
(saveSignal) =>
|
|
66
|
+
saveNewV3Settings(
|
|
67
|
+
{
|
|
68
|
+
setupName: targetName,
|
|
69
|
+
connectionName: targetName,
|
|
70
|
+
connection: { type: "webdav", url, credentials: { username, password } },
|
|
71
|
+
setup: {
|
|
72
|
+
storage: { connection: targetName, path: remotePath },
|
|
73
|
+
sync: { include: [...include, ...(sessions ? ["sessions"] : [])], automatic },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
saveSignal,
|
|
77
|
+
),
|
|
78
|
+
signal,
|
|
79
|
+
);
|
|
80
|
+
if (saved && !signal?.aborted)
|
|
81
|
+
ctx.ui.notify(`Sync setup “${safe(targetName)}” saved. Choose Sync now to start.`, "info");
|
|
82
|
+
return saved;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function showAddWebDavTarget(
|
|
86
|
+
ctx: ExtensionCommandContext,
|
|
87
|
+
name: string,
|
|
88
|
+
profile: string,
|
|
89
|
+
signal?: AbortSignal,
|
|
90
|
+
) {
|
|
91
|
+
const remotePath = await chooseDestination(ctx, signal);
|
|
92
|
+
if (!remotePath) return false;
|
|
93
|
+
const storage = await promptAvailableSetupStorage(
|
|
94
|
+
ctx,
|
|
95
|
+
{ connection: profile, path: remotePath },
|
|
96
|
+
signal,
|
|
97
|
+
);
|
|
98
|
+
if (!storage) return false;
|
|
99
|
+
const include = await chooseSetupContent(ctx, signal);
|
|
100
|
+
if (!include) return false;
|
|
101
|
+
const automatic = await chooseAutomaticSync(ctx, signal);
|
|
102
|
+
if (automatic === undefined) return false;
|
|
103
|
+
const connection = await readSetupConnection(profile, signal);
|
|
104
|
+
if (connection.type !== "webdav") throw new Error("Storage connection changed; reopen setup.");
|
|
105
|
+
const saved = await saveReviewedDraft(
|
|
106
|
+
ctx,
|
|
107
|
+
"Review WebDAV sync setup",
|
|
108
|
+
[
|
|
109
|
+
`Sync setup: ${safe(name)}`,
|
|
110
|
+
`Storage connection: ${safe(profile)}`,
|
|
111
|
+
`URL: ${safe(connection.url)}`,
|
|
112
|
+
`Storage location: ${safe(storage.path)}`,
|
|
113
|
+
...includedContentLines(include),
|
|
114
|
+
`Automatic sync: ${automaticSyncSummary(automatic)}`,
|
|
115
|
+
"Adding this setup does not sync or modify remote data.",
|
|
116
|
+
],
|
|
117
|
+
"Add sync setup",
|
|
118
|
+
(saveSignal) =>
|
|
119
|
+
addSyncSetup(name, { storage, sync: { include, automatic } }, saveSignal, connection),
|
|
120
|
+
signal,
|
|
121
|
+
);
|
|
122
|
+
if (saved && !signal?.aborted) ctx.ui.notify(`Added sync setup “${safe(name)}”.`, "info");
|
|
123
|
+
return saved;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function showEditWebDavTarget(
|
|
127
|
+
ctx: ExtensionCommandContext,
|
|
128
|
+
partial: PartialConfig,
|
|
129
|
+
signal?: AbortSignal,
|
|
130
|
+
) {
|
|
131
|
+
const remotePath = await chooseDestination(ctx, signal, partial.storagePath);
|
|
132
|
+
if (!remotePath) return false;
|
|
133
|
+
const connection = await readSetupConnection(partial.connectionName, signal);
|
|
134
|
+
if (connection.type !== "webdav") throw new Error("Storage connection changed; reopen setup.");
|
|
135
|
+
const saved = await saveReviewedDraft(
|
|
136
|
+
ctx,
|
|
137
|
+
"Review sync setup",
|
|
138
|
+
[
|
|
139
|
+
`Sync setup: ${safe(partial.setupName)}`,
|
|
140
|
+
`Storage connection: ${safe(partial.connectionName)}`,
|
|
141
|
+
`URL: ${safe(connection.url)}`,
|
|
142
|
+
`Storage path: ${safe(partial.storagePath)} → ${safe(remotePath)}`,
|
|
143
|
+
"Saving changes the future storage location only; it does not move or delete remote data.",
|
|
144
|
+
],
|
|
145
|
+
"Save sync setup",
|
|
146
|
+
(saveSignal) =>
|
|
147
|
+
updateSyncSetup(
|
|
148
|
+
partial.setupName,
|
|
149
|
+
(setup) => ({
|
|
150
|
+
...setup,
|
|
151
|
+
storage: { ...setup.storage, path: remotePath },
|
|
152
|
+
}),
|
|
153
|
+
{ expectedStorage: partial, expectedConnection: connection, signal: saveSignal },
|
|
154
|
+
),
|
|
155
|
+
signal,
|
|
156
|
+
);
|
|
157
|
+
if (saved && !signal?.aborted)
|
|
158
|
+
ctx.ui.notify(`Saved sync setup “${safe(partial.setupName)}”.`, "info");
|
|
159
|
+
return saved;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function showAddWebDavStorageProfile(
|
|
163
|
+
ctx: ExtensionCommandContext,
|
|
164
|
+
signal?: AbortSignal,
|
|
165
|
+
) {
|
|
166
|
+
const name = await promptResourceName(ctx, "storage connection", "webdav", signal, true);
|
|
167
|
+
if (!name) return false;
|
|
168
|
+
const url = await promptWebDavUrl(ctx, signal);
|
|
169
|
+
if (!url) return false;
|
|
170
|
+
const username = await promptUsername(ctx, signal);
|
|
171
|
+
if (!username) return false;
|
|
172
|
+
const password = await promptSecret(ctx, "WebDAV password", { signal });
|
|
173
|
+
signal?.throwIfAborted();
|
|
174
|
+
if (password === undefined) return false;
|
|
175
|
+
const saved = await saveReviewedDraft(
|
|
176
|
+
ctx,
|
|
177
|
+
"Review storage connection",
|
|
178
|
+
[
|
|
179
|
+
`Name: ${safe(name)}`,
|
|
180
|
+
"Type: WebDAV",
|
|
181
|
+
`URL: ${safe(url)}`,
|
|
182
|
+
"Username and password: stored privately (values hidden)",
|
|
183
|
+
"Adding a connection does not contact the server or start syncing.",
|
|
184
|
+
],
|
|
185
|
+
"Add storage connection",
|
|
186
|
+
(saveSignal) =>
|
|
187
|
+
addStorageConnection(
|
|
188
|
+
name,
|
|
189
|
+
{ type: "webdav", url, credentials: { username, password } },
|
|
190
|
+
saveSignal,
|
|
191
|
+
),
|
|
192
|
+
signal,
|
|
193
|
+
);
|
|
194
|
+
if (saved && !signal?.aborted) ctx.ui.notify(`Added storage connection “${safe(name)}”.`, "info");
|
|
195
|
+
return saved;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function showEditWebDavStorageProfile(
|
|
199
|
+
ctx: ExtensionCommandContext,
|
|
200
|
+
name: string,
|
|
201
|
+
profile: Record<string, unknown>,
|
|
202
|
+
signal?: AbortSignal,
|
|
203
|
+
affectedSetups?: string[],
|
|
204
|
+
) {
|
|
205
|
+
const url = await promptWebDavUrl(
|
|
206
|
+
ctx,
|
|
207
|
+
signal,
|
|
208
|
+
typeof profile.url === "string" ? profile.url : undefined,
|
|
209
|
+
);
|
|
210
|
+
if (!url) return false;
|
|
211
|
+
const username = await promptUsername(ctx, signal);
|
|
212
|
+
if (!username) return false;
|
|
213
|
+
const hasPassword = typeof profile.password === "string" && profile.password.length > 0;
|
|
214
|
+
const passwordAction = hasPassword
|
|
215
|
+
? await select(
|
|
216
|
+
ctx,
|
|
217
|
+
"WebDAV password",
|
|
218
|
+
["Keep current password", "Replace password", "Cancel"],
|
|
219
|
+
signal,
|
|
220
|
+
)
|
|
221
|
+
: "Replace password";
|
|
222
|
+
if (!passwordAction || passwordAction === "Cancel") return false;
|
|
223
|
+
const replacePassword = passwordAction === "Replace password";
|
|
224
|
+
const password = replacePassword
|
|
225
|
+
? await promptSecret(ctx, "New WebDAV password", { signal })
|
|
226
|
+
: undefined;
|
|
227
|
+
signal?.throwIfAborted();
|
|
228
|
+
if (replacePassword && password === undefined) return false;
|
|
229
|
+
const saved = await saveReviewedDraft(
|
|
230
|
+
ctx,
|
|
231
|
+
"Review storage connection",
|
|
232
|
+
[
|
|
233
|
+
`Storage connection: ${safe(name)}`,
|
|
234
|
+
`URL: ${safe(String(profile.url))} → ${safe(url)}`,
|
|
235
|
+
"Username: stored privately (value hidden)",
|
|
236
|
+
`Password: ${replacePassword ? "will be replaced" : "unchanged"} (value hidden)`,
|
|
237
|
+
`Affected sync setups: ${affectedSetups?.length ? affectedSetups.map(safe).join(", ") : "None"}`,
|
|
238
|
+
"Saving changes future storage access for every affected setup; it does not move remote data.",
|
|
239
|
+
],
|
|
240
|
+
"Save storage connection",
|
|
241
|
+
(saveSignal) =>
|
|
242
|
+
updateStorageConnection(
|
|
243
|
+
name,
|
|
244
|
+
(current) => {
|
|
245
|
+
if (
|
|
246
|
+
current.type !== "webdav" ||
|
|
247
|
+
current.url !== profile.url ||
|
|
248
|
+
current.credentials.username !== profile.username ||
|
|
249
|
+
current.credentials.password !== profile.password
|
|
250
|
+
)
|
|
251
|
+
throw new Error("Storage connection changed while it was open; reopen it.");
|
|
252
|
+
return {
|
|
253
|
+
...current,
|
|
254
|
+
url,
|
|
255
|
+
credentials: {
|
|
256
|
+
...current.credentials,
|
|
257
|
+
username,
|
|
258
|
+
password: password ?? current.credentials.password,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
affectedSetups,
|
|
263
|
+
saveSignal,
|
|
264
|
+
),
|
|
265
|
+
signal,
|
|
266
|
+
);
|
|
267
|
+
if (saved && !signal?.aborted) ctx.ui.notify(`Saved storage connection “${safe(name)}”.`, "info");
|
|
268
|
+
return saved;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function chooseDestination(
|
|
272
|
+
ctx: ExtensionCommandContext,
|
|
273
|
+
signal?: AbortSignal,
|
|
274
|
+
current = "./",
|
|
275
|
+
) {
|
|
276
|
+
return promptTextInput(
|
|
277
|
+
ctx,
|
|
278
|
+
"WebDAV storage path\n\nFolder relative to the collection URL, not your local filesystem.\n./ uses the collection root. Use different folders for independent setups.",
|
|
279
|
+
{ defaultValue: current, validate: normalizeWebDavPath },
|
|
280
|
+
signal,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function promptUsername(ctx: ExtensionCommandContext, signal?: AbortSignal) {
|
|
285
|
+
return promptTextInput(
|
|
286
|
+
ctx,
|
|
287
|
+
"WebDAV username\n\nEnter the account username; stored values remain hidden.",
|
|
288
|
+
{
|
|
289
|
+
example: "user",
|
|
290
|
+
validate: (value) => {
|
|
291
|
+
validateWebDavCredentials(value);
|
|
292
|
+
return value;
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
signal,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function promptWebDavUrl(
|
|
300
|
+
ctx: ExtensionCommandContext,
|
|
301
|
+
signal?: AbortSignal,
|
|
302
|
+
current?: string,
|
|
303
|
+
) {
|
|
304
|
+
return promptTextInput(
|
|
305
|
+
ctx,
|
|
306
|
+
"WebDAV collection URL\n\nUse the HTTPS WebDAV URL from your provider, not its web login page.",
|
|
307
|
+
{
|
|
308
|
+
...(current
|
|
309
|
+
? { defaultValue: current }
|
|
310
|
+
: { example: "https://cloud.example.com/remote.php/dav/files/user" }),
|
|
311
|
+
validate: (value) => {
|
|
312
|
+
const url = normalizeWebDavUrl(value);
|
|
313
|
+
if (!url) throw new Error("WebDAV URL is required.");
|
|
314
|
+
return url;
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
signal,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function select(
|
|
322
|
+
ctx: ExtensionCommandContext,
|
|
323
|
+
title: string,
|
|
324
|
+
options: string[],
|
|
325
|
+
signal?: AbortSignal,
|
|
326
|
+
) {
|
|
327
|
+
const value = await ctx.ui.select(title, options, { signal });
|
|
328
|
+
signal?.throwIfAborted();
|
|
329
|
+
return value;
|
|
330
|
+
}
|
|
@@ -1,19 +1,29 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
1
2
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
3
|
import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
|
|
3
|
-
import { isCloudflareR2Endpoint, readLocalConfigObject } from "./config.js";
|
|
4
|
-
import { showAddGitStorageProfile, showEditGitStorageProfile } from "./git-ui.js";
|
|
5
|
-
import { errorMessage, ownRecord, requiredInput, safeTerminalText } from "./manager-helpers.js";
|
|
6
|
-
import {
|
|
7
|
-
applyS3CredentialUpdate,
|
|
8
|
-
chooseS3Credentials,
|
|
9
|
-
chooseS3CredentialUpdate,
|
|
10
|
-
} from "./s3-credentials-ui.js";
|
|
11
4
|
import {
|
|
12
5
|
addStorageConnection,
|
|
13
6
|
removeStorageConnection,
|
|
14
7
|
updateStorageConnection,
|
|
15
|
-
} from "
|
|
16
|
-
import {
|
|
8
|
+
} from "../settings/settings-management.js";
|
|
9
|
+
import { readLocalConfigObject } from "../settings/settings-store.js";
|
|
10
|
+
import {
|
|
11
|
+
isCloudflareR2Endpoint,
|
|
12
|
+
normalizeS3Endpoint,
|
|
13
|
+
ownRecord,
|
|
14
|
+
} from "../settings/settings-validation.js";
|
|
15
|
+
import { syncErrorGuidance } from "../sync/sync-error-guidance.js";
|
|
16
|
+
import { showAddGitStorageProfile, showEditGitStorageProfile } from "./setup/git-ui.js";
|
|
17
|
+
import {
|
|
18
|
+
applyS3CredentialUpdate,
|
|
19
|
+
chooseS3Credentials,
|
|
20
|
+
chooseS3CredentialUpdate,
|
|
21
|
+
} from "./setup/s3-credentials-ui.js";
|
|
22
|
+
import { promptResourceName } from "./setup/setup-prompts.js";
|
|
23
|
+
import { saveReviewedDraft } from "./setup/setup-review.js";
|
|
24
|
+
import { requiredInput, requiredValueInput } from "./setup/text-input.js";
|
|
25
|
+
import { showAddWebDavStorageProfile, showEditWebDavStorageProfile } from "./setup/webdav-ui.js";
|
|
26
|
+
import { safeTerminalText } from "./terminal-text.js";
|
|
17
27
|
|
|
18
28
|
export async function showStorageConnections(ctx: ExtensionCommandContext, signal?: AbortSignal) {
|
|
19
29
|
let selectedName: string | undefined;
|
|
@@ -35,7 +45,7 @@ export async function showStorageConnections(ctx: ExtensionCommandContext, signa
|
|
|
35
45
|
kind: "actions",
|
|
36
46
|
title: "Storage connections",
|
|
37
47
|
lines: state.version3
|
|
38
|
-
? []
|
|
48
|
+
? ["Server addresses and sign-in details, reusable across sync setups."]
|
|
39
49
|
: ["Create version 3 settings before managing storage connections."],
|
|
40
50
|
items: state.version3
|
|
41
51
|
? [
|
|
@@ -46,6 +56,7 @@ export async function showStorageConnections(ctx: ExtensionCommandContext, signa
|
|
|
46
56
|
return {
|
|
47
57
|
id,
|
|
48
58
|
label: safeTerminalText(name),
|
|
59
|
+
description: connectionSummary(ownRecord(state.profiles[name]) ?? {}),
|
|
49
60
|
action: "select" as const,
|
|
50
61
|
};
|
|
51
62
|
}),
|
|
@@ -84,10 +95,7 @@ export async function showStorageConnections(ctx: ExtensionCommandContext, signa
|
|
|
84
95
|
await showAddStorageConnection(ctx, signal);
|
|
85
96
|
} catch (error) {
|
|
86
97
|
if (!signal?.aborted) {
|
|
87
|
-
ctx.ui.notify(
|
|
88
|
-
`Storage connection was not added: ${safeTerminalText(errorMessage(error))} Retry from Add storage connection.`,
|
|
89
|
-
"error",
|
|
90
|
-
);
|
|
98
|
+
ctx.ui.notify(`Storage connection was not added: ${syncErrorGuidance(error)}`, "error");
|
|
91
99
|
}
|
|
92
100
|
}
|
|
93
101
|
return { kind: "stay" };
|
|
@@ -165,7 +173,9 @@ async function loadStorageMenuState(selectedName: string | undefined, signal?: A
|
|
|
165
173
|
`Credentials: ${credentialSource(profile)}`,
|
|
166
174
|
`Used by: ${usedBy.length > 0 ? usedBy.map(safeTerminalText).join(", ") : "No sync setups"}`,
|
|
167
175
|
...(usedBy.length > 0
|
|
168
|
-
? [
|
|
176
|
+
? [
|
|
177
|
+
"Remove unavailable: remove the listed sync setups first. Switch away from a current setup before removing it if other setups remain.",
|
|
178
|
+
]
|
|
169
179
|
: []),
|
|
170
180
|
],
|
|
171
181
|
},
|
|
@@ -180,7 +190,7 @@ function notifyConnectionError(
|
|
|
180
190
|
) {
|
|
181
191
|
if (signal?.aborted) return;
|
|
182
192
|
ctx.ui.notify(
|
|
183
|
-
`Storage connection “${safeTerminalText(name)}” was not changed: ${
|
|
193
|
+
`Storage connection “${safeTerminalText(name)}” was not changed: ${syncErrorGuidance(error)}`,
|
|
184
194
|
"error",
|
|
185
195
|
);
|
|
186
196
|
}
|
|
@@ -218,9 +228,15 @@ async function editStorageConnection(
|
|
|
218
228
|
"Endpoint",
|
|
219
229
|
String(profile.endpoint ?? "https://s3.example.com"),
|
|
220
230
|
signal,
|
|
231
|
+
normalizeS3Endpoint,
|
|
221
232
|
);
|
|
222
233
|
if (!endpoint || signal?.aborted) return;
|
|
223
|
-
const region = await requiredInput(
|
|
234
|
+
const region = await requiredInput(
|
|
235
|
+
ctx,
|
|
236
|
+
"Region\n\nUse your bucket's region; Cloudflare R2 uses auto.",
|
|
237
|
+
String(profile.region ?? "auto"),
|
|
238
|
+
signal,
|
|
239
|
+
);
|
|
224
240
|
if (!region || signal?.aborted) return;
|
|
225
241
|
const storedCredentials = ownRecord(profile.credentials) ?? {};
|
|
226
242
|
const credentials = await chooseS3CredentialUpdate(
|
|
@@ -229,41 +245,41 @@ async function editStorageConnection(
|
|
|
229
245
|
signal,
|
|
230
246
|
);
|
|
231
247
|
if (!credentials || signal?.aborted) return;
|
|
232
|
-
const
|
|
248
|
+
const saved = await saveReviewedDraft(
|
|
249
|
+
ctx,
|
|
250
|
+
"Review storage connection",
|
|
233
251
|
[
|
|
234
|
-
"Review storage connection",
|
|
235
|
-
"",
|
|
236
252
|
`Storage connection: ${safeTerminalText(name)}`,
|
|
237
253
|
`Endpoint: ${safeTerminalText(String(profile.endpoint ?? "missing"))} → ${safeTerminalText(endpoint)}`,
|
|
238
254
|
`Region: ${safeTerminalText(String(profile.region ?? "auto"))} → ${safeTerminalText(region)}`,
|
|
239
255
|
`Credentials: ${safeTerminalText(credentials.summary)}`,
|
|
240
256
|
`Affected sync setups: ${usedBy.length > 0 ? usedBy.map(safeTerminalText).join(", ") : "None"}`,
|
|
241
257
|
"Saving changes future storage access for every affected setup; it does not move remote data.",
|
|
242
|
-
]
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
258
|
+
],
|
|
259
|
+
"Save storage connection",
|
|
260
|
+
(saveSignal) =>
|
|
261
|
+
updateStorageConnection(
|
|
262
|
+
name,
|
|
263
|
+
(current) => {
|
|
264
|
+
if (current.type !== "s3" || !isDeepStrictEqual(current, profile)) {
|
|
265
|
+
throw new Error("Storage connection changed while it was open; reopen it.");
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
...current,
|
|
269
|
+
endpoint,
|
|
270
|
+
region,
|
|
271
|
+
credentials: applyS3CredentialUpdate(
|
|
272
|
+
current.credentials,
|
|
273
|
+
credentials,
|
|
274
|
+
) as typeof current.credentials,
|
|
275
|
+
};
|
|
276
|
+
},
|
|
277
|
+
usedBy,
|
|
278
|
+
saveSignal,
|
|
279
|
+
),
|
|
264
280
|
signal,
|
|
265
281
|
);
|
|
266
|
-
if (signal?.aborted) return;
|
|
282
|
+
if (!saved || signal?.aborted) return;
|
|
267
283
|
ctx.ui.notify(`Saved storage connection “${safeTerminalText(name)}”.`, "info");
|
|
268
284
|
}
|
|
269
285
|
|
|
@@ -283,55 +299,64 @@ export async function showAddStorageConnection(ctx: ExtensionCommandContext, sig
|
|
|
283
299
|
if (signal?.aborted || !preset || preset === "Cancel") return false;
|
|
284
300
|
if (preset === "WebDAV") return showAddWebDavStorageProfile(ctx, signal);
|
|
285
301
|
if (preset === "Git") return showAddGitStorageProfile(ctx, signal);
|
|
286
|
-
const name = await
|
|
302
|
+
const name = await promptResourceName(
|
|
287
303
|
ctx,
|
|
288
|
-
"
|
|
304
|
+
"storage connection",
|
|
289
305
|
preset === "Cloudflare R2" ? "r2" : "s3",
|
|
290
306
|
signal,
|
|
291
307
|
);
|
|
292
308
|
if (!name || signal?.aborted) return false;
|
|
293
|
-
const endpoint = await
|
|
309
|
+
const endpoint = await requiredValueInput(
|
|
294
310
|
ctx,
|
|
295
|
-
"
|
|
311
|
+
"S3 API endpoint\n\nUse the API URL from your storage provider, not its web console.",
|
|
296
312
|
preset === "Cloudflare R2"
|
|
297
313
|
? "https://<account-id>.r2.cloudflarestorage.com"
|
|
298
314
|
: "https://s3.example.com",
|
|
299
315
|
signal,
|
|
316
|
+
normalizeS3Endpoint,
|
|
300
317
|
);
|
|
301
318
|
if (!endpoint || signal?.aborted) return false;
|
|
302
319
|
const region =
|
|
303
|
-
preset === "Cloudflare R2"
|
|
320
|
+
preset === "Cloudflare R2"
|
|
321
|
+
? "auto"
|
|
322
|
+
: await requiredInput(
|
|
323
|
+
ctx,
|
|
324
|
+
"Region\n\nUse the region assigned to your bucket by the provider.",
|
|
325
|
+
"us-east-1",
|
|
326
|
+
signal,
|
|
327
|
+
);
|
|
304
328
|
if (!region || signal?.aborted) return false;
|
|
305
329
|
const credentials = await chooseS3Credentials(ctx, signal);
|
|
306
330
|
if (!credentials || signal?.aborted) return false;
|
|
307
|
-
const
|
|
331
|
+
const saved = await saveReviewedDraft(
|
|
332
|
+
ctx,
|
|
333
|
+
"Review storage connection",
|
|
308
334
|
[
|
|
309
|
-
"Review storage connection",
|
|
310
|
-
"",
|
|
311
335
|
`Name: ${safeTerminalText(name)}`,
|
|
312
336
|
`Type: ${preset}`,
|
|
313
337
|
`Endpoint: ${safeTerminalText(endpoint)}`,
|
|
314
338
|
`Region: ${safeTerminalText(region)}`,
|
|
315
339
|
`Credentials: ${safeTerminalText(credentials.summary)}`,
|
|
316
340
|
"Adding a connection does not contact remote storage or start syncing.",
|
|
317
|
-
]
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
341
|
+
],
|
|
342
|
+
"Add storage connection",
|
|
343
|
+
(saveSignal) =>
|
|
344
|
+
addStorageConnection(
|
|
345
|
+
name,
|
|
346
|
+
{
|
|
347
|
+
type: "s3",
|
|
348
|
+
endpoint,
|
|
349
|
+
region,
|
|
350
|
+
credentials: {
|
|
351
|
+
accessKeyId: credentials.profileFields.accessKeyId ?? "",
|
|
352
|
+
secretAccessKey: credentials.profileFields.secretAccessKey ?? "",
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
saveSignal,
|
|
356
|
+
),
|
|
333
357
|
signal,
|
|
334
358
|
);
|
|
359
|
+
if (!saved) return false;
|
|
335
360
|
if (signal?.aborted) return true;
|
|
336
361
|
ctx.ui.notify(`Added storage connection “${safeTerminalText(name)}”.`, "info");
|
|
337
362
|
return true;
|
|
@@ -344,6 +369,10 @@ function referencingSetups(raw: Record<string, unknown> | undefined, connection:
|
|
|
344
369
|
.sort((left, right) => left.localeCompare(right));
|
|
345
370
|
}
|
|
346
371
|
|
|
372
|
+
export function connectionSummary(profile: Record<string, unknown>) {
|
|
373
|
+
return `${connectionType(profile)} · ${connectionEndpoint(profile)}`;
|
|
374
|
+
}
|
|
375
|
+
|
|
347
376
|
function connectionType(profile: Record<string, unknown>) {
|
|
348
377
|
if (profile.type === "git") return "Git";
|
|
349
378
|
if (profile.type === "webdav") return "WebDAV";
|
|
@@ -367,7 +396,8 @@ function connectionEndpoint(profile: Record<string, unknown>) {
|
|
|
367
396
|
if (typeof value !== "string" || value.length === 0) return "Missing";
|
|
368
397
|
if (profile.type === "git") return safeTerminalText(value);
|
|
369
398
|
try {
|
|
370
|
-
|
|
399
|
+
const url = new URL(value);
|
|
400
|
+
return safeTerminalText(`${url.origin}${url.pathname}`);
|
|
371
401
|
} catch {
|
|
372
402
|
return "Invalid";
|
|
373
403
|
}
|