gdrive-syncer 2.2.0 → 3.0.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/Readme.md +78 -8
- package/package.json +1 -1
- package/run.js +10 -6
- package/src/envSync.js +232 -20
- package/src/gdriveCmd.js +301 -0
- package/src/list.js +20 -11
- package/src/sync.js +19 -4
- package/gdrive.config.json +0 -16
package/Readme.md
CHANGED
|
@@ -4,15 +4,54 @@ A command line tool to manage sync folders with Google Drive. Features two-way s
|
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
7
|
-
Install
|
|
7
|
+
Install the gdrive CLI. This tool supports both gdrive@2 and gdrive@3.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
### Option 1: gdrive@3 (recommended)
|
|
10
|
+
|
|
11
|
+
Install [gdrive@3](https://github.com/glotlabs/gdrive) via Homebrew:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
brew install gdrive
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Setup requires Google OAuth credentials.
|
|
18
|
+
|
|
19
|
+
> **Before you start:** Ask your team if someone already has OAuth credentials set up for gdrive. Sharing existing credentials avoids creating duplicate Google Cloud projects and simplifies onboarding.
|
|
20
|
+
|
|
21
|
+
#### Setting up OAuth credentials
|
|
22
|
+
|
|
23
|
+
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
|
|
24
|
+
2. Create a new project or select existing
|
|
25
|
+
3. Enable the Google Drive API
|
|
26
|
+
4. Configure OAuth consent screen:
|
|
27
|
+
- **For organizations (Google Workspace):** Select **Internal** user type. This avoids the app review process and allows immediate use by your team.
|
|
28
|
+
- **For personal accounts:** Select **External** and add your email as a test user.
|
|
29
|
+
5. Create OAuth 2.0 credentials (Desktop application)
|
|
30
|
+
6. Add an account:
|
|
31
|
+
```bash
|
|
32
|
+
gdrive account add
|
|
33
|
+
```
|
|
34
|
+
7. Enter your Client ID and Client Secret when prompted
|
|
35
|
+
8. Complete the OAuth flow in your browser
|
|
36
|
+
|
|
37
|
+
> **Tip:** If you're in an organization, using **Internal** OAuth consent means the app is automatically trusted for all users in your domain—no verification required.
|
|
38
|
+
|
|
39
|
+
**Note:** gdrive@3 does not support `gdrive sync` commands. Use the `filesync:*` commands instead.
|
|
40
|
+
|
|
41
|
+
### Option 2: gdrive@2 (legacy)
|
|
42
|
+
|
|
43
|
+
[gdrive@2](https://github.com/prasmussen/gdrive) is deprecated and no longer available via Homebrew. If you have it installed, login with:
|
|
10
44
|
|
|
11
|
-
After [gdrive](https://github.com/prasmussen/gdrive) is installed, login to your Google Drive account:
|
|
12
45
|
```bash
|
|
13
46
|
gdrive about
|
|
14
47
|
```
|
|
15
48
|
|
|
49
|
+
### Verify Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
gdrive version
|
|
53
|
+
```
|
|
54
|
+
|
|
16
55
|
## Installation
|
|
17
56
|
|
|
18
57
|
```bash
|
|
@@ -33,14 +72,15 @@ Run without arguments for interactive menu, or use direct commands.
|
|
|
33
72
|
|
|
34
73
|
| Command | Description |
|
|
35
74
|
|---------|-------------|
|
|
36
|
-
| `filesync:diff [local\|global]` | Show differences between local and Drive |
|
|
37
|
-
| `filesync:download [local\|global]` | Download changed files from Drive |
|
|
38
|
-
| `filesync:upload [local\|global]` | Upload changed files to Drive |
|
|
75
|
+
| `filesync:diff [local\|global\|registered]` | Show differences between local and Drive |
|
|
76
|
+
| `filesync:download [local\|global\|registered]` | Download changed files from Drive |
|
|
77
|
+
| `filesync:upload [local\|global\|registered]` | Upload changed files to Drive |
|
|
39
78
|
| `filesync:init` | Create or add to `.gdrive-sync.json` config |
|
|
40
79
|
| `filesync:show` | Show sync configurations |
|
|
41
80
|
| `filesync:remove` | Remove a sync from config |
|
|
42
81
|
| `filesync:register` | Register local config to global registry |
|
|
43
82
|
| `filesync:unregister` | Remove from global registry |
|
|
83
|
+
| `filesync:migrate` | Migrate legacy syncs to Files Sync format |
|
|
44
84
|
|
|
45
85
|
### Drive Operations
|
|
46
86
|
|
|
@@ -51,7 +91,9 @@ Run without arguments for interactive menu, or use direct commands.
|
|
|
51
91
|
| `drive:mkdir` | Create a directory in Drive |
|
|
52
92
|
| `drive:delete` | Delete a file/folder from Drive |
|
|
53
93
|
|
|
54
|
-
### Legacy Sync (gdrive
|
|
94
|
+
### Legacy Sync (gdrive@2 only)
|
|
95
|
+
|
|
96
|
+
**Note:** These commands require gdrive@2. They are not available with gdrive@3.
|
|
55
97
|
|
|
56
98
|
| Command | Description |
|
|
57
99
|
|---------|-------------|
|
|
@@ -213,7 +255,9 @@ gdrive-syncer filesync:diff
|
|
|
213
255
|
4. Multi-select which projects to sync
|
|
214
256
|
5. Operation runs on all selected projects
|
|
215
257
|
|
|
216
|
-
## Legacy Sync Configuration
|
|
258
|
+
## Legacy Sync Configuration (gdrive@2 only)
|
|
259
|
+
|
|
260
|
+
> **Note:** Legacy sync requires gdrive@2. If you have gdrive@3 installed, use Files Sync (`filesync:*` commands) instead.
|
|
217
261
|
|
|
218
262
|
Legacy sync uses the `gdrive sync upload` command under the hood. Configuration is stored globally at:
|
|
219
263
|
|
|
@@ -287,10 +331,30 @@ gdrive-syncer sync:upload
|
|
|
287
331
|
gdrive-syncer sync:list
|
|
288
332
|
```
|
|
289
333
|
|
|
334
|
+
## Migrating from Legacy Sync to Files Sync
|
|
335
|
+
|
|
336
|
+
If you have existing legacy sync configurations (`~/.gdrive_syncer/gdrive.config.json`), you can migrate them to the new Files Sync format:
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
gdrive-syncer filesync:migrate
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The migration wizard:
|
|
343
|
+
1. Shows all legacy syncs and lets you select which to migrate
|
|
344
|
+
2. For each sync, asks whether to save to **Local** or **Global** config
|
|
345
|
+
3. For local configs, calculates the relative path from the config location
|
|
346
|
+
4. Warns if the path is outside the project directory (suggests using global instead)
|
|
347
|
+
5. Lets you set a file pattern (default: `*` for all files)
|
|
348
|
+
6. Shows a preview before confirming
|
|
349
|
+
7. Optionally removes migrated syncs from the legacy config
|
|
350
|
+
|
|
351
|
+
This is not a one-time migration—you can run it multiple times to gradually migrate syncs as needed.
|
|
352
|
+
|
|
290
353
|
## Files Sync vs Legacy Sync
|
|
291
354
|
|
|
292
355
|
| Feature | Files Sync | Legacy Sync |
|
|
293
356
|
|---------|----------|-------------|
|
|
357
|
+
| **gdrive version** | gdrive@2 and gdrive@3 | gdrive@2 only |
|
|
294
358
|
| Config location | Local (`.gdrive-sync.json`) or Global (`~/.gdrive_syncer/env-sync.json`) | `~/.gdrive_syncer/gdrive.config.json` (global only) |
|
|
295
359
|
| Direction | Two-way (upload & download) | One-way (upload only) |
|
|
296
360
|
| File patterns | Supported (`.env.*`, `*`) | All files in folder |
|
|
@@ -318,6 +382,9 @@ gdrive-syncer filesync:diff local
|
|
|
318
382
|
# Show differences for global config only
|
|
319
383
|
gdrive-syncer filesync:diff global
|
|
320
384
|
|
|
385
|
+
# Show differences for registered local configs
|
|
386
|
+
gdrive-syncer filesync:diff registered
|
|
387
|
+
|
|
321
388
|
# Download from Drive (with backup)
|
|
322
389
|
gdrive-syncer filesync:download
|
|
323
390
|
gdrive-syncer filesync:download local
|
|
@@ -326,6 +393,9 @@ gdrive-syncer filesync:download local
|
|
|
326
393
|
gdrive-syncer filesync:upload
|
|
327
394
|
gdrive-syncer filesync:upload global
|
|
328
395
|
|
|
396
|
+
# Upload from all registered configs
|
|
397
|
+
gdrive-syncer filesync:upload registered
|
|
398
|
+
|
|
329
399
|
# Initialize config (choose Local or Global)
|
|
330
400
|
gdrive-syncer filesync:init
|
|
331
401
|
|
package/package.json
CHANGED
package/run.js
CHANGED
|
@@ -5,15 +5,15 @@ const color = require('picocolors');
|
|
|
5
5
|
const { cfgAdd, cfgRm, cfgShow } = require('./src/cfgManager');
|
|
6
6
|
const { runSync } = require('./src/sync');
|
|
7
7
|
const { runList, runSearch, runDelete, runMkdir, runListSync } = require('./src/list');
|
|
8
|
-
const { envInit, envRun, envShow, envRemove, envRegister, envUnregister } = require('./src/envSync');
|
|
8
|
+
const { envInit, envRun, envShow, envRemove, envRegister, envUnregister, envMigrate } = require('./src/envSync');
|
|
9
9
|
|
|
10
10
|
const [, , ...args] = process.argv;
|
|
11
11
|
const [firstArg] = args;
|
|
12
12
|
|
|
13
|
-
// Get config type from args (local/global)
|
|
13
|
+
// Get config type from args (local/global/registered)
|
|
14
14
|
const getConfigType = () => {
|
|
15
15
|
const arg = args[1]?.toLowerCase();
|
|
16
|
-
if (arg === 'local' || arg === 'global') return arg;
|
|
16
|
+
if (arg === 'local' || arg === 'global' || arg === 'registered') return arg;
|
|
17
17
|
return null;
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -29,6 +29,7 @@ const commands = {
|
|
|
29
29
|
'filesync:remove': { handler: envRemove, desc: 'Remove sync config' },
|
|
30
30
|
'filesync:register': { handler: envRegister, desc: 'Register local config to global' },
|
|
31
31
|
'filesync:unregister': { handler: envUnregister, desc: 'Unregister local config' },
|
|
32
|
+
'filesync:migrate': { handler: envMigrate, desc: 'Migrate legacy syncs to Files Sync' },
|
|
32
33
|
|
|
33
34
|
// Drive Operations
|
|
34
35
|
'drive:search': { handler: runSearch, desc: 'Search files in Drive' },
|
|
@@ -49,14 +50,15 @@ const showHelp = () => {
|
|
|
49
50
|
const lines = [
|
|
50
51
|
`${color.bold('Files Sync')} ${color.dim('(two-way sync with .gdrive-sync.json)')}`,
|
|
51
52
|
` filesync ${color.dim('Interactive sync menu')}`,
|
|
52
|
-
` filesync:diff [local|global] ${color.dim('Show differences')}`,
|
|
53
|
-
` filesync:download [local|global] ${color.dim('Download from Drive')}`,
|
|
54
|
-
` filesync:upload [local|global] ${color.dim('Upload to Drive')}`,
|
|
53
|
+
` filesync:diff [local|global|registered] ${color.dim('Show differences')}`,
|
|
54
|
+
` filesync:download [local|global|registered] ${color.dim('Download from Drive')}`,
|
|
55
|
+
` filesync:upload [local|global|registered] ${color.dim('Upload to Drive')}`,
|
|
55
56
|
` filesync:init ${color.dim('Create/add sync config')}`,
|
|
56
57
|
` filesync:show ${color.dim('Show configurations')}`,
|
|
57
58
|
` filesync:remove ${color.dim('Remove sync config')}`,
|
|
58
59
|
` filesync:register ${color.dim('Register local config to global')}`,
|
|
59
60
|
` filesync:unregister ${color.dim('Unregister local config')}`,
|
|
61
|
+
` filesync:migrate ${color.dim('Migrate legacy syncs to Files Sync')}`,
|
|
60
62
|
``,
|
|
61
63
|
`${color.bold('Drive Operations')}`,
|
|
62
64
|
` drive:search ${color.dim('Search files')}`,
|
|
@@ -136,6 +138,7 @@ const showHelp = () => {
|
|
|
136
138
|
{ value: 'filesync:remove', label: 'Remove', hint: 'Remove a sync from config' },
|
|
137
139
|
{ value: 'filesync:register', label: 'Register', hint: 'Register local config to global' },
|
|
138
140
|
{ value: 'filesync:unregister', label: 'Unregister', hint: 'Unregister local config' },
|
|
141
|
+
{ value: 'filesync:migrate', label: 'Migrate', hint: 'Migrate legacy syncs to Files Sync' },
|
|
139
142
|
],
|
|
140
143
|
});
|
|
141
144
|
} else if (category === 'drive') {
|
|
@@ -158,6 +161,7 @@ const showHelp = () => {
|
|
|
158
161
|
{ value: 'sync:show', label: 'Show Config', hint: 'View sync configurations' },
|
|
159
162
|
{ value: 'sync:add', label: 'Add Config', hint: 'Add a new sync configuration' },
|
|
160
163
|
{ value: 'sync:remove', label: 'Remove Config', hint: 'Remove a sync configuration' },
|
|
164
|
+
{ value: 'filesync:migrate', label: 'Migrate to Files Sync', hint: 'Migrate to new format' },
|
|
161
165
|
],
|
|
162
166
|
});
|
|
163
167
|
}
|
package/src/envSync.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const shell = require('shelljs');
|
|
4
4
|
const fs = require('fs-extra');
|
|
5
|
+
const gdrive = require('./gdriveCmd');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
const os = require('os');
|
|
7
8
|
const { select, multiselect, text, isCancel, cancel, spinner, confirm, note, log } = require('@clack/prompts');
|
|
@@ -739,6 +740,10 @@ const envRun = async (presetAction, presetConfigType) => {
|
|
|
739
740
|
log.error('No syncs in global config.');
|
|
740
741
|
return;
|
|
741
742
|
}
|
|
743
|
+
if (presetConfigType === 'registered' && registeredRefs.length === 0) {
|
|
744
|
+
log.error('No registered local configs found.');
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
742
747
|
configType = presetConfigType;
|
|
743
748
|
log.info(`Using ${configType} config`);
|
|
744
749
|
} else {
|
|
@@ -907,8 +912,9 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
|
|
|
907
912
|
const s = spinner();
|
|
908
913
|
s.start('Fetching files from Google Drive...');
|
|
909
914
|
|
|
910
|
-
const listResult =
|
|
911
|
-
|
|
915
|
+
const listResult = gdrive.list({
|
|
916
|
+
query: `'${folderId}' in parents`,
|
|
917
|
+
noHeader: true,
|
|
912
918
|
});
|
|
913
919
|
|
|
914
920
|
if (listResult.code !== 0) {
|
|
@@ -921,24 +927,18 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
|
|
|
921
927
|
const driveFiles = [];
|
|
922
928
|
const stdout = listResult.stdout.trim();
|
|
923
929
|
if (stdout) {
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
if (parts.length >= 2) {
|
|
929
|
-
const fileId = parts[0].trim();
|
|
930
|
-
const fileName = parts[1].trim();
|
|
931
|
-
if (matchPattern(fileName, pattern, ignore)) {
|
|
932
|
-
driveFiles.push({ id: fileId, name: fileName });
|
|
933
|
-
}
|
|
930
|
+
const parsed = gdrive.parseListOutput(stdout);
|
|
931
|
+
for (const file of parsed) {
|
|
932
|
+
if (file.id && file.name && matchPattern(file.name, pattern, ignore)) {
|
|
933
|
+
driveFiles.push({ id: file.id, name: file.name });
|
|
934
934
|
}
|
|
935
|
-
}
|
|
935
|
+
}
|
|
936
936
|
}
|
|
937
937
|
|
|
938
938
|
// Download Drive files to temp for comparison
|
|
939
939
|
s.message('Downloading Drive files for comparison...');
|
|
940
940
|
for (const file of driveFiles) {
|
|
941
|
-
|
|
941
|
+
gdrive.download(file.id, { destination: tempDir, overwrite: true });
|
|
942
942
|
}
|
|
943
943
|
s.stop(color.green(`Found ${driveFiles.length} matching file(s) on Drive`));
|
|
944
944
|
|
|
@@ -1102,17 +1102,13 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
|
|
|
1102
1102
|
for (const filename of changes.modified) {
|
|
1103
1103
|
const driveFile = driveFiles.find((f) => f.name === filename);
|
|
1104
1104
|
if (driveFile) {
|
|
1105
|
-
|
|
1106
|
-
silent: true,
|
|
1107
|
-
});
|
|
1105
|
+
gdrive.update(driveFile.id, path.join(envDir, filename));
|
|
1108
1106
|
replaced++;
|
|
1109
1107
|
}
|
|
1110
1108
|
}
|
|
1111
1109
|
|
|
1112
1110
|
for (const filename of changes.localOnly) {
|
|
1113
|
-
|
|
1114
|
-
silent: true,
|
|
1115
|
-
});
|
|
1111
|
+
gdrive.upload(path.join(envDir, filename), { parent: folderId });
|
|
1116
1112
|
uploaded++;
|
|
1117
1113
|
}
|
|
1118
1114
|
|
|
@@ -1125,6 +1121,221 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
|
|
|
1125
1121
|
}
|
|
1126
1122
|
};
|
|
1127
1123
|
|
|
1124
|
+
/**
|
|
1125
|
+
* Migrate legacy syncs to Files Sync config
|
|
1126
|
+
*/
|
|
1127
|
+
const envMigrate = async () => {
|
|
1128
|
+
try {
|
|
1129
|
+
const { paths } = require('./config');
|
|
1130
|
+
const legacyConfigPath = paths.cfgFile;
|
|
1131
|
+
|
|
1132
|
+
// Check if legacy config exists
|
|
1133
|
+
if (!fs.existsSync(legacyConfigPath)) {
|
|
1134
|
+
log.error('No legacy config found at ~/.gdrive_syncer/gdrive.config.json');
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
const legacyConfig = JSON.parse(fs.readFileSync(legacyConfigPath, 'utf-8'));
|
|
1139
|
+
const legacySyncs = legacyConfig.syncs || [];
|
|
1140
|
+
|
|
1141
|
+
if (legacySyncs.length === 0) {
|
|
1142
|
+
log.warn('No syncs found in legacy config.');
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// Show legacy syncs for selection
|
|
1147
|
+
const syncOptions = legacySyncs.map((s) => ({
|
|
1148
|
+
value: s.name,
|
|
1149
|
+
label: s.name,
|
|
1150
|
+
hint: `${s.fullpath} → ${s.driveId.slice(0, 12)}...`,
|
|
1151
|
+
}));
|
|
1152
|
+
|
|
1153
|
+
const selectedNames = await multiselect({
|
|
1154
|
+
message: 'Select syncs to migrate (space to toggle, enter to confirm)',
|
|
1155
|
+
options: syncOptions,
|
|
1156
|
+
required: true,
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
if (isCancel(selectedNames)) {
|
|
1160
|
+
cancel('Migration cancelled.');
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
const selectedSyncs = legacySyncs.filter((s) => selectedNames.includes(s.name));
|
|
1165
|
+
|
|
1166
|
+
// Process each selected sync
|
|
1167
|
+
for (const legacySync of selectedSyncs) {
|
|
1168
|
+
console.log('');
|
|
1169
|
+
note(
|
|
1170
|
+
`${color.cyan('Name:')} ${legacySync.name}\n${color.cyan('Path:')} ${legacySync.fullpath}\n${color.cyan('Drive ID:')} ${legacySync.driveId}`,
|
|
1171
|
+
`Migrating: ${legacySync.name}`
|
|
1172
|
+
);
|
|
1173
|
+
|
|
1174
|
+
// Ask: Local or Global?
|
|
1175
|
+
const destination = await select({
|
|
1176
|
+
message: `Where to migrate "${legacySync.name}"?`,
|
|
1177
|
+
options: [
|
|
1178
|
+
{ value: 'local', label: 'Local', hint: '.gdrive-sync.json in a project directory' },
|
|
1179
|
+
{ value: 'global', label: 'Global', hint: '~/.gdrive_syncer/env-sync.json' },
|
|
1180
|
+
],
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
if (isCancel(destination)) {
|
|
1184
|
+
cancel('Migration cancelled.');
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// Get pattern
|
|
1189
|
+
const pattern = await text({
|
|
1190
|
+
message: 'File pattern to sync',
|
|
1191
|
+
placeholder: '*',
|
|
1192
|
+
defaultValue: '*',
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
if (isCancel(pattern)) {
|
|
1196
|
+
cancel('Migration cancelled.');
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
let configPath;
|
|
1201
|
+
let localDir;
|
|
1202
|
+
let projectRoot;
|
|
1203
|
+
|
|
1204
|
+
if (destination === 'local') {
|
|
1205
|
+
// Ask where to create/use local config
|
|
1206
|
+
const configLocation = await text({
|
|
1207
|
+
message: 'Project root for local config (where .gdrive-sync.json will be)',
|
|
1208
|
+
placeholder: process.cwd(),
|
|
1209
|
+
defaultValue: process.cwd(),
|
|
1210
|
+
validate: (v) => {
|
|
1211
|
+
if (!v.trim()) return 'Path is required';
|
|
1212
|
+
if (!fs.existsSync(v.trim())) return 'Directory does not exist';
|
|
1213
|
+
},
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
if (isCancel(configLocation)) {
|
|
1217
|
+
cancel('Migration cancelled.');
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
projectRoot = configLocation.trim();
|
|
1222
|
+
configPath = path.join(projectRoot, LOCAL_CONFIG_FILE);
|
|
1223
|
+
|
|
1224
|
+
// Calculate relative path
|
|
1225
|
+
const relativePath = path.relative(projectRoot, legacySync.fullpath);
|
|
1226
|
+
|
|
1227
|
+
// Check if path goes outside project
|
|
1228
|
+
if (relativePath.startsWith('..')) {
|
|
1229
|
+
log.warn(`Path "${legacySync.fullpath}" is outside the project root.`);
|
|
1230
|
+
log.info(`Relative path would be: ${relativePath}`);
|
|
1231
|
+
|
|
1232
|
+
const proceed = await confirm({
|
|
1233
|
+
message: 'Use this relative path anyway? (No = switch to global)',
|
|
1234
|
+
initialValue: false,
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
if (isCancel(proceed)) {
|
|
1238
|
+
cancel('Migration cancelled.');
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
if (!proceed) {
|
|
1243
|
+
// Switch to global
|
|
1244
|
+
log.info('Switching to global config...');
|
|
1245
|
+
configPath = GLOBAL_CONFIG_FILE;
|
|
1246
|
+
localDir = legacySync.fullpath; // Use absolute path for global
|
|
1247
|
+
projectRoot = null;
|
|
1248
|
+
} else {
|
|
1249
|
+
localDir = relativePath;
|
|
1250
|
+
}
|
|
1251
|
+
} else {
|
|
1252
|
+
localDir = relativePath;
|
|
1253
|
+
}
|
|
1254
|
+
} else {
|
|
1255
|
+
// Global config - use absolute path
|
|
1256
|
+
configPath = GLOBAL_CONFIG_FILE;
|
|
1257
|
+
localDir = legacySync.fullpath;
|
|
1258
|
+
projectRoot = null;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// Preview the new sync entry
|
|
1262
|
+
const newSync = {
|
|
1263
|
+
name: legacySync.name,
|
|
1264
|
+
localDir: localDir,
|
|
1265
|
+
folderId: legacySync.driveId,
|
|
1266
|
+
pattern: pattern.trim(),
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
note(
|
|
1270
|
+
`${color.cyan('Name:')} ${newSync.name}\n${color.cyan('Local Dir:')} ${newSync.localDir}\n${color.cyan('Folder ID:')} ${newSync.folderId}\n${color.cyan('Pattern:')} ${newSync.pattern}\n${color.cyan('Config:')} ${configPath}`,
|
|
1271
|
+
'Preview'
|
|
1272
|
+
);
|
|
1273
|
+
|
|
1274
|
+
const confirmMigrate = await confirm({
|
|
1275
|
+
message: 'Add this sync to the config?',
|
|
1276
|
+
initialValue: true,
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
if (isCancel(confirmMigrate) || !confirmMigrate) {
|
|
1280
|
+
log.info(`Skipped "${legacySync.name}"`);
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// Load or create config
|
|
1285
|
+
let config = { ...defaultConfig };
|
|
1286
|
+
if (fs.existsSync(configPath)) {
|
|
1287
|
+
config = loadConfig(configPath);
|
|
1288
|
+
}
|
|
1289
|
+
if (!config.syncs) {
|
|
1290
|
+
config.syncs = [];
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// Check for duplicate name
|
|
1294
|
+
const existingIndex = config.syncs.findIndex((s) => s.name === newSync.name);
|
|
1295
|
+
if (existingIndex !== -1) {
|
|
1296
|
+
const overwrite = await confirm({
|
|
1297
|
+
message: `Sync "${newSync.name}" already exists in config. Overwrite?`,
|
|
1298
|
+
initialValue: false,
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
if (isCancel(overwrite)) {
|
|
1302
|
+
cancel('Migration cancelled.');
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
if (overwrite) {
|
|
1307
|
+
config.syncs[existingIndex] = newSync;
|
|
1308
|
+
} else {
|
|
1309
|
+
log.info(`Skipped "${legacySync.name}" (already exists)`);
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
} else {
|
|
1313
|
+
config.syncs.push(newSync);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
saveConfig(configPath, config);
|
|
1317
|
+
log.success(`Migrated "${legacySync.name}" to ${configPath === GLOBAL_CONFIG_FILE ? 'global' : 'local'} config`);
|
|
1318
|
+
|
|
1319
|
+
// Ask if user wants to remove from legacy config
|
|
1320
|
+
const removeFromLegacy = await confirm({
|
|
1321
|
+
message: 'Remove from legacy config?',
|
|
1322
|
+
initialValue: false,
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
if (!isCancel(removeFromLegacy) && removeFromLegacy) {
|
|
1326
|
+
legacyConfig.syncs = legacyConfig.syncs.filter((s) => s.name !== legacySync.name);
|
|
1327
|
+
fs.writeFileSync(legacyConfigPath, JSON.stringify(legacyConfig, null, 2));
|
|
1328
|
+
log.success(`Removed "${legacySync.name}" from legacy config`);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
console.log('');
|
|
1333
|
+
log.success('Migration complete!');
|
|
1334
|
+
} catch (e) {
|
|
1335
|
+
log.error(e.message);
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
|
|
1128
1339
|
module.exports = {
|
|
1129
1340
|
envInit,
|
|
1130
1341
|
envRun,
|
|
@@ -1132,6 +1343,7 @@ module.exports = {
|
|
|
1132
1343
|
envRemove,
|
|
1133
1344
|
envRegister,
|
|
1134
1345
|
envUnregister,
|
|
1346
|
+
envMigrate,
|
|
1135
1347
|
// Exported for testing
|
|
1136
1348
|
globToRegex,
|
|
1137
1349
|
matchPattern,
|
package/src/gdriveCmd.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const shell = require('shelljs');
|
|
4
|
+
|
|
5
|
+
let cachedVersion = null;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Detect gdrive version (2 or 3)
|
|
9
|
+
* gdrive@2: "gdrive v2.1.1"
|
|
10
|
+
* gdrive@3: "gdrive 3.9.1 ..."
|
|
11
|
+
*/
|
|
12
|
+
const detectVersion = () => {
|
|
13
|
+
if (cachedVersion !== null) {
|
|
14
|
+
return cachedVersion;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const result = shell.exec('gdrive version', { silent: true });
|
|
18
|
+
if (result.code !== 0) {
|
|
19
|
+
// Try gdrive about as fallback (gdrive@2)
|
|
20
|
+
const aboutResult = shell.exec('gdrive about', { silent: true });
|
|
21
|
+
if (aboutResult.code === 0) {
|
|
22
|
+
cachedVersion = 2;
|
|
23
|
+
return cachedVersion;
|
|
24
|
+
}
|
|
25
|
+
throw new Error('gdrive not found. Please install gdrive CLI.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const output = result.stdout.toLowerCase();
|
|
29
|
+
// gdrive@3 outputs: "gdrive 3.x.x ..."
|
|
30
|
+
// gdrive@2 outputs: "gdrive v2.x.x"
|
|
31
|
+
if (output.includes('v2.') || output.includes('gdrive 2.')) {
|
|
32
|
+
cachedVersion = 2;
|
|
33
|
+
} else if (output.match(/gdrive\s+3\./)) {
|
|
34
|
+
cachedVersion = 3;
|
|
35
|
+
} else {
|
|
36
|
+
// Default to 3 for newer versions
|
|
37
|
+
cachedVersion = 3;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return cachedVersion;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the detected gdrive version
|
|
45
|
+
*/
|
|
46
|
+
const getVersion = () => detectVersion();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if sync commands are available (only in gdrive@2)
|
|
50
|
+
*/
|
|
51
|
+
const hasSyncSupport = () => detectVersion() === 2;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* List files in Google Drive
|
|
55
|
+
* @param {Object} options
|
|
56
|
+
* @param {string} options.query - Search query
|
|
57
|
+
* @param {number} options.max - Max results
|
|
58
|
+
* @param {boolean} options.noHeader - Skip header row
|
|
59
|
+
* @param {boolean} options.absolute - Show absolute paths (v2 only)
|
|
60
|
+
* @param {string} options.parent - Parent folder ID (v3 only, alternative to query)
|
|
61
|
+
*/
|
|
62
|
+
const list = (options = {}) => {
|
|
63
|
+
const version = detectVersion();
|
|
64
|
+
const { query, max = 30, noHeader = false, absolute = false, parent } = options;
|
|
65
|
+
|
|
66
|
+
let cmd;
|
|
67
|
+
if (version === 2) {
|
|
68
|
+
cmd = 'gdrive list';
|
|
69
|
+
if (max) cmd += ` --max ${max}`;
|
|
70
|
+
if (query) cmd += ` --query "${query}"`;
|
|
71
|
+
if (noHeader) cmd += ' --no-header';
|
|
72
|
+
if (absolute) cmd += ' --absolute';
|
|
73
|
+
} else {
|
|
74
|
+
cmd = 'gdrive files list';
|
|
75
|
+
if (max) cmd += ` --max ${max}`;
|
|
76
|
+
if (parent) {
|
|
77
|
+
cmd += ` --parent ${parent}`;
|
|
78
|
+
} else if (query) {
|
|
79
|
+
cmd += ` --query "${query}"`;
|
|
80
|
+
}
|
|
81
|
+
if (noHeader) cmd += ' --skip-header';
|
|
82
|
+
// Note: --absolute not available in v3
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return shell.exec(cmd, { silent: true });
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Download a file from Google Drive
|
|
90
|
+
* @param {string} fileId - File ID to download
|
|
91
|
+
* @param {Object} options
|
|
92
|
+
* @param {string} options.destination - Download destination path
|
|
93
|
+
* @param {boolean} options.overwrite - Overwrite existing files
|
|
94
|
+
* @param {boolean} options.recursive - Download directory recursively
|
|
95
|
+
*/
|
|
96
|
+
const download = (fileId, options = {}) => {
|
|
97
|
+
const version = detectVersion();
|
|
98
|
+
const { destination, overwrite = false, recursive = false } = options;
|
|
99
|
+
|
|
100
|
+
let cmd;
|
|
101
|
+
if (version === 2) {
|
|
102
|
+
cmd = `gdrive download "${fileId}"`;
|
|
103
|
+
if (destination) cmd += ` --path "${destination}"`;
|
|
104
|
+
if (overwrite) cmd += ' --force';
|
|
105
|
+
if (recursive) cmd += ' -r';
|
|
106
|
+
} else {
|
|
107
|
+
cmd = `gdrive files download "${fileId}"`;
|
|
108
|
+
if (destination) cmd += ` --destination "${destination}"`;
|
|
109
|
+
if (overwrite) cmd += ' --overwrite';
|
|
110
|
+
if (recursive) cmd += ' --recursive';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return shell.exec(cmd, { silent: true });
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Upload a file to Google Drive
|
|
118
|
+
* @param {string} filePath - Local file path to upload
|
|
119
|
+
* @param {Object} options
|
|
120
|
+
* @param {string} options.parent - Parent folder ID
|
|
121
|
+
* @param {boolean} options.recursive - Upload directory recursively
|
|
122
|
+
*/
|
|
123
|
+
const upload = (filePath, options = {}) => {
|
|
124
|
+
const version = detectVersion();
|
|
125
|
+
const { parent, recursive = false } = options;
|
|
126
|
+
|
|
127
|
+
let cmd;
|
|
128
|
+
if (version === 2) {
|
|
129
|
+
cmd = `gdrive upload "${filePath}"`;
|
|
130
|
+
if (parent) cmd += ` --parent "${parent}"`;
|
|
131
|
+
if (recursive) cmd += ' -r';
|
|
132
|
+
} else {
|
|
133
|
+
cmd = `gdrive files upload "${filePath}"`;
|
|
134
|
+
if (parent) cmd += ` --parent "${parent}"`;
|
|
135
|
+
if (recursive) cmd += ' --recursive';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return shell.exec(cmd, { silent: true });
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Update an existing file on Google Drive
|
|
143
|
+
* @param {string} fileId - File ID to update
|
|
144
|
+
* @param {string} filePath - Local file path with new content
|
|
145
|
+
*/
|
|
146
|
+
const update = (fileId, filePath) => {
|
|
147
|
+
const version = detectVersion();
|
|
148
|
+
|
|
149
|
+
let cmd;
|
|
150
|
+
if (version === 2) {
|
|
151
|
+
cmd = `gdrive update "${fileId}" "${filePath}"`;
|
|
152
|
+
} else {
|
|
153
|
+
cmd = `gdrive files update "${fileId}" "${filePath}"`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return shell.exec(cmd, { silent: true });
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Create a directory on Google Drive
|
|
161
|
+
* @param {string} name - Directory name
|
|
162
|
+
* @param {Object} options
|
|
163
|
+
* @param {string} options.parent - Parent folder ID
|
|
164
|
+
*/
|
|
165
|
+
const mkdir = (name, options = {}) => {
|
|
166
|
+
const version = detectVersion();
|
|
167
|
+
const { parent } = options;
|
|
168
|
+
|
|
169
|
+
let cmd;
|
|
170
|
+
if (version === 2) {
|
|
171
|
+
if (parent) {
|
|
172
|
+
cmd = `gdrive mkdir -p ${parent} "${name}"`;
|
|
173
|
+
} else {
|
|
174
|
+
cmd = `gdrive mkdir "${name}"`;
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
cmd = `gdrive files mkdir "${name}"`;
|
|
178
|
+
if (parent) cmd += ` --parent ${parent}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return shell.exec(cmd, { silent: true });
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Delete a file or directory on Google Drive
|
|
186
|
+
* @param {string} fileId - File/folder ID to delete
|
|
187
|
+
* @param {Object} options
|
|
188
|
+
* @param {boolean} options.recursive - Delete directory recursively
|
|
189
|
+
*/
|
|
190
|
+
const remove = (fileId, options = {}) => {
|
|
191
|
+
const version = detectVersion();
|
|
192
|
+
const { recursive = false } = options;
|
|
193
|
+
|
|
194
|
+
let cmd;
|
|
195
|
+
if (version === 2) {
|
|
196
|
+
cmd = `gdrive delete ${fileId}`;
|
|
197
|
+
if (recursive) cmd += ' -r';
|
|
198
|
+
} else {
|
|
199
|
+
cmd = `gdrive files delete ${fileId}`;
|
|
200
|
+
if (recursive) cmd += ' --recursive';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return shell.exec(cmd, { silent: true });
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Sync upload (gdrive@2 only) - uploads local to drive
|
|
208
|
+
* @param {string} localPath - Local directory path
|
|
209
|
+
* @param {string} driveId - Drive folder ID
|
|
210
|
+
* @param {Object} options
|
|
211
|
+
* @param {boolean} options.dryRun - Preview changes without applying
|
|
212
|
+
* @param {boolean} options.keepLocal - Don't delete local files
|
|
213
|
+
* @param {boolean} options.deleteExtraneous - Delete files on drive not in local
|
|
214
|
+
*/
|
|
215
|
+
const syncUpload = (localPath, driveId, options = {}) => {
|
|
216
|
+
const version = detectVersion();
|
|
217
|
+
const { dryRun = false, keepLocal = true, deleteExtraneous = false } = options;
|
|
218
|
+
|
|
219
|
+
if (version !== 2) {
|
|
220
|
+
return {
|
|
221
|
+
code: 1,
|
|
222
|
+
stdout: '',
|
|
223
|
+
stderr: 'gdrive sync commands are not available in gdrive@3. Use envSync instead.',
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let cmd = `gdrive sync upload`;
|
|
228
|
+
if (dryRun) cmd += ' --dry-run';
|
|
229
|
+
if (keepLocal) cmd += ' --keep-local';
|
|
230
|
+
if (deleteExtraneous) cmd += ' --delete-extraneous';
|
|
231
|
+
cmd += ` "${localPath}" ${driveId}`;
|
|
232
|
+
|
|
233
|
+
return shell.exec(cmd, { silent: true });
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* List sync tasks (gdrive@2 only)
|
|
238
|
+
*/
|
|
239
|
+
const syncList = () => {
|
|
240
|
+
const version = detectVersion();
|
|
241
|
+
|
|
242
|
+
if (version !== 2) {
|
|
243
|
+
return {
|
|
244
|
+
code: 1,
|
|
245
|
+
stdout: '',
|
|
246
|
+
stderr: 'gdrive sync commands are not available in gdrive@3.',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return shell.exec('gdrive sync list', { silent: true });
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Parse list output into structured data
|
|
255
|
+
* Works with both v2 and v3 output formats
|
|
256
|
+
* @param {string} stdout - Raw stdout from list command
|
|
257
|
+
* @returns {Array<{id: string, name: string, type: string, size: string, date: string}>}
|
|
258
|
+
*/
|
|
259
|
+
const parseListOutput = (stdout) => {
|
|
260
|
+
const lines = stdout.trim().split('\n').filter((line) => line.trim());
|
|
261
|
+
|
|
262
|
+
if (lines.length === 0) return [];
|
|
263
|
+
|
|
264
|
+
// Both v2 and v3 use space-padded columns for alignment
|
|
265
|
+
// Split by 2+ whitespace characters to handle this
|
|
266
|
+
const separator = /\s{2,}/;
|
|
267
|
+
|
|
268
|
+
return lines.map((line) => {
|
|
269
|
+
const parts = line.trim().split(separator);
|
|
270
|
+
return {
|
|
271
|
+
id: parts[0] || '',
|
|
272
|
+
name: parts[1] || '',
|
|
273
|
+
type: parts[2] || '',
|
|
274
|
+
size: parts[3] || '',
|
|
275
|
+
date: parts[4] || '',
|
|
276
|
+
};
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Clear the cached version (useful for testing)
|
|
282
|
+
*/
|
|
283
|
+
const clearCache = () => {
|
|
284
|
+
cachedVersion = null;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
module.exports = {
|
|
288
|
+
detectVersion,
|
|
289
|
+
getVersion,
|
|
290
|
+
hasSyncSupport,
|
|
291
|
+
list,
|
|
292
|
+
download,
|
|
293
|
+
upload,
|
|
294
|
+
update,
|
|
295
|
+
mkdir,
|
|
296
|
+
remove,
|
|
297
|
+
syncUpload,
|
|
298
|
+
syncList,
|
|
299
|
+
parseListOutput,
|
|
300
|
+
clearCache,
|
|
301
|
+
};
|
package/src/list.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const shell = require('shelljs');
|
|
4
4
|
const { text, isCancel, cancel, spinner, confirm, log } = require('@clack/prompts');
|
|
5
5
|
const color = require('picocolors');
|
|
6
|
+
const gdrive = require('./gdriveCmd');
|
|
6
7
|
|
|
7
8
|
const runSearch = async () => {
|
|
8
9
|
try {
|
|
@@ -38,10 +39,11 @@ const runSearch = async () => {
|
|
|
38
39
|
const s = spinner();
|
|
39
40
|
s.start(`Searching for "${search}"...`);
|
|
40
41
|
|
|
41
|
-
const result =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
const result = gdrive.list({
|
|
43
|
+
max: maxResults,
|
|
44
|
+
query: `name contains '${search.trim()}'`,
|
|
45
|
+
absolute: true, // Only works with gdrive@2
|
|
46
|
+
});
|
|
45
47
|
|
|
46
48
|
s.stop(color.green('Search complete!'));
|
|
47
49
|
console.log(result.stdout);
|
|
@@ -88,10 +90,11 @@ const runList = async () => {
|
|
|
88
90
|
const s = spinner();
|
|
89
91
|
s.start('Fetching folder contents...');
|
|
90
92
|
|
|
91
|
-
const result =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
const result = gdrive.list({
|
|
94
|
+
max: maxResults,
|
|
95
|
+
query: `parents in '${driveId.trim()}'`,
|
|
96
|
+
absolute: true, // Only works with gdrive@2
|
|
97
|
+
});
|
|
95
98
|
|
|
96
99
|
s.stop(color.green('Done!'));
|
|
97
100
|
console.log(result.stdout);
|
|
@@ -106,10 +109,16 @@ const runList = async () => {
|
|
|
106
109
|
|
|
107
110
|
const runListSync = async () => {
|
|
108
111
|
try {
|
|
112
|
+
if (!gdrive.hasSyncSupport()) {
|
|
113
|
+
log.warn('gdrive sync commands are not available in gdrive@3.');
|
|
114
|
+
log.info('Use "gdrive-syncer env" for file sync functionality instead.');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
109
118
|
const s = spinner();
|
|
110
119
|
s.start('Fetching sync list...');
|
|
111
120
|
|
|
112
|
-
const result =
|
|
121
|
+
const result = gdrive.syncList();
|
|
113
122
|
|
|
114
123
|
s.stop(color.green('Done!'));
|
|
115
124
|
console.log(result.stdout);
|
|
@@ -153,7 +162,7 @@ const runMkdir = async () => {
|
|
|
153
162
|
const s = spinner();
|
|
154
163
|
s.start(`Creating folder "${folder}"...`);
|
|
155
164
|
|
|
156
|
-
const result =
|
|
165
|
+
const result = gdrive.mkdir(folder.trim(), { parent: parentId.trim() });
|
|
157
166
|
|
|
158
167
|
s.stop(color.green(`Folder "${folder}" created!`));
|
|
159
168
|
console.log(result.stdout);
|
|
@@ -194,7 +203,7 @@ const runDelete = async () => {
|
|
|
194
203
|
const s = spinner();
|
|
195
204
|
s.start('Deleting...');
|
|
196
205
|
|
|
197
|
-
const result =
|
|
206
|
+
const result = gdrive.remove(driveId.trim(), { recursive: true });
|
|
198
207
|
|
|
199
208
|
s.stop(color.green('Deleted successfully!'));
|
|
200
209
|
console.log(result.stdout);
|
package/src/sync.js
CHANGED
|
@@ -6,12 +6,10 @@ const os = require('os');
|
|
|
6
6
|
const { select, isCancel, cancel, spinner, note, log, confirm } = require('@clack/prompts');
|
|
7
7
|
const color = require('picocolors');
|
|
8
8
|
const { getCfgFile } = require('./helpers');
|
|
9
|
+
const gdrive = require('./gdriveCmd');
|
|
9
10
|
|
|
10
11
|
const homedir = os.homedir();
|
|
11
12
|
|
|
12
|
-
const getQuery = ({ dry, pth, dId }) =>
|
|
13
|
-
`gdrive sync upload ${dry ? '--dry-run' : ''} --keep-local --delete-extraneous ${pth} ${dId}`;
|
|
14
|
-
|
|
15
13
|
const syncOne = (cfg, dryRun) => {
|
|
16
14
|
const fullpath = cfg.fullpath;
|
|
17
15
|
const driveId = cfg.driveId;
|
|
@@ -29,7 +27,14 @@ const syncOne = (cfg, dryRun) => {
|
|
|
29
27
|
return;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
const result = gdrive.syncUpload(fullpath, driveId, {
|
|
31
|
+
dryRun: !!dryRun,
|
|
32
|
+
keepLocal: true,
|
|
33
|
+
deleteExtraneous: true,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (result.stdout) console.log(result.stdout);
|
|
37
|
+
if (result.stderr) log.error(result.stderr);
|
|
33
38
|
};
|
|
34
39
|
|
|
35
40
|
const syncAll = (syncs, dryRun) => {
|
|
@@ -44,6 +49,16 @@ const syncAll = (syncs, dryRun) => {
|
|
|
44
49
|
|
|
45
50
|
const runSync = async (dryRun) => {
|
|
46
51
|
try {
|
|
52
|
+
// Check if sync commands are available (gdrive@2 only)
|
|
53
|
+
if (!gdrive.hasSyncSupport()) {
|
|
54
|
+
log.error('gdrive sync commands are not available in gdrive@3.');
|
|
55
|
+
log.info('');
|
|
56
|
+
log.info('Options:');
|
|
57
|
+
log.info(' 1. Use "gdrive-syncer env" for file sync functionality (recommended)');
|
|
58
|
+
log.info(' 2. Install gdrive@2 if you need legacy sync support');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
47
62
|
note(
|
|
48
63
|
dryRun ? color.yellow('Running Dry Run') : color.green('Actual Sync'),
|
|
49
64
|
dryRun ? 'Preview Mode' : 'Upload Mode'
|
package/gdrive.config.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"syncs": [
|
|
3
|
-
{
|
|
4
|
-
"localpath": "project_envs/php-sayhey",
|
|
5
|
-
"driveId": "1jN1oOZKETwQL4FyoEoCCLlqBwLmYJLRd"
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
"localpath": "project_envs/sayhey",
|
|
9
|
-
"driveId": "1movI2Vgu2OMFRUl_aWvJqeQ65c_LZJG8"
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"localpath": "project_envs/pipeline-test",
|
|
13
|
-
"driveId": "19rP3yIsLNQ2V4ViahgGyK-BJu60W6rEp"
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|