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
@@ -1,50 +1,117 @@
1
1
  import { logger } from "../console/logger.js";
2
- import { logErrorAndExit, promptSelect, promptText } from "../console/logging.js";
2
+ import { promptConfirm, promptSelect, promptText } from "../console/logging.js";
3
+ import { createUserAuthError } from "../auth/errors.js";
3
4
  import { api } from "../utils/api.js";
4
- import { inspectCredentialsEnvFile, setCredentials } from "../utils/credentials.js";
5
+ import { setCredentials } from "../utils/credentials.js";
6
+ import { loginInteractively } from "../auth/interactiveLogin.js";
7
+ import { OnboardingError } from "./onboarding.js";
5
8
  import chalk from "chalk";
6
9
  import path from "node:path";
7
- import { createDiagnosticMessage } from "generaltranslation/diagnostics";
10
+ import { createDiagnosticMessage, formatDiagnosticErrorDetails } from "generaltranslation/diagnostics";
8
11
  import { ProjectApiKeyPermission } from "generaltranslation/api";
12
+ import { ApiError } from "generaltranslation/errors";
9
13
  //#region src/setup/developmentCredentials.ts
10
14
  const DEVELOPMENT_KEY_NAME = "Development key (gt init)";
11
- function noCreatableOrgError(dashboardUrl) {
15
+ /** .env.local never reaches production; the runtime key there is set on the host. */
16
+ function productionRuntimeKeyGuidance(dashboardUrl) {
17
+ return `${chalk.dim("For runtime translation in production, create an API key in the dashboard")} ${chalk.cyan(dashboardUrl)} ${chalk.dim("and set GT_API_KEY and GT_PROJECT_ID in your hosting environment.")}`;
18
+ }
19
+ /**
20
+ * Interactive setup keeps the browser login with its device fallback;
21
+ * noninteractive setup always shows a device code (and emits it as a JSON
22
+ * event) and waits for a person to approve it, without opening a browser.
23
+ */
24
+ async function signInForSetup(session, baseUrl) {
25
+ try {
26
+ await loginInteractively(baseUrl, session.interactive, (deviceCode) => session.emit({
27
+ type: "authorization_required",
28
+ ...deviceCode
29
+ }));
30
+ } catch (error) {
31
+ throw new OnboardingError(createUserAuthError("Sign in failed", error));
32
+ }
33
+ logger.message("You are now signed in.");
34
+ }
35
+ function noAccessibleOrgError(dashboardUrl) {
12
36
  return createDiagnosticMessage({
13
37
  source: "gt",
14
38
  severity: "Error",
15
- whatHappened: "You are not a member of an organization that can create projects",
39
+ whatHappened: "No accessible organizations were found",
16
40
  fix: `Create an organization in the dashboard ${dashboardUrl} or ask an admin for access, then rerun the setup wizard`
17
41
  });
18
42
  }
19
- async function createProject(settings, cwd) {
20
- const orgs = await api.listOrgs();
21
- if (orgs.length === 0) return logErrorAndExit(noCreatableOrgError(settings.dashboardUrl));
22
- const orgId = orgs.length === 1 ? orgs[0].id : await promptSelect({
23
- message: "Which organization should own the new project?",
24
- options: orgs.map((org) => ({
25
- value: org.id,
26
- label: org.name
27
- }))
43
+ function projectCreationDeniedError(orgId, error) {
44
+ return createDiagnosticMessage({
45
+ source: "gt",
46
+ severity: "Error",
47
+ whatHappened: `Project creation was denied for organization ${orgId}`,
48
+ why: "Listing an organization does not confirm permission to create projects in it",
49
+ fix: "Ask an organization admin for org:projects:create, or use credentials that have that permission for this organization",
50
+ details: formatDiagnosticErrorDetails(error)
28
51
  });
29
- const name = await promptText({
30
- message: "What should the project be called?",
31
- defaultValue: path.basename(cwd),
32
- validate: (value) => value.trim().length > 0 || "Enter a project name"
52
+ }
53
+ const noProjectChosenError = createDiagnosticMessage({
54
+ source: "gt",
55
+ severity: "Error",
56
+ whatHappened: "No project was chosen for the development credentials",
57
+ reassurance: "No project files were changed",
58
+ fix: "Rerun with --project-id <id> or --create-project, or skip them with --no-dev-credentials"
59
+ });
60
+ async function resolveNewProject(session, settings, options, cwd) {
61
+ const orgs = await api.listOrgs();
62
+ if (orgs.length === 0) throw new OnboardingError(noAccessibleOrgError(settings.dashboardUrl));
63
+ if (options.orgId && !orgs.some((org) => org.id === options.orgId)) session.reject(`--org-id ${options.orgId} is not an accessible organization`);
64
+ const orgId = await session.answer("--org-id", {
65
+ explicit: options.orgId,
66
+ configured: orgs.length === 1 ? orgs[0].id : void 0,
67
+ ask: () => promptSelect({
68
+ message: "Which organization should own the new project?",
69
+ options: orgs.map((org) => ({
70
+ value: org.id,
71
+ label: org.name
72
+ }))
73
+ })
33
74
  });
34
- const { project } = await api.createProject(orgId, {
35
- name: name.trim() || path.basename(cwd),
36
- defaultLocale: settings.defaultLocale
75
+ const name = await session.answer("--project-name", {
76
+ explicit: options.projectName?.trim() || void 0,
77
+ ask: async () => (await promptText({
78
+ message: "What should the project be called?",
79
+ defaultValue: path.basename(cwd)
80
+ })).trim() || path.basename(cwd)
37
81
  });
38
- logger.info(`Created ${project.name} (${project.id})`);
39
- return project.id;
82
+ return orgId && name ? { create: {
83
+ orgId,
84
+ name
85
+ } } : void 0;
86
+ }
87
+ /**
88
+ * Records project inputs a noninteractive run is missing, before signing in,
89
+ * so it never waits for approval only to fail on a missing flag.
90
+ */
91
+ function checkDevelopmentProjectInputs(session, settings, options, projectIdSource) {
92
+ if (settings.projectId && options.createProject) session.reject(`--create-project cannot be combined with project ${settings.projectId} from ${projectIdSource}; remove --create-project to keep using it, or remove the project ID from ${projectIdSource} to create a new project`);
93
+ if (settings.projectId || session.interactive) return;
94
+ if (!options.createProject) session.require("--project-id or --create-project");
95
+ else if (!options.projectName?.trim()) session.require("--project-name");
40
96
  }
41
- async function selectProject(settings, cwd) {
97
+ /**
98
+ * Chooses the project for development credentials without creating anything:
99
+ * a supplied or configured ID, explicit creation inputs, or (interactively)
100
+ * a selection among the account's projects.
101
+ */
102
+ async function resolveDevelopmentProject(session, settings, options, cwd = process.cwd()) {
103
+ if (settings.projectId) return { id: settings.projectId };
104
+ if (options.createProject) return resolveNewProject(session, settings, options, cwd);
42
105
  const projects = await api.listProjects();
43
106
  if (projects.length === 0) {
44
- logger.info("No projects found for your account; creating one.");
45
- return createProject(settings, cwd);
107
+ const create = await session.answer("--project-id or --create-project", { ask: () => promptConfirm({
108
+ message: "No projects found for your account. Create a new project?",
109
+ defaultValue: true
110
+ }) });
111
+ if (create === false) throw new OnboardingError(noProjectChosenError);
112
+ return create ? resolveNewProject(session, settings, options, cwd) : void 0;
46
113
  }
47
- const choice = await promptSelect({
114
+ const choice = await session.answer("--project-id or --create-project", { ask: () => promptSelect({
48
115
  message: "Which project should this app use?",
49
116
  options: [...projects.map((project) => ({
50
117
  value: project,
@@ -54,29 +121,44 @@ async function selectProject(settings, cwd) {
54
121
  value: null,
55
122
  label: "Create a new project"
56
123
  }]
57
- });
58
- return choice ? choice.id : createProject(settings, cwd);
124
+ }) });
125
+ if (choice === void 0) return void 0;
126
+ return choice ? { id: choice.id } : resolveNewProject(session, settings, options, cwd);
59
127
  }
60
128
  /**
61
- * Picks or creates the project, mints a development key limited to runtime
62
- * translation, and saves both to .env.local. The file is checked before any
63
- * key exists, every service call completes before anything is written, and
64
- * the key is never printed.
129
+ * Creates the chosen project if needed, mints a development key limited to
130
+ * runtime translation, and saves both to .env.local. Callers inspect
131
+ * .env.local first; the key is never printed.
65
132
  */
66
- async function provisionDevelopmentCredentials(settings, framework, cwd = process.cwd()) {
67
- await inspectCredentialsEnvFile(cwd);
68
- const projectId = settings.projectId || await selectProject(settings, cwd);
133
+ async function provisionDevelopmentCredentials(session, project, settings, framework, cwd = process.cwd()) {
134
+ let projectId;
135
+ if ("id" in project) projectId = project.id;
136
+ else {
137
+ const { orgId, name } = project.create;
138
+ const { project: created } = await api.createProject(orgId, {
139
+ name,
140
+ defaultLocale: settings.defaultLocale
141
+ }).catch((error) => {
142
+ if (!(error instanceof ApiError) || error.code !== 403) throw error;
143
+ throw new OnboardingError(projectCreationDeniedError(orgId, error));
144
+ });
145
+ logger.info(`Created ${created.name} (${created.id})`);
146
+ session.step(`created project ${created.id}`);
147
+ projectId = created.id;
148
+ }
69
149
  const { apiKey } = await api.createProjectApiKey(projectId, {
70
150
  name: DEVELOPMENT_KEY_NAME,
71
151
  permissions: [ProjectApiKeyPermission["PROJECT:TRANSLATIONS:GENERATE"]]
72
152
  });
153
+ session.step("created a development key");
73
154
  await setCredentials({
74
155
  projectId,
75
156
  apiKey: apiKey.key
76
157
  }, framework, cwd);
158
+ session.step("saved development credentials to .env.local");
77
159
  logger.success(`Saved the project ID and a development key to ${chalk.cyan(".env.local")}.`);
78
160
  }
79
161
  //#endregion
80
- export { provisionDevelopmentCredentials };
162
+ export { checkDevelopmentProjectInputs, productionRuntimeKeyGuidance, provisionDevelopmentCredentials, resolveDevelopmentProject, signInForSetup };
81
163
 
82
164
  //# sourceMappingURL=developmentCredentials.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"developmentCredentials.js","names":[],"sources":["../../src/setup/developmentCredentials.ts"],"sourcesContent":["import path from 'node:path';\nimport chalk from 'chalk';\nimport { ProjectApiKeyPermission } from 'generaltranslation/api';\nimport { createDiagnosticMessage } from 'generaltranslation/diagnostics';\nimport { logger } from '../console/logger.js';\nimport {\n logErrorAndExit,\n promptSelect,\n promptText,\n} from '../console/logging.js';\nimport type { Settings, SupportedFrameworks } from '../types/index.js';\nimport { api } from '../utils/api.js';\nimport {\n inspectCredentialsEnvFile,\n setCredentials,\n} from '../utils/credentials.js';\n\nconst DEVELOPMENT_KEY_NAME = 'Development key (gt init)';\n\ntype ProjectChoice = Awaited<ReturnType<typeof api.listProjects>>[number];\n\nfunction noCreatableOrgError(dashboardUrl: string): string {\n return createDiagnosticMessage({\n source: 'gt',\n severity: 'Error',\n whatHappened:\n 'You are not a member of an organization that can create projects',\n fix: `Create an organization in the dashboard ${dashboardUrl} or ask an admin for access, then rerun the setup wizard`,\n });\n}\n\nasync function createProject(settings: Settings, cwd: string) {\n // Organizations are only needed to create; picking an existing project\n // must work for members without create access.\n const orgs = await api.listOrgs();\n if (orgs.length === 0) {\n return logErrorAndExit(noCreatableOrgError(settings.dashboardUrl));\n }\n const orgId =\n orgs.length === 1\n ? orgs[0].id\n : await promptSelect({\n message: 'Which organization should own the new project?',\n options: orgs.map((org) => ({ value: org.id, label: org.name })),\n });\n const name = await promptText({\n message: 'What should the project be called?',\n defaultValue: path.basename(cwd),\n validate: (value) => value.trim().length > 0 || 'Enter a project name',\n });\n const { project } = await api.createProject(orgId, {\n name: name.trim() || path.basename(cwd),\n defaultLocale: settings.defaultLocale,\n });\n logger.info(`Created ${project.name} (${project.id})`);\n return project.id;\n}\n\nasync function selectProject(settings: Settings, cwd: string) {\n const projects = await api.listProjects();\n if (projects.length === 0) {\n logger.info('No projects found for your account; creating one.');\n return createProject(settings, cwd);\n }\n const choice = await promptSelect<ProjectChoice | null>({\n message: 'Which project should this app use?',\n options: [\n ...projects.map((project) => ({\n value: project,\n label: project.name,\n hint: project.orgName,\n })),\n { value: null, label: 'Create a new project' },\n ],\n });\n return choice ? choice.id : createProject(settings, cwd);\n}\n\n/**\n * Picks or creates the project, mints a development key limited to runtime\n * translation, and saves both to .env.local. The file is checked before any\n * key exists, every service call completes before anything is written, and\n * the key is never printed.\n */\nexport async function provisionDevelopmentCredentials(\n settings: Settings,\n framework: SupportedFrameworks | undefined,\n cwd: string = process.cwd()\n): Promise<void> {\n await inspectCredentialsEnvFile(cwd);\n const projectId = settings.projectId || (await selectProject(settings, cwd));\n const { apiKey } = await api.createProjectApiKey(projectId, {\n name: DEVELOPMENT_KEY_NAME,\n permissions: [ProjectApiKeyPermission['PROJECT:TRANSLATIONS:GENERATE']],\n });\n await setCredentials({ projectId, apiKey: apiKey.key }, framework, cwd);\n logger.success(\n `Saved the project ID and a development key to ${chalk.cyan('.env.local')}.`\n );\n}\n"],"mappings":";;;;;;;;;AAiBA,MAAM,uBAAuB;AAI7B,SAAS,oBAAoB,cAA8B;AACzD,QAAO,wBAAwB;EAC7B,QAAQ;EACR,UAAU;EACV,cACE;EACF,KAAK,2CAA2C,aAAa;EAC9D,CAAC;;AAGJ,eAAe,cAAc,UAAoB,KAAa;CAG5D,MAAM,OAAO,MAAM,IAAI,UAAU;AACjC,KAAI,KAAK,WAAW,EAClB,QAAO,gBAAgB,oBAAoB,SAAS,aAAa,CAAC;CAEpE,MAAM,QACJ,KAAK,WAAW,IACZ,KAAK,GAAG,KACR,MAAM,aAAa;EACjB,SAAS;EACT,SAAS,KAAK,KAAK,SAAS;GAAE,OAAO,IAAI;GAAI,OAAO,IAAI;GAAM,EAAE;EACjE,CAAC;CACR,MAAM,OAAO,MAAM,WAAW;EAC5B,SAAS;EACT,cAAc,KAAK,SAAS,IAAI;EAChC,WAAW,UAAU,MAAM,MAAM,CAAC,SAAS,KAAK;EACjD,CAAC;CACF,MAAM,EAAE,YAAY,MAAM,IAAI,cAAc,OAAO;EACjD,MAAM,KAAK,MAAM,IAAI,KAAK,SAAS,IAAI;EACvC,eAAe,SAAS;EACzB,CAAC;AACF,QAAO,KAAK,WAAW,QAAQ,KAAK,IAAI,QAAQ,GAAG,GAAG;AACtD,QAAO,QAAQ;;AAGjB,eAAe,cAAc,UAAoB,KAAa;CAC5D,MAAM,WAAW,MAAM,IAAI,cAAc;AACzC,KAAI,SAAS,WAAW,GAAG;AACzB,SAAO,KAAK,oDAAoD;AAChE,SAAO,cAAc,UAAU,IAAI;;CAErC,MAAM,SAAS,MAAM,aAAmC;EACtD,SAAS;EACT,SAAS,CACP,GAAG,SAAS,KAAK,aAAa;GAC5B,OAAO;GACP,OAAO,QAAQ;GACf,MAAM,QAAQ;GACf,EAAE,EACH;GAAE,OAAO;GAAM,OAAO;GAAwB,CAC/C;EACF,CAAC;AACF,QAAO,SAAS,OAAO,KAAK,cAAc,UAAU,IAAI;;;;;;;;AAS1D,eAAsB,gCACpB,UACA,WACA,MAAc,QAAQ,KAAK,EACZ;AACf,OAAM,0BAA0B,IAAI;CACpC,MAAM,YAAY,SAAS,aAAc,MAAM,cAAc,UAAU,IAAI;CAC3E,MAAM,EAAE,WAAW,MAAM,IAAI,oBAAoB,WAAW;EAC1D,MAAM;EACN,aAAa,CAAC,wBAAwB,iCAAiC;EACxE,CAAC;AACF,OAAM,eAAe;EAAE;EAAW,QAAQ,OAAO;EAAK,EAAE,WAAW,IAAI;AACvE,QAAO,QACL,iDAAiD,MAAM,KAAK,aAAa,CAAC,GAC3E"}
1
+ {"version":3,"file":"developmentCredentials.js","names":[],"sources":["../../src/setup/developmentCredentials.ts"],"sourcesContent":["import path from 'node:path';\nimport chalk from 'chalk';\nimport { ProjectApiKeyPermission } from 'generaltranslation/api';\nimport {\n createDiagnosticMessage,\n formatDiagnosticErrorDetails,\n} from 'generaltranslation/diagnostics';\nimport { ApiError } from 'generaltranslation/errors';\nimport { logger } from '../console/logger.js';\nimport { promptConfirm, promptSelect, promptText } from '../console/logging.js';\nimport type { Settings, SupportedFrameworks } from '../types/index.js';\nimport { api } from '../utils/api.js';\nimport { setCredentials } from '../utils/credentials.js';\nimport { createUserAuthError } from '../auth/errors.js';\nimport { loginInteractively } from '../auth/interactiveLogin.js';\nimport { OnboardingError, type OnboardingSession } from './onboarding.js';\n\nconst DEVELOPMENT_KEY_NAME = 'Development key (gt init)';\n\n/** .env.local never reaches production; the runtime key there is set on the host. */\nexport function productionRuntimeKeyGuidance(dashboardUrl: string): string {\n return `${chalk.dim('For runtime translation in production, create an API key in the dashboard')} ${chalk.cyan(dashboardUrl)} ${chalk.dim('and set GT_API_KEY and GT_PROJECT_ID in your hosting environment.')}`;\n}\n\n/**\n * Interactive setup keeps the browser login with its device fallback;\n * noninteractive setup always shows a device code (and emits it as a JSON\n * event) and waits for a person to approve it, without opening a browser.\n */\nexport async function signInForSetup(\n session: OnboardingSession,\n baseUrl: string | undefined\n): Promise<void> {\n try {\n await loginInteractively(baseUrl, session.interactive, (deviceCode) =>\n session.emit({ type: 'authorization_required', ...deviceCode })\n );\n } catch (error) {\n throw new OnboardingError(createUserAuthError('Sign in failed', error));\n }\n logger.message('You are now signed in.');\n}\n\ntype ProjectChoice = Awaited<ReturnType<typeof api.listProjects>>[number];\n\n/** An existing project, or the inputs for creating one. */\nexport type DevelopmentProject =\n | { id: string }\n | { create: { orgId: string; name: string } };\n\nexport type DevelopmentProjectOptions = {\n createProject?: boolean;\n orgId?: string;\n projectName?: string;\n};\n\nfunction noAccessibleOrgError(dashboardUrl: string): string {\n return createDiagnosticMessage({\n source: 'gt',\n severity: 'Error',\n whatHappened: 'No accessible organizations were found',\n fix: `Create an organization in the dashboard ${dashboardUrl} or ask an admin for access, then rerun the setup wizard`,\n });\n}\n\nfunction projectCreationDeniedError(orgId: string, error: unknown): string {\n return createDiagnosticMessage({\n source: 'gt',\n severity: 'Error',\n whatHappened: `Project creation was denied for organization ${orgId}`,\n why: 'Listing an organization does not confirm permission to create projects in it',\n fix: 'Ask an organization admin for org:projects:create, or use credentials that have that permission for this organization',\n details: formatDiagnosticErrorDetails(error),\n });\n}\n\nconst noProjectChosenError = createDiagnosticMessage({\n source: 'gt',\n severity: 'Error',\n whatHappened: 'No project was chosen for the development credentials',\n reassurance: 'No project files were changed',\n fix: 'Rerun with --project-id <id> or --create-project, or skip them with --no-dev-credentials',\n});\n\nasync function resolveNewProject(\n session: OnboardingSession,\n settings: Settings,\n options: DevelopmentProjectOptions,\n cwd: string\n): Promise<DevelopmentProject | undefined> {\n // Discovery lists accessible organizations, not creation permissions.\n // The API enforces creation permission when provisioning the project.\n const orgs = await api.listOrgs();\n if (orgs.length === 0) {\n throw new OnboardingError(noAccessibleOrgError(settings.dashboardUrl));\n }\n if (options.orgId && !orgs.some((org) => org.id === options.orgId)) {\n session.reject(\n `--org-id ${options.orgId} is not an accessible organization`\n );\n }\n const orgId = await session.answer('--org-id', {\n explicit: options.orgId,\n // One organization is not a choice between options.\n configured: orgs.length === 1 ? orgs[0].id : undefined,\n ask: () =>\n promptSelect({\n message: 'Which organization should own the new project?',\n options: orgs.map((org) => ({ value: org.id, label: org.name })),\n }),\n });\n const name = await session.answer('--project-name', {\n explicit: options.projectName?.trim() || undefined,\n ask: async () =>\n (\n await promptText({\n message: 'What should the project be called?',\n defaultValue: path.basename(cwd),\n })\n ).trim() || path.basename(cwd),\n });\n return orgId && name ? { create: { orgId, name } } : undefined;\n}\n\n/**\n * Records project inputs a noninteractive run is missing, before signing in,\n * so it never waits for approval only to fail on a missing flag.\n */\nexport function checkDevelopmentProjectInputs(\n session: OnboardingSession,\n settings: Settings,\n options: DevelopmentProjectOptions,\n /** Where settings.projectId came from, so a person can remove it. */\n projectIdSource: string\n): void {\n if (settings.projectId && options.createProject) {\n session.reject(\n `--create-project cannot be combined with project ${settings.projectId} from ${projectIdSource}; remove --create-project to keep using it, or remove the project ID from ${projectIdSource} to create a new project`\n );\n }\n if (settings.projectId || session.interactive) return;\n if (!options.createProject) {\n session.require('--project-id or --create-project');\n } else if (!options.projectName?.trim()) {\n session.require('--project-name');\n }\n}\n\n/**\n * Chooses the project for development credentials without creating anything:\n * a supplied or configured ID, explicit creation inputs, or (interactively)\n * a selection among the account's projects.\n */\nexport async function resolveDevelopmentProject(\n session: OnboardingSession,\n settings: Settings,\n options: DevelopmentProjectOptions,\n cwd: string = process.cwd()\n): Promise<DevelopmentProject | undefined> {\n if (settings.projectId) return { id: settings.projectId };\n if (options.createProject) {\n return resolveNewProject(session, settings, options, cwd);\n }\n const projects = await api.listProjects();\n if (projects.length === 0) {\n // Creating a project is its own choice, separate from creating a key.\n const create = await session.answer('--project-id or --create-project', {\n ask: () =>\n promptConfirm({\n message: 'No projects found for your account. Create a new project?',\n defaultValue: true,\n }),\n });\n if (create === false) throw new OnboardingError(noProjectChosenError);\n return create\n ? resolveNewProject(session, settings, options, cwd)\n : undefined;\n }\n const choice = await session.answer<ProjectChoice | null>(\n '--project-id or --create-project',\n {\n ask: () =>\n promptSelect<ProjectChoice | null>({\n message: 'Which project should this app use?',\n options: [\n ...projects.map((project) => ({\n value: project,\n label: project.name,\n hint: project.orgName,\n })),\n { value: null, label: 'Create a new project' },\n ],\n }),\n }\n );\n if (choice === undefined) return undefined;\n return choice\n ? { id: choice.id }\n : resolveNewProject(session, settings, options, cwd);\n}\n\n/**\n * Creates the chosen project if needed, mints a development key limited to\n * runtime translation, and saves both to .env.local. Callers inspect\n * .env.local first; the key is never printed.\n */\nexport async function provisionDevelopmentCredentials(\n session: OnboardingSession,\n project: DevelopmentProject,\n settings: Settings,\n framework: SupportedFrameworks | undefined,\n cwd: string = process.cwd()\n): Promise<void> {\n let projectId: string;\n if ('id' in project) {\n projectId = project.id;\n } else {\n const { orgId, name } = project.create;\n const { project: created } = await api\n .createProject(orgId, { name, defaultLocale: settings.defaultLocale })\n .catch((error: unknown) => {\n if (!(error instanceof ApiError) || error.code !== 403) throw error;\n throw new OnboardingError(projectCreationDeniedError(orgId, error));\n });\n logger.info(`Created ${created.name} (${created.id})`);\n session.step(`created project ${created.id}`);\n projectId = created.id;\n }\n const { apiKey } = await api.createProjectApiKey(projectId, {\n name: DEVELOPMENT_KEY_NAME,\n permissions: [ProjectApiKeyPermission['PROJECT:TRANSLATIONS:GENERATE']],\n });\n session.step('created a development key');\n await setCredentials({ projectId, apiKey: apiKey.key }, framework, cwd);\n session.step('saved development credentials to .env.local');\n logger.success(\n `Saved the project ID and a development key to ${chalk.cyan('.env.local')}.`\n );\n}\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ This project is using [General Translation](https://generaltranslation.com/docs/
4
4
 
5
5
  ## Configuration
6
6
 
7
- The General Translation configuration file is called `gt.config.json`. It is usually located in the root or src directory of a project.
7
+ The General Translation configuration file is called `gt.config.json`. It is located in the root of a project; pass `--config` to use a different path.
8
8
 
9
9
  ```json
10
10
  {
@@ -0,0 +1,131 @@
1
+ import { Command } from 'commander';
2
+ import { type FrameworkObject, type SupportedReactFrameworks } from '../types/index.js';
3
+ import { type SetupFileFormat } from '../formats/files/supportedFiles.js';
4
+ import { type PackageManager } from '../utils/packageManager.js';
5
+ /** Answers accepted by `gt configure` (and the gt-vue `gt init`). */
6
+ export type ConfigureOptions = {
7
+ config: string;
8
+ src?: string[];
9
+ interactive?: boolean;
10
+ json?: boolean;
11
+ defaults?: boolean;
12
+ defaultLocale?: string;
13
+ locales?: string[];
14
+ storage?: 'local' | 'cdn';
15
+ translationsDir?: string;
16
+ fileFormats?: Array<SetupFileFormat | 'none'>;
17
+ filePatterns?: string[];
18
+ liveTranslations?: boolean;
19
+ devCredentials?: boolean;
20
+ projectId?: string;
21
+ createProject?: boolean;
22
+ orgId?: string;
23
+ projectName?: string;
24
+ packageManager?: string;
25
+ };
26
+ /** Answers accepted by the React-aware `gt init`. */
27
+ export type InitOptions = ConfigureOptions & {
28
+ locadex?: boolean;
29
+ reactSetup?: boolean;
30
+ framework?: SupportedReactFrameworks;
31
+ format?: boolean;
32
+ };
33
+ /** Flags for every configuration question; values prefill interactive runs. */
34
+ export declare function attachConfigureFlags(command: Command, sourceHelp: string): Command;
35
+ /**
36
+ * Pre-action hooks, such as the monorepo version check, can exit before
37
+ * runOnboarding installs its own result reporting; `--json` callers still
38
+ * get a result. runOnboarding takes over once it starts.
39
+ */
40
+ export declare function reportJsonExitsBeforeOnboarding(command: string): void;
41
+ /** Configuration flags plus the React application setup and Locadex choices. */
42
+ export declare function attachInitFlags(command: Command, sourceHelp: string): Command;
43
+ export type OnboardingEvent = {
44
+ type: 'authorization_required';
45
+ verificationUri: string;
46
+ verificationUriComplete?: string;
47
+ userCode: string;
48
+ } | {
49
+ type: 'handoff';
50
+ url: string;
51
+ reason: 'locadex';
52
+ } | {
53
+ type: 'result';
54
+ command: string;
55
+ outcome: 'success' | 'needs_human_action' | 'failed';
56
+ completedSteps: string[];
57
+ url?: string;
58
+ /** Manual steps setup could not do, for needs_human_action. */
59
+ actions?: string[];
60
+ missingOptions?: string[];
61
+ error?: string;
62
+ };
63
+ /** Needs-input and validation failures, raised before setup changes files. */
64
+ export declare class OnboardingError extends Error {
65
+ readonly missingOptions: string[];
66
+ constructor(message: string, missingOptions?: string[]);
67
+ }
68
+ /**
69
+ * One setup run: how unanswered questions are resolved, which options are
70
+ * still missing, and which steps already changed the project.
71
+ */
72
+ export declare class OnboardingSession {
73
+ readonly command: string;
74
+ readonly interactive: boolean;
75
+ readonly json: boolean;
76
+ defaults: boolean;
77
+ readonly completedSteps: string[];
78
+ /** Manual steps left for a person; they make the outcome needs_human_action. */
79
+ readonly humanActions: string[];
80
+ private readonly missing;
81
+ private readonly invalid;
82
+ private reported;
83
+ constructor(command: string, options: Pick<ConfigureOptions, 'json' | 'interactive'>);
84
+ /**
85
+ * Explicit flag, then existing config, then the recommended value when
86
+ * defaults were accepted, then a prompt. Noninteractive runs use the
87
+ * fallback of an optional question, or record the option as missing.
88
+ */
89
+ answer<T>(option: string, { explicit, configured, recommended, fallback, ask, }: {
90
+ explicit?: T;
91
+ configured?: T;
92
+ recommended?: T;
93
+ fallback?: T;
94
+ ask: () => Promise<T>;
95
+ }): Promise<T | undefined>;
96
+ require(option: string): void;
97
+ reject(problem: string): void;
98
+ /** Stops before any change when an answer is missing or invalid. */
99
+ assertResolved(): void;
100
+ step(name: string): void;
101
+ emit(event: OnboardingEvent): void;
102
+ /** A result for exits that bypass runOnboarding (e.g. settings validation). */
103
+ reportUnexpectedExit(code: number): void;
104
+ }
105
+ export type OnboardingOutcome = {
106
+ outcome: 'success';
107
+ } | {
108
+ outcome: 'needs_human_action';
109
+ url: string;
110
+ };
111
+ /**
112
+ * Runs one setup command: selects prompt and output modes, then reports the
113
+ * outcome. Failures exit nonzero with the steps that already ran.
114
+ */
115
+ export declare function runOnboarding(command: string, options: Pick<ConfigureOptions, 'json' | 'interactive'>, run: (session: OnboardingSession) => Promise<OnboardingOutcome>): Promise<void>;
116
+ export declare function asRecord(value: unknown): Record<string, unknown> | undefined;
117
+ /** Reads the setup config, failing before any change when it is unusable. */
118
+ export declare function readSetupConfig(configFilepath: string): Record<string, unknown>;
119
+ export declare const INIT_SOURCE_HELP = "Space-separated list of glob patterns containing the app's source code, by default 'src/**/*.{js,jsx,ts,tsx}' 'app/**/*.{js,jsx,ts,tsx}' 'pages/**/*.{js,jsx,ts,tsx}' 'components/**/*.{js,jsx,ts,tsx}'";
120
+ export declare function setupConfigPath(options: Pick<ConfigureOptions, 'config'>): string;
121
+ export declare function getConfiguredFramework(config: Record<string, unknown>): SupportedReactFrameworks | 'mintlify' | undefined;
122
+ /** JSON mode keeps the banner off stdout. */
123
+ export declare function displaySetupHeader(session: OnboardingSession, message: string): void;
124
+ export declare function describeDefaults(framework: FrameworkObject | undefined): string;
125
+ /** Setup patterns name where each locale's files live, so they need [locale]. */
126
+ export declare function validateSetupPattern(pattern: string): true | string;
127
+ /** Parses `--file-patterns <format>=<glob>` values, grouped by format. */
128
+ export declare function parseFilePatterns(session: OnboardingSession, values?: string[]): Map<SetupFileFormat, string[]>;
129
+ /** Detects the package manager, or asks only when prompts are allowed. */
130
+ export declare function resolvePackageManager(session: OnboardingSession, specified?: string): Promise<PackageManager | undefined>;
131
+ export declare function installWithProgress(session: OnboardingSession, packageName: string, packageManager: PackageManager, asDevDependency: boolean): Promise<void>;