@gadgetinc/ggt 0.1.17 → 0.1.18

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 CHANGED
@@ -129,7 +129,7 @@ EXAMPLES
129
129
  Goodbye!
130
130
  ```
131
131
 
132
- _See code: [src/commands/sync.ts](https://github.com/gadget-inc/ggt/blob/v0.1.17/src/commands/sync.ts)_
132
+ _See code: [src/commands/sync.ts](https://github.com/gadget-inc/ggt/blob/v0.1.18/src/commands/sync.ts)_
133
133
 
134
134
  ### `ggt help [COMMAND]`
135
135
 
@@ -143,7 +143,7 @@ ARGUMENTS
143
143
  COMMAND The command to show help for.
144
144
  ```
145
145
 
146
- _See code: [src/commands/help.ts](https://github.com/gadget-inc/ggt/blob/v0.1.17/src/commands/help.ts)_
146
+ _See code: [src/commands/help.ts](https://github.com/gadget-inc/ggt/blob/v0.1.18/src/commands/help.ts)_
147
147
 
148
148
  ### `ggt list`
149
149
 
@@ -170,7 +170,7 @@ EXAMPLES
170
170
  $ ggt list --sort=slug
171
171
  ```
172
172
 
173
- _See code: [src/commands/list.ts](https://github.com/gadget-inc/ggt/blob/v0.1.17/src/commands/list.ts)_
173
+ _See code: [src/commands/list.ts](https://github.com/gadget-inc/ggt/blob/v0.1.18/src/commands/list.ts)_
174
174
 
175
175
  ### `ggt login`
176
176
 
@@ -189,7 +189,7 @@ EXAMPLES
189
189
  Hello, Jane Doe (jane@example.com)
190
190
  ```
191
191
 
192
- _See code: [src/commands/login.ts](https://github.com/gadget-inc/ggt/blob/v0.1.17/src/commands/login.ts)_
192
+ _See code: [src/commands/login.ts](https://github.com/gadget-inc/ggt/blob/v0.1.18/src/commands/login.ts)_
193
193
 
194
194
  ### `ggt logout`
195
195
 
@@ -204,7 +204,7 @@ EXAMPLES
204
204
  Goodbye
205
205
  ```
206
206
 
207
- _See code: [src/commands/logout.ts](https://github.com/gadget-inc/ggt/blob/v0.1.17/src/commands/logout.ts)_
207
+ _See code: [src/commands/logout.ts](https://github.com/gadget-inc/ggt/blob/v0.1.18/src/commands/logout.ts)_
208
208
 
209
209
  ### `ggt whoami`
210
210
 
@@ -219,7 +219,7 @@ EXAMPLES
219
219
  You are logged in as Jane Doe (jane@example.com)
220
220
  ```
221
221
 
222
- _See code: [src/commands/whoami.ts](https://github.com/gadget-inc/ggt/blob/v0.1.17/src/commands/whoami.ts)_
222
+ _See code: [src/commands/whoami.ts](https://github.com/gadget-inc/ggt/blob/v0.1.18/src/commands/whoami.ts)_
223
223
 
224
224
  <!-- commandsstop -->
225
225
 
@@ -35,7 +35,7 @@ export default class Sync extends BaseCommand<typeof Sync> {
35
35
  * A list of filepaths that have changed because of a remote file-sync event. This is used to avoid sending files that
36
36
  * we recently received from a remote file-sync event.
37
37
  */
38
- recentRemoteChanges: Set<unknown>;
38
+ recentRemoteChanges: Set<string>;
39
39
  /**
40
40
  * A FIFO async callback queue that ensures we process file-sync events in the order they occurred.
41
41
  */
@@ -91,19 +91,20 @@ export default class Sync extends BaseCommand<typeof Sync> {
91
91
  absolute(...pathSegments: string[]): string;
92
92
  /**
93
93
  * Similar to {@linkcode relative} in that it turns a filepath into a relative one from {@linkcode dir}. However, it
94
- * also changes any slashes to be posix/unix-like forward slashes, condenses repeat slashes into a single slash.
94
+ * also changes any slashes to be posix/unix-like forward slashes, condenses repeated slashes into a single slash, and
95
+ * adds a trailing slash if the filepath is a directory.
95
96
  *
96
97
  * This is used when sending file-sync events to Gadget to ensure that the paths are consistent across platforms.
97
98
  *
98
99
  * @see https://www.npmjs.com/package/normalize-path
99
100
  */
100
- normalize(filepath: string, isDirectory?: boolean): string;
101
+ normalize(filepath: string, isDirectory: boolean): string;
101
102
  /**
102
103
  * Pretty-prints changed and deleted filepaths to the console.
103
104
  *
104
105
  * @param prefix The prefix to print before each line.
105
- * @param changed The filepaths that have changed.
106
- * @param deleted The filepaths that have been deleted.
106
+ * @param changed The normalized paths that have changed.
107
+ * @param deleted The normalized paths that have been deleted.
107
108
  * @param options.limit The maximum number of lines to print. Defaults to 10. If debug is enabled, this is ignored.
108
109
  */
109
110
  logPaths(prefix: string, changed: string[], deleted: string[], { limit }?: {
@@ -122,6 +123,12 @@ export default class Sync extends BaseCommand<typeof Sync> {
122
123
  * Runs the sync process until it is stopped or an error occurs.
123
124
  */
124
125
  run(): Promise<void>;
126
+ /**
127
+ * Enqueues a function that handles file-sync events onto the {@linkcode queue}.
128
+ *
129
+ * @param fn The function to enqueue.
130
+ */
131
+ private _enqueue;
125
132
  }
126
133
  export declare enum SyncStatus {
127
134
  STARTING = 0,
@@ -159,27 +159,28 @@ class Sync extends base_command_1.BaseCommand {
159
159
  }
160
160
  /**
161
161
  * Similar to {@linkcode relative} in that it turns a filepath into a relative one from {@linkcode dir}. However, it
162
- * also changes any slashes to be posix/unix-like forward slashes, condenses repeat slashes into a single slash.
162
+ * also changes any slashes to be posix/unix-like forward slashes, condenses repeated slashes into a single slash, and
163
+ * adds a trailing slash if the filepath is a directory.
163
164
  *
164
165
  * This is used when sending file-sync events to Gadget to ensure that the paths are consistent across platforms.
165
166
  *
166
167
  * @see https://www.npmjs.com/package/normalize-path
167
168
  */
168
- normalize(filepath, isDirectory = false) {
169
+ normalize(filepath, isDirectory) {
169
170
  return (0, normalize_path_1.default)(path_1.default.isAbsolute(filepath) ? this.relative(filepath) : filepath) + (isDirectory ? "/" : "");
170
171
  }
171
172
  /**
172
173
  * Pretty-prints changed and deleted filepaths to the console.
173
174
  *
174
175
  * @param prefix The prefix to print before each line.
175
- * @param changed The filepaths that have changed.
176
- * @param deleted The filepaths that have been deleted.
176
+ * @param changed The normalized paths that have changed.
177
+ * @param deleted The normalized paths that have been deleted.
177
178
  * @param options.limit The maximum number of lines to print. Defaults to 10. If debug is enabled, this is ignored.
178
179
  */
179
180
  logPaths(prefix, changed, deleted, { limit = 10 } = {}) {
180
181
  const lines = (0, lodash_1.sortBy)([
181
- ...changed.map((filepath) => (0, chalk_1.default) `{green ${prefix}} ${this.normalize(filepath)} {gray (changed)}`),
182
- ...deleted.map((filepath) => (0, chalk_1.default) `{red ${prefix}} ${this.normalize(filepath)} {gray (deleted)}`),
182
+ ...changed.map((normalizedPath) => (0, chalk_1.default) `{green ${prefix}} ${normalizedPath} {gray (changed)}`),
183
+ ...deleted.map((normalizedPath) => (0, chalk_1.default) `{red ${prefix}} ${normalizedPath} {gray (deleted)}`),
183
184
  ], (line) => line.slice(line.indexOf(" ") + 1));
184
185
  let logged = 0;
185
186
  for (const line of lines) {
@@ -262,14 +263,14 @@ class Sync extends base_command_1.BaseCommand {
262
263
  const hasRemoteChanges = BigInt(remoteFilesVersion) > BigInt(this.metadata.filesVersion);
263
264
  const getChangedFiles = async () => {
264
265
  const files = new Map();
265
- for await (const filepath of (0, fs_utils_1.walkDir)(this.dir, { ignorer: this.ignorer })) {
266
- const stats = await fs_extra_1.default.stat(filepath);
266
+ for await (const absolutePath of (0, fs_utils_1.walkDir)(this.dir, { ignorer: this.ignorer })) {
267
+ const stats = await fs_extra_1.default.stat(absolutePath);
267
268
  if (stats.mtime.getTime() > this.metadata.mtime) {
268
- files.set(this.absolute(filepath), stats);
269
+ files.set(this.normalize(absolutePath, stats.isDirectory()), stats);
269
270
  }
270
271
  }
271
272
  // never include the root directory
272
- files.delete(this.dir);
273
+ files.delete("/");
273
274
  return files;
274
275
  };
275
276
  const changedFiles = await getChangedFiles();
@@ -301,15 +302,14 @@ class Sync extends base_command_1.BaseCommand {
301
302
  variables: {
302
303
  input: {
303
304
  expectedRemoteFilesVersion: remoteFilesVersion,
304
- changed: await (0, p_map_1.default)(files, async ([filepath, stats]) => {
305
+ changed: await (0, p_map_1.default)(files, async ([normalizedPath, stats]) => {
305
306
  if (stats.mtime.getTime() > this.metadata.mtime) {
306
307
  this.metadata.mtime = stats.mtime.getTime();
307
308
  }
308
- const isDirectory = stats.isDirectory();
309
309
  return {
310
- path: this.normalize(filepath, isDirectory),
310
+ path: normalizedPath,
311
311
  mode: stats.mode,
312
- content: isDirectory ? "" : await fs_extra_1.default.readFile(filepath, "base64"),
312
+ content: stats.isDirectory() ? "" : await fs_extra_1.default.readFile(this.absolute(normalizedPath), "base64"),
313
313
  encoding: graphql_1.FileSyncEncoding.Base64,
314
314
  };
315
315
  }),
@@ -322,7 +322,7 @@ class Sync extends base_command_1.BaseCommand {
322
322
  case Action.RESET: {
323
323
  // delete all the local files that have changed since the last sync and set the files version to 0 so we receive
324
324
  // all the remote files again, including any files that we just deleted that still exist
325
- await (0, p_map_1.default)(changedFiles, ([filepath]) => fs_extra_1.default.remove(filepath));
325
+ await (0, p_map_1.default)(changedFiles.keys(), (normalizedPath) => fs_extra_1.default.remove(this.absolute(normalizedPath)));
326
326
  this.metadata.filesVersion = "0";
327
327
  break;
328
328
  }
@@ -379,13 +379,14 @@ class Sync extends base_command_1.BaseCommand {
379
379
  variables: () => ({ localFilesVersion: this.metadata.filesVersion }),
380
380
  }, {
381
381
  error: (error) => void this.stop(error),
382
- next: ({ remoteFileSyncEvents: { remoteFilesVersion, changed, deleted } }) => {
383
- const remoteFiles = new Map([...deleted, ...changed]
384
- .filter((event) => event.path.startsWith(".gadget/") || !this.ignorer.ignores(event.path))
385
- .map((e) => [e.path, e]));
386
- void this.queue
387
- .add(async () => {
388
- if (!remoteFiles.size) {
382
+ next: ({ remoteFileSyncEvents }) => {
383
+ const remoteFilesVersion = remoteFileSyncEvents.remoteFilesVersion;
384
+ // we always ignore .gadget/ files so that we don't publish them (they're managed by gadget), but we still want to receive them
385
+ const filter = (event) => event.path.startsWith(".gadget/") || !this.ignorer.ignores(event.path);
386
+ const changed = remoteFileSyncEvents.changed.filter(filter);
387
+ const deleted = remoteFileSyncEvents.deleted.filter(filter);
388
+ this._enqueue(async () => {
389
+ if (!changed.length && !deleted.length) {
389
390
  if (BigInt(remoteFilesVersion) > BigInt(this.metadata.filesVersion)) {
390
391
  // we still need to update filesVersion, otherwise our expectedFilesVersion will be behind the next time we publish
391
392
  this.debug("updated local files version from %s to %s", this.metadata.filesVersion, remoteFilesVersion);
@@ -394,106 +395,104 @@ class Sync extends base_command_1.BaseCommand {
394
395
  return;
395
396
  }
396
397
  this.log((0, chalk_1.default) `Received {gray ${(0, format_1.default)(new Date(), "pp")}}`);
397
- this.logPaths("←", changed.map((x) => x.path).filter((x) => remoteFiles.has(x)), deleted.map((x) => x.path).filter((x) => remoteFiles.has(x)));
398
- const handleFiles = async (files) => await (0, p_map_1.default)(files, async (file) => {
399
- if (!remoteFiles.has(file.path)) {
398
+ this.logPaths("←", changed.map((x) => x.path), deleted.map((x) => x.path));
399
+ // we need to processed deleted files first as we may delete an empty directory after a file has been put
400
+ // into it. if processed out of order the new file is deleted as well
401
+ await (0, p_map_1.default)(deleted, async (file) => {
402
+ this.recentRemoteChanges.add(file.path);
403
+ await fs_extra_1.default.remove(this.absolute(file.path));
404
+ });
405
+ await (0, p_map_1.default)(changed, async (file) => {
406
+ this.recentRemoteChanges.add(file.path);
407
+ const absolutePath = this.absolute(file.path);
408
+ if (file.path.endsWith("/")) {
409
+ await fs_extra_1.default.ensureDir(absolutePath, { mode: 0o755 });
400
410
  return;
401
411
  }
402
- const filepath = this.absolute(file.path);
403
- this.recentRemoteChanges.add(filepath);
404
- if ("content" in file) {
405
- if (!file.path.endsWith("/")) {
406
- this.recentRemoteChanges.add(path_1.default.dirname(filepath));
407
- await fs_extra_1.default.ensureDir(path_1.default.dirname(filepath), { mode: 0o755 });
408
- await fs_extra_1.default.writeFile(filepath, Buffer.from(file.content, file.encoding ?? graphql_1.FileSyncEncoding.Utf8), {
409
- mode: file.mode,
410
- });
411
- }
412
- else {
413
- await fs_extra_1.default.ensureDir(filepath, { mode: 0o755 });
414
- }
415
- if (filepath == this.absolute("yarn.lock")) {
416
- await (0, execa_1.default)("yarn", ["install"], { cwd: this.dir }).catch((err) => {
417
- this.debug("yarn install failed");
418
- this.debug(err.message);
419
- });
420
- }
421
- }
422
- else {
423
- await fs_extra_1.default.remove(filepath);
412
+ // we need to add the parent directory to recentRemoteChanges so that we don't try to publish it
413
+ this.recentRemoteChanges.add(path_1.default.dirname(file.path) + "/");
414
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(absolutePath), { mode: 0o755 });
415
+ await fs_extra_1.default.writeFile(absolutePath, Buffer.from(file.content, file.encoding), { mode: file.mode });
416
+ if (absolutePath == this.absolute("yarn.lock")) {
417
+ await (0, execa_1.default)("yarn", ["install"], { cwd: this.dir }).catch((err) => {
418
+ this.debug("yarn install failed");
419
+ this.debug(err.message);
420
+ });
424
421
  }
425
- if (filepath == this.ignorer.filepath) {
422
+ if (absolutePath == this.ignorer.filepath) {
426
423
  this.ignorer.reload();
427
424
  }
428
- }, { stopOnError: false });
429
- // we need to processed deleted files first as we may delete an empty directory once a file has been put into it. if processed out of order the new file is deleted as well
430
- await handleFiles(deleted);
431
- await handleFiles(changed);
425
+ });
432
426
  this.debug("updated local files version from %s to %s", this.metadata.filesVersion, remoteFilesVersion);
433
427
  this.metadata.filesVersion = remoteFilesVersion;
434
- })
435
- .catch(this.stop);
428
+ // always remove the root directory from recentRemoteChanges
429
+ this.recentRemoteChanges.delete("./");
430
+ // remove any files in recentRemoteChanges that are ignored (e.g. .gadget/ files)
431
+ for (const filepath of this.recentRemoteChanges) {
432
+ if (this.ignorer.ignores(filepath)) {
433
+ this.recentRemoteChanges.delete(filepath);
434
+ }
435
+ }
436
+ });
436
437
  },
437
438
  });
438
439
  const localFilesBuffer = new Map();
439
440
  this.publish = (0, lodash_2.debounce)(() => {
440
441
  const localFiles = new Map(localFilesBuffer.entries());
441
442
  localFilesBuffer.clear();
442
- void this.queue
443
- .add(async () => {
443
+ this._enqueue(async () => {
444
444
  const changed = [];
445
445
  const deleted = [];
446
- await (0, p_map_1.default)(localFiles, async ([filepath, file]) => {
446
+ await (0, p_map_1.default)(localFiles, async ([normalizedPath, file]) => {
447
447
  if ("isDeleted" in file) {
448
- deleted.push({ path: this.normalize(filepath, file.isDirectory) });
448
+ deleted.push({ path: normalizedPath });
449
+ return;
449
450
  }
450
- else {
451
- try {
452
- changed.push({
453
- path: this.normalize(filepath, file.isDirectory),
454
- mode: file.mode,
455
- content: file.isDirectory ? "" : await fs_extra_1.default.readFile(filepath, "base64"),
456
- encoding: graphql_1.FileSyncEncoding.Base64,
457
- });
458
- }
459
- catch (error) {
460
- // A file could have been changed and then deleted before we process the change event, so the readFile
461
- // above will raise an ENOENT. This is normal operation, so just ignore this event.
462
- (0, fs_utils_1.ignoreEnoent)(error);
463
- }
451
+ try {
452
+ changed.push({
453
+ path: normalizedPath,
454
+ mode: file.mode,
455
+ content: file.isDirectory ? "" : await fs_extra_1.default.readFile(this.absolute(normalizedPath), "base64"),
456
+ encoding: graphql_1.FileSyncEncoding.Base64,
457
+ });
464
458
  }
465
- }, { stopOnError: false });
466
- if (changed.length > 0 || deleted.length > 0) {
467
- const data = await this.client.queryUnwrap({
468
- query: exports.PUBLISH_FILE_SYNC_EVENTS_MUTATION,
469
- variables: { input: { expectedRemoteFilesVersion: this.metadata.filesVersion, changed, deleted } },
470
- });
471
- this.log((0, chalk_1.default) `Sent {gray ${(0, format_1.default)(new Date(), "pp")}}`);
472
- this.logPaths("→", changed.map((x) => x.path), deleted.map((x) => x.path));
473
- const { remoteFilesVersion } = data.publishFileSyncEvents;
474
- this.debug("remote files version after publishing %s", remoteFilesVersion);
475
- if (BigInt(remoteFilesVersion) > BigInt(this.metadata.filesVersion)) {
476
- this.debug("updated local files version from %s to %s", this.metadata.filesVersion, remoteFilesVersion);
477
- this.metadata.filesVersion = remoteFilesVersion;
459
+ catch (error) {
460
+ // A file could have been changed and then deleted before we process the change event, so the readFile
461
+ // above will raise an ENOENT. This is normal operation, so just ignore this event.
462
+ (0, fs_utils_1.ignoreEnoent)(error);
478
463
  }
464
+ });
465
+ if (!changed.length && !deleted.length) {
466
+ return;
467
+ }
468
+ const data = await this.client.queryUnwrap({
469
+ query: exports.PUBLISH_FILE_SYNC_EVENTS_MUTATION,
470
+ variables: { input: { expectedRemoteFilesVersion: this.metadata.filesVersion, changed, deleted } },
471
+ });
472
+ this.log((0, chalk_1.default) `Sent {gray ${(0, format_1.default)(new Date(), "pp")}}`);
473
+ this.logPaths("→", changed.map((x) => x.path), deleted.map((x) => x.path));
474
+ const { remoteFilesVersion } = data.publishFileSyncEvents;
475
+ this.debug("remote files version after publishing %s", remoteFilesVersion);
476
+ if (BigInt(remoteFilesVersion) > BigInt(this.metadata.filesVersion)) {
477
+ this.debug("updated local files version from %s to %s", this.metadata.filesVersion, remoteFilesVersion);
478
+ this.metadata.filesVersion = remoteFilesVersion;
479
479
  }
480
- })
481
- .catch(this.stop);
480
+ });
482
481
  }, this.flags["file-push-delay"]);
483
482
  this.watcher
484
483
  .add(`${this.dir}/**/*`)
485
484
  .on("error", (error) => void this.stop(error))
486
- .on("all", (event, filepath, stats) => {
487
- const relativePath = this.relative(filepath);
485
+ .on("all", (event, absolutePath, stats) => {
486
+ const normalizedPath = this.normalize(absolutePath, event == "addDir" || event == "unlinkDir");
488
487
  if (stats?.isSymbolicLink?.()) {
489
- this.debug("skipping event caused by symlink %s", relativePath);
488
+ this.debug("skipping event caused by symlink %s", normalizedPath);
490
489
  return;
491
490
  }
492
- if (filepath == this.ignorer.filepath) {
491
+ if (absolutePath == this.ignorer.filepath) {
493
492
  this.ignorer.reload();
494
493
  }
495
- else if (this.ignorer.ignores(filepath)) {
496
- this.debug("skipping event caused by ignored file %s", relativePath);
494
+ else if (this.ignorer.ignores(absolutePath)) {
495
+ this.debug("skipping event caused by ignored file %s", normalizedPath);
497
496
  return;
498
497
  }
499
498
  // we only update the mtime if the file is not ignored, because if we restart and the mtime is set to an ignored
@@ -502,24 +501,24 @@ class Sync extends base_command_1.BaseCommand {
502
501
  if (stats && stats.mtime.getTime() > this.metadata.mtime) {
503
502
  this.metadata.mtime = stats.mtime.getTime();
504
503
  }
505
- if (this.recentRemoteChanges.delete(filepath)) {
506
- this.debug("skipping event caused by recent write %s", relativePath);
504
+ if (this.recentRemoteChanges.delete(normalizedPath)) {
505
+ this.debug("skipping event caused by recent write %s", normalizedPath);
507
506
  return;
508
507
  }
509
- this.debug("file changed %s", relativePath, event);
508
+ this.debug("file changed %s", normalizedPath, event);
510
509
  switch (event) {
511
510
  case "add":
512
511
  case "change":
513
512
  (0, assert_1.default)(stats, "missing stats on add/change event");
514
- localFilesBuffer.set(filepath, { mode: stats.mode, mtime: stats.mtime.getTime(), isDirectory: false });
513
+ localFilesBuffer.set(normalizedPath, { mode: stats.mode, isDirectory: false });
515
514
  break;
516
515
  case "addDir":
517
516
  (0, assert_1.default)(stats, "missing stats on addDir event");
518
- localFilesBuffer.set(filepath, { mode: stats.mode, mtime: stats.mtime.getTime(), isDirectory: true });
517
+ localFilesBuffer.set(normalizedPath, { mode: stats.mode, isDirectory: true });
519
518
  break;
520
519
  case "unlinkDir":
521
520
  case "unlink":
522
- localFilesBuffer.set(filepath, { isDeleted: true, isDirectory: event === "unlinkDir" });
521
+ localFilesBuffer.set(normalizedPath, { isDeleted: true, isDirectory: event === "unlinkDir" });
523
522
  break;
524
523
  }
525
524
  this.publish();
@@ -555,6 +554,14 @@ class Sync extends base_command_1.BaseCommand {
555
554
  this.log("Goodbye!");
556
555
  }
557
556
  }
557
+ /**
558
+ * Enqueues a function that handles file-sync events onto the {@linkcode queue}.
559
+ *
560
+ * @param fn The function to enqueue.
561
+ */
562
+ _enqueue(fn) {
563
+ void this.queue.add(fn).catch(this.stop);
564
+ }
558
565
  }
559
566
  Object.defineProperty(Sync, "priority", {
560
567
  enumerable: true,