Skip to content

Commit 07f0756

Browse files
authored
fix(cli, typescript): Fix ts local github generation to match remote generation (#9869)
1 parent cc2c41b commit 07f0756

File tree

23 files changed

+755
-60
lines changed

23 files changed

+755
-60
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,6 @@ seed/python-sdk/**/output.prof
8585
.yarn-cache
8686
.pnpm-cache
8787

88-
**/vitest.config.ts.*.mjs
88+
**/vitest.config.ts.*.mjs
89+
90+
.local

generators/base/src/AbstractGeneratorAgent.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export abstract class AbstractGeneratorAgent<GeneratorContext extends AbstractGe
7272
}): Promise<string> {
7373
const readmeConfig = this.getReadmeConfig({
7474
context,
75-
remote: this.getRemote(),
75+
remote: this.getRemote(context),
7676
featureConfig: await this.readFeatureConfig(),
7777
endpointSnippets
7878
});
@@ -127,17 +127,44 @@ export abstract class AbstractGeneratorAgent<GeneratorContext extends AbstractGe
127127
return loaded;
128128
}
129129

130-
protected getRemote(): FernGeneratorCli.Remote | undefined {
130+
protected getRemote(context: GeneratorContext): FernGeneratorCli.Remote | undefined {
131131
const outputMode = this.config.output.mode.type === "github" ? this.config.output.mode : undefined;
132132
if (outputMode?.repoUrl != null && outputMode?.installationToken != null) {
133133
return FernGeneratorCli.Remote.github({
134134
repoUrl: outputMode.repoUrl,
135135
installationToken: outputMode.installationToken
136136
});
137137
}
138+
139+
try {
140+
const githubConfig = this.getGitHubConfig({ context });
141+
if (githubConfig.uri != null && githubConfig.token != null) {
142+
return FernGeneratorCli.Remote.github({
143+
repoUrl: this.normalizeRepoUrl(githubConfig.uri),
144+
installationToken: githubConfig.token
145+
});
146+
}
147+
} catch (error) {
148+
return undefined;
149+
}
138150
return undefined;
139151
}
140152

153+
private normalizeRepoUrl(repoUrl: string): string {
154+
// If it's already a full URL, return as-is
155+
if (repoUrl.startsWith("https://")) {
156+
return repoUrl;
157+
}
158+
159+
// If it's in owner/repo format, convert to full GitHub URL
160+
if (repoUrl.match(/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+$/)) {
161+
return `https://github.com/${repoUrl}`;
162+
}
163+
164+
// Default: assume it's a GitHub URL and add prefix
165+
return `https://github.com/${repoUrl}`;
166+
}
167+
141168
private async getFeaturesConfig(): Promise<string> {
142169
// try to find the features.yml file using the well-known paths
143170
for (const each of FEATURES_CONFIG_PATHS) {

generators/swift/sdk/src/SwiftGeneratorAgent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class SwiftGeneratorAgent extends AbstractGeneratorAgent<SdkGeneratorCont
4040
}): Promise<string> {
4141
const readmeConfig = this.getReadmeConfig({
4242
context,
43-
remote: this.getRemote(),
43+
remote: this.getRemote(context),
4444
featureConfig: await this.readFeatureConfig(),
4545
endpointSnippets
4646
});

generators/typescript-v2/ast/src/custom-config/TypescriptCustomConfigSchema.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ export const TypescriptCustomConfigSchema = z.strictObject({
6565

6666
// deprecated
6767
timeoutInSeconds: z.optional(z.union([z.literal("infinity"), z.number()])),
68-
includeApiReference: z.optional(z.boolean())
68+
includeApiReference: z.optional(z.boolean()),
69+
70+
// internal - license name extracted from custom license file
71+
_fernLicenseName: z.optional(z.string())
6972
});
7073

7174
export type TypescriptCustomConfigSchema = z.infer<typeof TypescriptCustomConfigSchema>;

generators/typescript-v2/browser-compatible-base/src/utils/constructNpmPackage.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
import { FernGeneratorExec } from "@fern-fern/generator-exec-sdk";
22

33
import { type NpmPackage } from "../NpmPackage";
4+
import { PublishInfo } from "../PublishInfo";
5+
6+
export interface constructNpmPackageArgs {
7+
packageName?: string;
8+
version?: string;
9+
repoUrl?: string;
10+
publishInfo?: PublishInfo;
11+
licenseConfig?: FernGeneratorExec.LicenseConfig;
12+
isPackagePrivate: boolean;
13+
}
14+
15+
export function constructNpmPackageFromArgs(args: constructNpmPackageArgs): NpmPackage | undefined {
16+
const { packageName, version, repoUrl, publishInfo, licenseConfig, isPackagePrivate } = args;
17+
if (packageName == null || version == null) {
18+
return undefined;
19+
}
20+
21+
return {
22+
packageName,
23+
version,
24+
private: isPackagePrivate,
25+
publishInfo,
26+
license: licenseFromLicenseConfig(licenseConfig),
27+
repoUrl: getRepoUrlFromUrl(repoUrl)
28+
};
29+
}
430

531
export function constructNpmPackage({
632
generatorConfig,
@@ -50,7 +76,10 @@ export function constructNpmPackage({
5076
}
5177
}
5278

53-
function getRepoUrlFromUrl(repoUrl: string): string {
79+
function getRepoUrlFromUrl(repoUrl: string | undefined): string | undefined {
80+
if (repoUrl == null) {
81+
return undefined;
82+
}
5483
if (repoUrl.startsWith("https://github.com/")) {
5584
return `github:${removeGitSuffix(repoUrl).replace("https://github.com/", "")}`;
5685
}
@@ -84,3 +113,11 @@ function removeGitSuffix(repoUrl: string): string {
84113
}
85114
return repoUrl;
86115
}
116+
117+
function licenseFromLicenseConfig(licenseConfig: FernGeneratorExec.LicenseConfig | undefined): string | undefined {
118+
return licenseConfig?._visit({
119+
basic: (basic) => basic.id,
120+
custom: (custom) => `See ${custom.filename}`,
121+
_other: () => undefined
122+
});
123+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { constructNpmPackage } from "./constructNpmPackage";
1+
export * from "./constructNpmPackage";
22
export { getNamespaceExport } from "./getNamespaceExport";

generators/typescript/sdk/cli/src/SdkGeneratorCli.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
} from "@fern-typescript/commons";
1414
import { GeneratorContext } from "@fern-typescript/contexts";
1515
import { SdkGenerator } from "@fern-typescript/sdk-generator";
16+
import { copyFile } from "fs/promises";
17+
import path from "path";
1618

1719
import { SdkCustomConfig } from "./custom-config/SdkCustomConfig";
1820
import { SdkCustomConfigSchema } from "./custom-config/schema/SdkCustomConfigSchema";
@@ -251,6 +253,7 @@ export class SdkGeneratorCli extends AbstractGeneratorCli<SdkCustomConfig> {
251253
pathToSrc: persistedTypescriptProject.getSrcDirectory()
252254
});
253255
await writeTemplateFiles(rootDirectory, this.getTemplateVariables(customConfig));
256+
await this.writeLicenseFile(config, rootDirectory, generatorContext.logger);
254257
await this.postProcess(persistedTypescriptProject, customConfig);
255258

256259
return persistedTypescriptProject;
@@ -265,6 +268,45 @@ export class SdkGeneratorCli extends AbstractGeneratorCli<SdkCustomConfig> {
265268
};
266269
}
267270

271+
private async writeLicenseFile(
272+
config: FernGeneratorExec.GeneratorConfig,
273+
rootDirectory: AbsoluteFilePath,
274+
logger: Logger
275+
): Promise<void> {
276+
if (config.license?.type === "custom") {
277+
// For custom licenses, we need to get the license content from the source file
278+
// The CLI should have read the license file content and made it available
279+
// For now, we'll read the license file from the original location
280+
281+
try {
282+
// The license file path is relative to the fern config directory
283+
// We need to construct the full path to read the license content
284+
const licenseFileName = config.license.filename ?? "LICENSE";
285+
const licenseFilePath = path.join(rootDirectory, licenseFileName);
286+
287+
await this.copyLicenseFile(licenseFilePath);
288+
logger.debug(`Successfully wrote LICENSE file to ${licenseFilePath}`);
289+
} catch (error) {
290+
// If we can't read the license file, we'll skip writing it
291+
// This maintains backwards compatibility
292+
logger.warn(`Failed to write LICENSE file: ${error instanceof Error ? error.message : String(error)}`);
293+
}
294+
}
295+
}
296+
297+
private async copyLicenseFile(destinationPath: string): Promise<void> {
298+
// In Docker execution environment, the license file is mounted at /tmp/LICENSE
299+
const dockerLicensePath = "/tmp/LICENSE";
300+
301+
try {
302+
await copyFile(dockerLicensePath, destinationPath);
303+
} catch (error) {
304+
throw new Error(
305+
`Could not copy license file from ${dockerLicensePath}: ${error instanceof Error ? error.message : String(error)}`
306+
);
307+
}
308+
}
309+
268310
private async postProcess(
269311
persistedTypescriptProject: PersistedTypescriptProject,
270312
_customConfig: SdkCustomConfig

generators/typescript/sdk/client-class-generator/src/GeneratedSdkClientClassImpl.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,12 @@ export class GeneratedSdkClientClassImpl implements GeneratedSdkClientClass {
12741274
header: context.ir.sdkConfig.platformHeaders.userAgent.header,
12751275
value: ts.factory.createStringLiteral(context.ir.sdkConfig.platformHeaders.userAgent.value)
12761276
});
1277+
} else if (this.npmPackage != null) {
1278+
// Fallback: generate User-Agent header from npm package info
1279+
headers.push({
1280+
header: "User-Agent",
1281+
value: ts.factory.createStringLiteral(`${this.npmPackage.packageName}/${this.npmPackage.version}`)
1282+
});
12771283
}
12781284

12791285
const generatedVersion = context.versionContext.getGeneratedVersion();

generators/typescript/sdk/generator/src/TypeScriptGeneratorAgent.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AbstractGeneratorAgent } from "@fern-api/base-generator";
1+
import { AbstractGeneratorAgent, RawGithubConfig } from "@fern-api/base-generator";
22
import { Logger } from "@fern-api/logger";
33
import { FernGeneratorCli } from "@fern-fern/generator-cli-sdk";
44
import { FernGeneratorExec } from "@fern-fern/generator-exec-sdk";
@@ -70,13 +70,14 @@ export class TypeScriptGeneratorAgent extends AbstractGeneratorAgent<SdkContext>
7070
};
7171
}
7272

73-
public getGitHubConfig(args: AbstractGeneratorAgent.GitHubConfigArgs<SdkContext>): FernGeneratorCli.GitHubConfig {
73+
public getGitHubConfig(args: AbstractGeneratorAgent.GitHubConfigArgs<SdkContext>): RawGithubConfig {
7474
// TODO: get from env
7575
return {
76-
sourceDirectory: "NONE",
77-
uri: "NONE",
78-
token: "token",
79-
branch: "NONE"
76+
sourceDirectory: "fern/output",
77+
type: this.publishConfig?.type,
78+
uri: this.publishConfig?.type === "github" ? this.publishConfig.uri : undefined,
79+
token: this.publishConfig?.type === "github" ? this.publishConfig.token : undefined,
80+
mode: this.publishConfig?.type === "github" ? this.publishConfig.mode : undefined
8081
};
8182
}
8283
}

generators/typescript/sdk/versions.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
2+
- version: 3.28.3
3+
changelogEntry:
4+
- summary: |
5+
Fix local GitHub generation to match remote generation.
6+
type: fix
7+
createdAt: "2025-11-06"
8+
irVersion: 61
9+
210
- version: 3.28.2
311
changelogEntry:
412
- summary: |

0 commit comments

Comments
 (0)