@syntero/orca-cli 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assistant.d.ts +17 -3
- package/dist/assistant.d.ts.map +1 -1
- package/dist/assistant.js +217 -22
- package/dist/assistant.js.map +1 -1
- package/dist/auth.d.ts +97 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +438 -0
- package/dist/auth.js.map +1 -0
- package/dist/autocomplete.d.ts +24 -0
- package/dist/autocomplete.d.ts.map +1 -0
- package/dist/autocomplete.js +43 -0
- package/dist/autocomplete.js.map +1 -0
- package/dist/components/ChatApp.d.ts +32 -0
- package/dist/components/ChatApp.d.ts.map +1 -0
- package/dist/components/ChatApp.js +470 -0
- package/dist/components/ChatApp.js.map +1 -0
- package/dist/components/CommandPalette.d.ts +15 -0
- package/dist/components/CommandPalette.d.ts.map +1 -0
- package/dist/components/CommandPalette.js +22 -0
- package/dist/components/CommandPalette.js.map +1 -0
- package/dist/components/ConfigWizard.d.ts +25 -0
- package/dist/components/ConfigWizard.d.ts.map +1 -0
- package/dist/components/ConfigWizard.js +401 -0
- package/dist/components/ConfigWizard.js.map +1 -0
- package/dist/components/InputFooter.d.ts +33 -0
- package/dist/components/InputFooter.d.ts.map +1 -0
- package/dist/components/InputFooter.js +618 -0
- package/dist/components/InputFooter.js.map +1 -0
- package/dist/components/InputModal.d.ts +25 -0
- package/dist/components/InputModal.d.ts.map +1 -0
- package/dist/components/InputModal.js +529 -0
- package/dist/components/InputModal.js.map +1 -0
- package/dist/components/ModelMenu.d.ts +24 -0
- package/dist/components/ModelMenu.d.ts.map +1 -0
- package/dist/components/ModelMenu.js +175 -0
- package/dist/components/ModelMenu.js.map +1 -0
- package/dist/components/ProviderMenu.d.ts +20 -0
- package/dist/components/ProviderMenu.d.ts.map +1 -0
- package/dist/components/ProviderMenu.js +31 -0
- package/dist/components/ProviderMenu.js.map +1 -0
- package/dist/components/SyncMenu.d.ts +23 -0
- package/dist/components/SyncMenu.d.ts.map +1 -0
- package/dist/components/SyncMenu.js +135 -0
- package/dist/components/SyncMenu.js.map +1 -0
- package/dist/components/TokenInput.d.ts +15 -0
- package/dist/components/TokenInput.d.ts.map +1 -0
- package/dist/components/TokenInput.js +103 -0
- package/dist/components/TokenInput.js.map +1 -0
- package/dist/index.js +323 -173
- package/dist/index.js.map +1 -1
- package/dist/ink-input.d.ts +14 -0
- package/dist/ink-input.d.ts.map +1 -0
- package/dist/ink-input.js +92 -0
- package/dist/ink-input.js.map +1 -0
- package/dist/models.d.ts +115 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +221 -0
- package/dist/models.js.map +1 -0
- package/dist/providers.d.ts +19 -4
- package/dist/providers.d.ts.map +1 -1
- package/dist/providers.js +80 -112
- package/dist/providers.js.map +1 -1
- package/dist/settings.d.ts +15 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +56 -21
- package/dist/settings.js.map +1 -1
- package/dist/sync/docker.d.ts +68 -0
- package/dist/sync/docker.d.ts.map +1 -0
- package/dist/sync/docker.js +234 -0
- package/dist/sync/docker.js.map +1 -0
- package/dist/sync/download.d.ts +87 -0
- package/dist/sync/download.d.ts.map +1 -0
- package/dist/sync/download.js +291 -0
- package/dist/sync/download.js.map +1 -0
- package/dist/sync/hash.d.ts +60 -0
- package/dist/sync/hash.d.ts.map +1 -0
- package/dist/sync/hash.js +92 -0
- package/dist/sync/hash.js.map +1 -0
- package/dist/sync/index.d.ts +15 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +21 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/manifest.d.ts +132 -0
- package/dist/sync/manifest.d.ts.map +1 -0
- package/dist/sync/manifest.js +260 -0
- package/dist/sync/manifest.js.map +1 -0
- package/dist/sync/progress.d.ts +164 -0
- package/dist/sync/progress.d.ts.map +1 -0
- package/dist/sync/progress.js +233 -0
- package/dist/sync/progress.js.map +1 -0
- package/dist/sync/services.d.ts +70 -0
- package/dist/sync/services.d.ts.map +1 -0
- package/dist/sync/services.js +134 -0
- package/dist/sync/services.js.map +1 -0
- package/dist/sync/sync-engine.d.ts +69 -0
- package/dist/sync/sync-engine.d.ts.map +1 -0
- package/dist/sync/sync-engine.js +533 -0
- package/dist/sync/sync-engine.js.map +1 -0
- package/dist/tokens.d.ts +70 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +198 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tools.d.ts +60 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +34 -0
- package/dist/tools.js.map +1 -1
- package/dist/types/sync.d.ts +284 -0
- package/dist/types/sync.d.ts.map +1 -0
- package/dist/types/sync.js +5 -0
- package/dist/types/sync.js.map +1 -0
- package/package.json +10 -2
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker interaction module for library sync.
|
|
3
|
+
* Provides functions to interact with the sandbox container.
|
|
4
|
+
*/
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
/**
|
|
9
|
+
* Check if Docker is available and running.
|
|
10
|
+
*/
|
|
11
|
+
export function isDockerAvailable() {
|
|
12
|
+
try {
|
|
13
|
+
execSync('docker info', { encoding: 'utf-8', stdio: 'pipe' });
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Find the sandbox container for the current organization.
|
|
22
|
+
* Container naming convention: orca-sandbox-{org_id}
|
|
23
|
+
*/
|
|
24
|
+
export function findSandboxContainer(orgId) {
|
|
25
|
+
const containerName = `orca-sandbox-${orgId}`;
|
|
26
|
+
try {
|
|
27
|
+
const result = execSync(`docker ps --filter "name=^${containerName}$" --format "{{.Names}}"`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
28
|
+
return result === containerName ? containerName : null;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if the sandbox container is running.
|
|
36
|
+
*/
|
|
37
|
+
export function isSandboxRunning(orgId) {
|
|
38
|
+
const container = findSandboxContainer(orgId);
|
|
39
|
+
if (!container)
|
|
40
|
+
return false;
|
|
41
|
+
try {
|
|
42
|
+
const state = execSync(`docker inspect -f '{{.State.Running}}' ${container}`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
43
|
+
return state === 'true';
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Write a file to the sandbox container.
|
|
51
|
+
* Uses docker cp for binary-safe file transfer.
|
|
52
|
+
*/
|
|
53
|
+
export async function writeFileToSandbox(orgId, containerPath, content) {
|
|
54
|
+
const container = `orca-sandbox-${orgId}`;
|
|
55
|
+
// Create temp file on host
|
|
56
|
+
const tempFile = `/tmp/orca-sync-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
57
|
+
await fs.promises.writeFile(tempFile, content);
|
|
58
|
+
try {
|
|
59
|
+
// Ensure parent directory exists in container
|
|
60
|
+
const parentDir = path.dirname(containerPath);
|
|
61
|
+
execSync(`docker exec ${container} mkdir -p "${parentDir}"`, { stdio: 'pipe' });
|
|
62
|
+
// Copy file to container
|
|
63
|
+
execSync(`docker cp "${tempFile}" "${container}:${containerPath}"`, { stdio: 'pipe' });
|
|
64
|
+
// Fix ownership (docker cp creates files as root)
|
|
65
|
+
execSync(`docker exec ${container} chown 1000:1000 "${containerPath}"`, { stdio: 'pipe' });
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
// Clean up temp file
|
|
69
|
+
await fs.promises.unlink(tempFile).catch(() => { });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Read a file from the sandbox container.
|
|
74
|
+
*/
|
|
75
|
+
export async function readFileFromSandbox(orgId, containerPath) {
|
|
76
|
+
const container = `orca-sandbox-${orgId}`;
|
|
77
|
+
const tempFile = `/tmp/orca-sync-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
78
|
+
try {
|
|
79
|
+
execSync(`docker cp "${container}:${containerPath}" "${tempFile}"`, { stdio: 'pipe' });
|
|
80
|
+
return await fs.promises.readFile(tempFile);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await fs.promises.unlink(tempFile).catch(() => { });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check if a file exists in the sandbox container.
|
|
88
|
+
*/
|
|
89
|
+
export function fileExistsInSandbox(orgId, containerPath) {
|
|
90
|
+
const container = `orca-sandbox-${orgId}`;
|
|
91
|
+
try {
|
|
92
|
+
execSync(`docker exec ${container} test -f "${containerPath}"`, { stdio: 'pipe' });
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if a directory exists in the sandbox container.
|
|
101
|
+
*/
|
|
102
|
+
export function directoryExistsInSandbox(orgId, containerPath) {
|
|
103
|
+
const container = `orca-sandbox-${orgId}`;
|
|
104
|
+
try {
|
|
105
|
+
execSync(`docker exec ${container} test -d "${containerPath}"`, { stdio: 'pipe' });
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Delete a file or directory from the sandbox container.
|
|
114
|
+
*/
|
|
115
|
+
export function deleteFromSandbox(orgId, containerPath) {
|
|
116
|
+
const container = `orca-sandbox-${orgId}`;
|
|
117
|
+
execSync(`docker exec ${container} rm -rf "${containerPath}"`, { stdio: 'pipe' });
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* List files in a directory in the sandbox container.
|
|
121
|
+
*/
|
|
122
|
+
export function listSandboxDirectory(orgId, containerPath) {
|
|
123
|
+
const container = `orca-sandbox-${orgId}`;
|
|
124
|
+
try {
|
|
125
|
+
const result = execSync(`docker exec ${container} find "${containerPath}" -type f -printf '%P\\n' 2>/dev/null`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
126
|
+
return result.trim().split('\n').filter(Boolean);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Compute hash of a file in the sandbox container.
|
|
134
|
+
*/
|
|
135
|
+
export function hashFileInSandbox(orgId, containerPath) {
|
|
136
|
+
const container = `orca-sandbox-${orgId}`;
|
|
137
|
+
try {
|
|
138
|
+
const result = execSync(`docker exec ${container} sha256sum "${containerPath}" 2>/dev/null`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
139
|
+
const hash = result.split(' ')[0];
|
|
140
|
+
return hash ? `sha256:${hash}` : null;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Extract a tar.gz archive into the sandbox container.
|
|
148
|
+
*
|
|
149
|
+
* @param orgId Organization ID
|
|
150
|
+
* @param archivePath Path to the tar.gz archive on host
|
|
151
|
+
* @param targetDir Target directory in container to extract to
|
|
152
|
+
*/
|
|
153
|
+
export async function extractArchiveToSandbox(orgId, archivePath, targetDir) {
|
|
154
|
+
const container = `orca-sandbox-${orgId}`;
|
|
155
|
+
// Copy archive to container
|
|
156
|
+
const containerArchive = `/tmp/sync-archive-${Date.now()}.tar.gz`;
|
|
157
|
+
execSync(`docker cp "${archivePath}" "${container}:${containerArchive}"`, { stdio: 'pipe' });
|
|
158
|
+
try {
|
|
159
|
+
// Create target directory if it doesn't exist
|
|
160
|
+
execSync(`docker exec ${container} mkdir -p "${targetDir}"`, { stdio: 'pipe' });
|
|
161
|
+
// Extract archive
|
|
162
|
+
execSync(`docker exec ${container} tar -xzf "${containerArchive}" -C "${targetDir}"`, { stdio: 'pipe' });
|
|
163
|
+
// Fix ownership recursively
|
|
164
|
+
execSync(`docker exec ${container} chown -R 1000:1000 "${targetDir}"`, { stdio: 'pipe' });
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
// Clean up archive in container
|
|
168
|
+
execSync(`docker exec ${container} rm -f "${containerArchive}"`, { stdio: 'pipe' });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Run pre-flight checks before sync.
|
|
173
|
+
*/
|
|
174
|
+
export async function runPreflightChecks(orgId) {
|
|
175
|
+
const result = { success: true, errors: [], warnings: [] };
|
|
176
|
+
// 1. Check Docker availability
|
|
177
|
+
if (!isDockerAvailable()) {
|
|
178
|
+
result.errors.push('Docker is not running or not installed.');
|
|
179
|
+
result.success = false;
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
// 2. Check sandbox container exists and is running
|
|
183
|
+
const containerName = `orca-sandbox-${orgId}`;
|
|
184
|
+
if (!isSandboxRunning(orgId)) {
|
|
185
|
+
result.errors.push(`Sandbox container '${containerName}' is not running.\n` +
|
|
186
|
+
' Start the local Orca deployment first:\n' +
|
|
187
|
+
' cd deployment/local && docker compose up -d');
|
|
188
|
+
result.success = false;
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
result.containerName = containerName;
|
|
192
|
+
// 3. Check write permissions to /workspace/library
|
|
193
|
+
try {
|
|
194
|
+
execSync(`docker exec ${containerName} touch /workspace/library/.sync-test && ` +
|
|
195
|
+
`docker exec ${containerName} rm /workspace/library/.sync-test`, { stdio: 'pipe' });
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
result.errors.push('Cannot write to /workspace/library in sandbox container');
|
|
199
|
+
result.success = false;
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
// 4. Check available disk space (warning if < 100MB)
|
|
203
|
+
try {
|
|
204
|
+
const df = execSync(`docker exec ${containerName} df -m /workspace | tail -1 | awk '{print $4}'`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
205
|
+
const availableMB = parseInt(df.trim(), 10);
|
|
206
|
+
if (!isNaN(availableMB) && availableMB < 100) {
|
|
207
|
+
result.warnings.push(`Low disk space in container: ${availableMB}MB available`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Non-fatal, continue
|
|
212
|
+
}
|
|
213
|
+
// 5. Check for existing library content (for first sync warning)
|
|
214
|
+
const existingContent = listSandboxDirectory(orgId, '/workspace/library');
|
|
215
|
+
// Filter out hidden files like .services.json
|
|
216
|
+
const visibleContent = existingContent.filter(f => !f.startsWith('.'));
|
|
217
|
+
if (visibleContent.length > 0) {
|
|
218
|
+
result.existingLibraryContent = visibleContent.length;
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get the library path in the sandbox container.
|
|
224
|
+
*/
|
|
225
|
+
export function getLibraryPath() {
|
|
226
|
+
return '/workspace/library';
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get the services registry path in the sandbox container.
|
|
230
|
+
*/
|
|
231
|
+
export function getServicesRegistryPath() {
|
|
232
|
+
return '/workspace/library/.services.json';
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=docker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker.js","sourceRoot":"","sources":["../../src/sync/docker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch download module for library sync.
|
|
3
|
+
*
|
|
4
|
+
* Handles downloading files from the backend as tar.gz archives
|
|
5
|
+
* for efficient sync operations.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* File to download with metadata.
|
|
9
|
+
*/
|
|
10
|
+
export interface FileToDownload {
|
|
11
|
+
solution: string;
|
|
12
|
+
path: string;
|
|
13
|
+
hash: string;
|
|
14
|
+
size: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Downloaded file result.
|
|
18
|
+
*/
|
|
19
|
+
export interface DownloadedFile {
|
|
20
|
+
solution: string;
|
|
21
|
+
path: string;
|
|
22
|
+
content: Buffer;
|
|
23
|
+
hash: string;
|
|
24
|
+
verified: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Download result.
|
|
28
|
+
*/
|
|
29
|
+
export interface DownloadResult {
|
|
30
|
+
success: boolean;
|
|
31
|
+
files: DownloadedFile[];
|
|
32
|
+
errors: {
|
|
33
|
+
solution: string;
|
|
34
|
+
path: string;
|
|
35
|
+
error: string;
|
|
36
|
+
}[];
|
|
37
|
+
bytesDownloaded: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create a temporary directory for downloads.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createTempDir(): string;
|
|
43
|
+
/**
|
|
44
|
+
* Clean up a temporary directory.
|
|
45
|
+
*/
|
|
46
|
+
export declare function cleanupTempDir(tempDir: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* Split files into batches for download.
|
|
49
|
+
*
|
|
50
|
+
* @param files Files to download
|
|
51
|
+
* @param maxSize Maximum files per batch
|
|
52
|
+
* @returns Array of batches
|
|
53
|
+
*/
|
|
54
|
+
export declare function splitIntoBatches(files: FileToDownload[], maxSize?: number): FileToDownload[][];
|
|
55
|
+
/**
|
|
56
|
+
* Download a single file from the backend.
|
|
57
|
+
*
|
|
58
|
+
* @param solution Library ID
|
|
59
|
+
* @param filePath Relative file path
|
|
60
|
+
* @param expectedHash Expected SHA-256 hash
|
|
61
|
+
* @returns Downloaded file content
|
|
62
|
+
*/
|
|
63
|
+
export declare function downloadSingleFile(solution: string, filePath: string, expectedHash: string): Promise<Buffer>;
|
|
64
|
+
/**
|
|
65
|
+
* Download a batch of files as a tar.gz archive.
|
|
66
|
+
*
|
|
67
|
+
* @param files Files to download
|
|
68
|
+
* @returns Path to downloaded archive
|
|
69
|
+
*/
|
|
70
|
+
export declare function downloadBatch(files: FileToDownload[]): Promise<string>;
|
|
71
|
+
/**
|
|
72
|
+
* Extract a tar.gz archive and verify file hashes.
|
|
73
|
+
*
|
|
74
|
+
* @param archivePath Path to the tar.gz archive
|
|
75
|
+
* @param expectedFiles Expected files with their hashes
|
|
76
|
+
* @returns Extracted and verified files
|
|
77
|
+
*/
|
|
78
|
+
export declare function extractAndVerify(archivePath: string, expectedFiles: FileToDownload[]): Promise<DownloadResult>;
|
|
79
|
+
/**
|
|
80
|
+
* Download files with retry logic.
|
|
81
|
+
*
|
|
82
|
+
* @param files Files to download
|
|
83
|
+
* @param onProgress Progress callback
|
|
84
|
+
* @returns Download result
|
|
85
|
+
*/
|
|
86
|
+
export declare function downloadWithRetry(files: FileToDownload[], onProgress?: (downloaded: number, total: number) => void): Promise<DownloadResult>;
|
|
87
|
+
//# sourceMappingURL=download.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/sync/download.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoBH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5D,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAMpD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,GAAE,MAAuB,GAC/B,cAAc,EAAE,EAAE,CAQpB;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CAkBjB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,cAAc,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,CAyBjB;AA4CD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,cAAc,EAAE,GAC9B,OAAO,CAAC,cAAc,CAAC,CAmFzB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,cAAc,EAAE,EACvB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACvD,OAAO,CAAC,cAAc,CAAC,CAyEzB"}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch download module for library sync.
|
|
3
|
+
*
|
|
4
|
+
* Handles downloading files from the backend as tar.gz archives
|
|
5
|
+
* for efficient sync operations.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
import * as tar from 'tar';
|
|
11
|
+
import { cloudRequest } from '../auth.js';
|
|
12
|
+
import { hashBuffer } from './hash.js';
|
|
13
|
+
/** Maximum files per batch request */
|
|
14
|
+
const MAX_BATCH_SIZE = 100;
|
|
15
|
+
/** Maximum retries for download */
|
|
16
|
+
const MAX_RETRIES = 3;
|
|
17
|
+
/** Backoff factor for retries (ms) */
|
|
18
|
+
const BACKOFF_BASE = 1000;
|
|
19
|
+
/**
|
|
20
|
+
* Create a temporary directory for downloads.
|
|
21
|
+
*/
|
|
22
|
+
export function createTempDir() {
|
|
23
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'orca-sync-'));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Clean up a temporary directory.
|
|
27
|
+
*/
|
|
28
|
+
export function cleanupTempDir(tempDir) {
|
|
29
|
+
try {
|
|
30
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Ignore cleanup errors
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Split files into batches for download.
|
|
38
|
+
*
|
|
39
|
+
* @param files Files to download
|
|
40
|
+
* @param maxSize Maximum files per batch
|
|
41
|
+
* @returns Array of batches
|
|
42
|
+
*/
|
|
43
|
+
export function splitIntoBatches(files, maxSize = MAX_BATCH_SIZE) {
|
|
44
|
+
const batches = [];
|
|
45
|
+
for (let i = 0; i < files.length; i += maxSize) {
|
|
46
|
+
batches.push(files.slice(i, i + maxSize));
|
|
47
|
+
}
|
|
48
|
+
return batches;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Download a single file from the backend.
|
|
52
|
+
*
|
|
53
|
+
* @param solution Library ID
|
|
54
|
+
* @param filePath Relative file path
|
|
55
|
+
* @param expectedHash Expected SHA-256 hash
|
|
56
|
+
* @returns Downloaded file content
|
|
57
|
+
*/
|
|
58
|
+
export async function downloadSingleFile(solution, filePath, expectedHash) {
|
|
59
|
+
const params = new URLSearchParams({
|
|
60
|
+
solution,
|
|
61
|
+
path: filePath
|
|
62
|
+
});
|
|
63
|
+
const response = await cloudRequest('GET', `/api/v2/library/sync/file?${params.toString()}`);
|
|
64
|
+
if (response.status !== 200) {
|
|
65
|
+
throw new Error(`Download failed: ${response.data.error || 'Unknown error'}`);
|
|
66
|
+
}
|
|
67
|
+
// Response data is raw binary in this case - need to handle differently
|
|
68
|
+
// For now, fall back to batch download for all files
|
|
69
|
+
throw new Error('Single file download not implemented - use batch download');
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Download a batch of files as a tar.gz archive.
|
|
73
|
+
*
|
|
74
|
+
* @param files Files to download
|
|
75
|
+
* @returns Path to downloaded archive
|
|
76
|
+
*/
|
|
77
|
+
export async function downloadBatch(files) {
|
|
78
|
+
if (files.length === 0) {
|
|
79
|
+
throw new Error('No files to download');
|
|
80
|
+
}
|
|
81
|
+
if (files.length > MAX_BATCH_SIZE) {
|
|
82
|
+
throw new Error(`Batch too large: ${files.length} > ${MAX_BATCH_SIZE}`);
|
|
83
|
+
}
|
|
84
|
+
// Prepare request body
|
|
85
|
+
const body = {
|
|
86
|
+
files: files.map(f => ({
|
|
87
|
+
solution: f.solution,
|
|
88
|
+
path: f.path
|
|
89
|
+
}))
|
|
90
|
+
};
|
|
91
|
+
// Make request - need to handle binary response
|
|
92
|
+
const response = await fetchBinaryFromCloud('/api/v2/library/sync/batch', body);
|
|
93
|
+
// Save to temp file
|
|
94
|
+
const tempFile = path.join(os.tmpdir(), `orca-sync-batch-${Date.now()}.tar.gz`);
|
|
95
|
+
await fs.promises.writeFile(tempFile, response);
|
|
96
|
+
return tempFile;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Fetch binary content from cloud endpoint.
|
|
100
|
+
* This is a custom function since cloudRequest doesn't handle binary responses.
|
|
101
|
+
*/
|
|
102
|
+
async function fetchBinaryFromCloud(urlPath, body) {
|
|
103
|
+
const { loadCredentials } = await import('../auth.js');
|
|
104
|
+
const creds = loadCredentials();
|
|
105
|
+
if (!creds) {
|
|
106
|
+
throw new Error('Not authenticated. Run /token first.');
|
|
107
|
+
}
|
|
108
|
+
const url = `${creds.endpoint}${urlPath}`;
|
|
109
|
+
// Use native fetch for binary response
|
|
110
|
+
const response = await fetch(url, {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
headers: {
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
'Authorization': `Bearer ${creds.token}`
|
|
115
|
+
},
|
|
116
|
+
body: JSON.stringify(body)
|
|
117
|
+
});
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
let errorMsg = `HTTP ${response.status}`;
|
|
120
|
+
try {
|
|
121
|
+
const errorData = await response.json();
|
|
122
|
+
errorMsg = errorData.error || errorMsg;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Ignore JSON parse error
|
|
126
|
+
}
|
|
127
|
+
throw new Error(`Download failed: ${errorMsg}`);
|
|
128
|
+
}
|
|
129
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
130
|
+
return Buffer.from(arrayBuffer);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Extract a tar.gz archive and verify file hashes.
|
|
134
|
+
*
|
|
135
|
+
* @param archivePath Path to the tar.gz archive
|
|
136
|
+
* @param expectedFiles Expected files with their hashes
|
|
137
|
+
* @returns Extracted and verified files
|
|
138
|
+
*/
|
|
139
|
+
export async function extractAndVerify(archivePath, expectedFiles) {
|
|
140
|
+
const result = {
|
|
141
|
+
success: true,
|
|
142
|
+
files: [],
|
|
143
|
+
errors: [],
|
|
144
|
+
bytesDownloaded: 0
|
|
145
|
+
};
|
|
146
|
+
// Create temp directory for extraction
|
|
147
|
+
const tempDir = createTempDir();
|
|
148
|
+
try {
|
|
149
|
+
// Extract archive
|
|
150
|
+
await tar.extract({
|
|
151
|
+
file: archivePath,
|
|
152
|
+
cwd: tempDir
|
|
153
|
+
});
|
|
154
|
+
// Create lookup map for expected files
|
|
155
|
+
const expectedMap = new Map();
|
|
156
|
+
for (const f of expectedFiles) {
|
|
157
|
+
const key = `${f.solution}/${f.path}`;
|
|
158
|
+
expectedMap.set(key, f);
|
|
159
|
+
}
|
|
160
|
+
// Process extracted files
|
|
161
|
+
for (const expected of expectedFiles) {
|
|
162
|
+
const relPath = `${expected.solution}/${expected.path}`;
|
|
163
|
+
const extractedPath = path.join(tempDir, relPath);
|
|
164
|
+
try {
|
|
165
|
+
// Check if file was extracted
|
|
166
|
+
if (!fs.existsSync(extractedPath)) {
|
|
167
|
+
result.errors.push({
|
|
168
|
+
solution: expected.solution,
|
|
169
|
+
path: expected.path,
|
|
170
|
+
error: 'File not in archive'
|
|
171
|
+
});
|
|
172
|
+
result.success = false;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
// Read file content
|
|
176
|
+
const content = await fs.promises.readFile(extractedPath);
|
|
177
|
+
result.bytesDownloaded += content.length;
|
|
178
|
+
// Verify hash
|
|
179
|
+
const actualHash = hashBuffer(content);
|
|
180
|
+
const verified = actualHash === expected.hash;
|
|
181
|
+
if (!verified) {
|
|
182
|
+
result.errors.push({
|
|
183
|
+
solution: expected.solution,
|
|
184
|
+
path: expected.path,
|
|
185
|
+
error: `Hash mismatch: expected ${expected.hash}, got ${actualHash}`
|
|
186
|
+
});
|
|
187
|
+
result.success = false;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
// Add to results
|
|
191
|
+
result.files.push({
|
|
192
|
+
solution: expected.solution,
|
|
193
|
+
path: expected.path,
|
|
194
|
+
content,
|
|
195
|
+
hash: actualHash,
|
|
196
|
+
verified: true
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
result.errors.push({
|
|
201
|
+
solution: expected.solution,
|
|
202
|
+
path: expected.path,
|
|
203
|
+
error: e instanceof Error ? e.message : String(e)
|
|
204
|
+
});
|
|
205
|
+
result.success = false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
// Cleanup
|
|
211
|
+
cleanupTempDir(tempDir);
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Download files with retry logic.
|
|
217
|
+
*
|
|
218
|
+
* @param files Files to download
|
|
219
|
+
* @param onProgress Progress callback
|
|
220
|
+
* @returns Download result
|
|
221
|
+
*/
|
|
222
|
+
export async function downloadWithRetry(files, onProgress) {
|
|
223
|
+
const batches = splitIntoBatches(files);
|
|
224
|
+
const result = {
|
|
225
|
+
success: true,
|
|
226
|
+
files: [],
|
|
227
|
+
errors: [],
|
|
228
|
+
bytesDownloaded: 0
|
|
229
|
+
};
|
|
230
|
+
let downloadedCount = 0;
|
|
231
|
+
const totalCount = files.length;
|
|
232
|
+
for (const batch of batches) {
|
|
233
|
+
let lastError = null;
|
|
234
|
+
let batchResult = null;
|
|
235
|
+
// Retry loop for this batch
|
|
236
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
237
|
+
try {
|
|
238
|
+
// Download batch
|
|
239
|
+
const archivePath = await downloadBatch(batch);
|
|
240
|
+
try {
|
|
241
|
+
// Extract and verify
|
|
242
|
+
batchResult = await extractAndVerify(archivePath, batch);
|
|
243
|
+
break; // Success, exit retry loop
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
// Clean up archive
|
|
247
|
+
try {
|
|
248
|
+
await fs.promises.unlink(archivePath);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Ignore cleanup errors
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch (e) {
|
|
256
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
257
|
+
if (attempt < MAX_RETRIES) {
|
|
258
|
+
// Exponential backoff
|
|
259
|
+
const delay = BACKOFF_BASE * Math.pow(2, attempt - 1);
|
|
260
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Check if batch succeeded
|
|
265
|
+
if (batchResult) {
|
|
266
|
+
result.files.push(...batchResult.files);
|
|
267
|
+
result.errors.push(...batchResult.errors);
|
|
268
|
+
result.bytesDownloaded += batchResult.bytesDownloaded;
|
|
269
|
+
if (!batchResult.success) {
|
|
270
|
+
result.success = false;
|
|
271
|
+
}
|
|
272
|
+
downloadedCount += batch.length;
|
|
273
|
+
if (onProgress) {
|
|
274
|
+
onProgress(downloadedCount, totalCount);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
// All retries failed
|
|
279
|
+
result.success = false;
|
|
280
|
+
for (const f of batch) {
|
|
281
|
+
result.errors.push({
|
|
282
|
+
solution: f.solution,
|
|
283
|
+
path: f.path,
|
|
284
|
+
error: lastError?.message || 'Download failed after retries'
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=download.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download.js","sourceRoot":"","sources":["../../src/sync/download.ts"],"names":[],"mappings":"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"}
|