@gadgetinc/ggt 0.4.6 → 0.4.7

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.
@@ -12,22 +12,20 @@ import pRetry from "p-retry";
12
12
  import { z } from "zod";
13
13
  import { FileSyncEncoding } from "../../__generated__/graphql.js";
14
14
  import { getApps } from "../app/app.js";
15
- import { EditGraphQL, FILE_SYNC_COMPARISON_HASHES_QUERY, FILE_SYNC_FILES_QUERY, FILE_SYNC_HASHES_QUERY, PUBLISH_FILE_SYNC_EVENTS_MUTATION, REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION } from "../app/edit-graphql.js";
15
+ import { AppArg } from "../app/arg.js";
16
+ import { EditGraphQL, EditGraphQLError, FILE_SYNC_COMPARISON_HASHES_QUERY, FILE_SYNC_FILES_QUERY, FILE_SYNC_HASHES_QUERY, PUBLISH_FILE_SYNC_EVENTS_MUTATION, REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION } from "../app/edit-graphql.js";
16
17
  import { ArgError } from "../command/arg.js";
17
18
  import { config, homePath } from "../config/config.js";
18
- import { createLogger } from "../output/log/logger.js";
19
19
  import { select } from "../output/prompt.js";
20
20
  import { sprint } from "../output/sprint.js";
21
21
  import { sortBySimilar } from "../util/collection.js";
22
22
  import { noop } from "../util/function.js";
23
+ import { isGraphQLErrors, isGraphQLResult, isObject, isString } from "../util/is.js";
23
24
  import { Changes, printChanges } from "./changes.js";
24
25
  import { getConflicts, printConflicts, withoutConflictingChanges } from "./conflicts.js";
25
26
  import { Directory, supportsPermissions, swallowEnoent } from "./directory.js";
26
27
  import { InvalidSyncFileError, TooManySyncAttemptsError } from "./error.js";
27
28
  import { getChanges, isEqualHashes } from "./hashes.js";
28
- const log = createLogger({
29
- name: "filesync"
30
- });
31
29
  export class FileSync {
32
30
  /**
33
31
  * The last filesVersion that was written to the filesystem.
@@ -51,16 +49,19 @@ export class FileSync {
51
49
  * - Ensures the directory is empty or contains a `.gadget/sync.json` file (unless `options.force` is `true`)
52
50
  * - Ensures an app is specified (either via `options.app` or by prompting the user)
53
51
  * - Ensures the specified app matches the app the directory was previously synced to (unless `options.force` is `true`)
54
- */ static async init(options) {
55
- const apps = await getApps(options.user);
52
+ */ static async init({ ctx, user }) {
53
+ ctx = ctx.clone({
54
+ name: "filesync"
55
+ });
56
+ const apps = await getApps(user);
56
57
  if (apps.length === 0) {
57
58
  throw new ArgError(sprint`
58
- You (${options.user.email}) don't have have any Gadget applications.
59
+ You (${user.email}) don't have have any Gadget applications.
59
60
 
60
61
  Visit https://gadget.new to create one!
61
62
  `);
62
63
  }
63
- let dir = options.dir;
64
+ let dir = ctx.args._[0];
64
65
  if (!dir) {
65
66
  // the user didn't specify a directory
66
67
  const filepath = await findUp(".gadget/sync.json");
@@ -85,7 +86,7 @@ export class FileSync {
85
86
  filesVersion: z.string(),
86
87
  mtime: z.number()
87
88
  }).parse(json)).catch(noop);
88
- let appSlug = options.app || state?.app;
89
+ let appSlug = ctx.args["--app"] || state?.app;
89
90
  if (!appSlug) {
90
91
  // the user didn't specify an app, suggest some apps that they can sync to
91
92
  appSlug = await select({
@@ -114,10 +115,10 @@ export class FileSync {
114
115
  const directory = await Directory.init(dir);
115
116
  if (!state) {
116
117
  // the .gadget/sync.json file didn't exist or contained invalid json
117
- if (wasEmptyOrNonExistent || options.force) {
118
+ if (wasEmptyOrNonExistent || ctx.args["--force"]) {
118
119
  // the directory was empty or the user passed --force
119
120
  // either way, create a fresh .gadget/sync.json file
120
- return new FileSync(directory, wasEmptyOrNonExistent, app, {
121
+ return new FileSync(ctx, directory, app, {
121
122
  app: app.slug,
122
123
  filesVersion: "0",
123
124
  mtime: 0
@@ -129,12 +130,12 @@ export class FileSync {
129
130
  // the .gadget/sync.json file exists
130
131
  if (state.app === app.slug) {
131
132
  // the .gadget/sync.json file is for the same app that the user specified
132
- return new FileSync(directory, wasEmptyOrNonExistent, app, state);
133
+ return new FileSync(ctx, directory, app, state);
133
134
  }
134
135
  // the .gadget/sync.json file is for a different app
135
- if (options.force) {
136
+ if (ctx.args["--force"]) {
136
137
  // the user passed --force, so use the app they specified and overwrite everything
137
- return new FileSync(directory, wasEmptyOrNonExistent, app, {
138
+ return new FileSync(ctx, directory, app, {
138
139
  app: app.slug,
139
140
  filesVersion: "0",
140
141
  mtime: 0
@@ -168,9 +169,19 @@ export class FileSync {
168
169
  * @param changes - The changes to send.
169
170
  * @returns A promise that resolves when the changes have been sent.
170
171
  */ async sendChangesToGadget({ changes }) {
171
- await this._syncOperations.add(()=>this._sendChangesToGadget({
172
- changes
173
- }));
172
+ await this._syncOperations.add(async ()=>{
173
+ try {
174
+ await this._sendChangesToGadget({
175
+ changes
176
+ });
177
+ } catch (error) {
178
+ swallowFilesVersionMismatch(this.ctx, error);
179
+ // we either sent the wrong expectedFilesVersion or we received
180
+ // a filesVersion that is greater than the expectedFilesVersion
181
+ // + 1, so we need to stop what we're doing and get in sync
182
+ await this.sync();
183
+ }
184
+ });
174
185
  }
175
186
  /**
176
187
  * Subscribes to file changes on Gadget and executes the provided
@@ -192,12 +203,12 @@ export class FileSync {
192
203
  onData: ({ remoteFileSyncEvents: { changed, deleted, remoteFilesVersion } })=>{
193
204
  this._syncOperations.add(async ()=>{
194
205
  if (BigInt(remoteFilesVersion) < this.filesVersion) {
195
- this.log.warn("skipping received changes because files version is outdated", {
206
+ this.ctx.log.warn("skipping received changes because files version is outdated", {
196
207
  filesVersion: remoteFilesVersion
197
208
  });
198
209
  return;
199
210
  }
200
- this.log.debug("received files", {
211
+ this.ctx.log.debug("received files", {
201
212
  remoteFilesVersion: remoteFilesVersion,
202
213
  changed: changed.map((change)=>change.path),
203
214
  deleted: deleted.map((change)=>change.path)
@@ -205,7 +216,7 @@ export class FileSync {
205
216
  const filterIgnoredFiles = (file)=>{
206
217
  const ignored = this.directory.ignores(file.path);
207
218
  if (ignored) {
208
- this.log.warn("skipping received change because file is ignored", {
219
+ this.ctx.log.warn("skipping received change because file is ignored", {
209
220
  path: file.path
210
221
  });
211
222
  }
@@ -247,33 +258,37 @@ export class FileSync {
247
258
  * - Conflicts are resolved by prompting the user to either keep their
248
259
  * local changes or keep Gadget's changes.
249
260
  * - This function will not return until the filesystem is in sync.
250
- */ async sync({ preference, maxAttempts = 10 } = {}) {
251
- this._syncOperations.pause();
252
- try {
253
- for(let attempt = 0; attempt < maxAttempts; attempt++){
254
- const { inSync, ...hashes } = await this.hashes();
255
- if (inSync) {
256
- this.log.info("filesystem is in sync");
257
- await this._save(hashes.gadgetFilesVersion);
258
- return;
259
- }
260
- await this._sync({
261
- preference,
261
+ */ async sync({ maxAttempts = 10 } = {}) {
262
+ let attempt = 0;
263
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
264
+ while(true){
265
+ const { inSync, ...hashes } = await this.hashes();
266
+ if (inSync) {
267
+ this._syncOperations.clear();
268
+ this.ctx.log.info("filesystem is in sync", {
269
+ attempt
270
+ });
271
+ await this._save(hashes.gadgetFilesVersion);
272
+ return;
273
+ }
274
+ if (attempt++ >= maxAttempts) {
275
+ throw new TooManySyncAttemptsError(maxAttempts);
276
+ }
277
+ try {
278
+ this.ctx.log.info("syncing", {
279
+ attempt,
262
280
  ...hashes
263
281
  });
282
+ await this._sync(hashes);
283
+ } catch (error) {
284
+ swallowFilesVersionMismatch(this.ctx, error);
285
+ // we either sent the wrong expectedFilesVersion or we received
286
+ // a filesVersion that is greater than the expectedFilesVersion
287
+ // + 1, so try again
264
288
  }
265
- } finally{
266
- this._syncOperations.start();
267
289
  }
268
- throw new TooManySyncAttemptsError(maxAttempts);
269
290
  }
270
- async _sync({ preference, filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion }) {
271
- this.log.debug("syncing", {
272
- filesVersionHashes,
273
- localHashes,
274
- gadgetHashes,
275
- gadgetFilesVersion
276
- });
291
+ async _sync({ filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion }) {
277
292
  let localChanges = getChanges({
278
293
  from: filesVersionHashes,
279
294
  to: localHashes,
@@ -303,9 +318,10 @@ export class FileSync {
303
318
  gadgetChanges
304
319
  });
305
320
  if (conflicts.size > 0) {
306
- this.log.debug("conflicts detected", {
321
+ this.ctx.log.debug("conflicts detected", {
307
322
  conflicts
308
323
  });
324
+ let preference = this.ctx.args["--prefer"];
309
325
  if (!preference) {
310
326
  printConflicts({
311
327
  message: sprint`{bold You have conflicting changes with Gadget}`,
@@ -401,7 +417,7 @@ export class FileSync {
401
417
  };
402
418
  }
403
419
  async _getChangesFromGadget({ filesVersion, changes }) {
404
- this.log.debug("getting changes from gadget", {
420
+ this.ctx.log.debug("getting changes from gadget", {
405
421
  filesVersion,
406
422
  changes
407
423
  });
@@ -434,7 +450,7 @@ export class FileSync {
434
450
  });
435
451
  }
436
452
  async _sendChangesToGadget({ expectedFilesVersion = this.filesVersion, changes, printLimit }) {
437
- this.log.debug("sending changes to gadget", {
453
+ this.ctx.log.debug("sending changes to gadget", {
438
454
  expectedFilesVersion,
439
455
  changes
440
456
  });
@@ -453,7 +469,7 @@ export class FileSync {
453
469
  stats = await fs.stat(absolutePath);
454
470
  } catch (error) {
455
471
  swallowEnoent(error);
456
- this.log.debug("skipping change because file doesn't exist", {
472
+ this.ctx.log.debug("skipping change because file doesn't exist", {
457
473
  path: normalizedPath
458
474
  });
459
475
  return;
@@ -475,7 +491,7 @@ export class FileSync {
475
491
  });
476
492
  });
477
493
  if (changed.length === 0 && deleted.length === 0) {
478
- this.log.debug("skipping send because there are no changes");
494
+ this.ctx.log.debug("skipping send because there are no changes");
479
495
  return;
480
496
  }
481
497
  const { publishFileSyncEvents: { remoteFilesVersion } } = await this.editGraphQL.query({
@@ -486,20 +502,41 @@ export class FileSync {
486
502
  changed,
487
503
  deleted
488
504
  }
505
+ },
506
+ http: {
507
+ retry: {
508
+ // we can retry this request because
509
+ // expectedRemoteFilesVersion makes it idempotent
510
+ methods: [
511
+ "POST"
512
+ ],
513
+ calculateDelay: ({ error, computedValue })=>{
514
+ if (isFilesVersionMismatchError(error.response?.body)) {
515
+ // don't retry if we get a files version mismatch error
516
+ return 0;
517
+ }
518
+ return computedValue;
519
+ }
520
+ }
489
521
  }
490
522
  });
491
- await this._save(remoteFilesVersion);
492
523
  printChanges({
493
524
  changes,
494
525
  tense: "past",
495
526
  message: sprint`→ Sent {gray ${dayjs().format("hh:mm:ss A")}}`,
496
527
  limit: printLimit
497
528
  });
529
+ if (BigInt(remoteFilesVersion) > expectedFilesVersion + 1n) {
530
+ // we can't save the remoteFilesVersion because we haven't
531
+ // received the intermediate filesVersions yet
532
+ throw new Error("Files version mismatch");
533
+ }
534
+ await this._save(remoteFilesVersion);
498
535
  }
499
536
  async _writeToLocalFilesystem(options) {
500
537
  const filesVersion = BigInt(options.filesVersion);
501
538
  assert(filesVersion >= this.filesVersion, "filesVersion must be greater than or equal to current filesVersion");
502
- this.log.debug("writing to local filesystem", {
539
+ this.ctx.log.debug("writing to local filesystem", {
503
540
  filesVersion,
504
541
  files: options.files.map((file)=>file.path),
505
542
  delete: options.delete
@@ -527,7 +564,7 @@ export class FileSync {
527
564
  retries: 2,
528
565
  minTimeout: ms("100ms"),
529
566
  onFailedAttempt: (error)=>{
530
- this.log.warn("failed to move file to backup", {
567
+ this.ctx.log.warn("failed to move file to backup", {
531
568
  error,
532
569
  currentPath,
533
570
  backupPath
@@ -587,7 +624,7 @@ export class FileSync {
587
624
  mtime: Date.now() + 1,
588
625
  filesVersion: String(filesVersion)
589
626
  };
590
- this.log.debug("saving state", {
627
+ this.ctx.log.debug("saving state", {
591
628
  state: this._state
592
629
  });
593
630
  await fs.outputJSON(this.directory.absolute(".gadget/sync.json"), this._state, {
@@ -595,33 +632,30 @@ export class FileSync {
595
632
  });
596
633
  }
597
634
  constructor(/**
635
+ * The {@linkcode Context} that was used to initialize this
636
+ * {@linkcode FileSync} instance.
637
+ */ ctx, /**
598
638
  * The directory that is being synced to.
599
639
  */ directory, /**
600
- * Whether the directory was empty or non-existent when we started.
601
- */ wasEmptyOrNonExistent, /**
602
640
  * The Gadget application that is being synced to.
603
641
  */ app, /**
604
642
  * The state of the filesystem.
605
643
  *
606
644
  * This is persisted to `.gadget/sync.json` within the {@linkcode directory}.
607
645
  */ _state){
646
+ _define_property(this, "ctx", void 0);
608
647
  _define_property(this, "directory", void 0);
609
- _define_property(this, "wasEmptyOrNonExistent", void 0);
610
648
  _define_property(this, "app", void 0);
611
649
  _define_property(this, "_state", void 0);
612
650
  _define_property(this, "editGraphQL", void 0);
613
- _define_property(this, "log", void 0);
614
651
  /**
615
652
  * A FIFO async callback queue that ensures we process filesync events
616
653
  * in the order we receive them.
617
654
  */ _define_property(this, "_syncOperations", void 0);
655
+ this.ctx = ctx;
618
656
  this.directory = directory;
619
- this.wasEmptyOrNonExistent = wasEmptyOrNonExistent;
620
657
  this.app = app;
621
658
  this._state = _state;
622
- this.log = log.extend("filesync", ()=>({
623
- state: this._state
624
- }));
625
659
  this._syncOperations = new PQueue({
626
660
  concurrency: 1
627
661
  });
@@ -671,5 +705,34 @@ export const ConflictPreferenceArg = (value, name)=>{
671
705
  ${name} gadget
672
706
  `);
673
707
  };
708
+ export const FileSyncArgs = {
709
+ "--app": {
710
+ type: AppArg,
711
+ alias: "-a"
712
+ },
713
+ "--prefer": ConflictPreferenceArg,
714
+ "--force": Boolean
715
+ };
716
+ export const isFilesVersionMismatchError = (error)=>{
717
+ if (error instanceof EditGraphQLError) {
718
+ error = error.cause;
719
+ }
720
+ if (isGraphQLResult(error)) {
721
+ error = error.errors;
722
+ }
723
+ if (isGraphQLErrors(error)) {
724
+ error = error[0];
725
+ }
726
+ return isObject(error) && "message" in error && isString(error.message) && error.message.startsWith("Files version mismatch");
727
+ };
728
+ const swallowFilesVersionMismatch = (ctx, error)=>{
729
+ if (isFilesVersionMismatchError(error)) {
730
+ ctx.log.debug("swallowing files version mismatch", {
731
+ error
732
+ });
733
+ return;
734
+ }
735
+ throw error;
736
+ };
674
737
 
675
738
  //# sourceMappingURL=filesync.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/services/filesync/filesync.ts"],"sourcesContent":["import dayjs from \"dayjs\";\nimport { findUp } from \"find-up\";\nimport fs from \"fs-extra\";\nimport ms from \"ms\";\nimport assert from \"node:assert\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport pMap from \"p-map\";\nimport PQueue from \"p-queue\";\nimport pRetry from \"p-retry\";\nimport type { Promisable } from \"type-fest\";\nimport { z } from \"zod\";\nimport { FileSyncEncoding, type FileSyncChangedEventInput, type FileSyncDeletedEventInput } from \"../../__generated__/graphql.js\";\nimport type { App } from \"../app/app.js\";\nimport { getApps } from \"../app/app.js\";\nimport {\n EditGraphQL,\n FILE_SYNC_COMPARISON_HASHES_QUERY,\n FILE_SYNC_FILES_QUERY,\n FILE_SYNC_HASHES_QUERY,\n PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n} from \"../app/edit-graphql.js\";\nimport { ArgError } from \"../command/arg.js\";\nimport { config, homePath } from \"../config/config.js\";\nimport { createLogger } from \"../output/log/logger.js\";\nimport { select } from \"../output/prompt.js\";\nimport { sprint } from \"../output/sprint.js\";\nimport type { User } from \"../user/user.js\";\nimport { sortBySimilar } from \"../util/collection.js\";\nimport { noop } from \"../util/function.js\";\nimport { Changes, printChanges } from \"./changes.js\";\nimport { getConflicts, printConflicts, withoutConflictingChanges } from \"./conflicts.js\";\nimport { Directory, supportsPermissions, swallowEnoent, type Hashes } from \"./directory.js\";\nimport { InvalidSyncFileError, TooManySyncAttemptsError } from \"./error.js\";\nimport type { File } from \"./file.js\";\nimport { getChanges, isEqualHashes, type ChangesWithHash } from \"./hashes.js\";\n\nconst log = createLogger({ name: \"filesync\" });\n\nexport type FileSyncHashes = {\n inSync: boolean;\n filesVersionHashes: Hashes;\n localHashes: Hashes;\n gadgetHashes: Hashes;\n gadgetFilesVersion: bigint;\n};\n\nexport class FileSync {\n readonly editGraphQL: EditGraphQL;\n\n readonly log = log.extend(\"filesync\", () => ({ state: this._state }));\n\n /**\n * A FIFO async callback queue that ensures we process filesync events\n * in the order we receive them.\n */\n private _syncOperations = new PQueue({ concurrency: 1 });\n\n private constructor(\n /**\n * The directory that is being synced to.\n */\n readonly directory: Directory,\n\n /**\n * Whether the directory was empty or non-existent when we started.\n */\n readonly wasEmptyOrNonExistent: boolean,\n\n /**\n * The Gadget application that is being synced to.\n */\n readonly app: App,\n\n /**\n * The state of the filesystem.\n *\n * This is persisted to `.gadget/sync.json` within the {@linkcode directory}.\n */\n private _state: { app: string; filesVersion: string; mtime: number },\n ) {\n this.editGraphQL = new EditGraphQL(this.app);\n }\n\n /**\n * The last filesVersion that was written to the filesystem.\n *\n * This determines if the filesystem in Gadget is ahead of the\n * filesystem on the local machine.\n */\n get filesVersion(): bigint {\n return BigInt(this._state.filesVersion);\n }\n\n /**\n * The largest mtime that was seen on the filesystem.\n *\n * This is used to determine if any files have changed since the last\n * sync. This does not include the mtime of files that are ignored.\n */\n get mtime(): number {\n return this._state.mtime;\n }\n\n /**\n * Initializes a {@linkcode FileSync} instance.\n * - Ensures the directory exists.\n * - Ensures the directory is empty or contains a `.gadget/sync.json` file (unless `options.force` is `true`)\n * - Ensures an app is specified (either via `options.app` or by prompting the user)\n * - Ensures the specified app matches the app the directory was previously synced to (unless `options.force` is `true`)\n */\n static async init(options: { user: User; dir?: string; app?: string; force?: boolean }): Promise<FileSync> {\n const apps = await getApps(options.user);\n if (apps.length === 0) {\n throw new ArgError(\n sprint`\n You (${options.user.email}) don't have have any Gadget applications.\n\n Visit https://gadget.new to create one!\n `,\n );\n }\n\n let dir = options.dir;\n if (!dir) {\n // the user didn't specify a directory\n const filepath = await findUp(\".gadget/sync.json\");\n if (filepath) {\n // we found a .gadget/sync.json file, use its parent directory\n dir = path.join(filepath, \"../..\");\n } else {\n // we didn't find a .gadget/sync.json file, use the current directory\n dir = process.cwd();\n }\n }\n\n if (config.windows && dir.startsWith(\"~/\")) {\n // `~` doesn't expand to the home directory on Windows\n dir = homePath(dir.slice(2));\n }\n\n // ensure the root directory is an absolute path and exists\n const wasEmptyOrNonExistent = await isEmptyOrNonExistentDir(dir);\n await fs.ensureDir((dir = path.resolve(dir)));\n\n // try to load the .gadget/sync.json file\n const state = await fs\n .readJson(path.join(dir, \".gadget/sync.json\"))\n .then((json) =>\n z\n .object({\n app: z.string(),\n filesVersion: z.string(),\n mtime: z.number(),\n })\n .parse(json),\n )\n .catch(noop);\n\n let appSlug = options.app || state?.app;\n if (!appSlug) {\n // the user didn't specify an app, suggest some apps that they can sync to\n appSlug = await select({\n message: \"Select the app to sync to\",\n choices: apps.map((x) => x.slug),\n });\n }\n\n // try to find the appSlug in their list of apps\n const app = apps.find((app) => app.slug === appSlug);\n if (!app) {\n // the specified appSlug doesn't exist in their list of apps,\n // either they misspelled it or they don't have access to it\n // anymore, suggest some apps that are similar to the one they\n // specified\n const similarAppSlugs = sortBySimilar(\n appSlug,\n apps.map((app) => app.slug),\n ).slice(0, 5);\n\n throw new ArgError(\n sprint`\n Unknown application:\n\n ${appSlug}\n\n Did you mean one of these?\n\n\n `.concat(` • ${similarAppSlugs.join(\"\\n • \")}`),\n );\n }\n\n const directory = await Directory.init(dir);\n\n if (!state) {\n // the .gadget/sync.json file didn't exist or contained invalid json\n if (wasEmptyOrNonExistent || options.force) {\n // the directory was empty or the user passed --force\n // either way, create a fresh .gadget/sync.json file\n return new FileSync(directory, wasEmptyOrNonExistent, app, { app: app.slug, filesVersion: \"0\", mtime: 0 });\n }\n\n // the directory isn't empty and the user didn't pass --force\n throw new InvalidSyncFileError(dir, app.slug);\n }\n\n // the .gadget/sync.json file exists\n if (state.app === app.slug) {\n // the .gadget/sync.json file is for the same app that the user specified\n return new FileSync(directory, wasEmptyOrNonExistent, app, state);\n }\n\n // the .gadget/sync.json file is for a different app\n if (options.force) {\n // the user passed --force, so use the app they specified and overwrite everything\n return new FileSync(directory, wasEmptyOrNonExistent, app, { app: app.slug, filesVersion: \"0\", mtime: 0 });\n }\n\n // the user didn't pass --force, so throw an error\n throw new ArgError(sprint`\n You were about to sync the following app to the following directory:\n\n {dim ${app.slug}} → {dim ${dir}}\n\n However, that directory has already been synced with this app:\n\n {dim ${state.app}}\n\n If you're sure that you want to sync:\n\n {dim ${app.slug}} → {dim ${dir}}\n\n Then run {dim ggt sync} again with the {dim --force} flag.\n `);\n }\n\n /**\n * Waits for all pending and ongoing filesync operations to complete.\n */\n async idle(): Promise<void> {\n await this._syncOperations.onIdle();\n }\n\n /**\n * Sends file changes to the Gadget.\n *\n * @param changes - The changes to send.\n * @returns A promise that resolves when the changes have been sent.\n */\n async sendChangesToGadget({ changes }: { changes: Changes }): Promise<void> {\n await this._syncOperations.add(() => this._sendChangesToGadget({ changes }));\n }\n\n /**\n * Subscribes to file changes on Gadget and executes the provided\n * callbacks before and after the changes occur.\n *\n * @returns A function that unsubscribes from changes on Gadget.\n */\n subscribeToGadgetChanges({\n beforeChanges,\n afterChanges,\n onError,\n }: {\n beforeChanges: (data: { changed: string[]; deleted: string[] }) => Promisable<void>;\n afterChanges: (data: { changes: Changes }) => Promisable<void>;\n onError: (error: unknown) => void;\n }): () => void {\n return this.editGraphQL.subscribe({\n query: REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n // the reason this is a function rather than a static value is\n // so that it will be re-evaluated if the connection is lost and\n // then re-established. this ensures that we send our current\n // filesVersion rather than the one that was sent when we first\n // subscribed\n variables: () => ({ localFilesVersion: String(this.filesVersion) }),\n onError,\n onData: ({ remoteFileSyncEvents: { changed, deleted, remoteFilesVersion } }) => {\n this._syncOperations\n .add(async () => {\n if (BigInt(remoteFilesVersion) < this.filesVersion) {\n this.log.warn(\"skipping received changes because files version is outdated\", { filesVersion: remoteFilesVersion });\n return;\n }\n\n this.log.debug(\"received files\", {\n remoteFilesVersion: remoteFilesVersion,\n changed: changed.map((change) => change.path),\n deleted: deleted.map((change) => change.path),\n });\n\n const filterIgnoredFiles = (file: { path: string }): boolean => {\n const ignored = this.directory.ignores(file.path);\n if (ignored) {\n this.log.warn(\"skipping received change because file is ignored\", { path: file.path });\n }\n return !ignored;\n };\n\n changed = changed.filter(filterIgnoredFiles);\n deleted = deleted.filter(filterIgnoredFiles);\n\n if (changed.length === 0 && deleted.length === 0) {\n await this._save(remoteFilesVersion);\n return;\n }\n\n await beforeChanges({\n changed: changed.map((file) => file.path),\n deleted: deleted.map((file) => file.path),\n });\n\n const changes = await this._writeToLocalFilesystem({\n filesVersion: remoteFilesVersion,\n files: changed,\n delete: deleted.map((file) => file.path),\n });\n\n if (changes.size > 0) {\n printChanges({\n message: sprint`← Received {gray ${dayjs().format(\"hh:mm:ss A\")}}`,\n changes,\n tense: \"past\",\n limit: 10,\n });\n }\n\n await afterChanges({ changes });\n })\n .catch(onError);\n },\n });\n }\n\n /**\n * Ensures the local filesystem is in sync with Gadget's filesystem.\n * - All non-conflicting changes are automatically merged.\n * - Conflicts are resolved by prompting the user to either keep their\n * local changes or keep Gadget's changes.\n * - This function will not return until the filesystem is in sync.\n */\n async sync({ preference, maxAttempts = 10 }: { preference?: ConflictPreference; maxAttempts?: number } = {}): Promise<void> {\n this._syncOperations.pause();\n\n try {\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const { inSync, ...hashes } = await this.hashes();\n\n if (inSync) {\n this.log.info(\"filesystem is in sync\");\n await this._save(hashes.gadgetFilesVersion);\n return;\n }\n\n await this._sync({ preference, ...hashes });\n }\n } finally {\n this._syncOperations.start();\n }\n\n throw new TooManySyncAttemptsError(maxAttempts);\n }\n\n async _sync({\n preference,\n filesVersionHashes,\n localHashes,\n gadgetHashes,\n gadgetFilesVersion,\n }: { preference?: ConflictPreference } & Omit<FileSyncHashes, \"inSync\">): Promise<void> {\n this.log.debug(\"syncing\", { filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion });\n let localChanges = getChanges({ from: filesVersionHashes, to: localHashes, existing: gadgetHashes, ignore: [\".gadget/\"] });\n let gadgetChanges = getChanges({ from: filesVersionHashes, to: gadgetHashes, existing: localHashes });\n\n if (localChanges.size === 0 && gadgetChanges.size === 0) {\n // the local filesystem is missing .gadget/ files\n gadgetChanges = getChanges({ from: localHashes, to: gadgetHashes });\n assertAllGadgetFiles({ gadgetChanges });\n }\n\n assert(localChanges.size > 0 || gadgetChanges.size > 0, \"there must be changes if hashes don't match\");\n\n const conflicts = getConflicts({ localChanges, gadgetChanges });\n if (conflicts.size > 0) {\n this.log.debug(\"conflicts detected\", { conflicts });\n\n if (!preference) {\n printConflicts({\n message: sprint`{bold You have conflicting changes with Gadget}`,\n conflicts,\n });\n\n preference = await select({\n message: \"How would you like to resolve these conflicts?\",\n choices: Object.values(ConflictPreference),\n });\n }\n\n switch (preference) {\n case ConflictPreference.CANCEL: {\n process.exit(0);\n break;\n }\n case ConflictPreference.LOCAL: {\n gadgetChanges = withoutConflictingChanges({ conflicts, changes: gadgetChanges });\n break;\n }\n case ConflictPreference.GADGET: {\n localChanges = withoutConflictingChanges({ conflicts, changes: localChanges });\n break;\n }\n }\n }\n\n if (gadgetChanges.size > 0) {\n await this._getChangesFromGadget({ changes: gadgetChanges, filesVersion: gadgetFilesVersion });\n }\n\n if (localChanges.size > 0) {\n await this._sendChangesToGadget({ changes: localChanges, expectedFilesVersion: gadgetFilesVersion });\n }\n }\n\n async hashes(): Promise<FileSyncHashes> {\n const [localHashes, { filesVersionHashes, gadgetHashes, gadgetFilesVersion }] = await Promise.all([\n // get the hashes of our local files\n this.directory.hashes(),\n // get the hashes of our local filesVersion and the latest filesVersion\n (async () => {\n let gadgetFilesVersion: bigint;\n let gadgetHashes: Hashes;\n let filesVersionHashes: Hashes;\n\n if (this.filesVersion === 0n) {\n // this is the first time we're syncing, so just get the\n // hashes of the latest filesVersion\n const { fileSyncHashes } = await this.editGraphQL.query({ query: FILE_SYNC_HASHES_QUERY });\n gadgetFilesVersion = BigInt(fileSyncHashes.filesVersion);\n gadgetHashes = fileSyncHashes.hashes;\n filesVersionHashes = {};\n } else {\n // this isn't the first time we're syncing, so get the hashes\n // of the files at our local filesVersion and the latest\n // filesVersion\n const { fileSyncComparisonHashes } = await this.editGraphQL.query({\n query: FILE_SYNC_COMPARISON_HASHES_QUERY,\n variables: { filesVersion: String(this.filesVersion) },\n });\n gadgetFilesVersion = BigInt(fileSyncComparisonHashes.latestFilesVersionHashes.filesVersion);\n gadgetHashes = fileSyncComparisonHashes.latestFilesVersionHashes.hashes;\n filesVersionHashes = fileSyncComparisonHashes.filesVersionHashes.hashes;\n }\n\n return { filesVersionHashes, gadgetHashes, gadgetFilesVersion };\n })(),\n ]);\n\n return { filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion, inSync: isEqualHashes(localHashes, gadgetHashes) };\n }\n\n private async _getChangesFromGadget({\n filesVersion,\n changes,\n }: {\n filesVersion: bigint;\n changes: Changes | ChangesWithHash;\n }): Promise<void> {\n this.log.debug(\"getting changes from gadget\", { filesVersion, changes });\n const created = changes.created();\n const updated = changes.updated();\n\n let files: File[] = [];\n if (created.length > 0 || updated.length > 0) {\n const { fileSyncFiles } = await this.editGraphQL.query({\n query: FILE_SYNC_FILES_QUERY,\n variables: {\n paths: [...created, ...updated],\n filesVersion: String(filesVersion),\n encoding: FileSyncEncoding.Base64,\n },\n });\n\n files = fileSyncFiles.files;\n }\n\n await this._writeToLocalFilesystem({\n filesVersion,\n files,\n delete: changes.deleted(),\n });\n\n printChanges({\n changes,\n tense: \"past\",\n message: sprint`← Received {gray ${dayjs().format(\"hh:mm:ss A\")}}`,\n });\n }\n\n private async _sendChangesToGadget({\n expectedFilesVersion = this.filesVersion,\n changes,\n printLimit,\n }: {\n expectedFilesVersion?: bigint;\n changes: Changes;\n printLimit?: number;\n }): Promise<void> {\n this.log.debug(\"sending changes to gadget\", { expectedFilesVersion, changes });\n const changed: FileSyncChangedEventInput[] = [];\n const deleted: FileSyncDeletedEventInput[] = [];\n\n await pMap(changes, async ([normalizedPath, change]) => {\n if (change.type === \"delete\") {\n deleted.push({ path: normalizedPath });\n return;\n }\n\n const absolutePath = this.directory.absolute(normalizedPath);\n\n let stats;\n try {\n stats = await fs.stat(absolutePath);\n } catch (error) {\n swallowEnoent(error);\n this.log.debug(\"skipping change because file doesn't exist\", { path: normalizedPath });\n return;\n }\n\n let content = \"\";\n if (stats.isFile()) {\n content = await fs.readFile(absolutePath, FileSyncEncoding.Base64);\n }\n\n let oldPath;\n if (change.type === \"create\" && change.oldPath) {\n oldPath = change.oldPath;\n }\n\n changed.push({\n content,\n oldPath,\n path: normalizedPath,\n mode: stats.mode,\n encoding: FileSyncEncoding.Base64,\n });\n });\n\n if (changed.length === 0 && deleted.length === 0) {\n this.log.debug(\"skipping send because there are no changes\");\n return;\n }\n\n const {\n publishFileSyncEvents: { remoteFilesVersion },\n } = await this.editGraphQL.query({\n query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n variables: {\n input: {\n expectedRemoteFilesVersion: String(expectedFilesVersion),\n changed,\n deleted,\n },\n },\n });\n\n await this._save(remoteFilesVersion);\n\n printChanges({\n changes,\n tense: \"past\",\n message: sprint`→ Sent {gray ${dayjs().format(\"hh:mm:ss A\")}}`,\n limit: printLimit,\n });\n }\n\n private async _writeToLocalFilesystem(options: { filesVersion: bigint | string; files: File[]; delete: string[] }): Promise<Changes> {\n const filesVersion = BigInt(options.filesVersion);\n assert(filesVersion >= this.filesVersion, \"filesVersion must be greater than or equal to current filesVersion\");\n\n this.log.debug(\"writing to local filesystem\", {\n filesVersion,\n files: options.files.map((file) => file.path),\n delete: options.delete,\n });\n\n const created: string[] = [];\n const updated: string[] = [];\n\n await pMap(options.delete, async (filepath) => {\n const currentPath = this.directory.absolute(filepath);\n const backupPath = this.directory.absolute(\".gadget/backup\", this.directory.relative(filepath));\n\n // rather than `rm -rf`ing files, we move them to\n // `.gadget/backup/` so that users can recover them if something\n // goes wrong. We've seen a lot of EBUSY/EINVAL errors when moving\n // files so we retry a few times.\n await pRetry(\n async () => {\n try {\n // remove the current backup file in case it exists and is a\n // different type (file vs directory)\n await fs.remove(backupPath);\n await fs.move(currentPath, backupPath);\n } catch (error) {\n // replicate the behavior of `rm -rf` and ignore ENOENT\n swallowEnoent(error);\n }\n },\n {\n retries: 2,\n minTimeout: ms(\"100ms\"),\n onFailedAttempt: (error) => {\n this.log.warn(\"failed to move file to backup\", { error, currentPath, backupPath });\n },\n },\n );\n });\n\n await pMap(options.files, async (file) => {\n const absolutePath = this.directory.absolute(file.path);\n if (await fs.pathExists(absolutePath)) {\n updated.push(file.path);\n } else {\n created.push(file.path);\n }\n\n if (file.path.endsWith(\"/\")) {\n await fs.ensureDir(absolutePath);\n } else {\n await fs.outputFile(absolutePath, Buffer.from(file.content, file.encoding));\n }\n\n if (supportsPermissions) {\n // the os's default umask makes setting the mode during creation\n // not work, so an additional fs.chmod call is necessary to\n // ensure the file has the correct mode\n await fs.chmod(absolutePath, file.mode & 0o777);\n }\n\n if (absolutePath === this.directory.absolute(\".ignore\")) {\n await this.directory.loadIgnoreFile();\n }\n });\n\n await this._save(String(filesVersion));\n\n return new Changes([\n ...created.map((path) => [path, { type: \"create\" }] as const),\n ...updated.map((path) => [path, { type: \"update\" }] as const),\n ...options.delete.map((path) => [path, { type: \"delete\" }] as const),\n ]);\n }\n\n /**\n * Updates {@linkcode _state} and saves it to `.gadget/sync.json`.\n */\n private async _save(filesVersion: string | bigint): Promise<void> {\n this._state = { ...this._state, mtime: Date.now() + 1, filesVersion: String(filesVersion) };\n this.log.debug(\"saving state\", { state: this._state });\n await fs.outputJSON(this.directory.absolute(\".gadget/sync.json\"), this._state, { spaces: 2 });\n }\n}\n\n/**\n * Checks if a directory is empty or non-existent.\n *\n * @param dir - The directory path to check.\n * @returns A Promise that resolves to a boolean indicating whether the directory is empty or non-existent.\n */\nexport const isEmptyOrNonExistentDir = async (dir: string): Promise<boolean> => {\n try {\n for await (const _ of await fs.opendir(dir, { bufferSize: 1 })) {\n return false;\n }\n return true;\n } catch (error) {\n swallowEnoent(error);\n return true;\n }\n};\n\nexport const assertAllGadgetFiles = ({ gadgetChanges }: { gadgetChanges: Changes }): void => {\n assert(\n gadgetChanges.created().length > 0 || gadgetChanges.deleted().length > 0 || gadgetChanges.updated().length > 0,\n \"expected gadgetChanges to have changes\",\n );\n\n const allGadgetFiles = Array.from(gadgetChanges.keys()).every((path) => path.startsWith(\".gadget/\"));\n assert(allGadgetFiles, \"expected all gadgetChanges to be .gadget/ files\");\n};\n\nexport const ConflictPreference = Object.freeze({\n CANCEL: \"Cancel (Ctrl+C)\",\n LOCAL: \"Keep my conflicting changes\",\n GADGET: \"Keep Gadget's conflicting changes\",\n});\n\nexport type ConflictPreference = (typeof ConflictPreference)[keyof typeof ConflictPreference];\n\nexport const ConflictPreferenceArg = (value: string, name: string): ConflictPreference => {\n if ([\"local\", \"gadget\"].includes(value)) {\n return ConflictPreference[value.toUpperCase() as keyof typeof ConflictPreference];\n }\n\n throw new ArgError(sprint`\n ${name} must be {bold local} or {bold gadget}\n\n {bold EXAMPLES:}\n ${name} local\n ${name} gadget\n `);\n};\n"],"names":["dayjs","findUp","fs","ms","assert","path","process","pMap","PQueue","pRetry","z","FileSyncEncoding","getApps","EditGraphQL","FILE_SYNC_COMPARISON_HASHES_QUERY","FILE_SYNC_FILES_QUERY","FILE_SYNC_HASHES_QUERY","PUBLISH_FILE_SYNC_EVENTS_MUTATION","REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION","ArgError","config","homePath","createLogger","select","sprint","sortBySimilar","noop","Changes","printChanges","getConflicts","printConflicts","withoutConflictingChanges","Directory","supportsPermissions","swallowEnoent","InvalidSyncFileError","TooManySyncAttemptsError","getChanges","isEqualHashes","log","name","FileSync","filesVersion","BigInt","_state","mtime","init","options","apps","user","length","email","dir","filepath","join","cwd","windows","startsWith","slice","wasEmptyOrNonExistent","isEmptyOrNonExistentDir","ensureDir","resolve","state","readJson","then","json","object","app","string","number","parse","catch","appSlug","message","choices","map","x","slug","find","similarAppSlugs","concat","directory","force","idle","_syncOperations","onIdle","sendChangesToGadget","changes","add","_sendChangesToGadget","subscribeToGadgetChanges","beforeChanges","afterChanges","onError","editGraphQL","subscribe","query","variables","localFilesVersion","String","onData","remoteFileSyncEvents","changed","deleted","remoteFilesVersion","warn","debug","change","filterIgnoredFiles","file","ignored","ignores","filter","_save","_writeToLocalFilesystem","files","delete","size","format","tense","limit","sync","preference","maxAttempts","pause","attempt","inSync","hashes","info","gadgetFilesVersion","_sync","start","filesVersionHashes","localHashes","gadgetHashes","localChanges","from","to","existing","ignore","gadgetChanges","assertAllGadgetFiles","conflicts","Object","values","ConflictPreference","CANCEL","exit","LOCAL","GADGET","_getChangesFromGadget","expectedFilesVersion","Promise","all","fileSyncHashes","fileSyncComparisonHashes","latestFilesVersionHashes","created","updated","fileSyncFiles","paths","encoding","Base64","printLimit","normalizedPath","type","push","absolutePath","absolute","stats","stat","error","content","isFile","readFile","oldPath","mode","publishFileSyncEvents","input","expectedRemoteFilesVersion","currentPath","backupPath","relative","remove","move","retries","minTimeout","onFailedAttempt","pathExists","endsWith","outputFile","Buffer","chmod","loadIgnoreFile","Date","now","outputJSON","spaces","extend","concurrency","_","opendir","bufferSize","allGadgetFiles","Array","keys","every","freeze","ConflictPreferenceArg","value","includes","toUpperCase"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/services/filesync/filesync.ts"],"sourcesContent":["import dayjs from \"dayjs\";\nimport { findUp } from \"find-up\";\nimport fs from \"fs-extra\";\nimport ms from \"ms\";\nimport assert from \"node:assert\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport pMap from \"p-map\";\nimport PQueue from \"p-queue\";\nimport pRetry from \"p-retry\";\nimport type { Promisable } from \"type-fest\";\nimport { z } from \"zod\";\nimport { FileSyncEncoding, type FileSyncChangedEventInput, type FileSyncDeletedEventInput } from \"../../__generated__/graphql.js\";\nimport type { App } from \"../app/app.js\";\nimport { getApps } from \"../app/app.js\";\nimport { AppArg } from \"../app/arg.js\";\nimport {\n EditGraphQL,\n EditGraphQLError,\n FILE_SYNC_COMPARISON_HASHES_QUERY,\n FILE_SYNC_FILES_QUERY,\n FILE_SYNC_HASHES_QUERY,\n PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n} from \"../app/edit-graphql.js\";\nimport { ArgError, type ArgsSpec } from \"../command/arg.js\";\nimport type { Context } from \"../command/context.js\";\nimport { config, homePath } from \"../config/config.js\";\nimport { select } from \"../output/prompt.js\";\nimport { sprint } from \"../output/sprint.js\";\nimport type { User } from \"../user/user.js\";\nimport { sortBySimilar } from \"../util/collection.js\";\nimport { noop } from \"../util/function.js\";\nimport { isGraphQLErrors, isGraphQLResult, isObject, isString } from \"../util/is.js\";\nimport { Changes, printChanges } from \"./changes.js\";\nimport { getConflicts, printConflicts, withoutConflictingChanges } from \"./conflicts.js\";\nimport { Directory, supportsPermissions, swallowEnoent, type Hashes } from \"./directory.js\";\nimport { InvalidSyncFileError, TooManySyncAttemptsError } from \"./error.js\";\nimport type { File } from \"./file.js\";\nimport { getChanges, isEqualHashes, type ChangesWithHash } from \"./hashes.js\";\n\nexport type FileSyncHashes = {\n inSync: boolean;\n filesVersionHashes: Hashes;\n localHashes: Hashes;\n gadgetHashes: Hashes;\n gadgetFilesVersion: bigint;\n};\n\nexport class FileSync {\n readonly editGraphQL: EditGraphQL;\n\n /**\n * A FIFO async callback queue that ensures we process filesync events\n * in the order we receive them.\n */\n private _syncOperations = new PQueue({ concurrency: 1 });\n\n private constructor(\n /**\n * The {@linkcode Context} that was used to initialize this\n * {@linkcode FileSync} instance.\n */\n readonly ctx: Context<FileSyncArgs>,\n\n /**\n * The directory that is being synced to.\n */\n readonly directory: Directory,\n\n /**\n * The Gadget application that is being synced to.\n */\n readonly app: App,\n\n /**\n * The state of the filesystem.\n *\n * This is persisted to `.gadget/sync.json` within the {@linkcode directory}.\n */\n private _state: { app: string; filesVersion: string; mtime: number },\n ) {\n this.editGraphQL = new EditGraphQL(this.app);\n }\n\n /**\n * The last filesVersion that was written to the filesystem.\n *\n * This determines if the filesystem in Gadget is ahead of the\n * filesystem on the local machine.\n */\n get filesVersion(): bigint {\n return BigInt(this._state.filesVersion);\n }\n\n /**\n * The largest mtime that was seen on the filesystem.\n *\n * This is used to determine if any files have changed since the last\n * sync. This does not include the mtime of files that are ignored.\n */\n get mtime(): number {\n return this._state.mtime;\n }\n\n /**\n * Initializes a {@linkcode FileSync} instance.\n * - Ensures the directory exists.\n * - Ensures the directory is empty or contains a `.gadget/sync.json` file (unless `options.force` is `true`)\n * - Ensures an app is specified (either via `options.app` or by prompting the user)\n * - Ensures the specified app matches the app the directory was previously synced to (unless `options.force` is `true`)\n */\n static async init({ ctx, user }: { ctx: Context<FileSyncArgs>; user: User }): Promise<FileSync> {\n ctx = ctx.clone({ name: \"filesync\" });\n\n const apps = await getApps(user);\n if (apps.length === 0) {\n throw new ArgError(\n sprint`\n You (${user.email}) don't have have any Gadget applications.\n\n Visit https://gadget.new to create one!\n `,\n );\n }\n\n let dir = ctx.args._[0];\n if (!dir) {\n // the user didn't specify a directory\n const filepath = await findUp(\".gadget/sync.json\");\n if (filepath) {\n // we found a .gadget/sync.json file, use its parent directory\n dir = path.join(filepath, \"../..\");\n } else {\n // we didn't find a .gadget/sync.json file, use the current directory\n dir = process.cwd();\n }\n }\n\n if (config.windows && dir.startsWith(\"~/\")) {\n // `~` doesn't expand to the home directory on Windows\n dir = homePath(dir.slice(2));\n }\n\n // ensure the root directory is an absolute path and exists\n const wasEmptyOrNonExistent = await isEmptyOrNonExistentDir(dir);\n await fs.ensureDir((dir = path.resolve(dir)));\n\n // try to load the .gadget/sync.json file\n const state = await fs\n .readJson(path.join(dir, \".gadget/sync.json\"))\n .then((json) =>\n z\n .object({\n app: z.string(),\n filesVersion: z.string(),\n mtime: z.number(),\n })\n .parse(json),\n )\n .catch(noop);\n\n let appSlug = ctx.args[\"--app\"] || state?.app;\n if (!appSlug) {\n // the user didn't specify an app, suggest some apps that they can sync to\n appSlug = await select({\n message: \"Select the app to sync to\",\n choices: apps.map((x) => x.slug),\n });\n }\n\n // try to find the appSlug in their list of apps\n const app = apps.find((app) => app.slug === appSlug);\n if (!app) {\n // the specified appSlug doesn't exist in their list of apps,\n // either they misspelled it or they don't have access to it\n // anymore, suggest some apps that are similar to the one they\n // specified\n const similarAppSlugs = sortBySimilar(\n appSlug,\n apps.map((app) => app.slug),\n ).slice(0, 5);\n\n throw new ArgError(\n sprint`\n Unknown application:\n\n ${appSlug}\n\n Did you mean one of these?\n\n\n `.concat(` • ${similarAppSlugs.join(\"\\n • \")}`),\n );\n }\n\n const directory = await Directory.init(dir);\n\n if (!state) {\n // the .gadget/sync.json file didn't exist or contained invalid json\n if (wasEmptyOrNonExistent || ctx.args[\"--force\"]) {\n // the directory was empty or the user passed --force\n // either way, create a fresh .gadget/sync.json file\n return new FileSync(ctx, directory, app, { app: app.slug, filesVersion: \"0\", mtime: 0 });\n }\n\n // the directory isn't empty and the user didn't pass --force\n throw new InvalidSyncFileError(dir, app.slug);\n }\n\n // the .gadget/sync.json file exists\n if (state.app === app.slug) {\n // the .gadget/sync.json file is for the same app that the user specified\n return new FileSync(ctx, directory, app, state);\n }\n\n // the .gadget/sync.json file is for a different app\n if (ctx.args[\"--force\"]) {\n // the user passed --force, so use the app they specified and overwrite everything\n return new FileSync(ctx, directory, app, { app: app.slug, filesVersion: \"0\", mtime: 0 });\n }\n\n // the user didn't pass --force, so throw an error\n throw new ArgError(sprint`\n You were about to sync the following app to the following directory:\n\n {dim ${app.slug}} → {dim ${dir}}\n\n However, that directory has already been synced with this app:\n\n {dim ${state.app}}\n\n If you're sure that you want to sync:\n\n {dim ${app.slug}} → {dim ${dir}}\n\n Then run {dim ggt sync} again with the {dim --force} flag.\n `);\n }\n\n /**\n * Waits for all pending and ongoing filesync operations to complete.\n */\n async idle(): Promise<void> {\n await this._syncOperations.onIdle();\n }\n\n /**\n * Sends file changes to the Gadget.\n *\n * @param changes - The changes to send.\n * @returns A promise that resolves when the changes have been sent.\n */\n async sendChangesToGadget({ changes }: { changes: Changes }): Promise<void> {\n await this._syncOperations.add(async () => {\n try {\n await this._sendChangesToGadget({ changes });\n } catch (error) {\n swallowFilesVersionMismatch(this.ctx, error);\n // we either sent the wrong expectedFilesVersion or we received\n // a filesVersion that is greater than the expectedFilesVersion\n // + 1, so we need to stop what we're doing and get in sync\n await this.sync();\n }\n });\n }\n\n /**\n * Subscribes to file changes on Gadget and executes the provided\n * callbacks before and after the changes occur.\n *\n * @returns A function that unsubscribes from changes on Gadget.\n */\n subscribeToGadgetChanges({\n beforeChanges,\n afterChanges,\n onError,\n }: {\n beforeChanges: (data: { changed: string[]; deleted: string[] }) => Promisable<void>;\n afterChanges: (data: { changes: Changes }) => Promisable<void>;\n onError: (error: unknown) => void;\n }): () => void {\n return this.editGraphQL.subscribe({\n query: REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n // the reason this is a function rather than a static value is\n // so that it will be re-evaluated if the connection is lost and\n // then re-established. this ensures that we send our current\n // filesVersion rather than the one that was sent when we first\n // subscribed\n variables: () => ({ localFilesVersion: String(this.filesVersion) }),\n onError,\n onData: ({ remoteFileSyncEvents: { changed, deleted, remoteFilesVersion } }) => {\n this._syncOperations\n .add(async () => {\n if (BigInt(remoteFilesVersion) < this.filesVersion) {\n this.ctx.log.warn(\"skipping received changes because files version is outdated\", { filesVersion: remoteFilesVersion });\n return;\n }\n\n this.ctx.log.debug(\"received files\", {\n remoteFilesVersion: remoteFilesVersion,\n changed: changed.map((change) => change.path),\n deleted: deleted.map((change) => change.path),\n });\n\n const filterIgnoredFiles = (file: { path: string }): boolean => {\n const ignored = this.directory.ignores(file.path);\n if (ignored) {\n this.ctx.log.warn(\"skipping received change because file is ignored\", { path: file.path });\n }\n return !ignored;\n };\n\n changed = changed.filter(filterIgnoredFiles);\n deleted = deleted.filter(filterIgnoredFiles);\n\n if (changed.length === 0 && deleted.length === 0) {\n await this._save(remoteFilesVersion);\n return;\n }\n\n await beforeChanges({\n changed: changed.map((file) => file.path),\n deleted: deleted.map((file) => file.path),\n });\n\n const changes = await this._writeToLocalFilesystem({\n filesVersion: remoteFilesVersion,\n files: changed,\n delete: deleted.map((file) => file.path),\n });\n\n if (changes.size > 0) {\n printChanges({\n message: sprint`← Received {gray ${dayjs().format(\"hh:mm:ss A\")}}`,\n changes,\n tense: \"past\",\n limit: 10,\n });\n }\n\n await afterChanges({ changes });\n })\n .catch(onError);\n },\n });\n }\n\n /**\n * Ensures the local filesystem is in sync with Gadget's filesystem.\n * - All non-conflicting changes are automatically merged.\n * - Conflicts are resolved by prompting the user to either keep their\n * local changes or keep Gadget's changes.\n * - This function will not return until the filesystem is in sync.\n */\n async sync({ maxAttempts = 10 }: { maxAttempts?: number } = {}): Promise<void> {\n let attempt = 0;\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition\n while (true) {\n const { inSync, ...hashes } = await this.hashes();\n\n if (inSync) {\n this._syncOperations.clear();\n this.ctx.log.info(\"filesystem is in sync\", { attempt });\n await this._save(hashes.gadgetFilesVersion);\n return;\n }\n\n if (attempt++ >= maxAttempts) {\n throw new TooManySyncAttemptsError(maxAttempts);\n }\n\n try {\n this.ctx.log.info(\"syncing\", { attempt, ...hashes });\n await this._sync(hashes);\n } catch (error) {\n swallowFilesVersionMismatch(this.ctx, error);\n // we either sent the wrong expectedFilesVersion or we received\n // a filesVersion that is greater than the expectedFilesVersion\n // + 1, so try again\n }\n }\n }\n\n async _sync({ filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion }: Omit<FileSyncHashes, \"inSync\">): Promise<void> {\n let localChanges = getChanges({ from: filesVersionHashes, to: localHashes, existing: gadgetHashes, ignore: [\".gadget/\"] });\n let gadgetChanges = getChanges({ from: filesVersionHashes, to: gadgetHashes, existing: localHashes });\n\n if (localChanges.size === 0 && gadgetChanges.size === 0) {\n // the local filesystem is missing .gadget/ files\n gadgetChanges = getChanges({ from: localHashes, to: gadgetHashes });\n assertAllGadgetFiles({ gadgetChanges });\n }\n\n assert(localChanges.size > 0 || gadgetChanges.size > 0, \"there must be changes if hashes don't match\");\n\n const conflicts = getConflicts({ localChanges, gadgetChanges });\n if (conflicts.size > 0) {\n this.ctx.log.debug(\"conflicts detected\", { conflicts });\n\n let preference = this.ctx.args[\"--prefer\"];\n if (!preference) {\n printConflicts({\n message: sprint`{bold You have conflicting changes with Gadget}`,\n conflicts,\n });\n\n preference = await select({\n message: \"How would you like to resolve these conflicts?\",\n choices: Object.values(ConflictPreference),\n });\n }\n\n switch (preference) {\n case ConflictPreference.CANCEL: {\n process.exit(0);\n break;\n }\n case ConflictPreference.LOCAL: {\n gadgetChanges = withoutConflictingChanges({ conflicts, changes: gadgetChanges });\n break;\n }\n case ConflictPreference.GADGET: {\n localChanges = withoutConflictingChanges({ conflicts, changes: localChanges });\n break;\n }\n }\n }\n\n if (gadgetChanges.size > 0) {\n await this._getChangesFromGadget({ changes: gadgetChanges, filesVersion: gadgetFilesVersion });\n }\n\n if (localChanges.size > 0) {\n await this._sendChangesToGadget({ changes: localChanges, expectedFilesVersion: gadgetFilesVersion });\n }\n }\n\n async hashes(): Promise<FileSyncHashes> {\n const [localHashes, { filesVersionHashes, gadgetHashes, gadgetFilesVersion }] = await Promise.all([\n // get the hashes of our local files\n this.directory.hashes(),\n // get the hashes of our local filesVersion and the latest filesVersion\n (async () => {\n let gadgetFilesVersion: bigint;\n let gadgetHashes: Hashes;\n let filesVersionHashes: Hashes;\n\n if (this.filesVersion === 0n) {\n // this is the first time we're syncing, so just get the\n // hashes of the latest filesVersion\n const { fileSyncHashes } = await this.editGraphQL.query({ query: FILE_SYNC_HASHES_QUERY });\n gadgetFilesVersion = BigInt(fileSyncHashes.filesVersion);\n gadgetHashes = fileSyncHashes.hashes;\n filesVersionHashes = {};\n } else {\n // this isn't the first time we're syncing, so get the hashes\n // of the files at our local filesVersion and the latest\n // filesVersion\n const { fileSyncComparisonHashes } = await this.editGraphQL.query({\n query: FILE_SYNC_COMPARISON_HASHES_QUERY,\n variables: { filesVersion: String(this.filesVersion) },\n });\n gadgetFilesVersion = BigInt(fileSyncComparisonHashes.latestFilesVersionHashes.filesVersion);\n gadgetHashes = fileSyncComparisonHashes.latestFilesVersionHashes.hashes;\n filesVersionHashes = fileSyncComparisonHashes.filesVersionHashes.hashes;\n }\n\n return { filesVersionHashes, gadgetHashes, gadgetFilesVersion };\n })(),\n ]);\n\n return { filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion, inSync: isEqualHashes(localHashes, gadgetHashes) };\n }\n\n private async _getChangesFromGadget({\n filesVersion,\n changes,\n }: {\n filesVersion: bigint;\n changes: Changes | ChangesWithHash;\n }): Promise<void> {\n this.ctx.log.debug(\"getting changes from gadget\", { filesVersion, changes });\n const created = changes.created();\n const updated = changes.updated();\n\n let files: File[] = [];\n if (created.length > 0 || updated.length > 0) {\n const { fileSyncFiles } = await this.editGraphQL.query({\n query: FILE_SYNC_FILES_QUERY,\n variables: {\n paths: [...created, ...updated],\n filesVersion: String(filesVersion),\n encoding: FileSyncEncoding.Base64,\n },\n });\n\n files = fileSyncFiles.files;\n }\n\n await this._writeToLocalFilesystem({\n filesVersion,\n files,\n delete: changes.deleted(),\n });\n\n printChanges({\n changes,\n tense: \"past\",\n message: sprint`← Received {gray ${dayjs().format(\"hh:mm:ss A\")}}`,\n });\n }\n\n private async _sendChangesToGadget({\n expectedFilesVersion = this.filesVersion,\n changes,\n printLimit,\n }: {\n expectedFilesVersion?: bigint;\n changes: Changes;\n printLimit?: number;\n }): Promise<void> {\n this.ctx.log.debug(\"sending changes to gadget\", { expectedFilesVersion, changes });\n const changed: FileSyncChangedEventInput[] = [];\n const deleted: FileSyncDeletedEventInput[] = [];\n\n await pMap(changes, async ([normalizedPath, change]) => {\n if (change.type === \"delete\") {\n deleted.push({ path: normalizedPath });\n return;\n }\n\n const absolutePath = this.directory.absolute(normalizedPath);\n\n let stats;\n try {\n stats = await fs.stat(absolutePath);\n } catch (error) {\n swallowEnoent(error);\n this.ctx.log.debug(\"skipping change because file doesn't exist\", { path: normalizedPath });\n return;\n }\n\n let content = \"\";\n if (stats.isFile()) {\n content = await fs.readFile(absolutePath, FileSyncEncoding.Base64);\n }\n\n let oldPath;\n if (change.type === \"create\" && change.oldPath) {\n oldPath = change.oldPath;\n }\n\n changed.push({\n content,\n oldPath,\n path: normalizedPath,\n mode: stats.mode,\n encoding: FileSyncEncoding.Base64,\n });\n });\n\n if (changed.length === 0 && deleted.length === 0) {\n this.ctx.log.debug(\"skipping send because there are no changes\");\n return;\n }\n\n const {\n publishFileSyncEvents: { remoteFilesVersion },\n } = await this.editGraphQL.query({\n query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n variables: {\n input: {\n expectedRemoteFilesVersion: String(expectedFilesVersion),\n changed,\n deleted,\n },\n },\n http: {\n retry: {\n // we can retry this request because\n // expectedRemoteFilesVersion makes it idempotent\n methods: [\"POST\"],\n calculateDelay: ({ error, computedValue }) => {\n if (isFilesVersionMismatchError(error.response?.body)) {\n // don't retry if we get a files version mismatch error\n return 0;\n }\n return computedValue;\n },\n },\n },\n });\n\n printChanges({\n changes,\n tense: \"past\",\n message: sprint`→ Sent {gray ${dayjs().format(\"hh:mm:ss A\")}}`,\n limit: printLimit,\n });\n\n if (BigInt(remoteFilesVersion) > expectedFilesVersion + 1n) {\n // we can't save the remoteFilesVersion because we haven't\n // received the intermediate filesVersions yet\n throw new Error(\"Files version mismatch\");\n }\n\n await this._save(remoteFilesVersion);\n }\n\n private async _writeToLocalFilesystem(options: { filesVersion: bigint | string; files: File[]; delete: string[] }): Promise<Changes> {\n const filesVersion = BigInt(options.filesVersion);\n assert(filesVersion >= this.filesVersion, \"filesVersion must be greater than or equal to current filesVersion\");\n\n this.ctx.log.debug(\"writing to local filesystem\", {\n filesVersion,\n files: options.files.map((file) => file.path),\n delete: options.delete,\n });\n\n const created: string[] = [];\n const updated: string[] = [];\n\n await pMap(options.delete, async (filepath) => {\n const currentPath = this.directory.absolute(filepath);\n const backupPath = this.directory.absolute(\".gadget/backup\", this.directory.relative(filepath));\n\n // rather than `rm -rf`ing files, we move them to\n // `.gadget/backup/` so that users can recover them if something\n // goes wrong. We've seen a lot of EBUSY/EINVAL errors when moving\n // files so we retry a few times.\n await pRetry(\n async () => {\n try {\n // remove the current backup file in case it exists and is a\n // different type (file vs directory)\n await fs.remove(backupPath);\n await fs.move(currentPath, backupPath);\n } catch (error) {\n // replicate the behavior of `rm -rf` and ignore ENOENT\n swallowEnoent(error);\n }\n },\n {\n retries: 2,\n minTimeout: ms(\"100ms\"),\n onFailedAttempt: (error) => {\n this.ctx.log.warn(\"failed to move file to backup\", { error, currentPath, backupPath });\n },\n },\n );\n });\n\n await pMap(options.files, async (file) => {\n const absolutePath = this.directory.absolute(file.path);\n if (await fs.pathExists(absolutePath)) {\n updated.push(file.path);\n } else {\n created.push(file.path);\n }\n\n if (file.path.endsWith(\"/\")) {\n await fs.ensureDir(absolutePath);\n } else {\n await fs.outputFile(absolutePath, Buffer.from(file.content, file.encoding));\n }\n\n if (supportsPermissions) {\n // the os's default umask makes setting the mode during creation\n // not work, so an additional fs.chmod call is necessary to\n // ensure the file has the correct mode\n await fs.chmod(absolutePath, file.mode & 0o777);\n }\n\n if (absolutePath === this.directory.absolute(\".ignore\")) {\n await this.directory.loadIgnoreFile();\n }\n });\n\n await this._save(String(filesVersion));\n\n return new Changes([\n ...created.map((path) => [path, { type: \"create\" }] as const),\n ...updated.map((path) => [path, { type: \"update\" }] as const),\n ...options.delete.map((path) => [path, { type: \"delete\" }] as const),\n ]);\n }\n\n /**\n * Updates {@linkcode _state} and saves it to `.gadget/sync.json`.\n */\n private async _save(filesVersion: string | bigint): Promise<void> {\n this._state = { ...this._state, mtime: Date.now() + 1, filesVersion: String(filesVersion) };\n this.ctx.log.debug(\"saving state\", { state: this._state });\n await fs.outputJSON(this.directory.absolute(\".gadget/sync.json\"), this._state, { spaces: 2 });\n }\n}\n\n/**\n * Checks if a directory is empty or non-existent.\n *\n * @param dir - The directory path to check.\n * @returns A Promise that resolves to a boolean indicating whether the directory is empty or non-existent.\n */\nexport const isEmptyOrNonExistentDir = async (dir: string): Promise<boolean> => {\n try {\n for await (const _ of await fs.opendir(dir, { bufferSize: 1 })) {\n return false;\n }\n return true;\n } catch (error) {\n swallowEnoent(error);\n return true;\n }\n};\n\nexport const assertAllGadgetFiles = ({ gadgetChanges }: { gadgetChanges: Changes }): void => {\n assert(\n gadgetChanges.created().length > 0 || gadgetChanges.deleted().length > 0 || gadgetChanges.updated().length > 0,\n \"expected gadgetChanges to have changes\",\n );\n\n const allGadgetFiles = Array.from(gadgetChanges.keys()).every((path) => path.startsWith(\".gadget/\"));\n assert(allGadgetFiles, \"expected all gadgetChanges to be .gadget/ files\");\n};\n\nexport const ConflictPreference = Object.freeze({\n CANCEL: \"Cancel (Ctrl+C)\",\n LOCAL: \"Keep my conflicting changes\",\n GADGET: \"Keep Gadget's conflicting changes\",\n});\n\nexport type ConflictPreference = (typeof ConflictPreference)[keyof typeof ConflictPreference];\n\nexport const ConflictPreferenceArg = (value: string, name: string): ConflictPreference => {\n if ([\"local\", \"gadget\"].includes(value)) {\n return ConflictPreference[value.toUpperCase() as keyof typeof ConflictPreference];\n }\n\n throw new ArgError(sprint`\n ${name} must be {bold local} or {bold gadget}\n\n {bold EXAMPLES:}\n ${name} local\n ${name} gadget\n `);\n};\n\nexport const FileSyncArgs = {\n \"--app\": { type: AppArg, alias: \"-a\" },\n \"--prefer\": ConflictPreferenceArg,\n \"--force\": Boolean,\n} satisfies ArgsSpec;\n\nexport type FileSyncArgs = typeof FileSyncArgs;\n\nexport const isFilesVersionMismatchError = (error: unknown): boolean => {\n if (error instanceof EditGraphQLError) {\n error = error.cause;\n }\n if (isGraphQLResult(error)) {\n error = error.errors;\n }\n if (isGraphQLErrors(error)) {\n error = error[0];\n }\n return isObject(error) && \"message\" in error && isString(error.message) && error.message.startsWith(\"Files version mismatch\");\n};\n\nconst swallowFilesVersionMismatch = (ctx: Context, error: unknown): void => {\n if (isFilesVersionMismatchError(error)) {\n ctx.log.debug(\"swallowing files version mismatch\", { error });\n return;\n }\n throw error;\n};\n"],"names":["dayjs","findUp","fs","ms","assert","path","process","pMap","PQueue","pRetry","z","FileSyncEncoding","getApps","AppArg","EditGraphQL","EditGraphQLError","FILE_SYNC_COMPARISON_HASHES_QUERY","FILE_SYNC_FILES_QUERY","FILE_SYNC_HASHES_QUERY","PUBLISH_FILE_SYNC_EVENTS_MUTATION","REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION","ArgError","config","homePath","select","sprint","sortBySimilar","noop","isGraphQLErrors","isGraphQLResult","isObject","isString","Changes","printChanges","getConflicts","printConflicts","withoutConflictingChanges","Directory","supportsPermissions","swallowEnoent","InvalidSyncFileError","TooManySyncAttemptsError","getChanges","isEqualHashes","FileSync","filesVersion","BigInt","_state","mtime","init","ctx","user","clone","name","apps","length","email","dir","args","_","filepath","join","cwd","windows","startsWith","slice","wasEmptyOrNonExistent","isEmptyOrNonExistentDir","ensureDir","resolve","state","readJson","then","json","object","app","string","number","parse","catch","appSlug","message","choices","map","x","slug","find","similarAppSlugs","concat","directory","idle","_syncOperations","onIdle","sendChangesToGadget","changes","add","_sendChangesToGadget","error","swallowFilesVersionMismatch","sync","subscribeToGadgetChanges","beforeChanges","afterChanges","onError","editGraphQL","subscribe","query","variables","localFilesVersion","String","onData","remoteFileSyncEvents","changed","deleted","remoteFilesVersion","log","warn","debug","change","filterIgnoredFiles","file","ignored","ignores","filter","_save","_writeToLocalFilesystem","files","delete","size","format","tense","limit","maxAttempts","attempt","inSync","hashes","clear","info","gadgetFilesVersion","_sync","filesVersionHashes","localHashes","gadgetHashes","localChanges","from","to","existing","ignore","gadgetChanges","assertAllGadgetFiles","conflicts","preference","Object","values","ConflictPreference","CANCEL","exit","LOCAL","GADGET","_getChangesFromGadget","expectedFilesVersion","Promise","all","fileSyncHashes","fileSyncComparisonHashes","latestFilesVersionHashes","created","updated","fileSyncFiles","paths","encoding","Base64","printLimit","normalizedPath","type","push","absolutePath","absolute","stats","stat","content","isFile","readFile","oldPath","mode","publishFileSyncEvents","input","expectedRemoteFilesVersion","http","retry","methods","calculateDelay","computedValue","isFilesVersionMismatchError","response","body","Error","options","currentPath","backupPath","relative","remove","move","retries","minTimeout","onFailedAttempt","pathExists","endsWith","outputFile","Buffer","chmod","loadIgnoreFile","Date","now","outputJSON","spaces","concurrency","opendir","bufferSize","allGadgetFiles","Array","keys","every","freeze","ConflictPreferenceArg","value","includes","toUpperCase","FileSyncArgs","alias","Boolean","cause","errors"],"mappings":"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"}
@@ -1,7 +1,10 @@
1
1
  import { got } from "got";
2
+ import { Agent as HttpAgent } from "node:http";
3
+ import { Agent as HttpsAgent } from "node:https";
2
4
  import { config } from "../config/config.js";
3
5
  import { createLogger } from "../output/log/logger.js";
4
6
  import { writeSession } from "../user/session.js";
7
+ import { serializeError } from "../util/object.js";
5
8
  import { isGadgetServicesRequest } from "./auth.js";
6
9
  export const log = createLogger({
7
10
  name: "http"
@@ -10,6 +13,14 @@ export const log = createLogger({
10
13
  * An instance of the `got` library with hooks for logging and handling
11
14
  * 401 errors. This should be used for all HTTP requests.
12
15
  */ export const http = got.extend({
16
+ agent: {
17
+ http: new HttpAgent({
18
+ keepAlive: true
19
+ }),
20
+ https: new HttpsAgent({
21
+ keepAlive: true
22
+ })
23
+ },
13
24
  hooks: {
14
25
  beforeRequest: [
15
26
  (options)=>{
@@ -26,11 +37,7 @@ export const log = createLogger({
26
37
  (error, retryCount)=>{
27
38
  log.warn("http request failed, retrying...", {
28
39
  retryCount,
29
- error: {
30
- code: error.code,
31
- name: error.name,
32
- message: error.message
33
- },
40
+ error: serializeError(error),
34
41
  request: error.request && {
35
42
  method: error.request.options.method,
36
43
  url: error.request.options.url?.toString()