@gadgetinc/ggt 0.2.0 → 0.2.2
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 +6 -6
- package/bin/dev.js +2 -2
- package/bin/run.js +1 -1
- package/lib/__generated__/graphql.js.map +1 -1
- package/lib/commands/help.js +3 -3
- package/lib/commands/help.js.map +1 -1
- package/lib/commands/list.js +4 -4
- package/lib/commands/list.js.map +1 -1
- package/lib/commands/login.js +2 -2
- package/lib/commands/login.js.map +1 -1
- package/lib/commands/logout.js +2 -2
- package/lib/commands/logout.js.map +1 -1
- package/lib/commands/sync.js +239 -83
- package/lib/commands/sync.js.map +1 -1
- package/lib/commands/whoami.js +2 -2
- package/lib/commands/whoami.js.map +1 -1
- package/lib/{utils → services}/base-command.js +25 -10
- package/lib/services/base-command.js.map +1 -0
- package/lib/{utils → services}/client.js +70 -21
- package/lib/services/client.js.map +1 -0
- package/lib/{utils → services}/context.js +42 -27
- package/lib/services/context.js.map +1 -0
- package/lib/{utils → services}/errors.js +8 -8
- package/lib/services/errors.js.map +1 -0
- package/lib/{utils → services}/flags.js +4 -3
- package/lib/services/flags.js.map +1 -0
- package/lib/{utils → services}/fs-utils.js +6 -2
- package/lib/services/fs-utils.js.map +1 -0
- package/lib/{utils → services}/help.js +1 -1
- package/lib/services/help.js.map +1 -0
- package/lib/services/promise.js.map +1 -0
- package/lib/{utils → services}/sleep.js +6 -2
- package/lib/services/sleep.js.map +1 -0
- package/npm-shrinkwrap.json +2378 -2704
- package/oclif.manifest.json +35 -15
- package/package.json +35 -34
- package/lib/utils/base-command.js.map +0 -1
- package/lib/utils/client.js.map +0 -1
- package/lib/utils/context.js.map +0 -1
- package/lib/utils/errors.js.map +0 -1
- package/lib/utils/flags.js.map +0 -1
- package/lib/utils/fs-utils.js.map +0 -1
- package/lib/utils/help.js.map +0 -1
- package/lib/utils/promise.js.map +0 -1
- package/lib/utils/sleep.js.map +0 -1
- /package/lib/{utils → services}/promise.js +0 -0
package/lib/commands/sync.js
CHANGED
|
@@ -5,7 +5,6 @@ import { _ as _define_property } from "@swc/helpers/_/_define_property";
|
|
|
5
5
|
import { Args, Flags } from "@oclif/core";
|
|
6
6
|
import assert from "assert";
|
|
7
7
|
import chalkTemplate from "chalk-template";
|
|
8
|
-
import { FSWatcher } from "chokidar";
|
|
9
8
|
import { format } from "date-fns";
|
|
10
9
|
import { execa } from "execa";
|
|
11
10
|
import fs from "fs-extra";
|
|
@@ -17,15 +16,16 @@ import PQueue from "p-queue";
|
|
|
17
16
|
import path from "path";
|
|
18
17
|
import pluralize from "pluralize";
|
|
19
18
|
import { dedent } from "ts-dedent";
|
|
19
|
+
import FSWatcher from "watcher";
|
|
20
20
|
import which from "which";
|
|
21
21
|
import { FileSyncEncoding } from "../__generated__/graphql.js";
|
|
22
|
-
import { BaseCommand } from "../
|
|
23
|
-
import { Client } from "../
|
|
24
|
-
import { context } from "../
|
|
25
|
-
import { InvalidSyncAppFlagError, InvalidSyncFileError, YarnNotFoundError } from "../
|
|
26
|
-
import { app } from "../
|
|
27
|
-
import { FSIgnorer, ignoreEnoent, isEmptyDir, walkDir } from "../
|
|
28
|
-
import { PromiseSignal } from "../
|
|
22
|
+
import { BaseCommand } from "../services/base-command.js";
|
|
23
|
+
import { Client } from "../services/client.js";
|
|
24
|
+
import { context } from "../services/context.js";
|
|
25
|
+
import { InvalidSyncAppFlagError, InvalidSyncFileError, YarnNotFoundError } from "../services/errors.js";
|
|
26
|
+
import { app } from "../services/flags.js";
|
|
27
|
+
import { FSIgnorer, ignoreEnoent, isEmptyDir, walkDir } from "../services/fs-utils.js";
|
|
28
|
+
import { PromiseSignal } from "../services/promise.js";
|
|
29
29
|
class Sync extends BaseCommand {
|
|
30
30
|
/**
|
|
31
31
|
* Turns an absolute filepath into a relative one from {@linkcode dir}.
|
|
@@ -67,10 +67,10 @@ class Sync extends BaseCommand {
|
|
|
67
67
|
* @param changed The normalized paths that have changed.
|
|
68
68
|
* @param deleted The normalized paths that have been deleted.
|
|
69
69
|
* @param options.limit The maximum number of lines to print. Defaults to 10. If debug is enabled, this is ignored.
|
|
70
|
-
*/ logPaths(prefix, changed, deleted, { limit =10
|
|
70
|
+
*/ logPaths(prefix, changed, deleted, { limit = 10 } = {}) {
|
|
71
71
|
const lines = _.sortBy([
|
|
72
|
-
...
|
|
73
|
-
...
|
|
72
|
+
..._.map(changed, (normalizedPath)=>chalkTemplate`{green ${prefix}} ${normalizedPath} {gray (changed)}`),
|
|
73
|
+
..._.map(deleted, (normalizedPath)=>chalkTemplate`{red ${prefix}} ${normalizedPath} {gray (deleted)}`)
|
|
74
74
|
], (line)=>line.slice(line.indexOf(" ") + 1));
|
|
75
75
|
let logged = 0;
|
|
76
76
|
for (const line of lines){
|
|
@@ -92,16 +92,21 @@ class Sync extends BaseCommand {
|
|
|
92
92
|
* - Prompts the user how to resolve conflicts if the local filesystem has changed since the last sync.
|
|
93
93
|
*/ async init() {
|
|
94
94
|
await super.init();
|
|
95
|
-
|
|
96
|
-
this.dir = this.config.windows && this.args["directory"].startsWith("~/") ? path.join(this.config.home, this.args["directory"].slice(2)) : path.resolve(this.args["directory"]);
|
|
95
|
+
this.dir = this.config.windows && _.startsWith(this.args.directory, "~/") ? path.join(this.config.home, this.args.directory.slice(2)) : path.resolve(this.args.directory);
|
|
97
96
|
const getApp = async ()=>{
|
|
98
|
-
if (this.flags.app)
|
|
99
|
-
|
|
97
|
+
if (this.flags.app) {
|
|
98
|
+
return this.flags.app;
|
|
99
|
+
}
|
|
100
|
+
// this.state can be undefined if the user is running `ggt sync` for the first time
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
102
|
+
if (this.state?.app) {
|
|
103
|
+
return this.state.app;
|
|
104
|
+
}
|
|
100
105
|
const selected = await inquirer.prompt({
|
|
101
106
|
type: "list",
|
|
102
107
|
name: "app",
|
|
103
108
|
message: "Please select the app to sync to.",
|
|
104
|
-
choices: await context.getAvailableApps().then((apps)=>
|
|
109
|
+
choices: await context.getAvailableApps().then((apps)=>_.map(apps, "slug"))
|
|
105
110
|
});
|
|
106
111
|
return selected.app;
|
|
107
112
|
};
|
|
@@ -134,26 +139,13 @@ class Sync extends BaseCommand {
|
|
|
134
139
|
".gadget",
|
|
135
140
|
".git"
|
|
136
141
|
]);
|
|
137
|
-
this.watcher = new FSWatcher({
|
|
138
|
-
ignored: (filepath)=>this.ignorer.ignores(filepath),
|
|
139
|
-
// don't emit an event for every watched file on boot
|
|
140
|
-
ignoreInitial: true,
|
|
141
|
-
// make sure stats are always present on add/change events
|
|
142
|
-
alwaysStat: true,
|
|
143
|
-
// wait for the entire file to be written before emitting add/change events
|
|
144
|
-
awaitWriteFinish: {
|
|
145
|
-
pollInterval: this.flags["file-poll-interval"],
|
|
146
|
-
stabilityThreshold: this.flags["file-stability-threshold"]
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
this.debug("starting");
|
|
150
142
|
if (!which.sync("yarn", {
|
|
151
143
|
nothrow: true
|
|
152
144
|
})) {
|
|
153
145
|
throw new YarnNotFoundError();
|
|
154
146
|
}
|
|
155
147
|
await fs.ensureDir(this.dir);
|
|
156
|
-
const { remoteFilesVersion
|
|
148
|
+
const { remoteFilesVersion } = await this.client.queryUnwrap({
|
|
157
149
|
query: REMOTE_FILES_VERSION_QUERY
|
|
158
150
|
});
|
|
159
151
|
const hasRemoteChanges = BigInt(remoteFilesVersion) > BigInt(this.state.filesVersion);
|
|
@@ -171,7 +163,7 @@ class Sync extends BaseCommand {
|
|
|
171
163
|
files.delete("/");
|
|
172
164
|
return files;
|
|
173
165
|
};
|
|
174
|
-
|
|
166
|
+
let changedFiles = await getChangedFiles();
|
|
175
167
|
const hasLocalChanges = changedFiles.size > 0;
|
|
176
168
|
if (hasLocalChanges) {
|
|
177
169
|
this.log("Local files have changed since you last synced");
|
|
@@ -180,15 +172,20 @@ class Sync extends BaseCommand {
|
|
|
180
172
|
});
|
|
181
173
|
this.log();
|
|
182
174
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
175
|
+
context.addBreadcrumb({
|
|
176
|
+
category: "sync",
|
|
177
|
+
message: "Initializing",
|
|
178
|
+
data: {
|
|
179
|
+
state: this.state,
|
|
180
|
+
remoteFilesVersion,
|
|
181
|
+
hasRemoteChanges,
|
|
182
|
+
hasLocalChanges,
|
|
183
|
+
changed: Array.from(changedFiles.keys())
|
|
184
|
+
}
|
|
188
185
|
});
|
|
189
186
|
let action;
|
|
190
187
|
if (hasLocalChanges) {
|
|
191
|
-
({ action
|
|
188
|
+
({ action } = await inquirer.prompt({
|
|
192
189
|
type: "list",
|
|
193
190
|
name: "action",
|
|
194
191
|
choices: [
|
|
@@ -199,11 +196,20 @@ class Sync extends BaseCommand {
|
|
|
199
196
|
message: hasRemoteChanges ? "Remote files have also changed. How would you like to proceed?" : "How would you like to proceed?"
|
|
200
197
|
}));
|
|
201
198
|
}
|
|
199
|
+
// get all the changed files again in case more changed
|
|
200
|
+
changedFiles = await getChangedFiles();
|
|
202
201
|
switch(action){
|
|
203
202
|
case Action.MERGE:
|
|
204
203
|
{
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
context.addBreadcrumb({
|
|
205
|
+
category: "sync",
|
|
206
|
+
message: "Merging local changes",
|
|
207
|
+
data: {
|
|
208
|
+
state: this.state,
|
|
209
|
+
remoteFilesVersion,
|
|
210
|
+
changed: Array.from(changedFiles.keys())
|
|
211
|
+
}
|
|
212
|
+
});
|
|
207
213
|
// We purposefully don't set the returned remoteFilesVersion here because we haven't received the remote changes
|
|
208
214
|
// yet. This will cause us to receive the local files that we just published + the remote files that were
|
|
209
215
|
// changed since the last sync.
|
|
@@ -212,7 +218,7 @@ class Sync extends BaseCommand {
|
|
|
212
218
|
variables: {
|
|
213
219
|
input: {
|
|
214
220
|
expectedRemoteFilesVersion: remoteFilesVersion,
|
|
215
|
-
changed: await pMap(
|
|
221
|
+
changed: await pMap(changedFiles, async ([normalizedPath, stats])=>{
|
|
216
222
|
if (stats.mtime.getTime() > this.state.mtime) {
|
|
217
223
|
this.state.mtime = stats.mtime.getTime();
|
|
218
224
|
}
|
|
@@ -231,6 +237,15 @@ class Sync extends BaseCommand {
|
|
|
231
237
|
}
|
|
232
238
|
case Action.RESET:
|
|
233
239
|
{
|
|
240
|
+
context.addBreadcrumb({
|
|
241
|
+
category: "sync",
|
|
242
|
+
message: "Resetting local changes",
|
|
243
|
+
data: {
|
|
244
|
+
state: this.state,
|
|
245
|
+
remoteFilesVersion,
|
|
246
|
+
changed: Array.from(changedFiles.keys())
|
|
247
|
+
}
|
|
248
|
+
});
|
|
234
249
|
// delete all the local files that have changed since the last sync and set the files version to 0 so we receive
|
|
235
250
|
// all the remote files again, including any files that we just deleted that still exist
|
|
236
251
|
await pMap(changedFiles.keys(), (normalizedPath)=>this.softDelete(normalizedPath));
|
|
@@ -242,7 +257,13 @@ class Sync extends BaseCommand {
|
|
|
242
257
|
process.exit(0);
|
|
243
258
|
}
|
|
244
259
|
}
|
|
245
|
-
|
|
260
|
+
context.addBreadcrumb({
|
|
261
|
+
category: "sync",
|
|
262
|
+
message: "Initialized",
|
|
263
|
+
data: {
|
|
264
|
+
state: this.state
|
|
265
|
+
}
|
|
266
|
+
});
|
|
246
267
|
}
|
|
247
268
|
/**
|
|
248
269
|
* Runs the sync process until it is stopped or an error occurs.
|
|
@@ -251,9 +272,17 @@ class Sync extends BaseCommand {
|
|
|
251
272
|
const stopped = new PromiseSignal();
|
|
252
273
|
this.stop = async (e)=>{
|
|
253
274
|
if (this.status != SyncStatus.RUNNING) return;
|
|
254
|
-
error = e;
|
|
255
|
-
this.debug("stopping");
|
|
256
275
|
this.status = SyncStatus.STOPPING;
|
|
276
|
+
error = e;
|
|
277
|
+
context.addBreadcrumb({
|
|
278
|
+
category: "sync",
|
|
279
|
+
message: "Stopping",
|
|
280
|
+
level: error ? "error" : undefined,
|
|
281
|
+
data: {
|
|
282
|
+
state: this.state,
|
|
283
|
+
error
|
|
284
|
+
}
|
|
285
|
+
});
|
|
257
286
|
try {
|
|
258
287
|
unsubscribe();
|
|
259
288
|
this.watcher.removeAllListeners();
|
|
@@ -265,9 +294,15 @@ class Sync extends BaseCommand {
|
|
|
265
294
|
this.watcher.close(),
|
|
266
295
|
this.client.dispose()
|
|
267
296
|
]);
|
|
268
|
-
this.debug("stopped");
|
|
269
297
|
this.status = SyncStatus.STOPPED;
|
|
270
298
|
stopped.resolve();
|
|
299
|
+
context.addBreadcrumb({
|
|
300
|
+
category: "sync",
|
|
301
|
+
message: "Stopped",
|
|
302
|
+
data: {
|
|
303
|
+
state: this.state
|
|
304
|
+
}
|
|
305
|
+
});
|
|
271
306
|
}
|
|
272
307
|
};
|
|
273
308
|
for (const signal of [
|
|
@@ -295,23 +330,50 @@ class Sync extends BaseCommand {
|
|
|
295
330
|
})
|
|
296
331
|
}, {
|
|
297
332
|
error: (error)=>void this.stop(error),
|
|
298
|
-
next: ({ remoteFileSyncEvents
|
|
333
|
+
next: ({ remoteFileSyncEvents })=>{
|
|
334
|
+
context.addBreadcrumb({
|
|
335
|
+
category: "sync",
|
|
336
|
+
message: "Received file sync events",
|
|
337
|
+
data: {
|
|
338
|
+
state: this.state,
|
|
339
|
+
remoteFilesVersion: remoteFileSyncEvents.remoteFilesVersion,
|
|
340
|
+
changed: _.map(remoteFileSyncEvents.changed, "path"),
|
|
341
|
+
deleted: _.map(remoteFileSyncEvents.deleted, "path")
|
|
342
|
+
}
|
|
343
|
+
});
|
|
299
344
|
const remoteFilesVersion = remoteFileSyncEvents.remoteFilesVersion;
|
|
300
345
|
// we always ignore .gadget/ files so that we don't publish them (they're managed by gadget), but we still want to receive them
|
|
301
|
-
const filter = (event)=>event.path
|
|
302
|
-
const changed = remoteFileSyncEvents.changed
|
|
303
|
-
const deleted = remoteFileSyncEvents.deleted
|
|
346
|
+
const filter = (event)=>_.startsWith(event.path, ".gadget/") || !this.ignorer.ignores(event.path);
|
|
347
|
+
const changed = _.filter(remoteFileSyncEvents.changed, filter);
|
|
348
|
+
const deleted = _.filter(remoteFileSyncEvents.deleted, filter);
|
|
304
349
|
this._enqueue(async ()=>{
|
|
350
|
+
context.addBreadcrumb({
|
|
351
|
+
category: "sync",
|
|
352
|
+
message: "Processing received file sync events",
|
|
353
|
+
data: {
|
|
354
|
+
state: this.state,
|
|
355
|
+
remoteFilesVersion: remoteFileSyncEvents.remoteFilesVersion,
|
|
356
|
+
changed: _.map(remoteFileSyncEvents.changed, "path"),
|
|
357
|
+
deleted: _.map(remoteFileSyncEvents.deleted, "path")
|
|
358
|
+
}
|
|
359
|
+
});
|
|
305
360
|
if (!changed.length && !deleted.length) {
|
|
306
361
|
if (BigInt(remoteFilesVersion) > this.state.filesVersion) {
|
|
307
362
|
// we still need to update filesVersion, otherwise our expectedFilesVersion will be behind the next time we publish
|
|
308
|
-
this.debug("updated local files version from %s to %s", this.state.filesVersion, remoteFilesVersion);
|
|
309
363
|
this.state.filesVersion = remoteFilesVersion;
|
|
364
|
+
context.addBreadcrumb({
|
|
365
|
+
category: "sync",
|
|
366
|
+
message: "Received empty file sync events",
|
|
367
|
+
data: {
|
|
368
|
+
state: this.state,
|
|
369
|
+
remoteFilesVersion: remoteFileSyncEvents.remoteFilesVersion
|
|
370
|
+
}
|
|
371
|
+
});
|
|
310
372
|
}
|
|
311
373
|
return;
|
|
312
374
|
}
|
|
313
375
|
this.log(chalkTemplate`Received {gray ${format(new Date(), "pp")}}`);
|
|
314
|
-
this.logPaths("←",
|
|
376
|
+
this.logPaths("←", _.map(changed, "path"), _.map(deleted, "path"));
|
|
315
377
|
// we need to processed deleted files first as we may delete an empty directory after a file has been put
|
|
316
378
|
// into it. if processed out of order the new file will be deleted as well
|
|
317
379
|
await pMap(deleted, async (file)=>{
|
|
@@ -321,14 +383,16 @@ class Sync extends BaseCommand {
|
|
|
321
383
|
await pMap(changed, async (file)=>{
|
|
322
384
|
this.recentRemoteChanges.add(file.path);
|
|
323
385
|
const absolutePath = this.absolute(file.path);
|
|
324
|
-
if (file.path
|
|
386
|
+
if (_.endsWith(file.path, "/")) {
|
|
325
387
|
await fs.ensureDir(absolutePath, {
|
|
326
388
|
mode: 0o755
|
|
327
389
|
});
|
|
328
390
|
return;
|
|
329
391
|
}
|
|
330
|
-
// we need to add
|
|
331
|
-
|
|
392
|
+
// we need to add all parent directories to recentRemoteChanges so that we don't re-publish them
|
|
393
|
+
for (const dir of _.split(path.dirname(file.path), "/")){
|
|
394
|
+
this.recentRemoteChanges.add(dir + "/");
|
|
395
|
+
}
|
|
332
396
|
await fs.ensureDir(path.dirname(absolutePath), {
|
|
333
397
|
mode: 0o755
|
|
334
398
|
});
|
|
@@ -340,16 +404,22 @@ class Sync extends BaseCommand {
|
|
|
340
404
|
"install"
|
|
341
405
|
], {
|
|
342
406
|
cwd: this.dir
|
|
343
|
-
}).catch((
|
|
344
|
-
|
|
345
|
-
|
|
407
|
+
}).catch((error)=>{
|
|
408
|
+
context.addBreadcrumb({
|
|
409
|
+
category: "sync",
|
|
410
|
+
message: "Yarn install failed",
|
|
411
|
+
level: "error",
|
|
412
|
+
data: {
|
|
413
|
+
state: this.state,
|
|
414
|
+
error
|
|
415
|
+
}
|
|
416
|
+
});
|
|
346
417
|
});
|
|
347
418
|
}
|
|
348
419
|
if (absolutePath == this.ignorer.filepath) {
|
|
349
420
|
this.ignorer.reload();
|
|
350
421
|
}
|
|
351
422
|
});
|
|
352
|
-
this.debug("updated local files version from %s to %s", this.state.filesVersion, remoteFilesVersion);
|
|
353
423
|
this.state.filesVersion = remoteFilesVersion;
|
|
354
424
|
// always remove the root directory from recentRemoteChanges
|
|
355
425
|
this.recentRemoteChanges.delete("./");
|
|
@@ -359,6 +429,17 @@ class Sync extends BaseCommand {
|
|
|
359
429
|
this.recentRemoteChanges.delete(filepath);
|
|
360
430
|
}
|
|
361
431
|
}
|
|
432
|
+
context.addBreadcrumb({
|
|
433
|
+
category: "sync",
|
|
434
|
+
message: "Processed received file sync events",
|
|
435
|
+
data: {
|
|
436
|
+
state: this.state,
|
|
437
|
+
remoteFilesVersion: remoteFileSyncEvents.remoteFilesVersion,
|
|
438
|
+
changed: _.map(remoteFileSyncEvents.changed, "path"),
|
|
439
|
+
deleted: _.map(remoteFileSyncEvents.deleted, "path"),
|
|
440
|
+
recentRemoteChanges: Array.from(this.recentRemoteChanges.keys())
|
|
441
|
+
}
|
|
442
|
+
});
|
|
362
443
|
});
|
|
363
444
|
}
|
|
364
445
|
});
|
|
@@ -367,6 +448,14 @@ class Sync extends BaseCommand {
|
|
|
367
448
|
const localFiles = new Map(localFilesBuffer.entries());
|
|
368
449
|
localFilesBuffer.clear();
|
|
369
450
|
this._enqueue(async ()=>{
|
|
451
|
+
context.addBreadcrumb({
|
|
452
|
+
category: "sync",
|
|
453
|
+
message: "Publishing file sync events",
|
|
454
|
+
data: {
|
|
455
|
+
state: this.state,
|
|
456
|
+
localFiles: Array.from(localFiles.keys())
|
|
457
|
+
}
|
|
458
|
+
});
|
|
370
459
|
const changed = [];
|
|
371
460
|
const deleted = [];
|
|
372
461
|
await pMap(localFiles, async ([normalizedPath, file])=>{
|
|
@@ -379,8 +468,9 @@ class Sync extends BaseCommand {
|
|
|
379
468
|
try {
|
|
380
469
|
changed.push({
|
|
381
470
|
path: normalizedPath,
|
|
471
|
+
oldPath: "oldPath" in file ? file.oldPath : undefined,
|
|
382
472
|
mode: file.mode,
|
|
383
|
-
content: file.isDirectory ? "" : await fs.readFile(this.absolute(normalizedPath),
|
|
473
|
+
content: file.isDirectory ? "" : await fs.readFile(this.absolute(normalizedPath), FileSyncEncoding.Base64),
|
|
384
474
|
encoding: FileSyncEncoding.Base64
|
|
385
475
|
});
|
|
386
476
|
} catch (error) {
|
|
@@ -392,7 +482,7 @@ class Sync extends BaseCommand {
|
|
|
392
482
|
if (!changed.length && !deleted.length) {
|
|
393
483
|
return;
|
|
394
484
|
}
|
|
395
|
-
const
|
|
485
|
+
const { publishFileSyncEvents } = await this.client.queryUnwrap({
|
|
396
486
|
query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,
|
|
397
487
|
variables: {
|
|
398
488
|
input: {
|
|
@@ -402,28 +492,52 @@ class Sync extends BaseCommand {
|
|
|
402
492
|
}
|
|
403
493
|
}
|
|
404
494
|
});
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
495
|
+
context.addBreadcrumb({
|
|
496
|
+
category: "sync",
|
|
497
|
+
message: "Published file sync events",
|
|
498
|
+
data: {
|
|
499
|
+
state: this.state,
|
|
500
|
+
remoteFilesVersion: publishFileSyncEvents.remoteFilesVersion,
|
|
501
|
+
changed: _.map(changed, "path"),
|
|
502
|
+
deleted: _.map(deleted, "path")
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
if (BigInt(publishFileSyncEvents.remoteFilesVersion) > this.state.filesVersion) {
|
|
506
|
+
this.state.filesVersion = publishFileSyncEvents.remoteFilesVersion;
|
|
412
507
|
}
|
|
508
|
+
this.log(chalkTemplate`Sent {gray ${format(new Date(), "pp")}}`);
|
|
509
|
+
this.logPaths("→", _.map(changed, "path"), _.map(deleted, "path"));
|
|
413
510
|
});
|
|
414
511
|
}, this.flags["file-push-delay"]);
|
|
415
|
-
this.watcher
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
512
|
+
this.watcher = new FSWatcher(this.dir, {
|
|
513
|
+
// paths that we never want to publish
|
|
514
|
+
ignore: /(\.gadget|\.git|node_modules)/,
|
|
515
|
+
// don't emit an event for every watched file on boot
|
|
516
|
+
ignoreInitial: true,
|
|
517
|
+
renameDetection: true,
|
|
518
|
+
recursive: true,
|
|
519
|
+
debounce: this.flags["file-watch-debounce"],
|
|
520
|
+
pollingInterval: this.flags["file-watch-poll-interval"],
|
|
521
|
+
pollingTimeout: this.flags["file-watch-poll-timeout"],
|
|
522
|
+
renameTimeout: this.flags["file-watch-rename-timeout"]
|
|
523
|
+
});
|
|
524
|
+
this.watcher.once("error", (error)=>void this.stop(error));
|
|
525
|
+
this.watcher.on("all", (event, absolutePath, renamedPath)=>{
|
|
526
|
+
const filepath = event === "rename" || event === "renameDir" ? renamedPath : absolutePath;
|
|
527
|
+
const isDirectory = event === "renameDir" || event === "addDir" || event === "unlinkDir";
|
|
528
|
+
const normalizedPath = this.normalize(filepath, isDirectory);
|
|
529
|
+
if (filepath == this.ignorer.filepath) {
|
|
422
530
|
this.ignorer.reload();
|
|
423
|
-
} else if (this.ignorer.ignores(
|
|
531
|
+
} else if (this.ignorer.ignores(filepath)) {
|
|
424
532
|
this.debug("skipping event caused by ignored file %s", normalizedPath);
|
|
425
533
|
return;
|
|
426
534
|
}
|
|
535
|
+
let stats;
|
|
536
|
+
try {
|
|
537
|
+
stats = fs.statSync(filepath);
|
|
538
|
+
} catch (error) {
|
|
539
|
+
ignoreEnoent(error);
|
|
540
|
+
}
|
|
427
541
|
// we only update the mtime if the file is not ignored, because if we restart and the mtime is set to an ignored
|
|
428
542
|
// file, then it could be greater than the mtime of all non ignored files and we'll think that local files have
|
|
429
543
|
// changed when only an ignored one has
|
|
@@ -434,7 +548,7 @@ class Sync extends BaseCommand {
|
|
|
434
548
|
this.debug("skipping event caused by recent write %s", normalizedPath);
|
|
435
549
|
return;
|
|
436
550
|
}
|
|
437
|
-
this.debug("
|
|
551
|
+
this.debug("%s %s", event, normalizedPath);
|
|
438
552
|
switch(event){
|
|
439
553
|
case "add":
|
|
440
554
|
case "change":
|
|
@@ -458,6 +572,16 @@ class Sync extends BaseCommand {
|
|
|
458
572
|
isDirectory: event === "unlinkDir"
|
|
459
573
|
});
|
|
460
574
|
break;
|
|
575
|
+
case "rename":
|
|
576
|
+
case "renameDir":
|
|
577
|
+
assert(stats, "missing stats on rename/renameDir event");
|
|
578
|
+
localFilesBuffer.set(normalizedPath, {
|
|
579
|
+
oldPath: this.normalize(absolutePath, isDirectory),
|
|
580
|
+
newPath: normalizedPath,
|
|
581
|
+
isDirectory: event === "renameDir",
|
|
582
|
+
mode: stats.mode
|
|
583
|
+
});
|
|
584
|
+
break;
|
|
461
585
|
}
|
|
462
586
|
this.publish();
|
|
463
587
|
});
|
|
@@ -586,20 +710,33 @@ _define_property(Sync, "flags", {
|
|
|
586
710
|
default: 100,
|
|
587
711
|
hidden: true
|
|
588
712
|
}),
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
summary: "
|
|
713
|
+
// The following flags are passed to FSWatcher (https://github.com/fabiospampinato/watcher)
|
|
714
|
+
"file-watch-debounce": Flags.integer({
|
|
715
|
+
summary: "Amount of milliseconds to debounce file changed events",
|
|
592
716
|
helpGroup: "file",
|
|
593
717
|
helpValue: "ms",
|
|
594
|
-
default:
|
|
718
|
+
default: 300,
|
|
595
719
|
hidden: true
|
|
596
720
|
}),
|
|
597
|
-
"file-poll-interval": Flags.integer({
|
|
598
|
-
|
|
599
|
-
description: "Interval in milliseconds between polling a file's size.",
|
|
721
|
+
"file-watch-poll-interval": Flags.integer({
|
|
722
|
+
summary: "Polling is used as a last resort measure when watching non-existent paths inside non-existent directories, this controls how often polling is performed, in milliseconds. You can set it to a lower value to make the app detect events much more quickly, but don't set it too low if you are watching many paths that require polling as polling is expensive.",
|
|
600
723
|
helpGroup: "file",
|
|
601
724
|
helpValue: "ms",
|
|
602
|
-
default:
|
|
725
|
+
default: 3_000,
|
|
726
|
+
hidden: true
|
|
727
|
+
}),
|
|
728
|
+
"file-watch-poll-timeout": Flags.integer({
|
|
729
|
+
summary: "Sometimes polling will fail, for example if there are too many file descriptors currently open, usually eventually polling will succeed after a few tries though, this controls the amount of milliseconds the library should keep retrying for.",
|
|
730
|
+
helpGroup: "file",
|
|
731
|
+
helpValue: "ms",
|
|
732
|
+
default: 20_000,
|
|
733
|
+
hidden: true
|
|
734
|
+
}),
|
|
735
|
+
"file-watch-rename-timeout": Flags.integer({
|
|
736
|
+
summary: "Amount of milliseconds to wait for a potential rename/renameDir event to be detected. The higher this value is the more reliably renames will be detected, but don't set this too high, or the emission of some events could be delayed by that amount. The higher this value is the longer the library will take to emit add/addDir/unlink/unlinkDir events.",
|
|
737
|
+
helpGroup: "file",
|
|
738
|
+
helpValue: "ms",
|
|
739
|
+
default: 1_250,
|
|
603
740
|
hidden: true
|
|
604
741
|
})
|
|
605
742
|
});
|
|
@@ -689,6 +826,13 @@ var /**
|
|
|
689
826
|
* @returns The SyncFile instance.
|
|
690
827
|
*/ static load(rootDir) {
|
|
691
828
|
const state = fs.readJsonSync(path.join(rootDir, ".gadget/sync.json"));
|
|
829
|
+
context.addBreadcrumb({
|
|
830
|
+
category: "sync",
|
|
831
|
+
message: "Loaded sync state",
|
|
832
|
+
data: {
|
|
833
|
+
state
|
|
834
|
+
}
|
|
835
|
+
});
|
|
692
836
|
assert(_.isString(state.app), "missing or invalid app");
|
|
693
837
|
assert(_.isString(state.filesVersion), "missing or invalid filesVersion");
|
|
694
838
|
assert(_.isNumber(state.mtime), "missing or invalid mtime");
|
|
@@ -703,6 +847,11 @@ var /**
|
|
|
703
847
|
*/ flush() {
|
|
704
848
|
_class_private_field_get(this, _save).flush();
|
|
705
849
|
}
|
|
850
|
+
/**
|
|
851
|
+
* @returns The JSON representation of this instance.
|
|
852
|
+
*/ toJSON() {
|
|
853
|
+
return this._inner;
|
|
854
|
+
}
|
|
706
855
|
constructor(_rootDir, inner){
|
|
707
856
|
_define_property(this, "_rootDir", void 0);
|
|
708
857
|
_define_property(this, "_inner", void 0);
|
|
@@ -715,6 +864,13 @@ var /**
|
|
|
715
864
|
fs.outputJSONSync(path.join(this._rootDir, ".gadget/sync.json"), this._inner, {
|
|
716
865
|
spaces: 2
|
|
717
866
|
});
|
|
867
|
+
context.addBreadcrumb({
|
|
868
|
+
category: "sync",
|
|
869
|
+
message: "Saved sync state",
|
|
870
|
+
data: {
|
|
871
|
+
state: this._inner
|
|
872
|
+
}
|
|
873
|
+
});
|
|
718
874
|
}, 100));
|
|
719
875
|
this._inner = inner;
|
|
720
876
|
}
|