aspect-sync 0.0.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 +278 -0
- package/dist/cliDisplay.d.ts +8 -0
- package/dist/cliDisplay.d.ts.map +1 -0
- package/dist/cliDisplay.js +101 -0
- package/dist/cliDisplay.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/rclone.d.ts +17 -0
- package/dist/rclone.d.ts.map +1 -0
- package/dist/rclone.js +164 -0
- package/dist/rclone.js.map +1 -0
- package/dist/statusTracker.d.ts +36 -0
- package/dist/statusTracker.d.ts.map +1 -0
- package/dist/statusTracker.js +205 -0
- package/dist/statusTracker.js.map +1 -0
- package/dist/sync.d.ts +7 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +185 -0
- package/dist/sync.js.map +1 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/uploader.d.ts +13 -0
- package/dist/uploader.d.ts.map +1 -0
- package/dist/uploader.js +220 -0
- package/dist/uploader.js.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# Aspect Data Sync
|
|
2
|
+
|
|
3
|
+
A CLI tool to sync files from external services (via rclone) to the Aspect platform.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Rclone Integration**: Sync files from any rclone-supported remote (Dropbox, Google Drive, S3, etc.)
|
|
8
|
+
- **Directory Structure Preservation**: Maintains folder hierarchy from source to destination
|
|
9
|
+
- **Multipart Upload**: Efficient chunked uploads with retry logic (20MB chunks)
|
|
10
|
+
- **Dynamic CLI Progress**: Real-time upload progress display with in-place updates
|
|
11
|
+
- **Concurrent Uploads**: Configurable parallel chunk uploads for optimal performance
|
|
12
|
+
- **Automatic Cleanup**: Removes local files after successful upload
|
|
13
|
+
- **Retry Logic**: Automatic retry for failed uploads (part-level and file-level)
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
1. **Node.js 22+** installed
|
|
18
|
+
2. **rclone** installed and configured
|
|
19
|
+
- Install: https://rclone.org/install/
|
|
20
|
+
- Configure your remote: `rclone config`
|
|
21
|
+
3. **Aspect API Key** for authentication
|
|
22
|
+
|
|
23
|
+
## Configuring Rclone
|
|
24
|
+
|
|
25
|
+
Before using this tool, you must configure rclone with your cloud storage provider.
|
|
26
|
+
|
|
27
|
+
### Initial Setup
|
|
28
|
+
|
|
29
|
+
1. Run the rclone configuration wizard:
|
|
30
|
+
```bash
|
|
31
|
+
rclone config
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. Choose `n` for "New remote"
|
|
35
|
+
|
|
36
|
+
3. Give it a name (e.g., `dropbox`, `gdrive`, `my-s3`)
|
|
37
|
+
- **This name is what you'll use for the `--remote` parameter**
|
|
38
|
+
- Use only letters, numbers, hyphens, and underscores
|
|
39
|
+
- Don't include special characters or colons
|
|
40
|
+
|
|
41
|
+
4. Select your storage provider from the list (Dropbox, Google Drive, S3, etc.)
|
|
42
|
+
|
|
43
|
+
5. Follow the prompts to authenticate with your cloud provider
|
|
44
|
+
|
|
45
|
+
### Verify Configuration
|
|
46
|
+
|
|
47
|
+
Check your configured remotes:
|
|
48
|
+
```bash
|
|
49
|
+
rclone listremotes
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Output example:
|
|
53
|
+
```
|
|
54
|
+
dropbox:
|
|
55
|
+
gdrive:
|
|
56
|
+
my-s3:
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Important:** The remote names are shown **with a colon** (`:`) but you use them **without the colon** in the `--remote` parameter.
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
- If you see `dropbox:` → use `--remote dropbox`
|
|
63
|
+
- If you see `my-dropbox:` → use `--remote my-dropbox`
|
|
64
|
+
- If you see `gdrive:` → use `--remote gdrive`
|
|
65
|
+
|
|
66
|
+
### Test Your Remote
|
|
67
|
+
|
|
68
|
+
Before syncing, test that your remote works:
|
|
69
|
+
```bash
|
|
70
|
+
# List files in the root of your remote
|
|
71
|
+
rclone ls dropbox:
|
|
72
|
+
|
|
73
|
+
# List files in a specific folder
|
|
74
|
+
rclone ls dropbox:my-folder
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This should list files in your cloud storage. If you see files listed, your remote is configured correctly!
|
|
78
|
+
|
|
79
|
+
## Installation
|
|
80
|
+
|
|
81
|
+
Install globally via npm:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm install -g aspect-sync
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Or use directly with npx (no installation required):
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx aspect-sync --help
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Usage
|
|
94
|
+
|
|
95
|
+
### Basic Command
|
|
96
|
+
|
|
97
|
+
If installed globally:
|
|
98
|
+
```bash
|
|
99
|
+
aspect-sync \
|
|
100
|
+
--remote dropbox \
|
|
101
|
+
--path /my-videos \
|
|
102
|
+
--directory-id <aspect-directory-id> \
|
|
103
|
+
--project-id <aspect-project-id> \
|
|
104
|
+
--api-key <your-api-key>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Or with npx (no installation):
|
|
108
|
+
```bash
|
|
109
|
+
npx aspect-sync \
|
|
110
|
+
--remote dropbox \
|
|
111
|
+
--path /my-videos \
|
|
112
|
+
--directory-id <aspect-directory-id> \
|
|
113
|
+
--project-id <aspect-project-id> \
|
|
114
|
+
--api-key <your-api-key>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Command Options
|
|
118
|
+
|
|
119
|
+
| Option | Description | Required | Default |
|
|
120
|
+
|--------|-------------|----------|---------|
|
|
121
|
+
| `--remote <name>` | rclone remote name from your rclone config (without colon). Use `rclone listremotes` to see your configured remotes. | Yes | - |
|
|
122
|
+
| `--path <path>` | Remote path to sync from | Yes | - |
|
|
123
|
+
| `--directory-id <id>` | Aspect directory ID to upload to | Yes | - |
|
|
124
|
+
| `--project-id <id>` | Aspect project ID | Yes | - |
|
|
125
|
+
| `--api-key <key>` | Aspect API key (or set `ASPECT_API_KEY` env var) | Yes | - |
|
|
126
|
+
| `--api-url <url>` | Aspect API URL (or set `ASPECT_API_URL` env var) | No | `http://localhost:8000` |
|
|
127
|
+
| `--concurrent <number>` | Max concurrent chunk uploads | No | `4` |
|
|
128
|
+
| `--keep-local` | Keep local files after upload (for debugging) | No | `false` |
|
|
129
|
+
| `--temp-dir <path>` | Local temporary directory for synced files | No | `$TMPDIR/aspect-sync` |
|
|
130
|
+
|
|
131
|
+
### Environment Variables
|
|
132
|
+
|
|
133
|
+
You can set API credentials via environment variables:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
export ASPECT_API_KEY=your-api-key
|
|
137
|
+
export ASPECT_API_URL=https://api.aspect.inc
|
|
138
|
+
|
|
139
|
+
aspect-sync \
|
|
140
|
+
--remote dropbox \
|
|
141
|
+
--path /my-videos \
|
|
142
|
+
--directory-id <directory-id> \
|
|
143
|
+
--project-id <project-id>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Examples
|
|
147
|
+
|
|
148
|
+
### Sync from Dropbox
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
aspect-sync \
|
|
152
|
+
--remote dropbox \
|
|
153
|
+
--path /Camera Uploads \
|
|
154
|
+
--directory-id abc-123-def \
|
|
155
|
+
--project-id xyz-789-uvw \
|
|
156
|
+
--api-key your-api-key
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Sync from Google Drive with Custom Concurrency
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
aspect-sync \
|
|
163
|
+
--remote gdrive \
|
|
164
|
+
--path /Videos/2024 \
|
|
165
|
+
--directory-id abc-123-def \
|
|
166
|
+
--project-id xyz-789-uvw \
|
|
167
|
+
--api-key your-api-key \
|
|
168
|
+
--concurrent 8
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Sync from S3 Bucket
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
aspect-sync \
|
|
175
|
+
--remote s3 \
|
|
176
|
+
--path mybucket/videos \
|
|
177
|
+
--directory-id abc-123-def \
|
|
178
|
+
--project-id xyz-789-uvw \
|
|
179
|
+
--api-key your-api-key
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## How It Works
|
|
183
|
+
|
|
184
|
+
1. **Rclone Sync**: Downloads files from the remote to a local temp directory
|
|
185
|
+
2. **Directory Creation**: Creates matching folder structure in Aspect
|
|
186
|
+
3. **File Upload**: Uploads each file using multipart upload (20MB chunks)
|
|
187
|
+
4. **Progress Display**: Shows real-time upload progress in the CLI
|
|
188
|
+
5. **Cleanup**: Deletes local files after successful upload (unless `--keep-local` is set)
|
|
189
|
+
|
|
190
|
+
## CLI Progress Display
|
|
191
|
+
|
|
192
|
+
The tool shows dynamic, in-place progress updates:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
✓ video1.mp4 (100%)
|
|
196
|
+
✓ video2.mp4 (100%)
|
|
197
|
+
↑ video3.mp4 (47%)
|
|
198
|
+
↑ video4.mp4 (23%)
|
|
199
|
+
|
|
200
|
+
Total: 10 | Success: 2 | Failed: 0 | In Progress: 2 | Queued: 6 | Speed: 45.3 Mb/s | Time remaining: 2m 15s
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
- `✓` = Successfully uploaded
|
|
204
|
+
- `✗` = Failed upload
|
|
205
|
+
- `↑` = Currently uploading
|
|
206
|
+
|
|
207
|
+
Only files that are actively uploading are shown (not the entire queue).
|
|
208
|
+
|
|
209
|
+
## Error Handling
|
|
210
|
+
|
|
211
|
+
### Retry Logic
|
|
212
|
+
|
|
213
|
+
- **Part-level retries**: Each 20MB chunk is retried up to 3 times on failure
|
|
214
|
+
- **File-level retries**: Each file upload is retried up to 3 times on failure
|
|
215
|
+
- **Exponential backoff**: Delays increase exponentially between retries
|
|
216
|
+
|
|
217
|
+
### Common Errors
|
|
218
|
+
|
|
219
|
+
**rclone not found:**
|
|
220
|
+
```
|
|
221
|
+
Error: rclone is not installed or not in PATH
|
|
222
|
+
```
|
|
223
|
+
Solution: Install rclone from https://rclone.org/install/
|
|
224
|
+
|
|
225
|
+
**Authentication failed:**
|
|
226
|
+
```
|
|
227
|
+
Error: 401 Unauthorized
|
|
228
|
+
```
|
|
229
|
+
Solution: Check your `--api-key` is correct
|
|
230
|
+
|
|
231
|
+
**Directory not found:**
|
|
232
|
+
```
|
|
233
|
+
Error: 404 Not Found - Directory not found
|
|
234
|
+
```
|
|
235
|
+
Solution: Verify `--directory-id` exists and you have access
|
|
236
|
+
|
|
237
|
+
## Troubleshooting
|
|
238
|
+
|
|
239
|
+
### Keep local files for debugging
|
|
240
|
+
|
|
241
|
+
Use `--keep-local` to prevent deletion of synced files:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
aspect-sync \
|
|
245
|
+
--remote dropbox \
|
|
246
|
+
--path /test \
|
|
247
|
+
--directory-id abc-123 \
|
|
248
|
+
--project-id xyz-789 \
|
|
249
|
+
--api-key your-key \
|
|
250
|
+
--keep-local
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Increase concurrency
|
|
254
|
+
|
|
255
|
+
If you have high bandwidth, increase `--concurrent`:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
aspect-sync \
|
|
259
|
+
--remote dropbox \
|
|
260
|
+
--path /test \
|
|
261
|
+
--directory-id abc-123 \
|
|
262
|
+
--project-id xyz-789 \
|
|
263
|
+
--api-key your-key \
|
|
264
|
+
--concurrent 16
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Check rclone configuration
|
|
268
|
+
|
|
269
|
+
Verify your rclone remote is configured:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
rclone listremotes
|
|
273
|
+
rclone ls dropbox:
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cliDisplay.d.ts","sourceRoot":"","sources":["../src/cliDisplay.ts"],"names":[],"mappings":"AAKA,cAAM,UAAU;;IAOd,KAAK,IAAI,IAAI;IAkBb,IAAI,IAAI,IAAI;CAoGb;AAED,eAAO,MAAM,UAAU,YAAmB,CAAA"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import logUpdate from "log-update";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { statusTracker } from "./statusTracker.js";
|
|
4
|
+
import { UploadStatus } from "./types.js";
|
|
5
|
+
class CliDisplay {
|
|
6
|
+
#completedLines = [];
|
|
7
|
+
#completedFileIds = new Set();
|
|
8
|
+
#updateInterval = null;
|
|
9
|
+
#statsInterval = null;
|
|
10
|
+
#isRunning = false;
|
|
11
|
+
start() {
|
|
12
|
+
if (this.#isRunning)
|
|
13
|
+
return;
|
|
14
|
+
this.#isRunning = true;
|
|
15
|
+
// Update display every 100ms for smooth progress bars
|
|
16
|
+
this.#updateInterval = setInterval(() => {
|
|
17
|
+
this.#render();
|
|
18
|
+
}, 100);
|
|
19
|
+
// Update stats every 1 second for stable speed/time calculations
|
|
20
|
+
this.#statsInterval = setInterval(() => {
|
|
21
|
+
statusTracker.updateUploadStats();
|
|
22
|
+
}, 1000);
|
|
23
|
+
// Calculate stats immediately on start
|
|
24
|
+
statusTracker.updateUploadStats();
|
|
25
|
+
}
|
|
26
|
+
stop() {
|
|
27
|
+
if (!this.#isRunning)
|
|
28
|
+
return;
|
|
29
|
+
this.#isRunning = false;
|
|
30
|
+
if (this.#updateInterval) {
|
|
31
|
+
clearInterval(this.#updateInterval);
|
|
32
|
+
this.#updateInterval = null;
|
|
33
|
+
}
|
|
34
|
+
if (this.#statsInterval) {
|
|
35
|
+
clearInterval(this.#statsInterval);
|
|
36
|
+
this.#statsInterval = null;
|
|
37
|
+
}
|
|
38
|
+
// Update stats one final time for accurate final display
|
|
39
|
+
statusTracker.updateUploadStats();
|
|
40
|
+
this.#render();
|
|
41
|
+
logUpdate.done();
|
|
42
|
+
}
|
|
43
|
+
#render() {
|
|
44
|
+
const inProgressFiles = statusTracker.getInProgressFiles();
|
|
45
|
+
const allFiles = statusTracker.getAllUploadFiles();
|
|
46
|
+
const totals = statusTracker.getUploadTotals();
|
|
47
|
+
// Check for newly completed/failed files since last render
|
|
48
|
+
for (const file of allFiles) {
|
|
49
|
+
if ((file.uploadStatus === UploadStatus.SUCCESS || file.uploadStatus === UploadStatus.FAILED) &&
|
|
50
|
+
!this.#completedFileIds.has(file.fileId)) {
|
|
51
|
+
this.#addCompletedLine(file);
|
|
52
|
+
this.#completedFileIds.add(file.fileId);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Build the output
|
|
56
|
+
const lines = [];
|
|
57
|
+
// Add all completed lines (these are frozen)
|
|
58
|
+
lines.push(...this.#completedLines);
|
|
59
|
+
// Add in-progress files (these update dynamically)
|
|
60
|
+
for (const file of inProgressFiles) {
|
|
61
|
+
lines.push(this.#formatInProgressFile(file));
|
|
62
|
+
}
|
|
63
|
+
// Add summary line
|
|
64
|
+
lines.push("");
|
|
65
|
+
lines.push(this.#formatSummary(totals));
|
|
66
|
+
// Update the display
|
|
67
|
+
logUpdate(lines.join("\n"));
|
|
68
|
+
}
|
|
69
|
+
#addCompletedLine(file) {
|
|
70
|
+
if (file.uploadStatus === UploadStatus.SUCCESS) {
|
|
71
|
+
this.#completedLines.push(`${chalk.green("✓")} ${file.fileName} ${chalk.green("(100%)")}`);
|
|
72
|
+
}
|
|
73
|
+
else if (file.uploadStatus === UploadStatus.FAILED) {
|
|
74
|
+
this.#completedLines.push(`${chalk.red("✗")} ${file.fileName} ${chalk.red(`(Failed: ${file.errorMessage || "Unknown error"})`)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
#formatInProgressFile(file) {
|
|
78
|
+
const percentage = file.totalBytesToUpload > 0
|
|
79
|
+
? Math.floor((file.bytesUploaded / file.totalBytesToUpload) * 100)
|
|
80
|
+
: 0;
|
|
81
|
+
return `${chalk.blue("↑")} ${file.fileName} ${chalk.blue(`(${percentage}%)`)}`;
|
|
82
|
+
}
|
|
83
|
+
#formatSummary(totals) {
|
|
84
|
+
// Stats are updated by the 1-second interval, just read cached values
|
|
85
|
+
const stats = statusTracker.uploadStats;
|
|
86
|
+
const parts = [
|
|
87
|
+
`Total: ${totals.totalFileCount}`,
|
|
88
|
+
chalk.green(`Success: ${totals.successFileCount}`),
|
|
89
|
+
chalk.red(`Failed: ${totals.failedFileCount}`),
|
|
90
|
+
chalk.blue(`In Progress: ${totals.inProgressFileCount}`),
|
|
91
|
+
chalk.gray(`Queued: ${totals.queuedFileCount}`),
|
|
92
|
+
];
|
|
93
|
+
const statsLine = stats.formattedSpeed ? [
|
|
94
|
+
chalk.cyan(`Speed: ${stats.formattedSpeed}`),
|
|
95
|
+
chalk.magenta(`Time remaining: ${stats.formattedTime}`),
|
|
96
|
+
] : [];
|
|
97
|
+
return `${parts.join(" | ")}${statsLine.length > 0 ? ` | ${statsLine.join(" | ")}` : ""}`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export const cliDisplay = new CliDisplay();
|
|
101
|
+
//# sourceMappingURL=cliDisplay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cliDisplay.js","sourceRoot":"","sources":["../src/cliDisplay.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as os from "os";
|
|
5
|
+
import { SyncOrchestrator } from "./sync.js";
|
|
6
|
+
const program = new Command();
|
|
7
|
+
program
|
|
8
|
+
.name("aspect-sync")
|
|
9
|
+
.description("Sync files from external services to Aspect via rclone")
|
|
10
|
+
.version("0.1.0");
|
|
11
|
+
program
|
|
12
|
+
.requiredOption("--remote <remote>", "rclone remote name (e.g., dropbox)")
|
|
13
|
+
.requiredOption("--path <path>", "remote path to sync from")
|
|
14
|
+
.requiredOption("--directory-id <id>", "Aspect directory ID to upload to")
|
|
15
|
+
.requiredOption("--project-id <id>", "Aspect project ID")
|
|
16
|
+
.option("--api-url <url>", "Aspect API URL", process.env.ASPECT_API_URL || "https://api.aspect.inc")
|
|
17
|
+
.option("--api-key <key>", "Aspect API key", process.env.ASPECT_API_KEY || "")
|
|
18
|
+
.option("--concurrent <number>", "Maximum concurrent chunk uploads", "4")
|
|
19
|
+
.option("--keep-local", "Keep local files after upload (for debugging)", false)
|
|
20
|
+
.option("--temp-dir <path>", "Local temporary directory for synced files", path.join(os.homedir(), ".aspect", "sync"))
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
// Validate required options
|
|
23
|
+
if (!options.apiKey) {
|
|
24
|
+
console.error("Error: --api-key is required (or set ASPECT_API_KEY env variable)");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
// Build config
|
|
28
|
+
const config = {
|
|
29
|
+
remote: options.remote,
|
|
30
|
+
remotePath: options.path,
|
|
31
|
+
directoryId: options.directoryId,
|
|
32
|
+
projectId: options.projectId,
|
|
33
|
+
apiUrl: options.apiUrl,
|
|
34
|
+
apiKey: options.apiKey,
|
|
35
|
+
maxConcurrent: parseInt(options.concurrent, 10),
|
|
36
|
+
keepLocal: options.keepLocal,
|
|
37
|
+
localTempDir: options.tempDir,
|
|
38
|
+
};
|
|
39
|
+
// Validate config
|
|
40
|
+
if (isNaN(config.maxConcurrent) || config.maxConcurrent < 1) {
|
|
41
|
+
console.error("Error: --concurrent must be a positive integer");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const orchestrator = new SyncOrchestrator(config);
|
|
46
|
+
await orchestrator.run();
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error("Fatal error:", error);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
program.parse();
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAG5C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,aAAa,CAAC;KACnB,WAAW,CAAC,wDAAwD,CAAC;KACrE,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnB,OAAO;KACJ,cAAc,CAAC,mBAAmB,EAAE,oCAAoC,CAAC;KACzE,cAAc,CAAC,eAAe,EAAE,0BAA0B,CAAC;KAC3D,cAAc,CAAC,qBAAqB,EAAE,kCAAkC,CAAC;KACzE,cAAc,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;KACxD,MAAM,CAAC,iBAAiB,EAAE,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,wBAAwB,CAAC;KACnG,MAAM,CAAC,iBAAiB,EAAE,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC;KAC7E,MAAM,CAAC,uBAAuB,EAAE,kCAAkC,EAAE,GAAG,CAAC;KACxE,MAAM,CAAC,cAAc,EAAE,+CAA+C,EAAE,KAAK,CAAC;KAC9E,MAAM,CAAC,mBAAmB,EAAE,4CAA4C,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;KACrH,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,4BAA4B;IAC5B,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,mEAAmE,CAAC,CAAA;QAClF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,eAAe;IACf,MAAM,MAAM,GAAe;QACzB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,UAAU,EAAE,OAAO,CAAC,IAAI;QACxB,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,aAAa,EAAE,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;QAC/C,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,YAAY,EAAE,OAAO,CAAC,OAAO;KAC9B,CAAA;IAED,kBAAkB;IAClB,IAAI,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,MAAM,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC;QAC5D,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAA;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAA;QACjD,MAAM,YAAY,CAAC,GAAG,EAAE,CAAA;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAA;QACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,OAAO,CAAC,KAAK,EAAE,CAAA"}
|
package/dist/rclone.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare class RcloneError extends Error {
|
|
2
|
+
readonly stderr: string;
|
|
3
|
+
constructor(message: string, stderr: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function checkRcloneInstalled(): Promise<void>;
|
|
6
|
+
export declare function syncFromRemote(remote: string, remotePath: string, localPath: string): Promise<void>;
|
|
7
|
+
export declare function listRemoteFiles(remote: string, remotePath: string): Promise<string[]>;
|
|
8
|
+
export interface LocalFileInfo {
|
|
9
|
+
relativePath: string;
|
|
10
|
+
absolutePath: string;
|
|
11
|
+
fileName: string;
|
|
12
|
+
size: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function scanLocalDirectory(localPath: string): Promise<LocalFileInfo[]>;
|
|
15
|
+
export declare function deleteLocalFile(filePath: string): Promise<void>;
|
|
16
|
+
export declare function deleteEmptyDirectories(rootPath: string): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=rclone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rclone.d.ts","sourceRoot":"","sources":["../src/rclone.ts"],"names":[],"mappings":"AAIA,qBAAa,WAAY,SAAQ,KAAK;aACS,MAAM,EAAE,MAAM;gBAA/C,OAAO,EAAE,MAAM,EAAkB,MAAM,EAAE,MAAM;CAI5D;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAoB1D;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAuDf;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,EAAE,CAAC,CA0CnB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA0BpF;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMrE;AAED,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+B5E"}
|
package/dist/rclone.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
export class RcloneError extends Error {
|
|
5
|
+
stderr;
|
|
6
|
+
constructor(message, stderr) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.stderr = stderr;
|
|
9
|
+
this.name = "RcloneError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function checkRcloneInstalled() {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const process = spawn("rclone", ["version"]);
|
|
15
|
+
process.on("error", () => {
|
|
16
|
+
reject(new Error("rclone is not installed or not in PATH. Please install rclone first: https://rclone.org/install/"));
|
|
17
|
+
});
|
|
18
|
+
process.on("close", (code) => {
|
|
19
|
+
if (code === 0) {
|
|
20
|
+
resolve();
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
reject(new Error("rclone is not installed or not in PATH. Please install rclone first: https://rclone.org/install/"));
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export async function syncFromRemote(remote, remotePath, localPath) {
|
|
29
|
+
const remoteSource = `${remote}:${remotePath}`;
|
|
30
|
+
console.log(`Syncing from ${remoteSource} to ${localPath}...`);
|
|
31
|
+
console.log("");
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const rcloneProcess = spawn("rclone", [
|
|
34
|
+
"sync",
|
|
35
|
+
remoteSource,
|
|
36
|
+
localPath,
|
|
37
|
+
"--progress",
|
|
38
|
+
"--stats", "1s",
|
|
39
|
+
"--stats-one-line",
|
|
40
|
+
"-v"
|
|
41
|
+
]);
|
|
42
|
+
let stderrOutput = "";
|
|
43
|
+
// Stream stdout to console in real-time
|
|
44
|
+
rcloneProcess.stdout.on("data", (data) => {
|
|
45
|
+
process.stdout.write(data);
|
|
46
|
+
});
|
|
47
|
+
// Capture stderr for error reporting
|
|
48
|
+
rcloneProcess.stderr.on("data", (data) => {
|
|
49
|
+
const text = data.toString();
|
|
50
|
+
stderrOutput += text;
|
|
51
|
+
process.stderr.write(data);
|
|
52
|
+
});
|
|
53
|
+
// Handle process completion
|
|
54
|
+
rcloneProcess.on("close", (code) => {
|
|
55
|
+
if (code === 0) {
|
|
56
|
+
console.log("");
|
|
57
|
+
console.log(`✓ Sync complete: ${remoteSource} -> ${localPath}`);
|
|
58
|
+
resolve();
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.log("");
|
|
62
|
+
console.log(`✗ Failed to sync from ${remoteSource}`);
|
|
63
|
+
reject(new RcloneError(`rclone exited with code ${code}`, stderrOutput));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
// Handle spawn errors (e.g., rclone not found)
|
|
67
|
+
rcloneProcess.on("error", (error) => {
|
|
68
|
+
reject(new RcloneError(`Failed to start rclone: ${error.message}`, ""));
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export async function listRemoteFiles(remote, remotePath) {
|
|
73
|
+
const remoteSource = `${remote}:${remotePath}`;
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const rcloneProcess = spawn("rclone", [
|
|
76
|
+
"lsf",
|
|
77
|
+
remoteSource,
|
|
78
|
+
"--recursive"
|
|
79
|
+
]);
|
|
80
|
+
let stdoutOutput = "";
|
|
81
|
+
let stderrOutput = "";
|
|
82
|
+
rcloneProcess.stdout.on("data", (data) => {
|
|
83
|
+
stdoutOutput += data.toString();
|
|
84
|
+
});
|
|
85
|
+
rcloneProcess.stderr.on("data", (data) => {
|
|
86
|
+
stderrOutput += data.toString();
|
|
87
|
+
});
|
|
88
|
+
rcloneProcess.on("close", (code) => {
|
|
89
|
+
if (code === 0) {
|
|
90
|
+
const files = stdoutOutput
|
|
91
|
+
.split("\n")
|
|
92
|
+
.filter(line => line.trim() !== "" && !line.endsWith("/"));
|
|
93
|
+
resolve(files);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
reject(new RcloneError(`Failed to list files from ${remoteSource}`, stderrOutput));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
rcloneProcess.on("error", (error) => {
|
|
100
|
+
reject(new RcloneError(`Failed to start rclone: ${error.message}`, ""));
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
export async function scanLocalDirectory(localPath) {
|
|
105
|
+
const files = [];
|
|
106
|
+
async function scanDir(currentPath, relativePath = "") {
|
|
107
|
+
const entries = await fs.promises.readdir(currentPath, { withFileTypes: true });
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const entryPath = path.join(currentPath, entry.name);
|
|
110
|
+
const entryRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
|
111
|
+
if (entry.isDirectory()) {
|
|
112
|
+
await scanDir(entryPath, entryRelativePath);
|
|
113
|
+
}
|
|
114
|
+
else if (entry.isFile()) {
|
|
115
|
+
const stats = await fs.promises.stat(entryPath);
|
|
116
|
+
files.push({
|
|
117
|
+
relativePath: entryRelativePath,
|
|
118
|
+
absolutePath: entryPath,
|
|
119
|
+
fileName: entry.name,
|
|
120
|
+
size: stats.size,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
await scanDir(localPath);
|
|
126
|
+
return files;
|
|
127
|
+
}
|
|
128
|
+
export async function deleteLocalFile(filePath) {
|
|
129
|
+
try {
|
|
130
|
+
await fs.promises.unlink(filePath);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error(`Failed to delete local file ${filePath}:`, error.message);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export async function deleteEmptyDirectories(rootPath) {
|
|
137
|
+
async function deleteEmptyDirs(currentPath) {
|
|
138
|
+
const entries = await fs.promises.readdir(currentPath, { withFileTypes: true });
|
|
139
|
+
if (entries.length === 0) {
|
|
140
|
+
await fs.promises.rmdir(currentPath);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
let isEmpty = true;
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
if (entry.isDirectory()) {
|
|
146
|
+
const entryPath = path.join(currentPath, entry.name);
|
|
147
|
+
const wasDeleted = await deleteEmptyDirs(entryPath);
|
|
148
|
+
if (!wasDeleted) {
|
|
149
|
+
isEmpty = false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
isEmpty = false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (isEmpty && currentPath !== rootPath) {
|
|
157
|
+
await fs.promises.rmdir(currentPath);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
await deleteEmptyDirs(rootPath);
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=rclone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rclone.js","sourceRoot":"","sources":["../src/rclone.ts"],"names":[],"mappings":"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"}
|