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.
@@ -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 = shell.exec(
42
- `gdrive list --max ${maxResults} --query "name contains '${search.trim()}'" --absolute`,
43
- { silent: true }
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 = shell.exec(
92
- `gdrive list --max ${maxResults} --query "parents in '${driveId.trim()}'" --absolute`,
93
- { silent: true }
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 = shell.exec('gdrive sync list', { silent: true });
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 = shell.exec(`gdrive mkdir -p ${parentId.trim()} ${folder.trim()}`, { silent: true });
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 = shell.exec(`gdrive delete -r ${driveId.trim()}`, { silent: true });
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
- shell.exec(getQuery({ dry: !!dryRun, dId: driveId, pth: fullpath }));
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'
@@ -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
- }