@gadgetinc/ggt 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +2 -2
  2. package/bin/dev.js +2 -1
  3. package/bin/run.js +3 -1
  4. package/lib/commands/deploy.js +11 -15
  5. package/lib/commands/deploy.js.map +1 -1
  6. package/lib/commands/list.js +7 -11
  7. package/lib/commands/list.js.map +1 -1
  8. package/lib/commands/login.js +8 -12
  9. package/lib/commands/login.js.map +1 -1
  10. package/lib/commands/logout.js +3 -7
  11. package/lib/commands/logout.js.map +1 -1
  12. package/lib/commands/root.js +17 -44
  13. package/lib/commands/root.js.map +1 -1
  14. package/lib/commands/sync.js +10 -29
  15. package/lib/commands/sync.js.map +1 -1
  16. package/lib/commands/version.js +2 -6
  17. package/lib/commands/version.js.map +1 -1
  18. package/lib/commands/whoami.js +5 -9
  19. package/lib/commands/whoami.js.map +1 -1
  20. package/lib/ggt.js +40 -0
  21. package/lib/ggt.js.map +1 -0
  22. package/lib/services/app/app.js +8 -6
  23. package/lib/services/app/app.js.map +1 -1
  24. package/lib/services/app/edit/client.js +176 -0
  25. package/lib/services/app/edit/client.js.map +1 -0
  26. package/lib/services/app/edit/edit.js +155 -0
  27. package/lib/services/app/edit/edit.js.map +1 -0
  28. package/lib/services/app/edit/error.js +65 -0
  29. package/lib/services/app/edit/error.js.map +1 -0
  30. package/lib/services/app/edit/operation.js +87 -0
  31. package/lib/services/app/edit/operation.js.map +1 -0
  32. package/lib/services/command/arg.js +5 -5
  33. package/lib/services/command/arg.js.map +1 -1
  34. package/lib/services/command/command.js +21 -7
  35. package/lib/services/command/command.js.map +1 -1
  36. package/lib/services/command/context.js +108 -56
  37. package/lib/services/command/context.js.map +1 -1
  38. package/lib/services/filesync/changes.js +7 -9
  39. package/lib/services/filesync/changes.js.map +1 -1
  40. package/lib/services/filesync/conflicts.js +11 -9
  41. package/lib/services/filesync/conflicts.js.map +1 -1
  42. package/lib/services/filesync/directory.js +2 -2
  43. package/lib/services/filesync/directory.js.map +1 -1
  44. package/lib/services/filesync/filesync.js +73 -43
  45. package/lib/services/filesync/filesync.js.map +1 -1
  46. package/lib/services/filesync/hashes.js +18 -15
  47. package/lib/services/filesync/hashes.js.map +1 -1
  48. package/lib/services/http/auth.js +4 -4
  49. package/lib/services/http/auth.js.map +1 -1
  50. package/lib/services/http/http.js +83 -23
  51. package/lib/services/http/http.js.map +1 -1
  52. package/lib/services/output/log/logger.js +11 -6
  53. package/lib/services/output/log/logger.js.map +1 -1
  54. package/lib/services/output/log/printer.js.map +1 -1
  55. package/lib/services/output/log/structured.js +2 -2
  56. package/lib/services/output/log/structured.js.map +1 -1
  57. package/lib/services/output/notify.js +3 -7
  58. package/lib/services/output/notify.js.map +1 -1
  59. package/lib/services/output/prompt.js +11 -11
  60. package/lib/services/output/prompt.js.map +1 -1
  61. package/lib/services/output/report.js +51 -67
  62. package/lib/services/output/report.js.map +1 -1
  63. package/lib/services/output/update.js +9 -10
  64. package/lib/services/output/update.js.map +1 -1
  65. package/lib/services/user/user.js +19 -25
  66. package/lib/services/user/user.js.map +1 -1
  67. package/lib/services/util/collection.js +1 -1
  68. package/lib/services/util/collection.js.map +1 -1
  69. package/lib/services/util/function.js +29 -11
  70. package/lib/services/util/function.js.map +1 -1
  71. package/lib/services/util/number.js +3 -3
  72. package/lib/services/util/number.js.map +1 -1
  73. package/lib/services/util/object.js +4 -4
  74. package/lib/services/util/object.js.map +1 -1
  75. package/lib/services/util/paths.js +4 -4
  76. package/lib/services/util/paths.js.map +1 -1
  77. package/lib/services/util/types.js +3 -1
  78. package/lib/services/util/types.js.map +1 -1
  79. package/npm-shrinkwrap.json +579 -411
  80. package/package.json +15 -14
  81. package/lib/main.js +0 -8
  82. package/lib/main.js.map +0 -1
  83. package/lib/services/app/edit-graphql.js +0 -392
  84. package/lib/services/app/edit-graphql.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/sync.ts"],"sourcesContent":["import dayjs from \"dayjs\";\nimport { execa } from \"execa\";\nimport ms from \"ms\";\nimport path from \"node:path\";\nimport Watcher from \"watcher\";\nimport which from \"which\";\nimport type { ArgsSpec } from \"../services/command/arg.js\";\nimport type { Command, Usage } from \"../services/command/command.js\";\nimport { config } from \"../services/config/config.js\";\nimport { Changes } from \"../services/filesync/changes.js\";\nimport { YarnNotFoundError } from \"../services/filesync/error.js\";\nimport { FileSync, FileSyncArgs } from \"../services/filesync/filesync.js\";\nimport { notify } from \"../services/output/notify.js\";\nimport { reportErrorAndExit } from \"../services/output/report.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { getUserOrLogin } from \"../services/user/user.js\";\nimport { debounce } from \"../services/util/function.js\";\nimport { isAbortError } from \"../services/util/is.js\";\n\nexport const usage: Usage = () => sprint`\n Sync your Gadget environment's source code with your local filesystem.\n\n {bold USAGE}\n ggt sync [DIRECTORY]\n\n {bold ARGUMENTS}\n DIRECTORY The directory to sync files to (default: \".\")\n\n {bold FLAGS}\n -a, --app=<name> The Gadget application to sync files to\n --prefer=<filesystem> Prefer \"local\" or \"gadget\" conflicting changes\n --once Sync once and exit\n --force Sync regardless of local filesystem state\n\n {bold DESCRIPTION}\n Sync allows you to synchronize your Gadget application's source\n code with your local filesystem.\n\n While ggt sync is running, local file changes are immediately\n reflected within Gadget, while files that are changed in Gadget are\n immediately saved to your local filesystem.\n\n Ideal for:\n • Local development with editors like VSCode\n • Storing source code in a Git repository like GitHub\n\n Sync looks for a \".ignore\" file to exclude certain files/directories\n from being synced. The format is identical to Git's.\n\n These files are always ignored:\n • .DS_Store\n • .gadget\n • .git\n • node_modules\n\n Note:\n • Sync only works with your development environment\n • Avoid deleting/moving all your files while sync is running\n • Gadget only supports Yarn v1 for dependency installation\n\n {bold EXAMPLE}\n $ ggt sync ~/gadget/example --app example\n\n App example\n Editor https://example.gadget.app/edit\n Playground https://example.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/example\n\n Endpoints\n • https://example.gadget.app\n • https://example--development.gadget.app\n\n Watching for file changes... {gray Press Ctrl+C to stop}\n\n → Sent {gray 09:06:25 AM}\n {greenBright routes/GET-hello.js + created}\n\n → Sent {gray 09:06:49 AM}\n {blueBright routes/GET-hello.js ± updated}\n\n ← Received {gray 09:06:54 AM}\n {blueBright routes/GET-hello.js ± updated}\n\n ← Received {gray 09:06:56 AM}\n {redBright routes/GET-hello.js - deleted}\n ^C Stopping... {gray press Ctrl+C again to force}\n\n Goodbye!\n`;\n\nexport const args = {\n ...FileSyncArgs,\n \"--once\": Boolean,\n \"--file-push-delay\": { type: Number, default: ms(\"100ms\") },\n \"--file-watch-debounce\": { type: Number, default: ms(\"300ms\") },\n \"--file-watch-poll-interval\": { type: Number, default: ms(\"3s\") },\n \"--file-watch-poll-timeout\": { type: Number, default: ms(\"20s\") },\n \"--file-watch-rename-timeout\": { type: Number, default: ms(\"1.25s\") },\n} satisfies ArgsSpec;\n\nexport type SyncArgs = typeof args;\n\n/**\n * Runs the sync process until it is stopped or an error occurs.\n */\nexport const command: Command<typeof args> = async (ctx) => {\n const filesync = await FileSync.init({ ctx, user: await getUserOrLogin() });\n await filesync.sync();\n\n if (ctx.args[\"--once\"]) {\n ctx.log.println(\"Done!\");\n return;\n }\n\n if (!which.sync(\"yarn\", { nothrow: true })) {\n throw new YarnNotFoundError();\n }\n\n /**\n * A list of filepaths that have changed because we (this ggt process)\n * modified them. This is used to avoid reacting to filesystem events\n * that we caused, which would cause an infinite loop.\n */\n const recentWritesToLocalFilesystem = new Map<string, number>();\n\n const clearRecentWritesInterval = setInterval(() => {\n for (const [path, timestamp] of recentWritesToLocalFilesystem) {\n if (dayjs().isAfter(timestamp + ms(\"5s\"))) {\n // this change should have been seen by now\n recentWritesToLocalFilesystem.delete(path);\n }\n }\n }, ms(\"1s\")).unref();\n\n /**\n * Subscribe to file changes on Gadget and apply them to the local\n * filesystem.\n */\n const unsubscribeFromGadgetChanges = filesync.subscribeToGadgetChanges({\n onError: (error) => ctx.abort(error),\n beforeChanges: ({ changed, deleted }) => {\n // add all the files and directories we're about to touch to\n // recentWritesToLocalFilesystem so that we don't send them back\n // to Gadget\n for (const filepath of [...changed, ...deleted]) {\n recentWritesToLocalFilesystem.set(filepath, Date.now());\n\n let dir = path.dirname(filepath);\n while (dir !== \".\") {\n recentWritesToLocalFilesystem.set(dir + \"/\", Date.now());\n dir = path.dirname(dir);\n }\n }\n },\n afterChanges: async ({ changes }) => {\n if (changes.has(\"yarn.lock\")) {\n await execa(\"yarn\", [\"install\", \"--check-files\"], { cwd: filesync.directory.path }).catch((error) => {\n ctx.log.error(\"yarn install failed\", { error });\n });\n }\n },\n });\n\n /**\n * A buffer of local file changes to send to Gadget.\n */\n const localChangesBuffer = new Changes();\n\n /**\n * A debounced function that sends the local file changes to Gadget.\n */\n const sendChangesToGadget = debounce(ctx.args[\"--file-push-delay\"], () => {\n const changes = new Changes(localChangesBuffer.entries());\n localChangesBuffer.clear();\n filesync.sendChangesToGadget({ changes }).catch((error) => ctx.abort(error));\n });\n\n ctx.log.debug(\"watching\", { path: filesync.directory.path });\n\n /**\n * Watches the local filesystem for changes.\n */\n const fileWatcher = new Watcher(\n filesync.directory.path,\n {\n // don't emit an event for every watched file on boot\n ignoreInitial: true,\n // don't emit changes to .gadget/ files because they're readonly (Gadget manages them)\n ignore: (path: string) => filesync.directory.relative(path).startsWith(\".gadget\") || filesync.directory.ignores(path),\n renameDetection: true,\n recursive: true,\n debounce: ctx.args[\"--file-watch-debounce\"],\n pollingInterval: ctx.args[\"--file-watch-poll-interval\"],\n pollingTimeout: ctx.args[\"--file-watch-poll-timeout\"],\n renameTimeout: ctx.args[\"--file-watch-rename-timeout\"],\n },\n (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 = filesync.directory.normalize(filepath, isDirectory);\n\n ctx.log.trace(\"file event\", { event, isDirectory, path: normalizedPath });\n\n if (filepath === filesync.directory.absolute(\".ignore\")) {\n filesync.directory.loadIgnoreFile().catch((error) => ctx.abort(error));\n } else if (filesync.directory.ignores(filepath)) {\n return;\n }\n\n if (recentWritesToLocalFilesystem.delete(normalizedPath)) {\n ctx.log.trace(\"ignoring event because we caused it\", { event, path: normalizedPath });\n return;\n }\n\n switch (event) {\n case \"add\":\n case \"addDir\":\n localChangesBuffer.set(normalizedPath, { type: \"create\" });\n break;\n case \"rename\":\n case \"renameDir\": {\n const oldNormalizedPath = filesync.directory.normalize(absolutePath, isDirectory);\n localChangesBuffer.set(normalizedPath, { type: \"create\", oldPath: oldNormalizedPath });\n break;\n }\n case \"change\": {\n localChangesBuffer.set(normalizedPath, { type: \"update\" });\n break;\n }\n case \"unlink\":\n case \"unlinkDir\": {\n localChangesBuffer.set(normalizedPath, { type: \"delete\" });\n break;\n }\n }\n\n sendChangesToGadget();\n },\n ).once(\"error\", (error) => ctx.abort(error));\n\n ctx.log.printlns`\n ggt v${config.version}\n\n App ${filesync.app.slug}\n Editor https://${filesync.app.slug}.gadget.app/edit\n Playground https://${filesync.app.slug}.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/${filesync.app.slug}\n\n Endpoints ${\n filesync.app.hasSplitEnvironments\n ? `\n • https://${filesync.app.primaryDomain}\n • https://${filesync.app.slug}--development.gadget.app`\n : `\n • https://${filesync.app.primaryDomain}`\n }\n\n Watching for file changes... {gray Press Ctrl+C to stop}\n `;\n\n ctx.onAbort(async (reason) => {\n ctx.log.info(\"stopping\", { reason });\n\n unsubscribeFromGadgetChanges();\n fileWatcher.close();\n clearInterval(clearRecentWritesInterval);\n sendChangesToGadget.flush();\n\n try {\n await filesync.idle();\n } catch (error) {\n ctx.log.error(\"error while waiting for idle\", { error });\n }\n\n if (isAbortError(reason)) {\n ctx.log.printlns(\"Goodbye!\");\n return;\n }\n\n notify({ subtitle: \"Uh oh!\", message: \"An error occurred while syncing files\" });\n await reportErrorAndExit(reason);\n });\n};\n"],"names":["dayjs","execa","ms","path","Watcher","which","config","Changes","YarnNotFoundError","FileSync","FileSyncArgs","notify","reportErrorAndExit","sprint","getUserOrLogin","debounce","isAbortError","usage","args","Boolean","type","Number","default","command","ctx","filesync","init","user","sync","log","println","nothrow","recentWritesToLocalFilesystem","Map","clearRecentWritesInterval","setInterval","timestamp","isAfter","delete","unref","unsubscribeFromGadgetChanges","subscribeToGadgetChanges","onError","error","abort","beforeChanges","changed","deleted","filepath","set","Date","now","dir","dirname","afterChanges","changes","has","cwd","directory","catch","localChangesBuffer","sendChangesToGadget","entries","clear","debug","fileWatcher","ignoreInitial","ignore","relative","startsWith","ignores","renameDetection","recursive","pollingInterval","pollingTimeout","renameTimeout","event","absolutePath","renamedPath","isDirectory","normalizedPath","normalize","trace","absolute","loadIgnoreFile","oldNormalizedPath","oldPath","once","printlns","version","app","slug","hasSplitEnvironments","primaryDomain","onAbort","reason","info","close","clearInterval","flush","idle","subtitle","message"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/commands/sync.ts"],"sourcesContent":["import dayjs from \"dayjs\";\nimport ms from \"ms\";\nimport path from \"node:path\";\nimport Watcher from \"watcher\";\nimport which from \"which\";\nimport type { ArgsDefinition } from \"../services/command/arg.js\";\nimport type { Command, Usage } from \"../services/command/command.js\";\nimport { config } from \"../services/config/config.js\";\nimport { Changes } from \"../services/filesync/changes.js\";\nimport { YarnNotFoundError } from \"../services/filesync/error.js\";\nimport { FileSync, FileSyncArgs } from \"../services/filesync/filesync.js\";\nimport { notify } from \"../services/output/notify.js\";\nimport { reportErrorAndExit } from \"../services/output/report.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { debounce } from \"../services/util/function.js\";\nimport { isAbortError } from \"../services/util/is.js\";\n\nexport const usage: Usage = () => sprint`\n Sync your Gadget environment's source code with your local filesystem.\n\n {bold USAGE}\n ggt sync [DIRECTORY]\n\n {bold ARGUMENTS}\n DIRECTORY The directory to sync files to (default: \".\")\n\n {bold FLAGS}\n -a, --app=<name> The Gadget application to sync files to\n --prefer=<filesystem> Prefer \"local\" or \"gadget\" conflicting changes\n --once Sync once and exit\n --force Sync regardless of local filesystem state\n\n {bold DESCRIPTION}\n Sync allows you to synchronize your Gadget application's source\n code with your local filesystem.\n\n While ggt sync is running, local file changes are immediately\n reflected within Gadget, while files that are changed in Gadget are\n immediately saved to your local filesystem.\n\n Ideal for:\n • Local development with editors like VSCode\n • Storing source code in a Git repository like GitHub\n\n Sync looks for a \".ignore\" file to exclude certain files/directories\n from being synced. The format is identical to Git's.\n\n These files are always ignored:\n • .DS_Store\n • .gadget\n • .git\n • node_modules\n\n Note:\n • Sync only works with your development environment\n • Avoid deleting/moving all your files while sync is running\n • Gadget only supports Yarn v1 for dependency installation\n\n {bold EXAMPLE}\n $ ggt sync ~/gadget/example --app example\n\n App example\n Editor https://example.gadget.app/edit\n Playground https://example.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/example\n\n Endpoints\n • https://example.gadget.app\n • https://example--development.gadget.app\n\n Watching for file changes... {gray Press Ctrl+C to stop}\n\n → Sent {gray 09:06:25 AM}\n {greenBright routes/GET-hello.js + created}\n\n → Sent {gray 09:06:49 AM}\n {blueBright routes/GET-hello.js ± updated}\n\n ← Received {gray 09:06:54 AM}\n {blueBright routes/GET-hello.js ± updated}\n\n ← Received {gray 09:06:56 AM}\n {redBright routes/GET-hello.js - deleted}\n ^C Stopping... {gray press Ctrl+C again to force}\n\n Goodbye!\n`;\n\nexport const args = {\n ...FileSyncArgs,\n \"--once\": Boolean,\n \"--file-push-delay\": { type: Number, default: ms(\"100ms\") },\n \"--file-watch-debounce\": { type: Number, default: ms(\"300ms\") },\n \"--file-watch-poll-interval\": { type: Number, default: ms(\"3s\") },\n \"--file-watch-poll-timeout\": { type: Number, default: ms(\"20s\") },\n \"--file-watch-rename-timeout\": { type: Number, default: ms(\"1.25s\") },\n} satisfies ArgsDefinition;\n\nexport type SyncArgs = typeof args;\n\n/**\n * Runs the sync process until it is stopped or an error occurs.\n */\nexport const command: Command<SyncArgs> = async (ctx) => {\n if (!which.sync(\"yarn\", { nothrow: true })) {\n throw new YarnNotFoundError();\n }\n\n const filesync = await FileSync.init(ctx);\n await filesync.sync();\n\n if (ctx.args[\"--once\"]) {\n ctx.log.println(\"Done!\");\n return;\n }\n\n /**\n * A list of filepaths that have changed because we (this ggt process)\n * modified them. This is used to avoid reacting to filesystem events\n * that we caused, which would cause an infinite loop.\n */\n const recentWritesToLocalFilesystem = new Map<string, number>();\n\n const clearRecentWritesInterval = setInterval(() => {\n for (const [path, timestamp] of recentWritesToLocalFilesystem) {\n if (dayjs().isAfter(timestamp + ms(\"5s\"))) {\n // this change should have been seen by now\n recentWritesToLocalFilesystem.delete(path);\n }\n }\n }, ms(\"1s\")).unref();\n\n /**\n * Subscribe to file changes on Gadget and apply them to the local\n * filesystem.\n */\n const unsubscribeFromGadgetChanges = filesync.subscribeToGadgetChanges({\n onError: (error) => ctx.abort(error),\n beforeChanges: ({ changed, deleted }) => {\n // add all the files and directories we're about to touch to\n // recentWritesToLocalFilesystem so that we don't send them back\n // to Gadget\n for (const filepath of [...changed, ...deleted]) {\n recentWritesToLocalFilesystem.set(filepath, Date.now());\n\n let dir = path.dirname(filepath);\n while (dir !== \".\") {\n recentWritesToLocalFilesystem.set(dir + \"/\", Date.now());\n dir = path.dirname(dir);\n }\n }\n },\n });\n\n /**\n * A buffer of local file changes to send to Gadget.\n */\n const localChangesBuffer = new Changes();\n\n /**\n * A debounced function that sends the local file changes to Gadget.\n */\n const sendChangesToGadget = debounce(ctx.args[\"--file-push-delay\"], () => {\n const changes = new Changes(localChangesBuffer.entries());\n localChangesBuffer.clear();\n filesync.sendChangesToGadget({ changes }).catch((error) => ctx.abort(error));\n });\n\n ctx.log.debug(\"watching\", { path: filesync.directory.path });\n\n /**\n * Watches the local filesystem for changes.\n */\n const fileWatcher = new Watcher(\n filesync.directory.path,\n {\n // don't emit an event for every watched file on boot\n ignoreInitial: true,\n // don't emit changes to .gadget/ files because they're readonly (Gadget manages them)\n ignore: (path: string) => filesync.directory.relative(path).startsWith(\".gadget\") || filesync.directory.ignores(path),\n renameDetection: true,\n recursive: true,\n debounce: ctx.args[\"--file-watch-debounce\"],\n pollingInterval: ctx.args[\"--file-watch-poll-interval\"],\n pollingTimeout: ctx.args[\"--file-watch-poll-timeout\"],\n renameTimeout: ctx.args[\"--file-watch-rename-timeout\"],\n },\n (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 = filesync.directory.normalize(filepath, isDirectory);\n\n ctx.log.trace(\"file event\", { event, isDirectory, path: normalizedPath });\n\n if (filepath === filesync.directory.absolute(\".ignore\")) {\n filesync.directory.loadIgnoreFile().catch((error) => ctx.abort(error));\n } else if (filesync.directory.ignores(filepath)) {\n return;\n }\n\n if (recentWritesToLocalFilesystem.delete(normalizedPath)) {\n ctx.log.trace(\"ignoring event because we caused it\", { event, path: normalizedPath });\n return;\n }\n\n switch (event) {\n case \"add\":\n case \"addDir\":\n localChangesBuffer.set(normalizedPath, { type: \"create\" });\n break;\n case \"rename\":\n case \"renameDir\": {\n const oldNormalizedPath = filesync.directory.normalize(absolutePath, isDirectory);\n localChangesBuffer.set(normalizedPath, { type: \"create\", oldPath: oldNormalizedPath });\n break;\n }\n case \"change\": {\n localChangesBuffer.set(normalizedPath, { type: \"update\" });\n break;\n }\n case \"unlink\":\n case \"unlinkDir\": {\n localChangesBuffer.set(normalizedPath, { type: \"delete\" });\n break;\n }\n }\n\n sendChangesToGadget();\n },\n ).once(\"error\", (error) => ctx.abort(error));\n\n ctx.log.printlns`\n ggt v${config.version}\n\n App ${filesync.app.slug}\n Editor https://${filesync.app.primaryDomain}/edit\n Playground https://${filesync.app.primaryDomain}/api/graphql/playground\n Docs https://docs.gadget.dev/api/${filesync.app.slug}\n\n Endpoints ${\n filesync.app.hasSplitEnvironments\n ? `\n • https://${filesync.app.primaryDomain}\n • https://${filesync.app.slug}--development.gadget.app`\n : `\n • https://${filesync.app.primaryDomain}`\n }\n\n Watching for file changes... {gray Press Ctrl+C to stop}\n `;\n\n ctx.onAbort(async (reason) => {\n ctx.log.info(\"stopping\", { reason });\n\n unsubscribeFromGadgetChanges();\n fileWatcher.close();\n clearInterval(clearRecentWritesInterval);\n sendChangesToGadget.flush();\n\n try {\n await filesync.idle();\n } catch (error) {\n ctx.log.error(\"error while waiting for idle\", { error });\n }\n\n if (isAbortError(reason)) {\n ctx.log.printlns(\"Goodbye!\");\n return;\n }\n\n notify(ctx, { subtitle: \"Uh oh!\", message: \"An error occurred while syncing files\" });\n await reportErrorAndExit(ctx, reason);\n });\n};\n"],"names":["dayjs","ms","path","Watcher","which","config","Changes","YarnNotFoundError","FileSync","FileSyncArgs","notify","reportErrorAndExit","sprint","debounce","isAbortError","usage","args","Boolean","type","Number","default","command","ctx","sync","nothrow","filesync","init","log","println","recentWritesToLocalFilesystem","Map","clearRecentWritesInterval","setInterval","timestamp","isAfter","delete","unref","unsubscribeFromGadgetChanges","subscribeToGadgetChanges","onError","error","abort","beforeChanges","changed","deleted","filepath","set","Date","now","dir","dirname","localChangesBuffer","sendChangesToGadget","changes","entries","clear","catch","debug","directory","fileWatcher","ignoreInitial","ignore","relative","startsWith","ignores","renameDetection","recursive","pollingInterval","pollingTimeout","renameTimeout","event","absolutePath","renamedPath","isDirectory","normalizedPath","normalize","trace","absolute","loadIgnoreFile","oldNormalizedPath","oldPath","once","printlns","version","app","slug","primaryDomain","hasSplitEnvironments","onAbort","reason","info","close","clearInterval","flush","idle","subtitle","message"],"mappings":"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"}
@@ -1,9 +1,5 @@
1
1
  import { config } from "../services/config/config.js";
2
- import { createLogger } from "../services/output/log/logger.js";
3
2
  import { sprint } from "../services/output/sprint.js";
4
- const log = createLogger({
5
- name: "version"
6
- });
7
3
  export const usage = ()=>sprint`
8
4
  Print the version of ggt
9
5
 
@@ -14,8 +10,8 @@ export const usage = ()=>sprint`
14
10
  $ ggt version
15
11
  ${config.version}
16
12
  `;
17
- export const command = ()=>{
18
- log.println(config.version);
13
+ export const command = (ctx)=>{
14
+ ctx.log.println(config.version);
19
15
  };
20
16
 
21
17
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/version.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { config } from \"../services/config/config.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\n\nconst log = createLogger({ name: \"version\" });\n\nexport const usage: Usage = () => sprint`\n Print the version of ggt\n\n {bold USAGE}\n ggt version\n\n {bold EXAMPLE}\n $ ggt version\n ${config.version}\n`;\n\nexport const command: Command = () => {\n log.println(config.version);\n};\n"],"names":["config","createLogger","sprint","log","name","usage","version","command","println"],"mappings":"AACA,SAASA,MAAM,QAAQ,+BAA+B;AACtD,SAASC,YAAY,QAAQ,mCAAmC;AAChE,SAASC,MAAM,QAAQ,+BAA+B;AAEtD,MAAMC,MAAMF,aAAa;IAAEG,MAAM;AAAU;AAE3C,OAAO,MAAMC,QAAe,IAAMH,MAAM,CAAC;;;;;;;;MAQnC,EAAEF,OAAOM,OAAO,CAAC;AACvB,CAAC,CAAC;AAEF,OAAO,MAAMC,UAAmB;IAC9BJ,IAAIK,OAAO,CAACR,OAAOM,OAAO;AAC5B,EAAE"}
1
+ {"version":3,"sources":["../../src/commands/version.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { config } from \"../services/config/config.js\";\nimport { sprint } from \"../services/output/sprint.js\";\n\nexport const usage: Usage = () => sprint`\n Print the version of ggt\n\n {bold USAGE}\n ggt version\n\n {bold EXAMPLE}\n $ ggt version\n ${config.version}\n`;\n\nexport const command: Command = (ctx) => {\n ctx.log.println(config.version);\n};\n"],"names":["config","sprint","usage","version","command","ctx","log","println"],"mappings":"AACA,SAASA,MAAM,QAAQ,+BAA+B;AACtD,SAASC,MAAM,QAAQ,+BAA+B;AAEtD,OAAO,MAAMC,QAAe,IAAMD,MAAM,CAAC;;;;;;;;MAQnC,EAAED,OAAOG,OAAO,CAAC;AACvB,CAAC,CAAC;AAEF,OAAO,MAAMC,UAAmB,CAACC;IAC/BA,IAAIC,GAAG,CAACC,OAAO,CAACP,OAAOG,OAAO;AAChC,EAAE"}
@@ -1,9 +1,5 @@
1
- import { createLogger } from "../services/output/log/logger.js";
2
1
  import { sprint } from "../services/output/sprint.js";
3
2
  import { getUser } from "../services/user/user.js";
4
- const log = createLogger({
5
- name: "whoami"
6
- });
7
3
  export const usage = ()=>sprint`
8
4
  Show the name and email address of the currently logged in user
9
5
 
@@ -14,16 +10,16 @@ export const usage = ()=>sprint`
14
10
  $ ggt whoami
15
11
  You are logged in as Jane Doe (jane@example.com)
16
12
  `;
17
- export const command = async ()=>{
18
- const user = await getUser();
13
+ export const command = async (ctx)=>{
14
+ const user = await getUser(ctx);
19
15
  if (!user) {
20
- log.println`You are not logged in`;
16
+ ctx.log.println`You are not logged in`;
21
17
  return;
22
18
  }
23
19
  if (user.name) {
24
- log.println`You are logged in as ${user.name} {gray (${user.email})}`;
20
+ ctx.log.println`You are logged in as ${user.name} {gray (${user.email})}`;
25
21
  } else {
26
- log.println`You are logged in as ${user.email}`;
22
+ ctx.log.println`You are logged in as ${user.email}`;
27
23
  }
28
24
  };
29
25
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/whoami.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { getUser } from \"../services/user/user.js\";\n\nconst log = createLogger({ name: \"whoami\" });\n\nexport const usage: Usage = () => sprint`\n Show the name and email address of the currently logged in user\n\n {bold USAGE}\n ggt whoami\n\n {bold EXAMPLE}\n $ ggt whoami\n You are logged in as Jane Doe (jane@example.com)\n`;\n\nexport const command: Command = async () => {\n const user = await getUser();\n if (!user) {\n log.println`You are not logged in`;\n return;\n }\n\n if (user.name) {\n log.println`You are logged in as ${user.name} {gray (${user.email})}`;\n } else {\n log.println`You are logged in as ${user.email}`;\n }\n};\n"],"names":["createLogger","sprint","getUser","log","name","usage","command","user","println","email"],"mappings":"AACA,SAASA,YAAY,QAAQ,mCAAmC;AAChE,SAASC,MAAM,QAAQ,+BAA+B;AACtD,SAASC,OAAO,QAAQ,2BAA2B;AAEnD,MAAMC,MAAMH,aAAa;IAAEI,MAAM;AAAS;AAE1C,OAAO,MAAMC,QAAe,IAAMJ,MAAM,CAAC;;;;;;;;;AASzC,CAAC,CAAC;AAEF,OAAO,MAAMK,UAAmB;IAC9B,MAAMC,OAAO,MAAML;IACnB,IAAI,CAACK,MAAM;QACTJ,IAAIK,OAAO,CAAC,qBAAqB,CAAC;QAClC;IACF;IAEA,IAAID,KAAKH,IAAI,EAAE;QACbD,IAAIK,OAAO,CAAC,qBAAqB,EAAED,KAAKH,IAAI,CAAC,QAAQ,EAAEG,KAAKE,KAAK,CAAC,EAAE,CAAC;IACvE,OAAO;QACLN,IAAIK,OAAO,CAAC,qBAAqB,EAAED,KAAKE,KAAK,CAAC,CAAC;IACjD;AACF,EAAE"}
1
+ {"version":3,"sources":["../../src/commands/whoami.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { getUser } from \"../services/user/user.js\";\n\nexport const usage: Usage = () => sprint`\n Show the name and email address of the currently logged in user\n\n {bold USAGE}\n ggt whoami\n\n {bold EXAMPLE}\n $ ggt whoami\n You are logged in as Jane Doe (jane@example.com)\n`;\n\nexport const command: Command = async (ctx) => {\n const user = await getUser(ctx);\n if (!user) {\n ctx.log.println`You are not logged in`;\n return;\n }\n\n if (user.name) {\n ctx.log.println`You are logged in as ${user.name} {gray (${user.email})}`;\n } else {\n ctx.log.println`You are logged in as ${user.email}`;\n }\n};\n"],"names":["sprint","getUser","usage","command","ctx","user","log","println","name","email"],"mappings":"AACA,SAASA,MAAM,QAAQ,+BAA+B;AACtD,SAASC,OAAO,QAAQ,2BAA2B;AAEnD,OAAO,MAAMC,QAAe,IAAMF,MAAM,CAAC;;;;;;;;;AASzC,CAAC,CAAC;AAEF,OAAO,MAAMG,UAAmB,OAAOC;IACrC,MAAMC,OAAO,MAAMJ,QAAQG;IAC3B,IAAI,CAACC,MAAM;QACTD,IAAIE,GAAG,CAACC,OAAO,CAAC,qBAAqB,CAAC;QACtC;IACF;IAEA,IAAIF,KAAKG,IAAI,EAAE;QACbJ,IAAIE,GAAG,CAACC,OAAO,CAAC,qBAAqB,EAAEF,KAAKG,IAAI,CAAC,QAAQ,EAAEH,KAAKI,KAAK,CAAC,EAAE,CAAC;IAC3E,OAAO;QACLL,IAAIE,GAAG,CAACC,OAAO,CAAC,qBAAqB,EAAEF,KAAKI,KAAK,CAAC,CAAC;IACrD;AACF,EAAE"}
package/lib/ggt.js ADDED
@@ -0,0 +1,40 @@
1
+ import ms from "ms";
2
+ import * as root from "./commands/root.js";
3
+ import { Context } from "./services/command/context.js";
4
+ import { installErrorHandlers, reportErrorAndExit } from "./services/output/report.js";
5
+ import { installJsonExtensions } from "./services/util/json.js";
6
+ export const ggt = async (ctx = Context.init({
7
+ name: "ggt"
8
+ }))=>{
9
+ installJsonExtensions();
10
+ installErrorHandlers(ctx);
11
+ try {
12
+ for (const signal of [
13
+ "SIGINT",
14
+ "SIGTERM"
15
+ ]){
16
+ process.once(signal, ()=>{
17
+ ctx.log.trace("received signal", {
18
+ signal
19
+ });
20
+ ctx.log.println` Stopping... {gray Press Ctrl+C again to force}`;
21
+ ctx.abort();
22
+ // when ggt is run via npx, and the user presses ctrl+c, npx
23
+ // sends sigint twice in quick succession. in order to prevent
24
+ // the second sigint from triggering the force exit listener,
25
+ // we wait a bit before registering it
26
+ setTimeout(()=>{
27
+ process.once(signal, ()=>{
28
+ ctx.log.println(" Exiting immediately");
29
+ process.exit(1);
30
+ });
31
+ }, ms("100ms")).unref();
32
+ });
33
+ }
34
+ await root.command(ctx);
35
+ } catch (error) {
36
+ await reportErrorAndExit(ctx, error);
37
+ }
38
+ };
39
+
40
+ //# sourceMappingURL=ggt.js.map
package/lib/ggt.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ggt.ts"],"sourcesContent":["import ms from \"ms\";\nimport * as root from \"./commands/root.js\";\nimport { Context } from \"./services/command/context.js\";\nimport { installErrorHandlers, reportErrorAndExit } from \"./services/output/report.js\";\nimport { installJsonExtensions } from \"./services/util/json.js\";\n\nexport const ggt = async (ctx = Context.init({ name: \"ggt\" })): Promise<void> => {\n installJsonExtensions();\n installErrorHandlers(ctx);\n\n try {\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.once(signal, () => {\n ctx.log.trace(\"received signal\", { signal });\n ctx.log.println` Stopping... {gray Press Ctrl+C again to force}`;\n ctx.abort();\n\n // when ggt is run via npx, and the user presses ctrl+c, npx\n // sends sigint twice in quick succession. in order to prevent\n // the second sigint from triggering the force exit listener,\n // we wait a bit before registering it\n setTimeout(() => {\n process.once(signal, () => {\n ctx.log.println(\" Exiting immediately\");\n process.exit(1);\n });\n }, ms(\"100ms\")).unref();\n });\n }\n\n await root.command(ctx);\n } catch (error) {\n await reportErrorAndExit(ctx, error);\n }\n};\n"],"names":["ms","root","Context","installErrorHandlers","reportErrorAndExit","installJsonExtensions","ggt","ctx","init","name","signal","process","once","log","trace","println","abort","setTimeout","exit","unref","command","error"],"mappings":"AAAA,OAAOA,QAAQ,KAAK;AACpB,YAAYC,UAAU,qBAAqB;AAC3C,SAASC,OAAO,QAAQ,gCAAgC;AACxD,SAASC,oBAAoB,EAAEC,kBAAkB,QAAQ,8BAA8B;AACvF,SAASC,qBAAqB,QAAQ,0BAA0B;AAEhE,OAAO,MAAMC,MAAM,OAAOC,MAAML,QAAQM,IAAI,CAAC;IAAEC,MAAM;AAAM,EAAE;IAC3DJ;IACAF,qBAAqBI;IAErB,IAAI;QACF,KAAK,MAAMG,UAAU;YAAC;YAAU;SAAU,CAAW;YACnDC,QAAQC,IAAI,CAACF,QAAQ;gBACnBH,IAAIM,GAAG,CAACC,KAAK,CAAC,mBAAmB;oBAAEJ;gBAAO;gBAC1CH,IAAIM,GAAG,CAACE,OAAO,CAAC,+CAA+C,CAAC;gBAChER,IAAIS,KAAK;gBAET,4DAA4D;gBAC5D,8DAA8D;gBAC9D,6DAA6D;gBAC7D,sCAAsC;gBACtCC,WAAW;oBACTN,QAAQC,IAAI,CAACF,QAAQ;wBACnBH,IAAIM,GAAG,CAACE,OAAO,CAAC;wBAChBJ,QAAQO,IAAI,CAAC;oBACf;gBACF,GAAGlB,GAAG,UAAUmB,KAAK;YACvB;QACF;QAEA,MAAMlB,KAAKmB,OAAO,CAACb;IACrB,EAAE,OAAOc,OAAO;QACd,MAAMjB,mBAAmBG,KAAKc;IAChC;AACF,EAAE"}
@@ -1,3 +1,4 @@
1
+ import assert from "node:assert";
1
2
  import { z } from "zod";
2
3
  import { config } from "../config/config.js";
3
4
  import { loadCookie } from "../http/auth.js";
@@ -16,14 +17,18 @@ export const App = z.object({
16
17
  * Retrieves a list of apps for the given user. If the user is not
17
18
  * logged in, an empty array is returned instead.
18
19
  *
19
- * @param user The user for whom to retrieve the apps.
20
+ * @param ctx - The current context.
20
21
  * @returns A promise that resolves to an array of App objects.
21
- */ export const getApps = async (user)=>{
22
+ */ export const getApps = async (ctx)=>{
22
23
  const cookie = loadCookie();
23
24
  if (!cookie) {
24
25
  return [];
25
26
  }
27
+ assert(ctx.user, "must get user before getting apps");
26
28
  const json = await http({
29
+ context: {
30
+ ctx
31
+ },
27
32
  url: `https://${config.domains.services}/auth/api/apps`,
28
33
  headers: {
29
34
  cookie
@@ -31,10 +36,7 @@ export const App = z.object({
31
36
  responseType: "json",
32
37
  resolveBodyOnly: true
33
38
  });
34
- return z.array(App).parse(json).map((app)=>({
35
- ...app,
36
- user
37
- }));
39
+ return z.array(App).parse(json);
38
40
  };
39
41
 
40
42
  //# sourceMappingURL=app.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/services/app/app.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { config } from \"../config/config.js\";\nimport { loadCookie } from \"../http/auth.js\";\nimport { http } from \"../http/http.js\";\nimport type { User } from \"../user/user.js\";\n\nexport const App = z.object({\n id: z.union([z.string(), z.number(), z.bigint()]),\n slug: z.string(),\n primaryDomain: z.string(),\n hasSplitEnvironments: z.boolean(),\n});\n\nexport type App = z.infer<typeof App> & { user: User };\n\n/**\n * Retrieves a list of apps for the given user. If the user is not\n * logged in, an empty array is returned instead.\n *\n * @param user The user for whom to retrieve the apps.\n * @returns A promise that resolves to an array of App objects.\n */\nexport const getApps = async (user: User): Promise<App[]> => {\n const cookie = loadCookie();\n if (!cookie) {\n return [];\n }\n\n const json = await http({\n url: `https://${config.domains.services}/auth/api/apps`,\n headers: { cookie },\n responseType: \"json\",\n resolveBodyOnly: true,\n });\n\n return z\n .array(App)\n .parse(json)\n .map((app) => ({ ...app, user }));\n};\n"],"names":["z","config","loadCookie","http","App","object","id","union","string","number","bigint","slug","primaryDomain","hasSplitEnvironments","boolean","getApps","user","cookie","json","url","domains","services","headers","responseType","resolveBodyOnly","array","parse","map","app"],"mappings":"AAAA,SAASA,CAAC,QAAQ,MAAM;AACxB,SAASC,MAAM,QAAQ,sBAAsB;AAC7C,SAASC,UAAU,QAAQ,kBAAkB;AAC7C,SAASC,IAAI,QAAQ,kBAAkB;AAGvC,OAAO,MAAMC,MAAMJ,EAAEK,MAAM,CAAC;IAC1BC,IAAIN,EAAEO,KAAK,CAAC;QAACP,EAAEQ,MAAM;QAAIR,EAAES,MAAM;QAAIT,EAAEU,MAAM;KAAG;IAChDC,MAAMX,EAAEQ,MAAM;IACdI,eAAeZ,EAAEQ,MAAM;IACvBK,sBAAsBb,EAAEc,OAAO;AACjC,GAAG;AAIH;;;;;;CAMC,GACD,OAAO,MAAMC,UAAU,OAAOC;IAC5B,MAAMC,SAASf;IACf,IAAI,CAACe,QAAQ;QACX,OAAO,EAAE;IACX;IAEA,MAAMC,OAAO,MAAMf,KAAK;QACtBgB,KAAK,CAAC,QAAQ,EAAElB,OAAOmB,OAAO,CAACC,QAAQ,CAAC,cAAc,CAAC;QACvDC,SAAS;YAAEL;QAAO;QAClBM,cAAc;QACdC,iBAAiB;IACnB;IAEA,OAAOxB,EACJyB,KAAK,CAACrB,KACNsB,KAAK,CAACR,MACNS,GAAG,CAAC,CAACC,MAAS,CAAA;YAAE,GAAGA,GAAG;YAAEZ;QAAK,CAAA;AAClC,EAAE"}
1
+ {"version":3,"sources":["../../../src/services/app/app.ts"],"sourcesContent":["import assert from \"node:assert\";\nimport { z } from \"zod\";\nimport type { Context } from \"../command/context.js\";\nimport { config } from \"../config/config.js\";\nimport { loadCookie } from \"../http/auth.js\";\nimport { http } from \"../http/http.js\";\n\nexport const App = z.object({\n id: z.union([z.string(), z.number(), z.bigint()]),\n slug: z.string(),\n primaryDomain: z.string(),\n hasSplitEnvironments: z.boolean(),\n});\n\nexport type App = z.infer<typeof App>;\n\n/**\n * Retrieves a list of apps for the given user. If the user is not\n * logged in, an empty array is returned instead.\n *\n * @param ctx - The current context.\n * @returns A promise that resolves to an array of App objects.\n */\nexport const getApps = async (ctx: Context): Promise<App[]> => {\n const cookie = loadCookie();\n if (!cookie) {\n return [];\n }\n\n assert(ctx.user, \"must get user before getting apps\");\n\n const json = await http({\n context: { ctx },\n url: `https://${config.domains.services}/auth/api/apps`,\n headers: { cookie },\n responseType: \"json\",\n resolveBodyOnly: true,\n });\n\n return z.array(App).parse(json);\n};\n"],"names":["assert","z","config","loadCookie","http","App","object","id","union","string","number","bigint","slug","primaryDomain","hasSplitEnvironments","boolean","getApps","ctx","cookie","user","json","context","url","domains","services","headers","responseType","resolveBodyOnly","array","parse"],"mappings":"AAAA,OAAOA,YAAY,cAAc;AACjC,SAASC,CAAC,QAAQ,MAAM;AAExB,SAASC,MAAM,QAAQ,sBAAsB;AAC7C,SAASC,UAAU,QAAQ,kBAAkB;AAC7C,SAASC,IAAI,QAAQ,kBAAkB;AAEvC,OAAO,MAAMC,MAAMJ,EAAEK,MAAM,CAAC;IAC1BC,IAAIN,EAAEO,KAAK,CAAC;QAACP,EAAEQ,MAAM;QAAIR,EAAES,MAAM;QAAIT,EAAEU,MAAM;KAAG;IAChDC,MAAMX,EAAEQ,MAAM;IACdI,eAAeZ,EAAEQ,MAAM;IACvBK,sBAAsBb,EAAEc,OAAO;AACjC,GAAG;AAIH;;;;;;CAMC,GACD,OAAO,MAAMC,UAAU,OAAOC;IAC5B,MAAMC,SAASf;IACf,IAAI,CAACe,QAAQ;QACX,OAAO,EAAE;IACX;IAEAlB,OAAOiB,IAAIE,IAAI,EAAE;IAEjB,MAAMC,OAAO,MAAMhB,KAAK;QACtBiB,SAAS;YAAEJ;QAAI;QACfK,KAAK,CAAC,QAAQ,EAAEpB,OAAOqB,OAAO,CAACC,QAAQ,CAAC,cAAc,CAAC;QACvDC,SAAS;YAAEP;QAAO;QAClBQ,cAAc;QACdC,iBAAiB;IACnB;IAEA,OAAO1B,EAAE2B,KAAK,CAACvB,KAAKwB,KAAK,CAACT;AAC5B,EAAE"}
@@ -0,0 +1,176 @@
1
+ import { _ as _define_property } from "@swc/helpers/_/_define_property";
2
+ import { createClient } from "graphql-ws";
3
+ import assert from "node:assert";
4
+ import PQueue from "p-queue";
5
+ import WebSocket from "ws";
6
+ import { config } from "../../config/config.js";
7
+ import { loadCookie } from "../../http/auth.js";
8
+ import { http } from "../../http/http.js";
9
+ import { noop, unthunk } from "../../util/function.js";
10
+ import { isObject } from "../../util/is.js";
11
+ import { EditError } from "./error.js";
12
+ var ConnectionStatus;
13
+ (function(ConnectionStatus) {
14
+ ConnectionStatus[ConnectionStatus["CONNECTED"] = 0] = "CONNECTED";
15
+ ConnectionStatus[ConnectionStatus["DISCONNECTED"] = 1] = "DISCONNECTED";
16
+ ConnectionStatus[ConnectionStatus["RECONNECTING"] = 2] = "RECONNECTING";
17
+ })(ConnectionStatus || (ConnectionStatus = {}));
18
+ /**
19
+ * Client is a GraphQL client connected to a Gadget application's
20
+ * /edit/api/graphql endpoint.
21
+ */ export class Client {
22
+ /**
23
+ * Subscribe to a GraphQL subscription.
24
+ */ subscribe(ctx, { subscription, variables, onResponse, onError: optionsOnError, onComplete = noop }) {
25
+ let request = {
26
+ query: subscription,
27
+ variables: unthunk(variables)
28
+ };
29
+ const removeConnectedListener = this._graphqlWsClient.on("connected", ()=>{
30
+ if (this.status === 2) {
31
+ request = {
32
+ query: subscription,
33
+ variables: unthunk(variables)
34
+ };
35
+ ctx.log.info("re-subscribing to graphql subscription");
36
+ }
37
+ });
38
+ const queue = new PQueue({
39
+ concurrency: 1
40
+ });
41
+ const onError = (error)=>optionsOnError(new EditError(subscription, error));
42
+ const unsubscribe = this._graphqlWsClient.subscribe(request, {
43
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
44
+ next: (response)=>queue.add(()=>onResponse(response)).catch(onError),
45
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
46
+ error: (error)=>queue.add(()=>onError(error)),
47
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
48
+ complete: ()=>queue.add(()=>onComplete()).catch(onError)
49
+ });
50
+ return ()=>{
51
+ removeConnectedListener();
52
+ unsubscribe();
53
+ };
54
+ }
55
+ /**
56
+ * Execute a GraphQL query or mutation.
57
+ */ async execute(ctx, request) {
58
+ assert(ctx.app, "missing app when executing GraphQL query");
59
+ const cookie = loadCookie();
60
+ assert(cookie, "missing cookie when executing GraphQL request");
61
+ let subdomain = ctx.app.slug;
62
+ if (ctx.app.hasSplitEnvironments) {
63
+ subdomain += "--development";
64
+ }
65
+ try {
66
+ const json = await http({
67
+ context: {
68
+ ctx
69
+ },
70
+ method: "POST",
71
+ url: `https://${subdomain}.${config.domains.app}/edit/api/graphql`,
72
+ headers: {
73
+ cookie
74
+ },
75
+ json: {
76
+ query: request.operation,
77
+ variables: unthunk(request.variables)
78
+ },
79
+ responseType: "json",
80
+ resolveBodyOnly: true,
81
+ throwHttpErrors: false,
82
+ ...request.http
83
+ });
84
+ if (!isObject(json) || !("data" in json) && !("errors" in json)) {
85
+ ctx.log.error("received invalid graphql response", {
86
+ error: json
87
+ });
88
+ throw json;
89
+ }
90
+ return json;
91
+ } catch (error) {
92
+ throw new EditError(request.operation, error);
93
+ }
94
+ }
95
+ /**
96
+ * Close the connection to the server.
97
+ */ async dispose() {
98
+ await this._graphqlWsClient.dispose();
99
+ }
100
+ constructor(ctx){
101
+ // assume the client is going to connect
102
+ _define_property(this, "status", 0);
103
+ _define_property(this, "ctx", void 0);
104
+ _define_property(this, "_graphqlWsClient", void 0);
105
+ this.ctx = ctx.child({
106
+ name: "client"
107
+ });
108
+ assert(ctx.app, "missing app when creating edit client");
109
+ let subdomain = ctx.app.slug;
110
+ if (ctx.app.hasSplitEnvironments) {
111
+ subdomain += "--development";
112
+ }
113
+ this._graphqlWsClient = createClient({
114
+ url: `wss://${subdomain}.${config.domains.app}/edit/api/graphql-ws`,
115
+ shouldRetry: ()=>true,
116
+ webSocketImpl: class extends WebSocket {
117
+ constructor(address, protocols, wsOptions){
118
+ // this cookie should be available since we were given an app which requires a cookie to load
119
+ const cookie = loadCookie();
120
+ assert(cookie, "missing cookie when connecting to GraphQL API");
121
+ super(address, protocols, {
122
+ signal: ctx.signal,
123
+ ...wsOptions,
124
+ headers: {
125
+ ...wsOptions?.headers,
126
+ "user-agent": config.versionFull,
127
+ cookie
128
+ }
129
+ });
130
+ }
131
+ },
132
+ on: {
133
+ connecting: ()=>{
134
+ switch(this.status){
135
+ case 1:
136
+ this.status = 2;
137
+ this.ctx.log.info("reconnecting");
138
+ break;
139
+ case 2:
140
+ this.ctx.log.info("retrying");
141
+ break;
142
+ default:
143
+ this.ctx.log.debug("connecting");
144
+ break;
145
+ }
146
+ },
147
+ connected: ()=>{
148
+ if (this.status === 2) {
149
+ this.ctx.log.info("reconnected");
150
+ } else {
151
+ this.ctx.log.debug("connected");
152
+ }
153
+ // let the other on connected listeners see what status we're in
154
+ setImmediate(()=>this.status = 0);
155
+ },
156
+ closed: ()=>{
157
+ this.status = 1;
158
+ this.ctx.log.debug("disconnected");
159
+ },
160
+ error: (error)=>{
161
+ if (this.status === 2) {
162
+ this.ctx.log.error("failed to reconnect", {
163
+ error
164
+ });
165
+ } else {
166
+ this.ctx.log.error("connection error", {
167
+ error
168
+ });
169
+ }
170
+ }
171
+ }
172
+ });
173
+ }
174
+ }
175
+
176
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/services/app/edit/client.ts"],"sourcesContent":["import type { ExecutionResult } from \"graphql-ws\";\nimport { createClient } from \"graphql-ws\";\nimport assert from \"node:assert\";\nimport type { ClientRequestArgs } from \"node:http\";\nimport PQueue from \"p-queue\";\nimport type { Promisable } from \"type-fest\";\nimport WebSocket from \"ws\";\nimport type { Context } from \"../../command/context.js\";\nimport { config } from \"../../config/config.js\";\nimport { loadCookie } from \"../../http/auth.js\";\nimport { http, type HttpOptions } from \"../../http/http.js\";\nimport { noop, unthunk, type Thunk } from \"../../util/function.js\";\nimport { isObject } from \"../../util/is.js\";\nimport { EditError } from \"./error.js\";\nimport type { GraphQLMutation, GraphQLQuery, GraphQLSubscription } from \"./operation.js\";\n\nenum ConnectionStatus {\n CONNECTED,\n DISCONNECTED,\n RECONNECTING,\n}\n\n/**\n * Client is a GraphQL client connected to a Gadget application's\n * /edit/api/graphql endpoint.\n */\nexport class Client {\n // assume the client is going to connect\n status = ConnectionStatus.CONNECTED;\n\n readonly ctx: Context;\n\n private _graphqlWsClient: ReturnType<typeof createClient>;\n\n constructor(ctx: Context) {\n this.ctx = ctx.child({ name: \"client\" });\n assert(ctx.app, \"missing app when creating edit client\");\n\n let subdomain = ctx.app.slug;\n if (ctx.app.hasSplitEnvironments) {\n subdomain += \"--development\";\n }\n\n this._graphqlWsClient = createClient({\n url: `wss://${subdomain}.${config.domains.app}/edit/api/graphql-ws`,\n shouldRetry: () => 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 signal: ctx.signal,\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 this.ctx.log.info(\"reconnecting\");\n break;\n case ConnectionStatus.RECONNECTING:\n this.ctx.log.info(\"retrying\");\n break;\n default:\n this.ctx.log.debug(\"connecting\");\n break;\n }\n },\n connected: () => {\n if (this.status === ConnectionStatus.RECONNECTING) {\n this.ctx.log.info(\"reconnected\");\n } else {\n this.ctx.log.debug(\"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: () => {\n this.status = ConnectionStatus.DISCONNECTED;\n this.ctx.log.debug(\"disconnected\");\n },\n error: (error) => {\n if (this.status === ConnectionStatus.RECONNECTING) {\n this.ctx.log.error(\"failed to reconnect\", { error });\n } else {\n this.ctx.log.error(\"connection error\", { error });\n }\n },\n },\n });\n }\n\n /**\n * Subscribe to a GraphQL subscription.\n */\n subscribe<Subscription extends GraphQLSubscription>(\n ctx: Context,\n {\n subscription,\n variables,\n onResponse,\n onError: optionsOnError,\n onComplete = noop,\n }: {\n subscription: Subscription;\n variables?: Thunk<Subscription[\"Variables\"]> | null;\n onResponse: (response: ExecutionResult<Subscription[\"Data\"], Subscription[\"Extensions\"]>) => Promisable<void>;\n onError: (error: EditError) => Promisable<void>;\n onComplete?: () => Promisable<void>;\n },\n ): () => void {\n let request = { query: subscription, variables: unthunk(variables) };\n\n const removeConnectedListener = this._graphqlWsClient.on(\"connected\", () => {\n if (this.status === ConnectionStatus.RECONNECTING) {\n request = { query: subscription, variables: unthunk(variables) };\n ctx.log.info(\"re-subscribing to graphql subscription\");\n }\n });\n\n const queue = new PQueue({ concurrency: 1 });\n const onError = (error: unknown): Promisable<void> => optionsOnError(new EditError(subscription, error));\n\n const unsubscribe = this._graphqlWsClient.subscribe<Subscription[\"Data\"], Subscription[\"Extensions\"]>(request, {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n next: (response) => queue.add(() => onResponse(response)).catch(onError),\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n error: (error) => queue.add(() => onError(error)),\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n complete: () => queue.add(() => onComplete()).catch(onError),\n });\n\n return () => {\n removeConnectedListener();\n unsubscribe();\n };\n }\n\n /**\n * Execute a GraphQL query or mutation.\n */\n async execute<Operation extends GraphQLQuery | GraphQLMutation>(\n ctx: Context,\n request: {\n operation: Operation;\n variables?: Thunk<Operation[\"Variables\"]> | null;\n http?: HttpOptions;\n },\n ): Promise<ExecutionResult<Operation[\"Data\"], Operation[\"Extensions\"]>> {\n assert(ctx.app, \"missing app when executing GraphQL query\");\n\n const cookie = loadCookie();\n assert(cookie, \"missing cookie when executing GraphQL request\");\n\n let subdomain = ctx.app.slug;\n if (ctx.app.hasSplitEnvironments) {\n subdomain += \"--development\";\n }\n\n try {\n const json = await http({\n context: { ctx },\n method: \"POST\",\n url: `https://${subdomain}.${config.domains.app}/edit/api/graphql`,\n headers: { cookie },\n json: { query: request.operation, variables: unthunk(request.variables) },\n responseType: \"json\",\n resolveBodyOnly: true,\n throwHttpErrors: false,\n ...request.http,\n });\n\n if (!isObject(json) || (!(\"data\" in json) && !(\"errors\" in json))) {\n ctx.log.error(\"received invalid graphql response\", { error: json });\n throw json;\n }\n\n return json as Operation[\"Response\"];\n } catch (error) {\n throw new EditError(request.operation, error);\n }\n }\n\n /**\n * Close the connection to the server.\n */\n async dispose(): Promise<void> {\n await this._graphqlWsClient.dispose();\n }\n}\n"],"names":["createClient","assert","PQueue","WebSocket","config","loadCookie","http","noop","unthunk","isObject","EditError","ConnectionStatus","Client","subscribe","ctx","subscription","variables","onResponse","onError","optionsOnError","onComplete","request","query","removeConnectedListener","_graphqlWsClient","on","status","log","info","queue","concurrency","error","unsubscribe","next","response","add","catch","complete","execute","app","cookie","subdomain","slug","hasSplitEnvironments","json","context","method","url","domains","headers","operation","responseType","resolveBodyOnly","throwHttpErrors","dispose","constructor","child","name","shouldRetry","webSocketImpl","address","protocols","wsOptions","signal","versionFull","connecting","debug","connected","setImmediate","closed"],"mappings":"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"}
@@ -0,0 +1,155 @@
1
+ import { _ as _class_private_field_get } from "@swc/helpers/_/_class_private_field_get";
2
+ import { _ as _class_private_field_init } from "@swc/helpers/_/_class_private_field_init";
3
+ import { _ as _class_private_field_set } from "@swc/helpers/_/_class_private_field_set";
4
+ import { _ as _define_property } from "@swc/helpers/_/_define_property";
5
+ import { unthunk } from "../../util/function.js";
6
+ import { Client } from "./client.js";
7
+ import { EditError } from "./error.js";
8
+ var _client = /*#__PURE__*/ new WeakMap();
9
+ export class Edit {
10
+ /**
11
+ * Execute a GraphQL query.
12
+ *
13
+ * @param request - The query and variables to send to the server.
14
+ * @param request.query - The GraphQL query to execute.
15
+ * @param request.variables - The variables to send to the server.
16
+ * @param request.http - {@linkcode HttpOptions} to pass to http.
17
+ * @returns The data returned by the server.
18
+ */ async query({ query, variables, ...options }) {
19
+ const name = query.split(/ |\(/, 2)[1];
20
+ const ctx = this.ctx.child({
21
+ fields: {
22
+ edit: {
23
+ query: name
24
+ }
25
+ },
26
+ devFields: {
27
+ edit: {
28
+ query: name,
29
+ variables: unthunk(variables)
30
+ }
31
+ }
32
+ });
33
+ ctx.log.info("executing graphql query");
34
+ const response = await _class_private_field_get(this, _client).execute(ctx, {
35
+ operation: query,
36
+ variables,
37
+ ...options,
38
+ http: {
39
+ retry: {
40
+ // queries _should_ be idempotent, so automatically retry them
41
+ methods: [
42
+ "POST"
43
+ ]
44
+ },
45
+ ...options.http
46
+ }
47
+ });
48
+ if (response.errors) {
49
+ throw new EditError(query, response.errors);
50
+ }
51
+ if (!response.data) {
52
+ throw new EditError(query, "Query response did not contain data");
53
+ }
54
+ return response.data;
55
+ }
56
+ /**
57
+ * Execute a GraphQL mutation.
58
+ *
59
+ * @param request - The query and variables to send to the server.
60
+ * @param request.mutation - The GraphQL query to execute.
61
+ * @param request.variables - The variables to send to the server.
62
+ * @param request.http - {@linkcode HttpOptions} to pass to http.
63
+ * @returns The data returned by the server.
64
+ */ async mutate({ mutation, variables, ...options }) {
65
+ const name = mutation.split(/ |\(/, 2)[1];
66
+ const ctx = this.ctx.child({
67
+ fields: {
68
+ edit: {
69
+ mutation: name
70
+ }
71
+ },
72
+ devFields: {
73
+ edit: {
74
+ mutation: name,
75
+ variables: unthunk(variables)
76
+ }
77
+ }
78
+ });
79
+ ctx.log.info("executing graphql mutation");
80
+ const response = await _class_private_field_get(this, _client).execute(ctx, {
81
+ operation: mutation,
82
+ variables,
83
+ ...options
84
+ });
85
+ if (response.errors) {
86
+ throw new EditError(mutation, response.errors);
87
+ }
88
+ if (!response.data) {
89
+ throw new EditError(mutation, "Mutation response did not contain data");
90
+ }
91
+ return response.data;
92
+ }
93
+ /**
94
+ * Subscribe to a GraphQL subscription.
95
+ *
96
+ * @param options - The query and variables to send to the server.
97
+ * @param options.subscription - The GraphQL subscription to subscribe to.
98
+ * @param options.variables - The variables to send to the server.
99
+ * @param options.onData - A callback that will be called with the data returned by the server.
100
+ * @param options.onError - A callback that will be called with any errors returned by the server.
101
+ * @param options.onComplete - A callback that will be called when the subscription is complete.
102
+ * @returns A function to unsubscribe from the subscription.
103
+ */ subscribe({ onData, ...options }) {
104
+ const name = options.subscription.split(/ |\(/, 2)[1];
105
+ const ctx = this.ctx.child({
106
+ fields: {
107
+ edit: {
108
+ subscription: name
109
+ }
110
+ },
111
+ devFields: {
112
+ edit: {
113
+ subscription: name,
114
+ variables: unthunk(options.variables)
115
+ }
116
+ }
117
+ });
118
+ ctx.log.info("subscribing to graphql subscription");
119
+ const unsubscribe = _class_private_field_get(this, _client).subscribe(ctx, {
120
+ ...options,
121
+ onResponse: async (response)=>{
122
+ if (response.errors) {
123
+ unsubscribe();
124
+ await options.onError(new EditError(options.subscription, response.errors));
125
+ return;
126
+ }
127
+ if (!response.data) {
128
+ unsubscribe();
129
+ await options.onError(new EditError(options.subscription, "Subscription response did not contain data"));
130
+ return;
131
+ }
132
+ await onData(response.data);
133
+ }
134
+ });
135
+ return unsubscribe;
136
+ }
137
+ /**
138
+ * Close the client.
139
+ */ async dispose() {
140
+ await _class_private_field_get(this, _client).dispose();
141
+ }
142
+ constructor(ctx){
143
+ _define_property(this, "ctx", void 0);
144
+ _class_private_field_init(this, _client, {
145
+ writable: true,
146
+ value: void 0
147
+ });
148
+ this.ctx = ctx.child({
149
+ name: "edit"
150
+ });
151
+ _class_private_field_set(this, _client, new Client(this.ctx));
152
+ }
153
+ }
154
+
155
+ //# sourceMappingURL=edit.js.map