@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.
Files changed (46) hide show
  1. package/README.md +6 -6
  2. package/bin/dev.js +2 -2
  3. package/bin/run.js +1 -1
  4. package/lib/__generated__/graphql.js.map +1 -1
  5. package/lib/commands/help.js +3 -3
  6. package/lib/commands/help.js.map +1 -1
  7. package/lib/commands/list.js +4 -4
  8. package/lib/commands/list.js.map +1 -1
  9. package/lib/commands/login.js +2 -2
  10. package/lib/commands/login.js.map +1 -1
  11. package/lib/commands/logout.js +2 -2
  12. package/lib/commands/logout.js.map +1 -1
  13. package/lib/commands/sync.js +239 -83
  14. package/lib/commands/sync.js.map +1 -1
  15. package/lib/commands/whoami.js +2 -2
  16. package/lib/commands/whoami.js.map +1 -1
  17. package/lib/{utils → services}/base-command.js +25 -10
  18. package/lib/services/base-command.js.map +1 -0
  19. package/lib/{utils → services}/client.js +70 -21
  20. package/lib/services/client.js.map +1 -0
  21. package/lib/{utils → services}/context.js +42 -27
  22. package/lib/services/context.js.map +1 -0
  23. package/lib/{utils → services}/errors.js +8 -8
  24. package/lib/services/errors.js.map +1 -0
  25. package/lib/{utils → services}/flags.js +4 -3
  26. package/lib/services/flags.js.map +1 -0
  27. package/lib/{utils → services}/fs-utils.js +6 -2
  28. package/lib/services/fs-utils.js.map +1 -0
  29. package/lib/{utils → services}/help.js +1 -1
  30. package/lib/services/help.js.map +1 -0
  31. package/lib/services/promise.js.map +1 -0
  32. package/lib/{utils → services}/sleep.js +6 -2
  33. package/lib/services/sleep.js.map +1 -0
  34. package/npm-shrinkwrap.json +2378 -2704
  35. package/oclif.manifest.json +35 -15
  36. package/package.json +35 -34
  37. package/lib/utils/base-command.js.map +0 -1
  38. package/lib/utils/client.js.map +0 -1
  39. package/lib/utils/context.js.map +0 -1
  40. package/lib/utils/errors.js.map +0 -1
  41. package/lib/utils/flags.js.map +0 -1
  42. package/lib/utils/fs-utils.js.map +0 -1
  43. package/lib/utils/help.js.map +0 -1
  44. package/lib/utils/promise.js.map +0 -1
  45. package/lib/utils/sleep.js.map +0 -1
  46. /package/lib/{utils → services}/promise.js +0 -0
@@ -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 "../utils/base-command.js";
23
- import { Client } from "../utils/client.js";
24
- import { context } from "../utils/context.js";
25
- import { InvalidSyncAppFlagError, InvalidSyncFileError, YarnNotFoundError } from "../utils/errors.js";
26
- import { app } from "../utils/flags.js";
27
- import { FSIgnorer, ignoreEnoent, isEmptyDir, walkDir } from "../utils/fs-utils.js";
28
- import { PromiseSignal } from "../utils/promise.js";
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
- ...changed.map((normalizedPath)=>chalkTemplate`{green ${prefix}} ${normalizedPath} {gray (changed)}`),
73
- ...deleted.map((normalizedPath)=>chalkTemplate`{red ${prefix}} ${normalizedPath} {gray (deleted)}`)
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
- assert(_.isString(this.args["directory"]));
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) return this.flags.app;
99
- if (this.state?.app) return this.state.app;
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)=>apps.map((app)=>app.slug))
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 } = await this.client.queryUnwrap({
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
- const changedFiles = await getChangedFiles();
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
- this.debug("init %O", {
184
- metadata: this.state,
185
- remoteFilesVersion,
186
- hasRemoteChanges,
187
- hasLocalChanges
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 } = await inquirer.prompt({
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
- // get all the changed files again in case more changed
206
- const files = await getChangedFiles();
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(files, async ([normalizedPath, stats])=>{
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
- this.debug("started");
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.startsWith(".gadget/") || !this.ignorer.ignores(event.path);
302
- const changed = remoteFileSyncEvents.changed.filter(filter);
303
- const deleted = remoteFileSyncEvents.deleted.filter(filter);
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("←", changed.map((x)=>x.path), deleted.map((x)=>x.path));
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.endsWith("/")) {
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 the parent directory to recentRemoteChanges so that we don't try to publish it
331
- this.recentRemoteChanges.add(path.dirname(file.path) + "/");
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((err)=>{
344
- this.debug("yarn install failed");
345
- this.debug(err.message);
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), "base64"),
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 data = await this.client.queryUnwrap({
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
- this.log(chalkTemplate`Sent {gray ${format(new Date(), "pp")}}`);
406
- this.logPaths("", changed.map((x)=>x.path), deleted.map((x)=>x.path));
407
- const { remoteFilesVersion } = data.publishFileSyncEvents;
408
- this.debug("remote files version after publishing %s", remoteFilesVersion);
409
- if (BigInt(remoteFilesVersion) > this.state.filesVersion) {
410
- this.debug("updated local files version from %s to %s", this.state.filesVersion, remoteFilesVersion);
411
- this.state.filesVersion = remoteFilesVersion;
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.add(`${this.dir}/**/*`).on("error", (error)=>void this.stop(error)).on("all", (event, absolutePath, stats)=>{
416
- const normalizedPath = this.normalize(absolutePath, event == "addDir" || event == "unlinkDir");
417
- if (stats?.isSymbolicLink?.()) {
418
- this.debug("skipping event caused by symlink %s", normalizedPath);
419
- return;
420
- }
421
- if (absolutePath == this.ignorer.filepath) {
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(absolutePath)) {
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("file changed %s", normalizedPath, event);
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
- "file-stability-threshold": Flags.integer({
590
- name: "file-stability-threshold",
591
- summary: "Time in milliseconds a file's size must remain the same.",
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: 500,
718
+ default: 300,
595
719
  hidden: true
596
720
  }),
597
- "file-poll-interval": Flags.integer({
598
- name: "file-poll-interval",
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: 100,
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
  }