gt 2.22.4 → 2.23.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 (127) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/auth/errors.d.ts +1 -0
  3. package/dist/auth/errors.js +12 -2
  4. package/dist/auth/errors.js.map +1 -1
  5. package/dist/auth/interactiveLogin.d.ts +2 -0
  6. package/dist/auth/interactiveLogin.js +22 -0
  7. package/dist/auth/interactiveLogin.js.map +1 -0
  8. package/dist/auth/oauth.js +2 -2
  9. package/dist/auth/oauth.js.map +1 -1
  10. package/dist/cli/base.d.ts +22 -4
  11. package/dist/cli/base.js +328 -223
  12. package/dist/cli/base.js.map +1 -1
  13. package/dist/cli/commands/api.js +15 -16
  14. package/dist/cli/commands/api.js.map +1 -1
  15. package/dist/cli/node.js +1 -1
  16. package/dist/cli/node.js.map +1 -1
  17. package/dist/cli/react.js +1 -1
  18. package/dist/cli/react.js.map +1 -1
  19. package/dist/cli/vue.js +2 -2
  20. package/dist/cli/vue.js.map +1 -1
  21. package/dist/config/generateSettings.d.ts +2 -0
  22. package/dist/config/generateSettings.js +13 -6
  23. package/dist/config/generateSettings.js.map +1 -1
  24. package/dist/config/resolveConfig.js +0 -4
  25. package/dist/config/resolveConfig.js.map +1 -1
  26. package/dist/config/validateSettings.js +5 -4
  27. package/dist/config/validateSettings.js.map +1 -1
  28. package/dist/console/{inkLocaleData.d.ts → localeOptions.d.ts} +7 -2
  29. package/dist/console/{inkLocaleData.js → localeOptions.js} +3 -8
  30. package/dist/console/localeOptions.js.map +1 -0
  31. package/dist/console/logger.d.ts +7 -0
  32. package/dist/console/logger.js +48 -85
  33. package/dist/console/logger.js.map +1 -1
  34. package/dist/console/logging.d.ts +11 -1
  35. package/dist/console/logging.js +88 -100
  36. package/dist/console/logging.js.map +1 -1
  37. package/dist/formats/files/aggregateFiles.js +1 -1
  38. package/dist/formats/files/supportedFiles.d.ts +3 -0
  39. package/dist/formats/files/supportedFiles.js +10 -1
  40. package/dist/formats/files/supportedFiles.js.map +1 -1
  41. package/dist/formats/yaml/mergeYaml.js +1 -1
  42. package/dist/fs/config/loadConfig.d.ts +2 -0
  43. package/dist/fs/config/loadConfig.js +5 -1
  44. package/dist/fs/config/loadConfig.js.map +1 -1
  45. package/dist/fs/config/setupConfig.d.ts +18 -9
  46. package/dist/fs/config/setupConfig.js +51 -31
  47. package/dist/fs/config/setupConfig.js.map +1 -1
  48. package/dist/fs/createLoadTranslationsFile.d.ts +22 -1
  49. package/dist/fs/createLoadTranslationsFile.js +43 -20
  50. package/dist/fs/createLoadTranslationsFile.js.map +1 -1
  51. package/dist/generated/version.d.ts +1 -1
  52. package/dist/generated/version.js +1 -1
  53. package/dist/generated/version.js.map +1 -1
  54. package/dist/hooks/postProcess.d.ts +1 -2
  55. package/dist/hooks/postProcess.js +1 -1
  56. package/dist/hooks/postProcess.js.map +1 -1
  57. package/dist/locadex/setupFlow.d.ts +4 -1
  58. package/dist/locadex/setupFlow.js +5 -3
  59. package/dist/locadex/setupFlow.js.map +1 -1
  60. package/dist/react/jsx/utils/jsxParsing/addGTIdentifierToSyntaxTree.js +1 -1
  61. package/dist/react/jsx/utils/jsxParsing/parseJsx.js +1 -1
  62. package/dist/react/jsx/utils/parseString.js +1 -1
  63. package/dist/react/jsx/utils/stringParsing/derivation/index.js +1 -1
  64. package/dist/setup/buildTools/index.d.ts +44 -0
  65. package/dist/setup/buildTools/index.js +10 -0
  66. package/dist/setup/buildTools/index.js.map +1 -0
  67. package/dist/setup/buildTools/vite.d.ts +2 -0
  68. package/dist/setup/buildTools/vite.js +61 -0
  69. package/dist/setup/buildTools/vite.js.map +1 -0
  70. package/dist/setup/developmentCredentials.d.ts +40 -5
  71. package/dist/setup/developmentCredentials.js +119 -37
  72. package/dist/setup/developmentCredentials.js.map +1 -1
  73. package/dist/setup/instructions/base.md +1 -1
  74. package/dist/setup/onboarding.d.ts +131 -0
  75. package/dist/setup/onboarding.js +289 -0
  76. package/dist/setup/onboarding.js.map +1 -0
  77. package/dist/setup/setupTarget.d.ts +1 -0
  78. package/dist/setup/setupTarget.js +52 -0
  79. package/dist/setup/setupTarget.js.map +1 -0
  80. package/dist/setup/setupViteSPA.d.ts +32 -1
  81. package/dist/setup/setupViteSPA.js +110 -20
  82. package/dist/setup/setupViteSPA.js.map +1 -1
  83. package/dist/setup/userInput.d.ts +12 -3
  84. package/dist/setup/userInput.js +27 -11
  85. package/dist/setup/userInput.js.map +1 -1
  86. package/dist/setup/wizard.d.ts +19 -3
  87. package/dist/setup/wizard.js +119 -105
  88. package/dist/setup/wizard.js.map +1 -1
  89. package/dist/types/index.d.ts +2 -1
  90. package/dist/types/index.js +13 -1
  91. package/dist/types/index.js.map +1 -0
  92. package/dist/utils/credentials.d.ts +9 -6
  93. package/dist/utils/credentials.js +36 -22
  94. package/dist/utils/credentials.js.map +1 -1
  95. package/dist/utils/monorepoVersionCheck.d.ts +1 -1
  96. package/dist/utils/monorepoVersionCheck.js +5 -5
  97. package/dist/utils/monorepoVersionCheck.js.map +1 -1
  98. package/dist/utils/packageManager.js +13 -14
  99. package/dist/utils/packageManager.js.map +1 -1
  100. package/package.json +5 -9
  101. package/dist/console/inkFields.d.ts +0 -23
  102. package/dist/console/inkFields.js +0 -88
  103. package/dist/console/inkFields.js.map +0 -1
  104. package/dist/console/inkLayout.d.ts +0 -7
  105. package/dist/console/inkLayout.js +0 -140
  106. package/dist/console/inkLayout.js.map +0 -1
  107. package/dist/console/inkLocaleData.js.map +0 -1
  108. package/dist/console/inkPrompts.d.ts +0 -8
  109. package/dist/console/inkPrompts.js +0 -499
  110. package/dist/console/inkPrompts.js.map +0 -1
  111. package/dist/console/inkSession.d.ts +0 -3
  112. package/dist/console/inkSession.js +0 -42
  113. package/dist/console/inkSession.js.map +0 -1
  114. package/dist/console/inkTerminal.d.ts +0 -4
  115. package/dist/console/inkTerminal.js +0 -15
  116. package/dist/console/inkTerminal.js.map +0 -1
  117. package/dist/console/inkTheme.d.ts +0 -1
  118. package/dist/console/inkTheme.js +0 -6
  119. package/dist/console/inkTheme.js.map +0 -1
  120. package/dist/console/inkTypes.d.ts +0 -69
  121. package/dist/console/inkTypes.js +0 -1
  122. package/dist/console/inkUtils.d.ts +0 -24
  123. package/dist/console/inkUtils.js +0 -87
  124. package/dist/console/inkUtils.js.map +0 -1
  125. package/dist/console/terminalSession.d.ts +0 -5
  126. package/dist/console/terminalSession.js +0 -38
  127. package/dist/console/terminalSession.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.23.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#2338](https://github.com/generaltranslation/gt/pull/2338) [`680c595`](https://github.com/generaltranslation/gt/commit/680c5951efb93177f7c87e8df6809d7a624a3268) Thanks [@chenxin-yan](https://github.com/chenxin-yan)! - `gt init` and `gt configure` accept a flag for every setup question, so scripts and agents can run the same onboarding as the interactive wizard. Flags prefill answers; interactive runs only ask what is left, and `--no-interactive` (automatic without a terminal) lists missing options before changing anything. `--defaults` accepts the recommended local choices without creating projects or keys, `--json` writes sign-in, handoff and result events to stdout, and noninteractive sign-in uses a device code. Setup validates the configuration it is about to write, signs in only when it creates a project or key (before changing files), replaces the configured locale list and file formats with explicit selections, keeps an explicit CDN choice on reruns, stops on an invalid `gt.config.json`, and reports completed steps when a later step fails. Argument errors under `--json` also produce a JSON result.
8
+
9
+ Reruns that change `--translations-dir` update a generated Vite loader, or report a manual update for a custom loader (or when `gt init` skips the React setup), and match the framework-specific project ID to its development key. Setup now runs in a standalone app whose `pnpm-workspace.yaml` lists only the app itself, while still stopping at monorepo roots. Organization discovery is treated as access, not guaranteed project-creation permission, and denied creation reports how to obtain the required permission. Device sign-in no longer repeats the code when it is included in the URL.
10
+
11
+ Package-manager selection honors `--package-manager`, then `package.json#packageManager`, then an unambiguous lockfile, asking only when detection fails. Explicit overrides and separate projects no longer inherit an earlier process-wide selection. Next.js App Router setup writes and recognizes `NEXT_PUBLIC_GT_PROJECT_ID` and `NEXT_PUBLIC_GT_DEV_API_KEY` so development translation works in both server and client components without a tooling `GT_API_KEY`. Development keys must be omitted from production builds.
12
+
13
+ The CLI no longer reads `src/gt.config.json`. Move it to the project root, or pass `--config src/gt.config.json`. The monorepo version check and `gt login` now read the config given with `--config`.
14
+
15
+ ### Patch Changes
16
+
17
+ - [#2352](https://github.com/generaltranslation/gt/pull/2352) [`d76457e`](https://github.com/generaltranslation/gt/commit/d76457e4d9fc055cab9b46a35c50968b6093e956) Thanks [@chenxin-yan](https://github.com/chenxin-yan)! - When `gt configure --storage cdn` leaves more than one `loadTranslations` file in a Next.js app, the manual action now names all of them. gt-next falls back to the next file when one is deleted, so deleting only the first one kept translations loading locally.
18
+
19
+ - [#2344](https://github.com/generaltranslation/gt/pull/2344) [`1c6abfb`](https://github.com/generaltranslation/gt/commit/1c6abfb87494e66fc1ea87989040bd2b1ef8edf6) Thanks [@chenxin-yan](https://github.com/chenxin-yan)! - Replace the custom Ink prompt UI with Clack, preserving searchable locale selection, custom aliases, defaults and cancellation. Keep an unmatched single-locale search open until a valid option is selected, focus the best match as the search changes, accept suggested text defaults on Enter, and remove the CLI's Ink/React rendering dependencies.
20
+
21
+ - [#2353](https://github.com/generaltranslation/gt/pull/2353) [`458398d`](https://github.com/generaltranslation/gt/commit/458398de6b89cb7124a838581cc5660aac25574b) Thanks [@chenxin-yan](https://github.com/chenxin-yan)! - Read listed projects and organizations from the `items` field that the API now returns, so `gt init` can list them again.
22
+
23
+ - [#2345](https://github.com/generaltranslation/gt/pull/2345) [`80cb896`](https://github.com/generaltranslation/gt/commit/80cb896a3cc032456dcbf491a7dea5fe290b12c2) Thanks [@chenxin-yan](https://github.com/chenxin-yan)! - Make setup config updates replace selected locales, preserve unrelated fields, and fail instead of overwriting invalid config. Check credential-file content before changing ignore rules. Refresh translation loaders matching the previous config only after setup's prompts finish. Preserve custom code (including static import-path edits) and loaders of unknown ownership with manual-review guidance, recreate missing locale files on unchanged-loader reruns, and propagate file-write failures. Vite setup preserves app-owned bootstraps, including namespace imports and entries its parser cannot read, for manual review and supports default or named custom loader exports instead of generating broken imports. Setup no longer creates an empty translation file for the default locale.
24
+
25
+ - Updated dependencies [[`458398d`](https://github.com/generaltranslation/gt/commit/458398de6b89cb7124a838581cc5660aac25574b)]:
26
+ - generaltranslation@9.5.2
27
+ - @generaltranslation/python-extractor@0.2.56
28
+ - @generaltranslation/supported-locales@2.1.37
29
+ - @generaltranslation/vue-extractor@0.1.16
30
+
3
31
  ## 2.22.4
4
32
 
5
33
  ### Patch Changes
@@ -5,3 +5,4 @@ export declare class UserAuthError extends Error {
5
5
  constructor(code: UserAuthErrorCode, whatHappened: string, fix?: string, details?: string);
6
6
  }
7
7
  export declare function loginRequiredError(): UserAuthError;
8
+ export declare function createUserAuthError(whatHappened: string, error: unknown): string;
@@ -1,4 +1,4 @@
1
- import { createDiagnosticMessage } from "generaltranslation/diagnostics";
1
+ import { createDiagnosticMessage, formatDiagnosticErrorDetails } from "generaltranslation/diagnostics";
2
2
  //#region src/auth/errors.ts
3
3
  /** `message` is a complete `gt Error:` diagnostic; commands print it as-is. */
4
4
  var UserAuthError = class extends Error {
@@ -17,7 +17,17 @@ var UserAuthError = class extends Error {
17
17
  function loginRequiredError() {
18
18
  return new UserAuthError("login_required", "You are not signed in", "Run `gt login` to sign in");
19
19
  }
20
+ function createUserAuthError(whatHappened, error) {
21
+ if (error instanceof UserAuthError) return error.message;
22
+ return createDiagnosticMessage({
23
+ source: "gt",
24
+ severity: "Error",
25
+ whatHappened,
26
+ details: formatDiagnosticErrorDetails(error),
27
+ fix: "Run `gt login` and try again"
28
+ });
29
+ }
20
30
  //#endregion
21
- export { UserAuthError, loginRequiredError };
31
+ export { UserAuthError, createUserAuthError, loginRequiredError };
22
32
 
23
33
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../../src/auth/errors.ts"],"sourcesContent":["import { createDiagnosticMessage } from 'generaltranslation/diagnostics';\n\nexport type UserAuthErrorCode =\n | 'login_required'\n | 'obsolete_credentials'\n | 'resource_mismatch'\n | 'oauth';\n\n/** `message` is a complete `gt Error:` diagnostic; commands print it as-is. */\nexport class UserAuthError extends Error {\n constructor(\n readonly code: UserAuthErrorCode,\n whatHappened: string,\n fix?: string,\n details?: string\n ) {\n super(\n createDiagnosticMessage({\n source: 'gt',\n severity: 'Error',\n whatHappened,\n fix,\n details,\n })\n );\n this.name = 'UserAuthError';\n }\n}\n\nexport function loginRequiredError(): UserAuthError {\n return new UserAuthError(\n 'login_required',\n 'You are not signed in',\n 'Run `gt login` to sign in'\n );\n}\n"],"mappings":";;;AASA,IAAa,gBAAb,cAAmC,MAAM;CACvC,YACE,MACA,cACA,KACA,SACA;AACA,QACE,wBAAwB;GACtB,QAAQ;GACR,UAAU;GACV;GACA;GACA;GACD,CAAC,CACH;AAbQ,OAAA,OAAA;AAcT,OAAK,OAAO;;;AAIhB,SAAgB,qBAAoC;AAClD,QAAO,IAAI,cACT,kBACA,yBACA,4BACD"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../../src/auth/errors.ts"],"sourcesContent":["import {\n createDiagnosticMessage,\n formatDiagnosticErrorDetails,\n} from 'generaltranslation/diagnostics';\n\nexport type UserAuthErrorCode =\n | 'login_required'\n | 'obsolete_credentials'\n | 'resource_mismatch'\n | 'oauth';\n\n/** `message` is a complete `gt Error:` diagnostic; commands print it as-is. */\nexport class UserAuthError extends Error {\n constructor(\n readonly code: UserAuthErrorCode,\n whatHappened: string,\n fix?: string,\n details?: string\n ) {\n super(\n createDiagnosticMessage({\n source: 'gt',\n severity: 'Error',\n whatHappened,\n fix,\n details,\n })\n );\n this.name = 'UserAuthError';\n }\n}\n\nexport function loginRequiredError(): UserAuthError {\n return new UserAuthError(\n 'login_required',\n 'You are not signed in',\n 'Run `gt login` to sign in'\n );\n}\n\nexport function createUserAuthError(\n whatHappened: string,\n error: unknown\n): string {\n if (error instanceof UserAuthError) return error.message;\n return createDiagnosticMessage({\n source: 'gt',\n severity: 'Error',\n whatHappened,\n details: formatDiagnosticErrorDetails(error),\n fix: 'Run `gt login` and try again',\n });\n}\n"],"mappings":";;;AAYA,IAAa,gBAAb,cAAmC,MAAM;CACvC,YACE,MACA,cACA,KACA,SACA;AACA,QACE,wBAAwB;GACtB,QAAQ;GACR,UAAU;GACV;GACA;GACA;GACD,CAAC,CACH;AAbQ,OAAA,OAAA;AAcT,OAAK,OAAO;;;AAIhB,SAAgB,qBAAoC;AAClD,QAAO,IAAI,cACT,kBACA,yBACA,4BACD;;AAGH,SAAgB,oBACd,cACA,OACQ;AACR,KAAI,iBAAiB,cAAe,QAAO,MAAM;AACjD,QAAO,wBAAwB;EAC7B,QAAQ;EACR,UAAU;EACV;EACA,SAAS,6BAA6B,MAAM;EAC5C,KAAK;EACN,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { type DeviceCode } from './oauth.js';
2
+ export declare function loginInteractively(baseUrl: string | undefined, useBrowser?: boolean, onDeviceCode?: (deviceCode: DeviceCode) => void): Promise<void>;
@@ -0,0 +1,22 @@
1
+ import { logger } from "../console/logger.js";
2
+ import { login } from "./oauth.js";
3
+ import chalk from "chalk";
4
+ //#region src/auth/interactiveLogin.ts
5
+ async function loginInteractively(baseUrl, useBrowser = true, onDeviceCode) {
6
+ await login({
7
+ baseUrl,
8
+ noBrowser: !useBrowser,
9
+ onDeviceCode: (deviceCode) => {
10
+ const { userCode, verificationUri, verificationUriComplete } = deviceCode;
11
+ onDeviceCode?.(deviceCode);
12
+ logger.message(`Visit:\n\n${chalk.cyan(verificationUriComplete ?? verificationUri)}\n\n${verificationUriComplete ? "" : `Then enter the code ${chalk.bold(userCode)}.\n`}Waiting for authentication...`);
13
+ },
14
+ onAuthorizationUrl: (url) => {
15
+ logger.message(`Opening your browser to sign in. If it does not open, visit:\n${chalk.cyan(url)}`);
16
+ }
17
+ });
18
+ }
19
+ //#endregion
20
+ export { loginInteractively };
21
+
22
+ //# sourceMappingURL=interactiveLogin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactiveLogin.js","names":[],"sources":["../../src/auth/interactiveLogin.ts"],"sourcesContent":["import chalk from 'chalk';\nimport { logger } from '../console/logger.js';\nimport { login, type DeviceCode } from './oauth.js';\n\nexport async function loginInteractively(\n baseUrl: string | undefined,\n useBrowser = true,\n onDeviceCode?: (deviceCode: DeviceCode) => void\n): Promise<void> {\n await login({\n baseUrl,\n noBrowser: !useBrowser,\n onDeviceCode: (deviceCode) => {\n const { userCode, verificationUri, verificationUriComplete } = deviceCode;\n onDeviceCode?.(deviceCode);\n logger.message(\n `Visit:\\n\\n${chalk.cyan(verificationUriComplete ?? verificationUri)}\\n\\n${verificationUriComplete ? '' : `Then enter the code ${chalk.bold(userCode)}.\\n`}Waiting for authentication...`\n );\n },\n onAuthorizationUrl: (url) => {\n logger.message(\n `Opening your browser to sign in. If it does not open, visit:\\n${chalk.cyan(url)}`\n );\n },\n });\n}\n"],"mappings":";;;;AAIA,eAAsB,mBACpB,SACA,aAAa,MACb,cACe;AACf,OAAM,MAAM;EACV;EACA,WAAW,CAAC;EACZ,eAAe,eAAe;GAC5B,MAAM,EAAE,UAAU,iBAAiB,4BAA4B;AAC/D,kBAAe,WAAW;AAC1B,UAAO,QACL,aAAa,MAAM,KAAK,2BAA2B,gBAAgB,CAAC,MAAM,0BAA0B,KAAK,uBAAuB,MAAM,KAAK,SAAS,CAAC,KAAK,+BAC3J;;EAEH,qBAAqB,QAAQ;AAC3B,UAAO,QACL,iEAAiE,MAAM,KAAK,IAAI,GACjF;;EAEJ,CAAC"}
@@ -3,8 +3,8 @@ import "../utils/constants.js";
3
3
  import { UserAuthError, loginRequiredError } from "./errors.js";
4
4
  import { deleteOAuthTokens, readOAuthTokens, writeOAuthTokens } from "./credentialStore.js";
5
5
  import { startLoopbackServer } from "./loopback.js";
6
- import { createDiagnosticMessage } from "generaltranslation/diagnostics";
7
6
  import { defaultBaseUrl } from "generaltranslation/internal";
7
+ import { createDiagnosticMessage as createDiagnosticMessage$1 } from "generaltranslation/diagnostics";
8
8
  import open from "open";
9
9
  import * as oidc from "openid-client";
10
10
  //#region src/auth/oauth.ts
@@ -232,7 +232,7 @@ async function logout(options = {}) {
232
232
  const tokens = await readOAuthTokens(authBaseUrl);
233
233
  if (tokens?.refreshToken) await oidc.tokenRevocation(await configuration(options), tokens.refreshToken, { token_type_hint: "refresh_token" });
234
234
  } catch {
235
- logger.warn(createDiagnosticMessage({
235
+ logger.warn(createDiagnosticMessage$1({
236
236
  source: "gt",
237
237
  severity: "Warning",
238
238
  whatHappened: "Signed out locally, but remote session revocation could not be confirmed",
@@ -1 +1 @@
1
- {"version":3,"file":"oauth.js","names":[],"sources":["../../src/auth/oauth.ts"],"sourcesContent":["import open from 'open';\nimport * as oidc from 'openid-client';\nimport type { UserTokenProvider } from 'generaltranslation/api';\nimport { createDiagnosticMessage } from 'generaltranslation/diagnostics';\nimport { defaultBaseUrl } from 'generaltranslation/internal';\nimport { GT_DASHBOARD_URL } from '../utils/constants.js';\nimport { logger } from '../console/logger.js';\nimport {\n deleteOAuthTokens,\n readOAuthTokens,\n writeOAuthTokens,\n} from './credentialStore.js';\nimport type { OAuthTokens } from './credentialStore.js';\nimport { UserAuthError, loginRequiredError } from './errors.js';\nimport { startLoopbackServer } from './loopback.js';\n\n/** Public native client seeded by the authorization server; never a secret. */\nconst OAUTH_CLIENT_ID = 'gt-cli';\n/** Explicit on both grants: identity, refresh and the user's dashboard permissions.\n * Must match the seeded gt-cli client; the device endpoint does not default scope.\n */\nconst OAUTH_SCOPE = 'openid profile offline_access gt:*';\n// Refresh before expiry so an in-flight request does not carry an expired token.\nconst TOKEN_REFRESH_BUFFER_MS = 30_000;\n\nexport type DeviceCode = {\n userCode: string;\n verificationUri: string;\n verificationUriComplete?: string;\n};\nexport type UserInfo = { email?: string; name?: string; sub: string };\ntype OAuthRequestOptions = { authBaseUrl?: string; fetch?: typeof fetch };\nexport type UserTokenProviderOptions = OAuthRequestOptions & {\n /** API the requests go to; must match the resource the login was issued for. */\n baseUrl: string;\n};\nexport type LoginOptions = OAuthRequestOptions & {\n /** API the token is issued for; GT_API_URL overrides, then the public API. */\n baseUrl?: string;\n noBrowser?: boolean;\n onDeviceCode?: (deviceCode: DeviceCode) => void;\n signal?: AbortSignal;\n onAuthorizationUrl?: (url: string) => void;\n openBrowser?: (url: string) => Promise<unknown>;\n timeoutMs?: number;\n};\n\nfunction oauthFailure(\n whatHappened: string,\n fix?: string,\n details?: string\n): UserAuthError {\n return new UserAuthError('oauth', whatHappened, fix, details);\n}\n\nfunction oauthError(error: unknown, fallback: string): UserAuthError {\n if (\n error instanceof oidc.AuthorizationResponseError ||\n error instanceof oidc.ResponseBodyError\n ) {\n const descriptions: Record<string, string> = {\n access_denied: 'Sign in was denied in the browser',\n expired_token: 'The sign-in code expired before it was approved',\n invalid_scope: 'The authorization server rejected the requested scopes',\n invalid_grant: 'Your login or sign-in code expired or was already used',\n invalid_client: 'This authorization server does not recognize the gt CLI',\n unauthorized_client:\n 'This authorization server does not recognize the gt CLI',\n };\n return oauthFailure(\n descriptions[error.error] ?? fallback,\n ['invalid_client', 'unauthorized_client'].includes(error.error)\n ? 'Check GT_AUTH_URL or update the server'\n : 'Run `gt login` again',\n // Scope descriptions are useful configuration diagnostics, not token data.\n error.error === 'invalid_scope' ? error.error_description : undefined\n );\n }\n if (error instanceof oidc.ClientError) {\n const status =\n error.cause instanceof Response ? ` (HTTP ${error.cause.status})` : '';\n return oauthFailure(\n `${fallback}${status}`,\n 'Check the authorization server and try again',\n error.code\n );\n }\n if (\n error instanceof Error &&\n ['AbortError', 'TimeoutError'].includes(error.name)\n ) {\n return oauthFailure(\n 'Sign in was cancelled or timed out',\n 'Run `gt login` again'\n );\n }\n // Do not expose transport causes, callback URLs or token responses.\n return oauthFailure(fallback, 'Check the authorization server and try again');\n}\n\nfunction assertEndpoint(url: URL, issuer: URL): void {\n if (\n url.username ||\n url.password ||\n url.hash ||\n (url.protocol !== 'https:' &&\n !(issuer.protocol === 'http:' && url.origin === issuer.origin))\n ) {\n throw oauthFailure(\n 'The authorization server supplied an unsafe endpoint',\n 'Use HTTPS, or same-origin local development HTTP'\n );\n }\n}\n\nasync function configuration(\n {\n authBaseUrl = getAuthBaseUrl(),\n fetch: fetchImplementation = globalThis.fetch,\n }: OAuthRequestOptions,\n signal?: AbortSignal\n): Promise<oidc.Configuration> {\n const issuer = new URL(authBaseUrl);\n const local =\n issuer.hostname === 'localhost' ||\n issuer.hostname.endsWith('.localhost') ||\n issuer.hostname === '127.0.0.1' ||\n issuer.hostname === '[::1]';\n if (\n issuer.username ||\n issuer.password ||\n issuer.search ||\n issuer.hash ||\n (issuer.protocol !== 'https:' && !(issuer.protocol === 'http:' && local))\n ) {\n throw oauthFailure(\n 'GT_AUTH_URL is not a trusted issuer URL',\n 'Use HTTPS, or a local development HTTP issuer without credentials, query or fragment'\n );\n }\n return oidc.discovery(issuer, OAUTH_CLIENT_ID, undefined, oidc.None(), {\n execute:\n issuer.protocol === 'http:'\n ? [oidc.enableNonRepudiationChecks, oidc.allowInsecureRequests]\n : [oidc.enableNonRepudiationChecks],\n [oidc.customFetch]: (url, init) => {\n assertEndpoint(new URL(url), issuer);\n return fetchImplementation(url, {\n ...init,\n // Include operation cancellation in JWKS fetches as well as token requests.\n signal: signal\n ? AbortSignal.any([signal, ...(init.signal ? [init.signal] : [])])\n : init.signal,\n body:\n init.body instanceof Uint8Array\n ? new Uint8Array(init.body)\n : init.body,\n });\n },\n });\n}\n\nfunction parseTokens(\n result: oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers,\n previous?: OAuthTokens\n): Omit<OAuthTokens, 'resource'> {\n const subject = result.claims()?.sub ?? previous?.subject;\n if (!subject || (previous && subject !== previous.subject)) {\n throw oauthFailure(\n 'The authorization server did not confirm the expected account identity',\n 'Run `gt login` again'\n );\n }\n if (\n result.token_type !== 'bearer' ||\n typeof result.expires_in !== 'number' ||\n !Number.isFinite(result.expires_in) ||\n result.expires_in <= 0\n ) {\n throw oauthFailure(\n 'The authorization server returned an unsupported token type or lifetime'\n );\n }\n return {\n accessToken: result.access_token,\n expiresAt: Date.now() + result.expires_in * 1000,\n refreshToken: result.refresh_token ?? previous?.refreshToken ?? '',\n scope: result.scope ?? previous?.scope ?? OAUTH_SCOPE,\n tokenType: result.token_type,\n subject,\n };\n}\n\nfunction getAuthBaseUrl(): string {\n return (process.env.GT_AUTH_URL ?? `${GT_DASHBOARD_URL}/api/auth`).replace(\n /\\/$/,\n ''\n );\n}\n\n/** Resource indicators are the API origin serialized as a URL href (trailing slash). */\nfunction toApiResource(baseUrl: string): string {\n return new URL(baseUrl).href;\n}\n\nfunction loginResource(options: LoginOptions): string {\n return toApiResource(\n process.env.GT_API_URL ?? options.baseUrl ?? defaultBaseUrl\n );\n}\n\nasync function loginWithDeviceCode(\n options: LoginOptions\n): Promise<OAuthTokens> {\n if (options.noBrowser && !options.onDeviceCode) {\n throw oauthFailure(\n 'Device login needs a way to display the verification code',\n 'Provide onDeviceCode when using noBrowser'\n );\n }\n const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();\n const resource = loginResource(options);\n const lifetime = new AbortController();\n const signal = options.signal\n ? AbortSignal.any([options.signal, lifetime.signal])\n : lifetime.signal;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let result: oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers;\n try {\n signal.throwIfAborted();\n const config = await configuration({ ...options, authBaseUrl }, signal);\n const started = Date.now();\n const response = await oidc.initiateDeviceAuthorization(config, {\n scope: OAUTH_SCOPE,\n resource,\n });\n const deadline = started + response.expires_in * 1000;\n const checkLifetime = () => {\n if (Date.now() >= deadline)\n lifetime.abort(new DOMException('Device code expired', 'TimeoutError'));\n signal.throwIfAborted();\n };\n checkLifetime();\n timer = setTimeout(\n () =>\n lifetime.abort(new DOMException('Device code expired', 'TimeoutError')),\n deadline - Date.now()\n );\n assertEndpoint(new URL(response.verification_uri), new URL(authBaseUrl));\n if (response.verification_uri_complete)\n assertEndpoint(\n new URL(response.verification_uri_complete),\n new URL(authBaseUrl)\n );\n options.onDeviceCode?.({\n userCode: response.user_code,\n verificationUri: response.verification_uri,\n verificationUriComplete: response.verification_uri_complete,\n });\n checkLifetime();\n if (!options.noBrowser) {\n void (options.openBrowser ?? open)(\n response.verification_uri_complete ?? response.verification_uri\n ).catch(() => undefined);\n }\n result = await oidc.pollDeviceAuthorizationGrant(\n config,\n response,\n { resource },\n { signal }\n );\n checkLifetime();\n } catch (error) {\n throw oauthError(error, 'Could not complete device sign in');\n } finally {\n clearTimeout(timer);\n }\n const tokens = { ...parseTokens(result), resource };\n await writeOAuthTokens(tokens, authBaseUrl);\n return tokens;\n}\n\n/** Browser S256/loopback, or device login for SSH, --no-browser and bind failure. */\nexport async function login(options: LoginOptions = {}): Promise<OAuthTokens> {\n if (\n options.noBrowser ||\n process.env.SSH_CONNECTION ||\n process.env.SSH_CLIENT ||\n process.env.SSH_TTY\n ) {\n return loginWithDeviceCode({ ...options, noBrowser: true });\n }\n const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();\n const resource = loginResource(options);\n const config = await configuration({ ...options, authBaseUrl }).catch(\n (error: unknown) => {\n throw oauthError(error, 'Could not discover the authorization server');\n }\n );\n const loopback = await startLoopbackServer().catch(() => undefined);\n if (!loopback) return loginWithDeviceCode(options);\n try {\n const codeVerifier = oidc.randomPKCECodeVerifier();\n const state = oidc.randomState();\n const authorizationUrl = oidc.buildAuthorizationUrl(config, {\n redirect_uri: loopback.redirectUri,\n scope: OAUTH_SCOPE,\n resource,\n state,\n code_challenge: await oidc.calculatePKCECodeChallenge(codeVerifier),\n code_challenge_method: 'S256',\n });\n assertEndpoint(authorizationUrl, new URL(authBaseUrl));\n const callback = loopback.waitForCallback(async (callbackUrl) => {\n const result = await oidc\n .authorizationCodeGrant(\n config,\n callbackUrl,\n {\n pkceCodeVerifier: codeVerifier,\n expectedState: state,\n idTokenExpected: true,\n },\n { resource }\n )\n .catch((error: unknown) => {\n throw oauthError(error, 'Failed to authenticate via web browser');\n });\n const tokens = { ...parseTokens(result), resource };\n await writeOAuthTokens(tokens, authBaseUrl);\n return tokens;\n }, options.timeoutMs);\n // Printing/launching may fail before we await the listener.\n callback.catch(() => undefined);\n options.onAuthorizationUrl?.(authorizationUrl.href);\n void (options.openBrowser ?? open)(authorizationUrl.href).catch(\n () => undefined\n );\n return await callback;\n } finally {\n loopback.close();\n }\n}\n\nconst pendingRefreshes = new Map<string, Promise<OAuthTokens>>();\nfunction refreshTokens(options: OAuthRequestOptions): Promise<OAuthTokens> {\n const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();\n const pending = pendingRefreshes.get(authBaseUrl);\n if (pending) return pending;\n const refresh = exchangeRefreshToken({ ...options, authBaseUrl }).finally(\n () => pendingRefreshes.delete(authBaseUrl)\n );\n pendingRefreshes.set(authBaseUrl, refresh);\n return refresh;\n}\n\nasync function exchangeRefreshToken(\n options: OAuthRequestOptions & { authBaseUrl: string }\n): Promise<OAuthTokens> {\n const current = await readOAuthTokens(options.authBaseUrl);\n if (!current?.refreshToken) throw loginRequiredError();\n const result = await (async () =>\n oidc.refreshTokenGrant(\n await configuration(options),\n current.refreshToken\n ))().catch((error: unknown) => {\n throw oauthError(error, 'Could not refresh your login');\n });\n // The grant reuses the bound resource, so the refreshed token keeps its audience.\n const tokens = {\n ...parseTokens(result, current),\n resource: current.resource,\n };\n await writeOAuthTokens(tokens, options.authBaseUrl);\n return tokens;\n}\n\nasync function validTokens(\n options: OAuthRequestOptions\n): Promise<OAuthTokens | undefined> {\n const tokens = await readOAuthTokens(options.authBaseUrl ?? getAuthBaseUrl());\n if (!tokens || tokens.expiresAt > Date.now() + TOKEN_REFRESH_BUFFER_MS)\n return tokens;\n return refreshTokens(options);\n}\n\n/** Whether a stored login exists for this API; does not refresh or contact the server. */\nexport async function hasLogin(\n options: UserTokenProviderOptions\n): Promise<boolean> {\n const tokens = await readOAuthTokens(options.authBaseUrl ?? getAuthBaseUrl());\n return tokens?.resource === toApiResource(options.baseUrl);\n}\n\n/** Construction is deliberately free of auth/storage/discovery I/O. */\nexport function createUserTokenProvider(\n options: UserTokenProviderOptions\n): UserTokenProvider {\n // Check the audience before any refresh so a stale mismatched login is\n // never rotated on behalf of the wrong API.\n const storedTokens = async (): Promise<OAuthTokens> => {\n const tokens = await readOAuthTokens(\n options.authBaseUrl ?? getAuthBaseUrl()\n );\n if (!tokens) throw loginRequiredError();\n const resource = toApiResource(options.baseUrl);\n if (tokens.resource !== resource) {\n throw new UserAuthError(\n 'resource_mismatch',\n `You are signed in to ${tokens.resource}, but this project uses ${resource}`,\n 'Run `gt login` in this project to sign in to its API'\n );\n }\n return tokens;\n };\n return {\n getAccessToken: async () => {\n const tokens = await storedTokens();\n if (tokens.expiresAt > Date.now() + TOKEN_REFRESH_BUFFER_MS)\n return tokens.accessToken;\n return (await refreshTokens(options)).accessToken;\n },\n refreshAccessToken: async () => {\n await storedTokens();\n return (await refreshTokens(options)).accessToken;\n },\n };\n}\n\nexport async function logout(options: OAuthRequestOptions = {}): Promise<void> {\n const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();\n try {\n const tokens = await readOAuthTokens(authBaseUrl);\n if (tokens?.refreshToken)\n await oidc.tokenRevocation(\n await configuration(options),\n tokens.refreshToken,\n { token_type_hint: 'refresh_token' }\n );\n } catch {\n logger.warn(\n createDiagnosticMessage({\n source: 'gt',\n severity: 'Warning',\n whatHappened:\n 'Signed out locally, but remote session revocation could not be confirmed',\n fix: 'Revoke the session in the dashboard if needed',\n })\n );\n } finally {\n await deleteOAuthTokens(authBaseUrl);\n }\n}\n\nexport async function whoAmI(\n options: OAuthRequestOptions = {}\n): Promise<UserInfo> {\n const tokens = await validTokens(options);\n if (!tokens) throw loginRequiredError();\n try {\n const result = await oidc.fetchUserInfo(\n await configuration(options),\n tokens.accessToken,\n tokens.subject\n );\n return { sub: result.sub, email: result.email, name: result.name };\n } catch (error) {\n throw oauthError(error, 'Could not validate your account identity');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"oauth.js","names":["createDiagnosticMessage"],"sources":["../../src/auth/oauth.ts"],"sourcesContent":["import open from 'open';\nimport * as oidc from 'openid-client';\nimport type { UserTokenProvider } from 'generaltranslation/api';\nimport { createDiagnosticMessage } from 'generaltranslation/diagnostics';\nimport { defaultBaseUrl } from 'generaltranslation/internal';\nimport { GT_DASHBOARD_URL } from '../utils/constants.js';\nimport { logger } from '../console/logger.js';\nimport {\n deleteOAuthTokens,\n readOAuthTokens,\n writeOAuthTokens,\n} from './credentialStore.js';\nimport type { OAuthTokens } from './credentialStore.js';\nimport { UserAuthError, loginRequiredError } from './errors.js';\nimport { startLoopbackServer } from './loopback.js';\n\n/** Public native client seeded by the authorization server; never a secret. */\nconst OAUTH_CLIENT_ID = 'gt-cli';\n/** Explicit on both grants: identity, refresh and the user's dashboard permissions.\n * Must match the seeded gt-cli client; the device endpoint does not default scope.\n */\nconst OAUTH_SCOPE = 'openid profile offline_access gt:*';\n// Refresh before expiry so an in-flight request does not carry an expired token.\nconst TOKEN_REFRESH_BUFFER_MS = 30_000;\n\nexport type DeviceCode = {\n userCode: string;\n verificationUri: string;\n verificationUriComplete?: string;\n};\nexport type UserInfo = { email?: string; name?: string; sub: string };\ntype OAuthRequestOptions = { authBaseUrl?: string; fetch?: typeof fetch };\nexport type UserTokenProviderOptions = OAuthRequestOptions & {\n /** API the requests go to; must match the resource the login was issued for. */\n baseUrl: string;\n};\nexport type LoginOptions = OAuthRequestOptions & {\n /** API the token is issued for; GT_API_URL overrides, then the public API. */\n baseUrl?: string;\n noBrowser?: boolean;\n onDeviceCode?: (deviceCode: DeviceCode) => void;\n signal?: AbortSignal;\n onAuthorizationUrl?: (url: string) => void;\n openBrowser?: (url: string) => Promise<unknown>;\n timeoutMs?: number;\n};\n\nfunction oauthFailure(\n whatHappened: string,\n fix?: string,\n details?: string\n): UserAuthError {\n return new UserAuthError('oauth', whatHappened, fix, details);\n}\n\nfunction oauthError(error: unknown, fallback: string): UserAuthError {\n if (\n error instanceof oidc.AuthorizationResponseError ||\n error instanceof oidc.ResponseBodyError\n ) {\n const descriptions: Record<string, string> = {\n access_denied: 'Sign in was denied in the browser',\n expired_token: 'The sign-in code expired before it was approved',\n invalid_scope: 'The authorization server rejected the requested scopes',\n invalid_grant: 'Your login or sign-in code expired or was already used',\n invalid_client: 'This authorization server does not recognize the gt CLI',\n unauthorized_client:\n 'This authorization server does not recognize the gt CLI',\n };\n return oauthFailure(\n descriptions[error.error] ?? fallback,\n ['invalid_client', 'unauthorized_client'].includes(error.error)\n ? 'Check GT_AUTH_URL or update the server'\n : 'Run `gt login` again',\n // Scope descriptions are useful configuration diagnostics, not token data.\n error.error === 'invalid_scope' ? error.error_description : undefined\n );\n }\n if (error instanceof oidc.ClientError) {\n const status =\n error.cause instanceof Response ? ` (HTTP ${error.cause.status})` : '';\n return oauthFailure(\n `${fallback}${status}`,\n 'Check the authorization server and try again',\n error.code\n );\n }\n if (\n error instanceof Error &&\n ['AbortError', 'TimeoutError'].includes(error.name)\n ) {\n return oauthFailure(\n 'Sign in was cancelled or timed out',\n 'Run `gt login` again'\n );\n }\n // Do not expose transport causes, callback URLs or token responses.\n return oauthFailure(fallback, 'Check the authorization server and try again');\n}\n\nfunction assertEndpoint(url: URL, issuer: URL): void {\n if (\n url.username ||\n url.password ||\n url.hash ||\n (url.protocol !== 'https:' &&\n !(issuer.protocol === 'http:' && url.origin === issuer.origin))\n ) {\n throw oauthFailure(\n 'The authorization server supplied an unsafe endpoint',\n 'Use HTTPS, or same-origin local development HTTP'\n );\n }\n}\n\nasync function configuration(\n {\n authBaseUrl = getAuthBaseUrl(),\n fetch: fetchImplementation = globalThis.fetch,\n }: OAuthRequestOptions,\n signal?: AbortSignal\n): Promise<oidc.Configuration> {\n const issuer = new URL(authBaseUrl);\n const local =\n issuer.hostname === 'localhost' ||\n issuer.hostname.endsWith('.localhost') ||\n issuer.hostname === '127.0.0.1' ||\n issuer.hostname === '[::1]';\n if (\n issuer.username ||\n issuer.password ||\n issuer.search ||\n issuer.hash ||\n (issuer.protocol !== 'https:' && !(issuer.protocol === 'http:' && local))\n ) {\n throw oauthFailure(\n 'GT_AUTH_URL is not a trusted issuer URL',\n 'Use HTTPS, or a local development HTTP issuer without credentials, query or fragment'\n );\n }\n return oidc.discovery(issuer, OAUTH_CLIENT_ID, undefined, oidc.None(), {\n execute:\n issuer.protocol === 'http:'\n ? [oidc.enableNonRepudiationChecks, oidc.allowInsecureRequests]\n : [oidc.enableNonRepudiationChecks],\n [oidc.customFetch]: (url, init) => {\n assertEndpoint(new URL(url), issuer);\n return fetchImplementation(url, {\n ...init,\n // Include operation cancellation in JWKS fetches as well as token requests.\n signal: signal\n ? AbortSignal.any([signal, ...(init.signal ? [init.signal] : [])])\n : init.signal,\n body:\n init.body instanceof Uint8Array\n ? new Uint8Array(init.body)\n : init.body,\n });\n },\n });\n}\n\nfunction parseTokens(\n result: oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers,\n previous?: OAuthTokens\n): Omit<OAuthTokens, 'resource'> {\n const subject = result.claims()?.sub ?? previous?.subject;\n if (!subject || (previous && subject !== previous.subject)) {\n throw oauthFailure(\n 'The authorization server did not confirm the expected account identity',\n 'Run `gt login` again'\n );\n }\n if (\n result.token_type !== 'bearer' ||\n typeof result.expires_in !== 'number' ||\n !Number.isFinite(result.expires_in) ||\n result.expires_in <= 0\n ) {\n throw oauthFailure(\n 'The authorization server returned an unsupported token type or lifetime'\n );\n }\n return {\n accessToken: result.access_token,\n expiresAt: Date.now() + result.expires_in * 1000,\n refreshToken: result.refresh_token ?? previous?.refreshToken ?? '',\n scope: result.scope ?? previous?.scope ?? OAUTH_SCOPE,\n tokenType: result.token_type,\n subject,\n };\n}\n\nfunction getAuthBaseUrl(): string {\n return (process.env.GT_AUTH_URL ?? `${GT_DASHBOARD_URL}/api/auth`).replace(\n /\\/$/,\n ''\n );\n}\n\n/** Resource indicators are the API origin serialized as a URL href (trailing slash). */\nfunction toApiResource(baseUrl: string): string {\n return new URL(baseUrl).href;\n}\n\nfunction loginResource(options: LoginOptions): string {\n return toApiResource(\n process.env.GT_API_URL ?? options.baseUrl ?? defaultBaseUrl\n );\n}\n\nasync function loginWithDeviceCode(\n options: LoginOptions\n): Promise<OAuthTokens> {\n if (options.noBrowser && !options.onDeviceCode) {\n throw oauthFailure(\n 'Device login needs a way to display the verification code',\n 'Provide onDeviceCode when using noBrowser'\n );\n }\n const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();\n const resource = loginResource(options);\n const lifetime = new AbortController();\n const signal = options.signal\n ? AbortSignal.any([options.signal, lifetime.signal])\n : lifetime.signal;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let result: oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers;\n try {\n signal.throwIfAborted();\n const config = await configuration({ ...options, authBaseUrl }, signal);\n const started = Date.now();\n const response = await oidc.initiateDeviceAuthorization(config, {\n scope: OAUTH_SCOPE,\n resource,\n });\n const deadline = started + response.expires_in * 1000;\n const checkLifetime = () => {\n if (Date.now() >= deadline)\n lifetime.abort(new DOMException('Device code expired', 'TimeoutError'));\n signal.throwIfAborted();\n };\n checkLifetime();\n timer = setTimeout(\n () =>\n lifetime.abort(new DOMException('Device code expired', 'TimeoutError')),\n deadline - Date.now()\n );\n assertEndpoint(new URL(response.verification_uri), new URL(authBaseUrl));\n if (response.verification_uri_complete)\n assertEndpoint(\n new URL(response.verification_uri_complete),\n new URL(authBaseUrl)\n );\n options.onDeviceCode?.({\n userCode: response.user_code,\n verificationUri: response.verification_uri,\n verificationUriComplete: response.verification_uri_complete,\n });\n checkLifetime();\n if (!options.noBrowser) {\n void (options.openBrowser ?? open)(\n response.verification_uri_complete ?? response.verification_uri\n ).catch(() => undefined);\n }\n result = await oidc.pollDeviceAuthorizationGrant(\n config,\n response,\n { resource },\n { signal }\n );\n checkLifetime();\n } catch (error) {\n throw oauthError(error, 'Could not complete device sign in');\n } finally {\n clearTimeout(timer);\n }\n const tokens = { ...parseTokens(result), resource };\n await writeOAuthTokens(tokens, authBaseUrl);\n return tokens;\n}\n\n/** Browser S256/loopback, or device login for SSH, --no-browser and bind failure. */\nexport async function login(options: LoginOptions = {}): Promise<OAuthTokens> {\n if (\n options.noBrowser ||\n process.env.SSH_CONNECTION ||\n process.env.SSH_CLIENT ||\n process.env.SSH_TTY\n ) {\n return loginWithDeviceCode({ ...options, noBrowser: true });\n }\n const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();\n const resource = loginResource(options);\n const config = await configuration({ ...options, authBaseUrl }).catch(\n (error: unknown) => {\n throw oauthError(error, 'Could not discover the authorization server');\n }\n );\n const loopback = await startLoopbackServer().catch(() => undefined);\n if (!loopback) return loginWithDeviceCode(options);\n try {\n const codeVerifier = oidc.randomPKCECodeVerifier();\n const state = oidc.randomState();\n const authorizationUrl = oidc.buildAuthorizationUrl(config, {\n redirect_uri: loopback.redirectUri,\n scope: OAUTH_SCOPE,\n resource,\n state,\n code_challenge: await oidc.calculatePKCECodeChallenge(codeVerifier),\n code_challenge_method: 'S256',\n });\n assertEndpoint(authorizationUrl, new URL(authBaseUrl));\n const callback = loopback.waitForCallback(async (callbackUrl) => {\n const result = await oidc\n .authorizationCodeGrant(\n config,\n callbackUrl,\n {\n pkceCodeVerifier: codeVerifier,\n expectedState: state,\n idTokenExpected: true,\n },\n { resource }\n )\n .catch((error: unknown) => {\n throw oauthError(error, 'Failed to authenticate via web browser');\n });\n const tokens = { ...parseTokens(result), resource };\n await writeOAuthTokens(tokens, authBaseUrl);\n return tokens;\n }, options.timeoutMs);\n // Printing/launching may fail before we await the listener.\n callback.catch(() => undefined);\n options.onAuthorizationUrl?.(authorizationUrl.href);\n void (options.openBrowser ?? open)(authorizationUrl.href).catch(\n () => undefined\n );\n return await callback;\n } finally {\n loopback.close();\n }\n}\n\nconst pendingRefreshes = new Map<string, Promise<OAuthTokens>>();\nfunction refreshTokens(options: OAuthRequestOptions): Promise<OAuthTokens> {\n const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();\n const pending = pendingRefreshes.get(authBaseUrl);\n if (pending) return pending;\n const refresh = exchangeRefreshToken({ ...options, authBaseUrl }).finally(\n () => pendingRefreshes.delete(authBaseUrl)\n );\n pendingRefreshes.set(authBaseUrl, refresh);\n return refresh;\n}\n\nasync function exchangeRefreshToken(\n options: OAuthRequestOptions & { authBaseUrl: string }\n): Promise<OAuthTokens> {\n const current = await readOAuthTokens(options.authBaseUrl);\n if (!current?.refreshToken) throw loginRequiredError();\n const result = await (async () =>\n oidc.refreshTokenGrant(\n await configuration(options),\n current.refreshToken\n ))().catch((error: unknown) => {\n throw oauthError(error, 'Could not refresh your login');\n });\n // The grant reuses the bound resource, so the refreshed token keeps its audience.\n const tokens = {\n ...parseTokens(result, current),\n resource: current.resource,\n };\n await writeOAuthTokens(tokens, options.authBaseUrl);\n return tokens;\n}\n\nasync function validTokens(\n options: OAuthRequestOptions\n): Promise<OAuthTokens | undefined> {\n const tokens = await readOAuthTokens(options.authBaseUrl ?? getAuthBaseUrl());\n if (!tokens || tokens.expiresAt > Date.now() + TOKEN_REFRESH_BUFFER_MS)\n return tokens;\n return refreshTokens(options);\n}\n\n/** Whether a stored login exists for this API; does not refresh or contact the server. */\nexport async function hasLogin(\n options: UserTokenProviderOptions\n): Promise<boolean> {\n const tokens = await readOAuthTokens(options.authBaseUrl ?? getAuthBaseUrl());\n return tokens?.resource === toApiResource(options.baseUrl);\n}\n\n/** Construction is deliberately free of auth/storage/discovery I/O. */\nexport function createUserTokenProvider(\n options: UserTokenProviderOptions\n): UserTokenProvider {\n // Check the audience before any refresh so a stale mismatched login is\n // never rotated on behalf of the wrong API.\n const storedTokens = async (): Promise<OAuthTokens> => {\n const tokens = await readOAuthTokens(\n options.authBaseUrl ?? getAuthBaseUrl()\n );\n if (!tokens) throw loginRequiredError();\n const resource = toApiResource(options.baseUrl);\n if (tokens.resource !== resource) {\n throw new UserAuthError(\n 'resource_mismatch',\n `You are signed in to ${tokens.resource}, but this project uses ${resource}`,\n 'Run `gt login` in this project to sign in to its API'\n );\n }\n return tokens;\n };\n return {\n getAccessToken: async () => {\n const tokens = await storedTokens();\n if (tokens.expiresAt > Date.now() + TOKEN_REFRESH_BUFFER_MS)\n return tokens.accessToken;\n return (await refreshTokens(options)).accessToken;\n },\n refreshAccessToken: async () => {\n await storedTokens();\n return (await refreshTokens(options)).accessToken;\n },\n };\n}\n\nexport async function logout(options: OAuthRequestOptions = {}): Promise<void> {\n const authBaseUrl = options.authBaseUrl ?? getAuthBaseUrl();\n try {\n const tokens = await readOAuthTokens(authBaseUrl);\n if (tokens?.refreshToken)\n await oidc.tokenRevocation(\n await configuration(options),\n tokens.refreshToken,\n { token_type_hint: 'refresh_token' }\n );\n } catch {\n logger.warn(\n createDiagnosticMessage({\n source: 'gt',\n severity: 'Warning',\n whatHappened:\n 'Signed out locally, but remote session revocation could not be confirmed',\n fix: 'Revoke the session in the dashboard if needed',\n })\n );\n } finally {\n await deleteOAuthTokens(authBaseUrl);\n }\n}\n\nexport async function whoAmI(\n options: OAuthRequestOptions = {}\n): Promise<UserInfo> {\n const tokens = await validTokens(options);\n if (!tokens) throw loginRequiredError();\n try {\n const result = await oidc.fetchUserInfo(\n await configuration(options),\n tokens.accessToken,\n tokens.subject\n );\n return { sub: result.sub, email: result.email, name: result.name };\n } catch (error) {\n throw oauthError(error, 'Could not validate your account identity');\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  import { Command } from 'commander';
2
- import { Settings, SupportedFrameworks, SupportedLibraries, SetupOptions, TranslateFlags } from '../types/index.js';
2
+ import { Settings, SupportedFrameworks, SupportedLibraries, TranslateFlags } from '../types/index.js';
3
+ import { type ReactSetupPlan } from '../setup/wizard.js';
4
+ import { type ConfigureOptions, type InitOptions, type OnboardingSession } from '../setup/onboarding.js';
3
5
  export type UploadOptions = {
4
6
  config?: string;
5
7
  apiKey?: string;
@@ -68,8 +70,9 @@ export declare class BaseCLI {
68
70
  protected setupUploadCommand(): void;
69
71
  protected setupUserAuthCommands(): void;
70
72
  protected setupInitCommand(): void;
73
+ protected handleInitWizard(options: InitOptions): Promise<void>;
71
74
  protected setupConfigureCommand(): void;
72
- protected handleConfigureCommand(options?: SetupOptions): Promise<void>;
75
+ protected handleConfigureCommand(options: ConfigureOptions, command?: string): Promise<void>;
73
76
  protected handleUploadCommand(settings: Settings & UploadOptions): Promise<void>;
74
77
  /** Describes installed runtimes without changing historical package lookup. */
75
78
  protected getInlineRuntimeSetup(packageJson: Record<string, unknown>, ranReactSetup?: boolean): InlineRuntimeSetup;
@@ -78,9 +81,24 @@ export declare class BaseCLI {
78
81
  /** Returns whether every installed runtime can consume CDN translations. */
79
82
  protected supportsCDNStorage(runtimeSetup: InlineRuntimeSetup): boolean;
80
83
  /** Returns whether setup should generate a local runtime loader. */
81
- protected shouldGenerateLocalTranslationLoader(isVite: boolean, runtimeSetup: InlineRuntimeSetup): boolean;
84
+ protected shouldGenerateLocalTranslationLoader(buildToolOwnsLoader: boolean, runtimeSetup: InlineRuntimeSetup): boolean;
82
85
  /** Returns framework guidance after selecting local translation storage. */
83
86
  protected getLocalTranslationGuidance({ generatedLoader, runtimeSetup, translationsDir, }: LocalTranslationGuidanceOptions): string | undefined;
84
- protected handleInitCommand(ranReactSetup: boolean, useDefaults?: boolean, isVite?: boolean, options?: SetupOptions, framework?: SupportedFrameworks): Promise<void>;
87
+ /**
88
+ * Resolves every configuration answer and validates the resulting config,
89
+ * checks the project and signs in before the first change, then applies
90
+ * the React setup, gt.config.json, installs and development credentials.
91
+ */
92
+ protected handleInitCommand(session: OnboardingSession, options: ConfigureOptions, setup: {
93
+ /** Chosen once by the entry point from the detected framework. */
94
+ configFilepath: string;
95
+ /** Resolved framework; drives storage, env names and build-tool setup. */
96
+ framework?: SupportedFrameworks;
97
+ /** Write the framework even when no build-tool setup saves it. */
98
+ saveFramework?: boolean;
99
+ reactSetup?: ReactSetupPlan;
100
+ /** Init without the React setup never changes application source. */
101
+ keepAppSource?: boolean;
102
+ }): Promise<void>;
85
103
  }
86
104
  export {};