@narumitw/pi-sync 0.50.3 → 0.51.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 +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/src/sync/sync-policy.ts
CHANGED
|
@@ -3,15 +3,15 @@ import { isDeniedPath, toPosix } from "../paths.js";
|
|
|
3
3
|
import type { Snapshot, SnapshotSelection } from "../snapshot/snapshot-types.js";
|
|
4
4
|
|
|
5
5
|
export const BUILT_IN_SYNC_ROOTS = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
"settings.json",
|
|
7
|
+
"keybindings.json",
|
|
8
|
+
"models.json",
|
|
9
|
+
"AGENTS.md",
|
|
10
|
+
"APPEND_SYSTEM.md",
|
|
11
|
+
"skills",
|
|
12
|
+
"prompts",
|
|
13
|
+
"themes",
|
|
14
|
+
"extensions",
|
|
15
15
|
] as const;
|
|
16
16
|
|
|
17
17
|
export const DEFAULT_SYNC_INCLUDE = [...BUILT_IN_SYNC_ROOTS] as const;
|
|
@@ -22,417 +22,387 @@ const MAX_SYNC_INCLUDE_PATH_BYTES = 4_096;
|
|
|
22
22
|
const MAX_SYNC_INCLUDE_TOTAL_BYTES = 256 * 1_024;
|
|
23
23
|
|
|
24
24
|
interface IncludePathNode {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
selected?: string;
|
|
26
|
+
children: Map<string, IncludePathNode>;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const BUILT_IN_BY_LOWER = new Map<string, BuiltInSyncFile>(
|
|
30
|
-
|
|
30
|
+
BUILT_IN_SYNC_ROOTS.map((fileName) => [fileName.toLowerCase(), fileName]),
|
|
31
31
|
);
|
|
32
32
|
const TOP_LEVEL_FILE_PATHS = new Map<string, string>(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
);
|
|
38
|
-
const TOP_LEVEL_DIRS = new Set<string>(
|
|
39
|
-
BUILT_IN_SYNC_ROOTS.filter((fileName) => !fileName.includes(".")),
|
|
33
|
+
BUILT_IN_SYNC_ROOTS.filter((fileName) => fileName.includes(".")).map((fileName) => [
|
|
34
|
+
fileName.toLowerCase(),
|
|
35
|
+
fileName,
|
|
36
|
+
]),
|
|
40
37
|
);
|
|
38
|
+
const TOP_LEVEL_DIRS = new Set<string>(BUILT_IN_SYNC_ROOTS.filter((fileName) => !fileName.includes(".")));
|
|
41
39
|
const RESERVED_TOP_LEVEL_NAMES = new Set<string>([...BUILT_IN_BY_LOWER.keys(), "sessions"]);
|
|
42
40
|
|
|
43
41
|
export function normalizeSyncInclude(value: unknown): string[] {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
return result;
|
|
42
|
+
if (!Array.isArray(value)) {
|
|
43
|
+
throw new Error("Invalid pi-sync settings: sync.include must be an array.");
|
|
44
|
+
}
|
|
45
|
+
if (value.length > MAX_SYNC_INCLUDE_ITEMS) {
|
|
46
|
+
throw new Error(`Invalid pi-sync settings: sync.include has too many items; limit: ${MAX_SYNC_INCLUDE_ITEMS}.`);
|
|
47
|
+
}
|
|
48
|
+
const result: string[] = [];
|
|
49
|
+
const seen = new Set<string>();
|
|
50
|
+
const pathRoot: IncludePathNode = { children: new Map() };
|
|
51
|
+
let totalBytes = 0;
|
|
52
|
+
for (const item of value) {
|
|
53
|
+
if (typeof item !== "string") {
|
|
54
|
+
throw new Error("Invalid pi-sync settings: sync.include items must be strings.");
|
|
55
|
+
}
|
|
56
|
+
const itemBytes = Buffer.byteLength(item, "utf8");
|
|
57
|
+
if (itemBytes > MAX_SYNC_INCLUDE_PATH_BYTES) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Invalid pi-sync settings: sync.include item is too long; limit: ${MAX_SYNC_INCLUDE_PATH_BYTES} bytes.`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
totalBytes += itemBytes;
|
|
63
|
+
if (totalBytes > MAX_SYNC_INCLUDE_TOTAL_BYTES) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Invalid pi-sync settings: sync.include is too large; limit: ${MAX_SYNC_INCLUDE_TOTAL_BYTES} bytes.`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
const trimmed = item.trim();
|
|
69
|
+
const builtIn = BUILT_IN_BY_LOWER.get(trimmed.toLowerCase());
|
|
70
|
+
const normalized = builtIn ?? (trimmed.toLowerCase() === "sessions" ? "sessions" : trimmed);
|
|
71
|
+
if (!builtIn && normalized !== "sessions") validateAgentRelativeInclude(normalized);
|
|
72
|
+
const identity = normalized.toLowerCase();
|
|
73
|
+
if (seen.has(identity)) {
|
|
74
|
+
throw new Error(`Invalid pi-sync settings: duplicate sync.include item: ${item}`);
|
|
75
|
+
}
|
|
76
|
+
addIncludePath(pathRoot, identity, item);
|
|
77
|
+
seen.add(identity);
|
|
78
|
+
result.push(normalized);
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
85
81
|
}
|
|
86
82
|
|
|
87
83
|
function addIncludePath(root: IncludePathNode, identity: string, source: string) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
84
|
+
let node = root;
|
|
85
|
+
for (const segment of identity.split("/")) {
|
|
86
|
+
if (node.selected !== undefined) throwOverlappingInclude(source);
|
|
87
|
+
let child = node.children.get(segment);
|
|
88
|
+
if (!child) {
|
|
89
|
+
child = { children: new Map() };
|
|
90
|
+
node.children.set(segment, child);
|
|
91
|
+
}
|
|
92
|
+
node = child;
|
|
93
|
+
}
|
|
94
|
+
if (node.children.size > 0) throwOverlappingInclude(source);
|
|
95
|
+
node.selected = identity;
|
|
100
96
|
}
|
|
101
97
|
|
|
102
98
|
function throwOverlappingInclude(item: string): never {
|
|
103
|
-
|
|
104
|
-
`Invalid pi-sync settings: overlapping sync.include items are ambiguous: ${item}`,
|
|
105
|
-
);
|
|
99
|
+
throw new Error(`Invalid pi-sync settings: overlapping sync.include items are ambiguous: ${item}`);
|
|
106
100
|
}
|
|
107
101
|
|
|
108
102
|
function validateAgentRelativeInclude(value: string) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
);
|
|
133
|
-
}
|
|
103
|
+
const normalized = toPosix(value);
|
|
104
|
+
const topLevel = normalized.split("/")[0]?.toLowerCase();
|
|
105
|
+
if (
|
|
106
|
+
!normalized ||
|
|
107
|
+
normalized === "." ||
|
|
108
|
+
normalized === ".." ||
|
|
109
|
+
normalized.startsWith("../") ||
|
|
110
|
+
path.posix.isAbsolute(normalized) ||
|
|
111
|
+
normalized.includes("\\") ||
|
|
112
|
+
path.posix.normalize(normalized) !== normalized ||
|
|
113
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: Include paths cannot contain controls.
|
|
114
|
+
/[\u0000-\u001f\u007f-\u009f]/u.test(normalized)
|
|
115
|
+
) {
|
|
116
|
+
throw new Error(`Invalid pi-sync settings: sync.include item must be a safe agent-relative path: ${value}`);
|
|
117
|
+
}
|
|
118
|
+
if (isDeniedPath(normalized)) {
|
|
119
|
+
throw new Error(`Invalid pi-sync settings: ${value} cannot be synced.`);
|
|
120
|
+
}
|
|
121
|
+
if (topLevel && RESERVED_TOP_LEVEL_NAMES.has(topLevel)) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Invalid pi-sync settings: use the canonical ${topLevel} root instead of a nested sync.include path.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
134
126
|
}
|
|
135
127
|
|
|
136
128
|
export function portableSnapshotSelection(value: unknown): SnapshotSelection {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
129
|
+
const selection = value as Partial<SnapshotSelection> | null;
|
|
130
|
+
if (
|
|
131
|
+
!selection ||
|
|
132
|
+
typeof selection !== "object" ||
|
|
133
|
+
Array.isArray(selection) ||
|
|
134
|
+
selection.version !== SNAPSHOT_SELECTION_VERSION ||
|
|
135
|
+
!Object.hasOwn(selection, "include") ||
|
|
136
|
+
Object.keys(selection).some((key) => key !== "version" && key !== "include")
|
|
137
|
+
) {
|
|
138
|
+
throw new Error("Invalid snapshot selection policy.");
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
version: SNAPSHOT_SELECTION_VERSION,
|
|
142
|
+
include: normalizeSyncInclude(selection.include),
|
|
143
|
+
};
|
|
152
144
|
}
|
|
153
145
|
|
|
154
146
|
export function snapshotSelectionInclude(snapshot: Pick<Snapshot, "selection">) {
|
|
155
|
-
|
|
156
|
-
? undefined
|
|
157
|
-
: portableSnapshotSelection(snapshot.selection).include;
|
|
147
|
+
return snapshot.selection === undefined ? undefined : portableSnapshotSelection(snapshot.selection).include;
|
|
158
148
|
}
|
|
159
149
|
|
|
160
150
|
export function selectionForSnapshot(include: unknown): SnapshotSelection {
|
|
161
|
-
|
|
151
|
+
return { version: SNAPSHOT_SELECTION_VERSION, include: normalizeSyncInclude(include) };
|
|
162
152
|
}
|
|
163
153
|
|
|
164
154
|
export function sameSyncInclude(left: unknown, right: unknown) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
155
|
+
const normalizedLeft = normalizeSyncInclude(left);
|
|
156
|
+
const normalizedRight = normalizeSyncInclude(right);
|
|
157
|
+
return (
|
|
158
|
+
normalizedLeft.length === normalizedRight.length &&
|
|
159
|
+
normalizedLeft.every((item, index) => item === normalizedRight[index])
|
|
160
|
+
);
|
|
171
161
|
}
|
|
172
162
|
|
|
173
163
|
export function compareSyncInclude(local: unknown, remote: unknown) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
164
|
+
const localInclude = normalizeSyncInclude(local);
|
|
165
|
+
const remoteInclude = normalizeSyncInclude(remote);
|
|
166
|
+
const localSet = new Set(localInclude);
|
|
167
|
+
const remoteSet = new Set(remoteInclude);
|
|
168
|
+
return {
|
|
169
|
+
same: sameSyncInclude(localInclude, remoteInclude),
|
|
170
|
+
remoteOnly: remoteInclude.filter((item) => !localSet.has(item)),
|
|
171
|
+
localOnly: localInclude.filter((item) => !remoteSet.has(item)),
|
|
172
|
+
};
|
|
183
173
|
}
|
|
184
174
|
|
|
185
175
|
export type RemoteSelectionState =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
176
|
+
| { kind: "legacy"; discovered: string[] }
|
|
177
|
+
| { kind: "same"; include: string[] }
|
|
178
|
+
| {
|
|
179
|
+
kind: "different";
|
|
180
|
+
include: string[];
|
|
181
|
+
remoteOnly: string[];
|
|
182
|
+
localOnly: string[];
|
|
183
|
+
};
|
|
194
184
|
|
|
195
185
|
export function inspectRemoteSelection(
|
|
196
|
-
|
|
197
|
-
|
|
186
|
+
localInclude: unknown,
|
|
187
|
+
snapshot: Pick<Snapshot, "selection" | "files">,
|
|
198
188
|
): RemoteSelectionState {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
189
|
+
const remoteInclude = snapshotSelectionInclude(snapshot);
|
|
190
|
+
if (!remoteInclude) {
|
|
191
|
+
return { kind: "legacy", discovered: discoverLegacySnapshotInclude(snapshot) };
|
|
192
|
+
}
|
|
193
|
+
const comparison = compareSyncInclude(localInclude, remoteInclude);
|
|
194
|
+
return comparison.same
|
|
195
|
+
? { kind: "same", include: remoteInclude }
|
|
196
|
+
: { kind: "different", include: remoteInclude, ...comparison };
|
|
207
197
|
}
|
|
208
198
|
|
|
209
199
|
export interface RemoteSelectionDecision {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
200
|
+
setupName: string;
|
|
201
|
+
configIdentity: string;
|
|
202
|
+
localInclude: readonly string[];
|
|
203
|
+
remoteInclude: readonly string[];
|
|
214
204
|
}
|
|
215
205
|
|
|
216
206
|
export class RemoteSelectionMismatchError extends Error {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
207
|
+
readonly decision: RemoteSelectionDecision;
|
|
208
|
+
readonly setupName: string;
|
|
209
|
+
readonly localInclude: string[];
|
|
210
|
+
readonly remoteInclude: string[];
|
|
211
|
+
|
|
212
|
+
constructor(
|
|
213
|
+
setupName: string,
|
|
214
|
+
localInclude: unknown,
|
|
215
|
+
remoteInclude: unknown,
|
|
216
|
+
configIdentity = JSON.stringify([setupName, normalizeSyncInclude(localInclude)]),
|
|
217
|
+
) {
|
|
218
|
+
const local = normalizeSyncInclude(localInclude);
|
|
219
|
+
const remote = normalizeSyncInclude(remoteInclude);
|
|
220
|
+
super(formatRemoteSelectionMismatch(setupName, local, remote));
|
|
221
|
+
this.name = "RemoteSelectionMismatchError";
|
|
222
|
+
this.setupName = setupName;
|
|
223
|
+
this.localInclude = local;
|
|
224
|
+
this.remoteInclude = remote;
|
|
225
|
+
this.decision = {
|
|
226
|
+
setupName,
|
|
227
|
+
configIdentity,
|
|
228
|
+
localInclude: [...local],
|
|
229
|
+
remoteInclude: [...remote],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
242
232
|
}
|
|
243
233
|
|
|
244
234
|
export function remoteSelectionMismatch(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
235
|
+
config: { setupName: string; include: unknown },
|
|
236
|
+
remoteInclude: unknown,
|
|
237
|
+
configIdentity?: string,
|
|
248
238
|
) {
|
|
249
|
-
|
|
250
|
-
config.setupName,
|
|
251
|
-
config.include,
|
|
252
|
-
remoteInclude,
|
|
253
|
-
configIdentity,
|
|
254
|
-
);
|
|
239
|
+
return new RemoteSelectionMismatchError(config.setupName, config.include, remoteInclude, configIdentity);
|
|
255
240
|
}
|
|
256
241
|
|
|
257
242
|
export function formatRemoteSelectionMismatch(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
243
|
+
setupName: string,
|
|
244
|
+
localInclude: readonly string[],
|
|
245
|
+
remoteInclude: readonly string[],
|
|
261
246
|
) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
247
|
+
const comparison = compareSyncInclude(localInclude, remoteInclude);
|
|
248
|
+
const lines = [
|
|
249
|
+
`Synced content differs for sync setup “${stripTerminalControls(setupName)}”.`,
|
|
250
|
+
`Remote-only: ${comparison.remoteOnly.join(", ") || "none"}`,
|
|
251
|
+
`This-device-only: ${comparison.localOnly.join(", ") || "none"}`,
|
|
252
|
+
];
|
|
253
|
+
if (comparison.remoteOnly.length === 0 && comparison.localOnly.length === 0) {
|
|
254
|
+
lines.push(
|
|
255
|
+
"Only ordering differs.",
|
|
256
|
+
`Remote order: ${remoteInclude.join(", ") || "none"}`,
|
|
257
|
+
`This device order: ${localInclude.join(", ") || "none"}`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
lines.push("Run /sync in TUI to review both content lists and choose what happens next.");
|
|
261
|
+
return lines.join("\n");
|
|
277
262
|
}
|
|
278
263
|
|
|
279
264
|
function stripTerminalControls(value: string) {
|
|
280
|
-
|
|
281
|
-
|
|
265
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: Escape untrusted terminal controls.
|
|
266
|
+
return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?");
|
|
282
267
|
}
|
|
283
268
|
|
|
284
269
|
export function discoverLegacySnapshotInclude(snapshot: Pick<Snapshot, "files">) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
];
|
|
270
|
+
const builtIns = new Set<BuiltInSyncFile>();
|
|
271
|
+
const custom = new Set<string>();
|
|
272
|
+
let sessions = false;
|
|
273
|
+
for (const file of snapshot.files) {
|
|
274
|
+
const normalized = toPosix(file.path);
|
|
275
|
+
if (
|
|
276
|
+
!normalized ||
|
|
277
|
+
file.path.includes("\\") ||
|
|
278
|
+
normalized.length > 4096 ||
|
|
279
|
+
normalized.startsWith("../") ||
|
|
280
|
+
path.posix.isAbsolute(normalized) ||
|
|
281
|
+
path.posix.normalize(normalized) !== normalized ||
|
|
282
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: Ignore unsafe legacy paths.
|
|
283
|
+
/[\u0000-\u001f\u007f-\u009f]/u.test(normalized) ||
|
|
284
|
+
isDeniedPath(normalized)
|
|
285
|
+
) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const [topLevel, ...rest] = normalized.split("/");
|
|
289
|
+
if (!topLevel) continue;
|
|
290
|
+
if (topLevel === "sessions" && rest.length > 0) {
|
|
291
|
+
sessions = true;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const builtIn = BUILT_IN_BY_LOWER.get(topLevel.toLowerCase());
|
|
295
|
+
if (
|
|
296
|
+
builtIn &&
|
|
297
|
+
((TOP_LEVEL_DIRS.has(builtIn) && rest.length > 0) || (!TOP_LEVEL_DIRS.has(builtIn) && rest.length === 0))
|
|
298
|
+
) {
|
|
299
|
+
builtIns.add(builtIn);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (isSafeCustomIncludePath(topLevel)) custom.add(topLevel);
|
|
303
|
+
}
|
|
304
|
+
return [
|
|
305
|
+
...BUILT_IN_SYNC_ROOTS.filter((item) => builtIns.has(item)),
|
|
306
|
+
...[...custom].sort((left, right) => left.localeCompare(right)),
|
|
307
|
+
...(sessions ? ["sessions"] : []),
|
|
308
|
+
];
|
|
325
309
|
}
|
|
326
310
|
|
|
327
311
|
export function syncIncludeSelection(value: unknown) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const custom = include.filter(
|
|
333
|
-
(item) => item !== "sessions" && !BUILT_IN_BY_LOWER.has(item.toLowerCase()),
|
|
334
|
-
);
|
|
335
|
-
return { include, builtIns, custom, sessions: include.includes("sessions") };
|
|
312
|
+
const include = normalizeSyncInclude(value);
|
|
313
|
+
const builtIns = include.filter((item): item is BuiltInSyncFile => BUILT_IN_BY_LOWER.has(item.toLowerCase()));
|
|
314
|
+
const custom = include.filter((item) => item !== "sessions" && !BUILT_IN_BY_LOWER.has(item.toLowerCase()));
|
|
315
|
+
return { include, builtIns, custom, sessions: include.includes("sessions") };
|
|
336
316
|
}
|
|
337
317
|
|
|
338
318
|
export function customIncludePathsByLower(value: unknown) {
|
|
339
|
-
|
|
340
|
-
syncIncludeSelection(value).custom.map((relativePath) => [
|
|
341
|
-
relativePath.toLowerCase(),
|
|
342
|
-
relativePath,
|
|
343
|
-
]),
|
|
344
|
-
);
|
|
319
|
+
return new Map(syncIncludeSelection(value).custom.map((relativePath) => [relativePath.toLowerCase(), relativePath]));
|
|
345
320
|
}
|
|
346
321
|
|
|
347
322
|
export interface SyncSelectionConfig {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
323
|
+
include?: unknown;
|
|
324
|
+
syncFiles?: unknown;
|
|
325
|
+
syncSessions?: boolean;
|
|
326
|
+
extraFiles?: unknown;
|
|
352
327
|
}
|
|
353
328
|
|
|
354
329
|
export function includeFromSelectionConfig(config: SyncSelectionConfig) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
330
|
+
if (config.include !== undefined) return normalizeSyncInclude(config.include);
|
|
331
|
+
return [
|
|
332
|
+
...normalizeSyncFiles(config.syncFiles),
|
|
333
|
+
...normalizeExtraFiles(config.extraFiles),
|
|
334
|
+
...(config.syncSessions ? ["sessions"] : []),
|
|
335
|
+
];
|
|
361
336
|
}
|
|
362
337
|
|
|
363
338
|
export function isConfiguredSnapshotPath(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
339
|
+
relativePath: string,
|
|
340
|
+
config: SyncSelectionConfig,
|
|
341
|
+
_legacyExtraFiles?: Set<string>,
|
|
367
342
|
) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
343
|
+
const normalized = toPosix(relativePath);
|
|
344
|
+
const selection = syncIncludeSelection(includeFromSelectionConfig(config));
|
|
345
|
+
if (normalized.startsWith("sessions/")) return selection.sessions;
|
|
346
|
+
const lower = normalized.toLowerCase();
|
|
347
|
+
if (!normalized.includes("/")) {
|
|
348
|
+
const builtIn = BUILT_IN_BY_LOWER.get(lower);
|
|
349
|
+
if (builtIn) return selection.builtIns.includes(builtIn) && !TOP_LEVEL_DIRS.has(builtIn);
|
|
350
|
+
}
|
|
351
|
+
const topLevel = normalized.slice(0, normalized.indexOf("/"));
|
|
352
|
+
if (selection.builtIns.includes(topLevel as BuiltInSyncFile) && TOP_LEVEL_DIRS.has(topLevel)) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
return selection.custom.some((candidate) => {
|
|
356
|
+
const candidateLower = candidate.toLowerCase();
|
|
357
|
+
return lower === candidateLower || lower.startsWith(`${candidateLower}/`);
|
|
358
|
+
});
|
|
384
359
|
}
|
|
385
360
|
|
|
386
|
-
export function canonicalSnapshotPathForConfig(
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
)
|
|
390
|
-
const normalized = toPosix(relativePath);
|
|
391
|
-
const lower = normalized.toLowerCase();
|
|
392
|
-
return TOP_LEVEL_FILE_PATHS.get(lower) ?? includePaths.get(lower) ?? normalized;
|
|
361
|
+
export function canonicalSnapshotPathForConfig(relativePath: string, includePaths: Map<string, string>) {
|
|
362
|
+
const normalized = toPosix(relativePath);
|
|
363
|
+
const lower = normalized.toLowerCase();
|
|
364
|
+
return TOP_LEVEL_FILE_PATHS.get(lower) ?? includePaths.get(lower) ?? normalized;
|
|
393
365
|
}
|
|
394
366
|
|
|
395
367
|
export function isPreservableUnmanagedSnapshotPath(relativePath: string) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
368
|
+
const normalized = toPosix(relativePath);
|
|
369
|
+
if (!normalized || isDeniedPath(normalized)) return false;
|
|
370
|
+
if (normalized.startsWith("sessions/")) return normalized.endsWith(".jsonl");
|
|
371
|
+
if (!normalized.includes("/")) {
|
|
372
|
+
const lower = normalized.toLowerCase();
|
|
373
|
+
return TOP_LEVEL_FILE_PATHS.has(lower) || !RESERVED_TOP_LEVEL_NAMES.has(lower);
|
|
374
|
+
}
|
|
375
|
+
return true;
|
|
404
376
|
}
|
|
405
377
|
|
|
406
378
|
export function isSafeCustomIncludePath(relativePath: string) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
379
|
+
try {
|
|
380
|
+
validateAgentRelativeInclude(relativePath);
|
|
381
|
+
return true;
|
|
382
|
+
} catch {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
413
385
|
}
|
|
414
386
|
|
|
415
387
|
export function isBuiltInTopLevelFile(fileName: string) {
|
|
416
|
-
|
|
388
|
+
return TOP_LEVEL_FILE_PATHS.has(path.posix.basename(fileName).toLowerCase());
|
|
417
389
|
}
|
|
418
390
|
|
|
419
391
|
/** Compatibility projection for backend-neutral output while callers move to sync.include. */
|
|
420
392
|
export function normalizeSyncFiles(value: unknown): BuiltInSyncFile[] {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
393
|
+
if (value === undefined) return [...DEFAULT_SYNC_INCLUDE];
|
|
394
|
+
if (Array.isArray(value)) {
|
|
395
|
+
return normalizeSyncInclude(value).filter((item): item is BuiltInSyncFile =>
|
|
396
|
+
BUILT_IN_BY_LOWER.has(item.toLowerCase()),
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
throw new Error("Invalid pi-sync settings: expected an include array.");
|
|
428
400
|
}
|
|
429
401
|
|
|
430
402
|
/** Compatibility projection for old rendering helpers; v3 persistence never writes this split field. */
|
|
431
403
|
export function normalizeExtraFiles(value: unknown) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
(item): item is string => typeof item === "string" && isSafeCustomIncludePath(item),
|
|
435
|
-
);
|
|
404
|
+
if (!Array.isArray(value)) return [];
|
|
405
|
+
return value.filter((item): item is string => typeof item === "string" && isSafeCustomIncludePath(item));
|
|
436
406
|
}
|
|
437
407
|
|
|
438
408
|
export const extraFilePathsByLower = customIncludePathsByLower;
|