gt 2.21.5 → 2.22.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 (114) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/api/collectUserEditDiffs.js +1 -1
  3. package/dist/auth/callbackPage.d.ts +1 -0
  4. package/dist/auth/callbackPage.js +77 -0
  5. package/dist/auth/callbackPage.js.map +1 -0
  6. package/dist/auth/credentialStore.d.ts +15 -0
  7. package/dist/auth/credentialStore.js +107 -0
  8. package/dist/auth/credentialStore.js.map +1 -0
  9. package/dist/auth/errors.d.ts +7 -0
  10. package/dist/auth/errors.js +23 -0
  11. package/dist/auth/errors.js.map +1 -0
  12. package/dist/auth/loopback.d.ts +9 -0
  13. package/dist/auth/loopback.js +73 -0
  14. package/dist/auth/loopback.js.map +1 -0
  15. package/dist/auth/loopback.test.d.ts +1 -0
  16. package/dist/auth/oauth.d.ts +39 -0
  17. package/dist/auth/oauth.js +260 -0
  18. package/dist/auth/oauth.js.map +1 -0
  19. package/dist/bin/bin-entry.js +2 -10
  20. package/dist/bin/bin-entry.js.map +1 -1
  21. package/dist/cli/base.d.ts +4 -8
  22. package/dist/cli/base.js +119 -60
  23. package/dist/cli/base.js.map +1 -1
  24. package/dist/cli/commands/api.js +6 -2
  25. package/dist/cli/commands/api.js.map +1 -1
  26. package/dist/cli/commands/upload.js +2 -2
  27. package/dist/cli/commands/upload.js.map +1 -1
  28. package/dist/cli/commands/utils/validation.d.ts +7 -7
  29. package/dist/cli/commands/utils/validation.js +7 -20
  30. package/dist/cli/commands/utils/validation.js.map +1 -1
  31. package/dist/config/generateSettings.js +4 -9
  32. package/dist/config/generateSettings.js.map +1 -1
  33. package/dist/config/optionPresets.js +16 -6
  34. package/dist/config/optionPresets.js.map +1 -1
  35. package/dist/config/validateSettings.d.ts +4 -2
  36. package/dist/config/validateSettings.js +9 -0
  37. package/dist/config/validateSettings.js.map +1 -1
  38. package/dist/console/index.d.ts +0 -2
  39. package/dist/console/index.js +2 -4
  40. package/dist/console/index.js.map +1 -1
  41. package/dist/console/inkPrompts.js +8 -6
  42. package/dist/console/inkPrompts.js.map +1 -1
  43. package/dist/console/inkTypes.d.ts +3 -0
  44. package/dist/console/logger.d.ts +13 -0
  45. package/dist/console/logger.js +60 -20
  46. package/dist/console/logger.js.map +1 -1
  47. package/dist/console/logging.d.ts +5 -3
  48. package/dist/console/logging.js +8 -6
  49. package/dist/console/logging.js.map +1 -1
  50. package/dist/console/promptParsing.d.ts +4 -3
  51. package/dist/console/promptParsing.js +7 -7
  52. package/dist/console/promptParsing.js.map +1 -1
  53. package/dist/formats/files/aggregateFiles.js +4 -4
  54. package/dist/formats/files/aggregateFiles.js.map +1 -1
  55. package/dist/formats/files/localeContent.js +2 -2
  56. package/dist/formats/files/localeContent.js.map +1 -1
  57. package/dist/formats/files/transformFormat.d.ts +2 -0
  58. package/dist/formats/files/transformFormat.js +8 -2
  59. package/dist/formats/files/transformFormat.js.map +1 -1
  60. package/dist/formats/json/extractJson.js +3 -3
  61. package/dist/formats/json/extractJson.js.map +1 -1
  62. package/dist/formats/json/mergeJson.js +6 -6
  63. package/dist/formats/json/mergeJson.js.map +1 -1
  64. package/dist/formats/xcstrings/mergeXcstrings.js +2 -2
  65. package/dist/formats/xcstrings/mergeXcstrings.js.map +1 -1
  66. package/dist/generated/version.d.ts +1 -1
  67. package/dist/generated/version.js +1 -1
  68. package/dist/generated/version.js.map +1 -1
  69. package/dist/git/setupMergeDrivers.js +1 -1
  70. package/dist/git/setupMergeDrivers.js.map +1 -1
  71. package/dist/index.js +2 -0
  72. package/dist/index.js.map +1 -1
  73. package/dist/main.js +2 -10
  74. package/dist/main.js.map +1 -1
  75. package/dist/react/jsx/utils/jsxParsing/addGTIdentifierToSyntaxTree.js +1 -1
  76. package/dist/react/jsx/utils/jsxParsing/parseJsx.js +1 -1
  77. package/dist/react/jsx/utils/parseString.js +1 -1
  78. package/dist/react/jsx/utils/stringParsing/derivation/index.js +1 -1
  79. package/dist/setup/developmentCredentials.d.ts +8 -0
  80. package/dist/setup/developmentCredentials.js +82 -0
  81. package/dist/setup/developmentCredentials.js.map +1 -0
  82. package/dist/setup/setupViteSPA.js +1 -1
  83. package/dist/setup/setupViteSPA.js.map +1 -1
  84. package/dist/setup/userInput.d.ts +1 -0
  85. package/dist/setup/userInput.js +7 -2
  86. package/dist/setup/userInput.js.map +1 -1
  87. package/dist/translation/extractInline.js +1 -1
  88. package/dist/translation/extractInline.js.map +1 -1
  89. package/dist/types/index.d.ts +4 -1
  90. package/dist/utils/api.d.ts +146 -67
  91. package/dist/utils/api.js +5 -94
  92. package/dist/utils/api.js.map +1 -1
  93. package/dist/utils/credentials.d.ts +37 -10
  94. package/dist/utils/credentials.js +267 -58
  95. package/dist/utils/credentials.js.map +1 -1
  96. package/dist/utils/loadEnv.d.ts +2 -0
  97. package/dist/utils/loadEnv.js +24 -0
  98. package/dist/utils/loadEnv.js.map +1 -0
  99. package/dist/utils/reactPackageCompatibility.js +1 -1
  100. package/dist/utils/reactPackageCompatibility.js.map +1 -1
  101. package/dist/workflows/steps/PollJobsStep.js +40 -53
  102. package/dist/workflows/steps/PollJobsStep.js.map +1 -1
  103. package/dist/workflows/utils/syncFonts.js +2 -2
  104. package/dist/workflows/utils/syncFonts.js.map +1 -1
  105. package/package.json +8 -7
  106. package/dist/next/config/parseNextConfig.d.ts +0 -10
  107. package/dist/next/config/parseNextConfig.js +0 -34
  108. package/dist/next/config/parseNextConfig.js.map +0 -1
  109. package/dist/utils/fetch.d.ts +0 -11
  110. package/dist/utils/fetch.js +0 -24
  111. package/dist/utils/fetch.js.map +0 -1
  112. package/dist/utils/gt.d.ts +0 -2
  113. package/dist/utils/gt.js +0 -7
  114. package/dist/utils/gt.js.map +0 -1
@@ -1,71 +1,280 @@
1
- import { logger } from "../console/logger.js";
2
- import { logErrorAndExit } from "../console/logging.js";
3
- import apiRequest from "./fetch.js";
4
- import chalk from "chalk";
1
+ import { randomUUID } from "node:crypto";
5
2
  import path from "node:path";
6
3
  import fs from "node:fs";
4
+ import { createDiagnosticMessage, formatDiagnosticErrorDetails } from "generaltranslation/internal";
5
+ import { execFile } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+ import dotenv from "dotenv";
7
8
  //#region src/utils/credentials.ts
8
- async function retrieveCredentials(settings, keyType) {
9
- const { sessionId } = await generateCredentialsSession(settings.baseUrl, keyType);
10
- const urlToOpen = `${settings.dashboardUrl}/cli/wizard/${sessionId}`;
11
- await import("open").then((open) => open.default(urlToOpen, { wait: false }));
12
- logger.message(`${chalk.dim(`If the browser window didn't open automatically, please open the following link:`)}\n\n${chalk.cyan(urlToOpen)}`);
13
- const spinner = logger.createSpinner("dots");
14
- spinner.start("Waiting for response from dashboard...");
15
- const credentials = await new Promise((resolve) => {
16
- const interval = setInterval(async () => {
17
- try {
18
- const res = await apiRequest(settings.baseUrl, `/cli/wizard/${sessionId}`, { method: "GET" });
19
- if (res.status === 200) {
20
- resolve(await res.json());
21
- clearInterval(interval);
22
- clearTimeout(timeout);
23
- apiRequest(settings.baseUrl, `/cli/wizard/${sessionId}`, { method: "DELETE" });
24
- }
25
- } catch (err) {
26
- console.error(err);
27
- }
28
- }, 2e3);
29
- const timeout = setTimeout(() => {
30
- spinner.stop("Timed out");
31
- clearInterval(interval);
32
- logErrorAndExit("Timed out waiting for response from dashboard");
33
- }, 1e3 * 60 * 60);
9
+ const execFileAsync = promisify(execFile);
10
+ const unsafeCredentialsEnvError = createDiagnosticMessage({
11
+ whatHappened: "Cannot safely update .env.local",
12
+ reassurance: "The existing .env.local file was not changed",
13
+ fix: "Move multiline values away from project/key assignments or set the development credentials manually, then retry"
14
+ });
15
+ function unwritableEnvFileError(envFile) {
16
+ return createDiagnosticMessage({
17
+ whatHappened: `${envFile} is not a regular file`,
18
+ reassurance: "Nothing was changed",
19
+ fix: "Replace it with a regular file or a symlink to one, or set the development credentials manually, then retry"
20
+ });
21
+ }
22
+ function trackedEnvFileError(file) {
23
+ return createDiagnosticMessage({
24
+ whatHappened: `${file} is tracked by Git`,
25
+ why: "development credentials saved there would be committed",
26
+ reassurance: "Nothing was changed",
27
+ fix: `Run \`git rm --cached ${path.basename(file)}\` in ${path.dirname(file)} and add the file to .gitignore, then retry`
28
+ });
29
+ }
30
+ function unignoredEnvFileError(file) {
31
+ return createDiagnosticMessage({
32
+ whatHappened: `${file} is not ignored by Git`,
33
+ why: "development credentials saved there could be committed",
34
+ reassurance: "Nothing was changed",
35
+ fix: `Add the file to a .gitignore that applies to ${path.dirname(file)}, then retry`
36
+ });
37
+ }
38
+ function unignoredEnvFileReferentError(envFile, referent) {
39
+ return createDiagnosticMessage({
40
+ whatHappened: `${envFile} links to ${referent}, which Git does not ignore`,
41
+ why: "development credentials saved there could be committed",
42
+ reassurance: "Nothing was changed",
43
+ fix: `Add ${referent} to a .gitignore in its repository or set the development credentials manually, then retry`
44
+ });
45
+ }
46
+ function gitUnavailableError(file, error) {
47
+ return createDiagnosticMessage({
48
+ whatHappened: `Could not run Git to verify that ${file} is untracked and ignored`,
49
+ reassurance: "Nothing was changed",
50
+ fix: "Install Git or make it available on PATH, then retry",
51
+ details: formatDiagnosticErrorDetails(error)
52
+ });
53
+ }
54
+ function gitOverrideError(file, names) {
55
+ return createDiagnosticMessage({
56
+ whatHappened: `Git repository overrides are set in the environment: ${names.join(", ")}`,
57
+ why: `they could point the untracked and ignore checks for ${file} at a different repository or index`,
58
+ reassurance: "Nothing was changed",
59
+ fix: "Unset them (for example, run outside a Git hook), then retry"
60
+ });
61
+ }
62
+ function gitCheckFailedError(file, stderr) {
63
+ return createDiagnosticMessage({
64
+ whatHappened: `Git could not check whether ${file} is untracked and ignored`,
65
+ reassurance: "Nothing was changed",
66
+ fix: "Fix the reported Git problem, then retry",
67
+ details: stderr.trim()
34
68
  });
35
- spinner.stop("Received credentials");
36
- return credentials;
37
69
  }
38
- async function generateCredentialsSession(url, keyType) {
39
- const res = await apiRequest(url, "/cli/wizard/session", { body: { keyType } });
40
- if (!res.ok) logErrorAndExit("Failed to generate credentials session");
41
- return await res.json();
70
+ const FRAMEWORK_ENV_PREFIXES = {
71
+ "next-pages": "NEXT_PUBLIC_",
72
+ vite: "VITE_",
73
+ gatsby: "GATSBY_",
74
+ react: "REACT_APP_",
75
+ redwood: "REDWOOD_ENV_"
76
+ };
77
+ /** Names of the runtime variables init writes for this framework. */
78
+ function getDevelopmentEnvNames(framework) {
79
+ const prefix = (framework && FRAMEWORK_ENV_PREFIXES[framework]) ?? "";
80
+ return {
81
+ projectId: `${prefix}GT_PROJECT_ID`,
82
+ devApiKey: `${prefix}GT_DEV_API_KEY`
83
+ };
84
+ }
85
+ /**
86
+ * Whether the project already has a runtime key: the framework's development
87
+ * key, or an explicit production key for server runtimes (gt-next, gt-node).
88
+ * Browser frameworks only read their prefixed key, so there `settings.apiKey`
89
+ * is tooling auth alone. `settings.projectId` already resolves every
90
+ * framework prefix. Login is checked separately.
91
+ */
92
+ function areCredentialsSet(settings, framework) {
93
+ const { devApiKey } = getDevelopmentEnvNames(framework);
94
+ const browserOnly = Boolean(framework && FRAMEWORK_ENV_PREFIXES[framework]);
95
+ return Boolean(settings.projectId && (process.env[devApiKey] || !browserOnly && settings.apiKey));
96
+ }
97
+ function assignedName(line) {
98
+ return /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line)?.[1];
99
+ }
100
+ /**
101
+ * dotenv continues a quoted value onto later lines until its closing quote.
102
+ * Replacing such a line would orphan the continuation, even when a later
103
+ * duplicate hides the multiline value from dotenv's effective values.
104
+ */
105
+ function opensMultilineValue(line) {
106
+ const value = line.slice(line.indexOf("=") + 1).trim();
107
+ const quote = value[0];
108
+ return [
109
+ "\"",
110
+ "'",
111
+ "`"
112
+ ].includes(quote) && !value.slice(1).replaceAll(`\\${quote}`, "").includes(quote);
113
+ }
114
+ function upsertEnvAssignment(content, name, value) {
115
+ const assignsName = (line) => assignedName(line) === name;
116
+ const lines = content.split("\n");
117
+ const first = lines.findIndex(assignsName);
118
+ if (first === -1) return `${content}${content && !content.endsWith("\n") ? "\n" : ""}${name}=${value}\n`;
119
+ return lines.map((line, index) => index === first ? `${name}=${value}` : line).filter((line, index) => index === first || !assignsName(line)).join("\n");
120
+ }
121
+ /**
122
+ * Where .env.local content lives and its mode. Follows an existing symlink so
123
+ * its referent is replaced, never the link itself; anything but a regular
124
+ * file (dangling link, directory) fails before any write.
125
+ */
126
+ async function resolveEnvFile(envFile) {
127
+ const entry = await fs.promises.lstat(envFile).catch((error) => {
128
+ if (error.code === "ENOENT") return void 0;
129
+ throw error;
130
+ });
131
+ if (!entry) return void 0;
132
+ const stat = await fs.promises.stat(envFile).catch((error) => {
133
+ if (error.code === "ENOENT") return void 0;
134
+ throw error;
135
+ });
136
+ if (!stat?.isFile()) throw new Error(unwritableEnvFileError(envFile));
137
+ return {
138
+ target: entry.isSymbolicLink() ? await fs.promises.realpath(envFile) : envFile,
139
+ mode: stat.mode & 511
140
+ };
141
+ }
142
+ const GIT_LOCATION_OVERRIDES = [
143
+ "GIT_DIR",
144
+ "GIT_WORK_TREE",
145
+ "GIT_INDEX_FILE",
146
+ "GIT_COMMON_DIR",
147
+ "GIT_CEILING_DIRECTORIES"
148
+ ];
149
+ /**
150
+ * How Git in the file's own repository treats the path, using Git's real
151
+ * index and effective ignore rules (comments, negations, nested .gitignore,
152
+ * excludes). Only a Git-confirmed non-repository counts as no repository;
153
+ * a missing binary, a repository override, or any other Git error fails
154
+ * closed.
155
+ */
156
+ async function inspectGitExposure(file) {
157
+ const overrides = GIT_LOCATION_OVERRIDES.filter((name) => process.env[name] !== void 0);
158
+ if (overrides.length > 0) throw new Error(gitOverrideError(file, overrides));
159
+ const cwd = path.dirname(file);
160
+ const run = async (args) => {
161
+ try {
162
+ await execFileAsync("git", args, {
163
+ cwd,
164
+ encoding: "utf8",
165
+ windowsHide: true,
166
+ env: {
167
+ ...process.env,
168
+ LC_ALL: "C"
169
+ }
170
+ });
171
+ return {
172
+ code: 0,
173
+ stderr: ""
174
+ };
175
+ } catch (error) {
176
+ const { code, stderr } = error;
177
+ if (typeof code !== "number") throw new Error(gitUnavailableError(file, error));
178
+ return {
179
+ code,
180
+ stderr: typeof stderr === "string" ? stderr : ""
181
+ };
182
+ }
183
+ };
184
+ const toplevel = await run(["rev-parse", "--show-toplevel"]);
185
+ if (toplevel.code === 128 && /not a git repository/i.test(toplevel.stderr)) return "no-repository";
186
+ if (toplevel.code !== 0) throw new Error(gitCheckFailedError(file, toplevel.stderr));
187
+ const tracked = await run([
188
+ "ls-files",
189
+ "--error-unmatch",
190
+ "--",
191
+ file
192
+ ]);
193
+ if (tracked.code === 0) return "tracked";
194
+ if (tracked.code !== 1) throw new Error(gitCheckFailedError(file, tracked.stderr));
195
+ const ignored = await run([
196
+ "check-ignore",
197
+ "-q",
198
+ "--",
199
+ file
200
+ ]);
201
+ if (ignored.code === 0) return "ignored";
202
+ if (ignored.code === 1) return "unignored";
203
+ throw new Error(gitCheckFailedError(file, ignored.stderr));
204
+ }
205
+ /**
206
+ * Everything that must hold before credentials may be saved to .env.local,
207
+ * so callers can fail before minting a key: the path is a regular file (or a
208
+ * link to one) or absent, and neither it nor a link's referent is tracked by
209
+ * Git. A referent inside any worktree must already be ignored there; only
210
+ * .env.local itself gets an ignore rule added, by setCredentials.
211
+ */
212
+ async function inspectCredentialsEnvFile(cwd = process.cwd()) {
213
+ const envFile = path.resolve(cwd, ".env.local");
214
+ const existing = await resolveEnvFile(envFile);
215
+ const exposure = await inspectGitExposure(envFile);
216
+ if (exposure === "tracked") throw new Error(trackedEnvFileError(envFile));
217
+ if (existing && existing.target !== envFile) {
218
+ const referentExposure = await inspectGitExposure(existing.target);
219
+ if (referentExposure === "tracked") throw new Error(trackedEnvFileError(existing.target));
220
+ if (referentExposure === "unignored") throw new Error(unignoredEnvFileReferentError(envFile, existing.target));
221
+ }
222
+ return {
223
+ envFile,
224
+ existing,
225
+ exposure
226
+ };
227
+ }
228
+ async function appendGitignoreRule(cwd) {
229
+ const gitignoreFile = path.join(cwd, ".gitignore");
230
+ const content = await fs.promises.readFile(gitignoreFile, "utf8").catch((error) => {
231
+ if (error.code === "ENOENT") return "";
232
+ throw error;
233
+ });
234
+ const separator = content && !content.endsWith("\n") ? "\n" : "";
235
+ await fs.promises.appendFile(gitignoreFile, `${separator}.env.local\n`, "utf8");
42
236
  }
43
- function areCredentialsSet() {
44
- return process.env.GT_PROJECT_ID && (process.env.GT_API_KEY || process.env.GT_DEV_API_KEY);
237
+ async function writeEnvFileAtomically(target, content, mode) {
238
+ const temporaryPath = path.join(path.dirname(target), `.env.local.${randomUUID()}.tmp`);
239
+ try {
240
+ await fs.promises.writeFile(temporaryPath, content, {
241
+ encoding: "utf8",
242
+ flag: "wx",
243
+ mode
244
+ });
245
+ await fs.promises.chmod(temporaryPath, mode);
246
+ await fs.promises.rename(temporaryPath, target);
247
+ } catch (error) {
248
+ await fs.promises.rm(temporaryPath, { force: true });
249
+ throw error;
250
+ }
45
251
  }
46
252
  async function setCredentials(credentials, framework, cwd = process.cwd()) {
47
- const envFile = path.join(cwd, ".env.local");
48
- let envContent = "";
49
- if (!fs.existsSync(envFile)) {
50
- await fs.promises.writeFile(envFile, "", "utf8");
253
+ const { envFile, existing: existingEnvFile, exposure } = await inspectCredentialsEnvFile(cwd);
254
+ if (exposure === "unignored") {
255
+ await appendGitignoreRule(cwd);
256
+ if (await inspectGitExposure(envFile) !== "ignored") throw new Error(unignoredEnvFileError(envFile));
257
+ } else if (exposure === "no-repository") {
51
258
  const gitignoreFile = path.join(cwd, ".gitignore");
52
- if (fs.existsSync(gitignoreFile)) {
53
- if (!(await fs.promises.readFile(gitignoreFile, "utf8")).includes(".env.local")) await fs.promises.appendFile(gitignoreFile, "\n.env.local\n", "utf8");
54
- } else await fs.promises.writeFile(gitignoreFile, ".env.local\n", "utf8");
55
- } else envContent = await fs.promises.readFile(envFile, "utf8");
56
- let prefix = "";
57
- if (framework === "next-pages") prefix = "NEXT_PUBLIC_";
58
- else if (framework === "vite") prefix = "VITE_";
59
- else if (framework === "gatsby") prefix = "GATSBY_";
60
- else if (framework === "react") prefix = "REACT_APP_";
61
- else if (framework === "redwood") prefix = "REDWOOD_ENV_";
62
- envContent += `\n${prefix}GT_PROJECT_ID=${credentials.projectId}\n`;
63
- for (const apiKey of credentials.apiKeys) if (apiKey.type === "development") envContent += `${prefix || ""}GT_DEV_API_KEY=${apiKey.key}\n`;
64
- else envContent += `GT_API_KEY=${apiKey.key}\n`;
65
- envContent = envContent.replace(/\n{3,}/g, "\n\n").trim() + "\n";
66
- await fs.promises.writeFile(envFile, envContent, "utf8");
259
+ if (!fs.existsSync(gitignoreFile) || (await fs.promises.readFile(gitignoreFile, "utf8")).trimEnd().split(/\r?\n/).at(-1) !== ".env.local") await appendGitignoreRule(cwd);
260
+ }
261
+ let envContent = existingEnvFile ? await fs.promises.readFile(existingEnvFile.target, "utf8") : "";
262
+ const names = getDevelopmentEnvNames(framework);
263
+ const targetNames = Object.values(names);
264
+ const targetOpensMultiline = envContent.split("\n").some((line) => targetNames.includes(assignedName(line) ?? "") && opensMultilineValue(line));
265
+ const original = dotenv.parse(envContent);
266
+ const expected = {
267
+ ...original,
268
+ [names.projectId]: credentials.projectId,
269
+ [names.devApiKey]: credentials.apiKey
270
+ };
271
+ envContent = upsertEnvAssignment(envContent, names.projectId, credentials.projectId);
272
+ envContent = upsertEnvAssignment(envContent, names.devApiKey, credentials.apiKey);
273
+ const updated = dotenv.parse(envContent);
274
+ if (targetOpensMultiline || targetNames.some((name) => original[name]?.includes("\n")) || Object.keys(updated).length !== Object.keys(expected).length || Object.entries(expected).some(([name, value]) => updated[name] !== value)) throw new Error(unsafeCredentialsEnvError);
275
+ await writeEnvFileAtomically(existingEnvFile?.target ?? envFile, envContent, existingEnvFile?.mode ?? 384);
67
276
  }
68
277
  //#endregion
69
- export { areCredentialsSet, generateCredentialsSession, retrieveCredentials, setCredentials };
278
+ export { areCredentialsSet, getDevelopmentEnvNames, inspectCredentialsEnvFile, setCredentials };
70
279
 
71
280
  //# sourceMappingURL=credentials.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.js","names":[],"sources":["../../src/utils/credentials.ts"],"sourcesContent":["import { logErrorAndExit } from '../console/logging.js';\nimport { logger } from '../console/logger.js';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport { Settings, SupportedFrameworks } from '../types/index.js';\nimport chalk from 'chalk';\nimport apiRequest from './fetch.js';\n// Type for credentials returned from the dashboard\ntype Credentials = {\n apiKeys: ApiKey[];\n projectId: string;\n};\n\ntype ApiKey = {\n key: string;\n type: 'development' | 'production';\n};\n\n// Fetches project ID and API key by opening the dashboard in the browser\nexport async function retrieveCredentials(\n settings: Settings,\n keyType: 'development' | 'production' | 'all'\n): Promise<Credentials> {\n // Generate a session ID\n const { sessionId } = await generateCredentialsSession(\n settings.baseUrl,\n keyType\n );\n\n const urlToOpen = `${settings.dashboardUrl}/cli/wizard/${sessionId}`;\n await import('open').then((open) =>\n open.default(urlToOpen, {\n wait: false,\n })\n );\n\n logger.message(\n `${chalk.dim(\n `If the browser window didn't open automatically, please open the following link:`\n )}\\n\\n${chalk.cyan(urlToOpen)}`\n );\n\n const spinner = logger.createSpinner('dots');\n spinner.start('Waiting for response from dashboard...');\n\n const credentials = await new Promise<Credentials>((resolve) => {\n const interval = setInterval(async () => {\n // Ping the dashboard to see if the credentials are set\n try {\n const res = await apiRequest(\n settings.baseUrl,\n `/cli/wizard/${sessionId}`,\n {\n method: 'GET',\n }\n );\n if (res.status === 200) {\n const data = await res.json();\n resolve(data as Credentials);\n clearInterval(interval);\n clearTimeout(timeout);\n apiRequest(settings.baseUrl, `/cli/wizard/${sessionId}`, {\n method: 'DELETE',\n });\n }\n } catch (err) {\n console.error(err);\n }\n }, 2000);\n // timeout after 1 hour\n const timeout = setTimeout(\n () => {\n spinner.stop('Timed out');\n clearInterval(interval);\n logErrorAndExit('Timed out waiting for response from dashboard');\n },\n 1000 * 60 * 60\n );\n });\n spinner.stop('Received credentials');\n return credentials;\n}\n\nexport async function generateCredentialsSession(\n url: string,\n keyType: 'development' | 'production' | 'all'\n): Promise<{\n sessionId: string;\n}> {\n const res = await apiRequest(url, '/cli/wizard/session', {\n body: { keyType },\n });\n if (!res.ok) {\n logErrorAndExit('Failed to generate credentials session');\n }\n return await res.json();\n}\n\n// Checks if the credentials are set in the environment variables\nexport function areCredentialsSet() {\n return (\n process.env.GT_PROJECT_ID &&\n (process.env.GT_API_KEY || process.env.GT_DEV_API_KEY)\n );\n}\n\n// Sets the credentials in .env.local file\nexport async function setCredentials(\n credentials: Credentials,\n framework?: SupportedFrameworks,\n cwd: string = process.cwd()\n) {\n const envFile = path.join(cwd, '.env.local');\n let envContent = '';\n\n // Check if .env.local exists, create it if it doesn't\n if (!fs.existsSync(envFile)) {\n // File doesn't exist, create it\n await fs.promises.writeFile(envFile, '', 'utf8');\n\n // Add .env.local to .gitignore if it exists\n const gitignoreFile = path.join(cwd, '.gitignore');\n if (fs.existsSync(gitignoreFile)) {\n const gitignoreContent = await fs.promises.readFile(\n gitignoreFile,\n 'utf8'\n );\n if (!gitignoreContent.includes('.env.local')) {\n await fs.promises.appendFile(gitignoreFile, '\\n.env.local\\n', 'utf8');\n }\n } else {\n // Create .gitignore file with .env.local\n await fs.promises.writeFile(gitignoreFile, '.env.local\\n', 'utf8');\n }\n } else {\n // Read existing content\n envContent = await fs.promises.readFile(envFile, 'utf8');\n }\n\n // Always append the credentials to the file\n let prefix = '';\n if (framework === 'next-pages') {\n prefix = 'NEXT_PUBLIC_';\n } else if (framework === 'vite') {\n prefix = 'VITE_';\n } else if (framework === 'gatsby') {\n prefix = 'GATSBY_';\n } else if (framework === 'react') {\n prefix = 'REACT_APP_';\n } else if (framework === 'redwood') {\n prefix = 'REDWOOD_ENV_';\n }\n\n envContent += `\\n${prefix}GT_PROJECT_ID=${credentials.projectId}\\n`;\n\n for (const apiKey of credentials.apiKeys) {\n if (apiKey.type === 'development') {\n envContent += `${prefix || ''}GT_DEV_API_KEY=${apiKey.key}\\n`;\n } else {\n envContent += `GT_API_KEY=${apiKey.key}\\n`;\n }\n }\n\n // Ensure we don't have excessive newlines\n envContent = envContent.replace(/\\n{3,}/g, '\\n\\n').trim() + '\\n';\n\n // Write the updated content back to the file\n await fs.promises.writeFile(envFile, envContent, 'utf8');\n}\n"],"mappings":";;;;;;;AAmBA,eAAsB,oBACpB,UACA,SACsB;CAEtB,MAAM,EAAE,cAAc,MAAM,2BAC1B,SAAS,SACT,QACD;CAED,MAAM,YAAY,GAAG,SAAS,aAAa,cAAc;AACzD,OAAM,OAAO,QAAQ,MAAM,SACzB,KAAK,QAAQ,WAAW,EACtB,MAAM,OACP,CAAC,CACH;AAED,QAAO,QACL,GAAG,MAAM,IACP,mFACD,CAAC,MAAM,MAAM,KAAK,UAAU,GAC9B;CAED,MAAM,UAAU,OAAO,cAAc,OAAO;AAC5C,SAAQ,MAAM,yCAAyC;CAEvD,MAAM,cAAc,MAAM,IAAI,SAAsB,YAAY;EAC9D,MAAM,WAAW,YAAY,YAAY;AAEvC,OAAI;IACF,MAAM,MAAM,MAAM,WAChB,SAAS,SACT,eAAe,aACf,EACE,QAAQ,OACT,CACF;AACD,QAAI,IAAI,WAAW,KAAK;AAEtB,aAAQ,MADW,IAAI,MAAM,CACD;AAC5B,mBAAc,SAAS;AACvB,kBAAa,QAAQ;AACrB,gBAAW,SAAS,SAAS,eAAe,aAAa,EACvD,QAAQ,UACT,CAAC;;YAEG,KAAK;AACZ,YAAQ,MAAM,IAAI;;KAEnB,IAAK;EAER,MAAM,UAAU,iBACR;AACJ,WAAQ,KAAK,YAAY;AACzB,iBAAc,SAAS;AACvB,mBAAgB,gDAAgD;KAElE,MAAO,KAAK,GACb;GACD;AACF,SAAQ,KAAK,uBAAuB;AACpC,QAAO;;AAGT,eAAsB,2BACpB,KACA,SAGC;CACD,MAAM,MAAM,MAAM,WAAW,KAAK,uBAAuB,EACvD,MAAM,EAAE,SAAS,EAClB,CAAC;AACF,KAAI,CAAC,IAAI,GACP,iBAAgB,yCAAyC;AAE3D,QAAO,MAAM,IAAI,MAAM;;AAIzB,SAAgB,oBAAoB;AAClC,QACE,QAAQ,IAAI,kBACX,QAAQ,IAAI,cAAc,QAAQ,IAAI;;AAK3C,eAAsB,eACpB,aACA,WACA,MAAc,QAAQ,KAAK,EAC3B;CACA,MAAM,UAAU,KAAK,KAAK,KAAK,aAAa;CAC5C,IAAI,aAAa;AAGjB,KAAI,CAAC,GAAG,WAAW,QAAQ,EAAE;AAE3B,QAAM,GAAG,SAAS,UAAU,SAAS,IAAI,OAAO;EAGhD,MAAM,gBAAgB,KAAK,KAAK,KAAK,aAAa;AAClD,MAAI,GAAG,WAAW,cAAc;OAK1B,EAAC,MAJ0B,GAAG,SAAS,SACzC,eACA,OACD,EACqB,SAAS,aAAa,CAC1C,OAAM,GAAG,SAAS,WAAW,eAAe,kBAAkB,OAAO;QAIvE,OAAM,GAAG,SAAS,UAAU,eAAe,gBAAgB,OAAO;OAIpE,cAAa,MAAM,GAAG,SAAS,SAAS,SAAS,OAAO;CAI1D,IAAI,SAAS;AACb,KAAI,cAAc,aAChB,UAAS;UACA,cAAc,OACvB,UAAS;UACA,cAAc,SACvB,UAAS;UACA,cAAc,QACvB,UAAS;UACA,cAAc,UACvB,UAAS;AAGX,eAAc,KAAK,OAAO,gBAAgB,YAAY,UAAU;AAEhE,MAAK,MAAM,UAAU,YAAY,QAC/B,KAAI,OAAO,SAAS,cAClB,eAAc,GAAG,UAAU,GAAG,iBAAiB,OAAO,IAAI;KAE1D,eAAc,cAAc,OAAO,IAAI;AAK3C,cAAa,WAAW,QAAQ,WAAW,OAAO,CAAC,MAAM,GAAG;AAG5D,OAAM,GAAG,SAAS,UAAU,SAAS,YAAY,OAAO"}
1
+ {"version":3,"file":"credentials.js","names":[],"sources":["../../src/utils/credentials.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport { promisify } from 'node:util';\nimport dotenv from 'dotenv';\nimport { Settings, SupportedFrameworks } from '../types/index.js';\nimport {\n createDiagnosticMessage,\n formatDiagnosticErrorDetails,\n} from 'generaltranslation/internal';\n\nconst execFileAsync = promisify(execFile);\n\nconst unsafeCredentialsEnvError = createDiagnosticMessage({\n whatHappened: 'Cannot safely update .env.local',\n reassurance: 'The existing .env.local file was not changed',\n fix: 'Move multiline values away from project/key assignments or set the development credentials manually, then retry',\n});\n\nfunction unwritableEnvFileError(envFile: string): string {\n return createDiagnosticMessage({\n whatHappened: `${envFile} is not a regular file`,\n reassurance: 'Nothing was changed',\n fix: 'Replace it with a regular file or a symlink to one, or set the development credentials manually, then retry',\n });\n}\n\nfunction trackedEnvFileError(file: string): string {\n return createDiagnosticMessage({\n whatHappened: `${file} is tracked by Git`,\n why: 'development credentials saved there would be committed',\n reassurance: 'Nothing was changed',\n fix: `Run \\`git rm --cached ${path.basename(file)}\\` in ${path.dirname(file)} and add the file to .gitignore, then retry`,\n });\n}\n\nfunction unignoredEnvFileError(file: string): string {\n return createDiagnosticMessage({\n whatHappened: `${file} is not ignored by Git`,\n why: 'development credentials saved there could be committed',\n reassurance: 'Nothing was changed',\n fix: `Add the file to a .gitignore that applies to ${path.dirname(file)}, then retry`,\n });\n}\n\nfunction unignoredEnvFileReferentError(envFile: string, referent: string) {\n return createDiagnosticMessage({\n whatHappened: `${envFile} links to ${referent}, which Git does not ignore`,\n why: 'development credentials saved there could be committed',\n reassurance: 'Nothing was changed',\n fix: `Add ${referent} to a .gitignore in its repository or set the development credentials manually, then retry`,\n });\n}\n\nfunction gitUnavailableError(file: string, error: unknown): string {\n return createDiagnosticMessage({\n whatHappened: `Could not run Git to verify that ${file} is untracked and ignored`,\n reassurance: 'Nothing was changed',\n fix: 'Install Git or make it available on PATH, then retry',\n details: formatDiagnosticErrorDetails(error),\n });\n}\n\nfunction gitOverrideError(file: string, names: string[]): string {\n return createDiagnosticMessage({\n whatHappened: `Git repository overrides are set in the environment: ${names.join(', ')}`,\n why: `they could point the untracked and ignore checks for ${file} at a different repository or index`,\n reassurance: 'Nothing was changed',\n fix: 'Unset them (for example, run outside a Git hook), then retry',\n });\n}\n\nfunction gitCheckFailedError(file: string, stderr: string): string {\n return createDiagnosticMessage({\n whatHappened: `Git could not check whether ${file} is untracked and ignored`,\n reassurance: 'Nothing was changed',\n fix: 'Fix the reported Git problem, then retry',\n details: stderr.trim(),\n });\n}\n\nexport type Credentials = { apiKey: string; projectId: string };\n\n// Client-side frameworks only expose variables carrying their public prefix.\nconst FRAMEWORK_ENV_PREFIXES: Partial<\n Record<NonNullable<SupportedFrameworks>, string>\n> = {\n 'next-pages': 'NEXT_PUBLIC_',\n vite: 'VITE_',\n gatsby: 'GATSBY_',\n react: 'REACT_APP_',\n redwood: 'REDWOOD_ENV_',\n};\n\n/** Names of the runtime variables init writes for this framework. */\nexport function getDevelopmentEnvNames(framework?: SupportedFrameworks) {\n const prefix = (framework && FRAMEWORK_ENV_PREFIXES[framework]) ?? '';\n return {\n projectId: `${prefix}GT_PROJECT_ID`,\n devApiKey: `${prefix}GT_DEV_API_KEY`,\n };\n}\n\n/**\n * Whether the project already has a runtime key: the framework's development\n * key, or an explicit production key for server runtimes (gt-next, gt-node).\n * Browser frameworks only read their prefixed key, so there `settings.apiKey`\n * is tooling auth alone. `settings.projectId` already resolves every\n * framework prefix. Login is checked separately.\n */\nexport function areCredentialsSet(\n settings: Pick<Settings, 'projectId' | 'apiKey'>,\n framework?: SupportedFrameworks\n): boolean {\n const { devApiKey } = getDevelopmentEnvNames(framework);\n const browserOnly = Boolean(framework && FRAMEWORK_ENV_PREFIXES[framework]);\n return Boolean(\n settings.projectId &&\n (process.env[devApiKey] || (!browserOnly && settings.apiKey))\n );\n}\n\nfunction assignedName(line: string): string | undefined {\n return /^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=/.exec(line)?.[1];\n}\n\n/**\n * dotenv continues a quoted value onto later lines until its closing quote.\n * Replacing such a line would orphan the continuation, even when a later\n * duplicate hides the multiline value from dotenv's effective values.\n */\nfunction opensMultilineValue(line: string): boolean {\n const value = line.slice(line.indexOf('=') + 1).trim();\n const quote = value[0];\n return (\n ['\"', \"'\", '`'].includes(quote) &&\n !value.slice(1).replaceAll(`\\\\${quote}`, '').includes(quote)\n );\n}\n\nfunction upsertEnvAssignment(\n content: string,\n name: string,\n value: string\n): string {\n const assignsName = (line: string) => assignedName(line) === name;\n const lines = content.split('\\n');\n const first = lines.findIndex(assignsName);\n if (first === -1) {\n const separator = content && !content.endsWith('\\n') ? '\\n' : '';\n return `${content}${separator}${name}=${value}\\n`;\n }\n // Replace the first assignment in place and drop stale duplicates.\n return lines\n .map((line, index) => (index === first ? `${name}=${value}` : line))\n .filter((line, index) => index === first || !assignsName(line))\n .join('\\n');\n}\n\n/**\n * Where .env.local content lives and its mode. Follows an existing symlink so\n * its referent is replaced, never the link itself; anything but a regular\n * file (dangling link, directory) fails before any write.\n */\nasync function resolveEnvFile(\n envFile: string\n): Promise<{ target: string; mode: number } | undefined> {\n const entry = await fs.promises.lstat(envFile).catch((error) => {\n if (error.code === 'ENOENT') return undefined;\n throw error;\n });\n if (!entry) return undefined;\n // stat follows the link and fails on a dangling one.\n const stat = await fs.promises.stat(envFile).catch((error) => {\n if (error.code === 'ENOENT') return undefined;\n throw error;\n });\n if (!stat?.isFile()) throw new Error(unwritableEnvFileError(envFile));\n return {\n target: entry.isSymbolicLink()\n ? await fs.promises.realpath(envFile)\n : envFile,\n mode: stat.mode & 0o777,\n };\n}\n\ntype GitExposure = 'no-repository' | 'tracked' | 'ignored' | 'unignored';\n\n// Git honors these over discovering the repository from the working\n// directory, so an inherited value could select another repository, hide this\n// one, or substitute an empty index. Configuration variables stay in effect.\nconst GIT_LOCATION_OVERRIDES = [\n 'GIT_DIR',\n 'GIT_WORK_TREE',\n 'GIT_INDEX_FILE',\n 'GIT_COMMON_DIR',\n 'GIT_CEILING_DIRECTORIES',\n];\n\n/**\n * How Git in the file's own repository treats the path, using Git's real\n * index and effective ignore rules (comments, negations, nested .gitignore,\n * excludes). Only a Git-confirmed non-repository counts as no repository;\n * a missing binary, a repository override, or any other Git error fails\n * closed.\n */\nasync function inspectGitExposure(file: string): Promise<GitExposure> {\n const overrides = GIT_LOCATION_OVERRIDES.filter(\n (name) => process.env[name] !== undefined\n );\n if (overrides.length > 0) throw new Error(gitOverrideError(file, overrides));\n const cwd = path.dirname(file);\n const run = async (args: string[]) => {\n try {\n await execFileAsync('git', args, {\n cwd,\n encoding: 'utf8',\n windowsHide: true,\n // The no-repository check reads Git's untranslated message.\n env: { ...process.env, LC_ALL: 'C' },\n });\n return { code: 0, stderr: '' };\n } catch (error) {\n const { code, stderr } = error as { code?: unknown; stderr?: unknown };\n if (typeof code !== 'number')\n throw new Error(gitUnavailableError(file, error));\n return { code, stderr: typeof stderr === 'string' ? stderr : '' };\n }\n };\n\n const toplevel = await run(['rev-parse', '--show-toplevel']);\n if (toplevel.code === 128 && /not a git repository/i.test(toplevel.stderr)) {\n return 'no-repository';\n }\n if (toplevel.code !== 0) {\n throw new Error(gitCheckFailedError(file, toplevel.stderr));\n }\n // check-ignore ignores tracked files, so the index is consulted first.\n const tracked = await run(['ls-files', '--error-unmatch', '--', file]);\n if (tracked.code === 0) return 'tracked';\n if (tracked.code !== 1)\n throw new Error(gitCheckFailedError(file, tracked.stderr));\n const ignored = await run(['check-ignore', '-q', '--', file]);\n if (ignored.code === 0) return 'ignored';\n if (ignored.code === 1) return 'unignored';\n throw new Error(gitCheckFailedError(file, ignored.stderr));\n}\n\ntype CredentialsEnvFile = {\n envFile: string;\n existing: Awaited<ReturnType<typeof resolveEnvFile>>;\n exposure: GitExposure;\n};\n\n/**\n * Everything that must hold before credentials may be saved to .env.local,\n * so callers can fail before minting a key: the path is a regular file (or a\n * link to one) or absent, and neither it nor a link's referent is tracked by\n * Git. A referent inside any worktree must already be ignored there; only\n * .env.local itself gets an ignore rule added, by setCredentials.\n */\nexport async function inspectCredentialsEnvFile(\n cwd: string = process.cwd()\n): Promise<CredentialsEnvFile> {\n const envFile = path.resolve(cwd, '.env.local');\n const existing = await resolveEnvFile(envFile);\n const exposure = await inspectGitExposure(envFile);\n if (exposure === 'tracked') throw new Error(trackedEnvFileError(envFile));\n if (existing && existing.target !== envFile) {\n const referentExposure = await inspectGitExposure(existing.target);\n if (referentExposure === 'tracked') {\n throw new Error(trackedEnvFileError(existing.target));\n }\n if (referentExposure === 'unignored') {\n throw new Error(unignoredEnvFileReferentError(envFile, existing.target));\n }\n }\n return { envFile, existing, exposure };\n}\n\nasync function appendGitignoreRule(cwd: string): Promise<void> {\n const gitignoreFile = path.join(cwd, '.gitignore');\n const content = await fs.promises\n .readFile(gitignoreFile, 'utf8')\n .catch((error) => {\n if (error.code === 'ENOENT') return '';\n throw error;\n });\n const separator = content && !content.endsWith('\\n') ? '\\n' : '';\n await fs.promises.appendFile(\n gitignoreFile,\n `${separator}.env.local\\n`,\n 'utf8'\n );\n}\n\n// Same-directory temporary file and rename, as in auth/credentialStore.ts:\n// a failed write never truncates the existing file.\nasync function writeEnvFileAtomically(\n target: string,\n content: string,\n mode: number\n): Promise<void> {\n const temporaryPath = path.join(\n path.dirname(target),\n `.env.local.${randomUUID()}.tmp`\n );\n try {\n await fs.promises.writeFile(temporaryPath, content, {\n encoding: 'utf8',\n flag: 'wx',\n mode,\n });\n await fs.promises.chmod(temporaryPath, mode);\n await fs.promises.rename(temporaryPath, target);\n } catch (error) {\n await fs.promises.rm(temporaryPath, { force: true });\n throw error;\n }\n}\n\n// Sets the credentials in .env.local file\nexport async function setCredentials(\n credentials: Credentials,\n framework?: SupportedFrameworks,\n cwd: string = process.cwd()\n) {\n const {\n envFile,\n existing: existingEnvFile,\n exposure,\n } = await inspectCredentialsEnvFile(cwd);\n\n if (exposure === 'unignored') {\n // A rule appended to the same-directory .gitignore is the last match at\n // the deepest level, so it wins over earlier negations; verify anyway.\n await appendGitignoreRule(cwd);\n if ((await inspectGitExposure(envFile)) !== 'ignored') {\n throw new Error(unignoredEnvFileError(envFile));\n }\n } else if (exposure === 'no-repository') {\n // Without a repository there are no effective rules to consult, so this\n // is unverified: seed a .gitignore so a later `git init` ignores the file.\n const gitignoreFile = path.join(cwd, '.gitignore');\n if (\n !fs.existsSync(gitignoreFile) ||\n (await fs.promises.readFile(gitignoreFile, 'utf8'))\n .trimEnd()\n .split(/\\r?\\n/)\n .at(-1) !== '.env.local'\n ) {\n await appendGitignoreRule(cwd);\n }\n }\n\n let envContent = existingEnvFile\n ? await fs.promises.readFile(existingEnvFile.target, 'utf8')\n : '';\n\n // Only the hot-reload key: the CLI itself acts as the signed-in user, and\n // CI keys are created deliberately rather than dropped into .env.local.\n // Other lines, comments, and any GT_API_KEY are left untouched.\n const names = getDevelopmentEnvNames(framework);\n const targetNames = Object.values(names);\n const targetOpensMultiline = envContent\n .split('\\n')\n .some(\n (line) =>\n targetNames.includes(assignedName(line) ?? '') &&\n opensMultilineValue(line)\n );\n const original = dotenv.parse(envContent);\n const expected = {\n ...original,\n [names.projectId]: credentials.projectId,\n [names.devApiKey]: credentials.apiKey,\n };\n envContent = upsertEnvAssignment(\n envContent,\n names.projectId,\n credentials.projectId\n );\n envContent = upsertEnvAssignment(\n envContent,\n names.devApiKey,\n credentials.apiKey\n );\n\n const updated = dotenv.parse(envContent);\n if (\n targetOpensMultiline ||\n targetNames.some((name) => original[name]?.includes('\\n')) ||\n Object.keys(updated).length !== Object.keys(expected).length ||\n Object.entries(expected).some(([name, value]) => updated[name] !== value)\n ) {\n throw new Error(unsafeCredentialsEnvError);\n }\n\n await writeEnvFileAtomically(\n existingEnvFile?.target ?? envFile,\n envContent,\n existingEnvFile?.mode ?? 0o600\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ /** Load executable startup env files without contaminating command stdout. */
2
+ export declare function loadEnv(): void;
@@ -0,0 +1,24 @@
1
+ import dotenv from "dotenv";
2
+ //#region src/utils/loadEnv.ts
3
+ /** Load executable startup env files without contaminating command stdout. */
4
+ function loadEnv() {
5
+ const log = console.log;
6
+ console.log = console.error;
7
+ try {
8
+ dotenv.config({ path: ".env" });
9
+ dotenv.config({
10
+ path: ".env.local",
11
+ override: true
12
+ });
13
+ dotenv.config({
14
+ path: ".env.production",
15
+ override: true
16
+ });
17
+ } finally {
18
+ console.log = log;
19
+ }
20
+ }
21
+ //#endregion
22
+ export { loadEnv };
23
+
24
+ //# sourceMappingURL=loadEnv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadEnv.js","names":[],"sources":["../../src/utils/loadEnv.ts"],"sourcesContent":["import dotenv from 'dotenv';\n\n/** Load executable startup env files without contaminating command stdout. */\nexport function loadEnv(): void {\n /* eslint-disable no-console -- dotenv 16's vault warnings bypass quiet via console.log. */\n const log = console.log;\n // Keep this override synchronous; remove it when dotenv supports stderr diagnostics.\n console.log = console.error;\n try {\n dotenv.config({ path: '.env' });\n dotenv.config({ path: '.env.local', override: true });\n dotenv.config({ path: '.env.production', override: true });\n } finally {\n console.log = log;\n }\n /* eslint-enable no-console */\n}\n"],"mappings":";;;AAGA,SAAgB,UAAgB;CAE9B,MAAM,MAAM,QAAQ;AAEpB,SAAQ,MAAM,QAAQ;AACtB,KAAI;AACF,SAAO,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC/B,SAAO,OAAO;GAAE,MAAM;GAAc,UAAU;GAAM,CAAC;AACrD,SAAO,OAAO;GAAE,MAAM;GAAmB,UAAU;GAAM,CAAC;WAClD;AACR,UAAQ,MAAM"}
@@ -1,7 +1,7 @@
1
1
  import { logger } from "../console/logger.js";
2
2
  import { getPackageJson, getPackageVersion } from "./packageJson.js";
3
3
  import { REACT_LIBRARIES } from "../types/libraries.js";
4
- import { createDiagnosticMessage } from "generaltranslation/internal";
4
+ import { createDiagnosticMessage } from "generaltranslation/diagnostics";
5
5
  import { minVersion } from "semver";
6
6
  //#region src/utils/reactPackageCompatibility.ts
7
7
  const MINIMUM_REACT_PACKAGE_MAJOR = 11;
@@ -1 +1 @@
1
- {"version":3,"file":"reactPackageCompatibility.js","names":[],"sources":["../../src/utils/reactPackageCompatibility.ts"],"sourcesContent":["import { createDiagnosticMessage } from 'generaltranslation/internal';\nimport { minVersion } from 'semver';\nimport { logger } from '../console/logger.js';\nimport { REACT_LIBRARIES } from '../types/libraries.js';\nimport { getPackageJson, getPackageVersion } from './packageJson.js';\n\nconst MINIMUM_REACT_PACKAGE_MAJOR = 11;\n\nfunction permitsVersionBelowMinimum(version: string): boolean {\n // A range is potentially incompatible if it permits any version below the\n // minimum; invalid ranges (workspace:*, tags, URLs) fail open\n try {\n const minimumVersion = minVersion(version);\n return (\n minimumVersion !== null &&\n minimumVersion.major < MINIMUM_REACT_PACKAGE_MAJOR\n );\n } catch {\n return false;\n }\n}\n\nexport async function warnReactPackageCompatibility(\n suppressWarning: boolean = false,\n cwd: string = process.cwd()\n): Promise<void> {\n if (suppressWarning) return;\n\n try {\n const packageJson = await getPackageJson(cwd);\n if (!packageJson) return;\n\n const incompatiblePackages = REACT_LIBRARIES.flatMap((packageName) => {\n const version = getPackageVersion(packageName, packageJson);\n if (!version) return [];\n\n return permitsVersionBelowMinimum(version)\n ? [`${packageName}@${version}`]\n : [];\n });\n if (incompatiblePackages.length === 0) return;\n\n logger.warn(\n createDiagnosticMessage({\n source: 'gt',\n severity: 'Warning',\n whatHappened:\n 'This GT CLI may be incompatible with the listed React packages',\n why: 'versions before 11 include the ID parameter in translation keys and may cause retranslation',\n fix: 'Upgrade the listed packages to version 11 or later or install gt@2.14.58',\n wayOut:\n 'rerun with --suppress-id-compatibility-warning to hide this warning',\n details: incompatiblePackages,\n })\n );\n } catch {\n // Compatibility detection is best-effort and must not block the CLI.\n }\n}\n"],"mappings":";;;;;;AAMA,MAAM,8BAA8B;AAEpC,SAAS,2BAA2B,SAA0B;AAG5D,KAAI;EACF,MAAM,iBAAiB,WAAW,QAAQ;AAC1C,SACE,mBAAmB,QACnB,eAAe,QAAQ;SAEnB;AACN,SAAO;;;AAIX,eAAsB,8BACpB,kBAA2B,OAC3B,MAAc,QAAQ,KAAK,EACZ;AACf,KAAI,gBAAiB;AAErB,KAAI;EACF,MAAM,cAAc,MAAM,eAAe,IAAI;AAC7C,MAAI,CAAC,YAAa;EAElB,MAAM,uBAAuB,gBAAgB,SAAS,gBAAgB;GACpE,MAAM,UAAU,kBAAkB,aAAa,YAAY;AAC3D,OAAI,CAAC,QAAS,QAAO,EAAE;AAEvB,UAAO,2BAA2B,QAAQ,GACtC,CAAC,GAAG,YAAY,GAAG,UAAU,GAC7B,EAAE;IACN;AACF,MAAI,qBAAqB,WAAW,EAAG;AAEvC,SAAO,KACL,wBAAwB;GACtB,QAAQ;GACR,UAAU;GACV,cACE;GACF,KAAK;GACL,KAAK;GACL,QACE;GACF,SAAS;GACV,CAAC,CACH;SACK"}
1
+ {"version":3,"file":"reactPackageCompatibility.js","names":[],"sources":["../../src/utils/reactPackageCompatibility.ts"],"sourcesContent":["import { createDiagnosticMessage } from 'generaltranslation/diagnostics';\nimport { minVersion } from 'semver';\nimport { logger } from '../console/logger.js';\nimport { REACT_LIBRARIES } from '../types/libraries.js';\nimport { getPackageJson, getPackageVersion } from './packageJson.js';\n\nconst MINIMUM_REACT_PACKAGE_MAJOR = 11;\n\nfunction permitsVersionBelowMinimum(version: string): boolean {\n // A range is potentially incompatible if it permits any version below the\n // minimum; invalid ranges (workspace:*, tags, URLs) fail open\n try {\n const minimumVersion = minVersion(version);\n return (\n minimumVersion !== null &&\n minimumVersion.major < MINIMUM_REACT_PACKAGE_MAJOR\n );\n } catch {\n return false;\n }\n}\n\nexport async function warnReactPackageCompatibility(\n suppressWarning: boolean = false,\n cwd: string = process.cwd()\n): Promise<void> {\n if (suppressWarning) return;\n\n try {\n const packageJson = await getPackageJson(cwd);\n if (!packageJson) return;\n\n const incompatiblePackages = REACT_LIBRARIES.flatMap((packageName) => {\n const version = getPackageVersion(packageName, packageJson);\n if (!version) return [];\n\n return permitsVersionBelowMinimum(version)\n ? [`${packageName}@${version}`]\n : [];\n });\n if (incompatiblePackages.length === 0) return;\n\n logger.warn(\n createDiagnosticMessage({\n source: 'gt',\n severity: 'Warning',\n whatHappened:\n 'This GT CLI may be incompatible with the listed React packages',\n why: 'versions before 11 include the ID parameter in translation keys and may cause retranslation',\n fix: 'Upgrade the listed packages to version 11 or later or install gt@2.14.58',\n wayOut:\n 'rerun with --suppress-id-compatibility-warning to hide this warning',\n details: incompatiblePackages,\n })\n );\n } catch {\n // Compatibility detection is best-effort and must not block the CLI.\n }\n}\n"],"mappings":";;;;;;AAMA,MAAM,8BAA8B;AAEpC,SAAS,2BAA2B,SAA0B;AAG5D,KAAI;EACF,MAAM,iBAAiB,WAAW,QAAQ;AAC1C,SACE,mBAAmB,QACnB,eAAe,QAAQ;SAEnB;AACN,SAAO;;;AAIX,eAAsB,8BACpB,kBAA2B,OAC3B,MAAc,QAAQ,KAAK,EACZ;AACf,KAAI,gBAAiB;AAErB,KAAI;EACF,MAAM,cAAc,MAAM,eAAe,IAAI;AAC7C,MAAI,CAAC,YAAa;EAElB,MAAM,uBAAuB,gBAAgB,SAAS,gBAAgB;GACpE,MAAM,UAAU,kBAAkB,aAAa,YAAY;AAC3D,OAAI,CAAC,QAAS,QAAO,EAAE;AAEvB,UAAO,2BAA2B,QAAQ,GACtC,CAAC,GAAG,YAAY,GAAG,UAAU,GAC7B,EAAE;IACN;AACF,MAAI,qBAAqB,WAAW,EAAG;AAEvC,SAAO,KACL,wBAAwB;GACtB,QAAQ;GACR,UAAU;GACV,cACE;GACF,KAAK;GACL,KAAK;GACL,QACE;GACF,SAAS;GACV,CAAC,CACH;SACK"}
@@ -61,59 +61,46 @@ var PollTranslationJobsStep = class {
61
61
  };
62
62
  }
63
63
  }
64
- const msUntilNextInterval = Math.max(0, 5e3 - (Date.now() - startTime) % 5e3);
65
- return new Promise((resolve) => {
66
- let intervalCheck;
67
- setTimeout(() => {
68
- intervalCheck = setInterval(async () => {
69
- try {
70
- const jobIds = Array.from(jobFileMap.keys());
71
- const jobStatusResponse = await this.api.checkJobStatus(jobIds);
72
- for (const job of jobStatusResponse) {
73
- const jobFileProperties = jobFileMap.get(job.jobId);
74
- if (jobFileProperties) {
75
- const fileKey = `${jobFileProperties.branchId}:${jobFileProperties.fileId}:${jobFileProperties.versionId}:${jobFileProperties.locale}`;
76
- const fileProperties = filePropertiesMap.get(fileKey);
77
- if (!fileProperties) continue;
78
- if (job.status === "completed") {
79
- fileTracker.completed.set(fileKey, fileProperties);
80
- fileTracker.inProgress.delete(fileKey);
81
- jobFileMap.delete(job.jobId);
82
- } else if (job.status === "failed") {
83
- fileTracker.failed.set(fileKey, fileProperties);
84
- fileTracker.inProgress.delete(fileKey);
85
- jobFileMap.delete(job.jobId);
86
- } else if (job.status === "unknown") {
87
- fileTracker.skipped.set(fileKey, fileProperties);
88
- fileTracker.inProgress.delete(fileKey);
89
- jobFileMap.delete(job.jobId);
90
- }
91
- }
92
- }
93
- this.updateSpinner(fileTracker, fileQueryData);
94
- const elapsed = Date.now() - startTime;
95
- if (fileTracker.inProgress.size === 0 || elapsed >= timeoutDuration * 1e3) {
96
- clearInterval(intervalCheck);
97
- if (fileTracker.inProgress.size === 0) {
98
- this.spinner.stop(chalk.green("Translation jobs finished"));
99
- resolve({
100
- success: true,
101
- fileTracker
102
- });
103
- } else {
104
- this.spinner.stop(chalk.red("Timed out waiting for translation jobs"));
105
- resolve({
106
- success: false,
107
- fileTracker
108
- });
109
- }
110
- }
111
- } catch (error) {
112
- logger.error(chalk.red("Error checking job status: ") + error);
113
- }
114
- }, 5e3);
115
- }, msUntilNextInterval);
116
- });
64
+ const timeoutSeconds = Math.max(0, timeoutDuration - (Date.now() - startTime) / 1e3);
65
+ const updateJobStatuses = (statuses) => {
66
+ for (const job of statuses) {
67
+ const jobFileProperties = jobFileMap.get(job.jobId);
68
+ if (!jobFileProperties) continue;
69
+ const fileKey = `${jobFileProperties.branchId}:${jobFileProperties.fileId}:${jobFileProperties.versionId}:${jobFileProperties.locale}`;
70
+ const fileProperties = filePropertiesMap.get(fileKey);
71
+ if (!fileProperties) continue;
72
+ if (job.status === "completed") fileTracker.completed.set(fileKey, fileProperties);
73
+ else if (job.status === "failed") fileTracker.failed.set(fileKey, fileProperties);
74
+ else if (job.status === "unknown") fileTracker.skipped.set(fileKey, fileProperties);
75
+ else continue;
76
+ fileTracker.inProgress.delete(fileKey);
77
+ jobFileMap.delete(job.jobId);
78
+ }
79
+ this.updateSpinner(fileTracker, fileQueryData);
80
+ };
81
+ try {
82
+ const result = await this.api.awaitJobs([...jobFileMap.keys()], {
83
+ pollingIntervalSeconds: 5,
84
+ timeoutSeconds,
85
+ onPoll: updateJobStatuses
86
+ });
87
+ if (result.complete) updateJobStatuses(result.jobs);
88
+ } catch (error) {
89
+ this.spinner.stop(chalk.red("Error checking translation job status"));
90
+ throw error;
91
+ }
92
+ if (fileTracker.inProgress.size === 0) {
93
+ this.spinner.stop(chalk.green("Translation jobs finished"));
94
+ return {
95
+ success: true,
96
+ fileTracker
97
+ };
98
+ }
99
+ this.spinner.stop(chalk.red("Timed out waiting for translation jobs"));
100
+ return {
101
+ success: false,
102
+ fileTracker
103
+ };
117
104
  }
118
105
  updateSpinner(fileTracker, fileQueryData) {
119
106
  if (!this.spinner) return;