gdrive-syncer 2.2.0 → 3.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/Readme.md +101 -11
- package/package.json +1 -1
- package/run.js +10 -6
- package/src/envSync.js +649 -83
- 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/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
|
-
}
|