@gadgetinc/ggt 0.4.10 → 1.0.0

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 (142) hide show
  1. package/README.md +165 -93
  2. package/lib/__generated__/graphql.js +66 -1
  3. package/lib/__generated__/graphql.js.map +1 -1
  4. package/lib/commands/deploy.js +328 -230
  5. package/lib/commands/deploy.js.map +1 -1
  6. package/lib/commands/dev.js +445 -0
  7. package/lib/commands/dev.js.map +1 -0
  8. package/lib/commands/list.js +27 -19
  9. package/lib/commands/list.js.map +1 -1
  10. package/lib/commands/login.js +15 -11
  11. package/lib/commands/login.js.map +1 -1
  12. package/lib/commands/logout.js +5 -5
  13. package/lib/commands/logout.js.map +1 -1
  14. package/lib/commands/open.js +200 -0
  15. package/lib/commands/open.js.map +1 -0
  16. package/lib/commands/pull.js +128 -0
  17. package/lib/commands/pull.js.map +1 -0
  18. package/lib/commands/push.js +126 -0
  19. package/lib/commands/push.js.map +1 -0
  20. package/lib/commands/root.js +46 -28
  21. package/lib/commands/root.js.map +1 -1
  22. package/lib/commands/status.js +61 -0
  23. package/lib/commands/status.js.map +1 -0
  24. package/lib/commands/version.js +6 -6
  25. package/lib/commands/version.js.map +1 -1
  26. package/lib/commands/whoami.js +6 -6
  27. package/lib/commands/whoami.js.map +1 -1
  28. package/lib/ggt.js +33 -8
  29. package/lib/ggt.js.map +1 -1
  30. package/lib/main.js +5 -0
  31. package/lib/main.js.map +1 -0
  32. package/lib/services/app/api/api.js +191 -0
  33. package/lib/services/app/api/api.js.map +1 -0
  34. package/lib/services/app/api/operation.js +12 -0
  35. package/lib/services/app/api/operation.js.map +1 -0
  36. package/lib/services/app/app.js +44 -10
  37. package/lib/services/app/app.js.map +1 -1
  38. package/lib/services/app/{edit/client.js → client.js} +29 -19
  39. package/lib/services/app/client.js.map +1 -0
  40. package/lib/services/app/edit/edit.js +67 -31
  41. package/lib/services/app/edit/edit.js.map +1 -1
  42. package/lib/services/app/edit/operation.js +4 -3
  43. package/lib/services/app/edit/operation.js.map +1 -1
  44. package/lib/services/app/{edit/error.js → error.js} +6 -6
  45. package/lib/services/app/error.js.map +1 -0
  46. package/lib/services/command/arg.js +4 -4
  47. package/lib/services/command/arg.js.map +1 -1
  48. package/lib/services/command/command.js +9 -7
  49. package/lib/services/command/command.js.map +1 -1
  50. package/lib/services/command/context.js +82 -20
  51. package/lib/services/command/context.js.map +1 -1
  52. package/lib/services/config/config.js +4 -7
  53. package/lib/services/config/config.js.map +1 -1
  54. package/lib/services/config/env.js +1 -1
  55. package/lib/services/config/env.js.map +1 -1
  56. package/lib/services/filesync/changes.js +76 -37
  57. package/lib/services/filesync/changes.js.map +1 -1
  58. package/lib/services/filesync/conflicts.js +10 -9
  59. package/lib/services/filesync/conflicts.js.map +1 -1
  60. package/lib/services/filesync/directory.js +16 -1
  61. package/lib/services/filesync/directory.js.map +1 -1
  62. package/lib/services/filesync/error.js +96 -27
  63. package/lib/services/filesync/error.js.map +1 -1
  64. package/lib/services/filesync/filesync.js +448 -490
  65. package/lib/services/filesync/filesync.js.map +1 -1
  66. package/lib/services/filesync/hashes.js +8 -5
  67. package/lib/services/filesync/hashes.js.map +1 -1
  68. package/lib/services/filesync/strategy.js +59 -0
  69. package/lib/services/filesync/strategy.js.map +1 -0
  70. package/lib/services/filesync/sync-json.js +475 -0
  71. package/lib/services/filesync/sync-json.js.map +1 -0
  72. package/lib/services/http/auth.js +30 -1
  73. package/lib/services/http/auth.js.map +1 -1
  74. package/lib/services/http/http.js +5 -0
  75. package/lib/services/http/http.js.map +1 -1
  76. package/lib/services/output/confirm.js +149 -0
  77. package/lib/services/output/confirm.js.map +1 -0
  78. package/lib/services/output/footer.js +22 -0
  79. package/lib/services/output/footer.js.map +1 -0
  80. package/lib/services/output/log/format/pretty.js +2 -1
  81. package/lib/services/output/log/format/pretty.js.map +1 -1
  82. package/lib/services/output/log/logger.js +13 -5
  83. package/lib/services/output/log/logger.js.map +1 -1
  84. package/lib/services/output/log/structured.js +2 -2
  85. package/lib/services/output/log/structured.js.map +1 -1
  86. package/lib/services/output/output.js +197 -0
  87. package/lib/services/output/output.js.map +1 -0
  88. package/lib/services/output/print.js +31 -0
  89. package/lib/services/output/print.js.map +1 -0
  90. package/lib/services/output/problems.js +84 -0
  91. package/lib/services/output/problems.js.map +1 -0
  92. package/lib/services/output/prompt.js +173 -40
  93. package/lib/services/output/prompt.js.map +1 -1
  94. package/lib/services/output/report.js +63 -19
  95. package/lib/services/output/report.js.map +1 -1
  96. package/lib/services/output/select.js +198 -0
  97. package/lib/services/output/select.js.map +1 -0
  98. package/lib/services/output/spinner.js +141 -0
  99. package/lib/services/output/spinner.js.map +1 -0
  100. package/lib/services/output/sprint.js +38 -15
  101. package/lib/services/output/sprint.js.map +1 -1
  102. package/lib/services/output/symbols.js +23 -0
  103. package/lib/services/output/symbols.js.map +1 -0
  104. package/lib/services/output/table.js +98 -0
  105. package/lib/services/output/table.js.map +1 -0
  106. package/lib/services/output/timestamp.js +12 -0
  107. package/lib/services/output/timestamp.js.map +1 -0
  108. package/lib/services/output/update.js +29 -9
  109. package/lib/services/output/update.js.map +1 -1
  110. package/lib/services/user/session.js +4 -0
  111. package/lib/services/user/session.js.map +1 -1
  112. package/lib/services/user/user.js +15 -10
  113. package/lib/services/user/user.js.map +1 -1
  114. package/lib/services/util/assert.js +11 -0
  115. package/lib/services/util/assert.js.map +1 -0
  116. package/lib/services/util/boolean.js +2 -2
  117. package/lib/services/util/boolean.js.map +1 -1
  118. package/lib/services/util/function.js +45 -7
  119. package/lib/services/util/function.js.map +1 -1
  120. package/lib/services/util/is.js +23 -2
  121. package/lib/services/util/is.js.map +1 -1
  122. package/lib/services/util/json.js +16 -13
  123. package/lib/services/util/json.js.map +1 -1
  124. package/lib/services/util/object.js +2 -2
  125. package/lib/services/util/object.js.map +1 -1
  126. package/lib/services/util/promise.js +5 -2
  127. package/lib/services/util/promise.js.map +1 -1
  128. package/lib/services/util/types.js.map +1 -1
  129. package/npm-shrinkwrap.json +3415 -2973
  130. package/package.json +47 -40
  131. package/bin/dev.cmd +0 -3
  132. package/bin/dev.js +0 -14
  133. package/bin/run.cmd +0 -3
  134. package/bin/run.js +0 -5
  135. package/lib/commands/sync.js +0 -284
  136. package/lib/commands/sync.js.map +0 -1
  137. package/lib/services/app/edit/client.js.map +0 -1
  138. package/lib/services/app/edit/error.js.map +0 -1
  139. package/lib/services/output/log/printer.js +0 -120
  140. package/lib/services/output/log/printer.js.map +0 -1
  141. package/lib/services/output/stream.js +0 -54
  142. package/lib/services/output/stream.js.map +0 -1
@@ -0,0 +1,475 @@
1
+ import { _ as _define_property } from "@swc/helpers/_/_define_property";
2
+ import { findUp } from "find-up";
3
+ import fs from "fs-extra";
4
+ import assert from "node:assert";
5
+ import path from "node:path";
6
+ import { simpleGit } from "simple-git";
7
+ import terminalLink from "terminal-link";
8
+ import { z } from "zod";
9
+ import { EnvironmentType, getApps } from "../app/app.js";
10
+ import { AppArg } from "../app/arg.js";
11
+ import { Edit } from "../app/edit/edit.js";
12
+ import { ArgError } from "../command/arg.js";
13
+ import { config, homePath } from "../config/config.js";
14
+ import { println } from "../output/print.js";
15
+ import { select } from "../output/select.js";
16
+ import { sprint, sprintln } from "../output/sprint.js";
17
+ import { getUserOrLogin } from "../user/user.js";
18
+ import { sortBySimilar } from "../util/collection.js";
19
+ import { defaults } from "../util/object.js";
20
+ import { Directory } from "./directory.js";
21
+ import { UnknownDirectoryError } from "./error.js";
22
+ export const SyncJsonArgs = {
23
+ "--app": {
24
+ type: AppArg,
25
+ alias: [
26
+ "-a",
27
+ "--application"
28
+ ]
29
+ },
30
+ "--env": {
31
+ type: String,
32
+ alias: [
33
+ "-e",
34
+ "--environment"
35
+ ]
36
+ },
37
+ "--allow-unknown-directory": Boolean,
38
+ "--allow-different-app": Boolean
39
+ };
40
+ /**
41
+ * The state of the filesystem.
42
+ *
43
+ * This is persisted to `.gadget/sync.json` within the {@linkcode directory}.
44
+ */ // TODO: rename and/or add to ctx?
45
+ export class SyncJson {
46
+ /**
47
+ * The last filesVersion that was written to the filesystem.
48
+ *
49
+ * This determines if the filesystem in Gadget is ahead of the
50
+ * filesystem on the local machine.
51
+ */ get filesVersion() {
52
+ const environment = this.state.environments[this.state.environment];
53
+ assert(environment, "environment must exist in environments");
54
+ return BigInt(environment.filesVersion);
55
+ }
56
+ /**
57
+ * Loads a {@linkcode SyncJson} from the specified directory.
58
+ *
59
+ * Returns undefined if the directory doesn't exist, is empty, or
60
+ * doesn't contain a `.gadget/sync.json` file.
61
+ */ static async load(ctx, { directory }) {
62
+ ctx = ctx.child({
63
+ name: "sync-json"
64
+ });
65
+ const user = await getUserOrLogin(ctx);
66
+ const availableApps = await getApps(ctx);
67
+ if (availableApps.length === 0) {
68
+ throw new ArgError(sprint`
69
+ You (${user.email}) don't have have any Gadget applications.
70
+
71
+ Visit https://gadget.new to create one!
72
+ `);
73
+ }
74
+ // try to load the .gadget/sync.json file
75
+ const syncJsonFile = await fs.readFile(directory.absolute(".gadget/sync.json"), "utf8").catch((error)=>ctx.log.warn("failed to read .gadget/sync.json", {
76
+ error
77
+ }));
78
+ if (!syncJsonFile) {
79
+ // the .gadget/sync.json file doesn't exist in the directory
80
+ // or any of its parents
81
+ return undefined;
82
+ }
83
+ // the .gadget/sync.json file exists, try to parse it
84
+ let state;
85
+ try {
86
+ state = SyncJsonState.parse(JSON.parse(syncJsonFile));
87
+ } catch (error) {
88
+ // the .gadget/sync.json file exists, but it's invalid
89
+ ctx.log.warn("failed to parse .gadget/sync.json", {
90
+ error,
91
+ syncJsonFile
92
+ });
93
+ return undefined;
94
+ }
95
+ ctx.app = await loadApp(ctx, {
96
+ availableApps,
97
+ state
98
+ });
99
+ ctx.env = await loadEnv(ctx, {
100
+ app: ctx.app,
101
+ state
102
+ });
103
+ if (state.application !== ctx.app.slug) {
104
+ // .gadget/sync.json is associated with a different app
105
+ if (ctx.args["--allow-different-app"]) {
106
+ // the user passed --allow-different-app, so use the application
107
+ // and environment they specified and clobber everything
108
+ const syncJson = new SyncJson(ctx, directory, undefined, {
109
+ application: ctx.app.slug,
110
+ environment: ctx.env.name,
111
+ environments: {
112
+ [ctx.env.name]: {
113
+ filesVersion: "0"
114
+ }
115
+ }
116
+ });
117
+ await syncJson.loadGitBranch();
118
+ return syncJson;
119
+ }
120
+ // the user didn't pass --allow-different-app, so throw an error
121
+ throw new ArgError(sprint`
122
+ You were about to sync the following app to the following directory:
123
+
124
+ ${ctx.app.slug} (${ctx.env.name}) → ${directory.path}
125
+
126
+ However, that directory has already been synced with this app:
127
+
128
+ ${state.application} (${state.environment})
129
+
130
+ If you're sure that you want to sync:
131
+
132
+ ${ctx.app.slug} (${ctx.env.name}) → ${directory.path}
133
+
134
+ Run "ggt dev" with the {bold --allow-different-app} flag.
135
+ `);
136
+ }
137
+ let previousEnvironment;
138
+ if (state.environment !== ctx.env.name) {
139
+ // the user specified a different environment, update the state
140
+ println({
141
+ ensureEmptyLineAbove: true
142
+ })`
143
+ Changing environment.
144
+
145
+ ${state.environment} → ${ctx.env.name}
146
+ `;
147
+ previousEnvironment = state.environment;
148
+ state.environment = ctx.env.name;
149
+ if (!state.environments[ctx.env.name]) {
150
+ // the user has never synced to this environment before
151
+ state.environments[ctx.env.name] = {
152
+ filesVersion: "0"
153
+ };
154
+ }
155
+ }
156
+ const syncJson = new SyncJson(ctx, directory, previousEnvironment, state);
157
+ await syncJson.save(syncJson.filesVersion);
158
+ await syncJson.loadGitBranch();
159
+ return syncJson;
160
+ }
161
+ /**
162
+ * Loads a {@linkcode SyncJson} from the specified directory or
163
+ * initializes a new one.
164
+ *
165
+ * - Ensures a user is logged in.
166
+ * - Ensures the user has at least one application.
167
+ * - Ensures the directory exists.
168
+ * - Ensures the directory is empty or contains a `.gadget/sync.json` file, unless --allow-unknown-directory was passed
169
+ * - Ensures the specified app matches the app the directory previously synced to, unless --allow-different-app was passed
170
+ */ // TODO: rename to loadOrAskAndInit
171
+ static async loadOrInit(ctx, { directory }) {
172
+ ctx = ctx.child({
173
+ name: "sync-json"
174
+ });
175
+ let syncJson = await SyncJson.load(ctx, {
176
+ directory
177
+ });
178
+ if (syncJson) {
179
+ // the .gadget/sync.json file already exists and is valid
180
+ return syncJson;
181
+ }
182
+ if (await directory.hasFiles() && !ctx.args["--allow-unknown-directory"]) {
183
+ // the directory isn't empty and the user didn't pass --allow-unknown-directory
184
+ throw new UnknownDirectoryError(ctx, {
185
+ directory
186
+ });
187
+ }
188
+ ctx.app = await loadApp(ctx, {
189
+ availableApps: await getApps(ctx)
190
+ });
191
+ ctx.env = await loadEnv(ctx, {
192
+ app: ctx.app
193
+ });
194
+ // the directory is empty or the user passed
195
+ // --allow-unknown-directory, either way ensure the directory exists
196
+ // and create a fresh .gadget/sync.json file
197
+ await fs.ensureDir(directory.path);
198
+ syncJson = new SyncJson(ctx, directory, undefined, {
199
+ application: ctx.app.slug,
200
+ environment: ctx.env.name,
201
+ environments: {
202
+ [ctx.env.name]: {
203
+ filesVersion: "0"
204
+ }
205
+ }
206
+ });
207
+ await syncJson.save(syncJson.filesVersion);
208
+ await syncJson.loadGitBranch();
209
+ return syncJson;
210
+ }
211
+ // TODO: just asks the user to select an app and environment, doesn't create a .gadget/sync.json file
212
+ // static async loadOrAsk(ctx: Context<SyncJsonArgs>, { directory }: { directory: Directory }): Promise<SyncJson | undefined> {
213
+ // }
214
+ /**
215
+ * Updates {@linkcode _syncJson} and saves it to `.gadget/sync.json`.
216
+ */ async save(filesVersion) {
217
+ const environment = this.state.environments[this.state.environment];
218
+ assert(environment, "environment must exist in environments");
219
+ environment.filesVersion = String(filesVersion);
220
+ this.ctx.log.debug("saving .gadget/sync.json");
221
+ this._mtime = Date.now();
222
+ await fs.outputJSON(this.directory.absolute(".gadget/sync.json"), {
223
+ ...this.state,
224
+ // v0.4
225
+ app: this.state.application,
226
+ filesVersion: String(filesVersion),
227
+ mtime: this._mtime
228
+ }, {
229
+ spaces: 2
230
+ });
231
+ }
232
+ async loadGitBranch() {
233
+ this.gitBranch = await loadBranch(this.ctx, {
234
+ directory: this.directory
235
+ });
236
+ }
237
+ sprint(options = {}) {
238
+ let str = sprintln`
239
+ Application ${this.app.slug}
240
+ Environment ${this.env.name}
241
+ `;
242
+ if (this.gitBranch) {
243
+ str += sprintln({
244
+ indent: 5
245
+ })`Branch ${this.gitBranch}`;
246
+ }
247
+ if (terminalLink.isSupported) {
248
+ str += sprintln({
249
+ ensureEmptyLineAbove: true
250
+ })`
251
+ ${terminalLink("Preview", `https://${this.app.slug}--${this.env.name}.gadget.app`)} ${terminalLink("Editor", `https://${this.app.primaryDomain}/edit/${this.env.name}`)} ${terminalLink("Playground", `https://${this.app.primaryDomain}/api/playground/graphql?environment=${this.env.name}`)} ${terminalLink("Docs", `https://docs.gadget.dev/api/${this.app.slug}`)}
252
+ `;
253
+ } else {
254
+ str += sprintln`
255
+ ------------------------
256
+ Preview https://${this.app.slug}--${this.env.name}.gadget.app
257
+ Editor https://${this.app.primaryDomain}/edit/${this.env.name}
258
+ Playground https://${this.app.primaryDomain}/api/playground/graphql?environment=${this.env.name}
259
+ Docs https://docs.gadget.dev/api/${this.app.slug}
260
+ `;
261
+ }
262
+ return sprintln(options)(str);
263
+ }
264
+ print(options) {
265
+ options = defaults(options, {
266
+ ensureEmptyLineAbove: true
267
+ });
268
+ println(this.sprint(options));
269
+ }
270
+ constructor(/**
271
+ * The {@linkcode Context} that was used to initialize this
272
+ * {@linkcode SyncJson} instance.
273
+ */ ctx, /**
274
+ * The root directory of the local filesystem, or in other words,
275
+ * the directory that contains the `.gadget/sync.json` file.
276
+ */ directory, /**
277
+ * Indicates whether the environment was changed when this instance
278
+ * was loaded or initialized.
279
+ */ previousEnvironment, /**
280
+ * The state of the `.gadget/sync.json` file on the local
281
+ * filesystem.
282
+ */ state){
283
+ _define_property(this, "ctx", void 0);
284
+ _define_property(this, "directory", void 0);
285
+ _define_property(this, "previousEnvironment", void 0);
286
+ _define_property(this, "state", void 0);
287
+ /**
288
+ * The {@linkcode Application} that the directory is synced to.
289
+ */ _define_property(this, "app", void 0);
290
+ /**
291
+ * The {@linkcode Environment} that the directory is synced to.
292
+ */ _define_property(this, "env", void 0);
293
+ /**
294
+ * The last git branch that was checked out in the directory.
295
+ */ _define_property(this, "gitBranch", void 0);
296
+ /**
297
+ * The {@linkcode Edit} client that can be used to send Gadget API
298
+ * requests to the environment that the directory is synced to.
299
+ */ _define_property(this, "edit", void 0);
300
+ /**
301
+ * The last time the `.gadget/sync.json` file was modified.
302
+ *
303
+ * @deprecated
304
+ * We don't use this anymore, it's only here because older versions
305
+ * of ggt expect it to be in the .gadget/sync.json file.
306
+ */ _define_property(this, "_mtime", void 0);
307
+ this.ctx = ctx;
308
+ this.directory = directory;
309
+ this.previousEnvironment = previousEnvironment;
310
+ this.state = state;
311
+ this.ctx = ctx.child({
312
+ fields: ()=>({
313
+ syncJson: {
314
+ directory: this.directory.path,
315
+ branch: this.gitBranch,
316
+ ...this.state
317
+ }
318
+ })
319
+ });
320
+ assert(this.ctx.app, "app must be set on syncJson context");
321
+ this.app = this.ctx.app;
322
+ assert(this.ctx.env, "env must be set on syncJson context");
323
+ this.env = this.ctx.env;
324
+ this.edit = new Edit(this.ctx);
325
+ }
326
+ }
327
+ export const loadSyncJsonDirectory = async (dir)=>{
328
+ if (config.windows && dir.startsWith("~/")) {
329
+ // "~" doesn't expand to the home directory on Windows
330
+ dir = homePath(dir.slice(2));
331
+ }
332
+ // TODO: rename to .gadget/ggt.json
333
+ const syncJsonPath = await findUp(".gadget/sync.json", {
334
+ cwd: dir
335
+ });
336
+ if (syncJsonPath) {
337
+ // we found a .gadget/sync.json file, use its parent directory
338
+ dir = path.join(syncJsonPath, "../..");
339
+ }
340
+ // ensure the directory path is absolute
341
+ dir = path.resolve(dir);
342
+ return await Directory.init(dir);
343
+ };
344
+ // ensure the selected app is valid
345
+ const loadApp = async (ctx, { availableApps, state })=>{
346
+ let appSlug = ctx.args["--app"] || state?.application;
347
+ if (!appSlug) {
348
+ // the user didn't specify an app, ask them to select one
349
+ appSlug = await select({
350
+ choices: availableApps.map((x)=>x.slug)
351
+ })`
352
+ Which application do you want to develop?
353
+ `;
354
+ }
355
+ const app = availableApps.find((app)=>app.slug === appSlug);
356
+ if (app) {
357
+ // the user specified an app or we loaded it from the state,
358
+ // and it exists in their list of applications, so return it
359
+ return app;
360
+ }
361
+ // the specified appSlug doesn't exist in their list of apps,
362
+ // either they misspelled it or they don't have access to it
363
+ // anymore, suggest some apps that are similar to the one they
364
+ // specified
365
+ const similarAppSlugs = sortBySimilar(appSlug, availableApps.map((app)=>app.slug)).slice(0, 5);
366
+ // TODO: differentiate between incorrect --app vs state.application?
367
+ throw new ArgError(sprint`
368
+ Unknown application:
369
+
370
+ ${appSlug}
371
+
372
+ Did you mean one of these?
373
+
374
+ • ${similarAppSlugs.join("\n • ")}
375
+ `);
376
+ };
377
+ const loadEnv = async (ctx, { app, state })=>{
378
+ if (ctx.args["--env"] && !app.multiEnvironmentEnabled) {
379
+ // this is a legacy app that only has 1 development environment, so
380
+ // let them know now rather than running into a weird error later
381
+ // TODO: come back to this
382
+ throw new ArgError(sprint`
383
+ You specified an environment but your app doesn't have multiple environments.
384
+
385
+ Remove the "--env" flag to sync to the {bold ${app.primaryDomain}} environment.
386
+ `);
387
+ }
388
+ const devEnvs = app.environments.filter((env)=>env.type === EnvironmentType.Development);
389
+ let envName = ctx.args["--env"] || state?.environment;
390
+ if (!envName) {
391
+ // user didn't specify an environment, ask them to select one
392
+ envName = await select({
393
+ choices: devEnvs.map((x)=>x.name)
394
+ })`
395
+ Which environment do you want to develop on?
396
+ `;
397
+ }
398
+ if (envName.toLowerCase() === "production") {
399
+ // specifically call out that they can't dev, push, or pull to prod
400
+ throw new ArgError(sprint`
401
+ You cannot "ggt ${ctx.command}" your {bold production} environment.
402
+ `);
403
+ }
404
+ const env = devEnvs.find((env)=>env.name === envName.toLowerCase());
405
+ if (env) {
406
+ // the user specified an environment or we loaded it from the state,
407
+ // and it exists in the app's list of environments, so return it
408
+ return env;
409
+ }
410
+ // the specified env doesn't exist in their list of environments,
411
+ // either they misspelled it or they don't have access to it
412
+ // anymore, suggest some envs that are similar to the one they
413
+ // specified
414
+ const similarEnvironments = sortBySimilar(envName, devEnvs.map((env)=>env.name)).slice(0, 5);
415
+ throw new ArgError(sprint`
416
+ Unknown environment:
417
+
418
+ ${envName}
419
+
420
+ Did you mean one of these?
421
+
422
+ • ${similarEnvironments.join("\n • ")}
423
+ `);
424
+ };
425
+ /**
426
+ * Returns the current git branch of the directory or undefined if
427
+ * the directory isn't a git repository.
428
+ */ const loadBranch = async (ctx, { directory })=>{
429
+ try {
430
+ const branch = await simpleGit(directory.path).revparse([
431
+ "--abbrev-ref",
432
+ "HEAD"
433
+ ]);
434
+ return branch;
435
+ } catch (error) {
436
+ ctx.log.warn("failed to read git branch", {
437
+ error
438
+ });
439
+ return undefined;
440
+ }
441
+ };
442
+ export const SyncJsonStateV05 = z.object({
443
+ application: z.string(),
444
+ environment: z.string(),
445
+ environments: z.record(z.object({
446
+ filesVersion: z.string()
447
+ }))
448
+ });
449
+ export const SyncJsonStateV04 = z.object({
450
+ app: z.string(),
451
+ filesVersion: z.string(),
452
+ mtime: z.number()
453
+ });
454
+ export const AnySyncJsonState = z.union([
455
+ SyncJsonStateV05,
456
+ SyncJsonStateV04
457
+ ]);
458
+ export const SyncJsonState = AnySyncJsonState.transform((state)=>{
459
+ if ("environment" in state) {
460
+ // it's a v0.5 state
461
+ return state;
462
+ }
463
+ // it's a v0.4 state, transform it to a v0.5 state
464
+ return {
465
+ application: state.app,
466
+ environment: "development",
467
+ environments: {
468
+ development: {
469
+ filesVersion: state.filesVersion
470
+ }
471
+ }
472
+ };
473
+ });
474
+
475
+ //# sourceMappingURL=sync-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/services/filesync/sync-json.ts"],"sourcesContent":["import { findUp } from \"find-up\";\nimport fs from \"fs-extra\";\nimport assert from \"node:assert\";\nimport path from \"node:path\";\nimport { simpleGit } from \"simple-git\";\nimport terminalLink from \"terminal-link\";\nimport { z } from \"zod\";\nimport { EnvironmentType, getApps, type Application, type Environment } from \"../app/app.js\";\nimport { AppArg } from \"../app/arg.js\";\nimport { Edit } from \"../app/edit/edit.js\";\nimport { ArgError, type ArgsDefinition } from \"../command/arg.js\";\nimport type { Context } from \"../command/context.js\";\nimport { config, homePath } from \"../config/config.js\";\nimport { println } from \"../output/print.js\";\nimport { select } from \"../output/select.js\";\nimport { sprint, sprintln, type SprintOptions } from \"../output/sprint.js\";\nimport { getUserOrLogin } from \"../user/user.js\";\nimport { sortBySimilar } from \"../util/collection.js\";\nimport { defaults } from \"../util/object.js\";\nimport { Directory } from \"./directory.js\";\nimport { UnknownDirectoryError } from \"./error.js\";\n\nexport const SyncJsonArgs = {\n \"--app\": { type: AppArg, alias: [\"-a\", \"--application\"] },\n \"--env\": { type: String, alias: [\"-e\", \"--environment\"] },\n \"--allow-unknown-directory\": Boolean,\n \"--allow-different-app\": Boolean,\n} satisfies ArgsDefinition;\n\nexport type SyncJsonArgs = typeof SyncJsonArgs;\n\n/**\n * The state of the filesystem.\n *\n * This is persisted to `.gadget/sync.json` within the {@linkcode directory}.\n */\n// TODO: rename and/or add to ctx?\nexport class SyncJson {\n /**\n * The {@linkcode Application} that the directory is synced to.\n */\n readonly app: Application;\n\n /**\n * The {@linkcode Environment} that the directory is synced to.\n */\n readonly env: Environment;\n\n /**\n * The last git branch that was checked out in the directory.\n */\n gitBranch: string | undefined;\n\n /**\n * The {@linkcode Edit} client that can be used to send Gadget API\n * requests to the environment that the directory is synced to.\n */\n readonly edit: Edit;\n\n /**\n * The last time the `.gadget/sync.json` file was modified.\n *\n * @deprecated\n * We don't use this anymore, it's only here because older versions\n * of ggt expect it to be in the .gadget/sync.json file.\n */\n private _mtime: number | undefined;\n\n private constructor(\n /**\n * The {@linkcode Context} that was used to initialize this\n * {@linkcode SyncJson} instance.\n */\n readonly ctx: Context<SyncJsonArgs>,\n\n /**\n * The root directory of the local filesystem, or in other words,\n * the directory that contains the `.gadget/sync.json` file.\n */\n readonly directory: Directory,\n\n /**\n * Indicates whether the environment was changed when this instance\n * was loaded or initialized.\n */\n readonly previousEnvironment: string | undefined,\n\n /**\n * The state of the `.gadget/sync.json` file on the local\n * filesystem.\n */\n readonly state: SyncJsonState,\n ) {\n this.ctx = ctx.child({\n fields: () => ({\n syncJson: {\n directory: this.directory.path,\n branch: this.gitBranch,\n ...this.state,\n },\n }),\n });\n\n assert(this.ctx.app, \"app must be set on syncJson context\");\n this.app = this.ctx.app;\n\n assert(this.ctx.env, \"env must be set on syncJson context\");\n this.env = this.ctx.env;\n\n this.edit = new Edit(this.ctx);\n }\n\n /**\n * The last filesVersion that was written to the filesystem.\n *\n * This determines if the filesystem in Gadget is ahead of the\n * filesystem on the local machine.\n */\n get filesVersion(): bigint {\n const environment = this.state.environments[this.state.environment];\n assert(environment, \"environment must exist in environments\");\n return BigInt(environment.filesVersion);\n }\n\n /**\n * Loads a {@linkcode SyncJson} from the specified directory.\n *\n * Returns undefined if the directory doesn't exist, is empty, or\n * doesn't contain a `.gadget/sync.json` file.\n */\n static async load(ctx: Context<SyncJsonArgs>, { directory }: { directory: Directory }): Promise<SyncJson | undefined> {\n ctx = ctx.child({ name: \"sync-json\" });\n\n const user = await getUserOrLogin(ctx);\n const availableApps = await getApps(ctx);\n if (availableApps.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 // try to load the .gadget/sync.json file\n const syncJsonFile = await fs\n .readFile(directory.absolute(\".gadget/sync.json\"), \"utf8\")\n .catch((error: unknown) => ctx.log.warn(\"failed to read .gadget/sync.json\", { error }));\n\n if (!syncJsonFile) {\n // the .gadget/sync.json file doesn't exist in the directory\n // or any of its parents\n return undefined;\n }\n\n // the .gadget/sync.json file exists, try to parse it\n let state: SyncJsonState | undefined;\n try {\n state = SyncJsonState.parse(JSON.parse(syncJsonFile));\n } catch (error) {\n // the .gadget/sync.json file exists, but it's invalid\n ctx.log.warn(\"failed to parse .gadget/sync.json\", { error, syncJsonFile });\n return undefined;\n }\n\n ctx.app = await loadApp(ctx, { availableApps, state });\n ctx.env = await loadEnv(ctx, { app: ctx.app, state });\n\n if (state.application !== ctx.app.slug) {\n // .gadget/sync.json is associated with a different app\n if (ctx.args[\"--allow-different-app\"]) {\n // the user passed --allow-different-app, so use the application\n // and environment they specified and clobber everything\n const syncJson = new SyncJson(ctx, directory, undefined, {\n application: ctx.app.slug,\n environment: ctx.env.name,\n environments: {\n [ctx.env.name]: { filesVersion: \"0\" },\n },\n });\n\n await syncJson.loadGitBranch();\n return syncJson;\n }\n\n // the user didn't pass --allow-different-app, so throw an error\n throw new ArgError(sprint`\n You were about to sync the following app to the following directory:\n\n ${ctx.app.slug} (${ctx.env.name}) → ${directory.path}\n\n However, that directory has already been synced with this app:\n\n ${state.application} (${state.environment})\n\n If you're sure that you want to sync:\n\n ${ctx.app.slug} (${ctx.env.name}) → ${directory.path}\n\n Run \"ggt dev\" with the {bold --allow-different-app} flag.\n `);\n }\n\n let previousEnvironment: string | undefined;\n if (state.environment !== ctx.env.name) {\n // the user specified a different environment, update the state\n println({ ensureEmptyLineAbove: true })`\n Changing environment.\n\n ${state.environment} → ${ctx.env.name}\n `;\n\n previousEnvironment = state.environment;\n state.environment = ctx.env.name;\n if (!state.environments[ctx.env.name]) {\n // the user has never synced to this environment before\n state.environments[ctx.env.name] = { filesVersion: \"0\" };\n }\n }\n\n const syncJson = new SyncJson(ctx, directory, previousEnvironment, state);\n await syncJson.save(syncJson.filesVersion);\n await syncJson.loadGitBranch();\n return syncJson;\n }\n\n /**\n * Loads a {@linkcode SyncJson} from the specified directory or\n * initializes a new one.\n *\n * - Ensures a user is logged in.\n * - Ensures the user has at least one application.\n * - Ensures the directory exists.\n * - Ensures the directory is empty or contains a `.gadget/sync.json` file, unless --allow-unknown-directory was passed\n * - Ensures the specified app matches the app the directory previously synced to, unless --allow-different-app was passed\n */\n // TODO: rename to loadOrAskAndInit\n static async loadOrInit(ctx: Context<SyncJsonArgs>, { directory }: { directory: Directory }): Promise<SyncJson> {\n ctx = ctx.child({ name: \"sync-json\" });\n\n let syncJson = await SyncJson.load(ctx, { directory });\n if (syncJson) {\n // the .gadget/sync.json file already exists and is valid\n return syncJson;\n }\n\n if ((await directory.hasFiles()) && !ctx.args[\"--allow-unknown-directory\"]) {\n // the directory isn't empty and the user didn't pass --allow-unknown-directory\n throw new UnknownDirectoryError(ctx, { directory });\n }\n\n ctx.app = await loadApp(ctx, { availableApps: await getApps(ctx) });\n ctx.env = await loadEnv(ctx, { app: ctx.app });\n\n // the directory is empty or the user passed\n // --allow-unknown-directory, either way ensure the directory exists\n // and create a fresh .gadget/sync.json file\n await fs.ensureDir(directory.path);\n\n syncJson = new SyncJson(ctx, directory, undefined, {\n application: ctx.app.slug,\n environment: ctx.env.name,\n environments: {\n [ctx.env.name]: { filesVersion: \"0\" },\n },\n });\n\n await syncJson.save(syncJson.filesVersion);\n await syncJson.loadGitBranch();\n\n return syncJson;\n }\n\n // TODO: just asks the user to select an app and environment, doesn't create a .gadget/sync.json file\n // static async loadOrAsk(ctx: Context<SyncJsonArgs>, { directory }: { directory: Directory }): Promise<SyncJson | undefined> {\n // }\n\n /**\n * Updates {@linkcode _syncJson} and saves it to `.gadget/sync.json`.\n */\n async save(filesVersion: string | bigint): Promise<void> {\n const environment = this.state.environments[this.state.environment];\n assert(environment, \"environment must exist in environments\");\n environment.filesVersion = String(filesVersion);\n\n this.ctx.log.debug(\"saving .gadget/sync.json\");\n this._mtime = Date.now();\n await fs.outputJSON(\n this.directory.absolute(\".gadget/sync.json\"),\n {\n ...this.state,\n\n // v0.4\n app: this.state.application,\n filesVersion: String(filesVersion),\n mtime: this._mtime,\n },\n { spaces: 2 },\n );\n }\n\n async loadGitBranch(): Promise<void> {\n this.gitBranch = await loadBranch(this.ctx, { directory: this.directory });\n }\n\n sprint(options: SprintOptions = {}): string {\n let str = sprintln`\n Application ${this.app.slug}\n Environment ${this.env.name}\n `;\n\n if (this.gitBranch) {\n str += sprintln({ indent: 5 })`Branch ${this.gitBranch}`;\n }\n\n if (terminalLink.isSupported) {\n str += sprintln({ ensureEmptyLineAbove: true })`\n ${terminalLink(\"Preview\", `https://${this.app.slug}--${this.env.name}.gadget.app`)} ${terminalLink(\"Editor\", `https://${this.app.primaryDomain}/edit/${this.env.name}`)} ${terminalLink(\"Playground\", `https://${this.app.primaryDomain}/api/playground/graphql?environment=${this.env.name}`)} ${terminalLink(\"Docs\", `https://docs.gadget.dev/api/${this.app.slug}`)}\n `;\n } else {\n str += sprintln`\n ------------------------\n Preview https://${this.app.slug}--${this.env.name}.gadget.app\n Editor https://${this.app.primaryDomain}/edit/${this.env.name}\n Playground https://${this.app.primaryDomain}/api/playground/graphql?environment=${this.env.name}\n Docs https://docs.gadget.dev/api/${this.app.slug}\n `;\n }\n\n return sprintln(options)(str);\n }\n\n print(options?: SprintOptions): void {\n options = defaults(options, { ensureEmptyLineAbove: true });\n println(this.sprint(options));\n }\n}\n\nexport const loadSyncJsonDirectory = async (dir: string): Promise<Directory> => {\n if (config.windows && dir.startsWith(\"~/\")) {\n // \"~\" doesn't expand to the home directory on Windows\n dir = homePath(dir.slice(2));\n }\n\n // TODO: rename to .gadget/ggt.json\n const syncJsonPath = await findUp(\".gadget/sync.json\", { cwd: dir });\n if (syncJsonPath) {\n // we found a .gadget/sync.json file, use its parent directory\n dir = path.join(syncJsonPath, \"../..\");\n }\n\n // ensure the directory path is absolute\n dir = path.resolve(dir);\n\n return await Directory.init(dir);\n};\n\n// ensure the selected app is valid\nconst loadApp = async (\n ctx: Context<SyncJsonArgs>,\n { availableApps, state }: { availableApps: Application[]; state?: SyncJsonState },\n): Promise<Application> => {\n let appSlug = ctx.args[\"--app\"] || state?.application;\n if (!appSlug) {\n // the user didn't specify an app, ask them to select one\n appSlug = await select({ choices: availableApps.map((x) => x.slug) })`\n Which application do you want to develop?\n `;\n }\n\n const app = availableApps.find((app) => app.slug === appSlug);\n if (app) {\n // the user specified an app or we loaded it from the state,\n // and it exists in their list of applications, so return it\n return app;\n }\n\n // the specified appSlug doesn't exist in their list of apps,\n // either they misspelled it or they don't have access to it\n // anymore, suggest some apps that are similar to the one they\n // specified\n const similarAppSlugs = sortBySimilar(\n appSlug,\n availableApps.map((app) => app.slug),\n ).slice(0, 5);\n\n // TODO: differentiate between incorrect --app vs state.application?\n throw new ArgError(\n sprint`\n Unknown application:\n\n ${appSlug}\n\n Did you mean one of these?\n\n • ${similarAppSlugs.join(\"\\n • \")}\n `,\n );\n};\n\nconst loadEnv = async (ctx: Context<SyncJsonArgs>, { app, state }: { app: Application; state?: SyncJsonState }): Promise<Environment> => {\n if (ctx.args[\"--env\"] && !app.multiEnvironmentEnabled) {\n // this is a legacy app that only has 1 development environment, so\n // let them know now rather than running into a weird error later\n // TODO: come back to this\n throw new ArgError(\n sprint`\n You specified an environment but your app doesn't have multiple environments.\n\n Remove the \"--env\" flag to sync to the {bold ${app.primaryDomain}} environment.\n `,\n );\n }\n\n const devEnvs = app.environments.filter((env) => env.type === EnvironmentType.Development);\n\n let envName = ctx.args[\"--env\"] || state?.environment;\n if (!envName) {\n // user didn't specify an environment, ask them to select one\n envName = await select({ choices: devEnvs.map((x) => x.name) })`\n Which environment do you want to develop on?\n `;\n }\n\n if (envName.toLowerCase() === \"production\") {\n // specifically call out that they can't dev, push, or pull to prod\n throw new ArgError(\n sprint`\n You cannot \"ggt ${ctx.command}\" your {bold production} environment.\n `,\n );\n }\n\n const env = devEnvs.find((env) => env.name === envName.toLowerCase());\n if (env) {\n // the user specified an environment or we loaded it from the state,\n // and it exists in the app's list of environments, so return it\n return env;\n }\n\n // the specified env doesn't exist in their list of environments,\n // either they misspelled it or they don't have access to it\n // anymore, suggest some envs that are similar to the one they\n // specified\n const similarEnvironments = sortBySimilar(\n envName,\n devEnvs.map((env) => env.name),\n ).slice(0, 5);\n\n throw new ArgError(\n sprint`\n Unknown environment:\n\n ${envName}\n\n Did you mean one of these?\n\n • ${similarEnvironments.join(\"\\n • \")}\n `,\n );\n};\n\n/**\n * Returns the current git branch of the directory or undefined if\n * the directory isn't a git repository.\n */\nconst loadBranch = async (ctx: Context<SyncJsonArgs>, { directory }: { directory: Directory }): Promise<string | undefined> => {\n try {\n const branch = await simpleGit(directory.path).revparse([\"--abbrev-ref\", \"HEAD\"]);\n return branch;\n } catch (error) {\n ctx.log.warn(\"failed to read git branch\", { error });\n return undefined;\n }\n};\n\nexport const SyncJsonStateV05 = z.object({\n application: z.string(),\n environment: z.string(),\n environments: z.record(z.object({ filesVersion: z.string() })),\n});\n\nexport const SyncJsonStateV04 = z.object({\n app: z.string(),\n filesVersion: z.string(),\n mtime: z.number(),\n});\n\nexport const AnySyncJsonState = z.union([SyncJsonStateV05, SyncJsonStateV04]);\n\nexport const SyncJsonState = AnySyncJsonState.transform((state): SyncJsonStateV05 => {\n if (\"environment\" in state) {\n // it's a v0.5 state\n return state;\n }\n\n // it's a v0.4 state, transform it to a v0.5 state\n return {\n application: state.app,\n environment: \"development\",\n environments: { development: { filesVersion: state.filesVersion } },\n };\n});\n\nexport type SyncJsonStateV05 = z.infer<typeof SyncJsonStateV05>;\nexport type SyncJsonStateV04 = z.infer<typeof SyncJsonStateV04>;\nexport type AnySyncJsonState = z.infer<typeof AnySyncJsonState>;\nexport type SyncJsonState = z.infer<typeof SyncJsonState>;\n"],"names":["findUp","fs","assert","path","simpleGit","terminalLink","z","EnvironmentType","getApps","AppArg","Edit","ArgError","config","homePath","println","select","sprint","sprintln","getUserOrLogin","sortBySimilar","defaults","Directory","UnknownDirectoryError","SyncJsonArgs","type","alias","String","Boolean","SyncJson","filesVersion","environment","state","environments","BigInt","load","ctx","directory","child","name","user","availableApps","length","email","syncJsonFile","readFile","absolute","catch","error","log","warn","undefined","SyncJsonState","parse","JSON","app","loadApp","env","loadEnv","application","slug","args","syncJson","loadGitBranch","previousEnvironment","ensureEmptyLineAbove","save","loadOrInit","hasFiles","ensureDir","debug","_mtime","Date","now","outputJSON","mtime","spaces","gitBranch","loadBranch","options","str","indent","isSupported","primaryDomain","print","edit","fields","branch","loadSyncJsonDirectory","dir","windows","startsWith","slice","syncJsonPath","cwd","join","resolve","init","appSlug","choices","map","x","find","similarAppSlugs","multiEnvironmentEnabled","devEnvs","filter","Development","envName","toLowerCase","command","similarEnvironments","revparse","SyncJsonStateV05","object","string","record","SyncJsonStateV04","number","AnySyncJsonState","union","transform","development"],"mappings":"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"}
@@ -1,6 +1,10 @@
1
1
  import { HTTPError } from "got";
2
2
  import { config } from "../config/config.js";
3
- import { readSession } from "../user/session.js";
3
+ import { createLogger } from "../output/log/logger.js";
4
+ import { readSession, readToken } from "../user/session.js";
5
+ const log = createLogger({
6
+ name: "auth"
7
+ });
4
8
  /**
5
9
  * Determines whether the given request options are for a Gadget
6
10
  * Services request.
@@ -19,6 +23,31 @@ import { readSession } from "../user/session.js";
19
23
  const token = readSession();
20
24
  return token && `session=${encodeURIComponent(token)};`;
21
25
  };
26
+ /**
27
+ * Loads the authentication headers.
28
+ *
29
+ * @returns The authentication headers as a record of key-value pairs, or undefined if no headers are available.
30
+ */ export const loadAuthHeaders = ()=>{
31
+ const cookie = loadCookie();
32
+ if (cookie) {
33
+ log.trace("loading cookie as auth header", {
34
+ cookie
35
+ });
36
+ return {
37
+ cookie
38
+ };
39
+ }
40
+ const token = readToken();
41
+ if (token) {
42
+ log.trace("loading token as auth header", {
43
+ token
44
+ });
45
+ return {
46
+ "x-platform-access-token": token
47
+ };
48
+ }
49
+ return undefined;
50
+ };
22
51
  export const isUnauthorizedError = (error)=>{
23
52
  return error instanceof HTTPError && error.response.statusCode === 401;
24
53
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/services/http/auth.ts"],"sourcesContent":["import { HTTPError, type OptionsInit } from \"got\";\nimport type { Context } from \"../command/context.js\";\nimport { config } from \"../config/config.js\";\nimport { readSession } from \"../user/session.js\";\n\n/**\n * Determines whether the given request options are for a Gadget\n * Services request.\n *\n * @param options - The request options to check.\n * @returns True if the request options are for a Gadget Services\n * request, false otherwise.\n */\nexport const isGadgetServicesRequest = (options: OptionsInit): boolean => {\n return options.url instanceof URL && options.url.host === config.domains.services;\n};\n\n/**\n * Loads the cookie from the session.\n *\n * @returns The cookie string or undefined if there is no session.\n */\nexport const loadCookie = (): string | undefined => {\n const token = readSession();\n return token && `session=${encodeURIComponent(token)};`;\n};\n\nexport const isUnauthorizedError = (error: unknown): error is HTTPError => {\n return error instanceof HTTPError && error.response.statusCode === 401;\n};\n\n/**\n * Swallows unauthorized errors and logs a warning, rethrows all other\n * errors.\n *\n * @param ctx - The current context.\n * @param error - The error to handle.\n */\nexport const swallowUnauthorized = (ctx: Context, error: unknown): void => {\n if (isUnauthorizedError(error)) {\n ctx.log.warn(\"swallowing unauthorized error\", { error });\n return;\n }\n throw error;\n};\n"],"names":["HTTPError","config","readSession","isGadgetServicesRequest","options","url","URL","host","domains","services","loadCookie","token","encodeURIComponent","isUnauthorizedError","error","response","statusCode","swallowUnauthorized","ctx","log","warn"],"mappings":"AAAA,SAASA,SAAS,QAA0B,MAAM;AAElD,SAASC,MAAM,QAAQ,sBAAsB;AAC7C,SAASC,WAAW,QAAQ,qBAAqB;AAEjD;;;;;;;CAOC,GACD,OAAO,MAAMC,0BAA0B,CAACC;IACtC,OAAOA,QAAQC,GAAG,YAAYC,OAAOF,QAAQC,GAAG,CAACE,IAAI,KAAKN,OAAOO,OAAO,CAACC,QAAQ;AACnF,EAAE;AAEF;;;;CAIC,GACD,OAAO,MAAMC,aAAa;IACxB,MAAMC,QAAQT;IACd,OAAOS,SAAS,CAAC,QAAQ,EAAEC,mBAAmBD,OAAO,CAAC,CAAC;AACzD,EAAE;AAEF,OAAO,MAAME,sBAAsB,CAACC;IAClC,OAAOA,iBAAiBd,aAAac,MAAMC,QAAQ,CAACC,UAAU,KAAK;AACrE,EAAE;AAEF;;;;;;CAMC,GACD,OAAO,MAAMC,sBAAsB,CAACC,KAAcJ;IAChD,IAAID,oBAAoBC,QAAQ;QAC9BI,IAAIC,GAAG,CAACC,IAAI,CAAC,iCAAiC;YAAEN;QAAM;QACtD;IACF;IACA,MAAMA;AACR,EAAE"}
1
+ {"version":3,"sources":["../../../src/services/http/auth.ts"],"sourcesContent":["import { HTTPError, type OptionsInit } from \"got\";\nimport type { Context } from \"../command/context.js\";\nimport { config } from \"../config/config.js\";\nimport { createLogger } from \"../output/log/logger.js\";\nimport { readSession, readToken } from \"../user/session.js\";\n\nconst log = createLogger({ name: \"auth\" });\n\n/**\n * Determines whether the given request options are for a Gadget\n * Services request.\n *\n * @param options - The request options to check.\n * @returns True if the request options are for a Gadget Services\n * request, false otherwise.\n */\nexport const isGadgetServicesRequest = (options: OptionsInit): boolean => {\n return options.url instanceof URL && options.url.host === config.domains.services;\n};\n\n/**\n * Loads the cookie from the session.\n *\n * @returns The cookie string or undefined if there is no session.\n */\nexport const loadCookie = (): string | undefined => {\n const token = readSession();\n return token && `session=${encodeURIComponent(token)};`;\n};\n\n/**\n * Loads the authentication headers.\n *\n * @returns The authentication headers as a record of key-value pairs, or undefined if no headers are available.\n */\nexport const loadAuthHeaders = (): Record<string, string> | undefined => {\n const cookie = loadCookie();\n if (cookie) {\n log.trace(\"loading cookie as auth header\", { cookie });\n return { cookie };\n }\n\n const token = readToken();\n\n if (token) {\n log.trace(\"loading token as auth header\", { token });\n return { \"x-platform-access-token\": token };\n }\n\n return undefined;\n};\n\nexport const isUnauthorizedError = (error: unknown): error is HTTPError => {\n return error instanceof HTTPError && error.response.statusCode === 401;\n};\n\n/**\n * Swallows unauthorized errors and logs a warning, rethrows all other\n * errors.\n *\n * @param ctx - The current context.\n * @param error - The error to handle.\n */\nexport const swallowUnauthorized = (ctx: Context, error: unknown): void => {\n if (isUnauthorizedError(error)) {\n ctx.log.warn(\"swallowing unauthorized error\", { error });\n return;\n }\n throw error;\n};\n"],"names":["HTTPError","config","createLogger","readSession","readToken","log","name","isGadgetServicesRequest","options","url","URL","host","domains","services","loadCookie","token","encodeURIComponent","loadAuthHeaders","cookie","trace","undefined","isUnauthorizedError","error","response","statusCode","swallowUnauthorized","ctx","warn"],"mappings":"AAAA,SAASA,SAAS,QAA0B,MAAM;AAElD,SAASC,MAAM,QAAQ,sBAAsB;AAC7C,SAASC,YAAY,QAAQ,0BAA0B;AACvD,SAASC,WAAW,EAAEC,SAAS,QAAQ,qBAAqB;AAE5D,MAAMC,MAAMH,aAAa;IAAEI,MAAM;AAAO;AAExC;;;;;;;CAOC,GACD,OAAO,MAAMC,0BAA0B,CAACC;IACtC,OAAOA,QAAQC,GAAG,YAAYC,OAAOF,QAAQC,GAAG,CAACE,IAAI,KAAKV,OAAOW,OAAO,CAACC,QAAQ;AACnF,EAAE;AAEF;;;;CAIC,GACD,OAAO,MAAMC,aAAa;IACxB,MAAMC,QAAQZ;IACd,OAAOY,SAAS,CAAC,QAAQ,EAAEC,mBAAmBD,OAAO,CAAC,CAAC;AACzD,EAAE;AAEF;;;;CAIC,GACD,OAAO,MAAME,kBAAkB;IAC7B,MAAMC,SAASJ;IACf,IAAII,QAAQ;QACVb,IAAIc,KAAK,CAAC,iCAAiC;YAAED;QAAO;QACpD,OAAO;YAAEA;QAAO;IAClB;IAEA,MAAMH,QAAQX;IAEd,IAAIW,OAAO;QACTV,IAAIc,KAAK,CAAC,gCAAgC;YAAEJ;QAAM;QAClD,OAAO;YAAE,2BAA2BA;QAAM;IAC5C;IAEA,OAAOK;AACT,EAAE;AAEF,OAAO,MAAMC,sBAAsB,CAACC;IAClC,OAAOA,iBAAiBtB,aAAasB,MAAMC,QAAQ,CAACC,UAAU,KAAK;AACrE,EAAE;AAEF;;;;;;CAMC,GACD,OAAO,MAAMC,sBAAsB,CAACC,KAAcJ;IAChD,IAAID,oBAAoBC,QAAQ;QAC9BI,IAAIrB,GAAG,CAACsB,IAAI,CAAC,iCAAiC;YAAEL;QAAM;QACtD;IACF;IACA,MAAMA;AACR,EAAE"}
@@ -97,6 +97,11 @@ const getContext = (options)=>{
97
97
  request: error.request && {
98
98
  method: error.request.options.method,
99
99
  url: error.request.options.url?.toString()
100
+ },
101
+ response: error.response && {
102
+ statusCode: error.response.statusCode,
103
+ traceId: error.response.headers["x-trace-id"],
104
+ durationMs: error.response.timings.phases.total
100
105
  }
101
106
  }
102
107
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/services/http/http.ts"],"sourcesContent":["import { got, type OptionsInit } from \"got\";\nimport ms from \"ms\";\nimport assert from \"node:assert\";\nimport { Agent as HttpAgent } from \"node:http\";\nimport { Agent as HttpsAgent } from \"node:https\";\nimport { Context } from \"../command/context.js\";\nimport { config } from \"../config/config.js\";\nimport { sprint } from \"../output/sprint.js\";\nimport { writeSession } from \"../user/session.js\";\nimport { serializeError } from \"../util/object.js\";\nimport { isGadgetServicesRequest } from \"./auth.js\";\n\nexport type HttpOptions = OptionsInit;\n\nconst getContext = (options: HttpOptions): Context => {\n assert(\n options.context?.[\"ctx\"] instanceof Context,\n sprint(`\n ctx must be provided to http requests:\n\n const response = await http({\n context: { ctx },\n ...options,\n });\n `),\n );\n\n return options.context[\"ctx\"] as Context;\n};\n\n/**\n * An instance of the `got` library with hooks for logging and handling\n * 401 errors. This should be used for all HTTP requests.\n */\nexport const http = got.extend({\n agent: {\n http: new HttpAgent({ keepAlive: true }),\n https: new HttpsAgent({ keepAlive: true }),\n },\n retry: {\n limit: 10,\n methods: [\"GET\", \"PUT\", \"HEAD\", \"DELETE\", \"OPTIONS\", \"TRACE\"],\n statusCodes: [408, 413, 429, 500, 502, 503, 504, 521, 522, 524],\n errorCodes: [\n \"ETIMEDOUT\",\n \"ECONNRESET\",\n \"EADDRINUSE\",\n \"ECONNREFUSED\",\n \"EPIPE\",\n \"ENOTFOUND\",\n \"ENETUNREACH\",\n \"EAI_AGAIN\",\n \"EADDRNOTAVAIL\",\n \"EHOSTUNREACH\",\n ],\n maxRetryAfter: undefined,\n calculateDelay: ({ computedValue }) => computedValue,\n backoffLimit: ms(\"5s\"),\n noise: 100,\n },\n hooks: {\n beforeRequest: [\n (options) => {\n const ctx = getContext(options);\n options.signal = ctx.signal;\n options.headers[\"user-agent\"] = config.versionFull;\n ctx.log.debug(\"http request\", {\n http: {\n request: {\n method: options.method,\n url: options.url?.toString(),\n },\n },\n });\n },\n ],\n beforeRetry: [\n (error, retryCount) => {\n const ctx = getContext(error.request?.options ?? error.options.context);\n ctx.log.warn(\"http request failed, retrying...\", {\n http: {\n retryCount,\n error: serializeError(error),\n request: error.request && {\n method: error.request.options.method,\n url: error.request.options.url?.toString(),\n },\n },\n });\n },\n ],\n afterResponse: [\n (response) => {\n const ctx = getContext(response.request.options);\n ctx.log.debug(\"http response\", {\n http: {\n request: {\n method: response.request.options.method,\n url: response.request.options.url?.toString(),\n },\n response: {\n statusCode: response.statusCode,\n traceId: response.headers[\"x-trace-id\"],\n durationMs: response.timings.phases.total,\n },\n },\n });\n\n if (response.statusCode === 401 && isGadgetServicesRequest(response.request.options)) {\n // clear the session if the request was unauthorized\n writeSession(undefined);\n }\n\n return response;\n },\n ],\n },\n});\n"],"names":["got","ms","assert","Agent","HttpAgent","HttpsAgent","Context","config","sprint","writeSession","serializeError","isGadgetServicesRequest","getContext","options","context","http","extend","agent","keepAlive","https","retry","limit","methods","statusCodes","errorCodes","maxRetryAfter","undefined","calculateDelay","computedValue","backoffLimit","noise","hooks","beforeRequest","ctx","signal","headers","versionFull","log","debug","request","method","url","toString","beforeRetry","error","retryCount","warn","afterResponse","response","statusCode","traceId","durationMs","timings","phases","total"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/services/http/http.ts"],"sourcesContent":["import { got, type OptionsInit } from \"got\";\nimport ms from \"ms\";\nimport assert from \"node:assert\";\nimport { Agent as HttpAgent } from \"node:http\";\nimport { Agent as HttpsAgent } from \"node:https\";\nimport { Context } from \"../command/context.js\";\nimport { config } from \"../config/config.js\";\nimport { sprint } from \"../output/sprint.js\";\nimport { writeSession } from \"../user/session.js\";\nimport { serializeError } from \"../util/object.js\";\nimport { isGadgetServicesRequest } from \"./auth.js\";\n\nexport type HttpOptions = OptionsInit;\n\nconst getContext = (options: HttpOptions): Context => {\n assert(\n options.context?.[\"ctx\"] instanceof Context,\n sprint(`\n ctx must be provided to http requests:\n\n const response = await http({\n context: { ctx },\n ...options,\n });\n `),\n );\n\n return options.context[\"ctx\"] as Context;\n};\n\n/**\n * An instance of the `got` library with hooks for logging and handling\n * 401 errors. This should be used for all HTTP requests.\n */\nexport const http = got.extend({\n agent: {\n http: new HttpAgent({ keepAlive: true }),\n https: new HttpsAgent({ keepAlive: true }),\n },\n retry: {\n limit: 10,\n methods: [\"GET\", \"PUT\", \"HEAD\", \"DELETE\", \"OPTIONS\", \"TRACE\"],\n statusCodes: [408, 413, 429, 500, 502, 503, 504, 521, 522, 524],\n errorCodes: [\n \"ETIMEDOUT\",\n \"ECONNRESET\",\n \"EADDRINUSE\",\n \"ECONNREFUSED\",\n \"EPIPE\",\n \"ENOTFOUND\",\n \"ENETUNREACH\",\n \"EAI_AGAIN\",\n \"EADDRNOTAVAIL\",\n \"EHOSTUNREACH\",\n ],\n maxRetryAfter: undefined,\n calculateDelay: ({ computedValue }) => computedValue,\n backoffLimit: ms(\"5s\"),\n noise: 100,\n },\n hooks: {\n beforeRequest: [\n (options) => {\n const ctx = getContext(options);\n options.signal = ctx.signal;\n options.headers[\"user-agent\"] = config.versionFull;\n ctx.log.debug(\"http request\", {\n http: {\n request: {\n method: options.method,\n url: options.url?.toString(),\n },\n },\n });\n },\n ],\n beforeRetry: [\n (error, retryCount) => {\n const ctx = getContext(error.request?.options ?? error.options.context);\n\n ctx.log.warn(\"http request failed, retrying...\", {\n http: {\n retryCount,\n error: serializeError(error),\n request: error.request && {\n method: error.request.options.method,\n url: error.request.options.url?.toString(),\n },\n response: error.response && {\n statusCode: error.response.statusCode,\n traceId: error.response.headers[\"x-trace-id\"],\n durationMs: error.response.timings.phases.total,\n },\n },\n });\n },\n ],\n afterResponse: [\n (response) => {\n const ctx = getContext(response.request.options);\n ctx.log.debug(\"http response\", {\n http: {\n request: {\n method: response.request.options.method,\n url: response.request.options.url?.toString(),\n },\n response: {\n statusCode: response.statusCode,\n traceId: response.headers[\"x-trace-id\"],\n durationMs: response.timings.phases.total,\n },\n },\n });\n\n if (response.statusCode === 401 && isGadgetServicesRequest(response.request.options)) {\n // clear the session if the request was unauthorized\n writeSession(undefined);\n }\n\n return response;\n },\n ],\n },\n});\n"],"names":["got","ms","assert","Agent","HttpAgent","HttpsAgent","Context","config","sprint","writeSession","serializeError","isGadgetServicesRequest","getContext","options","context","http","extend","agent","keepAlive","https","retry","limit","methods","statusCodes","errorCodes","maxRetryAfter","undefined","calculateDelay","computedValue","backoffLimit","noise","hooks","beforeRequest","ctx","signal","headers","versionFull","log","debug","request","method","url","toString","beforeRetry","error","retryCount","warn","response","statusCode","traceId","durationMs","timings","phases","total","afterResponse"],"mappings":"AAAA,SAASA,GAAG,QAA0B,MAAM;AAC5C,OAAOC,QAAQ,KAAK;AACpB,OAAOC,YAAY,cAAc;AACjC,SAASC,SAASC,SAAS,QAAQ,YAAY;AAC/C,SAASD,SAASE,UAAU,QAAQ,aAAa;AACjD,SAASC,OAAO,QAAQ,wBAAwB;AAChD,SAASC,MAAM,QAAQ,sBAAsB;AAC7C,SAASC,MAAM,QAAQ,sBAAsB;AAC7C,SAASC,YAAY,QAAQ,qBAAqB;AAClD,SAASC,cAAc,QAAQ,oBAAoB;AACnD,SAASC,uBAAuB,QAAQ,YAAY;AAIpD,MAAMC,aAAa,CAACC;IAClBX,OACEW,QAAQC,OAAO,EAAE,CAAC,MAAM,YAAYR,SACpCE,OAAO,CAAC;;;;;;;IAOR,CAAC;IAGH,OAAOK,QAAQC,OAAO,CAAC,MAAM;AAC/B;AAEA;;;CAGC,GACD,OAAO,MAAMC,OAAOf,IAAIgB,MAAM,CAAC;IAC7BC,OAAO;QACLF,MAAM,IAAIX,UAAU;YAAEc,WAAW;QAAK;QACtCC,OAAO,IAAId,WAAW;YAAEa,WAAW;QAAK;IAC1C;IACAE,OAAO;QACLC,OAAO;QACPC,SAAS;YAAC;YAAO;YAAO;YAAQ;YAAU;YAAW;SAAQ;QAC7DC,aAAa;YAAC;YAAK;YAAK;YAAK;YAAK;YAAK;YAAK;YAAK;YAAK;YAAK;SAAI;QAC/DC,YAAY;YACV;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,eAAeC;QACfC,gBAAgB,CAAC,EAAEC,aAAa,EAAE,GAAKA;QACvCC,cAAc5B,GAAG;QACjB6B,OAAO;IACT;IACAC,OAAO;QACLC,eAAe;YACb,CAACnB;gBACC,MAAMoB,MAAMrB,WAAWC;gBACvBA,QAAQqB,MAAM,GAAGD,IAAIC,MAAM;gBAC3BrB,QAAQsB,OAAO,CAAC,aAAa,GAAG5B,OAAO6B,WAAW;gBAClDH,IAAII,GAAG,CAACC,KAAK,CAAC,gBAAgB;oBAC5BvB,MAAM;wBACJwB,SAAS;4BACPC,QAAQ3B,QAAQ2B,MAAM;4BACtBC,KAAK5B,QAAQ4B,GAAG,EAAEC;wBACpB;oBACF;gBACF;YACF;SACD;QACDC,aAAa;YACX,CAACC,OAAOC;gBACN,MAAMZ,MAAMrB,WAAWgC,MAAML,OAAO,EAAE1B,WAAW+B,MAAM/B,OAAO,CAACC,OAAO;gBAEtEmB,IAAII,GAAG,CAACS,IAAI,CAAC,oCAAoC;oBAC/C/B,MAAM;wBACJ8B;wBACAD,OAAOlC,eAAekC;wBACtBL,SAASK,MAAML,OAAO,IAAI;4BACxBC,QAAQI,MAAML,OAAO,CAAC1B,OAAO,CAAC2B,MAAM;4BACpCC,KAAKG,MAAML,OAAO,CAAC1B,OAAO,CAAC4B,GAAG,EAAEC;wBAClC;wBACAK,UAAUH,MAAMG,QAAQ,IAAI;4BAC1BC,YAAYJ,MAAMG,QAAQ,CAACC,UAAU;4BACrCC,SAASL,MAAMG,QAAQ,CAACZ,OAAO,CAAC,aAAa;4BAC7Ce,YAAYN,MAAMG,QAAQ,CAACI,OAAO,CAACC,MAAM,CAACC,KAAK;wBACjD;oBACF;gBACF;YACF;SACD;QACDC,eAAe;YACb,CAACP;gBACC,MAAMd,MAAMrB,WAAWmC,SAASR,OAAO,CAAC1B,OAAO;gBAC/CoB,IAAII,GAAG,CAACC,KAAK,CAAC,iBAAiB;oBAC7BvB,MAAM;wBACJwB,SAAS;4BACPC,QAAQO,SAASR,OAAO,CAAC1B,OAAO,CAAC2B,MAAM;4BACvCC,KAAKM,SAASR,OAAO,CAAC1B,OAAO,CAAC4B,GAAG,EAAEC;wBACrC;wBACAK,UAAU;4BACRC,YAAYD,SAASC,UAAU;4BAC/BC,SAASF,SAASZ,OAAO,CAAC,aAAa;4BACvCe,YAAYH,SAASI,OAAO,CAACC,MAAM,CAACC,KAAK;wBAC3C;oBACF;gBACF;gBAEA,IAAIN,SAASC,UAAU,KAAK,OAAOrC,wBAAwBoC,SAASR,OAAO,CAAC1B,OAAO,GAAG;oBACpF,oDAAoD;oBACpDJ,aAAaiB;gBACf;gBAEA,OAAOqB;YACT;SACD;IACH;AACF,GAAG"}