@gadgetinc/ggt 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,7 +56,7 @@ $ ggt
56
56
  The command-line interface for Gadget
57
57
 
58
58
  VERSION
59
- ggt/0.3.0 linux-x64 node-v16.20.2
59
+ ggt/0.3.2 linux-x64 node-v16.20.2
60
60
 
61
61
  USAGE
62
62
  $ ggt [COMMAND]
@@ -66,12 +66,25 @@ export const run = async ()=>{
66
66
  // send the session to the server we just started.
67
67
  const url = new URL(`https://${config.domains.services}/auth/login`);
68
68
  url.searchParams.set("returnTo", `https://${config.domains.services}/auth/cli/callback?port=${port}`);
69
- await open(url.toString());
70
- println`
69
+ try {
70
+ await open(url.toString());
71
+ println`
71
72
  We've opened Gadget's login page using your default browser.
72
73
 
73
74
  Please log in and then return to this terminal.\n
74
75
  `;
76
+ } catch (error) {
77
+ log.error("failed to open browser", {
78
+ error
79
+ });
80
+ println`
81
+ Please open the following URL in your browser and log in:
82
+
83
+ {gray ${url.toString()}}
84
+
85
+ Once logged in, return to this terminal.\n
86
+ `;
87
+ }
75
88
  await receiveSession;
76
89
  } finally{
77
90
  server?.close();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/login.ts"],"sourcesContent":["import getPort from \"get-port\";\nimport assert from \"node:assert\";\nimport http, { type Server } from \"node:http\";\nimport open from \"open\";\nimport { config } from \"../services/config.js\";\nimport { createLogger } from \"../services/log.js\";\nimport { println, sprint } from \"../services/output.js\";\nimport { writeSession } from \"../services/session.js\";\nimport { getUser } from \"../services/user.js\";\n\nexport const usage = sprint`\n Log in to your account.\n\n {bold USAGE}\n $ ggt login\n\n {bold EXAMPLES}\n {gray $ ggt login}\n We've opened Gadget's login page using your default browser.\n\n Please log in and then return to this terminal.\n\n Hello, Jane Doe (jane@example.com)\n`;\n\nconst log = createLogger(\"login\");\n\nexport const run = async () => {\n let server: Server | undefined;\n\n try {\n const port = await getPort();\n const receiveSession = new Promise<void>((resolve, reject) => {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n server = http.createServer(async (req, res) => {\n const landingPage = new URL(`https://${config.domains.services}/auth/cli`);\n\n try {\n assert(req.url, \"missing url\");\n const session = new URL(req.url, `http://localhost:${port}`).searchParams.get(\"session\");\n assert(session, \"missing session\");\n\n writeSession(session);\n\n const user = await getUser();\n assert(user, \"missing user after successful login\");\n\n if (user.name) {\n println`Hello, ${user.name} {gray (${user.email})}`;\n } else {\n println`Hello, ${user.email}`;\n }\n println();\n\n landingPage.searchParams.set(\"success\", \"true\");\n resolve();\n } catch (error) {\n writeSession(undefined);\n landingPage.searchParams.set(\"success\", \"false\");\n reject(error);\n } finally {\n res.writeHead(303, { Location: landingPage.toString() });\n res.end();\n }\n });\n\n log.info(\"starting login server\", { port });\n server.listen(port);\n });\n\n // open the login page in the user's default browser have it\n // redirect to the cli callback route. The cli callback route will\n // send the session to the server we just started.\n const url = new URL(`https://${config.domains.services}/auth/login`);\n url.searchParams.set(\"returnTo\", `https://${config.domains.services}/auth/cli/callback?port=${port}`);\n await open(url.toString());\n\n println`\n We've opened Gadget's login page using your default browser.\n\n Please log in and then return to this terminal.\\n\n `;\n\n await receiveSession;\n } finally {\n server?.close();\n }\n};\n"],"names":["getPort","assert","http","open","config","createLogger","println","sprint","writeSession","getUser","usage","log","run","server","port","receiveSession","Promise","resolve","reject","createServer","req","res","landingPage","URL","domains","services","url","session","searchParams","get","user","name","email","set","error","undefined","writeHead","Location","toString","end","info","listen","close"],"mappings":"AAAA,OAAOA,aAAa,WAAW;AAC/B,OAAOC,YAAY,cAAc;AACjC,OAAOC,UAA2B,YAAY;AAC9C,OAAOC,UAAU,OAAO;AACxB,SAASC,MAAM,QAAQ,wBAAwB;AAC/C,SAASC,YAAY,QAAQ,qBAAqB;AAClD,SAASC,OAAO,EAAEC,MAAM,QAAQ,wBAAwB;AACxD,SAASC,YAAY,QAAQ,yBAAyB;AACtD,SAASC,OAAO,QAAQ,sBAAsB;AAE9C,OAAO,MAAMC,QAAQH,MAAM,CAAC;;;;;;;;;;;;;AAa5B,CAAC,CAAC;AAEF,MAAMI,MAAMN,aAAa;AAEzB,OAAO,MAAMO,MAAM;IACjB,IAAIC;IAEJ,IAAI;QACF,MAAMC,OAAO,MAAMd;QACnB,MAAMe,iBAAiB,IAAIC,QAAc,CAACC,SAASC;YACjD,kEAAkE;YAClEL,SAASX,KAAKiB,YAAY,CAAC,OAAOC,KAAKC;gBACrC,MAAMC,cAAc,IAAIC,IAAI,CAAC,QAAQ,EAAEnB,OAAOoB,OAAO,CAACC,QAAQ,CAAC,SAAS,CAAC;gBAEzE,IAAI;oBACFxB,OAAOmB,IAAIM,GAAG,EAAE;oBAChB,MAAMC,UAAU,IAAIJ,IAAIH,IAAIM,GAAG,EAAE,CAAC,iBAAiB,EAAEZ,KAAK,CAAC,EAAEc,YAAY,CAACC,GAAG,CAAC;oBAC9E5B,OAAO0B,SAAS;oBAEhBnB,aAAamB;oBAEb,MAAMG,OAAO,MAAMrB;oBACnBR,OAAO6B,MAAM;oBAEb,IAAIA,KAAKC,IAAI,EAAE;wBACbzB,OAAO,CAAC,OAAO,EAAEwB,KAAKC,IAAI,CAAC,QAAQ,EAAED,KAAKE,KAAK,CAAC,EAAE,CAAC;oBACrD,OAAO;wBACL1B,OAAO,CAAC,OAAO,EAAEwB,KAAKE,KAAK,CAAC,CAAC;oBAC/B;oBACA1B;oBAEAgB,YAAYM,YAAY,CAACK,GAAG,CAAC,WAAW;oBACxChB;gBACF,EAAE,OAAOiB,OAAO;oBACd1B,aAAa2B;oBACbb,YAAYM,YAAY,CAACK,GAAG,CAAC,WAAW;oBACxCf,OAAOgB;gBACT,SAAU;oBACRb,IAAIe,SAAS,CAAC,KAAK;wBAAEC,UAAUf,YAAYgB,QAAQ;oBAAG;oBACtDjB,IAAIkB,GAAG;gBACT;YACF;YAEA5B,IAAI6B,IAAI,CAAC,yBAAyB;gBAAE1B;YAAK;YACzCD,OAAO4B,MAAM,CAAC3B;QAChB;QAEA,4DAA4D;QAC5D,kEAAkE;QAClE,kDAAkD;QAClD,MAAMY,MAAM,IAAIH,IAAI,CAAC,QAAQ,EAAEnB,OAAOoB,OAAO,CAACC,QAAQ,CAAC,WAAW,CAAC;QACnEC,IAAIE,YAAY,CAACK,GAAG,CAAC,YAAY,CAAC,QAAQ,EAAE7B,OAAOoB,OAAO,CAACC,QAAQ,CAAC,wBAAwB,EAAEX,KAAK,CAAC;QACpG,MAAMX,KAAKuB,IAAIY,QAAQ;QAEvBhC,OAAO,CAAC;;;;IAIR,CAAC;QAED,MAAMS;IACR,SAAU;QACRF,QAAQ6B;IACV;AACF,EAAE"}
1
+ {"version":3,"sources":["../../src/commands/login.ts"],"sourcesContent":["import getPort from \"get-port\";\nimport assert from \"node:assert\";\nimport http, { type Server } from \"node:http\";\nimport open from \"open\";\nimport { config } from \"../services/config.js\";\nimport { createLogger } from \"../services/log.js\";\nimport { println, sprint } from \"../services/output.js\";\nimport { writeSession } from \"../services/session.js\";\nimport { getUser } from \"../services/user.js\";\n\nexport const usage = sprint`\n Log in to your account.\n\n {bold USAGE}\n $ ggt login\n\n {bold EXAMPLES}\n {gray $ ggt login}\n We've opened Gadget's login page using your default browser.\n\n Please log in and then return to this terminal.\n\n Hello, Jane Doe (jane@example.com)\n`;\n\nconst log = createLogger(\"login\");\n\nexport const run = async () => {\n let server: Server | undefined;\n\n try {\n const port = await getPort();\n const receiveSession = new Promise<void>((resolve, reject) => {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n server = http.createServer(async (req, res) => {\n const landingPage = new URL(`https://${config.domains.services}/auth/cli`);\n\n try {\n assert(req.url, \"missing url\");\n const session = new URL(req.url, `http://localhost:${port}`).searchParams.get(\"session\");\n assert(session, \"missing session\");\n\n writeSession(session);\n\n const user = await getUser();\n assert(user, \"missing user after successful login\");\n\n if (user.name) {\n println`Hello, ${user.name} {gray (${user.email})}`;\n } else {\n println`Hello, ${user.email}`;\n }\n println();\n\n landingPage.searchParams.set(\"success\", \"true\");\n resolve();\n } catch (error) {\n writeSession(undefined);\n landingPage.searchParams.set(\"success\", \"false\");\n reject(error);\n } finally {\n res.writeHead(303, { Location: landingPage.toString() });\n res.end();\n }\n });\n\n log.info(\"starting login server\", { port });\n server.listen(port);\n });\n\n // open the login page in the user's default browser have it\n // redirect to the cli callback route. The cli callback route will\n // send the session to the server we just started.\n const url = new URL(`https://${config.domains.services}/auth/login`);\n url.searchParams.set(\"returnTo\", `https://${config.domains.services}/auth/cli/callback?port=${port}`);\n\n try {\n await open(url.toString());\n println`\n We've opened Gadget's login page using your default browser.\n\n Please log in and then return to this terminal.\\n\n `;\n } catch (error) {\n log.error(\"failed to open browser\", { error });\n println`\n Please open the following URL in your browser and log in:\n\n {gray ${url.toString()}}\n\n Once logged in, return to this terminal.\\n\n `;\n }\n\n await receiveSession;\n } finally {\n server?.close();\n }\n};\n"],"names":["getPort","assert","http","open","config","createLogger","println","sprint","writeSession","getUser","usage","log","run","server","port","receiveSession","Promise","resolve","reject","createServer","req","res","landingPage","URL","domains","services","url","session","searchParams","get","user","name","email","set","error","undefined","writeHead","Location","toString","end","info","listen","close"],"mappings":"AAAA,OAAOA,aAAa,WAAW;AAC/B,OAAOC,YAAY,cAAc;AACjC,OAAOC,UAA2B,YAAY;AAC9C,OAAOC,UAAU,OAAO;AACxB,SAASC,MAAM,QAAQ,wBAAwB;AAC/C,SAASC,YAAY,QAAQ,qBAAqB;AAClD,SAASC,OAAO,EAAEC,MAAM,QAAQ,wBAAwB;AACxD,SAASC,YAAY,QAAQ,yBAAyB;AACtD,SAASC,OAAO,QAAQ,sBAAsB;AAE9C,OAAO,MAAMC,QAAQH,MAAM,CAAC;;;;;;;;;;;;;AAa5B,CAAC,CAAC;AAEF,MAAMI,MAAMN,aAAa;AAEzB,OAAO,MAAMO,MAAM;IACjB,IAAIC;IAEJ,IAAI;QACF,MAAMC,OAAO,MAAMd;QACnB,MAAMe,iBAAiB,IAAIC,QAAc,CAACC,SAASC;YACjD,kEAAkE;YAClEL,SAASX,KAAKiB,YAAY,CAAC,OAAOC,KAAKC;gBACrC,MAAMC,cAAc,IAAIC,IAAI,CAAC,QAAQ,EAAEnB,OAAOoB,OAAO,CAACC,QAAQ,CAAC,SAAS,CAAC;gBAEzE,IAAI;oBACFxB,OAAOmB,IAAIM,GAAG,EAAE;oBAChB,MAAMC,UAAU,IAAIJ,IAAIH,IAAIM,GAAG,EAAE,CAAC,iBAAiB,EAAEZ,KAAK,CAAC,EAAEc,YAAY,CAACC,GAAG,CAAC;oBAC9E5B,OAAO0B,SAAS;oBAEhBnB,aAAamB;oBAEb,MAAMG,OAAO,MAAMrB;oBACnBR,OAAO6B,MAAM;oBAEb,IAAIA,KAAKC,IAAI,EAAE;wBACbzB,OAAO,CAAC,OAAO,EAAEwB,KAAKC,IAAI,CAAC,QAAQ,EAAED,KAAKE,KAAK,CAAC,EAAE,CAAC;oBACrD,OAAO;wBACL1B,OAAO,CAAC,OAAO,EAAEwB,KAAKE,KAAK,CAAC,CAAC;oBAC/B;oBACA1B;oBAEAgB,YAAYM,YAAY,CAACK,GAAG,CAAC,WAAW;oBACxChB;gBACF,EAAE,OAAOiB,OAAO;oBACd1B,aAAa2B;oBACbb,YAAYM,YAAY,CAACK,GAAG,CAAC,WAAW;oBACxCf,OAAOgB;gBACT,SAAU;oBACRb,IAAIe,SAAS,CAAC,KAAK;wBAAEC,UAAUf,YAAYgB,QAAQ;oBAAG;oBACtDjB,IAAIkB,GAAG;gBACT;YACF;YAEA5B,IAAI6B,IAAI,CAAC,yBAAyB;gBAAE1B;YAAK;YACzCD,OAAO4B,MAAM,CAAC3B;QAChB;QAEA,4DAA4D;QAC5D,kEAAkE;QAClE,kDAAkD;QAClD,MAAMY,MAAM,IAAIH,IAAI,CAAC,QAAQ,EAAEnB,OAAOoB,OAAO,CAACC,QAAQ,CAAC,WAAW,CAAC;QACnEC,IAAIE,YAAY,CAACK,GAAG,CAAC,YAAY,CAAC,QAAQ,EAAE7B,OAAOoB,OAAO,CAACC,QAAQ,CAAC,wBAAwB,EAAEX,KAAK,CAAC;QAEpG,IAAI;YACF,MAAMX,KAAKuB,IAAIY,QAAQ;YACvBhC,OAAO,CAAC;;;;IAIV,CAAC;QACD,EAAE,OAAO4B,OAAO;YACdvB,IAAIuB,KAAK,CAAC,0BAA0B;gBAAEA;YAAM;YAC5C5B,OAAO,CAAC;;;gBAGE,EAAEoB,IAAIY,QAAQ,GAAG;;;MAG3B,CAAC;QACH;QAEA,MAAMvB;IACR,SAAU;QACRF,QAAQ6B;IACV;AACF,EAAE"}
@@ -13,8 +13,8 @@ import FSWatcher from "watcher";
13
13
  import which from "which";
14
14
  import { FileSyncEncoding } from "../__generated__/graphql.js";
15
15
  import { AppArg } from "../services/args.js";
16
- import { Client } from "../services/client.js";
17
16
  import { config } from "../services/config.js";
17
+ import { EditGraphQL } from "../services/edit-graphql.js";
18
18
  import { YarnNotFoundError } from "../services/errors.js";
19
19
  import { FileSync, PUBLISH_FILE_SYNC_EVENTS_MUTATION, REMOTE_FILES_VERSION_QUERY, REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION, printPaths } from "../services/filesync.js";
20
20
  import { swallowEnoent } from "../services/fs-utils.js";
@@ -149,15 +149,15 @@ export class Sync {
149
149
  }
150
150
  const user = await getUserOrLogin();
151
151
  this.filesync = await FileSync.init(user, {
152
- dir: rootArgs._[0],
152
+ dir: this.args._[0],
153
153
  app: this.args["--app"],
154
154
  force: this.args["--force"],
155
155
  extraIgnorePaths: [
156
156
  ".gadget"
157
157
  ]
158
158
  });
159
- this.client = new Client(this.filesync.app);
160
- const { remoteFilesVersion } = await this.client.queryUnwrap({
159
+ this.graphql = new EditGraphQL(this.filesync.app);
160
+ const { remoteFilesVersion } = await this.graphql.query({
161
161
  query: REMOTE_FILES_VERSION_QUERY
162
162
  });
163
163
  const hasRemoteChanges = BigInt(remoteFilesVersion) > this.filesync.filesVersion;
@@ -216,7 +216,7 @@ export class Sync {
216
216
  // will cause us to receive the remote files that have changed
217
217
  // since the last sync (+ the local files that we just
218
218
  // published)
219
- await this.client.queryUnwrap({
219
+ await this.graphql.query({
220
220
  query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,
221
221
  variables: {
222
222
  input: {
@@ -283,7 +283,7 @@ export class Sync {
283
283
  } finally{
284
284
  await Promise.allSettled([
285
285
  this.watcher.close(),
286
- this.client.dispose()
286
+ this.graphql.dispose()
287
287
  ]);
288
288
  this.status = 3;
289
289
  stopped.resolve();
@@ -308,7 +308,7 @@ export class Sync {
308
308
  }, 100).unref();
309
309
  });
310
310
  }
311
- const unsubscribe = this.client.subscribeUnwrap({
311
+ const unsubscribe = this.graphql.subscribe({
312
312
  query: REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,
313
313
  variables: ()=>({
314
314
  localFilesVersion: String(this.filesync.filesVersion)
@@ -390,7 +390,7 @@ export class Sync {
390
390
  if (!changed.length && !deleted.length) {
391
391
  return;
392
392
  }
393
- const { publishFileSyncEvents } = await this.client.queryUnwrap({
393
+ const { publishFileSyncEvents } = await this.graphql.query({
394
394
  query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,
395
395
  variables: {
396
396
  input: {
@@ -525,7 +525,7 @@ export class Sync {
525
525
  }));
526
526
  /**
527
527
  * A GraphQL client connected to the app's /edit/api/graphql-ws endpoint
528
- */ _define_property(this, "client", void 0);
528
+ */ _define_property(this, "graphql", void 0);
529
529
  /**
530
530
  * Watches the local filesystem for changes.
531
531
  */ _define_property(this, "watcher", void 0);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/sync.ts"],"sourcesContent":["import arg from \"arg\";\nimport { format as formatDate, isAfter } from \"date-fns\";\nimport { execa } from \"execa\";\nimport type { Stats } from \"fs-extra\";\nimport fs from \"fs-extra\";\nimport inquirer from \"inquirer\";\nimport _ from \"lodash\";\nimport ms from \"ms\";\nimport pMap from \"p-map\";\nimport PQueue from \"p-queue\";\nimport path from \"path\";\nimport FSWatcher from \"watcher\";\nimport which from \"which\";\nimport { FileSyncEncoding, type FileSyncChangedEventInput, type FileSyncDeletedEventInput } from \"../__generated__/graphql.js\";\nimport { AppArg } from \"../services/args.js\";\nimport { Client } from \"../services/client.js\";\nimport { config } from \"../services/config.js\";\nimport { YarnNotFoundError } from \"../services/errors.js\";\nimport {\n FileSync,\n PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n REMOTE_FILES_VERSION_QUERY,\n REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n printPaths,\n} from \"../services/filesync.js\";\nimport { swallowEnoent } from \"../services/fs-utils.js\";\nimport { createLogger } from \"../services/log.js\";\nimport { notify } from \"../services/notify.js\";\nimport { println, sprint } from \"../services/output.js\";\nimport { PromiseSignal } from \"../services/promise.js\";\nimport { getUserOrLogin } from \"../services/user.js\";\nimport { type RootArgs } from \"./root.js\";\n\nexport const usage = sprint`\n Sync your Gadget application's source code to and from\n your local filesystem.\n\n {bold USAGE}\n $ ggt sync [DIRECTORY] [--app <name>]\n\n {bold ARGUMENTS}\n DIRECTORY {dim [default: .] The directory to sync files to.\n\n If the directory doesn't exist, it will be created.}\n\n {bold FLAGS}\n -a, --app=<name> {dim The Gadget application to sync files to.}\n\n --force {dim Whether to sync even if we can't determine\n the state of your local files relative to\n your remote ones.}\n\n {bold DESCRIPTION}\n Sync provides the ability to sync your Gadget application's source\n code to and from your local filesystem.\n\n While ggt sync is running, local file changes are immediately\n reflected within Gadget, while files that are changed remotely are\n immediately saved to your local filesystem.\n\n Use cases for this include:\n • Developing locally with your own editor like VSCode\n • Storing your source code in a Git repository like GitHub\n\n Sync includes the concept of a {dim .ignore} file. This file may\n contain a list of files and directories that won't be received or\n sent to Gadget when syncing. The format of this file is identical\n to the one used by Git {dim (https://git-scm.com/docs/gitignore)}.\n\n The following files and directories are always ignored:\n • .DS_Store\n • .gadget\n • .git\n • node_modules\n\n Note:\n • If you have separate development and production environments,\n {dim ggt sync} will only sync with your development environment\n • Gadget applications only support installing dependencies\n with Yarn 1 {dim (https://classic.yarnpkg.com/lang/en/)}\n • Since file changes are immediately reflected in Gadget,\n avoid the following while {dim ggt sync} is running:\n • Deleting all your files\n • Moving all your files to a different directory\n\n {bold EXAMPLES}\n {dim $ ggt sync --app my-app ~/gadget/my-app}\n\n App my-app\n Editor https://my-app.gadget.app/edit\n Playground https://my-app.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/my-app\n\n Endpoints\n • https://my-app.gadget.app\n • https://my-app--development.gadget.app\n\n Watching for file changes... {dim Press Ctrl+C to stop}\n\n Received {dim 12:00:00 PM}\n {green ←} routes/GET.js {dim (changed)}\n {green ←} user/signUp/signIn.js {dim (changed)}\n {dim 2 files in total. 2 changed, 0 deleted.}\n\n Sent {dim 12:00:03 PM}\n {green →} routes/GET.ts {dim (changed)}\n {dim 1 file in total. 1 changed, 0 deleted.}\n\n ^C Stopping... {dim (press Ctrl+C again to force)}\n Goodbye!\n`;\n\nexport enum SyncStatus {\n STARTING,\n RUNNING,\n STOPPING,\n STOPPED,\n}\n\nexport enum Action {\n CANCEL = \"Cancel (Ctrl+C)\",\n MERGE = \"Merge local files with remote ones\",\n RESET = \"Reset local files to remote ones\",\n}\n\nconst argSpec = {\n \"-a\": \"--app\",\n \"--app\": AppArg,\n \"--force\": Boolean,\n \"--file-push-delay\": Number,\n \"--file-watch-debounce\": Number,\n \"--file-watch-poll-interval\": Number,\n \"--file-watch-poll-timeout\": Number,\n \"--file-watch-rename-timeout\": Number,\n};\n\nexport class Sync {\n args!: arg.Result<typeof argSpec>;\n\n /**\n * The current status of the sync process.\n */\n status = SyncStatus.STARTING;\n\n /**\n * A list of filepaths that have changed because of a remote file-sync\n * event. This is used to avoid sending files that we recently\n * received from a remote file-sync event.\n */\n recentRemoteChanges = new Map<string, number>();\n\n /**\n * A FIFO async callback queue that ensures we process file-sync events in the order they occurred.\n */\n queue = new PQueue({ concurrency: 1 });\n\n /**\n * A GraphQL client connected to the app's /edit/api/graphql-ws endpoint\n */\n client!: Client;\n\n /**\n * Watches the local filesystem for changes.\n */\n watcher!: FSWatcher;\n\n /**\n * Handles writing files to the local filesystem.\n */\n filesync!: FileSync;\n\n /**\n * A debounced function that enqueue's local file changes to be sent to Gadget.\n */\n publish!: _.DebouncedFunc<() => void>;\n\n /**\n * Gracefully stops the sync.\n */\n stop!: (error?: unknown) => Promise<void>;\n\n /**\n * A logger for the sync command.\n */\n log = createLogger(\"sync\", () => {\n return {\n app: this.filesync.app.slug,\n filesVersion: String(this.filesync.filesVersion),\n mtime: this.filesync.mtime,\n };\n });\n\n /**\n * Initializes the sync process.\n * - Ensures the directory exists.\n * - Ensures the directory is empty or contains a `.gadget/sync.json` file.\n * - Ensures an app is selected and that it matches the app the directory was previously synced to.\n * - Ensures yarn v1 is installed.\n * - Prompts the user how to resolve conflicts if the local filesystem has changed since the last sync.\n */\n async init(rootArgs: RootArgs): Promise<void> {\n this.args = _.defaults(arg(argSpec, { argv: rootArgs._ }), {\n \"--file-push-delay\": 100,\n \"--file-watch-debounce\": 300,\n \"--file-watch-poll-interval\": 3_000,\n \"--file-watch-poll-timeout\": 20_000,\n \"--file-watch-rename-timeout\": 1_250,\n });\n\n if (!which.sync(\"yarn\", { nothrow: true })) {\n throw new YarnNotFoundError();\n }\n\n const user = await getUserOrLogin();\n\n this.filesync = await FileSync.init(user, {\n dir: rootArgs._[0],\n app: this.args[\"--app\"],\n force: this.args[\"--force\"],\n extraIgnorePaths: [\".gadget\"],\n });\n\n this.client = new Client(this.filesync.app);\n\n const { remoteFilesVersion } = await this.client.queryUnwrap({ query: REMOTE_FILES_VERSION_QUERY });\n const hasRemoteChanges = BigInt(remoteFilesVersion) > this.filesync.filesVersion;\n\n const getChangedFiles = async (): Promise<Map<string, Stats>> => {\n const files = new Map();\n for await (const [absolutePath, stats] of this.filesync.walkDir()) {\n if (stats.mtime.getTime() > this.filesync.mtime) {\n files.set(this.filesync.normalize(absolutePath, stats.isDirectory()), stats);\n }\n }\n\n // never include the root directory\n files.delete(\"/\");\n\n return files;\n };\n\n let changedFiles = await getChangedFiles();\n const hasLocalChanges = changedFiles.size > 0;\n if (hasLocalChanges) {\n this.log.info(\"local files have changed\", {\n remoteFilesVersion,\n hasRemoteChanges,\n hasLocalChanges,\n changed: Array.from(changedFiles.keys()),\n });\n\n println(\"Local files have changed since you last synced\");\n printPaths(\"-\", Array.from(changedFiles.keys()), [], { limit: changedFiles.size });\n println();\n }\n\n let action: Action | undefined;\n if (hasLocalChanges) {\n ({ action } = await inquirer.prompt({\n type: \"list\",\n name: \"action\",\n choices: [Action.CANCEL, Action.MERGE, Action.RESET],\n message: hasRemoteChanges ? \"Remote files have also changed. How would you like to proceed?\" : \"How would you like to proceed?\",\n }));\n }\n\n // get all the changed files again in case more changed\n changedFiles = await getChangedFiles();\n\n switch (action) {\n case Action.MERGE: {\n this.log.info(\"merging local changes\", {\n remoteFilesVersion,\n hasRemoteChanges,\n hasLocalChanges,\n changed: Array.from(changedFiles.keys()),\n });\n\n // We purposefully don't write the returned files version here\n // because we haven't received its associated files yet. This\n // will cause us to receive the remote files that have changed\n // since the last sync (+ the local files that we just\n // published)\n await this.client.queryUnwrap({\n query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n variables: {\n input: {\n expectedRemoteFilesVersion: remoteFilesVersion,\n changed: await pMap(changedFiles, async ([normalizedPath, stats]) => ({\n path: normalizedPath,\n mode: stats.mode,\n content: stats.isDirectory() ? \"\" : await fs.readFile(this.filesync.absolute(normalizedPath), \"base64\"),\n encoding: FileSyncEncoding.Base64,\n })),\n deleted: [],\n },\n },\n });\n break;\n }\n case Action.RESET: {\n this.log.info(\"resetting local changes\", {\n remoteFilesVersion,\n hasRemoteChanges,\n hasLocalChanges,\n changed: Array.from(changedFiles.keys()),\n });\n\n // delete all the local files that have changed since the last\n // sync and set the files version to 0 so we receive all the\n // remote files again, including any files that we just deleted\n // that still exist\n await this.filesync.write(0n, [], changedFiles.keys(), true);\n break;\n }\n case Action.CANCEL: {\n process.exit(0);\n }\n }\n }\n\n /**\n * Runs the sync process until it is stopped or an error occurs.\n */\n async run(): Promise<void> {\n let error: unknown;\n const stopped = new PromiseSignal();\n\n const recentRemoteChangesInterval = setInterval(() => {\n for (const [path, timestamp] of this.recentRemoteChanges) {\n if (isAfter(Date.now(), timestamp + ms(\"5s\"))) {\n // this change should have been seen by now, so remove it\n this.recentRemoteChanges.delete(path);\n }\n }\n }, ms(\"1s\")).unref();\n\n this.stop = async (e?: unknown) => {\n if (this.status != SyncStatus.RUNNING) return;\n this.status = SyncStatus.STOPPING;\n error = e;\n\n this.log.info(\"stopping\", { error });\n\n try {\n clearInterval(recentRemoteChangesInterval);\n unsubscribe();\n this.watcher.removeAllListeners();\n this.publish.flush();\n await this.queue.onIdle();\n } finally {\n await Promise.allSettled([this.watcher.close(), this.client.dispose()]);\n\n this.status = SyncStatus.STOPPED;\n stopped.resolve();\n this.log.info(\"stopped\");\n }\n };\n\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.on(signal, () => {\n if (this.status != SyncStatus.RUNNING) return;\n\n println` Stopping... {gray (press Ctrl+C again to force)}`;\n void this.stop();\n\n // When ggt is run via npx, and the user presses Ctrl+C, npx sends SIGINT twice in quick succession. In order to prevent the second\n // SIGINT from triggering the force exit listener, we wait a bit before registering it. This is a bit of a hack, but it works.\n setTimeout(() => {\n process.once(signal, () => {\n println(\" Exiting immediately. Note that files may not have finished syncing.\");\n process.exit(1);\n });\n }, 100).unref();\n });\n }\n\n const unsubscribe = this.client.subscribeUnwrap(\n {\n query: REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n variables: () => ({ localFilesVersion: String(this.filesync.filesVersion) }),\n },\n {\n error: (error) => void this.stop(error),\n next: ({ remoteFileSyncEvents }) => {\n const remoteFilesVersion = remoteFileSyncEvents.remoteFilesVersion;\n\n // we always ignore .gadget/ files so that we don't publish them (they're managed by gadget), but we still want to receive them\n const filter = (event: { path: string }) => _.startsWith(event.path, \".gadget/\") || !this.filesync.ignores(event.path);\n const changed = _.filter(remoteFileSyncEvents.changed, filter);\n const deleted = _.filter(remoteFileSyncEvents.deleted, filter);\n\n this.log.info(\"received files\", {\n remoteFilesVersion,\n changed: _.map(changed, \"path\"),\n deleted: _.map(deleted, \"path\"),\n });\n\n this._enqueue(async () => {\n // add all the non-ignored files and directories we're about\n // to touch to recentRemoteChanges so that we don't send\n // them back\n for (const file of _.filter([...changed, ...deleted], (file) => !this.filesync.ignores(file.path))) {\n this.recentRemoteChanges.set(file.path, Date.now());\n\n let dir = path.dirname(file.path);\n while (dir !== \".\") {\n this.recentRemoteChanges.set(dir + \"/\", Date.now());\n dir = path.dirname(dir);\n }\n }\n\n if (changed.length || deleted.length) {\n println`Received {gray ${formatDate(new Date(), \"pp\")}}`;\n printPaths(\"←\", _.map(changed, \"path\"), _.map(deleted, \"path\"));\n }\n\n await this.filesync.write(remoteFilesVersion, changed, _.map(deleted, \"path\"));\n\n if (_.some(changed, [\"path\", \"yarn.lock\"])) {\n await execa(\"yarn\", [\"install\"], { cwd: this.filesync.dir }).catch(_.noop);\n }\n });\n },\n },\n );\n\n const localFilesBuffer = new Map<\n string,\n | { mode: number; isDirectory: boolean }\n | { isDeleted: true; isDirectory: boolean }\n | { mode: number; oldPath: string; newPath: string; isDirectory: boolean }\n >();\n\n this.publish = _.debounce(() => {\n const localFiles = new Map(localFilesBuffer.entries());\n localFilesBuffer.clear();\n\n this._enqueue(async () => {\n const changed: FileSyncChangedEventInput[] = [];\n const deleted: FileSyncDeletedEventInput[] = [];\n\n await pMap(localFiles, async ([normalizedPath, file]) => {\n if (\"isDeleted\" in file) {\n deleted.push({ path: normalizedPath });\n return;\n }\n\n try {\n changed.push({\n path: normalizedPath,\n oldPath: \"oldPath\" in file ? file.oldPath : undefined,\n mode: file.mode,\n content: file.isDirectory ? \"\" : await fs.readFile(this.filesync.absolute(normalizedPath), FileSyncEncoding.Base64),\n encoding: FileSyncEncoding.Base64,\n });\n } catch (error) {\n // A file could have been changed and then deleted before we process the change event, so the readFile\n // above will raise an ENOENT. This is normal operation, so just ignore this event.\n swallowEnoent(error);\n }\n });\n\n if (!changed.length && !deleted.length) {\n return;\n }\n\n const { publishFileSyncEvents } = await this.client.queryUnwrap({\n query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n variables: { input: { expectedRemoteFilesVersion: String(this.filesync.filesVersion), changed, deleted } },\n });\n\n await this.filesync.write(publishFileSyncEvents.remoteFilesVersion, [], []);\n\n println`Sent {gray ${formatDate(new Date(), \"pp\")}}`;\n printPaths(\"→\", _.map(changed, \"path\"), _.map(deleted, \"path\"));\n });\n }, this.args[\"--file-push-delay\"]);\n\n this.watcher = new FSWatcher(this.filesync.dir, {\n // paths that we never want to publish\n ignore: /(\\.gadget|\\.git|node_modules|\\.DS_Store)/,\n // don't emit an event for every watched file on boot\n ignoreInitial: true,\n renameDetection: true,\n recursive: true,\n debounce: this.args[\"--file-watch-debounce\"],\n pollingInterval: this.args[\"--file-watch-poll-interval\"],\n pollingTimeout: this.args[\"--file-watch-poll-timeout\"],\n renameTimeout: this.args[\"--file-watch-rename-timeout\"],\n });\n\n this.watcher.once(\"error\", (error) => void this.stop(error));\n\n this.watcher.on(\"all\", (event: string, absolutePath: string, renamedPath: string) => {\n const filepath = event === \"rename\" || event === \"renameDir\" ? renamedPath : absolutePath;\n const isDirectory = event === \"renameDir\" || event === \"addDir\" || event === \"unlinkDir\";\n const normalizedPath = this.filesync.normalize(filepath, isDirectory);\n\n this.log.debug(\"file event\", {\n event,\n path: normalizedPath,\n isDirectory,\n recentRemoteChanges: Array.from(this.recentRemoteChanges.keys()),\n });\n\n if (filepath === this.filesync.absolute(\".ignore\")) {\n this.filesync.reloadIgnorePaths();\n } else if (this.filesync.ignores(filepath)) {\n return;\n }\n\n if (this.recentRemoteChanges.delete(normalizedPath)) {\n return;\n }\n\n switch (event) {\n case \"add\":\n case \"addDir\":\n case \"change\": {\n const stats = fs.statSync(filepath);\n localFilesBuffer.set(normalizedPath, { mode: stats.mode, isDirectory });\n break;\n }\n case \"unlink\":\n case \"unlinkDir\": {\n localFilesBuffer.set(normalizedPath, { isDeleted: true, isDirectory });\n break;\n }\n case \"rename\":\n case \"renameDir\": {\n const stats = fs.statSync(filepath);\n localFilesBuffer.set(normalizedPath, {\n oldPath: this.filesync.normalize(absolutePath, isDirectory),\n newPath: normalizedPath,\n isDirectory,\n mode: stats.mode,\n });\n break;\n }\n }\n\n this.publish();\n });\n\n this.status = SyncStatus.RUNNING;\n\n println();\n println`\n {bold ggt v${config.version}}\n\n App ${this.filesync.app.slug}\n Editor https://${this.filesync.app.slug}.gadget.app/edit\n Playground https://${this.filesync.app.slug}.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/${this.filesync.app.slug}\n\n {underline Endpoints} ${\n this.filesync.app.hasSplitEnvironments\n ? `\n • https://${this.filesync.app.primaryDomain}\n • https://${this.filesync.app.slug}--development.gadget.app`\n : `\n • https://${this.filesync.app.primaryDomain}`\n }\n\n Watching for file changes... {gray Press Ctrl+C to stop}\n `;\n println();\n\n await stopped;\n\n if (error) {\n notify({ subtitle: \"Uh oh!\", message: \"An error occurred while syncing files\" });\n throw error as Error;\n } else {\n println(\"Goodbye!\");\n }\n }\n\n /**\n * Enqueues a function that handles file-sync events onto the {@linkcode queue}.\n *\n * @param fn The function to enqueue.\n */\n private _enqueue(fn: () => Promise<unknown>): void {\n void this.queue.add(fn).catch(this.stop);\n }\n}\n\nconst sync = new Sync();\nexport const init = sync.init.bind(sync);\nexport const run = sync.run.bind(sync);\n"],"names":["arg","format","formatDate","isAfter","execa","fs","inquirer","_","ms","pMap","PQueue","path","FSWatcher","which","FileSyncEncoding","AppArg","Client","config","YarnNotFoundError","FileSync","PUBLISH_FILE_SYNC_EVENTS_MUTATION","REMOTE_FILES_VERSION_QUERY","REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION","printPaths","swallowEnoent","createLogger","notify","println","sprint","PromiseSignal","getUserOrLogin","usage","SyncStatus","Action","argSpec","Boolean","Number","Sync","init","rootArgs","args","defaults","argv","sync","nothrow","user","filesync","dir","app","force","extraIgnorePaths","client","remoteFilesVersion","queryUnwrap","query","hasRemoteChanges","BigInt","filesVersion","getChangedFiles","files","Map","absolutePath","stats","walkDir","mtime","getTime","set","normalize","isDirectory","delete","changedFiles","hasLocalChanges","size","log","info","changed","Array","from","keys","limit","action","prompt","type","name","choices","message","variables","input","expectedRemoteFilesVersion","normalizedPath","mode","content","readFile","absolute","encoding","Base64","deleted","write","process","exit","run","error","stopped","recentRemoteChangesInterval","setInterval","timestamp","recentRemoteChanges","Date","now","unref","stop","e","status","clearInterval","unsubscribe","watcher","removeAllListeners","publish","flush","queue","onIdle","Promise","allSettled","close","dispose","resolve","signal","on","setTimeout","once","subscribeUnwrap","localFilesVersion","String","next","remoteFileSyncEvents","filter","event","startsWith","ignores","map","_enqueue","file","dirname","length","some","cwd","catch","noop","localFilesBuffer","debounce","localFiles","entries","clear","push","oldPath","undefined","publishFileSyncEvents","ignore","ignoreInitial","renameDetection","recursive","pollingInterval","pollingTimeout","renameTimeout","renamedPath","filepath","debug","reloadIgnorePaths","statSync","isDeleted","newPath","version","slug","hasSplitEnvironments","primaryDomain","subtitle","fn","add","concurrency","bind"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/commands/sync.ts"],"sourcesContent":["import arg from \"arg\";\nimport { format as formatDate, isAfter } from \"date-fns\";\nimport { execa } from \"execa\";\nimport type { Stats } from \"fs-extra\";\nimport fs from \"fs-extra\";\nimport inquirer from \"inquirer\";\nimport _ from \"lodash\";\nimport ms from \"ms\";\nimport pMap from \"p-map\";\nimport PQueue from \"p-queue\";\nimport path from \"path\";\nimport FSWatcher from \"watcher\";\nimport which from \"which\";\nimport { FileSyncEncoding, type FileSyncChangedEventInput, type FileSyncDeletedEventInput } from \"../__generated__/graphql.js\";\nimport { AppArg } from \"../services/args.js\";\nimport { config } from \"../services/config.js\";\nimport { EditGraphQL } from \"../services/edit-graphql.js\";\nimport { YarnNotFoundError } from \"../services/errors.js\";\nimport {\n FileSync,\n PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n REMOTE_FILES_VERSION_QUERY,\n REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n printPaths,\n} from \"../services/filesync.js\";\nimport { swallowEnoent } from \"../services/fs-utils.js\";\nimport { createLogger } from \"../services/log.js\";\nimport { notify } from \"../services/notify.js\";\nimport { println, sprint } from \"../services/output.js\";\nimport { PromiseSignal } from \"../services/promise.js\";\nimport { getUserOrLogin } from \"../services/user.js\";\nimport { type RootArgs } from \"./root.js\";\n\nexport const usage = sprint`\n Sync your Gadget application's source code to and from\n your local filesystem.\n\n {bold USAGE}\n $ ggt sync [DIRECTORY] [--app <name>]\n\n {bold ARGUMENTS}\n DIRECTORY {dim [default: .] The directory to sync files to.\n\n If the directory doesn't exist, it will be created.}\n\n {bold FLAGS}\n -a, --app=<name> {dim The Gadget application to sync files to.}\n\n --force {dim Whether to sync even if we can't determine\n the state of your local files relative to\n your remote ones.}\n\n {bold DESCRIPTION}\n Sync provides the ability to sync your Gadget application's source\n code to and from your local filesystem.\n\n While ggt sync is running, local file changes are immediately\n reflected within Gadget, while files that are changed remotely are\n immediately saved to your local filesystem.\n\n Use cases for this include:\n • Developing locally with your own editor like VSCode\n • Storing your source code in a Git repository like GitHub\n\n Sync includes the concept of a {dim .ignore} file. This file may\n contain a list of files and directories that won't be received or\n sent to Gadget when syncing. The format of this file is identical\n to the one used by Git {dim (https://git-scm.com/docs/gitignore)}.\n\n The following files and directories are always ignored:\n • .DS_Store\n • .gadget\n • .git\n • node_modules\n\n Note:\n • If you have separate development and production environments,\n {dim ggt sync} will only sync with your development environment\n • Gadget applications only support installing dependencies\n with Yarn 1 {dim (https://classic.yarnpkg.com/lang/en/)}\n • Since file changes are immediately reflected in Gadget,\n avoid the following while {dim ggt sync} is running:\n • Deleting all your files\n • Moving all your files to a different directory\n\n {bold EXAMPLES}\n {dim $ ggt sync --app my-app ~/gadget/my-app}\n\n App my-app\n Editor https://my-app.gadget.app/edit\n Playground https://my-app.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/my-app\n\n Endpoints\n • https://my-app.gadget.app\n • https://my-app--development.gadget.app\n\n Watching for file changes... {dim Press Ctrl+C to stop}\n\n Received {dim 12:00:00 PM}\n {green ←} routes/GET.js {dim (changed)}\n {green ←} user/signUp/signIn.js {dim (changed)}\n {dim 2 files in total. 2 changed, 0 deleted.}\n\n Sent {dim 12:00:03 PM}\n {green →} routes/GET.ts {dim (changed)}\n {dim 1 file in total. 1 changed, 0 deleted.}\n\n ^C Stopping... {dim (press Ctrl+C again to force)}\n Goodbye!\n`;\n\nexport enum SyncStatus {\n STARTING,\n RUNNING,\n STOPPING,\n STOPPED,\n}\n\nexport enum Action {\n CANCEL = \"Cancel (Ctrl+C)\",\n MERGE = \"Merge local files with remote ones\",\n RESET = \"Reset local files to remote ones\",\n}\n\nconst argSpec = {\n \"-a\": \"--app\",\n \"--app\": AppArg,\n \"--force\": Boolean,\n \"--file-push-delay\": Number,\n \"--file-watch-debounce\": Number,\n \"--file-watch-poll-interval\": Number,\n \"--file-watch-poll-timeout\": Number,\n \"--file-watch-rename-timeout\": Number,\n};\n\nexport class Sync {\n args!: arg.Result<typeof argSpec>;\n\n /**\n * The current status of the sync process.\n */\n status = SyncStatus.STARTING;\n\n /**\n * A list of filepaths that have changed because of a remote file-sync\n * event. This is used to avoid sending files that we recently\n * received from a remote file-sync event.\n */\n recentRemoteChanges = new Map<string, number>();\n\n /**\n * A FIFO async callback queue that ensures we process file-sync events in the order they occurred.\n */\n queue = new PQueue({ concurrency: 1 });\n\n /**\n * A GraphQL client connected to the app's /edit/api/graphql-ws endpoint\n */\n graphql!: EditGraphQL;\n\n /**\n * Watches the local filesystem for changes.\n */\n watcher!: FSWatcher;\n\n /**\n * Handles writing files to the local filesystem.\n */\n filesync!: FileSync;\n\n /**\n * A debounced function that enqueue's local file changes to be sent to Gadget.\n */\n publish!: _.DebouncedFunc<() => void>;\n\n /**\n * Gracefully stops the sync.\n */\n stop!: (error?: unknown) => Promise<void>;\n\n /**\n * A logger for the sync command.\n */\n log = createLogger(\"sync\", () => {\n return {\n app: this.filesync.app.slug,\n filesVersion: String(this.filesync.filesVersion),\n mtime: this.filesync.mtime,\n };\n });\n\n /**\n * Initializes the sync process.\n * - Ensures the directory exists.\n * - Ensures the directory is empty or contains a `.gadget/sync.json` file.\n * - Ensures an app is selected and that it matches the app the directory was previously synced to.\n * - Ensures yarn v1 is installed.\n * - Prompts the user how to resolve conflicts if the local filesystem has changed since the last sync.\n */\n async init(rootArgs: RootArgs): Promise<void> {\n this.args = _.defaults(arg(argSpec, { argv: rootArgs._ }), {\n \"--file-push-delay\": 100,\n \"--file-watch-debounce\": 300,\n \"--file-watch-poll-interval\": 3_000,\n \"--file-watch-poll-timeout\": 20_000,\n \"--file-watch-rename-timeout\": 1_250,\n });\n\n if (!which.sync(\"yarn\", { nothrow: true })) {\n throw new YarnNotFoundError();\n }\n\n const user = await getUserOrLogin();\n\n this.filesync = await FileSync.init(user, {\n dir: this.args._[0],\n app: this.args[\"--app\"],\n force: this.args[\"--force\"],\n extraIgnorePaths: [\".gadget\"],\n });\n\n this.graphql = new EditGraphQL(this.filesync.app);\n\n const { remoteFilesVersion } = await this.graphql.query({ query: REMOTE_FILES_VERSION_QUERY });\n const hasRemoteChanges = BigInt(remoteFilesVersion) > this.filesync.filesVersion;\n\n const getChangedFiles = async (): Promise<Map<string, Stats>> => {\n const files = new Map();\n for await (const [absolutePath, stats] of this.filesync.walkDir()) {\n if (stats.mtime.getTime() > this.filesync.mtime) {\n files.set(this.filesync.normalize(absolutePath, stats.isDirectory()), stats);\n }\n }\n\n // never include the root directory\n files.delete(\"/\");\n\n return files;\n };\n\n let changedFiles = await getChangedFiles();\n const hasLocalChanges = changedFiles.size > 0;\n if (hasLocalChanges) {\n this.log.info(\"local files have changed\", {\n remoteFilesVersion,\n hasRemoteChanges,\n hasLocalChanges,\n changed: Array.from(changedFiles.keys()),\n });\n\n println(\"Local files have changed since you last synced\");\n printPaths(\"-\", Array.from(changedFiles.keys()), [], { limit: changedFiles.size });\n println();\n }\n\n let action: Action | undefined;\n if (hasLocalChanges) {\n ({ action } = await inquirer.prompt({\n type: \"list\",\n name: \"action\",\n choices: [Action.CANCEL, Action.MERGE, Action.RESET],\n message: hasRemoteChanges ? \"Remote files have also changed. How would you like to proceed?\" : \"How would you like to proceed?\",\n }));\n }\n\n // get all the changed files again in case more changed\n changedFiles = await getChangedFiles();\n\n switch (action) {\n case Action.MERGE: {\n this.log.info(\"merging local changes\", {\n remoteFilesVersion,\n hasRemoteChanges,\n hasLocalChanges,\n changed: Array.from(changedFiles.keys()),\n });\n\n // We purposefully don't write the returned files version here\n // because we haven't received its associated files yet. This\n // will cause us to receive the remote files that have changed\n // since the last sync (+ the local files that we just\n // published)\n await this.graphql.query({\n query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n variables: {\n input: {\n expectedRemoteFilesVersion: remoteFilesVersion,\n changed: await pMap(changedFiles, async ([normalizedPath, stats]) => ({\n path: normalizedPath,\n mode: stats.mode,\n content: stats.isDirectory() ? \"\" : await fs.readFile(this.filesync.absolute(normalizedPath), \"base64\"),\n encoding: FileSyncEncoding.Base64,\n })),\n deleted: [],\n },\n },\n });\n break;\n }\n case Action.RESET: {\n this.log.info(\"resetting local changes\", {\n remoteFilesVersion,\n hasRemoteChanges,\n hasLocalChanges,\n changed: Array.from(changedFiles.keys()),\n });\n\n // delete all the local files that have changed since the last\n // sync and set the files version to 0 so we receive all the\n // remote files again, including any files that we just deleted\n // that still exist\n await this.filesync.write(0n, [], changedFiles.keys(), true);\n break;\n }\n case Action.CANCEL: {\n process.exit(0);\n }\n }\n }\n\n /**\n * Runs the sync process until it is stopped or an error occurs.\n */\n async run(): Promise<void> {\n let error: unknown;\n const stopped = new PromiseSignal();\n\n const recentRemoteChangesInterval = setInterval(() => {\n for (const [path, timestamp] of this.recentRemoteChanges) {\n if (isAfter(Date.now(), timestamp + ms(\"5s\"))) {\n // this change should have been seen by now, so remove it\n this.recentRemoteChanges.delete(path);\n }\n }\n }, ms(\"1s\")).unref();\n\n this.stop = async (e?: unknown) => {\n if (this.status != SyncStatus.RUNNING) return;\n this.status = SyncStatus.STOPPING;\n error = e;\n\n this.log.info(\"stopping\", { error });\n\n try {\n clearInterval(recentRemoteChangesInterval);\n unsubscribe();\n this.watcher.removeAllListeners();\n this.publish.flush();\n await this.queue.onIdle();\n } finally {\n await Promise.allSettled([this.watcher.close(), this.graphql.dispose()]);\n\n this.status = SyncStatus.STOPPED;\n stopped.resolve();\n this.log.info(\"stopped\");\n }\n };\n\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.on(signal, () => {\n if (this.status != SyncStatus.RUNNING) return;\n\n println` Stopping... {gray (press Ctrl+C again to force)}`;\n void this.stop();\n\n // When ggt is run via npx, and the user presses Ctrl+C, npx sends SIGINT twice in quick succession. In order to prevent the second\n // SIGINT from triggering the force exit listener, we wait a bit before registering it. This is a bit of a hack, but it works.\n setTimeout(() => {\n process.once(signal, () => {\n println(\" Exiting immediately. Note that files may not have finished syncing.\");\n process.exit(1);\n });\n }, 100).unref();\n });\n }\n\n const unsubscribe = this.graphql.subscribe(\n {\n query: REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n variables: () => ({ localFilesVersion: String(this.filesync.filesVersion) }),\n },\n {\n error: (error) => void this.stop(error),\n next: ({ remoteFileSyncEvents }) => {\n const remoteFilesVersion = remoteFileSyncEvents.remoteFilesVersion;\n\n // we always ignore .gadget/ files so that we don't publish them (they're managed by gadget), but we still want to receive them\n const filter = (event: { path: string }) => _.startsWith(event.path, \".gadget/\") || !this.filesync.ignores(event.path);\n const changed = _.filter(remoteFileSyncEvents.changed, filter);\n const deleted = _.filter(remoteFileSyncEvents.deleted, filter);\n\n this.log.info(\"received files\", {\n remoteFilesVersion,\n changed: _.map(changed, \"path\"),\n deleted: _.map(deleted, \"path\"),\n });\n\n this._enqueue(async () => {\n // add all the non-ignored files and directories we're about\n // to touch to recentRemoteChanges so that we don't send\n // them back\n for (const file of _.filter([...changed, ...deleted], (file) => !this.filesync.ignores(file.path))) {\n this.recentRemoteChanges.set(file.path, Date.now());\n\n let dir = path.dirname(file.path);\n while (dir !== \".\") {\n this.recentRemoteChanges.set(dir + \"/\", Date.now());\n dir = path.dirname(dir);\n }\n }\n\n if (changed.length || deleted.length) {\n println`Received {gray ${formatDate(new Date(), \"pp\")}}`;\n printPaths(\"←\", _.map(changed, \"path\"), _.map(deleted, \"path\"));\n }\n\n await this.filesync.write(remoteFilesVersion, changed, _.map(deleted, \"path\"));\n\n if (_.some(changed, [\"path\", \"yarn.lock\"])) {\n await execa(\"yarn\", [\"install\"], { cwd: this.filesync.dir }).catch(_.noop);\n }\n });\n },\n },\n );\n\n const localFilesBuffer = new Map<\n string,\n | { mode: number; isDirectory: boolean }\n | { isDeleted: true; isDirectory: boolean }\n | { mode: number; oldPath: string; newPath: string; isDirectory: boolean }\n >();\n\n this.publish = _.debounce(() => {\n const localFiles = new Map(localFilesBuffer.entries());\n localFilesBuffer.clear();\n\n this._enqueue(async () => {\n const changed: FileSyncChangedEventInput[] = [];\n const deleted: FileSyncDeletedEventInput[] = [];\n\n await pMap(localFiles, async ([normalizedPath, file]) => {\n if (\"isDeleted\" in file) {\n deleted.push({ path: normalizedPath });\n return;\n }\n\n try {\n changed.push({\n path: normalizedPath,\n oldPath: \"oldPath\" in file ? file.oldPath : undefined,\n mode: file.mode,\n content: file.isDirectory ? \"\" : await fs.readFile(this.filesync.absolute(normalizedPath), FileSyncEncoding.Base64),\n encoding: FileSyncEncoding.Base64,\n });\n } catch (error) {\n // A file could have been changed and then deleted before we process the change event, so the readFile\n // above will raise an ENOENT. This is normal operation, so just ignore this event.\n swallowEnoent(error);\n }\n });\n\n if (!changed.length && !deleted.length) {\n return;\n }\n\n const { publishFileSyncEvents } = await this.graphql.query({\n query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n variables: { input: { expectedRemoteFilesVersion: String(this.filesync.filesVersion), changed, deleted } },\n });\n\n await this.filesync.write(publishFileSyncEvents.remoteFilesVersion, [], []);\n\n println`Sent {gray ${formatDate(new Date(), \"pp\")}}`;\n printPaths(\"→\", _.map(changed, \"path\"), _.map(deleted, \"path\"));\n });\n }, this.args[\"--file-push-delay\"]);\n\n this.watcher = new FSWatcher(this.filesync.dir, {\n // paths that we never want to publish\n ignore: /(\\.gadget|\\.git|node_modules|\\.DS_Store)/,\n // don't emit an event for every watched file on boot\n ignoreInitial: true,\n renameDetection: true,\n recursive: true,\n debounce: this.args[\"--file-watch-debounce\"],\n pollingInterval: this.args[\"--file-watch-poll-interval\"],\n pollingTimeout: this.args[\"--file-watch-poll-timeout\"],\n renameTimeout: this.args[\"--file-watch-rename-timeout\"],\n });\n\n this.watcher.once(\"error\", (error) => void this.stop(error));\n\n this.watcher.on(\"all\", (event: string, absolutePath: string, renamedPath: string) => {\n const filepath = event === \"rename\" || event === \"renameDir\" ? renamedPath : absolutePath;\n const isDirectory = event === \"renameDir\" || event === \"addDir\" || event === \"unlinkDir\";\n const normalizedPath = this.filesync.normalize(filepath, isDirectory);\n\n this.log.debug(\"file event\", {\n event,\n path: normalizedPath,\n isDirectory,\n recentRemoteChanges: Array.from(this.recentRemoteChanges.keys()),\n });\n\n if (filepath === this.filesync.absolute(\".ignore\")) {\n this.filesync.reloadIgnorePaths();\n } else if (this.filesync.ignores(filepath)) {\n return;\n }\n\n if (this.recentRemoteChanges.delete(normalizedPath)) {\n return;\n }\n\n switch (event) {\n case \"add\":\n case \"addDir\":\n case \"change\": {\n const stats = fs.statSync(filepath);\n localFilesBuffer.set(normalizedPath, { mode: stats.mode, isDirectory });\n break;\n }\n case \"unlink\":\n case \"unlinkDir\": {\n localFilesBuffer.set(normalizedPath, { isDeleted: true, isDirectory });\n break;\n }\n case \"rename\":\n case \"renameDir\": {\n const stats = fs.statSync(filepath);\n localFilesBuffer.set(normalizedPath, {\n oldPath: this.filesync.normalize(absolutePath, isDirectory),\n newPath: normalizedPath,\n isDirectory,\n mode: stats.mode,\n });\n break;\n }\n }\n\n this.publish();\n });\n\n this.status = SyncStatus.RUNNING;\n\n println();\n println`\n {bold ggt v${config.version}}\n\n App ${this.filesync.app.slug}\n Editor https://${this.filesync.app.slug}.gadget.app/edit\n Playground https://${this.filesync.app.slug}.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/${this.filesync.app.slug}\n\n {underline Endpoints} ${\n this.filesync.app.hasSplitEnvironments\n ? `\n • https://${this.filesync.app.primaryDomain}\n • https://${this.filesync.app.slug}--development.gadget.app`\n : `\n • https://${this.filesync.app.primaryDomain}`\n }\n\n Watching for file changes... {gray Press Ctrl+C to stop}\n `;\n println();\n\n await stopped;\n\n if (error) {\n notify({ subtitle: \"Uh oh!\", message: \"An error occurred while syncing files\" });\n throw error as Error;\n } else {\n println(\"Goodbye!\");\n }\n }\n\n /**\n * Enqueues a function that handles file-sync events onto the {@linkcode queue}.\n *\n * @param fn The function to enqueue.\n */\n private _enqueue(fn: () => Promise<unknown>): void {\n void this.queue.add(fn).catch(this.stop);\n }\n}\n\nconst sync = new Sync();\nexport const init = sync.init.bind(sync);\nexport const run = sync.run.bind(sync);\n"],"names":["arg","format","formatDate","isAfter","execa","fs","inquirer","_","ms","pMap","PQueue","path","FSWatcher","which","FileSyncEncoding","AppArg","config","EditGraphQL","YarnNotFoundError","FileSync","PUBLISH_FILE_SYNC_EVENTS_MUTATION","REMOTE_FILES_VERSION_QUERY","REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION","printPaths","swallowEnoent","createLogger","notify","println","sprint","PromiseSignal","getUserOrLogin","usage","SyncStatus","Action","argSpec","Boolean","Number","Sync","init","rootArgs","args","defaults","argv","sync","nothrow","user","filesync","dir","app","force","extraIgnorePaths","graphql","remoteFilesVersion","query","hasRemoteChanges","BigInt","filesVersion","getChangedFiles","files","Map","absolutePath","stats","walkDir","mtime","getTime","set","normalize","isDirectory","delete","changedFiles","hasLocalChanges","size","log","info","changed","Array","from","keys","limit","action","prompt","type","name","choices","message","variables","input","expectedRemoteFilesVersion","normalizedPath","mode","content","readFile","absolute","encoding","Base64","deleted","write","process","exit","run","error","stopped","recentRemoteChangesInterval","setInterval","timestamp","recentRemoteChanges","Date","now","unref","stop","e","status","clearInterval","unsubscribe","watcher","removeAllListeners","publish","flush","queue","onIdle","Promise","allSettled","close","dispose","resolve","signal","on","setTimeout","once","subscribe","localFilesVersion","String","next","remoteFileSyncEvents","filter","event","startsWith","ignores","map","_enqueue","file","dirname","length","some","cwd","catch","noop","localFilesBuffer","debounce","localFiles","entries","clear","push","oldPath","undefined","publishFileSyncEvents","ignore","ignoreInitial","renameDetection","recursive","pollingInterval","pollingTimeout","renameTimeout","renamedPath","filepath","debug","reloadIgnorePaths","statSync","isDeleted","newPath","version","slug","hasSplitEnvironments","primaryDomain","subtitle","fn","add","concurrency","bind"],"mappings":"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"}
@@ -13,14 +13,59 @@ var ConnectionStatus;
13
13
  ConnectionStatus[ConnectionStatus["DISCONNECTED"] = 1] = "DISCONNECTED";
14
14
  ConnectionStatus[ConnectionStatus["RECONNECTING"] = 2] = "RECONNECTING";
15
15
  })(ConnectionStatus || (ConnectionStatus = {}));
16
- const log = createLogger("client");
16
+ const log = createLogger("edit-graphql");
17
17
  /**
18
- * Client is a GraphQL client connected to a Gadget application's /edit/api/graphql-ws endpoint.
19
- *
20
- * NOTE: In order to use the Client, the user must be logged in and an app must have been selected (`context.app` and
21
- * `context.session` must be set).
22
- */ export class Client {
23
- subscribe(payload, sink) {
18
+ * EditGraphQL is a GraphQL client connected to a Gadget application's /edit/api/graphql-ws endpoint.
19
+ */ export class EditGraphQL {
20
+ /**
21
+ * Subscribe to a GraphQL query.
22
+ *
23
+ * This method is typically used to subscribe to a GraphQL
24
+ * subscription. If you want to execute a GraphQL query or mutation,
25
+ * use {@link EditGraphQL.query} instead.
26
+ *
27
+ * @param payload The query and variables to send to the server.
28
+ * @param sink The callbacks to invoke when the server responds.
29
+ * @returns A function to unsubscribe from the subscription.
30
+ */ subscribe(payload, sink) {
31
+ const unsubscribe = this._subscribe(payload, {
32
+ ...sink,
33
+ next: (result)=>{
34
+ if (result.errors) {
35
+ unsubscribe();
36
+ sink.error(new ClientError(payload, result.errors));
37
+ return;
38
+ }
39
+ if (!result.data) {
40
+ sink.error(new ClientError(payload, "Received a GraphQL response without errors or data"));
41
+ unsubscribe();
42
+ return;
43
+ }
44
+ sink.next(result.data);
45
+ }
46
+ });
47
+ return unsubscribe;
48
+ }
49
+ /**
50
+ * Execute a GraphQL query or mutation.
51
+ * @param payload The query and variables to send to the server.
52
+ * @returns The data returned by the server.
53
+ */ async query(payload) {
54
+ const result = await this._query(payload);
55
+ if (result.errors) throw new ClientError(payload, result.errors);
56
+ if (!result.data) throw new ClientError(payload, "We received a response without data");
57
+ return result.data;
58
+ }
59
+ /**
60
+ * Close the connection to the server.
61
+ */ async dispose() {
62
+ await this._client.dispose();
63
+ }
64
+ /**
65
+ * Internal method to subscribe to a GraphQL query.
66
+ *
67
+ * This method shouldn't be used directly. It's exposed for testing.
68
+ */ _subscribe(payload, sink) {
24
69
  let subscribePayload;
25
70
  let removeConnectedListener = _.noop.bind(_);
26
71
  if (_.isFunction(payload.variables)) {
@@ -64,42 +109,14 @@ const log = createLogger("client");
64
109
  unsubscribe();
65
110
  };
66
111
  }
67
- subscribeUnwrap(payload, sink) {
68
- const unsubscribe = this.subscribe(payload, {
69
- ...sink,
70
- next: (result)=>{
71
- if (result.errors) {
72
- unsubscribe();
73
- sink.error(new ClientError(payload, result.errors));
74
- return;
75
- }
76
- if (!result.data) {
77
- sink.error(new ClientError(payload, "We received a response without data"));
78
- unsubscribe();
79
- return;
80
- }
81
- sink.next(result.data);
82
- }
83
- });
84
- return unsubscribe;
85
- }
86
- query(payload) {
112
+ _query(payload) {
87
113
  return new Promise((resolve, reject)=>{
88
- this.subscribe(payload, {
114
+ this._subscribe(payload, {
89
115
  next: resolve,
90
116
  error: reject
91
117
  });
92
118
  });
93
119
  }
94
- async queryUnwrap(payload) {
95
- const result = await this.query(payload);
96
- if (result.errors) throw new ClientError(payload, result.errors);
97
- if (!result.data) throw new ClientError(payload, "We received a response without data");
98
- return result.data;
99
- }
100
- async dispose() {
101
- await this._client.dispose();
102
- }
103
120
  constructor(app){
104
121
  // assume the client is going to connect
105
122
  _define_property(this, "status", 0);
@@ -173,4 +190,4 @@ const log = createLogger("client");
173
190
  }
174
191
  }
175
192
 
176
- //# sourceMappingURL=client.js.map
193
+ //# sourceMappingURL=edit-graphql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/edit-graphql.ts"],"sourcesContent":["import type { GraphQLError } from \"graphql\";\nimport type { ExecutionResult, SubscribePayload } from \"graphql-ws\";\nimport { createClient } from \"graphql-ws\";\nimport type { ClientRequestArgs } from \"http\";\nimport _ from \"lodash\";\nimport assert from \"node:assert\";\nimport type { JsonObject, SetOptional } from \"type-fest\";\nimport type { CloseEvent, ErrorEvent } from \"ws\";\nimport WebSocket from \"ws\";\nimport type { App } from \"./app.js\";\nimport { config } from \"./config.js\";\nimport { ClientError } from \"./errors.js\";\nimport { loadCookie } from \"./http.js\";\nimport { createLogger } from \"./log.js\";\n\nenum ConnectionStatus {\n CONNECTED,\n DISCONNECTED,\n RECONNECTING,\n}\n\nconst log = createLogger(\"edit-graphql\");\n\n/**\n * EditGraphQL is a GraphQL client connected to a Gadget application's /edit/api/graphql-ws endpoint.\n */\nexport class EditGraphQL {\n // assume the client is going to connect\n status = ConnectionStatus.CONNECTED;\n\n private _client: ReturnType<typeof createClient>;\n\n constructor(app: App) {\n this._client = createClient({\n url: `wss://${app.slug}.${config.domains.app}/edit/api/graphql-ws`,\n shouldRetry: _.constant(true),\n webSocketImpl: class extends WebSocket {\n constructor(address: string | URL, protocols?: string | string[], wsOptions?: WebSocket.ClientOptions | ClientRequestArgs) {\n // this cookie should be available since we were given an app which requires a cookie to load\n const cookie = loadCookie();\n assert(cookie, \"missing cookie when connecting to GraphQL API\");\n\n super(address, protocols, {\n ...wsOptions,\n headers: {\n ...wsOptions?.headers,\n \"user-agent\": config.versionFull,\n cookie,\n },\n });\n }\n },\n on: {\n connecting: () => {\n switch (this.status) {\n case ConnectionStatus.DISCONNECTED:\n this.status = ConnectionStatus.RECONNECTING;\n log.info(\"reconnecting\");\n break;\n case ConnectionStatus.RECONNECTING:\n log.info(\"retrying\");\n break;\n default:\n log.info(\"connecting\");\n break;\n }\n },\n connected: () => {\n if (this.status === ConnectionStatus.RECONNECTING) {\n log.info(\"reconnected\");\n } else {\n log.info(\"connected\");\n }\n\n // let the other on connected listeners see what status we're in\n setImmediate(() => (this.status = ConnectionStatus.CONNECTED));\n },\n closed: (e) => {\n const event = e as CloseEvent;\n if (event.wasClean) {\n log.info(\"connection closed\");\n return;\n }\n\n if (this.status === ConnectionStatus.CONNECTED) {\n this.status = ConnectionStatus.DISCONNECTED;\n log.info(\"disconnected\");\n }\n },\n error: (error) => {\n if (this.status == ConnectionStatus.RECONNECTING) {\n log.error(\"failed to reconnect\", { error });\n } else {\n log.error(\"connection error\", { error });\n }\n },\n },\n });\n }\n\n /**\n * Subscribe to a GraphQL query.\n *\n * This method is typically used to subscribe to a GraphQL\n * subscription. If you want to execute a GraphQL query or mutation,\n * use {@link EditGraphQL.query} instead.\n *\n * @param payload The query and variables to send to the server.\n * @param sink The callbacks to invoke when the server responds.\n * @returns A function to unsubscribe from the subscription.\n */\n subscribe<Data extends JsonObject, Variables extends JsonObject>(\n payload: Payload<Data, Variables>,\n sink: { next: (data: Data) => void; error: (error: ClientError) => void },\n ): () => void {\n const unsubscribe = this._subscribe(payload, {\n ...sink,\n next: (result) => {\n if (result.errors) {\n unsubscribe();\n sink.error(new ClientError(payload, result.errors));\n return;\n }\n\n if (!result.data) {\n sink.error(new ClientError(payload, \"Received a GraphQL response without errors or data\"));\n unsubscribe();\n return;\n }\n\n sink.next(result.data);\n },\n });\n\n return unsubscribe;\n }\n\n /**\n * Execute a GraphQL query or mutation.\n * @param payload The query and variables to send to the server.\n * @returns The data returned by the server.\n */\n async query<Data extends JsonObject, Variables extends JsonObject>(payload: Payload<Data, Variables>): Promise<Data> {\n const result = await this._query(payload);\n if (result.errors) throw new ClientError(payload, result.errors);\n if (!result.data) throw new ClientError(payload, \"We received a response without data\");\n return result.data;\n }\n\n /**\n * Close the connection to the server.\n */\n async dispose(): Promise<void> {\n await this._client.dispose();\n }\n\n /**\n * Internal method to subscribe to a GraphQL query.\n *\n * This method shouldn't be used directly. It's exposed for testing.\n */\n _subscribe<Data extends JsonObject, Variables extends JsonObject, Extensions extends JsonObject = JsonObject>(\n payload: Payload<Data, Variables>,\n sink: SetOptional<Sink<Data, Extensions>, \"complete\">,\n ): () => void {\n let subscribePayload: SubscribePayload;\n let removeConnectedListener = _.noop.bind(_);\n\n if (_.isFunction(payload.variables)) {\n // the caller wants us to re-evaluate the variables every time\n // graphql-ws re-subscribes after reconnecting\n subscribePayload = { ...payload, variables: payload.variables() };\n removeConnectedListener = this._client.on(\"connected\", () => {\n if (this.status == ConnectionStatus.RECONNECTING) {\n (subscribePayload as any).variables = (payload.variables as any)();\n const [type, operation] = _.split(subscribePayload.query, / |\\(/, 2);\n log.info(\"re-sending graphql query\", { type, operation });\n }\n });\n } else {\n subscribePayload = payload as SubscribePayload;\n }\n\n const [type, operation] = _.split(subscribePayload.query, / |\\(/, 2);\n log.info(\"sending graphql query\", { type, operation });\n\n const unsubscribe = this._client.subscribe(subscribePayload, {\n next: (result: ExecutionResult<Data, Extensions>) => {\n sink.next(result);\n },\n error: (error) => {\n sink.error(new ClientError(subscribePayload, error as Error | GraphQLError[] | CloseEvent | ErrorEvent));\n },\n complete: () => {\n sink.complete?.();\n },\n });\n\n return () => {\n removeConnectedListener();\n unsubscribe();\n };\n }\n\n private _query<Data extends JsonObject, Variables extends JsonObject, Extensions extends JsonObject = JsonObject>(\n payload: Payload<Data, Variables>,\n ): Promise<ExecutionResult<Data, Extensions>> {\n return new Promise((resolve, reject) => {\n this._subscribe<Data, Variables, Extensions>(payload, { next: resolve, error: reject });\n });\n }\n}\n\nexport type Query<\n Data extends JsonObject,\n Variables extends JsonObject = JsonObject,\n Extensions extends JsonObject = JsonObject,\n> = string & {\n __TData?: Data;\n __TVariables?: Variables;\n __TExtensions?: Extensions;\n};\n\nexport interface Payload<Data extends JsonObject, Variables extends JsonObject> {\n readonly query: Query<Data, Variables>;\n readonly variables?: Variables | (() => Variables) | null;\n}\n\nexport interface Sink<Data extends JsonObject, Extensions extends JsonObject> {\n next(value: ExecutionResult<Data, Extensions>): void;\n error(error: ClientError): void;\n complete(): void;\n}\n"],"names":["createClient","_","assert","WebSocket","config","ClientError","loadCookie","createLogger","ConnectionStatus","log","EditGraphQL","subscribe","payload","sink","unsubscribe","_subscribe","next","result","errors","error","data","query","_query","dispose","_client","subscribePayload","removeConnectedListener","noop","bind","isFunction","variables","on","status","type","operation","split","info","complete","Promise","resolve","reject","constructor","app","url","slug","domains","shouldRetry","constant","webSocketImpl","address","protocols","wsOptions","cookie","headers","versionFull","connecting","connected","setImmediate","closed","e","event","wasClean"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/errors.ts"],"sourcesContent":["import * as Sentry from \"@sentry/node\";\nimport arg from \"arg\";\nimport cleanStack from \"clean-stack\";\nimport { randomUUID } from \"crypto\";\nimport { RequestError } from \"got\";\nimport type { GraphQLError } from \"graphql\";\nimport _ from \"lodash\";\nimport os from \"os\";\nimport { serializeError as baseSerializeError } from \"serialize-error\";\nimport { dedent } from \"ts-dedent\";\nimport type { SetOptional } from \"type-fest\";\nimport { inspect } from \"util\";\nimport type { CloseEvent, ErrorEvent } from \"ws\";\nimport type { App } from \"./app.js\";\nimport type { Payload } from \"./client.js\";\nimport { config, env } from \"./config.js\";\nimport type { User } from \"./user.js\";\n\nlet app: App | undefined;\nlet user: User | undefined;\n\nexport const setUser = (newUser: User | undefined): void => {\n user = newUser;\n Sentry.setUser(newUser ?? null);\n};\n\nexport const setApp = (newApp: App | undefined): void => {\n app = newApp;\n};\n\n/**\n * Base class for all errors.\n */\nexport abstract class CLIError extends Error {\n /**\n * A GGT_CLI_SOMETHING human/machine readable unique identifier for this error.\n */\n code: string;\n\n /**\n * The Sentry event ID for this error.\n */\n sentryEventId = env.testLike ? \"00000000-0000-0000-0000-000000000000\" : randomUUID();\n\n /**\n * The underlying *thing* that caused this error.\n */\n cause?: any;\n\n /**\n * Assume the stack trace exists.\n */\n override stack!: string;\n\n /**\n * Indicates whether this error is considered a bug or not.\n */\n abstract isBug: IsBug;\n\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n Error.captureStackTrace(this, this.constructor);\n }\n\n /**\n * Constructs a CLIError from a cause.\n */\n static from(cause: unknown): CLIError {\n if (cause instanceof CLIError) return cause;\n if (cause instanceof arg.ArgError) return new ArgError(cause.message);\n return new UnexpectedError(cause);\n }\n\n async capture(): Promise<void> {\n if (this.isBug == IsBug.NO) return;\n\n Sentry.getCurrentHub().captureException(this, {\n event_id: this.sentryEventId,\n captureContext: {\n user: user ? { id: String(user.id), email: user.email, username: user.name ?? undefined } : undefined,\n tags: {\n applicationId: app ? app.id : undefined,\n arch: config.arch,\n isBug: this.isBug,\n code: this.code,\n environment: env.value,\n platform: config.platform,\n shell: config.shell,\n version: config.version,\n },\n contexts: {\n cause: this.cause ? serializeError(this.cause) : undefined,\n app: {\n command: `ggt ${process.argv.slice(2).join(\" \")}`,\n argv: process.argv,\n },\n device: {\n name: os.hostname(),\n family: os.type(),\n arch: os.arch(),\n },\n runtime: {\n name: process.release.name,\n version: process.version,\n },\n },\n },\n });\n\n await Sentry.flush(2000);\n }\n\n /**\n * Turns this error into a user-friendly message that explains what went wrong and how to fix it. A good write up of\n * what an error should look like can be found here: {@link https://clig.dev/#errors}\n */\n render(): string {\n return _.compact([this.header(), this.body(), this.footer()]).join(\"\\n\\n\");\n }\n\n protected header(): string {\n return `${this.code}: ${this.message}`;\n }\n\n protected footer(): string {\n if (this.isBug == IsBug.NO) return \"\";\n\n return dedent`\n ${this.isBug == IsBug.YES ? \"This is a bug\" : \"If you think this is a bug\"}, please submit an issue using the link below.\n\n https://github.com/gadget-inc/ggt/issues/new?template=bug_report.yml&error-id=${this.sentryEventId}\n `;\n }\n\n protected abstract body(): string;\n}\n\n/**\n * Universal Error object to json blob serializer.\n * Wraps `serialize-error` with some handy stuff, like special support for Got HTTP errors\n */\nexport function serializeError(error: unknown): Record<string, any> {\n let serialized = baseSerializeError(_.isArray(error) ? new AggregateError(error) : error);\n if (typeof serialized == \"string\") {\n serialized = { message: serialized };\n }\n\n if (error instanceof RequestError) {\n serialized[\"timings\"] = undefined;\n serialized[\"options\"] = {\n method: error.options.method,\n url: error.options.url instanceof URL ? error.options.url.toJSON() : error.options.url,\n };\n serialized[\"responseBody\"] = inspect(error.response?.body);\n }\n\n return serialized;\n}\n\nexport enum IsBug {\n YES = \"yes\",\n NO = \"no\",\n MAYBE = \"maybe\",\n}\n\n/**\n * Our \"catch all\" error. If this error is thrown, we almost certainly have a bug.\n *\n * Whenever possible, we should use a more specific error so that we can provide more useful information.\n */\nexport class UnexpectedError extends CLIError {\n isBug = IsBug.YES;\n\n constructor(override cause: unknown) {\n super(\"GGT_CLI_UNEXPECTED_ERROR\", \"An unexpected error occurred\");\n }\n\n protected body(): string {\n if (_.isError(this.cause)) {\n return cleanStack(this.cause.stack ?? this.stack);\n }\n return this.stack;\n }\n}\n\nexport class ClientError extends CLIError {\n isBug = IsBug.MAYBE;\n\n constructor(\n readonly payload: Payload<any, any>,\n override cause: string | Error | readonly GraphQLError[] | CloseEvent | ErrorEvent,\n ) {\n super(\"GGT_CLI_CLIENT_ERROR\", \"An error occurred while communicating with Gadget\");\n\n // ErrorEvent and CloseEvent aren't serializable, so we reconstruct them into an object. We discard the `target` property because it's large and not that useful\n if (isErrorEvent(cause)) {\n this.cause = {\n type: cause.type,\n message: cause.message,\n error: serializeError(cause.error),\n } as any;\n } else if (isCloseEvent(cause)) {\n this.cause = {\n type: cause.type,\n code: cause.code,\n reason: cause.reason,\n wasClean: cause.wasClean,\n } as any;\n }\n }\n\n override body(): string {\n if (isGraphQLErrors(this.cause)) {\n if (this.cause.length > 1) {\n const errors = _.uniqBy(this.cause, \"message\");\n\n let output = \"Gadget responded with multiple errors:\\n\";\n for (let i = 0; i < errors.length; i++) {\n output += `\\n ${i + 1}. ${errors[i]?.message}`;\n }\n\n return output;\n } else {\n return dedent`\n Gadget responded with the following error:\n\n ${this.cause[0]?.message}\n `;\n }\n }\n\n if (isCloseEvent(this.cause)) {\n return \"The connection to Gadget closed unexpectedly.\";\n }\n\n if (isErrorEvent(this.cause) || _.isError(this.cause)) {\n return this.cause.message;\n }\n\n return this.cause;\n }\n}\n\nexport class YarnNotFoundError extends CLIError {\n isBug = IsBug.NO;\n\n constructor() {\n super(\"GGT_CLI_YARN_NOT_FOUND\", \"Yarn not found\");\n }\n\n protected body(): string {\n return dedent`\n Yarn must be installed to sync your application. You can install it by running:\n\n $ npm install --global yarn\n\n For more information, see: https://classic.yarnpkg.com/en/docs/install\n `;\n }\n}\n\nexport class ArgError extends CLIError {\n isBug = IsBug.NO;\n\n constructor(message: string) {\n super(\"GGT_CLI_ARG_ERROR\", message);\n }\n\n // eslint-disable-next-line lodash/prefer-constant\n protected override header(): string {\n return \"\";\n }\n\n protected override body(): string {\n return this.message;\n }\n}\n\nexport class InvalidSyncFileError extends CLIError {\n isBug = IsBug.MAYBE;\n\n constructor(\n readonly dir: string,\n readonly app: string | undefined,\n ) {\n super(\"GGT_CLI_INVALID_SYNC_FILE\", \"The .gadget/sync.json file was invalid or not found\");\n this.app ??= \"<name of app>\";\n }\n\n protected body(): string {\n return dedent`\n We failed to read the Gadget metadata file in this directory:\n\n ${this.dir}\n\n If you're running \\`ggt sync\\` for the first time, we recommend using an empty directory such as:\n\n ~/gadget/${this.app}\n\n Otherwise, if you're sure you want to sync the contents of that directory to Gadget, run \\`ggt sync\\` again with the \\`--force\\` flag:\n\n $ ggt sync --app ${this.app} ${this.dir} --force\n\n You will be prompted to either merge your local files with your remote ones or reset your local files to your remote ones.\n `;\n }\n}\n\nfunction isCloseEvent(e: any): e is SetOptional<CloseEvent, \"target\"> {\n return !_.isNil(e) && _.isString(e.type) && _.isNumber(e.code) && _.isString(e.reason) && _.isBoolean(e.wasClean);\n}\n\nfunction isErrorEvent(e: any): e is SetOptional<ErrorEvent, \"target\"> {\n return !_.isNil(e) && _.isString(e.type) && _.isString(e.message) && !_.isNil(e.error);\n}\n\nfunction isGraphQLErrors(e: any): e is readonly GraphQLError[] {\n return _.isArray(e) && _.every(e, (e) => !_.isNil(e) && _.isString(e.message) && _.isArray(e.locations ?? []) && _.isArray(e.path ?? []));\n}\n"],"names":["Sentry","arg","cleanStack","randomUUID","RequestError","_","os","serializeError","baseSerializeError","dedent","inspect","config","env","app","user","setUser","newUser","setApp","newApp","CLIError","Error","from","cause","ArgError","message","UnexpectedError","capture","isBug","getCurrentHub","captureException","event_id","sentryEventId","captureContext","id","String","email","username","name","undefined","tags","applicationId","arch","code","environment","value","platform","shell","version","contexts","command","process","argv","slice","join","device","hostname","family","type","runtime","release","flush","render","compact","header","body","footer","constructor","testLike","stack","captureStackTrace","error","serialized","isArray","AggregateError","method","options","url","URL","toJSON","response","IsBug","isError","ClientError","isGraphQLErrors","length","errors","uniqBy","output","i","isCloseEvent","isErrorEvent","payload","reason","wasClean","YarnNotFoundError","InvalidSyncFileError","dir","e","isNil","isString","isNumber","isBoolean","every","locations","path"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/services/errors.ts"],"sourcesContent":["import * as Sentry from \"@sentry/node\";\nimport arg from \"arg\";\nimport cleanStack from \"clean-stack\";\nimport { randomUUID } from \"crypto\";\nimport { RequestError } from \"got\";\nimport type { GraphQLError } from \"graphql\";\nimport _ from \"lodash\";\nimport os from \"os\";\nimport { serializeError as baseSerializeError } from \"serialize-error\";\nimport { dedent } from \"ts-dedent\";\nimport type { SetOptional } from \"type-fest\";\nimport { inspect } from \"util\";\nimport type { CloseEvent, ErrorEvent } from \"ws\";\nimport type { App } from \"./app.js\";\nimport { config, env } from \"./config.js\";\nimport type { Payload } from \"./edit-graphql.js\";\nimport type { User } from \"./user.js\";\n\nlet app: App | undefined;\nlet user: User | undefined;\n\nexport const setUser = (newUser: User | undefined): void => {\n user = newUser;\n Sentry.setUser(newUser ?? null);\n};\n\nexport const setApp = (newApp: App | undefined): void => {\n app = newApp;\n};\n\n/**\n * Base class for all errors.\n */\nexport abstract class CLIError extends Error {\n /**\n * A GGT_CLI_SOMETHING human/machine readable unique identifier for this error.\n */\n code: string;\n\n /**\n * The Sentry event ID for this error.\n */\n sentryEventId = env.testLike ? \"00000000-0000-0000-0000-000000000000\" : randomUUID();\n\n /**\n * The underlying *thing* that caused this error.\n */\n cause?: any;\n\n /**\n * Assume the stack trace exists.\n */\n override stack!: string;\n\n /**\n * Indicates whether this error is considered a bug or not.\n */\n abstract isBug: IsBug;\n\n constructor(code: string, message: string) {\n super(message);\n this.code = code;\n Error.captureStackTrace(this, this.constructor);\n }\n\n /**\n * Constructs a CLIError from a cause.\n */\n static from(cause: unknown): CLIError {\n if (cause instanceof CLIError) return cause;\n if (cause instanceof arg.ArgError) return new ArgError(cause.message);\n return new UnexpectedError(cause);\n }\n\n async capture(): Promise<void> {\n if (this.isBug == IsBug.NO) return;\n\n Sentry.getCurrentHub().captureException(this, {\n event_id: this.sentryEventId,\n captureContext: {\n user: user ? { id: String(user.id), email: user.email, username: user.name ?? undefined } : undefined,\n tags: {\n applicationId: app ? app.id : undefined,\n arch: config.arch,\n isBug: this.isBug,\n code: this.code,\n environment: env.value,\n platform: config.platform,\n shell: config.shell,\n version: config.version,\n },\n contexts: {\n cause: this.cause ? serializeError(this.cause) : undefined,\n app: {\n command: `ggt ${process.argv.slice(2).join(\" \")}`,\n argv: process.argv,\n },\n device: {\n name: os.hostname(),\n family: os.type(),\n arch: os.arch(),\n },\n runtime: {\n name: process.release.name,\n version: process.version,\n },\n },\n },\n });\n\n await Sentry.flush(2000);\n }\n\n /**\n * Turns this error into a user-friendly message that explains what went wrong and how to fix it. A good write up of\n * what an error should look like can be found here: {@link https://clig.dev/#errors}\n */\n render(): string {\n return _.compact([this.header(), this.body(), this.footer()]).join(\"\\n\\n\");\n }\n\n protected header(): string {\n return `${this.code}: ${this.message}`;\n }\n\n protected footer(): string {\n if (this.isBug == IsBug.NO) return \"\";\n\n return dedent`\n ${this.isBug == IsBug.YES ? \"This is a bug\" : \"If you think this is a bug\"}, please submit an issue using the link below.\n\n https://github.com/gadget-inc/ggt/issues/new?template=bug_report.yml&error-id=${this.sentryEventId}\n `;\n }\n\n protected abstract body(): string;\n}\n\n/**\n * Universal Error object to json blob serializer.\n * Wraps `serialize-error` with some handy stuff, like special support for Got HTTP errors\n */\nexport function serializeError(error: unknown): Record<string, any> {\n let serialized = baseSerializeError(_.isArray(error) ? new AggregateError(error) : error);\n if (typeof serialized == \"string\") {\n serialized = { message: serialized };\n }\n\n if (error instanceof RequestError) {\n serialized[\"timings\"] = undefined;\n serialized[\"options\"] = {\n method: error.options.method,\n url: error.options.url instanceof URL ? error.options.url.toJSON() : error.options.url,\n };\n serialized[\"responseBody\"] = inspect(error.response?.body);\n }\n\n return serialized;\n}\n\nexport enum IsBug {\n YES = \"yes\",\n NO = \"no\",\n MAYBE = \"maybe\",\n}\n\n/**\n * Our \"catch all\" error. If this error is thrown, we almost certainly have a bug.\n *\n * Whenever possible, we should use a more specific error so that we can provide more useful information.\n */\nexport class UnexpectedError extends CLIError {\n isBug = IsBug.YES;\n\n constructor(override cause: unknown) {\n super(\"GGT_CLI_UNEXPECTED_ERROR\", \"An unexpected error occurred\");\n }\n\n protected body(): string {\n if (_.isError(this.cause)) {\n return cleanStack(this.cause.stack ?? this.stack);\n }\n return this.stack;\n }\n}\n\nexport class ClientError extends CLIError {\n isBug = IsBug.MAYBE;\n\n constructor(\n readonly payload: Payload<any, any>,\n override cause: string | Error | readonly GraphQLError[] | CloseEvent | ErrorEvent,\n ) {\n super(\"GGT_CLI_CLIENT_ERROR\", \"An error occurred while communicating with Gadget\");\n\n // ErrorEvent and CloseEvent aren't serializable, so we reconstruct them into an object. We discard the `target` property because it's large and not that useful\n if (isErrorEvent(cause)) {\n this.cause = {\n type: cause.type,\n message: cause.message,\n error: serializeError(cause.error),\n } as any;\n } else if (isCloseEvent(cause)) {\n this.cause = {\n type: cause.type,\n code: cause.code,\n reason: cause.reason,\n wasClean: cause.wasClean,\n } as any;\n }\n }\n\n override body(): string {\n if (isGraphQLErrors(this.cause)) {\n if (this.cause.length > 1) {\n const errors = _.uniqBy(this.cause, \"message\");\n\n let output = \"Gadget responded with multiple errors:\\n\";\n for (let i = 0; i < errors.length; i++) {\n output += `\\n ${i + 1}. ${errors[i]?.message}`;\n }\n\n return output;\n } else {\n return dedent`\n Gadget responded with the following error:\n\n ${this.cause[0]?.message}\n `;\n }\n }\n\n if (isCloseEvent(this.cause)) {\n return \"The connection to Gadget closed unexpectedly.\";\n }\n\n if (isErrorEvent(this.cause) || _.isError(this.cause)) {\n return this.cause.message;\n }\n\n return this.cause;\n }\n}\n\nexport class YarnNotFoundError extends CLIError {\n isBug = IsBug.NO;\n\n constructor() {\n super(\"GGT_CLI_YARN_NOT_FOUND\", \"Yarn not found\");\n }\n\n protected body(): string {\n return dedent`\n Yarn must be installed to sync your application. You can install it by running:\n\n $ npm install --global yarn\n\n For more information, see: https://classic.yarnpkg.com/en/docs/install\n `;\n }\n}\n\nexport class ArgError extends CLIError {\n isBug = IsBug.NO;\n\n constructor(message: string) {\n super(\"GGT_CLI_ARG_ERROR\", message);\n }\n\n // eslint-disable-next-line lodash/prefer-constant\n protected override header(): string {\n return \"\";\n }\n\n protected override body(): string {\n return this.message;\n }\n}\n\nexport class InvalidSyncFileError extends CLIError {\n isBug = IsBug.MAYBE;\n\n constructor(\n readonly dir: string,\n readonly app: string | undefined,\n ) {\n super(\"GGT_CLI_INVALID_SYNC_FILE\", \"The .gadget/sync.json file was invalid or not found\");\n this.app ??= \"<name of app>\";\n }\n\n protected body(): string {\n return dedent`\n We failed to read the Gadget metadata file in this directory:\n\n ${this.dir}\n\n If you're running \\`ggt sync\\` for the first time, we recommend using an empty directory such as:\n\n ~/gadget/${this.app}\n\n Otherwise, if you're sure you want to sync the contents of that directory to Gadget, run \\`ggt sync\\` again with the \\`--force\\` flag:\n\n $ ggt sync --app ${this.app} ${this.dir} --force\n\n You will be prompted to either merge your local files with your remote ones or reset your local files to your remote ones.\n `;\n }\n}\n\nfunction isCloseEvent(e: any): e is SetOptional<CloseEvent, \"target\"> {\n return !_.isNil(e) && _.isString(e.type) && _.isNumber(e.code) && _.isString(e.reason) && _.isBoolean(e.wasClean);\n}\n\nfunction isErrorEvent(e: any): e is SetOptional<ErrorEvent, \"target\"> {\n return !_.isNil(e) && _.isString(e.type) && _.isString(e.message) && !_.isNil(e.error);\n}\n\nfunction isGraphQLErrors(e: any): e is readonly GraphQLError[] {\n return _.isArray(e) && _.every(e, (e) => !_.isNil(e) && _.isString(e.message) && _.isArray(e.locations ?? []) && _.isArray(e.path ?? []));\n}\n"],"names":["Sentry","arg","cleanStack","randomUUID","RequestError","_","os","serializeError","baseSerializeError","dedent","inspect","config","env","app","user","setUser","newUser","setApp","newApp","CLIError","Error","from","cause","ArgError","message","UnexpectedError","capture","isBug","getCurrentHub","captureException","event_id","sentryEventId","captureContext","id","String","email","username","name","undefined","tags","applicationId","arch","code","environment","value","platform","shell","version","contexts","command","process","argv","slice","join","device","hostname","family","type","runtime","release","flush","render","compact","header","body","footer","constructor","testLike","stack","captureStackTrace","error","serialized","isArray","AggregateError","method","options","url","URL","toJSON","response","IsBug","isError","ClientError","isGraphQLErrors","length","errors","uniqBy","output","i","isCloseEvent","isErrorEvent","payload","reason","wasClean","YarnNotFoundError","InvalidSyncFileError","dir","e","isNil","isString","isNumber","isBoolean","every","locations","path"],"mappings":";AAAA,YAAYA,YAAY,eAAe;AACvC,OAAOC,SAAS,MAAM;AACtB,OAAOC,gBAAgB,cAAc;AACrC,SAASC,UAAU,QAAQ,SAAS;AACpC,SAASC,YAAY,QAAQ,MAAM;AAEnC,OAAOC,OAAO,SAAS;AACvB,OAAOC,QAAQ,KAAK;AACpB,SAASC,kBAAkBC,kBAAkB,QAAQ,kBAAkB;AACvE,SAASC,MAAM,QAAQ,YAAY;AAEnC,SAASC,OAAO,QAAQ,OAAO;AAG/B,SAASC,MAAM,EAAEC,GAAG,QAAQ,cAAc;AAI1C,IAAIC;AACJ,IAAIC;AAEJ,OAAO,MAAMC,UAAU,CAACC;IACtBF,OAAOE;IACPhB,OAAOe,OAAO,CAACC,WAAW;AAC5B,EAAE;AAEF,OAAO,MAAMC,SAAS,CAACC;IACrBL,MAAMK;AACR,EAAE;AAEF;;CAEC,GACD,OAAO,MAAeC,iBAAiBC;IAgCrC;;GAEC,GACD,OAAOC,KAAKC,KAAc,EAAY;QACpC,IAAIA,iBAAiBH,UAAU,OAAOG;QACtC,IAAIA,iBAAiBrB,IAAIsB,QAAQ,EAAE,OAAO,IAAIA,SAASD,MAAME,OAAO;QACpE,OAAO,IAAIC,gBAAgBH;IAC7B;IAEA,MAAMI,UAAyB;QAC7B,IAAI,IAAI,CAACC,KAAK,UAAc;QAE5B3B,OAAO4B,aAAa,GAAGC,gBAAgB,CAAC,IAAI,EAAE;YAC5CC,UAAU,IAAI,CAACC,aAAa;YAC5BC,gBAAgB;gBACdlB,MAAMA,OAAO;oBAAEmB,IAAIC,OAAOpB,KAAKmB,EAAE;oBAAGE,OAAOrB,KAAKqB,KAAK;oBAAEC,UAAUtB,KAAKuB,IAAI,IAAIC;gBAAU,IAAIA;gBAC5FC,MAAM;oBACJC,eAAe3B,MAAMA,IAAIoB,EAAE,GAAGK;oBAC9BG,MAAM9B,OAAO8B,IAAI;oBACjBd,OAAO,IAAI,CAACA,KAAK;oBACjBe,MAAM,IAAI,CAACA,IAAI;oBACfC,aAAa/B,IAAIgC,KAAK;oBACtBC,UAAUlC,OAAOkC,QAAQ;oBACzBC,OAAOnC,OAAOmC,KAAK;oBACnBC,SAASpC,OAAOoC,OAAO;gBACzB;gBACAC,UAAU;oBACR1B,OAAO,IAAI,CAACA,KAAK,GAAGf,eAAe,IAAI,CAACe,KAAK,IAAIgB;oBACjDzB,KAAK;wBACHoC,SAAS,CAAC,IAAI,EAAEC,QAAQC,IAAI,CAACC,KAAK,CAAC,GAAGC,IAAI,CAAC,KAAK,CAAC;wBACjDF,MAAMD,QAAQC,IAAI;oBACpB;oBACAG,QAAQ;wBACNjB,MAAM/B,GAAGiD,QAAQ;wBACjBC,QAAQlD,GAAGmD,IAAI;wBACfhB,MAAMnC,GAAGmC,IAAI;oBACf;oBACAiB,SAAS;wBACPrB,MAAMa,QAAQS,OAAO,CAACtB,IAAI;wBAC1BU,SAASG,QAAQH,OAAO;oBAC1B;gBACF;YACF;QACF;QAEA,MAAM/C,OAAO4D,KAAK,CAAC;IACrB;IAEA;;;GAGC,GACDC,SAAiB;QACf,OAAOxD,EAAEyD,OAAO,CAAC;YAAC,IAAI,CAACC,MAAM;YAAI,IAAI,CAACC,IAAI;YAAI,IAAI,CAACC,MAAM;SAAG,EAAEZ,IAAI,CAAC;IACrE;IAEUU,SAAiB;QACzB,OAAO,CAAC,EAAE,IAAI,CAACrB,IAAI,CAAC,EAAE,EAAE,IAAI,CAAClB,OAAO,CAAC,CAAC;IACxC;IAEUyC,SAAiB;QACzB,IAAI,IAAI,CAACtC,KAAK,UAAc,OAAO;QAEnC,OAAOlB,MAAM,CAAC;MACZ,EAAE,IAAI,CAACkB,KAAK,YAAgB,kBAAkB,6BAA6B;;oFAEG,EAAE,IAAI,CAACI,aAAa,CAAC;IACrG,CAAC;IACH;IA1EAmC,YAAYxB,IAAY,EAAElB,OAAe,CAAE;QACzC,KAAK,CAACA;QA1BR;;GAEC,GACDkB,uBAAAA,QAAAA,KAAAA;QAEA;;GAEC,GACDX,uBAAAA,iBAAgBnB,IAAIuD,QAAQ,GAAG,yCAAyChE;QAExE;;GAEC,GACDmB,uBAAAA,SAAAA,KAAAA;QAEA;;GAEC,GACD,uBAAS8C,SAAT,KAAA;QASE,IAAI,CAAC1B,IAAI,GAAGA;QACZtB,MAAMiD,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAACH,WAAW;IAChD;AAyEF;AAEA;;;CAGC,GACD,OAAO,SAAS3D,eAAe+D,KAAc;IAC3C,IAAIC,aAAa/D,mBAAmBH,EAAEmE,OAAO,CAACF,SAAS,IAAIG,eAAeH,SAASA;IACnF,IAAI,OAAOC,cAAc,UAAU;QACjCA,aAAa;YAAE/C,SAAS+C;QAAW;IACrC;IAEA,IAAID,iBAAiBlE,cAAc;QACjCmE,UAAU,CAAC,UAAU,GAAGjC;QACxBiC,UAAU,CAAC,UAAU,GAAG;YACtBG,QAAQJ,MAAMK,OAAO,CAACD,MAAM;YAC5BE,KAAKN,MAAMK,OAAO,CAACC,GAAG,YAAYC,MAAMP,MAAMK,OAAO,CAACC,GAAG,CAACE,MAAM,KAAKR,MAAMK,OAAO,CAACC,GAAG;QACxF;QACAL,UAAU,CAAC,eAAe,GAAG7D,QAAQ4D,MAAMS,QAAQ,EAAEf;IACvD;IAEA,OAAOO;AACT;;UAEYS;;;;GAAAA,UAAAA;AAMZ;;;;CAIC,GACD,OAAO,MAAMvD,wBAAwBN;IAOzB6C,OAAe;QACvB,IAAI3D,EAAE4E,OAAO,CAAC,IAAI,CAAC3D,KAAK,GAAG;YACzB,OAAOpB,WAAW,IAAI,CAACoB,KAAK,CAAC8C,KAAK,IAAI,IAAI,CAACA,KAAK;QAClD;QACA,OAAO,IAAI,CAACA,KAAK;IACnB;IATAF,YAAY,AAAS5C,KAAc,CAAE;QACnC,KAAK,CAAC,4BAA4B;;QAHpCK,uBAAAA,SAAAA,KAAAA;aAEqBL,QAAAA;aAFrBK;IAIA;AAQF;AAEA,OAAO,MAAMuD,oBAAoB/D;IA0BtB6C,OAAe;QACtB,IAAImB,gBAAgB,IAAI,CAAC7D,KAAK,GAAG;YAC/B,IAAI,IAAI,CAACA,KAAK,CAAC8D,MAAM,GAAG,GAAG;gBACzB,MAAMC,SAAShF,EAAEiF,MAAM,CAAC,IAAI,CAAChE,KAAK,EAAE;gBAEpC,IAAIiE,SAAS;gBACb,IAAK,IAAIC,IAAI,GAAGA,IAAIH,OAAOD,MAAM,EAAEI,IAAK;oBACtCD,UAAU,CAAC,IAAI,EAAEC,IAAI,EAAE,EAAE,EAAEH,MAAM,CAACG,EAAE,EAAEhE,QAAQ,CAAC;gBACjD;gBAEA,OAAO+D;YACT,OAAO;gBACL,OAAO9E,MAAM,CAAC;;;YAGV,EAAE,IAAI,CAACa,KAAK,CAAC,EAAE,EAAEE,QAAQ;QAC7B,CAAC;YACH;QACF;QAEA,IAAIiE,aAAa,IAAI,CAACnE,KAAK,GAAG;YAC5B,OAAO;QACT;QAEA,IAAIoE,aAAa,IAAI,CAACpE,KAAK,KAAKjB,EAAE4E,OAAO,CAAC,IAAI,CAAC3D,KAAK,GAAG;YACrD,OAAO,IAAI,CAACA,KAAK,CAACE,OAAO;QAC3B;QAEA,OAAO,IAAI,CAACF,KAAK;IACnB;IApDA4C,YACE,AAASyB,OAA0B,EACnC,AAASrE,KAAyE,CAClF;QACA,KAAK,CAAC,wBAAwB;;;QANhCK,uBAAAA,SAAAA,KAAAA;aAGWgE,UAAAA;aACArE,QAAAA;aAJXK;QAQE,gKAAgK;QAChK,IAAI+D,aAAapE,QAAQ;YACvB,IAAI,CAACA,KAAK,GAAG;gBACXmC,MAAMnC,MAAMmC,IAAI;gBAChBjC,SAASF,MAAME,OAAO;gBACtB8C,OAAO/D,eAAee,MAAMgD,KAAK;YACnC;QACF,OAAO,IAAImB,aAAanE,QAAQ;YAC9B,IAAI,CAACA,KAAK,GAAG;gBACXmC,MAAMnC,MAAMmC,IAAI;gBAChBf,MAAMpB,MAAMoB,IAAI;gBAChBkD,QAAQtE,MAAMsE,MAAM;gBACpBC,UAAUvE,MAAMuE,QAAQ;YAC1B;QACF;IACF;AAgCF;AAEA,OAAO,MAAMC,0BAA0B3E;IAO3B6C,OAAe;QACvB,OAAOvD,MAAM,CAAC;;;;;;IAMd,CAAC;IACH;IAZAyD,aAAc;QACZ,KAAK,CAAC,0BAA0B;QAHlCvC,uBAAAA;IAIA;AAWF;AAEA,OAAO,MAAMJ,iBAAiBJ;IAO5B,kDAAkD;IAC/B4C,SAAiB;QAClC,OAAO;IACT;IAEmBC,OAAe;QAChC,OAAO,IAAI,CAACxC,OAAO;IACrB;IAXA0C,YAAY1C,OAAe,CAAE;QAC3B,KAAK,CAAC,qBAAqBA;QAH7BG,uBAAAA;IAIA;AAUF;AAEA,OAAO,MAAMoE,6BAA6B5E;IAW9B6C,OAAe;QACvB,OAAOvD,MAAM,CAAC;;;QAGV,EAAE,IAAI,CAACuF,GAAG,CAAC;;;;iBAIF,EAAE,IAAI,CAACnF,GAAG,CAAC;;;;yBAIH,EAAE,IAAI,CAACA,GAAG,CAAC,CAAC,EAAE,IAAI,CAACmF,GAAG,CAAC;;;IAG5C,CAAC;IACH;IAxBA9B,YACE,AAAS8B,GAAW,EACpB,AAASnF,GAAuB,CAChC;QACA,KAAK,CAAC,6BAA6B;;;QANrCc,uBAAAA,SAAAA,KAAAA;aAGWqE,MAAAA;aACAnF,MAAAA;aAJXc;QAOE,IAAI,CAACd,GAAG,KAAK;IACf;AAmBF;AAEA,SAAS4E,aAAaQ,CAAM;IAC1B,OAAO,CAAC5F,EAAE6F,KAAK,CAACD,MAAM5F,EAAE8F,QAAQ,CAACF,EAAExC,IAAI,KAAKpD,EAAE+F,QAAQ,CAACH,EAAEvD,IAAI,KAAKrC,EAAE8F,QAAQ,CAACF,EAAEL,MAAM,KAAKvF,EAAEgG,SAAS,CAACJ,EAAEJ,QAAQ;AAClH;AAEA,SAASH,aAAaO,CAAM;IAC1B,OAAO,CAAC5F,EAAE6F,KAAK,CAACD,MAAM5F,EAAE8F,QAAQ,CAACF,EAAExC,IAAI,KAAKpD,EAAE8F,QAAQ,CAACF,EAAEzE,OAAO,KAAK,CAACnB,EAAE6F,KAAK,CAACD,EAAE3B,KAAK;AACvF;AAEA,SAASa,gBAAgBc,CAAM;IAC7B,OAAO5F,EAAEmE,OAAO,CAACyB,MAAM5F,EAAEiG,KAAK,CAACL,GAAG,CAACA,IAAM,CAAC5F,EAAE6F,KAAK,CAACD,MAAM5F,EAAE8F,QAAQ,CAACF,EAAEzE,OAAO,KAAKnB,EAAEmE,OAAO,CAACyB,EAAEM,SAAS,IAAI,EAAE,KAAKlG,EAAEmE,OAAO,CAACyB,EAAEO,IAAI,IAAI,EAAE;AACzI"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/filesync.ts"],"sourcesContent":["import chalkTemplate from \"chalk-template\";\nimport { findUp } from \"find-up\";\nimport type { Stats } from \"fs-extra\";\nimport fs from \"fs-extra\";\nimport type { Ignore } from \"ignore\";\nimport ignore from \"ignore\";\nimport inquirer from \"inquirer\";\nimport _ from \"lodash\";\nimport ms from \"ms\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport normalizePath from \"normalize-path\";\nimport pMap from \"p-map\";\nimport pRetry from \"p-retry\";\nimport pluralize from \"pluralize\";\nimport { dedent } from \"ts-dedent\";\nimport { z } from \"zod\";\nimport type {\n PublishFileSyncEventsMutation,\n PublishFileSyncEventsMutationVariables,\n RemoteFileSyncEventsSubscription,\n RemoteFileSyncEventsSubscriptionVariables,\n RemoteFilesVersionQuery,\n RemoteFilesVersionQueryVariables,\n} from \"../__generated__/graphql.js\";\nimport type { App } from \"./app.js\";\nimport { getApps } from \"./app.js\";\nimport type { Query } from \"./client.js\";\nimport { config } from \"./config.js\";\nimport { ArgError, InvalidSyncFileError } from \"./errors.js\";\nimport { isEmptyDir, swallowEnoent } from \"./fs-utils.js\";\nimport { createLogger } from \"./log.js\";\nimport { println, sortByLevenshtein, sprint } from \"./output.js\";\nimport type { User } from \"./user.js\";\n\nconst log = createLogger(\"filesync\");\n\ninterface File {\n path: string;\n mode: number;\n content: string;\n encoding: \"utf8\" | \"base64\";\n}\n\nexport class FileSync {\n /**\n * The {@linkcode Ignore} instance that is used to determine if a file\n * should be ignored.\n *\n * @see https://www.npmjs.com/package/ignore\n */\n private _ignorer!: Ignore;\n\n private constructor(\n /**\n * An absolute path to the directory that is being synced.\n */\n readonly dir: string,\n\n /**\n * The Gadget application this filesystem is synced to.\n */\n readonly app: App,\n\n /**\n * Additional paths to ignore when syncing the filesystem.\n */\n private _extraIgnorePaths: string[],\n\n /**\n * The state of the filesystem.\n *\n * This is persisted to `.gadget/sync.json`.\n */\n private _state: { app: string; filesVersion: string; mtime: number },\n ) {\n this._save();\n this.reloadIgnorePaths();\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() {\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() {\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(user: User, options: { dir?: string; app?: string; force?: boolean; extraIgnorePaths?: string[] }): Promise<FileSync> {\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 = 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 && _.startsWith(dir, \"~/\")) {\n // `~` doesn't expand to the home directory on Windows\n dir = path.join(config.homeDir, dir.slice(2));\n }\n\n // ensure the root directory is an absolute path and exists\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(\n z.object({\n app: z.string(),\n filesVersion: z.string(),\n mtime: z.number(),\n }).parse,\n )\n .catch(() => undefined);\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 inquirer.prompt<{ appSlug: string }>({\n type: \"list\",\n name: \"appSlug\",\n message: \"Please select the app to sync to.\",\n choices: _.map(apps, \"slug\"),\n }));\n }\n\n // try to find the appSlug in their list of apps\n const app = _.find(apps, [\"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 = sortByLevenshtein(appSlug, _.map(apps, \"slug\")).slice(0, 5);\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 ignore = options.extraIgnorePaths ?? [];\n\n if (!state) {\n // the .gadget/sync.json file didn't exist or contained invalid json\n if ((await isEmptyDir(dir)) || options.force) {\n // the directory is empty or the user passed --force\n // either way, create a fresh .gadget/sync.json file\n return new FileSync(dir, app, ignore, { 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(dir, app, ignore, 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(dir, app, ignore, { 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 * Converts an absolute path into a relative one from {@linkcode dir}.\n */\n relative(to: string): string {\n if (!path.isAbsolute(to)) {\n // the filepath is already relative\n return to;\n }\n\n return path.relative(this.dir, to);\n }\n\n /**\n * Converts a relative path into an absolute one from {@linkcode dir}.\n */\n absolute(...pathSegments: string[]): string {\n return path.resolve(this.dir, ...pathSegments);\n }\n\n /**\n * Similar to {@linkcode relative} in that it converts an absolute\n * path into a relative one from {@linkcode dir}. However, it also\n * changes any slashes to be posix/unix-like forward slashes,\n * condenses repeated slashes into a single slash, and adds a trailing\n * slash if the path is a directory.\n *\n * This is used when sending file-sync events to Gadget to ensure that\n * the paths are consistent across platforms.\n *\n * @see https://www.npmjs.com/package/normalize-path\n */\n normalize(filepath: string, isDirectory: boolean): string {\n return normalizePath(path.isAbsolute(filepath) ? this.relative(filepath) : filepath) + (isDirectory ? \"/\" : \"\");\n }\n\n /**\n * Reloads the ignore rules from the `.ignore` file.\n */\n reloadIgnorePaths(): void {\n this._ignorer = ignore.default();\n this._ignorer.add([\".DS_Store\", \"node_modules\", \".git\", ...this._extraIgnorePaths]);\n\n try {\n const content = fs.readFileSync(this.absolute(\".ignore\"), \"utf-8\");\n this._ignorer.add(content);\n log.info(\"reloaded ignore rules\");\n } catch (error) {\n swallowEnoent(error);\n }\n }\n\n /**\n * Returns `true` if the {@linkcode filepath} should be ignored.\n */\n ignores(filepath: string): boolean {\n const relative = this.relative(filepath);\n if (relative == \"\") {\n // don't ignore the root dir\n return false;\n }\n\n if (_.startsWith(relative, \"..\")) {\n // anything above the root dir is ignored\n return true;\n }\n\n return this._ignorer.ignores(relative);\n }\n\n /**\n * Walks the directory and yields each file and directory.\n *\n * If a directory is empty, or only contains ignored entries, it will\n * be yielded as a directory. Otherwise, each file within the\n * directory will be yielded.\n */\n async *walkDir({ dir = this.dir, skipIgnored = true } = {}): AsyncGenerator<[absolutePath: string, entry: Stats]> {\n // track whether the directory has any entries (ignored entries don't count)\n let hasEntries = false;\n\n for await (const entry of await fs.opendir(dir)) {\n const filepath = path.join(dir, entry.name);\n if (skipIgnored && this.ignores(filepath)) {\n continue;\n }\n\n hasEntries = true;\n\n if (entry.isDirectory()) {\n yield* this.walkDir({ dir: filepath, skipIgnored });\n } else if (entry.isFile()) {\n yield [filepath, await fs.stat(filepath)];\n }\n }\n\n if (!hasEntries) {\n // if the directory is empty, or only contains ignored entries, yield it as a directory\n yield [`${dir}/`, await fs.stat(dir)];\n }\n }\n\n /**\n * Writes the {@linkcode changed} and {@linkcode deleted} files to the filesystem.\n * @param filesVersion The files version associated with the files that are being written.\n * @param changed The files that have changed.\n * @param deleted The paths that have been deleted.\n * @param force If `true`, the files version will be updated even if it's less than the current files version.\n */\n async write(filesVersion: bigint | string, changed: Iterable<File>, deleted: Iterable<string>, force = false): Promise<void> {\n filesVersion = BigInt(filesVersion);\n\n await pMap(deleted, async (filepath) => {\n const currentPath = this.absolute(filepath);\n const backupPath = this.absolute(\".gadget/backup\", this.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 log.warn(\"failed to move file to backup\", { error });\n },\n },\n );\n });\n\n await pMap(changed, async (file) => {\n const absolutePath = this.absolute(file.path);\n if (_.endsWith(file.path, \"/\")) {\n await fs.ensureDir(absolutePath, { mode: 0o755 });\n return;\n }\n\n await fs.ensureDir(path.dirname(absolutePath), { mode: 0o755 });\n await fs.writeFile(absolutePath, Buffer.from(file.content, file.encoding), { mode: file.mode });\n\n if (absolutePath == this.absolute(\".ignore\")) {\n this.reloadIgnorePaths();\n }\n });\n\n this._state.mtime = Date.now();\n if (filesVersion > BigInt(this._state.filesVersion) || force) {\n this._state.filesVersion = String(filesVersion);\n }\n\n this._save();\n\n log.info(\"wrote\", {\n ...this._state,\n changed: _.map(Array.from(changed), \"path\"),\n deleted: Array.from(deleted),\n });\n }\n\n /**\n * Synchronously writes {@linkcode _state} to `.gadget/sync.json`.\n */\n private _save() {\n fs.outputJSONSync(this.absolute(\".gadget/sync.json\"), this._state, { spaces: 2 });\n }\n}\n\n/**\n * Pretty-prints changed and deleted filepaths to the console.\n *\n * @param prefix The prefix to print before each line.\n * @param changed The normalized paths that have changed.\n * @param deleted The normalized paths that have been deleted.\n * @param options.limit The maximum number of lines to print. Defaults to 10.\n */\nexport const printPaths = (prefix: string, changed: string[], deleted: string[], { limit = 10 } = {}) => {\n const lines = _.sortBy(\n [\n ..._.map(changed, (normalizedPath) => chalkTemplate`{green ${prefix}} ${normalizedPath} {gray (changed)}`),\n ..._.map(deleted, (normalizedPath) => chalkTemplate`{red ${prefix}} ${normalizedPath} {gray (deleted)}`),\n ],\n (line) => line.slice(line.indexOf(\" \") + 1),\n );\n\n let logged = 0;\n for (const line of lines) {\n println(line);\n if (++logged == limit) break;\n }\n\n if (lines.length > logged) {\n println`{gray … ${lines.length - logged} more}`;\n }\n\n println`{gray ${pluralize(\"file\", lines.length, true)} in total. ${changed.length} changed, ${deleted.length} deleted.}`;\n println();\n};\n\nexport const REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION = dedent(/* GraphQL */ `\n subscription RemoteFileSyncEvents($localFilesVersion: String!) {\n remoteFileSyncEvents(localFilesVersion: $localFilesVersion, encoding: base64) {\n remoteFilesVersion\n changed {\n path\n mode\n content\n encoding\n }\n deleted {\n path\n }\n }\n }\n`) as Query<RemoteFileSyncEventsSubscription, RemoteFileSyncEventsSubscriptionVariables>;\n\nexport const REMOTE_FILES_VERSION_QUERY = dedent(/* GraphQL */ `\n query RemoteFilesVersion {\n remoteFilesVersion\n }\n`) as Query<RemoteFilesVersionQuery, RemoteFilesVersionQueryVariables>;\n\nexport const PUBLISH_FILE_SYNC_EVENTS_MUTATION = dedent(/* GraphQL */ `\n mutation PublishFileSyncEvents($input: PublishFileSyncEventsInput!) {\n publishFileSyncEvents(input: $input) {\n remoteFilesVersion\n }\n }\n`) as Query<PublishFileSyncEventsMutation, PublishFileSyncEventsMutationVariables>;\n"],"names":["chalkTemplate","findUp","fs","ignore","inquirer","_","ms","path","process","normalizePath","pMap","pRetry","pluralize","dedent","z","getApps","config","ArgError","InvalidSyncFileError","isEmptyDir","swallowEnoent","createLogger","println","sortByLevenshtein","sprint","log","FileSync","filesVersion","BigInt","_state","mtime","init","user","options","apps","length","email","dir","filepath","join","cwd","windows","startsWith","homeDir","slice","ensureDir","resolve","state","readJson","then","object","app","string","number","parse","catch","undefined","appSlug","prompt","type","name","message","choices","map","find","similarAppSlugs","concat","extraIgnorePaths","force","slug","relative","to","isAbsolute","absolute","pathSegments","normalize","isDirectory","reloadIgnorePaths","_ignorer","default","add","_extraIgnorePaths","content","readFileSync","info","error","ignores","walkDir","skipIgnored","hasEntries","entry","opendir","isFile","stat","write","changed","deleted","currentPath","backupPath","remove","move","retries","minTimeout","onFailedAttempt","warn","file","absolutePath","endsWith","mode","dirname","writeFile","Buffer","from","encoding","Date","now","String","_save","Array","outputJSONSync","spaces","printPaths","prefix","limit","lines","sortBy","normalizedPath","line","indexOf","logged","REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION","REMOTE_FILES_VERSION_QUERY","PUBLISH_FILE_SYNC_EVENTS_MUTATION"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/services/filesync.ts"],"sourcesContent":["import chalkTemplate from \"chalk-template\";\nimport { findUp } from \"find-up\";\nimport type { Stats } from \"fs-extra\";\nimport fs from \"fs-extra\";\nimport type { Ignore } from \"ignore\";\nimport ignore from \"ignore\";\nimport inquirer from \"inquirer\";\nimport _ from \"lodash\";\nimport ms from \"ms\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport normalizePath from \"normalize-path\";\nimport pMap from \"p-map\";\nimport pRetry from \"p-retry\";\nimport pluralize from \"pluralize\";\nimport { dedent } from \"ts-dedent\";\nimport { z } from \"zod\";\nimport type {\n PublishFileSyncEventsMutation,\n PublishFileSyncEventsMutationVariables,\n RemoteFileSyncEventsSubscription,\n RemoteFileSyncEventsSubscriptionVariables,\n RemoteFilesVersionQuery,\n RemoteFilesVersionQueryVariables,\n} from \"../__generated__/graphql.js\";\nimport type { App } from \"./app.js\";\nimport { getApps } from \"./app.js\";\nimport { config } from \"./config.js\";\nimport type { Query } from \"./edit-graphql.js\";\nimport { ArgError, InvalidSyncFileError } from \"./errors.js\";\nimport { isEmptyDir, swallowEnoent } from \"./fs-utils.js\";\nimport { createLogger } from \"./log.js\";\nimport { println, sortByLevenshtein, sprint } from \"./output.js\";\nimport type { User } from \"./user.js\";\n\nconst log = createLogger(\"filesync\");\n\ninterface File {\n path: string;\n mode: number;\n content: string;\n encoding: \"utf8\" | \"base64\";\n}\n\nexport class FileSync {\n /**\n * The {@linkcode Ignore} instance that is used to determine if a file\n * should be ignored.\n *\n * @see https://www.npmjs.com/package/ignore\n */\n private _ignorer!: Ignore;\n\n private constructor(\n /**\n * An absolute path to the directory that is being synced.\n */\n readonly dir: string,\n\n /**\n * The Gadget application this filesystem is synced to.\n */\n readonly app: App,\n\n /**\n * Additional paths to ignore when syncing the filesystem.\n */\n private _extraIgnorePaths: string[],\n\n /**\n * The state of the filesystem.\n *\n * This is persisted to `.gadget/sync.json`.\n */\n private _state: { app: string; filesVersion: string; mtime: number },\n ) {\n this._save();\n this.reloadIgnorePaths();\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() {\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() {\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(user: User, options: { dir?: string; app?: string; force?: boolean; extraIgnorePaths?: string[] }): Promise<FileSync> {\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 = 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 && _.startsWith(dir, \"~/\")) {\n // `~` doesn't expand to the home directory on Windows\n dir = path.join(config.homeDir, dir.slice(2));\n }\n\n // ensure the root directory is an absolute path and exists\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(\n z.object({\n app: z.string(),\n filesVersion: z.string(),\n mtime: z.number(),\n }).parse,\n )\n .catch(() => undefined);\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 inquirer.prompt<{ appSlug: string }>({\n type: \"list\",\n name: \"appSlug\",\n message: \"Please select the app to sync to.\",\n choices: _.map(apps, \"slug\"),\n }));\n }\n\n // try to find the appSlug in their list of apps\n const app = _.find(apps, [\"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 = sortByLevenshtein(appSlug, _.map(apps, \"slug\")).slice(0, 5);\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 ignore = options.extraIgnorePaths ?? [];\n\n if (!state) {\n // the .gadget/sync.json file didn't exist or contained invalid json\n if ((await isEmptyDir(dir)) || options.force) {\n // the directory is empty or the user passed --force\n // either way, create a fresh .gadget/sync.json file\n return new FileSync(dir, app, ignore, { 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(dir, app, ignore, 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(dir, app, ignore, { 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 * Converts an absolute path into a relative one from {@linkcode dir}.\n */\n relative(to: string): string {\n if (!path.isAbsolute(to)) {\n // the filepath is already relative\n return to;\n }\n\n return path.relative(this.dir, to);\n }\n\n /**\n * Converts a relative path into an absolute one from {@linkcode dir}.\n */\n absolute(...pathSegments: string[]): string {\n return path.resolve(this.dir, ...pathSegments);\n }\n\n /**\n * Similar to {@linkcode relative} in that it converts an absolute\n * path into a relative one from {@linkcode dir}. However, it also\n * changes any slashes to be posix/unix-like forward slashes,\n * condenses repeated slashes into a single slash, and adds a trailing\n * slash if the path is a directory.\n *\n * This is used when sending file-sync events to Gadget to ensure that\n * the paths are consistent across platforms.\n *\n * @see https://www.npmjs.com/package/normalize-path\n */\n normalize(filepath: string, isDirectory: boolean): string {\n return normalizePath(path.isAbsolute(filepath) ? this.relative(filepath) : filepath) + (isDirectory ? \"/\" : \"\");\n }\n\n /**\n * Reloads the ignore rules from the `.ignore` file.\n */\n reloadIgnorePaths(): void {\n this._ignorer = ignore.default();\n this._ignorer.add([\".DS_Store\", \"node_modules\", \".git\", ...this._extraIgnorePaths]);\n\n try {\n const content = fs.readFileSync(this.absolute(\".ignore\"), \"utf-8\");\n this._ignorer.add(content);\n log.info(\"reloaded ignore rules\");\n } catch (error) {\n swallowEnoent(error);\n }\n }\n\n /**\n * Returns `true` if the {@linkcode filepath} should be ignored.\n */\n ignores(filepath: string): boolean {\n const relative = this.relative(filepath);\n if (relative == \"\") {\n // don't ignore the root dir\n return false;\n }\n\n if (_.startsWith(relative, \"..\")) {\n // anything above the root dir is ignored\n return true;\n }\n\n return this._ignorer.ignores(relative);\n }\n\n /**\n * Walks the directory and yields each file and directory.\n *\n * If a directory is empty, or only contains ignored entries, it will\n * be yielded as a directory. Otherwise, each file within the\n * directory will be yielded.\n */\n async *walkDir({ dir = this.dir, skipIgnored = true } = {}): AsyncGenerator<[absolutePath: string, entry: Stats]> {\n // track whether the directory has any entries (ignored entries don't count)\n let hasEntries = false;\n\n for await (const entry of await fs.opendir(dir)) {\n const filepath = path.join(dir, entry.name);\n if (skipIgnored && this.ignores(filepath)) {\n continue;\n }\n\n hasEntries = true;\n\n if (entry.isDirectory()) {\n yield* this.walkDir({ dir: filepath, skipIgnored });\n } else if (entry.isFile()) {\n yield [filepath, await fs.stat(filepath)];\n }\n }\n\n if (!hasEntries) {\n // if the directory is empty, or only contains ignored entries, yield it as a directory\n yield [`${dir}/`, await fs.stat(dir)];\n }\n }\n\n /**\n * Writes the {@linkcode changed} and {@linkcode deleted} files to the filesystem.\n * @param filesVersion The files version associated with the files that are being written.\n * @param changed The files that have changed.\n * @param deleted The paths that have been deleted.\n * @param force If `true`, the files version will be updated even if it's less than the current files version.\n */\n async write(filesVersion: bigint | string, changed: Iterable<File>, deleted: Iterable<string>, force = false): Promise<void> {\n filesVersion = BigInt(filesVersion);\n\n await pMap(deleted, async (filepath) => {\n const currentPath = this.absolute(filepath);\n const backupPath = this.absolute(\".gadget/backup\", this.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 log.warn(\"failed to move file to backup\", { error });\n },\n },\n );\n });\n\n await pMap(changed, async (file) => {\n const absolutePath = this.absolute(file.path);\n if (_.endsWith(file.path, \"/\")) {\n await fs.ensureDir(absolutePath, { mode: 0o755 });\n return;\n }\n\n await fs.ensureDir(path.dirname(absolutePath), { mode: 0o755 });\n await fs.writeFile(absolutePath, Buffer.from(file.content, file.encoding), { mode: file.mode });\n\n if (absolutePath == this.absolute(\".ignore\")) {\n this.reloadIgnorePaths();\n }\n });\n\n this._state.mtime = Date.now();\n if (filesVersion > BigInt(this._state.filesVersion) || force) {\n this._state.filesVersion = String(filesVersion);\n }\n\n this._save();\n\n log.info(\"wrote\", {\n ...this._state,\n changed: _.map(Array.from(changed), \"path\"),\n deleted: Array.from(deleted),\n });\n }\n\n /**\n * Synchronously writes {@linkcode _state} to `.gadget/sync.json`.\n */\n private _save() {\n fs.outputJSONSync(this.absolute(\".gadget/sync.json\"), this._state, { spaces: 2 });\n }\n}\n\n/**\n * Pretty-prints changed and deleted filepaths to the console.\n *\n * @param prefix The prefix to print before each line.\n * @param changed The normalized paths that have changed.\n * @param deleted The normalized paths that have been deleted.\n * @param options.limit The maximum number of lines to print. Defaults to 10.\n */\nexport const printPaths = (prefix: string, changed: string[], deleted: string[], { limit = 10 } = {}) => {\n const lines = _.sortBy(\n [\n ..._.map(changed, (normalizedPath) => chalkTemplate`{green ${prefix}} ${normalizedPath} {gray (changed)}`),\n ..._.map(deleted, (normalizedPath) => chalkTemplate`{red ${prefix}} ${normalizedPath} {gray (deleted)}`),\n ],\n (line) => line.slice(line.indexOf(\" \") + 1),\n );\n\n let logged = 0;\n for (const line of lines) {\n println(line);\n if (++logged == limit) break;\n }\n\n if (lines.length > logged) {\n println`{gray … ${lines.length - logged} more}`;\n }\n\n println`{gray ${pluralize(\"file\", lines.length, true)} in total. ${changed.length} changed, ${deleted.length} deleted.}`;\n println();\n};\n\nexport const REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION = dedent(/* GraphQL */ `\n subscription RemoteFileSyncEvents($localFilesVersion: String!) {\n remoteFileSyncEvents(localFilesVersion: $localFilesVersion, encoding: base64) {\n remoteFilesVersion\n changed {\n path\n mode\n content\n encoding\n }\n deleted {\n path\n }\n }\n }\n`) as Query<RemoteFileSyncEventsSubscription, RemoteFileSyncEventsSubscriptionVariables>;\n\nexport const REMOTE_FILES_VERSION_QUERY = dedent(/* GraphQL */ `\n query RemoteFilesVersion {\n remoteFilesVersion\n }\n`) as Query<RemoteFilesVersionQuery, RemoteFilesVersionQueryVariables>;\n\nexport const PUBLISH_FILE_SYNC_EVENTS_MUTATION = dedent(/* GraphQL */ `\n mutation PublishFileSyncEvents($input: PublishFileSyncEventsInput!) {\n publishFileSyncEvents(input: $input) {\n remoteFilesVersion\n }\n }\n`) as Query<PublishFileSyncEventsMutation, PublishFileSyncEventsMutationVariables>;\n"],"names":["chalkTemplate","findUp","fs","ignore","inquirer","_","ms","path","process","normalizePath","pMap","pRetry","pluralize","dedent","z","getApps","config","ArgError","InvalidSyncFileError","isEmptyDir","swallowEnoent","createLogger","println","sortByLevenshtein","sprint","log","FileSync","filesVersion","BigInt","_state","mtime","init","user","options","apps","length","email","dir","filepath","join","cwd","windows","startsWith","homeDir","slice","ensureDir","resolve","state","readJson","then","object","app","string","number","parse","catch","undefined","appSlug","prompt","type","name","message","choices","map","find","similarAppSlugs","concat","extraIgnorePaths","force","slug","relative","to","isAbsolute","absolute","pathSegments","normalize","isDirectory","reloadIgnorePaths","_ignorer","default","add","_extraIgnorePaths","content","readFileSync","info","error","ignores","walkDir","skipIgnored","hasEntries","entry","opendir","isFile","stat","write","changed","deleted","currentPath","backupPath","remove","move","retries","minTimeout","onFailedAttempt","warn","file","absolutePath","endsWith","mode","dirname","writeFile","Buffer","from","encoding","Date","now","String","_save","Array","outputJSONSync","spaces","printPaths","prefix","limit","lines","sortBy","normalizedPath","line","indexOf","logged","REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION","REMOTE_FILES_VERSION_QUERY","PUBLISH_FILE_SYNC_EVENTS_MUTATION"],"mappings":";AAAA,OAAOA,mBAAmB,iBAAiB;AAC3C,SAASC,MAAM,QAAQ,UAAU;AAEjC,OAAOC,QAAQ,WAAW;AAE1B,OAAOC,YAAY,SAAS;AAC5B,OAAOC,cAAc,WAAW;AAChC,OAAOC,OAAO,SAAS;AACvB,OAAOC,QAAQ,KAAK;AACpB,OAAOC,UAAU,YAAY;AAC7B,OAAOC,aAAa,eAAe;AACnC,OAAOC,mBAAmB,iBAAiB;AAC3C,OAAOC,UAAU,QAAQ;AACzB,OAAOC,YAAY,UAAU;AAC7B,OAAOC,eAAe,YAAY;AAClC,SAASC,MAAM,QAAQ,YAAY;AACnC,SAASC,CAAC,QAAQ,MAAM;AAUxB,SAASC,OAAO,QAAQ,WAAW;AACnC,SAASC,MAAM,QAAQ,cAAc;AAErC,SAASC,QAAQ,EAAEC,oBAAoB,QAAQ,cAAc;AAC7D,SAASC,UAAU,EAAEC,aAAa,QAAQ,gBAAgB;AAC1D,SAASC,YAAY,QAAQ,WAAW;AACxC,SAASC,OAAO,EAAEC,iBAAiB,EAAEC,MAAM,QAAQ,cAAc;AAGjE,MAAMC,MAAMJ,aAAa;AASzB,OAAO,MAAMK;IAoCX;;;;;GAKC,GACD,IAAIC,eAAe;QACjB,OAAOC,OAAO,IAAI,CAACC,MAAM,CAACF,YAAY;IACxC;IAEA;;;;;GAKC,GACD,IAAIG,QAAQ;QACV,OAAO,IAAI,CAACD,MAAM,CAACC,KAAK;IAC1B;IAEA;;;;;;GAMC,GACD,aAAaC,KAAKC,IAAU,EAAEC,OAAqF,EAAqB;QACtI,MAAMC,OAAO,MAAMnB,QAAQiB;QAC3B,IAAIE,KAAKC,MAAM,KAAK,GAAG;YACrB,MAAM,IAAIlB,SACRO,MAAM,CAAC;eACA,EAAEQ,KAAKI,KAAK,CAAC;;;MAGtB,CAAC;QAEH;QAEA,IAAIC,MAAMJ,QAAQI,GAAG;QACrB,IAAI,CAACA,KAAK;YACR,sCAAsC;YACtC,MAAMC,WAAW,MAAMrC,OAAO;YAC9B,IAAIqC,UAAU;gBACZ,8DAA8D;gBAC9DD,MAAM9B,KAAKgC,IAAI,CAACD,UAAU;YAC5B,OAAO;gBACL,qEAAqE;gBACrED,MAAM7B,QAAQgC,GAAG;YACnB;QACF;QAEA,IAAIxB,OAAOyB,OAAO,IAAIpC,EAAEqC,UAAU,CAACL,KAAK,OAAO;YAC7C,sDAAsD;YACtDA,MAAM9B,KAAKgC,IAAI,CAACvB,OAAO2B,OAAO,EAAEN,IAAIO,KAAK,CAAC;QAC5C;QAEA,2DAA2D;QAC3D,MAAM1C,GAAG2C,SAAS,CAAER,MAAM9B,KAAKuC,OAAO,CAACT;QAEvC,yCAAyC;QACzC,MAAMU,QAAQ,MAAM7C,GACjB8C,QAAQ,CAACzC,KAAKgC,IAAI,CAACF,KAAK,sBACxBY,IAAI,CACHnC,EAAEoC,MAAM,CAAC;YACPC,KAAKrC,EAAEsC,MAAM;YACbzB,cAAcb,EAAEsC,MAAM;YACtBtB,OAAOhB,EAAEuC,MAAM;QACjB,GAAGC,KAAK,EAETC,KAAK,CAAC,IAAMC;QAEf,IAAIC,UAAUxB,QAAQkB,GAAG,IAAIJ,OAAOI;QACpC,IAAI,CAACM,SAAS;YACZ,0EAA0E;YACzE,CAAA,EAAEA,OAAO,EAAE,GAAG,MAAMrD,SAASsD,MAAM,CAAsB;gBACxDC,MAAM;gBACNC,MAAM;gBACNC,SAAS;gBACTC,SAASzD,EAAE0D,GAAG,CAAC7B,MAAM;YACvB,EAAC;QACH;QAEA,gDAAgD;QAChD,MAAMiB,MAAM9C,EAAE2D,IAAI,CAAC9B,MAAM;YAAC;YAAQuB;SAAQ;QAC1C,IAAI,CAACN,KAAK;YACR,6DAA6D;YAC7D,4DAA4D;YAC5D,8DAA8D;YAC9D,YAAY;YACZ,MAAMc,kBAAkB1C,kBAAkBkC,SAASpD,EAAE0D,GAAG,CAAC7B,MAAM,SAASU,KAAK,CAAC,GAAG;YACjF,MAAM,IAAI3B,SACRO,MAAM,CAAC;;;UAGL,EAAEiC,QAAQ;;;;;MAKd,CAAC,CAACS,MAAM,CAAC,CAAC,IAAI,EAAED,gBAAgB1B,IAAI,CAAC,UAAU,CAAC;QAElD;QAEA,MAAMpC,SAAS8B,QAAQkC,gBAAgB,IAAI,EAAE;QAE7C,IAAI,CAACpB,OAAO;YACV,oEAAoE;YACpE,IAAI,AAAC,MAAM5B,WAAWkB,QAASJ,QAAQmC,KAAK,EAAE;gBAC5C,oDAAoD;gBACpD,oDAAoD;gBACpD,OAAO,IAAI1C,SAASW,KAAKc,KAAKhD,QAAQ;oBAAEgD,KAAKA,IAAIkB,IAAI;oBAAE1C,cAAc;oBAAKG,OAAO;gBAAE;YACrF;YAEA,6DAA6D;YAC7D,MAAM,IAAIZ,qBAAqBmB,KAAKc,IAAIkB,IAAI;QAC9C;QAEA,oCAAoC;QACpC,IAAItB,MAAMI,GAAG,KAAKA,IAAIkB,IAAI,EAAE;YAC1B,yEAAyE;YACzE,OAAO,IAAI3C,SAASW,KAAKc,KAAKhD,QAAQ4C;QACxC;QAEA,oDAAoD;QACpD,IAAId,QAAQmC,KAAK,EAAE;YACjB,kFAAkF;YAClF,OAAO,IAAI1C,SAASW,KAAKc,KAAKhD,QAAQ;gBAAEgD,KAAKA,IAAIkB,IAAI;gBAAE1C,cAAc;gBAAKG,OAAO;YAAE;QACrF;QAEA,kDAAkD;QAClD,MAAM,IAAIb,SAASO,MAAM,CAAC;;;eAGf,EAAE2B,IAAIkB,IAAI,CAAC,SAAS,EAAEhC,IAAI;;;;eAI1B,EAAEU,MAAMI,GAAG,CAAC;;;;eAIZ,EAAEA,IAAIkB,IAAI,CAAC,SAAS,EAAEhC,IAAI;;;MAGnC,CAAC;IACL;IAEA;;GAEC,GACDiC,SAASC,EAAU,EAAU;QAC3B,IAAI,CAAChE,KAAKiE,UAAU,CAACD,KAAK;YACxB,mCAAmC;YACnC,OAAOA;QACT;QAEA,OAAOhE,KAAK+D,QAAQ,CAAC,IAAI,CAACjC,GAAG,EAAEkC;IACjC;IAEA;;GAEC,GACDE,SAAS,GAAGC,YAAsB,EAAU;QAC1C,OAAOnE,KAAKuC,OAAO,CAAC,IAAI,CAACT,GAAG,KAAKqC;IACnC;IAEA;;;;;;;;;;;GAWC,GACDC,UAAUrC,QAAgB,EAAEsC,WAAoB,EAAU;QACxD,OAAOnE,cAAcF,KAAKiE,UAAU,CAAClC,YAAY,IAAI,CAACgC,QAAQ,CAAChC,YAAYA,YAAasC,CAAAA,cAAc,MAAM,EAAC;IAC/G;IAEA;;GAEC,GACDC,oBAA0B;QACxB,IAAI,CAACC,QAAQ,GAAG3E,OAAO4E,OAAO;QAC9B,IAAI,CAACD,QAAQ,CAACE,GAAG,CAAC;YAAC;YAAa;YAAgB;eAAW,IAAI,CAACC,iBAAiB;SAAC;QAElF,IAAI;YACF,MAAMC,UAAUhF,GAAGiF,YAAY,CAAC,IAAI,CAACV,QAAQ,CAAC,YAAY;YAC1D,IAAI,CAACK,QAAQ,CAACE,GAAG,CAACE;YAClBzD,IAAI2D,IAAI,CAAC;QACX,EAAE,OAAOC,OAAO;YACdjE,cAAciE;QAChB;IACF;IAEA;;GAEC,GACDC,QAAQhD,QAAgB,EAAW;QACjC,MAAMgC,WAAW,IAAI,CAACA,QAAQ,CAAChC;QAC/B,IAAIgC,YAAY,IAAI;YAClB,4BAA4B;YAC5B,OAAO;QACT;QAEA,IAAIjE,EAAEqC,UAAU,CAAC4B,UAAU,OAAO;YAChC,yCAAyC;YACzC,OAAO;QACT;QAEA,OAAO,IAAI,CAACQ,QAAQ,CAACQ,OAAO,CAAChB;IAC/B;IAEA;;;;;;GAMC,GACD,OAAOiB,QAAQ,EAAElD,MAAM,IAAI,CAACA,GAAG,EAAEmD,cAAc,IAAI,EAAE,GAAG,CAAC,CAAC,EAAwD;QAChH,4EAA4E;QAC5E,IAAIC,aAAa;QAEjB,WAAW,MAAMC,SAAS,CAAA,MAAMxF,GAAGyF,OAAO,CAACtD,IAAG,EAAG;YAC/C,MAAMC,WAAW/B,KAAKgC,IAAI,CAACF,KAAKqD,MAAM9B,IAAI;YAC1C,IAAI4B,eAAe,IAAI,CAACF,OAAO,CAAChD,WAAW;gBACzC;YACF;YAEAmD,aAAa;YAEb,IAAIC,MAAMd,WAAW,IAAI;gBACvB,OAAO,IAAI,CAACW,OAAO,CAAC;oBAAElD,KAAKC;oBAAUkD;gBAAY;YACnD,OAAO,IAAIE,MAAME,MAAM,IAAI;gBACzB,MAAM;oBAACtD;oBAAU,MAAMpC,GAAG2F,IAAI,CAACvD;iBAAU;YAC3C;QACF;QAEA,IAAI,CAACmD,YAAY;YACf,uFAAuF;YACvF,MAAM;gBAAC,CAAC,EAAEpD,IAAI,CAAC,CAAC;gBAAE,MAAMnC,GAAG2F,IAAI,CAACxD;aAAK;QACvC;IACF;IAEA;;;;;;GAMC,GACD,MAAMyD,MAAMnE,YAA6B,EAAEoE,OAAuB,EAAEC,OAAyB,EAAE5B,QAAQ,KAAK,EAAiB;QAC3HzC,eAAeC,OAAOD;QAEtB,MAAMjB,KAAKsF,SAAS,OAAO1D;YACzB,MAAM2D,cAAc,IAAI,CAACxB,QAAQ,CAACnC;YAClC,MAAM4D,aAAa,IAAI,CAACzB,QAAQ,CAAC,kBAAkB,IAAI,CAACH,QAAQ,CAAChC;YAEjE,iDAAiD;YACjD,gEAAgE;YAChE,kEAAkE;YAClE,iCAAiC;YACjC,MAAM3B,OACJ;gBACE,IAAI;oBACF,4DAA4D;oBAC5D,qCAAqC;oBACrC,MAAMT,GAAGiG,MAAM,CAACD;oBAChB,MAAMhG,GAAGkG,IAAI,CAACH,aAAaC;gBAC7B,EAAE,OAAOb,OAAO;oBACd,uDAAuD;oBACvDjE,cAAciE;gBAChB;YACF,GACA;gBACEgB,SAAS;gBACTC,YAAYhG,GAAG;gBACfiG,iBAAiB,CAAClB;oBAChB5D,IAAI+E,IAAI,CAAC,iCAAiC;wBAAEnB;oBAAM;gBACpD;YACF;QAEJ;QAEA,MAAM3E,KAAKqF,SAAS,OAAOU;YACzB,MAAMC,eAAe,IAAI,CAACjC,QAAQ,CAACgC,KAAKlG,IAAI;YAC5C,IAAIF,EAAEsG,QAAQ,CAACF,KAAKlG,IAAI,EAAE,MAAM;gBAC9B,MAAML,GAAG2C,SAAS,CAAC6D,cAAc;oBAAEE,MAAM;gBAAM;gBAC/C;YACF;YAEA,MAAM1G,GAAG2C,SAAS,CAACtC,KAAKsG,OAAO,CAACH,eAAe;gBAAEE,MAAM;YAAM;YAC7D,MAAM1G,GAAG4G,SAAS,CAACJ,cAAcK,OAAOC,IAAI,CAACP,KAAKvB,OAAO,EAAEuB,KAAKQ,QAAQ,GAAG;gBAAEL,MAAMH,KAAKG,IAAI;YAAC;YAE7F,IAAIF,gBAAgB,IAAI,CAACjC,QAAQ,CAAC,YAAY;gBAC5C,IAAI,CAACI,iBAAiB;YACxB;QACF;QAEA,IAAI,CAAChD,MAAM,CAACC,KAAK,GAAGoF,KAAKC,GAAG;QAC5B,IAAIxF,eAAeC,OAAO,IAAI,CAACC,MAAM,CAACF,YAAY,KAAKyC,OAAO;YAC5D,IAAI,CAACvC,MAAM,CAACF,YAAY,GAAGyF,OAAOzF;QACpC;QAEA,IAAI,CAAC0F,KAAK;QAEV5F,IAAI2D,IAAI,CAAC,SAAS;YAChB,GAAG,IAAI,CAACvD,MAAM;YACdkE,SAAS1F,EAAE0D,GAAG,CAACuD,MAAMN,IAAI,CAACjB,UAAU;YACpCC,SAASsB,MAAMN,IAAI,CAAChB;QACtB;IACF;IAEA;;GAEC,GACD,AAAQqB,QAAQ;QACdnH,GAAGqH,cAAc,CAAC,IAAI,CAAC9C,QAAQ,CAAC,sBAAsB,IAAI,CAAC5C,MAAM,EAAE;YAAE2F,QAAQ;QAAE;IACjF;IA9VA,YACE;;KAEC,GACD,AAASnF,GAAW,EAEpB;;KAEC,GACD,AAASc,GAAQ,EAEjB;;KAEC,GACD,AAAQ8B,iBAA2B,EAEnC;;;;KAIC,GACD,AAAQpD,MAA4D,CACpE;;;;;QA9BF;;;;;GAKC,GACD,uBAAQiD,YAAR,KAAA;aAMWzC,MAAAA;aAKAc,MAAAA;aAKD8B,oBAAAA;aAOApD,SAAAA;QAER,IAAI,CAACwF,KAAK;QACV,IAAI,CAACxC,iBAAiB;IACxB;AAsUF;AAEA;;;;;;;CAOC,GACD,OAAO,MAAM4C,aAAa,CAACC,QAAgB3B,SAAmBC,SAAmB,EAAE2B,QAAQ,EAAE,EAAE,GAAG,CAAC,CAAC;IAClG,MAAMC,QAAQvH,EAAEwH,MAAM,CACpB;WACKxH,EAAE0D,GAAG,CAACgC,SAAS,CAAC+B,iBAAmB9H,aAAa,CAAC,OAAO,EAAE0H,OAAO,EAAE,EAAEI,eAAe,iBAAiB,CAAC;WACtGzH,EAAE0D,GAAG,CAACiC,SAAS,CAAC8B,iBAAmB9H,aAAa,CAAC,KAAK,EAAE0H,OAAO,EAAE,EAAEI,eAAe,iBAAiB,CAAC;KACxG,EACD,CAACC,OAASA,KAAKnF,KAAK,CAACmF,KAAKC,OAAO,CAAC,OAAO;IAG3C,IAAIC,SAAS;IACb,KAAK,MAAMF,QAAQH,MAAO;QACxBtG,QAAQyG;QACR,IAAI,EAAEE,UAAUN,OAAO;IACzB;IAEA,IAAIC,MAAMzF,MAAM,GAAG8F,QAAQ;QACzB3G,OAAO,CAAC,QAAQ,EAAEsG,MAAMzF,MAAM,GAAG8F,OAAO,MAAM,CAAC;IACjD;IAEA3G,OAAO,CAAC,MAAM,EAAEV,UAAU,QAAQgH,MAAMzF,MAAM,EAAE,MAAM,WAAW,EAAE4D,QAAQ5D,MAAM,CAAC,UAAU,EAAE6D,QAAQ7D,MAAM,CAAC,UAAU,CAAC;IACxHb;AACF,EAAE;AAEF,OAAO,MAAM4G,uCAAuCrH,OAAO,WAAW,GAAG,CAAC;;;;;;;;;;;;;;;AAe1E,CAAC,EAAwF;AAEzF,OAAO,MAAMsH,6BAA6BtH,OAAO,WAAW,GAAG,CAAC;;;;AAIhE,CAAC,EAAsE;AAEvE,OAAO,MAAMuH,oCAAoCvH,OAAO,WAAW,GAAG,CAAC;;;;;;AAMvE,CAAC,EAAkF"}
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@gadgetinc/ggt",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@gadgetinc/ggt",
9
- "version": "0.3.0",
9
+ "version": "0.3.2",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@sentry/node": "^7.74.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gadgetinc/ggt",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "The command-line interface for Gadget",
5
5
  "homepage": "https://github.com/gadget-inc/ggt",
6
6
  "bugs": "https://github.com/gadget-inc/ggt/issues",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/services/client.ts"],"sourcesContent":["import type { GraphQLError } from \"graphql\";\nimport type { ExecutionResult, SubscribePayload } from \"graphql-ws\";\nimport { createClient } from \"graphql-ws\";\nimport type { ClientRequestArgs } from \"http\";\nimport _ from \"lodash\";\nimport assert from \"node:assert\";\nimport type { JsonObject, SetOptional } from \"type-fest\";\nimport type { CloseEvent, ErrorEvent } from \"ws\";\nimport WebSocket from \"ws\";\nimport type { App } from \"./app.js\";\nimport { config } from \"./config.js\";\nimport { ClientError } from \"./errors.js\";\nimport { loadCookie } from \"./http.js\";\nimport { createLogger } from \"./log.js\";\n\nenum ConnectionStatus {\n CONNECTED,\n DISCONNECTED,\n RECONNECTING,\n}\n\nconst log = createLogger(\"client\");\n\n/**\n * Client is a GraphQL client connected to a Gadget application's /edit/api/graphql-ws endpoint.\n *\n * NOTE: In order to use the Client, the user must be logged in and an app must have been selected (`context.app` and\n * `context.session` must be set).\n */\nexport class Client {\n // assume the client is going to connect\n status = ConnectionStatus.CONNECTED;\n\n private _client: ReturnType<typeof createClient>;\n\n constructor(app: App) {\n this._client = createClient({\n url: `wss://${app.slug}.${config.domains.app}/edit/api/graphql-ws`,\n shouldRetry: _.constant(true),\n webSocketImpl: class extends WebSocket {\n constructor(address: string | URL, protocols?: string | string[], wsOptions?: WebSocket.ClientOptions | ClientRequestArgs) {\n // this cookie should be available since we were given an app which requires a cookie to load\n const cookie = loadCookie();\n assert(cookie, \"missing cookie when connecting to GraphQL API\");\n\n super(address, protocols, {\n ...wsOptions,\n headers: {\n ...wsOptions?.headers,\n \"user-agent\": config.versionFull,\n cookie,\n },\n });\n }\n },\n on: {\n connecting: () => {\n switch (this.status) {\n case ConnectionStatus.DISCONNECTED:\n this.status = ConnectionStatus.RECONNECTING;\n log.info(\"reconnecting\");\n break;\n case ConnectionStatus.RECONNECTING:\n log.info(\"retrying\");\n break;\n default:\n log.info(\"connecting\");\n break;\n }\n },\n connected: () => {\n if (this.status === ConnectionStatus.RECONNECTING) {\n log.info(\"reconnected\");\n } else {\n log.info(\"connected\");\n }\n\n // let the other on connected listeners see what status we're in\n setImmediate(() => (this.status = ConnectionStatus.CONNECTED));\n },\n closed: (e) => {\n const event = e as CloseEvent;\n if (event.wasClean) {\n log.info(\"connection closed\");\n return;\n }\n\n if (this.status === ConnectionStatus.CONNECTED) {\n this.status = ConnectionStatus.DISCONNECTED;\n log.info(\"disconnected\");\n }\n },\n error: (error) => {\n if (this.status == ConnectionStatus.RECONNECTING) {\n log.error(\"failed to reconnect\", { error });\n } else {\n log.error(\"connection error\", { error });\n }\n },\n },\n });\n }\n\n subscribe<Data extends JsonObject, Variables extends JsonObject, Extensions extends JsonObject = JsonObject>(\n payload: Payload<Data, Variables>,\n sink: SetOptional<Sink<Data, Extensions>, \"complete\">,\n ): () => void {\n let subscribePayload: SubscribePayload;\n let removeConnectedListener = _.noop.bind(_);\n\n if (_.isFunction(payload.variables)) {\n // the caller wants us to re-evaluate the variables every time\n // graphql-ws re-subscribes after reconnecting\n subscribePayload = { ...payload, variables: payload.variables() };\n removeConnectedListener = this._client.on(\"connected\", () => {\n if (this.status == ConnectionStatus.RECONNECTING) {\n (subscribePayload as any).variables = (payload.variables as any)();\n const [type, operation] = _.split(subscribePayload.query, / |\\(/, 2);\n log.info(\"re-sending graphql query\", { type, operation });\n }\n });\n } else {\n subscribePayload = payload as SubscribePayload;\n }\n\n const [type, operation] = _.split(subscribePayload.query, / |\\(/, 2);\n log.info(\"sending graphql query\", { type, operation });\n\n const unsubscribe = this._client.subscribe(subscribePayload, {\n next: (result: ExecutionResult<Data, Extensions>) => {\n sink.next(result);\n },\n error: (error) => {\n sink.error(new ClientError(subscribePayload, error as Error | GraphQLError[] | CloseEvent | ErrorEvent));\n },\n complete: () => {\n sink.complete?.();\n },\n });\n\n return () => {\n removeConnectedListener();\n unsubscribe();\n };\n }\n\n subscribeUnwrap<Data extends JsonObject, Variables extends JsonObject>(\n payload: Payload<Data, Variables>,\n sink: { next: (data: Data) => void; error: (error: ClientError) => void },\n ): () => void {\n const unsubscribe = this.subscribe(payload, {\n ...sink,\n next: (result) => {\n if (result.errors) {\n unsubscribe();\n sink.error(new ClientError(payload, result.errors));\n return;\n }\n\n if (!result.data) {\n sink.error(new ClientError(payload, \"We received a response without data\"));\n unsubscribe();\n return;\n }\n\n sink.next(result.data);\n },\n });\n\n return unsubscribe;\n }\n\n query<Data extends JsonObject, Variables extends JsonObject, Extensions extends JsonObject = JsonObject>(\n payload: Payload<Data, Variables>,\n ): Promise<ExecutionResult<Data, Extensions>> {\n return new Promise((resolve, reject) => {\n this.subscribe<Data, Variables, Extensions>(payload, { next: resolve, error: reject });\n });\n }\n\n async queryUnwrap<Data extends JsonObject, Variables extends JsonObject>(payload: Payload<Data, Variables>): Promise<Data> {\n const result = await this.query(payload);\n if (result.errors) throw new ClientError(payload, result.errors);\n if (!result.data) throw new ClientError(payload, \"We received a response without data\");\n return result.data;\n }\n\n async dispose(): Promise<void> {\n await this._client.dispose();\n }\n}\n\nexport type Query<\n Data extends JsonObject,\n Variables extends JsonObject = JsonObject,\n Extensions extends JsonObject = JsonObject,\n> = string & {\n __TData?: Data;\n __TVariables?: Variables;\n __TExtensions?: Extensions;\n};\n\nexport interface Payload<Data extends JsonObject, Variables extends JsonObject> {\n readonly query: Query<Data, Variables>;\n readonly variables?: Variables | (() => Variables) | null;\n}\n\nexport interface Sink<Data extends JsonObject, Extensions extends JsonObject> {\n next(value: ExecutionResult<Data, Extensions>): void;\n error(error: ClientError): void;\n complete(): void;\n}\n"],"names":["createClient","_","assert","WebSocket","config","ClientError","loadCookie","createLogger","ConnectionStatus","log","Client","subscribe","payload","sink","subscribePayload","removeConnectedListener","noop","bind","isFunction","variables","_client","on","status","type","operation","split","query","info","unsubscribe","next","result","error","complete","subscribeUnwrap","errors","data","Promise","resolve","reject","queryUnwrap","dispose","constructor","app","url","slug","domains","shouldRetry","constant","webSocketImpl","address","protocols","wsOptions","cookie","headers","versionFull","connecting","connected","setImmediate","closed","e","event","wasClean"],"mappings":"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"}