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
7 changes: 7 additions & 0 deletions generators/cli/changes/0.38.0/fern-shield-in-readme.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# yaml-language-server: $schema=../../../../fern-changes-yml.schema.json

- summary: |
Generated CLI READMEs now include a "CLI generated by Fern" shield in the
header, matching the branding SDK READMEs already carry. The shield is
omitted for orgs configured for white-labeling.
type: feat
52 changes: 51 additions & 1 deletion generators/cli/src/__test__/emitReadme.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,57 @@ describe("emitReadme", () => {
});

expect(readme).not.toContain("npm shield");
expect(readme).not.toContain("img.shields.io");
expect(readme).not.toContain("img.shields.io/npm");
});

// ── Fern shield in header ───────────────────────────────────────

it("includes the Fern shield linked to the repo", async () => {
const readme = await emitAndRead({
outputDir,
binaryName: "petstore-api",
apiDisplayName: "Petstore",
authBindings: [bearerBinding],
npmPublishInfo,
repoUrl: "https://github.com/fern-api/petstore-cli"
});

expect(readme).toContain(
"[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-CLI%20generated%20by%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=https%3A%2F%2Fgithub.com%2Ffern-api%2Fpetstore-cli)"
);
});

it("falls back to the display name in the Fern shield link when repoUrl is absent", async () => {
const readme = await emitAndRead({
outputDir,
binaryName: "acme",
apiDisplayName: "Acme",
authBindings: [bearerBinding],
npmPublishInfo: undefined,
repoUrl: undefined
});

expect(readme).toContain(
"[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-CLI%20generated%20by%20Fern-brightgreen)]"
);
expect(readme).toContain("utm_source=Acme%2FCLI)");
});

it("omits the Fern shield when white-labeling is enabled", async () => {
const readme = await emitAndRead({
outputDir,
binaryName: "acme",
apiDisplayName: "Acme",
authBindings: [bearerBinding],
npmPublishInfo,
repoUrl: "https://github.com/acme/acme-cli",
whiteLabel: true
});

expect(readme).not.toContain("fern shield");
expect(readme).not.toContain("buildwithfern.com");
// The npm badge is unaffected by white-labeling.
expect(readme).toContain("npm shield");
});

// ── Build from source when npmPublishInfo absent ────────────────
Expand Down
3 changes: 2 additions & 1 deletion generators/cli/src/__test__/identity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const emptyIr = (apiDisplayName: string | undefined = undefined): IrSummary => (
auth: { schemes: [] },
globalParameters: [],
services: {},
environments: undefined
environments: undefined,
whiteLabel: false
});

describe("toKebabCase", () => {
Expand Down
3 changes: 2 additions & 1 deletion generators/cli/src/__test__/runPipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ describe("runPipeline", () => {
auth: overrides.auth ?? { schemes: [] },
globalParameters: overrides.globalParameters ?? [],
services: overrides.services ?? {},
environments: overrides.environments
environments: overrides.environments,
whiteLabel: overrides.whiteLabel ?? false
});

const localFilesConfig: ResolvedOutputConfig = {
Expand Down
31 changes: 28 additions & 3 deletions generators/cli/src/emitReadme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,17 @@ export async function emitReadme(args: {
* `packageIdentity.name` is set (or left at the template default).
*/
packageName?: string;
/**
* The IR's `readmeConfig.whiteLabel`. White-labeled orgs get no Fern
* shield, matching `ReadmeGenerator`'s behavior for SDK READMEs.
*/
whiteLabel?: boolean;
}): Promise<void> {
const { outputDir, binaryName, apiDisplayName, authBindings, npmPublishInfo, repoUrl, distribution } = args;
const installerName = args.packageName ?? TEMPLATE_PACKAGE_NAME;
const displayName = apiDisplayName ?? binaryName;

const header = generateHeader({ displayName, npmPublishInfo });
const header = generateHeader({ displayName, npmPublishInfo, repoUrl, whiteLabel: args.whiteLabel ?? false });
const blocks = generateBlocks({
binaryName,
displayName,
Expand All @@ -65,10 +70,18 @@ export async function emitReadme(args: {
// Header
// ---------------------------------------------------------------------------

function generateHeader(args: { displayName: string; npmPublishInfo: ResolvedNpmPublishInfo | undefined }): string {
const { displayName, npmPublishInfo } = args;
function generateHeader(args: {
displayName: string;
npmPublishInfo: ResolvedNpmPublishInfo | undefined;
repoUrl: string | undefined;
whiteLabel: boolean;
}): string {
const { displayName, npmPublishInfo, repoUrl, whiteLabel } = args;
const suffix = displayName.toUpperCase().endsWith("API") ? "" : " API";
const shieldLines: string[] = [];
if (!whiteLabel) {
shieldLines.push(fernShield({ displayName, repoUrl }));
}
if (npmPublishInfo != null) {
shieldLines.push(
`[![npm shield](https://img.shields.io/npm/v/${npmPublishInfo.packageName})](https://www.npmjs.com/package/${npmPublishInfo.packageName})`
Expand All @@ -87,6 +100,18 @@ function generateHeader(args: { displayName: string; npmPublishInfo: ResolvedNpm
return lines(`# ${displayName} CLI`, "", `Command-line interface for the ${displayName}${suffix}.`, "");
}

/**
* The "Built with Fern" badge, identical in shape to the one
* `ReadmeGenerator` writes for SDK READMEs so both surfaces carry the same
* branding. The `utm_source` identifies the repo the badge was clicked from,
* falling back to `<DisplayName>/CLI` when the CLI isn't published to a
* known repo.
*/
function fernShield(args: { displayName: string; repoUrl: string | undefined }): string {
const repoSource = args.repoUrl ?? `${args.displayName}/CLI`;
return `[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-CLI%20generated%20by%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=${encodeURIComponent(repoSource)})`;
}

// ---------------------------------------------------------------------------
// Table of contents — generated after the block merge so it reflects
// customer-added sections too. ReadmeParser strips any existing
Expand Down
9 changes: 8 additions & 1 deletion generators/cli/src/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ export interface IrSummary {
* to resolve the base URL the OAuth token endpoint path is joined to.
*/
environments: FernIr.EnvironmentsConfig | undefined;
/**
* The IR's `readmeConfig.whiteLabel`, defaulted to `false`. Set for orgs
* configured for white-labeling; suppresses the Fern shield in the
* generated README.
*/
whiteLabel: boolean;
}

/**
Expand Down Expand Up @@ -58,7 +64,8 @@ export async function readIr(irFilepath: string): Promise<IrSummary> {
auth: { schemes: ir.auth.schemes },
globalParameters: ir.globalParameters ?? [],
services: ir.services,
environments: ir.environments
environments: ir.environments,
whiteLabel: ir.readmeConfig?.whiteLabel ?? false
};
}

Expand Down
3 changes: 2 additions & 1 deletion generators/cli/src/runPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ export async function runPipeline(args: {
npmPublishInfo: outputConfig.npmPublishInfo,
repoUrl: outputConfig.repoUrl,
distribution,
packageName: customConfig.packageIdentity?.name
packageName: customConfig.packageIdentity?.name,
whiteLabel: ir.whiteLabel
});
await emitReference({
outputDir,
Expand Down
9 changes: 9 additions & 0 deletions generators/cli/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# yaml-language-server: $schema=../../fern-versions-yml.schema.json
- version: 0.38.0
changelogEntry:
- summary: |
Generated CLI READMEs now include a "CLI generated by Fern" shield in the
header, matching the branding SDK READMEs already carry. The shield is
omitted for orgs configured for white-labeling.
type: feat
createdAt: "2026-08-22"
irVersion: 67
- version: 0.37.0
changelogEntry:
- summary: |
Expand Down
2 changes: 2 additions & 0 deletions seed/cli/allof-inline/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/allof/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/api-wide-base-path-with-default/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/cli-basic-auth/with-split-type-crates/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/cli-basic-auth/with-wire-tests/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/cli-header-auth/with-wire-tests/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/cli-multi-spec-namespaced/no-custom-config/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/cli-multi-spec-namespaced/with-wire-tests/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/cli-multi-spec/no-custom-config/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/cli-namespace-stutter/with-wire-tests/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/cli-oauth-login-flow/with-wire-tests/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/cli-oauth/client-credentials/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/cli-reserved-keywords/with-wire-tests/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/cli-shared-types/with-split-type-crates/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/discriminated-union-with-nested-oneof/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/file-upload-openapi/with-wire-tests/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/imdb/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/inline-enum-type-name-override/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/multi-content-type-examples/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/multi-url-environment-reference/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/no-content-response/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/null-type/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/nullable-allof-extends/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions seed/cli/nullable-request-body/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading