@panorama-ai/gateway 2.30.207 → 2.30.279
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 +10 -0
- package/dist/cli-providers/types.d.ts +1 -1
- package/dist/cli-providers/types.d.ts.map +1 -1
- package/dist/database.types.d.ts +1723 -172
- package/dist/database.types.d.ts.map +1 -1
- package/dist/database.types.js.map +1 -1
- package/dist/finalize-subagent-run.d.ts +2 -1
- package/dist/finalize-subagent-run.d.ts.map +1 -1
- package/dist/finalize-subagent-run.js.map +1 -1
- package/dist/index.js +3384 -241
- package/dist/index.js.map +4 -4
- package/dist/managed-runtime/config.d.ts +13 -0
- package/dist/managed-runtime/config.d.ts.map +1 -1
- package/dist/managed-runtime/config.js +31 -0
- package/dist/managed-runtime/config.js.map +1 -1
- package/dist/managed-runtime/dependencies.d.ts +2 -0
- package/dist/managed-runtime/dependencies.d.ts.map +1 -1
- package/dist/managed-runtime/dependencies.js +2 -0
- package/dist/managed-runtime/dependencies.js.map +1 -1
- package/dist/managed-runtime/drive-sync-filesystem.d.ts +39 -0
- package/dist/managed-runtime/drive-sync-filesystem.d.ts.map +1 -0
- package/dist/managed-runtime/drive-sync-filesystem.js +434 -0
- package/dist/managed-runtime/drive-sync-filesystem.js.map +1 -0
- package/dist/managed-runtime/drive-sync-planner.d.ts +76 -0
- package/dist/managed-runtime/drive-sync-planner.d.ts.map +1 -0
- package/dist/managed-runtime/drive-sync-planner.js +363 -0
- package/dist/managed-runtime/drive-sync-planner.js.map +1 -0
- package/dist/managed-runtime/drive-sync-remote-planner.d.ts +52 -0
- package/dist/managed-runtime/drive-sync-remote-planner.d.ts.map +1 -0
- package/dist/managed-runtime/drive-sync-remote-planner.js +77 -0
- package/dist/managed-runtime/drive-sync-remote-planner.js.map +1 -0
- package/dist/managed-runtime/drive-sync-scheduler.d.ts +50 -0
- package/dist/managed-runtime/drive-sync-scheduler.d.ts.map +1 -0
- package/dist/managed-runtime/drive-sync-scheduler.js +302 -0
- package/dist/managed-runtime/drive-sync-scheduler.js.map +1 -0
- package/dist/managed-runtime/drive-sync-state-applier.d.ts +84 -0
- package/dist/managed-runtime/drive-sync-state-applier.d.ts.map +1 -0
- package/dist/managed-runtime/drive-sync-state-applier.js +153 -0
- package/dist/managed-runtime/drive-sync-state-applier.js.map +1 -0
- package/dist/managed-runtime/drive-sync-transfer.d.ts +86 -0
- package/dist/managed-runtime/drive-sync-transfer.d.ts.map +1 -0
- package/dist/managed-runtime/drive-sync-transfer.js +245 -0
- package/dist/managed-runtime/drive-sync-transfer.js.map +1 -0
- package/dist/managed-runtime/drive-sync.d.ts +416 -0
- package/dist/managed-runtime/drive-sync.d.ts.map +1 -0
- package/dist/managed-runtime/drive-sync.js +1641 -0
- package/dist/managed-runtime/drive-sync.js.map +1 -0
- package/dist/managed-runtime.d.ts.map +1 -1
- package/dist/managed-runtime.js +44 -0
- package/dist/managed-runtime.js.map +1 -1
- package/dist/subagent-adapters/types.d.ts +1 -1
- package/dist/subagent-adapters/types.d.ts.map +1 -1
- package/dist/subagent-output-persistence.d.ts +1 -1
- package/dist/subagent-output-persistence.d.ts.map +1 -1
- package/dist/subagent-output-persistence.js +1 -1
- package/dist/subagent-output-persistence.js.map +1 -1
- package/package.json +5 -5
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
export type ManagedGatewayDriveSyncConfig = {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
rootDir: string;
|
|
4
|
+
stateDir: string;
|
|
5
|
+
clientKey: string | null;
|
|
6
|
+
changeLimit: number;
|
|
7
|
+
maxBatchesPerTick: number;
|
|
8
|
+
debounceMs: number;
|
|
9
|
+
pollIntervalMs: number;
|
|
10
|
+
localAuditIntervalMs: number;
|
|
11
|
+
networkTimeoutMs?: number;
|
|
12
|
+
};
|
|
1
13
|
export type ManagedGatewayRuntimeConfig = {
|
|
2
14
|
agentId: string;
|
|
3
15
|
hostId: string;
|
|
@@ -15,6 +27,7 @@ export type ManagedGatewayRuntimeConfig = {
|
|
|
15
27
|
outputCaptureBytes: number;
|
|
16
28
|
heartbeatIntervalMs: number;
|
|
17
29
|
maxConsecutiveHeartbeatFailures: number;
|
|
30
|
+
driveSync: ManagedGatewayDriveSyncConfig;
|
|
18
31
|
};
|
|
19
32
|
export declare function resolveManagedGatewayRuntimeConfig(env?: NodeJS.ProcessEnv): ManagedGatewayRuntimeConfig;
|
|
20
33
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/managed-runtime/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/managed-runtime/config.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,qBAAqB,EAAE,MAAM,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,MAAM,CAAA;IACxB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,+BAA+B,EAAE,MAAM,CAAA;IACvC,SAAS,EAAE,6BAA6B,CAAA;CACzC,CAAA;AA6CD,wBAAgB,kCAAkC,CAChD,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,2BAA2B,CA0G7B"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DRIVE_TRANSFER_POLICY } from '@panorama/shared/drive-transfer-policy';
|
|
1
2
|
import { buildLinuxHostControlUrl } from '@panorama/shared/linux-host-control/contract';
|
|
2
3
|
import { buildToolExecutionUrl } from '@panorama/shared/tools/execution-contract';
|
|
3
4
|
import { hostname } from 'node:os';
|
|
@@ -8,6 +9,14 @@ const DEFAULT_EXEC_TIMEOUT_MS = 300_000;
|
|
|
8
9
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 15_000;
|
|
9
10
|
const DEFAULT_MAX_CONSECUTIVE_HEARTBEAT_FAILURES = 3;
|
|
10
11
|
const DEFAULT_OUTPUT_CAPTURE_BYTES = 5_000_000;
|
|
12
|
+
const DEFAULT_DRIVE_SYNC_ROOT = '/panorama/drives';
|
|
13
|
+
const DEFAULT_DRIVE_SYNC_STATE_DIR = '/var/lib/panorama/drive-sync';
|
|
14
|
+
const DEFAULT_DRIVE_SYNC_CHANGE_LIMIT = 50;
|
|
15
|
+
const DEFAULT_DRIVE_SYNC_MAX_BATCHES_PER_TICK = 25;
|
|
16
|
+
const DEFAULT_DRIVE_SYNC_DEBOUNCE_MS = 1_000;
|
|
17
|
+
const DEFAULT_DRIVE_SYNC_POLL_INTERVAL_MS = 5_000;
|
|
18
|
+
const DEFAULT_DRIVE_SYNC_LOCAL_AUDIT_INTERVAL_MS = 300_000;
|
|
19
|
+
const DEFAULT_DRIVE_SYNC_NETWORK_TIMEOUT_MS = DRIVE_TRANSFER_POLICY.networkRequestTimeoutMs;
|
|
11
20
|
function requireEnv(name, env = process.env) {
|
|
12
21
|
const value = env[name]?.trim();
|
|
13
22
|
if (!value) {
|
|
@@ -25,6 +34,16 @@ function readPositiveInt(name, fallback, env = process.env) {
|
|
|
25
34
|
}
|
|
26
35
|
return Math.floor(parsed);
|
|
27
36
|
}
|
|
37
|
+
function readBoolean(name, fallback, env = process.env) {
|
|
38
|
+
const raw = env[name]?.trim().toLowerCase();
|
|
39
|
+
if (!raw)
|
|
40
|
+
return fallback;
|
|
41
|
+
if (['1', 'true', 'yes', 'on'].includes(raw))
|
|
42
|
+
return true;
|
|
43
|
+
if (['0', 'false', 'no', 'off'].includes(raw))
|
|
44
|
+
return false;
|
|
45
|
+
throw new Error(`${name} must be a boolean`);
|
|
46
|
+
}
|
|
28
47
|
function requirePositiveInt(name, env = process.env) {
|
|
29
48
|
const raw = requireEnv(name, env);
|
|
30
49
|
const parsed = Number(raw);
|
|
@@ -75,6 +94,18 @@ export function resolveManagedGatewayRuntimeConfig(env = process.env) {
|
|
|
75
94
|
outputCaptureBytes: readPositiveInt('PANORAMA_MANAGED_RUNTIME_OUTPUT_CAPTURE_BYTES', DEFAULT_OUTPUT_CAPTURE_BYTES, env),
|
|
76
95
|
heartbeatIntervalMs: readPositiveInt('PANORAMA_MANAGED_RUNTIME_HEARTBEAT_INTERVAL_MS', DEFAULT_HEARTBEAT_INTERVAL_MS, env),
|
|
77
96
|
maxConsecutiveHeartbeatFailures: readPositiveInt('PANORAMA_MANAGED_RUNTIME_MAX_CONSECUTIVE_HEARTBEAT_FAILURES', DEFAULT_MAX_CONSECUTIVE_HEARTBEAT_FAILURES, env),
|
|
97
|
+
driveSync: {
|
|
98
|
+
enabled: readBoolean('PANORAMA_DRIVE_SYNC_ENABLED', false, env),
|
|
99
|
+
rootDir: env.PANORAMA_DRIVE_SYNC_ROOT?.trim() || DEFAULT_DRIVE_SYNC_ROOT,
|
|
100
|
+
stateDir: env.PANORAMA_DRIVE_SYNC_STATE_DIR?.trim() || DEFAULT_DRIVE_SYNC_STATE_DIR,
|
|
101
|
+
clientKey: env.PANORAMA_DRIVE_SYNC_CLIENT_KEY?.trim() || null,
|
|
102
|
+
changeLimit: readPositiveInt('PANORAMA_DRIVE_SYNC_CHANGE_LIMIT', DEFAULT_DRIVE_SYNC_CHANGE_LIMIT, env),
|
|
103
|
+
maxBatchesPerTick: readPositiveInt('PANORAMA_DRIVE_SYNC_MAX_BATCHES_PER_TICK', DEFAULT_DRIVE_SYNC_MAX_BATCHES_PER_TICK, env),
|
|
104
|
+
debounceMs: readPositiveInt('PANORAMA_DRIVE_SYNC_DEBOUNCE_MS', DEFAULT_DRIVE_SYNC_DEBOUNCE_MS, env),
|
|
105
|
+
pollIntervalMs: readPositiveInt('PANORAMA_DRIVE_SYNC_POLL_INTERVAL_MS', DEFAULT_DRIVE_SYNC_POLL_INTERVAL_MS, env),
|
|
106
|
+
localAuditIntervalMs: readPositiveInt('PANORAMA_DRIVE_SYNC_LOCAL_AUDIT_INTERVAL_MS', DEFAULT_DRIVE_SYNC_LOCAL_AUDIT_INTERVAL_MS, env),
|
|
107
|
+
networkTimeoutMs: readPositiveInt('PANORAMA_DRIVE_SYNC_NETWORK_TIMEOUT_MS', DEFAULT_DRIVE_SYNC_NETWORK_TIMEOUT_MS, env),
|
|
108
|
+
},
|
|
78
109
|
};
|
|
79
110
|
}
|
|
80
111
|
//# sourceMappingURL=config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/managed-runtime/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8CAA8C,CAAA;AACvF,OAAO,EAAE,qBAAqB,EAAE,MAAM,2CAA2C,CAAA;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAClC,OAAO,OAAO,MAAM,cAAc,CAAA;AAElC,MAAM,wBAAwB,GAAG,KAAK,CAAA;AACtC,MAAM,0BAA0B,GAAG,MAAM,CAAA;AACzC,MAAM,uBAAuB,GAAG,OAAO,CAAA;AACvC,MAAM,6BAA6B,GAAG,MAAM,CAAA;AAC5C,MAAM,0CAA0C,GAAG,CAAC,CAAA;AACpD,MAAM,4BAA4B,GAAG,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/managed-runtime/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { completeLinuxExecution, dispatchLinuxExecution, heartbeatLinuxHost, reconcileExecutionLifecycle, startLinuxExecution } from './control-client.js';
|
|
2
2
|
import { createManagedRealtimeClient, startManagedRealtimeWakeLoop } from './realtime.js';
|
|
3
|
+
import { createManagedDriveSyncService } from './drive-sync.js';
|
|
3
4
|
import { executeCommand } from './shell-execution.js';
|
|
4
5
|
export type ResolvedManagedGatewayRuntimeDependencies = {
|
|
5
6
|
heartbeatLinuxHost: typeof heartbeatLinuxHost;
|
|
@@ -10,6 +11,7 @@ export type ResolvedManagedGatewayRuntimeDependencies = {
|
|
|
10
11
|
executeCommand: typeof executeCommand;
|
|
11
12
|
createRealtimeClient: typeof createManagedRealtimeClient;
|
|
12
13
|
startRealtimeWakeLoop: typeof startManagedRealtimeWakeLoop;
|
|
14
|
+
createDriveSyncService: typeof createManagedDriveSyncService;
|
|
13
15
|
registerSignalHandlers: (onSignal: (signal: NodeJS.Signals) => void) => () => void;
|
|
14
16
|
};
|
|
15
17
|
export type ManagedGatewayRuntimeDependencies = Partial<ResolvedManagedGatewayRuntimeDependencies>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependencies.d.ts","sourceRoot":"","sources":["../../src/managed-runtime/dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,2BAA2B,EAC3B,mBAAmB,EACpB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAA;AACzF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErD,MAAM,MAAM,yCAAyC,GAAG;IACtD,kBAAkB,EAAE,OAAO,kBAAkB,CAAA;IAC7C,sBAAsB,EAAE,OAAO,sBAAsB,CAAA;IACrD,mBAAmB,EAAE,OAAO,mBAAmB,CAAA;IAC/C,sBAAsB,EAAE,OAAO,sBAAsB,CAAA;IACrD,2BAA2B,EAAE,OAAO,2BAA2B,CAAA;IAC/D,cAAc,EAAE,OAAO,cAAc,CAAA;IACrC,oBAAoB,EAAE,OAAO,2BAA2B,CAAA;IACxD,qBAAqB,EAAE,OAAO,4BAA4B,CAAA;IAC1D,sBAAsB,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;CACnF,CAAA;AAED,MAAM,MAAM,iCAAiC,GAAG,OAAO,CAAC,yCAAyC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"dependencies.d.ts","sourceRoot":"","sources":["../../src/managed-runtime/dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,2BAA2B,EAC3B,mBAAmB,EACpB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAA;AACzF,OAAO,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErD,MAAM,MAAM,yCAAyC,GAAG;IACtD,kBAAkB,EAAE,OAAO,kBAAkB,CAAA;IAC7C,sBAAsB,EAAE,OAAO,sBAAsB,CAAA;IACrD,mBAAmB,EAAE,OAAO,mBAAmB,CAAA;IAC/C,sBAAsB,EAAE,OAAO,sBAAsB,CAAA;IACrD,2BAA2B,EAAE,OAAO,2BAA2B,CAAA;IAC/D,cAAc,EAAE,OAAO,cAAc,CAAA;IACrC,oBAAoB,EAAE,OAAO,2BAA2B,CAAA;IACxD,qBAAqB,EAAE,OAAO,4BAA4B,CAAA;IAC1D,sBAAsB,EAAE,OAAO,6BAA6B,CAAA;IAC5D,sBAAsB,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;CACnF,CAAA;AAED,MAAM,MAAM,iCAAiC,GAAG,OAAO,CAAC,yCAAyC,CAAC,CAAA;AAsBlG,wBAAgB,wCAAwC,CACtD,YAAY,GAAE,iCAAsC,GACnD,yCAAyC,CAK3C"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { completeLinuxExecution, dispatchLinuxExecution, heartbeatLinuxHost, reconcileExecutionLifecycle, startLinuxExecution, } from './control-client.js';
|
|
2
2
|
import { createManagedRealtimeClient, startManagedRealtimeWakeLoop } from './realtime.js';
|
|
3
|
+
import { createManagedDriveSyncService } from './drive-sync.js';
|
|
3
4
|
import { executeCommand } from './shell-execution.js';
|
|
4
5
|
const defaultManagedGatewayRuntimeDependencies = {
|
|
5
6
|
heartbeatLinuxHost,
|
|
@@ -10,6 +11,7 @@ const defaultManagedGatewayRuntimeDependencies = {
|
|
|
10
11
|
executeCommand,
|
|
11
12
|
createRealtimeClient: createManagedRealtimeClient,
|
|
12
13
|
startRealtimeWakeLoop: startManagedRealtimeWakeLoop,
|
|
14
|
+
createDriveSyncService: createManagedDriveSyncService,
|
|
13
15
|
registerSignalHandlers(onSignal) {
|
|
14
16
|
process.once('SIGINT', onSignal);
|
|
15
17
|
process.once('SIGTERM', onSignal);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../../src/managed-runtime/dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAA;AACzF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../../src/managed-runtime/dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAA;AACzF,OAAO,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAiBrD,MAAM,wCAAwC,GAA8C;IAC1F,kBAAkB;IAClB,sBAAsB;IACtB,mBAAmB;IACnB,sBAAsB;IACtB,2BAA2B;IAC3B,cAAc;IACd,oBAAoB,EAAE,2BAA2B;IACjD,qBAAqB,EAAE,4BAA4B;IACnD,sBAAsB,EAAE,6BAA6B;IACrD,sBAAsB,CAAC,QAAQ;QAC7B,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QAChC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;QACjC,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;YAC/B,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;QAClC,CAAC,CAAA;IACH,CAAC;CACF,CAAA;AAED,MAAM,UAAU,wCAAwC,CACtD,eAAkD,EAAE;IAEpD,OAAO;QACL,GAAG,wCAAwC;QAC3C,GAAG,YAAY;KAChB,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type LocalDriveScanEntry } from './drive-sync-planner.js';
|
|
2
|
+
export declare const DRIVE_SYNC_MAX_LOCAL_FILE_BYTES: number;
|
|
3
|
+
export type LocalDriveScanResult = {
|
|
4
|
+
entries: Map<string, LocalDriveScanEntry>;
|
|
5
|
+
deferredPaths: Set<string>;
|
|
6
|
+
deferredFileCount: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function resolveDriveLocalRoot(rootDir: string, driveId: string): string;
|
|
9
|
+
export declare function parkStaleDriveLocalFolder(rootDir: string, driveId: string): Promise<string | null>;
|
|
10
|
+
export declare function resolveDriveLocalPath(driveRoot: string, drivePath: string): string;
|
|
11
|
+
export declare function resolveDirtyDrivePathsForDrive(driveRoot: string, dirtyLocalPaths: string[] | undefined): Set<string> | null;
|
|
12
|
+
export declare function readLocalDriveEntryAtPath(params: {
|
|
13
|
+
driveId: string;
|
|
14
|
+
driveRoot: string;
|
|
15
|
+
drivePath: string;
|
|
16
|
+
}): Promise<LocalDriveScanEntry | null>;
|
|
17
|
+
export declare function scanLocalDriveEntries(params: {
|
|
18
|
+
driveId: string;
|
|
19
|
+
driveRoot: string;
|
|
20
|
+
rootDrivePaths?: Set<string>;
|
|
21
|
+
}): Promise<LocalDriveScanResult>;
|
|
22
|
+
export declare function writeLocalConflictCopy(params: {
|
|
23
|
+
driveRoot: string;
|
|
24
|
+
source: LocalDriveScanEntry;
|
|
25
|
+
sourceRootPath: string;
|
|
26
|
+
conflictRootPath: string;
|
|
27
|
+
}): Promise<string>;
|
|
28
|
+
export declare function resolveAvailableConflictDrivePath(params: {
|
|
29
|
+
driveRoot: string;
|
|
30
|
+
sourcePath: string;
|
|
31
|
+
sourceRootPath: string;
|
|
32
|
+
conflictRootPath: string;
|
|
33
|
+
}): Promise<string>;
|
|
34
|
+
export declare function buildSubtreeConflictRootPath(deletedSubtreePath: string): string;
|
|
35
|
+
export declare function assertPathExists(filePath: string, errorMessage: string): Promise<void>;
|
|
36
|
+
export declare function assertLocalPathIsNotSymlink(filePath: string, errorMessage: string): Promise<void>;
|
|
37
|
+
export declare function assertNoSymlinkInExistingPath(driveRoot: string, targetPath: string): Promise<void>;
|
|
38
|
+
export declare function normalizeDrivePath(input: string): string;
|
|
39
|
+
//# sourceMappingURL=drive-sync-filesystem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drive-sync-filesystem.d.ts","sourceRoot":"","sources":["../../src/managed-runtime/drive-sync-filesystem.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAElE,eAAO,MAAM,+BAA+B,QAA2C,CAAA;AAEvF,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IACzC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC1B,iBAAiB,EAAE,MAAM,CAAA;CAC1B,CAAA;AASD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAK9E;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiBxB;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAYlF;AAED,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,EAAE,GAAG,SAAS,GACpC,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAoBpB;AAED,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAyDtC;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAC7B,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAoKhC;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,mBAAmB,CAAA;IAC3B,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBlB;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BlB;AAED,wBAAgB,4BAA4B,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAK/E;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM5F;AAED,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,wBAAsB,6BAA6B,CACjD,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWxD"}
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { copyFile, lstat, mkdir, readdir, readlink, rename, stat } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { DRIVE_TRANSFER_POLICY } from '@panorama/shared/drive-transfer-policy';
|
|
6
|
+
export const DRIVE_SYNC_MAX_LOCAL_FILE_BYTES = DRIVE_TRANSFER_POLICY.objectFileMaxBytes;
|
|
7
|
+
class UnstableLocalFileError extends Error {
|
|
8
|
+
drivePath;
|
|
9
|
+
constructor(drivePath) {
|
|
10
|
+
super(`Local drive file changed while it was being read; deferring sync for ${drivePath}`);
|
|
11
|
+
this.drivePath = drivePath;
|
|
12
|
+
this.name = 'UnstableLocalFileError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function resolveDriveLocalRoot(rootDir, driveId) {
|
|
16
|
+
if (!/^[0-9a-f-]{36}$/i.test(driveId)) {
|
|
17
|
+
throw new Error(`Invalid drive id "${driveId}"`);
|
|
18
|
+
}
|
|
19
|
+
return path.join(rootDir, driveId);
|
|
20
|
+
}
|
|
21
|
+
export async function parkStaleDriveLocalFolder(rootDir, driveId) {
|
|
22
|
+
const sourcePath = resolveDriveLocalRoot(rootDir, driveId);
|
|
23
|
+
try {
|
|
24
|
+
await lstat(sourcePath);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
const staleRoot = path.join(path.resolve(rootDir), '.stale');
|
|
33
|
+
await mkdir(staleRoot, { recursive: true });
|
|
34
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
35
|
+
const targetPath = path.join(staleRoot, `${driveId}-${timestamp}-${randomUUID()}`);
|
|
36
|
+
await rename(sourcePath, targetPath);
|
|
37
|
+
return targetPath;
|
|
38
|
+
}
|
|
39
|
+
export function resolveDriveLocalPath(driveRoot, drivePath) {
|
|
40
|
+
const normalizedDrivePath = normalizeDrivePath(drivePath);
|
|
41
|
+
const resolvedRoot = path.resolve(driveRoot);
|
|
42
|
+
if (normalizedDrivePath === '/') {
|
|
43
|
+
return resolvedRoot;
|
|
44
|
+
}
|
|
45
|
+
const resolvedPath = path.resolve(resolvedRoot, normalizedDrivePath.slice(1));
|
|
46
|
+
if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${path.sep}`)) {
|
|
47
|
+
throw new Error(`Drive path escapes drive root: ${drivePath}`);
|
|
48
|
+
}
|
|
49
|
+
return resolvedPath;
|
|
50
|
+
}
|
|
51
|
+
export function resolveDirtyDrivePathsForDrive(driveRoot, dirtyLocalPaths) {
|
|
52
|
+
if (!dirtyLocalPaths) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const resolvedRoot = path.resolve(driveRoot);
|
|
56
|
+
const dirtyDrivePaths = new Set();
|
|
57
|
+
for (const dirtyLocalPath of dirtyLocalPaths) {
|
|
58
|
+
const resolvedDirtyPath = path.resolve(dirtyLocalPath);
|
|
59
|
+
const relativePath = path.relative(resolvedRoot, resolvedDirtyPath);
|
|
60
|
+
if (!relativePath) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
dirtyDrivePaths.add(normalizeDrivePath(`/${relativePath.split(path.sep).join('/')}`));
|
|
67
|
+
}
|
|
68
|
+
return dirtyDrivePaths;
|
|
69
|
+
}
|
|
70
|
+
export async function readLocalDriveEntryAtPath(params) {
|
|
71
|
+
const absolutePath = resolveDriveLocalPath(params.driveRoot, params.drivePath);
|
|
72
|
+
let localStat;
|
|
73
|
+
try {
|
|
74
|
+
localStat = await lstat(absolutePath);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
if (localStat.isDirectory()) {
|
|
83
|
+
return {
|
|
84
|
+
driveId: params.driveId,
|
|
85
|
+
path: normalizeDrivePath(params.drivePath),
|
|
86
|
+
entryType: 'directory',
|
|
87
|
+
contentSha256: null,
|
|
88
|
+
sizeBytes: null,
|
|
89
|
+
mimeType: null,
|
|
90
|
+
absolutePath,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (localStat.isFile()) {
|
|
94
|
+
return await readStableLocalFileEntry({
|
|
95
|
+
driveId: params.driveId,
|
|
96
|
+
drivePath: normalizeDrivePath(params.drivePath),
|
|
97
|
+
absolutePath,
|
|
98
|
+
initialStat: localStat,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (localStat.isSymbolicLink()) {
|
|
102
|
+
const linkTarget = await readlink(absolutePath).catch(() => '');
|
|
103
|
+
return {
|
|
104
|
+
driveId: params.driveId,
|
|
105
|
+
path: normalizeDrivePath(params.drivePath),
|
|
106
|
+
entryType: 'symlink',
|
|
107
|
+
contentSha256: sha256Hex(Buffer.from(linkTarget)),
|
|
108
|
+
sizeBytes: Buffer.byteLength(linkTarget),
|
|
109
|
+
mimeType: null,
|
|
110
|
+
absolutePath,
|
|
111
|
+
unsupportedReason: 'symlink_entry_unsupported',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
driveId: params.driveId,
|
|
116
|
+
path: normalizeDrivePath(params.drivePath),
|
|
117
|
+
entryType: 'unsupported',
|
|
118
|
+
contentSha256: null,
|
|
119
|
+
sizeBytes: null,
|
|
120
|
+
mimeType: null,
|
|
121
|
+
absolutePath,
|
|
122
|
+
unsupportedReason: describeUnsupportedLocalEntry(localStat),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export async function scanLocalDriveEntries(params) {
|
|
126
|
+
const entries = new Map();
|
|
127
|
+
const deferredPaths = new Set();
|
|
128
|
+
const visitedDirectories = new Set();
|
|
129
|
+
let deferredFileCount = 0;
|
|
130
|
+
async function addAncestorDirectories(drivePath) {
|
|
131
|
+
const normalizedPath = normalizeDrivePath(drivePath);
|
|
132
|
+
const segments = normalizedPath.split('/').filter(Boolean);
|
|
133
|
+
for (let index = 1; index < segments.length; index += 1) {
|
|
134
|
+
const ancestorPath = normalizeDrivePath(`/${segments.slice(0, index).join('/')}`);
|
|
135
|
+
if (entries.has(ancestorPath)) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const absolutePath = resolveDriveLocalPath(params.driveRoot, ancestorPath);
|
|
139
|
+
try {
|
|
140
|
+
const ancestorStat = await lstat(absolutePath);
|
|
141
|
+
if (ancestorStat.isDirectory()) {
|
|
142
|
+
entries.set(ancestorPath, {
|
|
143
|
+
driveId: params.driveId,
|
|
144
|
+
path: ancestorPath,
|
|
145
|
+
entryType: 'directory',
|
|
146
|
+
contentSha256: null,
|
|
147
|
+
sizeBytes: null,
|
|
148
|
+
mimeType: null,
|
|
149
|
+
absolutePath,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function addPathEntry(absolutePath, drivePath, childStat) {
|
|
162
|
+
await addAncestorDirectories(drivePath);
|
|
163
|
+
if (childStat.isDirectory()) {
|
|
164
|
+
entries.set(drivePath, {
|
|
165
|
+
driveId: params.driveId,
|
|
166
|
+
path: drivePath,
|
|
167
|
+
entryType: 'directory',
|
|
168
|
+
contentSha256: null,
|
|
169
|
+
sizeBytes: null,
|
|
170
|
+
mimeType: null,
|
|
171
|
+
absolutePath,
|
|
172
|
+
});
|
|
173
|
+
await visitDirectory(absolutePath, drivePath);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (childStat.isFile()) {
|
|
177
|
+
try {
|
|
178
|
+
entries.set(drivePath, await readStableLocalFileEntry({
|
|
179
|
+
driveId: params.driveId,
|
|
180
|
+
drivePath,
|
|
181
|
+
absolutePath,
|
|
182
|
+
initialStat: childStat,
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (error instanceof UnstableLocalFileError) {
|
|
187
|
+
deferredPaths.add(drivePath);
|
|
188
|
+
deferredFileCount += 1;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (childStat.isSymbolicLink()) {
|
|
196
|
+
const linkTarget = await readlink(absolutePath).catch(() => '');
|
|
197
|
+
entries.set(drivePath, {
|
|
198
|
+
driveId: params.driveId,
|
|
199
|
+
path: drivePath,
|
|
200
|
+
entryType: 'symlink',
|
|
201
|
+
contentSha256: sha256Hex(Buffer.from(linkTarget)),
|
|
202
|
+
sizeBytes: Buffer.byteLength(linkTarget),
|
|
203
|
+
mimeType: null,
|
|
204
|
+
absolutePath,
|
|
205
|
+
unsupportedReason: 'symlink_entry_unsupported',
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
entries.set(drivePath, {
|
|
210
|
+
driveId: params.driveId,
|
|
211
|
+
path: drivePath,
|
|
212
|
+
entryType: 'unsupported',
|
|
213
|
+
contentSha256: null,
|
|
214
|
+
sizeBytes: null,
|
|
215
|
+
mimeType: null,
|
|
216
|
+
absolutePath,
|
|
217
|
+
unsupportedReason: describeUnsupportedLocalEntry(childStat),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async function visitDirectory(absoluteDirectoryPath, driveDirectoryPath) {
|
|
221
|
+
const resolvedDirectoryPath = path.resolve(absoluteDirectoryPath);
|
|
222
|
+
if (visitedDirectories.has(resolvedDirectoryPath)) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
visitedDirectories.add(resolvedDirectoryPath);
|
|
226
|
+
const children = await readdir(absoluteDirectoryPath, { withFileTypes: true });
|
|
227
|
+
for (const child of children) {
|
|
228
|
+
const childAbsolutePath = path.join(absoluteDirectoryPath, child.name);
|
|
229
|
+
const childDrivePath = normalizeDrivePath(driveDirectoryPath === '/' ? `/${child.name}` : `${driveDirectoryPath}/${child.name}`);
|
|
230
|
+
let childStat;
|
|
231
|
+
try {
|
|
232
|
+
childStat = await lstat(childAbsolutePath);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
await addPathEntry(childAbsolutePath, childDrivePath, childStat);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const rootDrivePaths = params.rootDrivePaths;
|
|
244
|
+
if (!rootDrivePaths) {
|
|
245
|
+
await visitDirectory(path.resolve(params.driveRoot), '/');
|
|
246
|
+
return { entries, deferredPaths, deferredFileCount };
|
|
247
|
+
}
|
|
248
|
+
for (const dirtyPath of rootDrivePaths) {
|
|
249
|
+
const normalizedDirtyPath = normalizeDrivePath(dirtyPath);
|
|
250
|
+
if (normalizedDirtyPath === '/') {
|
|
251
|
+
await visitDirectory(path.resolve(params.driveRoot), '/');
|
|
252
|
+
return { entries, deferredPaths, deferredFileCount };
|
|
253
|
+
}
|
|
254
|
+
const absolutePath = resolveDriveLocalPath(params.driveRoot, normalizedDirtyPath);
|
|
255
|
+
try {
|
|
256
|
+
const childStat = await lstat(absolutePath);
|
|
257
|
+
await addPathEntry(absolutePath, normalizedDirtyPath, childStat);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
261
|
+
await addAncestorDirectories(normalizedDirtyPath);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return { entries, deferredPaths, deferredFileCount };
|
|
268
|
+
}
|
|
269
|
+
export async function writeLocalConflictCopy(params) {
|
|
270
|
+
if (params.source.entryType !== 'file') {
|
|
271
|
+
throw new Error(`Cannot preserve non-file local conflict candidate: ${params.source.path}`);
|
|
272
|
+
}
|
|
273
|
+
const conflictDrivePath = await resolveAvailableConflictDrivePath({
|
|
274
|
+
driveRoot: params.driveRoot,
|
|
275
|
+
sourcePath: params.source.path,
|
|
276
|
+
sourceRootPath: params.sourceRootPath,
|
|
277
|
+
conflictRootPath: params.conflictRootPath,
|
|
278
|
+
});
|
|
279
|
+
const conflictLocalPath = resolveDriveLocalPath(params.driveRoot, conflictDrivePath);
|
|
280
|
+
await assertNoSymlinkInExistingPath(params.driveRoot, path.dirname(conflictLocalPath));
|
|
281
|
+
await mkdir(path.dirname(conflictLocalPath), { recursive: true });
|
|
282
|
+
await copyFile(params.source.absolutePath, conflictLocalPath);
|
|
283
|
+
return conflictDrivePath;
|
|
284
|
+
}
|
|
285
|
+
export async function resolveAvailableConflictDrivePath(params) {
|
|
286
|
+
const parsed = path.posix.parse(normalizeDrivePath(params.sourcePath));
|
|
287
|
+
const conflictRoot = normalizeDrivePath(params.conflictRootPath);
|
|
288
|
+
const sourceRoot = normalizeDrivePath(params.sourceRootPath);
|
|
289
|
+
const timestamp = new Date()
|
|
290
|
+
.toISOString()
|
|
291
|
+
.replace(/[-:]/g, '')
|
|
292
|
+
.replace(/\.\d{3}Z$/, 'Z');
|
|
293
|
+
const baseName = `${parsed.name} (Panorama conflict ${timestamp})`;
|
|
294
|
+
const targetDirectory = sourceRoot === params.sourcePath
|
|
295
|
+
? parsed.dir || '/'
|
|
296
|
+
: path.posix.join(conflictRoot, path.posix.relative(sourceRoot, parsed.dir || '/'));
|
|
297
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
298
|
+
const suffix = attempt === 0 ? '' : ` ${attempt + 1}`;
|
|
299
|
+
const candidate = normalizeDrivePath(path.posix.join(targetDirectory, `${baseName}${suffix}${parsed.ext}`));
|
|
300
|
+
try {
|
|
301
|
+
await lstat(resolveDriveLocalPath(params.driveRoot, candidate));
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
305
|
+
return candidate;
|
|
306
|
+
}
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
throw new Error(`Could not allocate a local conflict path for ${params.sourcePath}`);
|
|
311
|
+
}
|
|
312
|
+
export function buildSubtreeConflictRootPath(deletedSubtreePath) {
|
|
313
|
+
const parsed = path.posix.parse(normalizeDrivePath(deletedSubtreePath));
|
|
314
|
+
return normalizeDrivePath(path.posix.join(parsed.dir || '/', `${parsed.base} (Panorama conflicts)`));
|
|
315
|
+
}
|
|
316
|
+
export async function assertPathExists(filePath, errorMessage) {
|
|
317
|
+
try {
|
|
318
|
+
await stat(filePath);
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
throw new Error(errorMessage);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
export async function assertLocalPathIsNotSymlink(filePath, errorMessage) {
|
|
325
|
+
try {
|
|
326
|
+
const current = await lstat(filePath);
|
|
327
|
+
if (current.isSymbolicLink()) {
|
|
328
|
+
throw new Error(errorMessage);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
export async function assertNoSymlinkInExistingPath(driveRoot, targetPath) {
|
|
339
|
+
const resolvedRoot = path.resolve(driveRoot);
|
|
340
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
341
|
+
const relativePath = path.relative(resolvedRoot, resolvedTarget);
|
|
342
|
+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
343
|
+
throw new Error(`Local drive path escapes drive root: ${targetPath}`);
|
|
344
|
+
}
|
|
345
|
+
if (!relativePath) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
let currentPath = resolvedRoot;
|
|
349
|
+
for (const segment of relativePath.split(path.sep).filter(Boolean)) {
|
|
350
|
+
currentPath = path.join(currentPath, segment);
|
|
351
|
+
try {
|
|
352
|
+
const current = await lstat(currentPath);
|
|
353
|
+
if (current.isSymbolicLink()) {
|
|
354
|
+
throw new Error(`Local drive path contains a symlink: ${currentPath}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
export function normalizeDrivePath(input) {
|
|
366
|
+
if (!input.startsWith('/')) {
|
|
367
|
+
throw new Error(`Drive path must be absolute: ${input}`);
|
|
368
|
+
}
|
|
369
|
+
const segments = input.split('/').filter(Boolean);
|
|
370
|
+
if (segments.some((segment) => segment === '.' || segment === '..')) {
|
|
371
|
+
throw new Error(`Drive path must not contain traversal segments: ${input}`);
|
|
372
|
+
}
|
|
373
|
+
return input === '/' ? '/' : `/${segments.join('/')}`;
|
|
374
|
+
}
|
|
375
|
+
async function readStableLocalFileEntry(params) {
|
|
376
|
+
const initialSizeBytes = Number(params.initialStat.size);
|
|
377
|
+
const initialMtimeMs = Number(params.initialStat.mtimeMs);
|
|
378
|
+
const initialCtimeMs = Number(params.initialStat.ctimeMs);
|
|
379
|
+
if (initialSizeBytes > DRIVE_SYNC_MAX_LOCAL_FILE_BYTES) {
|
|
380
|
+
return {
|
|
381
|
+
driveId: params.driveId,
|
|
382
|
+
path: params.drivePath,
|
|
383
|
+
entryType: 'unsupported',
|
|
384
|
+
contentSha256: null,
|
|
385
|
+
sizeBytes: initialSizeBytes,
|
|
386
|
+
mimeType: null,
|
|
387
|
+
absolutePath: params.absolutePath,
|
|
388
|
+
mtimeMs: initialMtimeMs,
|
|
389
|
+
ctimeMs: initialCtimeMs,
|
|
390
|
+
unsupportedReason: `file_size_${initialSizeBytes}_exceeds_${DRIVE_SYNC_MAX_LOCAL_FILE_BYTES}_byte_limit`,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
const contentSha256 = await sha256File(params.absolutePath);
|
|
394
|
+
const finalStat = await lstat(params.absolutePath);
|
|
395
|
+
if (!finalStat.isFile() ||
|
|
396
|
+
Number(finalStat.size) !== initialSizeBytes ||
|
|
397
|
+
Number(finalStat.mtimeMs) !== initialMtimeMs ||
|
|
398
|
+
Number(finalStat.ctimeMs) !== initialCtimeMs) {
|
|
399
|
+
throw new UnstableLocalFileError(params.drivePath);
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
driveId: params.driveId,
|
|
403
|
+
path: params.drivePath,
|
|
404
|
+
entryType: 'file',
|
|
405
|
+
contentSha256,
|
|
406
|
+
sizeBytes: Number(finalStat.size),
|
|
407
|
+
mimeType: null,
|
|
408
|
+
absolutePath: params.absolutePath,
|
|
409
|
+
mtimeMs: Number(finalStat.mtimeMs),
|
|
410
|
+
ctimeMs: Number(finalStat.ctimeMs),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
async function sha256File(filePath) {
|
|
414
|
+
const hash = createHash('sha256');
|
|
415
|
+
for await (const chunk of createReadStream(filePath)) {
|
|
416
|
+
hash.update(chunk);
|
|
417
|
+
}
|
|
418
|
+
return hash.digest('hex');
|
|
419
|
+
}
|
|
420
|
+
function describeUnsupportedLocalEntry(entry) {
|
|
421
|
+
if (entry.isBlockDevice())
|
|
422
|
+
return 'block_device_unsupported';
|
|
423
|
+
if (entry.isCharacterDevice())
|
|
424
|
+
return 'character_device_unsupported';
|
|
425
|
+
if (entry.isFIFO())
|
|
426
|
+
return 'fifo_unsupported';
|
|
427
|
+
if (entry.isSocket())
|
|
428
|
+
return 'socket_unsupported';
|
|
429
|
+
return 'unsupported_entry_type';
|
|
430
|
+
}
|
|
431
|
+
function sha256Hex(bytes) {
|
|
432
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
433
|
+
}
|
|
434
|
+
//# sourceMappingURL=drive-sync-filesystem.js.map
|