Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/bright-phones-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'gt': patch
'generaltranslation': patch
---

feat: Auth wizard supports both types of key creation
59 changes: 36 additions & 23 deletions packages/cli/src/cli/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ export type UploadOptions = {
};

export type LoginOptions = {
keyType?: 'development' | 'production';
config?: string;
keyType?: 'development' | 'production' | 'all';
};

export class BaseCLI {
Expand Down Expand Up @@ -313,41 +314,44 @@ export class BaseCLI {
protected setupLoginCommand(): void {
this.program
.command('auth')
.description('Generate a General Translation API key and project ID')
.description('Generate General Translation API keys and project ID')
.option(
'-c, --config <path>',
'Filepath to config file, by default gt.config.json',
findFilepath(['gt.config.json'])
)
.option(
'-t, --key-type <type>',
'Type of key to generate, production | development'
'Type of key to generate, production | development | all'
)
.action(async (options: LoginOptions) => {
displayHeader('Authenticating with General Translation...');
if (!options.keyType) {
const packageJson = await searchForPackageJson();
if (
packageJson &&
INLINE_LIBRARIES.some((lib) => isPackageInstalled(lib, packageJson))
) {
options.keyType = 'development';
} else {
options.keyType = 'production';
}
options.keyType = await promptSelect<
'development' | 'production' | 'all'
>({
message: 'What type of API key would you like to generate?',
options: [
{ value: 'development', label: 'Development' },
{ value: 'production', label: 'Production' },
{ value: 'all', label: 'Both' },
],
defaultValue: 'all',
});
} else {
if (
options.keyType !== 'development' &&
options.keyType !== 'production'
options.keyType !== 'production' &&
options.keyType !== 'all'
) {
logErrorAndExit(
'Invalid key type, must be development or production'
'Invalid key type, must be development, production, or all'
);
}
}
await this.handleLoginCommand(options);
logger.endCommand(
`Done! A ${options.keyType} key has been generated and saved to your .env.local file.`
`Done! ${options.keyType} keys have been generated and saved to your .env.local file.`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect plural in success message for single key type

When options.keyType is 'development' or 'production', the message reads e.g. "Done! development keys have been generated..." — but only one key was produced. The phrasing should vary based on whether one or two keys were requested:

Suggested change
`Done! ${options.keyType} keys have been generated and saved to your .env.local file.`
`Done! ${options.keyType === 'all' ? 'Development and production keys have' : `A ${options.keyType} key has`} been generated and saved to your .env.local file.`
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/cli/src/cli/base.ts
Line: 354

Comment:
Incorrect plural in success message for single key type

When `options.keyType` is `'development'` or `'production'`, the message reads e.g. _"Done! development keys have been generated..."_ — but only one key was produced. The phrasing should vary based on whether one or two keys were requested:

```suggestion
          `Done! ${options.keyType === 'all' ? 'Development and production keys have' : `A ${options.keyType} key has`} been generated and saved to your .env.local file.`
```

How can I resolve this? If you propose a fix, please make it concise.

);
});
}
Expand Down Expand Up @@ -654,24 +658,33 @@ See https://generaltranslation.com/en/docs/next/guides/local-tx`
const loginQuestion = useDefaults
? true
: await promptConfirm({
message: `Would you like the wizard to automatically generate a ${
isUsingGT ? 'development' : 'production'
} API key and project ID for you?`,
message:
'Would you like the wizard to automatically generate API keys and a project ID for you?',
defaultValue: true,
});
if (loginQuestion) {
const settings = await generateSettings({});
const keyType = isUsingGT ? 'development' : 'production';
const keyType = useDefaults
? 'all'
: await promptSelect<'development' | 'production' | 'all'>({
message: 'What type of API key would you like to generate?',
options: [
{ value: 'development', label: 'Development' },
{ value: 'production', label: 'Production' },
{ value: 'all', label: 'Both' },
],
defaultValue: 'all',
});
const credentials = await retrieveCredentials(settings, keyType);
await setCredentials(credentials, keyType, settings.framework);
await setCredentials(credentials, settings.framework);
}
}
}
protected async handleLoginCommand(options: LoginOptions): Promise<void> {
const settings = await generateSettings({});
const keyType = options.keyType || 'production';
const settings = await generateSettings({ config: options.config });
const keyType = options.keyType || 'all';
const credentials = await retrieveCredentials(settings, keyType);
await setCredentials(credentials, keyType, settings.framework);
await setCredentials(credentials, settings.framework);
}

protected setupUpdateInstructionsCommand(): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/generated/version.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// This file is auto-generated. Do not edit manually.
export const PACKAGE_VERSION = '2.6.30';
export const PACKAGE_VERSION = '2.7.0';
50 changes: 27 additions & 23 deletions packages/cli/src/utils/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ import path from 'node:path';
import fs from 'node:fs';
import { Settings, SupportedFrameworks } from '../types/index.js';
import chalk from 'chalk';
import apiRequest from './fetch.js';
// Type for credentials returned from the dashboard
type Credentials = {
apiKey: string;
apiKeys: ApiKey[];
projectId: string;
};

type ApiKey = {
key: string;
type: 'development' | 'production';
};

// Fetches project ID and API key by opening the dashboard in the browser
export async function retrieveCredentials(
settings: Settings,
keyType: 'development' | 'production'
keyType: 'development' | 'production' | 'all'
): Promise<Credentials> {
// Generate a session ID
const { sessionId } = await generateCredentialsSession(
Expand Down Expand Up @@ -42,18 +48,17 @@ export async function retrieveCredentials(
const interval = setInterval(async () => {
// Ping the dashboard to see if the credentials are set
try {
const res = await fetch(
`${settings.baseUrl}/cli/wizard/${sessionId}`,
{
method: 'GET',
}
const res = await apiRequest(
settings.baseUrl,
`/cli/wizard/${sessionId}`,
{ method: 'GET' }
);
if (res.status === 200) {
const data = await res.json();
resolve(data as Credentials);
clearInterval(interval);
clearTimeout(timeout);
fetch(`${settings.baseUrl}/cli/wizard/${sessionId}`, {
apiRequest(settings.baseUrl, `/cli/wizard/${sessionId}`, {
method: 'DELETE',
});
}
Expand All @@ -78,18 +83,12 @@ export async function retrieveCredentials(

export async function generateCredentialsSession(
url: string,
keyType: 'development' | 'production'
keyType: 'development' | 'production' | 'all'
): Promise<{
sessionId: string;
}> {
const res = await fetch(`${url}/cli/wizard/session`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
keyType,
}),
const res = await apiRequest(url, '/cli/wizard/session', {
body: { keyType },
});
if (!res.ok) {
logErrorAndExit('Failed to generate credentials session');
Expand All @@ -99,13 +98,15 @@ export async function generateCredentialsSession(

// Checks if the credentials are set in the environment variables
export function areCredentialsSet() {
return process.env.GT_PROJECT_ID && process.env.GT_API_KEY;
return (
process.env.GT_PROJECT_ID &&
(process.env.GT_API_KEY || process.env.GT_DEV_API_KEY)
);
}

// Sets the credentials in .env.local file
export async function setCredentials(
credentials: Credentials,
type: 'development' | 'production',
framework?: SupportedFrameworks,
cwd: string = process.cwd()
) {
Expand Down Expand Up @@ -151,10 +152,13 @@ export async function setCredentials(
}

envContent += `\n${prefix}GT_PROJECT_ID=${credentials.projectId}\n`;
if (type === 'development') {
envContent += `${prefix || ''}GT_DEV_API_KEY=${credentials.apiKey}\n`;
} else {
envContent += `GT_API_KEY=${credentials.apiKey}\n`;

for (const apiKey of credentials.apiKeys) {
if (apiKey.type === 'development') {
envContent += `${prefix || ''}GT_DEV_API_KEY=${apiKey.key}\n`;
} else {
envContent += `${prefix || ''}GT_API_KEY=${apiKey.key}\n`;
}
}

// Ensure we don't have excessive newlines
Expand Down
33 changes: 33 additions & 0 deletions packages/cli/src/utils/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { API_VERSION } from 'generaltranslation';

/**
* @internal
*
* Makes an API request to the General Translation API.
*
* Encapsulates URL construction, headers, and JSON parsing.
*/
export default async function apiRequest<T>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused generic type parameter

The generic type parameter T is declared on the function signature but is never used — the return type is Promise<Response>, not Promise<T>. TypeScript will flag this as an unused type parameter. Either remove it or update the return type to use it:

Suggested change
export default async function apiRequest<T>(
export default async function apiRequest(
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/cli/src/utils/fetch.ts
Line: 10

Comment:
Unused generic type parameter

The generic type parameter `T` is declared on the function signature but is never used — the return type is `Promise<Response>`, not `Promise<T>`. TypeScript will flag this as an unused type parameter. Either remove it or update the return type to use it:

```suggestion
export default async function apiRequest(
```

How can I resolve this? If you propose a fix, please make it concise.

baseUrl: string,
endpoint: string,
options?: {
body?: unknown;
method?: 'GET' | 'POST' | 'DELETE';
}
): Promise<Response> {
const method = options?.method ?? 'POST';

const requestInit: RequestInit = {
method,
headers: {
'Content-Type': 'application/json',
'gt-api-version': API_VERSION,
},
};

if (options?.body !== undefined) {
requestInit.body = JSON.stringify(options.body);
}

return fetch(`${baseUrl}${endpoint}`, requestInit);
}
3 changes: 3 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import _getOrphanedFiles, {
} from './translate/getOrphanedFiles';
import { CutoffFormatOptions } from './formatting/custom-formats/CutoffFormat/types';
import { TranslateOptions } from './types-dir/api/entry';
import { API_VERSION as _API_VERSION } from './translate/api';

// ============================================================ //
// Core Class //
Expand Down Expand Up @@ -1996,3 +1997,5 @@ export function isSupersetLocale(
): boolean {
return _isSupersetLocale(superLocale, subLocale);
}

export const API_VERSION = _API_VERSION;
2 changes: 1 addition & 1 deletion packages/core/src/translate/api.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const API_VERSION = '2026-02-18.v1';
export const API_VERSION = '2026-03-06.v1';
Loading