diff --git a/fern/apis/generators-yml/definition/group.yml b/fern/apis/generators-yml/definition/group.yml index f4c97082d9ec..6f7e58d9091c 100644 --- a/fern/apis/generators-yml/definition/group.yml +++ b/fern/apis/generators-yml/definition/group.yml @@ -87,6 +87,9 @@ types: mode: optional branch: optional license: optional + pr-state: + type: optional + docs: The state of the pull request when created (draft or ready). Only applies when mode is pull-request. GithubSelfhostedMode: enum: @@ -94,6 +97,15 @@ types: value: pull-request - push + GithubPullRequestState: + docs: | + The state of the pull request when created. + - draft: Create the PR as a draft + - ready: Create the PR as ready for review (default) + enum: + - draft + - ready + GeneratorSnippetsSchema: properties: path: @@ -127,7 +139,9 @@ types: license: optional mode: literal<"pull-request"> reviewers: optional - # Add properties for pull request configuration + pr-state: + type: optional + docs: The state of the pull request when created (draft or ready). Defaults to ready. GithubPushSchema: properties: diff --git a/generators-yml.schema.json b/generators-yml.schema.json index 425c98bed83e..fdf760426096 100644 --- a/generators-yml.schema.json +++ b/generators-yml.schema.json @@ -3270,6 +3270,13 @@ } ] }, + "group.GithubPullRequestState": { + "type": "string", + "enum": [ + "draft", + "ready" + ] + }, "group.GithubSelfhostedSchema": { "type": "object", "properties": { @@ -3308,6 +3315,16 @@ "type": "null" } ] + }, + "pr-state": { + "oneOf": [ + { + "$ref": "#/definitions/group.GithubPullRequestState" + }, + { + "type": "null" + } + ] } }, "required": [ @@ -3437,6 +3454,16 @@ "type": "null" } ] + }, + "pr-state": { + "oneOf": [ + { + "$ref": "#/definitions/group.GithubPullRequestState" + }, + { + "type": "null" + } + ] } }, "required": [ diff --git a/packages/cli/cli/src/cli.ts b/packages/cli/cli/src/cli.ts index 885c0507b5d6..d629a82d5205 100644 --- a/packages/cli/cli/src/cli.ts +++ b/packages/cli/cli/src/cli.ts @@ -560,6 +560,11 @@ function addGenerateCommand(cli: Argv, cliContext: CliContext) choices: Object.values(GenerationMode), description: "Defaults to the mode specified in generators.yml" }) + .option("pr-state", { + choices: ["draft", "ready"] as const, + description: + "The state of the pull request when created (draft or ready). Only applies when --mode pull-request is used." + }) .option("version", { type: "string", description: "The version for the generated packages" @@ -645,6 +650,9 @@ function addGenerateCommand(cli: Argv, cliContext: CliContext) "The --fernignore flag is not supported with local generation (--local or --runner). It can only be used with remote generation." ); } + if (argv.prState != null && argv.mode !== GenerationMode.PullRequest) { + return cliContext.failWithoutThrowing("The --pr-state flag can only be used with --mode pull-request."); + } if (argv.api != null) { return await generateAPIWorkspaces({ project: await loadProjectAndRegisterWorkspacesWithContext(cliContext, { @@ -659,6 +667,7 @@ function addGenerateCommand(cli: Argv, cliContext: CliContext) useLocalDocker: argv.local || argv.runner != null, preview: argv.preview, mode: argv.mode, + prState: argv.prState, force: argv.force, runner: argv.runner as ContainerRunner, inspect: false, @@ -706,6 +715,7 @@ function addGenerateCommand(cli: Argv, cliContext: CliContext) useLocalDocker: argv.local, preview: argv.preview, mode: argv.mode, + prState: argv.prState, force: argv.force, runner: argv.runner as ContainerRunner, inspect: false, diff --git a/packages/cli/cli/src/commands/generate/generateAPIWorkspace.ts b/packages/cli/cli/src/commands/generate/generateAPIWorkspace.ts index a3e39bae230a..a4e2b935f1d6 100644 --- a/packages/cli/cli/src/commands/generate/generateAPIWorkspace.ts +++ b/packages/cli/cli/src/commands/generate/generateAPIWorkspace.ts @@ -15,7 +15,7 @@ import { FernFiddle } from "@fern-fern/fiddle-sdk"; import { GROUP_CLI_OPTION } from "../../constants"; import { validateAPIWorkspaceAndLogIssues } from "../validate/validateAPIWorkspaceAndLogIssues"; -import { GenerationMode } from "./generateAPIWorkspaces"; +import { GenerationMode, PullRequestState } from "./generateAPIWorkspaces"; export async function generateWorkspace({ organization, @@ -30,6 +30,7 @@ export async function generateWorkspace({ keepDocker, absolutePathToPreview, mode, + prState, runner, inspect, lfsOverride, @@ -47,6 +48,7 @@ export async function generateWorkspace({ keepDocker: boolean; absolutePathToPreview: AbsoluteFilePath | undefined; mode: GenerationMode | undefined; + prState: PullRequestState | undefined; runner: ContainerRunner | undefined; inspect: boolean; lfsOverride: string | undefined; @@ -122,7 +124,8 @@ export async function generateWorkspace({ runner, absolutePathToPreview, inspect, - ai + ai, + prState }); } else if (token != null) { await runRemoteGenerationForAPIWorkspace({ @@ -137,6 +140,7 @@ export async function generateWorkspace({ whitelabel: workspace.generatorsConfiguration?.whitelabel, absolutePathToPreview, mode, + prState, fernignorePath }); } diff --git a/packages/cli/cli/src/commands/generate/generateAPIWorkspaces.ts b/packages/cli/cli/src/commands/generate/generateAPIWorkspaces.ts index e39b2d1e1397..d0fb51e05ad8 100644 --- a/packages/cli/cli/src/commands/generate/generateAPIWorkspaces.ts +++ b/packages/cli/cli/src/commands/generate/generateAPIWorkspaces.ts @@ -16,6 +16,8 @@ export const GenerationMode = { export type GenerationMode = Values; +export type PullRequestState = "draft" | "ready"; + export async function generateAPIWorkspaces({ project, cliContext, @@ -26,6 +28,7 @@ export async function generateAPIWorkspaces({ useLocalDocker, preview, mode, + prState, force, runner, inspect, @@ -41,6 +44,7 @@ export async function generateAPIWorkspaces({ keepDocker: boolean; preview: boolean; mode: GenerationMode | undefined; + prState: PullRequestState | undefined; force: boolean; runner: ContainerRunner | undefined; inspect: boolean; @@ -131,6 +135,7 @@ export async function generateAPIWorkspaces({ keepDocker, absolutePathToPreview, mode, + prState, runner, inspect, lfsOverride, diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index 931a126b3acd..4bec0ec0bf26 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,4 +1,12 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- changelogEntry: + - summary: | + Add `--pr-state` CLI option for `fern generate --mode pull-request`. This allows users to specify whether the generated pull request should be created as a draft or ready for review. The option accepts `draft` or `ready` values. Additionally, `pr-state` can be configured in `generators.yml` under the `github` configuration for both self-hosted and pull-request modes. + type: feat + irVersion: 63 + createdAt: "2025-12-10" + version: 3.6.0 + - changelogEntry: - summary: | Add support for vendor-specific JSON content types in example validation. Content types like `application/vnd.bc.v1+json` are now accepted as valid JSON content types when validating examples against `literal<"application/json">` types. diff --git a/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/GithubPullRequestSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/GithubPullRequestSchema.ts index 6687138d473c..d787145e82b5 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/GithubPullRequestSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/GithubPullRequestSchema.ts @@ -10,4 +10,6 @@ export interface GithubPullRequestSchema { license?: FernDefinition.GithubLicenseSchema; mode: "pull-request"; reviewers?: FernDefinition.ReviewersSchema; + /** The state of the pull request when created (draft or ready). Defaults to ready. */ + "pr-state"?: FernDefinition.GithubPullRequestState; } diff --git a/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/GithubPullRequestState.ts b/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/GithubPullRequestState.ts new file mode 100644 index 000000000000..3ddb61c96b21 --- /dev/null +++ b/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/GithubPullRequestState.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * The state of the pull request when created. + * - draft: Create the PR as a draft + * - ready: Create the PR as ready for review (default) + */ +export type GithubPullRequestState = "draft" | "ready"; +export const GithubPullRequestState = { + Draft: "draft", + Ready: "ready", +} as const; diff --git a/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/GithubSelfhostedSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/GithubSelfhostedSchema.ts index 8de308683c7b..b58bd543fddb 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/GithubSelfhostedSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/GithubSelfhostedSchema.ts @@ -10,4 +10,6 @@ export interface GithubSelfhostedSchema { mode?: FernDefinition.GithubSelfhostedMode; branch?: string; license?: FernDefinition.GithubLicenseSchema; + /** The state of the pull request when created (draft or ready). Only applies when mode is pull-request. */ + "pr-state"?: FernDefinition.GithubPullRequestState; } diff --git a/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/index.ts b/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/index.ts index 55913271ecfb..95161df7655c 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/index.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/api/resources/group/types/index.ts @@ -5,6 +5,7 @@ export * from "./GeneratorOutputSchema"; export * from "./GithubConfigurationSchema"; export * from "./GithubSelfhostedSchema"; export * from "./GithubSelfhostedMode"; +export * from "./GithubPullRequestState"; export * from "./GeneratorSnippetsSchema"; export * from "./GeneratorPublishMetadataSchema"; export * from "./GithubCommitAndReleaseSchema"; diff --git a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/GithubPullRequestSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/GithubPullRequestSchema.ts index b424ac117779..2bc4ee0cbc6c 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/GithubPullRequestSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/GithubPullRequestSchema.ts @@ -7,6 +7,7 @@ import * as FernDefinition from "../../../../api/index"; import * as core from "../../../../core"; import { GithubLicenseSchema } from "../../license/types/GithubLicenseSchema"; import { ReviewersSchema } from "../../reviewers/types/ReviewersSchema"; +import { GithubPullRequestState } from "./GithubPullRequestState"; export const GithubPullRequestSchema: core.serialization.ObjectSchema< serializers.GithubPullRequestSchema.Raw, @@ -17,6 +18,7 @@ export const GithubPullRequestSchema: core.serialization.ObjectSchema< license: GithubLicenseSchema.optional(), mode: core.serialization.stringLiteral("pull-request"), reviewers: ReviewersSchema.optional(), + "pr-state": GithubPullRequestState.optional(), }); export declare namespace GithubPullRequestSchema { @@ -26,5 +28,6 @@ export declare namespace GithubPullRequestSchema { license?: GithubLicenseSchema.Raw | null; mode: "pull-request"; reviewers?: ReviewersSchema.Raw | null; + "pr-state"?: GithubPullRequestState.Raw | null; } } diff --git a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/GithubPullRequestState.ts b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/GithubPullRequestState.ts new file mode 100644 index 000000000000..25db66319491 --- /dev/null +++ b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/GithubPullRequestState.ts @@ -0,0 +1,16 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as FernDefinition from "../../../../api/index"; +import * as core from "../../../../core"; + +export const GithubPullRequestState: core.serialization.Schema< + serializers.GithubPullRequestState.Raw, + FernDefinition.GithubPullRequestState +> = core.serialization.enum_(["draft", "ready"]); + +export declare namespace GithubPullRequestState { + export type Raw = "draft" | "ready"; +} diff --git a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/GithubSelfhostedSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/GithubSelfhostedSchema.ts index a2fdc31db991..ffc95b2732f3 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/GithubSelfhostedSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/GithubSelfhostedSchema.ts @@ -7,6 +7,7 @@ import * as FernDefinition from "../../../../api/index"; import * as core from "../../../../core"; import { GithubSelfhostedMode } from "./GithubSelfhostedMode"; import { GithubLicenseSchema } from "../../license/types/GithubLicenseSchema"; +import { GithubPullRequestState } from "./GithubPullRequestState"; export const GithubSelfhostedSchema: core.serialization.ObjectSchema< serializers.GithubSelfhostedSchema.Raw, @@ -17,6 +18,7 @@ export const GithubSelfhostedSchema: core.serialization.ObjectSchema< mode: GithubSelfhostedMode.optional(), branch: core.serialization.string().optional(), license: GithubLicenseSchema.optional(), + "pr-state": GithubPullRequestState.optional(), }); export declare namespace GithubSelfhostedSchema { @@ -26,5 +28,6 @@ export declare namespace GithubSelfhostedSchema { mode?: GithubSelfhostedMode.Raw | null; branch?: string | null; license?: GithubLicenseSchema.Raw | null; + "pr-state"?: GithubPullRequestState.Raw | null; } } diff --git a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/index.ts b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/index.ts index 55913271ecfb..95161df7655c 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/index.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/serialization/resources/group/types/index.ts @@ -5,6 +5,7 @@ export * from "./GeneratorOutputSchema"; export * from "./GithubConfigurationSchema"; export * from "./GithubSelfhostedSchema"; export * from "./GithubSelfhostedMode"; +export * from "./GithubPullRequestState"; export * from "./GeneratorSnippetsSchema"; export * from "./GeneratorPublishMetadataSchema"; export * from "./GithubCommitAndReleaseSchema"; diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/runLocalGenerationForWorkspace.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/runLocalGenerationForWorkspace.ts index 44f5e5d4694b..a90e7c67397d 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/src/runLocalGenerationForWorkspace.ts +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/runLocalGenerationForWorkspace.ts @@ -24,6 +24,8 @@ import tmp from "tmp-promise"; import { writeFilesToDiskAndRunGenerator } from "./runGenerator"; import { isAutoVersion } from "./VersionUtils"; +export type PullRequestState = "draft" | "ready"; + export async function runLocalGenerationForWorkspace({ token, projectConfig, @@ -35,7 +37,8 @@ export async function runLocalGenerationForWorkspace({ context, absolutePathToPreview, runner, - ai + ai, + prState }: { token: FernToken | undefined; projectConfig: fernConfigJson.ProjectConfig; @@ -48,6 +51,7 @@ export async function runLocalGenerationForWorkspace({ runner: ContainerRunner | undefined; inspect: boolean; ai: generatorsYml.AiServicesSchema | undefined; + prState: PullRequestState | undefined; }): Promise { const results = await Promise.all( generatorGroup.generators.map(async (generatorInvocation) => { @@ -251,7 +255,8 @@ export async function runLocalGenerationForWorkspace({ interactiveTaskContext, selfhostedGithubConfig, absolutePathToLocalOutput, - autoVersioningCommitMessage + autoVersioningCommitMessage, + prState ); } }); @@ -311,7 +316,8 @@ async function postProcessGithubSelfHosted( context: TaskContext, selfhostedGithubConfig: SelhostedGithubConfig, absolutePathToLocalOutput: AbsoluteFilePath, - commitMessage?: string + commitMessage?: string, + prState?: PullRequestState ): Promise { try { context.logger.debug("Starting GitHub self-hosted flow in directory: " + absolutePathToLocalOutput); @@ -373,6 +379,12 @@ async function postProcessGithubSelfHosted( const { prTitle, prBody } = parseCommitMessageForPR(finalCommitMessage); try { + // Determine if PR should be created as draft + // CLI --pr-state takes precedence, then generators.yml pr-state, then default to ready (not draft) + const configPrState = selfhostedGithubConfig["pr-state"]; + const effectivePrState = prState ?? configPrState ?? "ready"; + const isDraft = effectivePrState === "draft"; + const { data: pullRequest } = await octokit.pulls.create({ owner, repo, @@ -380,7 +392,7 @@ async function postProcessGithubSelfHosted( body: prBody, head, base: baseBranch, - draft: false + draft: isDraft }); context.logger.info(`Created pull request: ${pullRequest.html_url}`); diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/runRemoteGenerationForAPIWorkspace.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/runRemoteGenerationForAPIWorkspace.ts index 578ab6fc30c1..965615e1b981 100644 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/src/runRemoteGenerationForAPIWorkspace.ts +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/runRemoteGenerationForAPIWorkspace.ts @@ -16,6 +16,8 @@ export interface RemoteGenerationForAPIWorkspaceResponse { snippetsProducedBy: generatorsYml.GeneratorInvocation[]; } +export type PullRequestState = "draft" | "ready"; + export async function runRemoteGenerationForAPIWorkspace({ projectConfig, organization, @@ -28,6 +30,7 @@ export async function runRemoteGenerationForAPIWorkspace({ whitelabel, absolutePathToPreview, mode, + prState, fernignorePath }: { projectConfig: fernConfigJson.ProjectConfig; @@ -41,6 +44,7 @@ export async function runRemoteGenerationForAPIWorkspace({ whitelabel: FernFiddle.WhitelabelConfig | undefined; absolutePathToPreview: AbsoluteFilePath | undefined; mode: "pull-request" | undefined; + prState: PullRequestState | undefined; fernignorePath: string | undefined; }): Promise { if (generatorGroup.generators.length === 0) {