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.
- package/CHANGELOG.md +28 -0
- package/dist/auth/errors.d.ts +1 -0
- package/dist/auth/errors.js +12 -2
- package/dist/auth/errors.js.map +1 -1
- package/dist/auth/interactiveLogin.d.ts +2 -0
- package/dist/auth/interactiveLogin.js +22 -0
- package/dist/auth/interactiveLogin.js.map +1 -0
- package/dist/auth/oauth.js +2 -2
- package/dist/auth/oauth.js.map +1 -1
- package/dist/cli/base.d.ts +22 -4
- package/dist/cli/base.js +328 -223
- package/dist/cli/base.js.map +1 -1
- package/dist/cli/commands/api.js +15 -16
- package/dist/cli/commands/api.js.map +1 -1
- package/dist/cli/node.js +1 -1
- package/dist/cli/node.js.map +1 -1
- package/dist/cli/react.js +1 -1
- package/dist/cli/react.js.map +1 -1
- package/dist/cli/vue.js +2 -2
- package/dist/cli/vue.js.map +1 -1
- package/dist/config/generateSettings.d.ts +2 -0
- package/dist/config/generateSettings.js +13 -6
- package/dist/config/generateSettings.js.map +1 -1
- package/dist/config/resolveConfig.js +0 -4
- package/dist/config/resolveConfig.js.map +1 -1
- package/dist/config/validateSettings.js +5 -4
- package/dist/config/validateSettings.js.map +1 -1
- package/dist/console/{inkLocaleData.d.ts → localeOptions.d.ts} +7 -2
- package/dist/console/{inkLocaleData.js → localeOptions.js} +3 -8
- package/dist/console/localeOptions.js.map +1 -0
- package/dist/console/logger.d.ts +7 -0
- package/dist/console/logger.js +48 -85
- package/dist/console/logger.js.map +1 -1
- package/dist/console/logging.d.ts +11 -1
- package/dist/console/logging.js +88 -100
- package/dist/console/logging.js.map +1 -1
- package/dist/formats/files/aggregateFiles.js +1 -1
- package/dist/formats/files/supportedFiles.d.ts +3 -0
- package/dist/formats/files/supportedFiles.js +10 -1
- package/dist/formats/files/supportedFiles.js.map +1 -1
- package/dist/formats/yaml/mergeYaml.js +1 -1
- package/dist/fs/config/loadConfig.d.ts +2 -0
- package/dist/fs/config/loadConfig.js +5 -1
- package/dist/fs/config/loadConfig.js.map +1 -1
- package/dist/fs/config/setupConfig.d.ts +18 -9
- package/dist/fs/config/setupConfig.js +51 -31
- package/dist/fs/config/setupConfig.js.map +1 -1
- package/dist/fs/createLoadTranslationsFile.d.ts +22 -1
- package/dist/fs/createLoadTranslationsFile.js +43 -20
- package/dist/fs/createLoadTranslationsFile.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/hooks/postProcess.d.ts +1 -2
- package/dist/hooks/postProcess.js +1 -1
- package/dist/hooks/postProcess.js.map +1 -1
- package/dist/locadex/setupFlow.d.ts +4 -1
- package/dist/locadex/setupFlow.js +5 -3
- package/dist/locadex/setupFlow.js.map +1 -1
- package/dist/react/jsx/utils/jsxParsing/addGTIdentifierToSyntaxTree.js +1 -1
- package/dist/react/jsx/utils/jsxParsing/parseJsx.js +1 -1
- package/dist/react/jsx/utils/parseString.js +1 -1
- package/dist/react/jsx/utils/stringParsing/derivation/index.js +1 -1
- package/dist/setup/buildTools/index.d.ts +44 -0
- package/dist/setup/buildTools/index.js +10 -0
- package/dist/setup/buildTools/index.js.map +1 -0
- package/dist/setup/buildTools/vite.d.ts +2 -0
- package/dist/setup/buildTools/vite.js +61 -0
- package/dist/setup/buildTools/vite.js.map +1 -0
- package/dist/setup/developmentCredentials.d.ts +40 -5
- package/dist/setup/developmentCredentials.js +119 -37
- package/dist/setup/developmentCredentials.js.map +1 -1
- package/dist/setup/instructions/base.md +1 -1
- package/dist/setup/onboarding.d.ts +131 -0
- package/dist/setup/onboarding.js +289 -0
- package/dist/setup/onboarding.js.map +1 -0
- package/dist/setup/setupTarget.d.ts +1 -0
- package/dist/setup/setupTarget.js +52 -0
- package/dist/setup/setupTarget.js.map +1 -0
- package/dist/setup/setupViteSPA.d.ts +32 -1
- package/dist/setup/setupViteSPA.js +110 -20
- package/dist/setup/setupViteSPA.js.map +1 -1
- package/dist/setup/userInput.d.ts +12 -3
- package/dist/setup/userInput.js +27 -11
- package/dist/setup/userInput.js.map +1 -1
- package/dist/setup/wizard.d.ts +19 -3
- package/dist/setup/wizard.js +119 -105
- package/dist/setup/wizard.js.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +13 -1
- package/dist/types/index.js.map +1 -0
- package/dist/utils/credentials.d.ts +9 -6
- package/dist/utils/credentials.js +36 -22
- package/dist/utils/credentials.js.map +1 -1
- package/dist/utils/monorepoVersionCheck.d.ts +1 -1
- package/dist/utils/monorepoVersionCheck.js +5 -5
- package/dist/utils/monorepoVersionCheck.js.map +1 -1
- package/dist/utils/packageManager.js +13 -14
- package/dist/utils/packageManager.js.map +1 -1
- package/package.json +5 -9
- package/dist/console/inkFields.d.ts +0 -23
- package/dist/console/inkFields.js +0 -88
- package/dist/console/inkFields.js.map +0 -1
- package/dist/console/inkLayout.d.ts +0 -7
- package/dist/console/inkLayout.js +0 -140
- package/dist/console/inkLayout.js.map +0 -1
- package/dist/console/inkLocaleData.js.map +0 -1
- package/dist/console/inkPrompts.d.ts +0 -8
- package/dist/console/inkPrompts.js +0 -499
- package/dist/console/inkPrompts.js.map +0 -1
- package/dist/console/inkSession.d.ts +0 -3
- package/dist/console/inkSession.js +0 -42
- package/dist/console/inkSession.js.map +0 -1
- package/dist/console/inkTerminal.d.ts +0 -4
- package/dist/console/inkTerminal.js +0 -15
- package/dist/console/inkTerminal.js.map +0 -1
- package/dist/console/inkTheme.d.ts +0 -1
- package/dist/console/inkTheme.js +0 -6
- package/dist/console/inkTheme.js.map +0 -1
- package/dist/console/inkTypes.d.ts +0 -69
- package/dist/console/inkTypes.js +0 -1
- package/dist/console/inkUtils.d.ts +0 -24
- package/dist/console/inkUtils.js +0 -87
- package/dist/console/inkUtils.js.map +0 -1
- package/dist/console/terminalSession.d.ts +0 -5
- package/dist/console/terminalSession.js +0 -38
- package/dist/console/terminalSession.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { createDiagnosticMessage, formatDiagnosticErrorDetails } from "generaltranslation/internal";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import fs from "node:fs";
|
|
4
|
-
import { createDiagnosticMessage, formatDiagnosticErrorDetails } from "generaltranslation/internal";
|
|
5
5
|
import { execFile } from "node:child_process";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import dotenv from "dotenv";
|
|
@@ -68,6 +68,7 @@ function gitCheckFailedError(file, stderr) {
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
const FRAMEWORK_ENV_PREFIXES = {
|
|
71
|
+
"next-app": "NEXT_PUBLIC_",
|
|
71
72
|
"next-pages": "NEXT_PUBLIC_",
|
|
72
73
|
vite: "VITE_",
|
|
73
74
|
gatsby: "GATSBY_",
|
|
@@ -83,16 +84,15 @@ function getDevelopmentEnvNames(framework) {
|
|
|
83
84
|
};
|
|
84
85
|
}
|
|
85
86
|
/**
|
|
86
|
-
* Whether the project
|
|
87
|
-
* key
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* framework prefix. Login is checked separately.
|
|
87
|
+
* Whether the project has the framework's development key, or an explicit
|
|
88
|
+
* API key for a server-only runtime. A tooling key cannot enable client-side
|
|
89
|
+
* hot reload, including in Next.js App Router. `settings.projectId` already
|
|
90
|
+
* resolves every framework prefix. Login is checked separately.
|
|
91
91
|
*/
|
|
92
92
|
function areCredentialsSet(settings, framework) {
|
|
93
93
|
const { devApiKey } = getDevelopmentEnvNames(framework);
|
|
94
|
-
const
|
|
95
|
-
return Boolean(settings.projectId && (process.env[devApiKey] || !
|
|
94
|
+
const needsPublicKey = Boolean(framework && FRAMEWORK_ENV_PREFIXES[framework]);
|
|
95
|
+
return Boolean(settings.projectId && (process.env[devApiKey] || !needsPublicKey && settings.apiKey));
|
|
96
96
|
}
|
|
97
97
|
function assignedName(line) {
|
|
98
98
|
return /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line)?.[1];
|
|
@@ -209,9 +209,13 @@ async function inspectGitExposure(file) {
|
|
|
209
209
|
* Git. A referent inside any worktree must already be ignored there; only
|
|
210
210
|
* .env.local itself gets an ignore rule added, by setCredentials.
|
|
211
211
|
*/
|
|
212
|
-
async function inspectCredentialsEnvFile(cwd = process.cwd()) {
|
|
212
|
+
async function inspectCredentialsEnvFile(cwd = process.cwd(), contentCheck) {
|
|
213
213
|
const envFile = path.resolve(cwd, ".env.local");
|
|
214
214
|
const existing = await resolveEnvFile(envFile);
|
|
215
|
+
if (existing && contentCheck) updateCredentialsEnvContent(await fs.promises.readFile(existing.target, "utf8"), {
|
|
216
|
+
projectId: "gt-preflight",
|
|
217
|
+
apiKey: "gt-preflight"
|
|
218
|
+
}, contentCheck.framework);
|
|
215
219
|
const exposure = await inspectGitExposure(envFile);
|
|
216
220
|
if (exposure === "tracked") throw new Error(trackedEnvFileError(envFile));
|
|
217
221
|
if (existing && existing.target !== envFile) {
|
|
@@ -249,16 +253,14 @@ async function writeEnvFileAtomically(target, content, mode) {
|
|
|
249
253
|
throw error;
|
|
250
254
|
}
|
|
251
255
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
let envContent = existingEnvFile ? await fs.promises.readFile(existingEnvFile.target, "utf8") : "";
|
|
256
|
+
/**
|
|
257
|
+
* .env.local content with the runtime credentials set. Only the hot-reload
|
|
258
|
+
* key: the CLI itself acts as the signed-in user, and CI keys are created
|
|
259
|
+
* deliberately rather than dropped into .env.local. Other lines, comments,
|
|
260
|
+
* and any GT_API_KEY are left untouched. Throws when the edit could corrupt
|
|
261
|
+
* a multiline value.
|
|
262
|
+
*/
|
|
263
|
+
function updateCredentialsEnvContent(envContent, credentials, framework) {
|
|
262
264
|
const names = getDevelopmentEnvNames(framework);
|
|
263
265
|
const targetNames = Object.values(names);
|
|
264
266
|
const targetOpensMultiline = envContent.split("\n").some((line) => targetNames.includes(assignedName(line) ?? "") && opensMultilineValue(line));
|
|
@@ -268,10 +270,22 @@ async function setCredentials(credentials, framework, cwd = process.cwd()) {
|
|
|
268
270
|
[names.projectId]: credentials.projectId,
|
|
269
271
|
[names.devApiKey]: credentials.apiKey
|
|
270
272
|
};
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const updated = dotenv.parse(
|
|
273
|
+
let updatedContent = upsertEnvAssignment(envContent, names.projectId, credentials.projectId);
|
|
274
|
+
updatedContent = upsertEnvAssignment(updatedContent, names.devApiKey, credentials.apiKey);
|
|
275
|
+
const updated = dotenv.parse(updatedContent);
|
|
274
276
|
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);
|
|
277
|
+
return updatedContent;
|
|
278
|
+
}
|
|
279
|
+
async function setCredentials(credentials, framework, cwd = process.cwd()) {
|
|
280
|
+
const { envFile, existing: existingEnvFile, exposure } = await inspectCredentialsEnvFile(cwd);
|
|
281
|
+
const envContent = updateCredentialsEnvContent(existingEnvFile ? await fs.promises.readFile(existingEnvFile.target, "utf8") : "", credentials, framework);
|
|
282
|
+
if (exposure === "unignored") {
|
|
283
|
+
await appendGitignoreRule(cwd);
|
|
284
|
+
if (await inspectGitExposure(envFile) !== "ignored") throw new Error(unignoredEnvFileError(envFile));
|
|
285
|
+
} else if (exposure === "no-repository") {
|
|
286
|
+
const gitignoreFile = path.join(cwd, ".gitignore");
|
|
287
|
+
if (!fs.existsSync(gitignoreFile) || (await fs.promises.readFile(gitignoreFile, "utf8")).trimEnd().split(/\r?\n/).at(-1) !== ".env.local") await appendGitignoreRule(cwd);
|
|
288
|
+
}
|
|
275
289
|
await writeEnvFileAtomically(existingEnvFile?.target ?? envFile, envContent, existingEnvFile?.mode ?? 384);
|
|
276
290
|
}
|
|
277
291
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
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// Include server-rendered frameworks with client-side development translation.\nconst FRAMEWORK_ENV_PREFIXES: Partial<\n Record<NonNullable<SupportedFrameworks>, string>\n> = {\n 'next-app': 'NEXT_PUBLIC_',\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 has the framework's development key, or an explicit\n * API key for a server-only runtime. A tooling key cannot enable client-side\n * hot reload, including in Next.js App Router. `settings.projectId` already\n * resolves every 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 needsPublicKey = Boolean(\n framework && FRAMEWORK_ENV_PREFIXES[framework]\n );\n return Boolean(\n settings.projectId &&\n (process.env[devApiKey] || (!needsPublicKey && 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 /** Also checks that the framework's credentials can be saved safely. */\n contentCheck?: { framework?: SupportedFrameworks }\n): Promise<CredentialsEnvFile> {\n const envFile = path.resolve(cwd, '.env.local');\n const existing = await resolveEnvFile(envFile);\n if (existing && contentCheck) {\n updateCredentialsEnvContent(\n await fs.promises.readFile(existing.target, 'utf8'),\n { projectId: 'gt-preflight', apiKey: 'gt-preflight' },\n contentCheck.framework\n );\n }\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/**\n * .env.local content with the runtime credentials set. Only the hot-reload\n * key: the CLI itself acts as the signed-in user, and CI keys are created\n * deliberately rather than dropped into .env.local. Other lines, comments,\n * and any GT_API_KEY are left untouched. Throws when the edit could corrupt\n * a multiline value.\n */\nfunction updateCredentialsEnvContent(\n envContent: string,\n credentials: Credentials,\n framework?: SupportedFrameworks\n): string {\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 let updatedContent = upsertEnvAssignment(\n envContent,\n names.projectId,\n credentials.projectId\n );\n updatedContent = upsertEnvAssignment(\n updatedContent,\n names.devApiKey,\n credentials.apiKey\n );\n\n const updated = dotenv.parse(updatedContent);\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 return updatedContent;\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 // Rechecked here: the file may have changed since the setup preflight.\n const envContent = updateCredentialsEnvContent(\n existingEnvFile\n ? await fs.promises.readFile(existingEnvFile.target, 'utf8')\n : '',\n credentials,\n framework\n );\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 await writeEnvFileAtomically(\n existingEnvFile?.target ?? envFile,\n envContent,\n existingEnvFile?.mode ?? 0o600\n );\n}\n"],"mappings":"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"}
|
|
@@ -5,4 +5,4 @@ import { type GTLibrary } from '../types/libraries.js';
|
|
|
5
5
|
* Silently returns if not in a monorepo or if all versions are consistent.
|
|
6
6
|
* Can be skipped via the --skip-version-check flag or "skipVersionCheck": true in gt.config.json.
|
|
7
7
|
*/
|
|
8
|
-
export declare function checkMonorepoVersionConsistency(libraries: readonly GTLibrary[]): void;
|
|
8
|
+
export declare function checkMonorepoVersionConsistency(libraries: readonly GTLibrary[], configPath?: string): void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { logger } from "../console/logger.js";
|
|
2
1
|
import "../types/libraries.js";
|
|
2
|
+
import { logErrorAndExit } from "../console/logging.js";
|
|
3
|
+
import { loadConfig, withJsonExtension } from "../fs/config/loadConfig.js";
|
|
3
4
|
import { resolveConfig } from "../config/resolveConfig.js";
|
|
4
5
|
import chalk from "chalk";
|
|
5
6
|
import path from "node:path";
|
|
@@ -200,17 +201,16 @@ function formatMismatchError(mismatches) {
|
|
|
200
201
|
* Silently returns if not in a monorepo or if all versions are consistent.
|
|
201
202
|
* Can be skipped via the --skip-version-check flag or "skipVersionCheck": true in gt.config.json.
|
|
202
203
|
*/
|
|
203
|
-
function checkMonorepoVersionConsistency(libraries) {
|
|
204
|
+
function checkMonorepoVersionConsistency(libraries, configPath) {
|
|
204
205
|
const cwd = process.cwd();
|
|
205
|
-
if (resolveConfig(cwd)?.config?.skipVersionCheck) return;
|
|
206
|
+
if ((configPath ? loadConfig(withJsonExtension(configPath)) : resolveConfig(cwd)?.config)?.skipVersionCheck) return;
|
|
206
207
|
const rootDir = findMonorepoRoot(cwd);
|
|
207
208
|
if (!rootDir) return;
|
|
208
209
|
const workspaceDirs = scanForPackageDirs(rootDir);
|
|
209
210
|
if (workspaceDirs.length <= 1) return;
|
|
210
211
|
const mismatches = findVersionMismatches(workspaceDirs, createPackageJsonReader(), libraries);
|
|
211
212
|
if (mismatches.length === 0) return;
|
|
212
|
-
|
|
213
|
-
process.exit(1);
|
|
213
|
+
logErrorAndExit(formatMismatchError(mismatches));
|
|
214
214
|
}
|
|
215
215
|
//#endregion
|
|
216
216
|
export { checkMonorepoVersionConsistency };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"monorepoVersionCheck.js","names":[],"sources":["../../src/utils/monorepoVersionCheck.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport fg from 'fast-glob';\nimport chalk from 'chalk';\nimport { logger } from '../console/logger.js';\nimport { Libraries, type GTLibrary } from '../types/libraries.js';\nimport { resolveConfig } from '../config/resolveConfig.js';\n\ninterface PackageJson {\n name?: string;\n version?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\ninterface VersionInfo {\n version: string;\n workspaces: string[];\n}\n\ninterface VersionMismatch {\n packageName: string;\n versions: VersionInfo[];\n}\n\ntype PackageJsonReader = (workspaceDir: string) => PackageJson | null;\n\ninterface SyncedVersionGroup {\n packages: readonly GTLibrary[];\n minVersion?: string;\n}\n\nconst SYNCED_VERSION_GROUPS: readonly SyncedVersionGroup[] = [\n {\n packages: [Libraries.GT_REACT, Libraries.GT_REACT_NATIVE],\n minVersion: '10.19.1',\n },\n];\n\nconst LOCKFILES = [\n 'pnpm-lock.yaml',\n 'package-lock.json',\n 'yarn.lock',\n 'bun.lock',\n 'bun.lockb',\n 'deno.lock',\n];\n\n/**\n * Walk up from startDir to find the monorepo root by looking for a lockfile.\n */\nfunction findMonorepoRoot(startDir: string): string | null {\n let dir = startDir;\n while (true) {\n if (LOCKFILES.some((lf) => fs.existsSync(path.join(dir, lf)))) {\n return dir;\n }\n\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Scan for all directories containing package.json under the root.\n */\nfunction scanForPackageDirs(rootDir: string): string[] {\n const matches = fg.sync('**/package.json', {\n cwd: rootDir,\n absolute: true,\n onlyFiles: true,\n ignore: ['**/node_modules/**'],\n });\n\n return matches.map((m) => path.dirname(m)).filter((dir) => dir !== rootDir);\n}\n\n/**\n * Creates a cached reader for workspace package.json files.\n * Cache is scoped to a single check invocation to avoid stale data.\n */\nfunction createPackageJsonReader(): PackageJsonReader {\n const cache = new Map<string, PackageJson | null>();\n return (workspaceDir: string) => {\n if (cache.has(workspaceDir)) return cache.get(workspaceDir) ?? null;\n const pkgPath = path.join(workspaceDir, 'package.json');\n if (!fs.existsSync(pkgPath)) {\n cache.set(workspaceDir, null);\n return null;\n }\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as PackageJson;\n cache.set(workspaceDir, pkg);\n return pkg;\n } catch {\n cache.set(workspaceDir, null);\n return null;\n }\n };\n}\n\n/**\n * Check if a version specifier is a real semver version\n */\nfunction isSemverSpecifier(version: string): boolean {\n return /^[\\^~>=<*]?\\d/.test(version);\n}\n\n/**\n * Get the declared version specifier for a GT package from a workspace's package.json.\n * Returns null if the package is not found or uses a non-semver protocol\n * (e.g. workspace:*, link:, file:, etc.)\n */\nfunction getDeclaredVersion(\n packageName: string,\n workspaceDir: string,\n readPkgJson: PackageJsonReader\n): string | null {\n const pkg = readPkgJson(workspaceDir);\n if (!pkg) return null;\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n const version = deps[packageName];\n if (!version || !isSemverSpecifier(version)) return null;\n return version;\n}\n\n/**\n * Scan all packages for mismatched GT package version specifiers.\n * Compares the declared versions in each package.json directly.\n */\nfunction findVersionMismatches(\n workspaceDirs: string[],\n readPkgJson: PackageJsonReader,\n libraries: readonly GTLibrary[]\n): VersionMismatch[] {\n // Map: packageName -> Map<versionSpecifier, workspaceNames[]>\n const packageVersions = new Map<string, Map<string, string[]>>();\n\n for (const wsDir of workspaceDirs) {\n const wsName = getWorkspaceName(wsDir, readPkgJson);\n\n for (const pkg of libraries) {\n const version = getDeclaredVersion(pkg, wsDir, readPkgJson);\n if (!version) continue;\n\n if (!packageVersions.has(pkg)) {\n packageVersions.set(pkg, new Map());\n }\n const versionMap = packageVersions.get(pkg)!;\n if (!versionMap.has(version)) {\n versionMap.set(version, []);\n }\n versionMap.get(version)!.push(wsName);\n }\n }\n\n const mismatches: VersionMismatch[] = [];\n for (const [packageName, versionMap] of packageVersions) {\n if (versionMap.size > 1) {\n mismatches.push({\n packageName,\n versions: getSortedVersionInfo(versionMap),\n });\n }\n }\n\n return [\n ...mismatches,\n ...findSyncedPackageVersionMismatches(packageVersions, libraries),\n ];\n}\n\n/**\n * Find mismatches between package names that must stay on the same version.\n */\nfunction findSyncedPackageVersionMismatches(\n packageVersions: Map<string, Map<string, string[]>>,\n libraries: readonly GTLibrary[]\n): VersionMismatch[] {\n const enabledLibraries = new Set(libraries);\n const mismatches: VersionMismatch[] = [];\n\n for (const group of SYNCED_VERSION_GROUPS) {\n const enabledGroupPackages = group.packages.filter((pkg) =>\n enabledLibraries.has(pkg)\n );\n if (enabledGroupPackages.length <= 1) continue;\n\n const presentGroupPackages = enabledGroupPackages.filter((pkg) =>\n packageVersions.has(pkg)\n );\n if (presentGroupPackages.length <= 1) continue;\n\n const versionMap = new Map<string, string[]>();\n for (const packageName of presentGroupPackages) {\n const packageVersionMap = packageVersions.get(packageName)!;\n\n for (const [version, workspaces] of packageVersionMap) {\n const locations = versionMap.get(version) ?? [];\n locations.push(\n ...workspaces.map((workspace) => `${packageName} in ${workspace}`)\n );\n versionMap.set(version, locations);\n }\n }\n\n if (\n versionMap.size > 1 &&\n shouldCheckSyncedVersionGroup(group, versionMap)\n ) {\n mismatches.push({\n packageName: presentGroupPackages.join(' / '),\n versions: getSortedVersionInfo(versionMap),\n });\n }\n }\n\n return mismatches;\n}\n\nfunction shouldCheckSyncedVersionGroup(\n group: SyncedVersionGroup,\n versionMap: Map<string, string[]>\n): boolean {\n const { minVersion } = group;\n if (!minVersion) return true;\n\n return [...versionMap.keys()].some((version) =>\n isVersionAtLeast(version, minVersion)\n );\n}\n\nfunction isVersionAtLeast(version: string, minVersion: string): boolean {\n const parsedVersion = parseSemverVersion(version);\n const parsedMinVersion = parseSemverVersion(minVersion);\n if (!parsedVersion || !parsedMinVersion) return true;\n\n return compareSemverVersions(parsedVersion, parsedMinVersion) >= 0;\n}\n\nfunction parseSemverVersion(\n version: string\n): { major: number; minor: number; patch: number } | null {\n const match = version\n .trim()\n .match(/^[\\^~<>=\\s]*(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?/);\n if (!match) return null;\n\n return {\n major: Number(match[1]),\n minor: Number(match[2] ?? 0),\n patch: Number(match[3] ?? 0),\n };\n}\n\nfunction compareSemverVersions(\n a: { major: number; minor: number; patch: number },\n b: { major: number; minor: number; patch: number }\n): number {\n if (a.major !== b.major) return a.major - b.major;\n if (a.minor !== b.minor) return a.minor - b.minor;\n return a.patch - b.patch;\n}\n\nfunction getSortedVersionInfo(\n versionMap: Map<string, string[]>\n): VersionInfo[] {\n const versions: VersionInfo[] = [];\n for (const [version, workspaces] of versionMap) {\n versions.push({ version, workspaces });\n }\n // Sort by version descending so the latest appears first\n versions.sort((a, b) =>\n b.version.localeCompare(a.version, undefined, { numeric: true })\n );\n return versions;\n}\n\n/**\n * Get a human-readable name for a workspace directory.\n */\nfunction getWorkspaceName(\n wsDir: string,\n readPkgJson: PackageJsonReader\n): string {\n const pkg = readPkgJson(wsDir);\n return pkg?.name ?? path.basename(wsDir);\n}\n\n/**\n * Format version mismatches into a human-readable error message.\n */\nfunction formatMismatchError(mismatches: VersionMismatch[]): string {\n const lines: string[] = [\n chalk.red.bold(\n 'Mismatched GT package versions detected across your monorepo!'\n ),\n '',\n chalk.yellow(\n 'Please update all workspaces to use the same version of each GT package, then reinstall.'\n ),\n '',\n ];\n\n for (const mismatch of mismatches) {\n lines.push(chalk.white.bold(` ${mismatch.packageName}:`));\n for (const { version, workspaces } of mismatch.versions) {\n lines.push(\n ` ${chalk.cyan(version)} ${chalk.dim('←')} ${workspaces.join(', ')}`\n );\n }\n lines.push('');\n }\n\n lines.push(\n chalk.dim(\n 'To skip this check, use --skip-version-check or set \"skipVersionCheck\": true in gt.config.json.\\n'\n )\n );\n\n return lines.join('\\n');\n}\n\n/**\n * Run the monorepo version consistency check.\n * If mismatched GT package versions are found, logs an error and exits with code 1.\n * Silently returns if not in a monorepo or if all versions are consistent.\n * Can be skipped via the --skip-version-check flag or \"skipVersionCheck\": true in gt.config.json.\n */\nexport function checkMonorepoVersionConsistency(\n libraries: readonly GTLibrary[]\n): void {\n const cwd = process.cwd();\n\n // Check if skipped via config\n const resolved = resolveConfig(cwd);\n if (resolved?.config?.skipVersionCheck) return;\n\n const rootDir = findMonorepoRoot(cwd);\n if (!rootDir) return; // No lockfile found — nothing to check\n\n const workspaceDirs = scanForPackageDirs(rootDir);\n if (workspaceDirs.length <= 1) return; // Single package — no mismatches possible\n\n const readPkgJson = createPackageJsonReader();\n const mismatches = findVersionMismatches(\n workspaceDirs,\n readPkgJson,\n libraries\n );\n if (mismatches.length === 0) return; // All consistent\n\n logger.error(formatMismatchError(mismatches));\n process.exit(1);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"monorepoVersionCheck.js","names":[],"sources":["../../src/utils/monorepoVersionCheck.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport fg from 'fast-glob';\nimport chalk from 'chalk';\nimport { logErrorAndExit } from '../console/logging.js';\nimport { Libraries, type GTLibrary } from '../types/libraries.js';\nimport { resolveConfig } from '../config/resolveConfig.js';\nimport { loadConfig, withJsonExtension } from '../fs/config/loadConfig.js';\n\ninterface PackageJson {\n name?: string;\n version?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\ninterface VersionInfo {\n version: string;\n workspaces: string[];\n}\n\ninterface VersionMismatch {\n packageName: string;\n versions: VersionInfo[];\n}\n\ntype PackageJsonReader = (workspaceDir: string) => PackageJson | null;\n\ninterface SyncedVersionGroup {\n packages: readonly GTLibrary[];\n minVersion?: string;\n}\n\nconst SYNCED_VERSION_GROUPS: readonly SyncedVersionGroup[] = [\n {\n packages: [Libraries.GT_REACT, Libraries.GT_REACT_NATIVE],\n minVersion: '10.19.1',\n },\n];\n\nconst LOCKFILES = [\n 'pnpm-lock.yaml',\n 'package-lock.json',\n 'yarn.lock',\n 'bun.lock',\n 'bun.lockb',\n 'deno.lock',\n];\n\n/**\n * Walk up from startDir to find the monorepo root by looking for a lockfile.\n */\nfunction findMonorepoRoot(startDir: string): string | null {\n let dir = startDir;\n while (true) {\n if (LOCKFILES.some((lf) => fs.existsSync(path.join(dir, lf)))) {\n return dir;\n }\n\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Scan for all directories containing package.json under the root.\n */\nfunction scanForPackageDirs(rootDir: string): string[] {\n const matches = fg.sync('**/package.json', {\n cwd: rootDir,\n absolute: true,\n onlyFiles: true,\n ignore: ['**/node_modules/**'],\n });\n\n return matches.map((m) => path.dirname(m)).filter((dir) => dir !== rootDir);\n}\n\n/**\n * Creates a cached reader for workspace package.json files.\n * Cache is scoped to a single check invocation to avoid stale data.\n */\nfunction createPackageJsonReader(): PackageJsonReader {\n const cache = new Map<string, PackageJson | null>();\n return (workspaceDir: string) => {\n if (cache.has(workspaceDir)) return cache.get(workspaceDir) ?? null;\n const pkgPath = path.join(workspaceDir, 'package.json');\n if (!fs.existsSync(pkgPath)) {\n cache.set(workspaceDir, null);\n return null;\n }\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as PackageJson;\n cache.set(workspaceDir, pkg);\n return pkg;\n } catch {\n cache.set(workspaceDir, null);\n return null;\n }\n };\n}\n\n/**\n * Check if a version specifier is a real semver version\n */\nfunction isSemverSpecifier(version: string): boolean {\n return /^[\\^~>=<*]?\\d/.test(version);\n}\n\n/**\n * Get the declared version specifier for a GT package from a workspace's package.json.\n * Returns null if the package is not found or uses a non-semver protocol\n * (e.g. workspace:*, link:, file:, etc.)\n */\nfunction getDeclaredVersion(\n packageName: string,\n workspaceDir: string,\n readPkgJson: PackageJsonReader\n): string | null {\n const pkg = readPkgJson(workspaceDir);\n if (!pkg) return null;\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n const version = deps[packageName];\n if (!version || !isSemverSpecifier(version)) return null;\n return version;\n}\n\n/**\n * Scan all packages for mismatched GT package version specifiers.\n * Compares the declared versions in each package.json directly.\n */\nfunction findVersionMismatches(\n workspaceDirs: string[],\n readPkgJson: PackageJsonReader,\n libraries: readonly GTLibrary[]\n): VersionMismatch[] {\n // Map: packageName -> Map<versionSpecifier, workspaceNames[]>\n const packageVersions = new Map<string, Map<string, string[]>>();\n\n for (const wsDir of workspaceDirs) {\n const wsName = getWorkspaceName(wsDir, readPkgJson);\n\n for (const pkg of libraries) {\n const version = getDeclaredVersion(pkg, wsDir, readPkgJson);\n if (!version) continue;\n\n if (!packageVersions.has(pkg)) {\n packageVersions.set(pkg, new Map());\n }\n const versionMap = packageVersions.get(pkg)!;\n if (!versionMap.has(version)) {\n versionMap.set(version, []);\n }\n versionMap.get(version)!.push(wsName);\n }\n }\n\n const mismatches: VersionMismatch[] = [];\n for (const [packageName, versionMap] of packageVersions) {\n if (versionMap.size > 1) {\n mismatches.push({\n packageName,\n versions: getSortedVersionInfo(versionMap),\n });\n }\n }\n\n return [\n ...mismatches,\n ...findSyncedPackageVersionMismatches(packageVersions, libraries),\n ];\n}\n\n/**\n * Find mismatches between package names that must stay on the same version.\n */\nfunction findSyncedPackageVersionMismatches(\n packageVersions: Map<string, Map<string, string[]>>,\n libraries: readonly GTLibrary[]\n): VersionMismatch[] {\n const enabledLibraries = new Set(libraries);\n const mismatches: VersionMismatch[] = [];\n\n for (const group of SYNCED_VERSION_GROUPS) {\n const enabledGroupPackages = group.packages.filter((pkg) =>\n enabledLibraries.has(pkg)\n );\n if (enabledGroupPackages.length <= 1) continue;\n\n const presentGroupPackages = enabledGroupPackages.filter((pkg) =>\n packageVersions.has(pkg)\n );\n if (presentGroupPackages.length <= 1) continue;\n\n const versionMap = new Map<string, string[]>();\n for (const packageName of presentGroupPackages) {\n const packageVersionMap = packageVersions.get(packageName)!;\n\n for (const [version, workspaces] of packageVersionMap) {\n const locations = versionMap.get(version) ?? [];\n locations.push(\n ...workspaces.map((workspace) => `${packageName} in ${workspace}`)\n );\n versionMap.set(version, locations);\n }\n }\n\n if (\n versionMap.size > 1 &&\n shouldCheckSyncedVersionGroup(group, versionMap)\n ) {\n mismatches.push({\n packageName: presentGroupPackages.join(' / '),\n versions: getSortedVersionInfo(versionMap),\n });\n }\n }\n\n return mismatches;\n}\n\nfunction shouldCheckSyncedVersionGroup(\n group: SyncedVersionGroup,\n versionMap: Map<string, string[]>\n): boolean {\n const { minVersion } = group;\n if (!minVersion) return true;\n\n return [...versionMap.keys()].some((version) =>\n isVersionAtLeast(version, minVersion)\n );\n}\n\nfunction isVersionAtLeast(version: string, minVersion: string): boolean {\n const parsedVersion = parseSemverVersion(version);\n const parsedMinVersion = parseSemverVersion(minVersion);\n if (!parsedVersion || !parsedMinVersion) return true;\n\n return compareSemverVersions(parsedVersion, parsedMinVersion) >= 0;\n}\n\nfunction parseSemverVersion(\n version: string\n): { major: number; minor: number; patch: number } | null {\n const match = version\n .trim()\n .match(/^[\\^~<>=\\s]*(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?/);\n if (!match) return null;\n\n return {\n major: Number(match[1]),\n minor: Number(match[2] ?? 0),\n patch: Number(match[3] ?? 0),\n };\n}\n\nfunction compareSemverVersions(\n a: { major: number; minor: number; patch: number },\n b: { major: number; minor: number; patch: number }\n): number {\n if (a.major !== b.major) return a.major - b.major;\n if (a.minor !== b.minor) return a.minor - b.minor;\n return a.patch - b.patch;\n}\n\nfunction getSortedVersionInfo(\n versionMap: Map<string, string[]>\n): VersionInfo[] {\n const versions: VersionInfo[] = [];\n for (const [version, workspaces] of versionMap) {\n versions.push({ version, workspaces });\n }\n // Sort by version descending so the latest appears first\n versions.sort((a, b) =>\n b.version.localeCompare(a.version, undefined, { numeric: true })\n );\n return versions;\n}\n\n/**\n * Get a human-readable name for a workspace directory.\n */\nfunction getWorkspaceName(\n wsDir: string,\n readPkgJson: PackageJsonReader\n): string {\n const pkg = readPkgJson(wsDir);\n return pkg?.name ?? path.basename(wsDir);\n}\n\n/**\n * Format version mismatches into a human-readable error message.\n */\nfunction formatMismatchError(mismatches: VersionMismatch[]): string {\n const lines: string[] = [\n chalk.red.bold(\n 'Mismatched GT package versions detected across your monorepo!'\n ),\n '',\n chalk.yellow(\n 'Please update all workspaces to use the same version of each GT package, then reinstall.'\n ),\n '',\n ];\n\n for (const mismatch of mismatches) {\n lines.push(chalk.white.bold(` ${mismatch.packageName}:`));\n for (const { version, workspaces } of mismatch.versions) {\n lines.push(\n ` ${chalk.cyan(version)} ${chalk.dim('←')} ${workspaces.join(', ')}`\n );\n }\n lines.push('');\n }\n\n lines.push(\n chalk.dim(\n 'To skip this check, use --skip-version-check or set \"skipVersionCheck\": true in gt.config.json.\\n'\n )\n );\n\n return lines.join('\\n');\n}\n\n/**\n * Run the monorepo version consistency check.\n * If mismatched GT package versions are found, logs an error and exits with code 1.\n * Silently returns if not in a monorepo or if all versions are consistent.\n * Can be skipped via the --skip-version-check flag or \"skipVersionCheck\": true in gt.config.json.\n */\nexport function checkMonorepoVersionConsistency(\n libraries: readonly GTLibrary[],\n configPath?: string\n): void {\n const cwd = process.cwd();\n\n // Check if skipped via config; an explicit --config replaces discovery.\n const config = configPath\n ? loadConfig(withJsonExtension(configPath))\n : resolveConfig(cwd)?.config;\n if (config?.skipVersionCheck) return;\n\n const rootDir = findMonorepoRoot(cwd);\n if (!rootDir) return; // No lockfile found — nothing to check\n\n const workspaceDirs = scanForPackageDirs(rootDir);\n if (workspaceDirs.length <= 1) return; // Single package — no mismatches possible\n\n const readPkgJson = createPackageJsonReader();\n const mismatches = findVersionMismatches(\n workspaceDirs,\n readPkgJson,\n libraries\n );\n if (mismatches.length === 0) return; // All consistent\n\n // Recorded as the exit error so a `--json` setup run can report it.\n logErrorAndExit(formatMismatchError(mismatches));\n}\n"],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { getPackageJson, updatePackageJson } from "./packageJson.js";
|
|
|
2
2
|
import { promptSelect } from "../console/logging.js";
|
|
3
3
|
import * as fs$1 from "fs";
|
|
4
4
|
import * as path$1 from "path";
|
|
5
|
+
import { parse } from "semver";
|
|
5
6
|
//#region src/utils/packageManager.ts
|
|
6
7
|
var NoPackageManagerError = class extends Error {
|
|
7
8
|
constructor(message) {
|
|
@@ -183,7 +184,7 @@ const NPM = {
|
|
|
183
184
|
devDependencyFlag: "--save-dev",
|
|
184
185
|
detect: (cwd) => {
|
|
185
186
|
try {
|
|
186
|
-
return fs$1.existsSync(path$1.join(cwd,
|
|
187
|
+
return ["package-lock.json", "npm-shrinkwrap.json"].some((lockFile) => fs$1.existsSync(path$1.join(cwd, lockFile)));
|
|
187
188
|
} catch {
|
|
188
189
|
return false;
|
|
189
190
|
}
|
|
@@ -215,30 +216,28 @@ function _detectPackageManger(cwd) {
|
|
|
215
216
|
return null;
|
|
216
217
|
}
|
|
217
218
|
async function getPackageManager(cwd = process.cwd(), specifiedPackageManager, errorIfNotFound = false) {
|
|
218
|
-
const globalWizard = global;
|
|
219
|
-
if (globalWizard._gt_wizard_cached_package_manager) return globalWizard._gt_wizard_cached_package_manager;
|
|
220
219
|
if (specifiedPackageManager) {
|
|
221
220
|
const packageManager = packageManagers.find((packageManager) => packageManager.id === specifiedPackageManager);
|
|
222
|
-
if (packageManager)
|
|
223
|
-
globalWizard._gt_wizard_cached_package_manager = packageManager;
|
|
224
|
-
return packageManager;
|
|
225
|
-
}
|
|
221
|
+
if (packageManager) return packageManager;
|
|
226
222
|
}
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
223
|
+
const declaration = (await getPackageJson(cwd))?.packageManager;
|
|
224
|
+
const match = typeof declaration === "string" && /^([^@]+)@(.+)$/.exec(declaration);
|
|
225
|
+
const version = match && parse(match[2]);
|
|
226
|
+
if (match && version) {
|
|
227
|
+
const id = match[1] === "yarn" ? version.major <= 1 ? YARN_V1.id : YARN_V2.id : match[1];
|
|
228
|
+
const declaredPackageManager = packageManagers.find((manager) => manager.id === id);
|
|
229
|
+
if (declaredPackageManager) return declaredPackageManager;
|
|
231
230
|
}
|
|
231
|
+
const detectedPackageManager = _detectPackageManger(cwd);
|
|
232
|
+
if (detectedPackageManager) return detectedPackageManager;
|
|
232
233
|
if (errorIfNotFound) throw new NoPackageManagerError("No package manager found");
|
|
233
|
-
|
|
234
|
+
return promptSelect({
|
|
234
235
|
message: "Select your package manager.",
|
|
235
236
|
options: packageManagers.map((packageManager) => ({
|
|
236
237
|
value: packageManager,
|
|
237
238
|
label: packageManager.label
|
|
238
239
|
}))
|
|
239
240
|
});
|
|
240
|
-
globalWizard._gt_wizard_cached_package_manager = selectedPackageManager;
|
|
241
|
-
return selectedPackageManager;
|
|
242
241
|
}
|
|
243
242
|
//#endregion
|
|
244
243
|
export { BUN, DENO, NPM, NoPackageManagerError, PNPM, YARN_V1, YARN_V2, _detectPackageManger, getPackageManager, packageManagers };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageManager.js","names":["fs","path"],"sources":["../../src/utils/packageManager.ts"],"sourcesContent":["// This file is MIT licensed and was adapted from https://github.com/getsentry/sentry-wizard/blob/master/src/utils/package-manager.ts and https://github.com/getsentry/sentry-wizard/blob/master/src/utils/clack/index.ts\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { getPackageJson, updatePackageJson } from './packageJson.js';\nimport { promptSelect } from '../console/logging.js';\n\nexport interface PackageManager {\n id: string;\n name: string;\n label: string;\n installCommand: string;\n installAllCommand: string;\n buildCommand: string;\n /* The command that the package manager uses to run a script from package.json */\n runScriptCommand: string;\n flags: string;\n forceInstallFlag: string;\n devDependencyFlag: string;\n registry?: string;\n detect: (cwd: string) => boolean;\n addOverride: (pkgName: string, pkgVersion: string) => Promise<void>;\n}\n\nexport class NoPackageManagerError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'NoPackageManagerError';\n }\n}\n\nexport const BUN: PackageManager = {\n id: 'bun',\n name: 'bun',\n label: 'Bun',\n installCommand: 'add',\n installAllCommand: 'bun install',\n buildCommand: 'bun run build',\n runScriptCommand: 'bun run',\n flags: '',\n forceInstallFlag: '--force',\n devDependencyFlag: '--dev',\n detect: (cwd: string) =>\n ['bun.lockb', 'bun.lock'].some((lockFile) => {\n try {\n return fs.existsSync(path.join(cwd, lockFile));\n } catch {\n return false;\n }\n }),\n addOverride: async (pkgName, pkgVersion): Promise<void> => {\n const packageDotJson = await getPackageJson();\n if (!packageDotJson) {\n return;\n }\n const overrides = packageDotJson.overrides || {};\n\n await updatePackageJson({\n ...packageDotJson,\n overrides: {\n ...overrides,\n [pkgName]: pkgVersion,\n },\n });\n },\n};\nexport const DENO: PackageManager = {\n id: 'deno',\n name: 'deno',\n label: 'Deno',\n installCommand: 'install',\n installAllCommand: 'deno install',\n buildCommand: 'deno task build',\n runScriptCommand: 'deno task',\n flags: '',\n forceInstallFlag: '--force',\n devDependencyFlag: '--dev',\n registry: 'npm',\n detect: (cwd: string) => {\n try {\n return fs.existsSync(path.join(cwd, 'deno.lock'));\n } catch {\n return false;\n }\n },\n addOverride: async (pkgName, pkgVersion): Promise<void> => {\n const packageDotJson = await getPackageJson();\n if (!packageDotJson) {\n return;\n }\n const overrides = packageDotJson.overrides || {};\n\n await updatePackageJson({\n ...packageDotJson,\n overrides: {\n ...overrides,\n [pkgName]: pkgVersion,\n },\n });\n },\n};\nexport const YARN_V1: PackageManager = {\n id: 'yarn_v1',\n name: 'yarn',\n label: 'Yarn V1',\n installCommand: 'add',\n installAllCommand: 'yarn install',\n buildCommand: 'yarn build',\n runScriptCommand: 'yarn',\n flags: '--ignore-workspace-root-check',\n forceInstallFlag: '--force',\n devDependencyFlag: '--dev',\n detect: (cwd: string) => {\n try {\n return fs\n .readFileSync(path.join(cwd, 'yarn.lock'), 'utf-8')\n .slice(0, 500)\n .includes('yarn lockfile v1');\n } catch {\n return false;\n }\n },\n addOverride: async (pkgName, pkgVersion): Promise<void> => {\n const packageDotJson = await getPackageJson();\n if (!packageDotJson) {\n return;\n }\n const resolutions = packageDotJson.resolutions || {};\n\n await updatePackageJson({\n ...packageDotJson,\n resolutions: {\n ...resolutions,\n [pkgName]: pkgVersion,\n },\n });\n },\n};\n/** YARN V2/3/4 */\nexport const YARN_V2: PackageManager = {\n id: 'yarn_v2',\n name: 'yarn',\n label: 'Yarn V2/3/4',\n installCommand: 'add',\n installAllCommand: 'yarn install',\n buildCommand: 'yarn build',\n runScriptCommand: 'yarn',\n flags: '',\n forceInstallFlag: '--force',\n devDependencyFlag: '--dev',\n detect: (cwd: string) => {\n try {\n return fs\n .readFileSync(path.join(cwd, 'yarn.lock'), 'utf-8')\n .slice(0, 500)\n .includes('__metadata');\n } catch {\n return false;\n }\n },\n addOverride: async (pkgName, pkgVersion): Promise<void> => {\n const packageDotJson = await getPackageJson();\n if (!packageDotJson) {\n return;\n }\n const resolutions = packageDotJson.resolutions || {};\n\n await updatePackageJson({\n ...packageDotJson,\n resolutions: {\n ...resolutions,\n [pkgName]: pkgVersion,\n },\n });\n },\n};\nexport const PNPM: PackageManager = {\n id: 'pnpm',\n name: 'pnpm',\n label: 'PNPM',\n installCommand: 'add',\n installAllCommand: 'pnpm install',\n buildCommand: 'pnpm build',\n runScriptCommand: 'pnpm',\n flags: '--ignore-workspace-root-check',\n forceInstallFlag: '--force',\n devDependencyFlag: '--save-dev',\n detect: (cwd: string) => {\n try {\n return fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'));\n } catch {\n return false;\n }\n },\n addOverride: async (pkgName, pkgVersion): Promise<void> => {\n const packageDotJson = await getPackageJson();\n if (!packageDotJson) {\n return;\n }\n const pnpm =\n (packageDotJson.pnpm as\n | { overrides?: Record<string, string> }\n | undefined) || {};\n const overrides = pnpm.overrides || {};\n\n await updatePackageJson({\n ...packageDotJson,\n pnpm: {\n ...pnpm,\n overrides: {\n ...overrides,\n [pkgName]: pkgVersion,\n },\n },\n });\n },\n};\nexport const NPM: PackageManager = {\n id: 'npm',\n name: 'npm',\n label: 'NPM',\n installCommand: 'install',\n installAllCommand: 'npm ci',\n buildCommand: 'npm run build',\n runScriptCommand: 'npm run',\n flags: '',\n forceInstallFlag: '--force',\n devDependencyFlag: '--save-dev',\n detect: (cwd: string) => {\n try {\n return fs.existsSync(path.join(cwd, 'package-lock.json'));\n } catch {\n return false;\n }\n },\n addOverride: async (pkgName, pkgVersion): Promise<void> => {\n const packageDotJson = await getPackageJson();\n if (!packageDotJson) {\n return;\n }\n const overrides = packageDotJson.overrides || {};\n\n await updatePackageJson({\n ...packageDotJson,\n overrides: {\n ...overrides,\n [pkgName]: pkgVersion,\n },\n });\n },\n};\n\nexport const packageManagers = [NPM, YARN_V1, YARN_V2, PNPM, BUN, DENO];\n\nexport function _detectPackageManger(cwd: string): PackageManager | null {\n const foundPackageMangers = packageManagers.filter((packageManager) =>\n packageManager.detect(cwd)\n );\n\n // Only consider a package manager detected if we found exactly one.\n // If we find more than one, we should not make any assumptions.\n if (foundPackageMangers.length === 1) {\n return foundPackageMangers[0];\n }\n\n return null;\n}\n\n// Get the package manager for the current project\n// Uses a global cache to avoid prompting the user multiple times\nexport async function getPackageManager(\n cwd: string = process.cwd(),\n specifiedPackageManager?: string,\n errorIfNotFound: boolean = false\n): Promise<PackageManager> {\n const globalWizard: typeof global & {\n _gt_wizard_cached_package_manager?: PackageManager;\n } = global;\n\n if (globalWizard._gt_wizard_cached_package_manager) {\n return globalWizard._gt_wizard_cached_package_manager;\n }\n\n if (specifiedPackageManager) {\n const packageManager = packageManagers.find(\n (packageManager) => packageManager.id === specifiedPackageManager\n );\n if (packageManager) {\n globalWizard._gt_wizard_cached_package_manager = packageManager;\n return packageManager;\n }\n }\n\n const detectedPackageManager = _detectPackageManger(cwd);\n\n if (detectedPackageManager) {\n globalWizard._gt_wizard_cached_package_manager = detectedPackageManager;\n return detectedPackageManager;\n }\n\n if (errorIfNotFound) {\n throw new NoPackageManagerError('No package manager found');\n }\n\n const selectedPackageManager: PackageManager =\n await promptSelect<PackageManager>({\n message: 'Select your package manager.',\n options: packageManagers.map((packageManager) => ({\n value: packageManager,\n label: packageManager.label,\n })),\n });\n\n globalWizard._gt_wizard_cached_package_manager = selectedPackageManager;\n\n return selectedPackageManager;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"packageManager.js","names":["fs","path"],"sources":["../../src/utils/packageManager.ts"],"sourcesContent":["// This file is MIT licensed and was adapted from https://github.com/getsentry/sentry-wizard/blob/master/src/utils/package-manager.ts and https://github.com/getsentry/sentry-wizard/blob/master/src/utils/clack/index.ts\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { parse } from 'semver';\nimport { getPackageJson, updatePackageJson } from './packageJson.js';\nimport { promptSelect } from '../console/logging.js';\n\nexport interface PackageManager {\n id: string;\n name: string;\n label: string;\n installCommand: string;\n installAllCommand: string;\n buildCommand: string;\n /* The command that the package manager uses to run a script from package.json */\n runScriptCommand: string;\n flags: string;\n forceInstallFlag: string;\n devDependencyFlag: string;\n registry?: string;\n detect: (cwd: string) => boolean;\n addOverride: (pkgName: string, pkgVersion: string) => Promise<void>;\n}\n\nexport class NoPackageManagerError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'NoPackageManagerError';\n }\n}\n\nexport const BUN: PackageManager = {\n id: 'bun',\n name: 'bun',\n label: 'Bun',\n installCommand: 'add',\n installAllCommand: 'bun install',\n buildCommand: 'bun run build',\n runScriptCommand: 'bun run',\n flags: '',\n forceInstallFlag: '--force',\n devDependencyFlag: '--dev',\n detect: (cwd: string) =>\n ['bun.lockb', 'bun.lock'].some((lockFile) => {\n try {\n return fs.existsSync(path.join(cwd, lockFile));\n } catch {\n return false;\n }\n }),\n addOverride: async (pkgName, pkgVersion): Promise<void> => {\n const packageDotJson = await getPackageJson();\n if (!packageDotJson) {\n return;\n }\n const overrides = packageDotJson.overrides || {};\n\n await updatePackageJson({\n ...packageDotJson,\n overrides: {\n ...overrides,\n [pkgName]: pkgVersion,\n },\n });\n },\n};\nexport const DENO: PackageManager = {\n id: 'deno',\n name: 'deno',\n label: 'Deno',\n installCommand: 'install',\n installAllCommand: 'deno install',\n buildCommand: 'deno task build',\n runScriptCommand: 'deno task',\n flags: '',\n forceInstallFlag: '--force',\n devDependencyFlag: '--dev',\n registry: 'npm',\n detect: (cwd: string) => {\n try {\n return fs.existsSync(path.join(cwd, 'deno.lock'));\n } catch {\n return false;\n }\n },\n addOverride: async (pkgName, pkgVersion): Promise<void> => {\n const packageDotJson = await getPackageJson();\n if (!packageDotJson) {\n return;\n }\n const overrides = packageDotJson.overrides || {};\n\n await updatePackageJson({\n ...packageDotJson,\n overrides: {\n ...overrides,\n [pkgName]: pkgVersion,\n },\n });\n },\n};\nexport const YARN_V1: PackageManager = {\n id: 'yarn_v1',\n name: 'yarn',\n label: 'Yarn V1',\n installCommand: 'add',\n installAllCommand: 'yarn install',\n buildCommand: 'yarn build',\n runScriptCommand: 'yarn',\n flags: '--ignore-workspace-root-check',\n forceInstallFlag: '--force',\n devDependencyFlag: '--dev',\n detect: (cwd: string) => {\n try {\n return fs\n .readFileSync(path.join(cwd, 'yarn.lock'), 'utf-8')\n .slice(0, 500)\n .includes('yarn lockfile v1');\n } catch {\n return false;\n }\n },\n addOverride: async (pkgName, pkgVersion): Promise<void> => {\n const packageDotJson = await getPackageJson();\n if (!packageDotJson) {\n return;\n }\n const resolutions = packageDotJson.resolutions || {};\n\n await updatePackageJson({\n ...packageDotJson,\n resolutions: {\n ...resolutions,\n [pkgName]: pkgVersion,\n },\n });\n },\n};\n/** YARN V2/3/4 */\nexport const YARN_V2: PackageManager = {\n id: 'yarn_v2',\n name: 'yarn',\n label: 'Yarn V2/3/4',\n installCommand: 'add',\n installAllCommand: 'yarn install',\n buildCommand: 'yarn build',\n runScriptCommand: 'yarn',\n flags: '',\n forceInstallFlag: '--force',\n devDependencyFlag: '--dev',\n detect: (cwd: string) => {\n try {\n return fs\n .readFileSync(path.join(cwd, 'yarn.lock'), 'utf-8')\n .slice(0, 500)\n .includes('__metadata');\n } catch {\n return false;\n }\n },\n addOverride: async (pkgName, pkgVersion): Promise<void> => {\n const packageDotJson = await getPackageJson();\n if (!packageDotJson) {\n return;\n }\n const resolutions = packageDotJson.resolutions || {};\n\n await updatePackageJson({\n ...packageDotJson,\n resolutions: {\n ...resolutions,\n [pkgName]: pkgVersion,\n },\n });\n },\n};\nexport const PNPM: PackageManager = {\n id: 'pnpm',\n name: 'pnpm',\n label: 'PNPM',\n installCommand: 'add',\n installAllCommand: 'pnpm install',\n buildCommand: 'pnpm build',\n runScriptCommand: 'pnpm',\n flags: '--ignore-workspace-root-check',\n forceInstallFlag: '--force',\n devDependencyFlag: '--save-dev',\n detect: (cwd: string) => {\n try {\n return fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'));\n } catch {\n return false;\n }\n },\n addOverride: async (pkgName, pkgVersion): Promise<void> => {\n const packageDotJson = await getPackageJson();\n if (!packageDotJson) {\n return;\n }\n const pnpm =\n (packageDotJson.pnpm as\n | { overrides?: Record<string, string> }\n | undefined) || {};\n const overrides = pnpm.overrides || {};\n\n await updatePackageJson({\n ...packageDotJson,\n pnpm: {\n ...pnpm,\n overrides: {\n ...overrides,\n [pkgName]: pkgVersion,\n },\n },\n });\n },\n};\nexport const NPM: PackageManager = {\n id: 'npm',\n name: 'npm',\n label: 'NPM',\n installCommand: 'install',\n installAllCommand: 'npm ci',\n buildCommand: 'npm run build',\n runScriptCommand: 'npm run',\n flags: '',\n forceInstallFlag: '--force',\n devDependencyFlag: '--save-dev',\n detect: (cwd: string) => {\n try {\n return ['package-lock.json', 'npm-shrinkwrap.json'].some((lockFile) =>\n fs.existsSync(path.join(cwd, lockFile))\n );\n } catch {\n return false;\n }\n },\n addOverride: async (pkgName, pkgVersion): Promise<void> => {\n const packageDotJson = await getPackageJson();\n if (!packageDotJson) {\n return;\n }\n const overrides = packageDotJson.overrides || {};\n\n await updatePackageJson({\n ...packageDotJson,\n overrides: {\n ...overrides,\n [pkgName]: pkgVersion,\n },\n });\n },\n};\n\nexport const packageManagers = [NPM, YARN_V1, YARN_V2, PNPM, BUN, DENO];\n\nexport function _detectPackageManger(cwd: string): PackageManager | null {\n const foundPackageMangers = packageManagers.filter((packageManager) =>\n packageManager.detect(cwd)\n );\n\n // Only consider a package manager detected if we found exactly one.\n // If we find more than one, we should not make any assumptions.\n if (foundPackageMangers.length === 1) {\n return foundPackageMangers[0];\n }\n\n return null;\n}\n\nexport async function getPackageManager(\n cwd: string = process.cwd(),\n specifiedPackageManager?: string,\n errorIfNotFound: boolean = false\n): Promise<PackageManager> {\n if (specifiedPackageManager) {\n const packageManager = packageManagers.find(\n (packageManager) => packageManager.id === specifiedPackageManager\n );\n if (packageManager) return packageManager;\n }\n\n // A fresh scaffold can declare its manager before a lockfile exists.\n // The npx launcher's manager says nothing about the target project.\n const declaration = (await getPackageJson(cwd))?.packageManager;\n const match =\n typeof declaration === 'string' && /^([^@]+)@(.+)$/.exec(declaration);\n const version = match && parse(match[2]);\n if (match && version) {\n const id =\n match[1] === 'yarn'\n ? version.major <= 1\n ? YARN_V1.id\n : YARN_V2.id\n : match[1];\n const declaredPackageManager = packageManagers.find(\n (manager) => manager.id === id\n );\n if (declaredPackageManager) return declaredPackageManager;\n }\n\n const detectedPackageManager = _detectPackageManger(cwd);\n if (detectedPackageManager) return detectedPackageManager;\n\n if (errorIfNotFound) {\n throw new NoPackageManagerError('No package manager found');\n }\n\n return promptSelect<PackageManager>({\n message: 'Select your package manager.',\n options: packageManagers.map((packageManager) => ({\n value: packageManager,\n label: packageManager.label,\n })),\n });\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gt",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.23.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": "bin/main.js",
|
|
6
6
|
"files": [
|
|
@@ -95,7 +95,6 @@
|
|
|
95
95
|
"fast-json-stable-stringify": "^2.1.0",
|
|
96
96
|
"fflate": "^0.8.2",
|
|
97
97
|
"html-entities": "^2.6.0",
|
|
98
|
-
"ink": "^5.2.1",
|
|
99
98
|
"json-pointer": "^0.6.2",
|
|
100
99
|
"jsonpath-plus": "^10.3.0",
|
|
101
100
|
"jsonpointer": "^5.0.1",
|
|
@@ -104,7 +103,6 @@
|
|
|
104
103
|
"open": "^10.1.1",
|
|
105
104
|
"openid-client": "6.8.8",
|
|
106
105
|
"pino": "^10.1.0",
|
|
107
|
-
"react": "^18.3.1",
|
|
108
106
|
"remark-frontmatter": "^5.0.0",
|
|
109
107
|
"remark-mdx": "^3.1.0",
|
|
110
108
|
"remark-parse": "^11.0.0",
|
|
@@ -118,10 +116,10 @@
|
|
|
118
116
|
"yaml": "^2.8.0",
|
|
119
117
|
"@generaltranslation/format": "0.1.9",
|
|
120
118
|
"@generaltranslation/icu": "0.1.2",
|
|
121
|
-
"@generaltranslation/python-extractor": "0.2.
|
|
122
|
-
"@generaltranslation/supported-locales": "2.1.
|
|
123
|
-
"@generaltranslation/vue-extractor": "0.1.
|
|
124
|
-
"generaltranslation": "9.5.
|
|
119
|
+
"@generaltranslation/python-extractor": "0.2.56",
|
|
120
|
+
"@generaltranslation/supported-locales": "2.1.37",
|
|
121
|
+
"@generaltranslation/vue-extractor": "0.1.16",
|
|
122
|
+
"generaltranslation": "9.5.2",
|
|
125
123
|
"gt-remark": "1.0.12"
|
|
126
124
|
},
|
|
127
125
|
"devDependencies": {
|
|
@@ -132,7 +130,6 @@
|
|
|
132
130
|
"@types/micromatch": "^4.0.9",
|
|
133
131
|
"@types/mock-require": "^2.0.3",
|
|
134
132
|
"@types/node": "^22.13.5",
|
|
135
|
-
"@types/react": "^18.3.4",
|
|
136
133
|
"@types/resolve": "^1.20.2",
|
|
137
134
|
"@types/semver": "^7.7.1",
|
|
138
135
|
"ajv": "^8.18.0",
|
|
@@ -141,7 +138,6 @@
|
|
|
141
138
|
"mdast-util-mdx-jsx": "^3.2.0",
|
|
142
139
|
"mdast-util-mdxjs-esm": "^2.0.1",
|
|
143
140
|
"prettier": "^3.4.2",
|
|
144
|
-
"react-devtools-core": "^4.28.5",
|
|
145
141
|
"ts-node": "^10.9.2",
|
|
146
142
|
"tsdown": "^0.21.10",
|
|
147
143
|
"tslib": "^2.8.1",
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { LocaleOption } from './inkTypes.js';
|
|
2
|
-
export declare function OptionRow({ active, selected, label, hint, width, }: {
|
|
3
|
-
active: boolean;
|
|
4
|
-
selected?: boolean;
|
|
5
|
-
label: string;
|
|
6
|
-
hint?: string;
|
|
7
|
-
width?: number;
|
|
8
|
-
}): import("react/jsx-runtime").JSX.Element;
|
|
9
|
-
export declare function SelectedTags({ selectedLocales, active, activeIndex, width, }: {
|
|
10
|
-
selectedLocales: string[];
|
|
11
|
-
active: boolean;
|
|
12
|
-
activeIndex: number;
|
|
13
|
-
width: number;
|
|
14
|
-
}): import("react/jsx-runtime").JSX.Element | null;
|
|
15
|
-
export declare function LocaleOptionsList({ options, activeIndex, width, }: {
|
|
16
|
-
options: LocaleOption[];
|
|
17
|
-
activeIndex: number;
|
|
18
|
-
width: number;
|
|
19
|
-
}): import("react/jsx-runtime").JSX.Element;
|
|
20
|
-
export declare function RemainingMatches({ total, visible, }: {
|
|
21
|
-
total: number;
|
|
22
|
-
visible: number;
|
|
23
|
-
}): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { stripAnsi } from "./logging.js";
|
|
2
|
-
import { getLocaleCodeWidth } from "./inkLocaleData.js";
|
|
3
|
-
import { INK_ACCENT_COLOR } from "./inkTheme.js";
|
|
4
|
-
import { truncate } from "./inkUtils.js";
|
|
5
|
-
import { Box, Text } from "ink";
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
//#region src/console/inkFields.tsx
|
|
8
|
-
function OptionRow({ active, selected, label, hint, width }) {
|
|
9
|
-
const text = `${active ? ">" : " "} ${selected == null ? "" : selected ? "[x] " : "[ ] "}${stripAnsi(label)}${hint ? ` ${stripAnsi(hint)}` : ""}`;
|
|
10
|
-
return active ? /* @__PURE__ */ jsx(Text, {
|
|
11
|
-
color: INK_ACCENT_COLOR,
|
|
12
|
-
bold: true,
|
|
13
|
-
children: width ? truncate(text, width) : text
|
|
14
|
-
}) : /* @__PURE__ */ jsx(Text, { children: width ? truncate(text, width) : text });
|
|
15
|
-
}
|
|
16
|
-
function SelectedTags({ selectedLocales, active, activeIndex, width }) {
|
|
17
|
-
if (selectedLocales.length === 0) return null;
|
|
18
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
19
|
-
width,
|
|
20
|
-
flexWrap: "wrap",
|
|
21
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
22
|
-
dimColor: true,
|
|
23
|
-
children: "Selected "
|
|
24
|
-
}), selectedLocales.map((locale, index) => {
|
|
25
|
-
const isActive = active && index === activeIndex;
|
|
26
|
-
return /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
27
|
-
backgroundColor: isActive ? INK_ACCENT_COLOR : "gray",
|
|
28
|
-
color: "black",
|
|
29
|
-
bold: isActive,
|
|
30
|
-
children: ` ${locale} `
|
|
31
|
-
}), index < selectedLocales.length - 1 ? /* @__PURE__ */ jsx(Text, { children: " " }) : null] }, locale);
|
|
32
|
-
})]
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
function LocaleOptionsList({ options, activeIndex, width }) {
|
|
36
|
-
const codeWidth = getLocaleCodeWidth();
|
|
37
|
-
return /* @__PURE__ */ jsx(Box, {
|
|
38
|
-
flexDirection: "column",
|
|
39
|
-
marginTop: 1,
|
|
40
|
-
children: options.map((option, index) => /* @__PURE__ */ jsx(LocaleRow, {
|
|
41
|
-
active: index === activeIndex,
|
|
42
|
-
option,
|
|
43
|
-
width,
|
|
44
|
-
codeWidth
|
|
45
|
-
}, option.code))
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
function LocaleRow({ active, option, width, codeWidth }) {
|
|
49
|
-
const marker = active ? ">" : " ";
|
|
50
|
-
const sameName = option.name === option.nativeName;
|
|
51
|
-
const codeStr = option.code.padEnd(codeWidth);
|
|
52
|
-
const reserved = 2 + codeWidth + 2;
|
|
53
|
-
const remainingForName = Math.max(1, width - reserved);
|
|
54
|
-
const nameMax = sameName ? remainingForName : Math.max(1, Math.ceil(remainingForName * .55));
|
|
55
|
-
const truncatedName = truncate(option.name, nameMax);
|
|
56
|
-
const nativeMax = sameName ? 0 : Math.max(1, remainingForName - truncatedName.length - 2);
|
|
57
|
-
const truncatedNative = sameName ? "" : truncate(option.nativeName, nativeMax);
|
|
58
|
-
if (active) return /* @__PURE__ */ jsxs(Text, {
|
|
59
|
-
color: INK_ACCENT_COLOR,
|
|
60
|
-
bold: true,
|
|
61
|
-
children: [`${marker} ${codeStr} ${truncatedName}`, sameName ? "" : ` ${truncatedNative}`]
|
|
62
|
-
});
|
|
63
|
-
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
64
|
-
/* @__PURE__ */ jsx(Text, { children: `${marker} ` }),
|
|
65
|
-
/* @__PURE__ */ jsx(Text, {
|
|
66
|
-
dimColor: true,
|
|
67
|
-
children: codeStr
|
|
68
|
-
}),
|
|
69
|
-
/* @__PURE__ */ jsx(Text, { children: ` ${truncatedName}` }),
|
|
70
|
-
sameName ? null : /* @__PURE__ */ jsx(Text, {
|
|
71
|
-
dimColor: true,
|
|
72
|
-
italic: true,
|
|
73
|
-
children: ` ${truncatedNative}`
|
|
74
|
-
})
|
|
75
|
-
] });
|
|
76
|
-
}
|
|
77
|
-
function RemainingMatches({ total, visible }) {
|
|
78
|
-
const remaining = total - visible;
|
|
79
|
-
if (remaining <= 0) return null;
|
|
80
|
-
return /* @__PURE__ */ jsxs(Text, {
|
|
81
|
-
dimColor: true,
|
|
82
|
-
children: [remaining, " more matches"]
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
//#endregion
|
|
86
|
-
export { LocaleOptionsList, OptionRow, RemainingMatches, SelectedTags };
|
|
87
|
-
|
|
88
|
-
//# sourceMappingURL=inkFields.js.map
|