Skip to content

Commit f5696be

Browse files
authored
Merge pull request #7 from fern-api/sync-openapi/use-fern-cli
2 parents c366269 + 7906a6a commit f5696be

21 files changed

Lines changed: 485 additions & 34830 deletions

File tree

.envrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
3+
# Automatically sets up your devbox environment whenever you cd into this
4+
# directory via our direnv integration:
5+
6+
eval "$(devbox generate direnv --print-envrc)"
7+
8+
# check out https://www.jetify.com/docs/devbox/ide_configuration/direnv/
9+
# for more details

.github/workflows/ci.yml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ concurrency:
1111
cancel-in-progress: true
1212

1313
jobs:
14+
actionlint:
15+
name: Lint GitHub Actions
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
21+
- name: Run actionlint
22+
uses: raven-actions/actionlint@v2
23+
with:
24+
matcher: true
25+
1426
ci:
1527
name: Test, Typecheck & Lint
1628
runs-on: ubuntu-latest
@@ -20,8 +32,6 @@ jobs:
2032

2133
- name: Setup pnpm
2234
uses: pnpm/action-setup@v4
23-
with:
24-
version: 10
2535

2636
- name: Setup Node.js
2737
uses: actions/setup-node@v4
@@ -53,8 +63,6 @@ jobs:
5363

5464
- name: Setup pnpm
5565
uses: pnpm/action-setup@v4
56-
with:
57-
version: 10
5866

5967
- name: Setup Node.js
6068
uses: actions/setup-node@v4
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Test resolve-cli
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
concurrency:
9+
group: ${{ github.workflow }}-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
test-resolve-cli:
14+
name: "resolve-cli (${{ matrix.version }})"
15+
runs-on: ubuntu-latest
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
include:
20+
- version: auto
21+
expected-fern-cmd: "npx --yes fern-api@latest"
22+
expect-no-version-redirection: "false"
23+
- version: latest
24+
expected-fern-cmd: "npx --yes fern-api@latest"
25+
expect-no-version-redirection: "true"
26+
- version: "0.46.10"
27+
expected-fern-cmd: "npx --yes fern-api@0.46.10"
28+
expect-no-version-redirection: "true"
29+
steps:
30+
- name: Checkout
31+
uses: actions/checkout@v4
32+
33+
- name: Resolve CLI
34+
id: cli
35+
uses: ./actions/resolve-cli
36+
with:
37+
version: ${{ matrix.version }}
38+
39+
- name: Assert fern-cmd
40+
shell: bash
41+
run: |
42+
actual="${{ steps.cli.outputs.fern-cmd }}"
43+
expected="${{ matrix.expected-fern-cmd }}"
44+
if [[ "$actual" != "$expected" ]]; then
45+
echo "::error::fern-cmd mismatch: expected '$expected', got '$actual'"
46+
exit 1
47+
fi
48+
echo "fern-cmd OK: $actual"
49+
50+
- name: Assert FERN_NO_VERSION_REDIRECTION
51+
shell: bash
52+
run: |
53+
expect="${{ matrix.expect-no-version-redirection }}"
54+
if [[ "$expect" == "true" ]]; then
55+
if [[ -z "${FERN_NO_VERSION_REDIRECTION:-}" ]]; then
56+
echo "::error::FERN_NO_VERSION_REDIRECTION should be set but is not"
57+
exit 1
58+
fi
59+
echo "FERN_NO_VERSION_REDIRECTION OK: set to '${FERN_NO_VERSION_REDIRECTION}'"
60+
else
61+
if [[ -n "${FERN_NO_VERSION_REDIRECTION:-}" ]]; then
62+
echo "::error::FERN_NO_VERSION_REDIRECTION should NOT be set but is '${FERN_NO_VERSION_REDIRECTION}'"
63+
exit 1
64+
fi
65+
echo "FERN_NO_VERSION_REDIRECTION OK: not set"
66+
fi
67+
68+
- name: Assert FERN_RUN_ID
69+
shell: bash
70+
run: |
71+
if [[ -z "${FERN_RUN_ID:-}" ]]; then
72+
echo "::error::FERN_RUN_ID is not set"
73+
exit 1
74+
fi
75+
echo "FERN_RUN_ID OK: ${FERN_RUN_ID}"

actions/generate/action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ description: "[ALPHA] Runs fern generate on push to main and opens SDK PRs in SD
33
author: "Fern"
44

55
inputs:
6+
version:
7+
description: "Fern CLI version to use. 'auto' respects fern.config.json and falls back to latest, 'latest' always uses the newest release, 'inherit' uses the bootstrapped binary as-is, or pin to a specific version (e.g. '0.15.0')."
8+
required: false
9+
default: "auto"
610
fern-token:
711
description: "Fern token for API access and SDK repo PR creation"
812
required: true

actions/resolve-cli/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# resolve-cli
2+
3+
A shared composite action that resolves the Fern CLI command based on a requested version. Used internally by other Fern actions to centralize CLI version resolution logic.
4+
5+
## Inputs
6+
7+
| Input | Description | Default |
8+
|-------|-------------|---------|
9+
| `version` | `auto` respects `fern.config.json`, `latest` uses the newest release, `inherit` uses whatever CLI is on PATH, or a specific version/npm tag (e.g. `0.15.0`, `beta`). | `auto` |
10+
11+
## Outputs
12+
13+
| Output | Description |
14+
|--------|-------------|
15+
| `fern-cmd` | The resolved command to invoke the Fern CLI (e.g. `npx --yes fern-api@latest` or `fern`). |
16+
17+
## Usage
18+
19+
```yaml
20+
steps:
21+
- uses: fern-api/actions/resolve-cli@main
22+
id: cli
23+
with:
24+
version: "latest"
25+
26+
- run: ${{ steps.cli.outputs.fern-cmd }} generate
27+
```
28+
29+
## Version resolution
30+
31+
| `version` value | Behavior |
32+
|-----------------|----------|
33+
| `auto` | Installs latest via `npx`, lets the CLI resolve from `fern.config.json` at runtime. |
34+
| `latest` | Installs latest via `npx` with `FERN_NO_VERSION_REDIRECTION=true`. |
35+
| `inherit` | Uses bare `fern` from PATH with `FERN_NO_VERSION_REDIRECTION=true`. Fails if `fern` is not found. |
36+
| `0.15.0` / `beta` / any tag | Installs that version via `npx` with `FERN_NO_VERSION_REDIRECTION=true`. |

actions/resolve-cli/action.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Resolve Fern CLI
2+
description: "Resolves the Fern CLI command based on the requested version. Outputs a fern-cmd string to use in subsequent steps."
3+
author: "Fern"
4+
5+
inputs:
6+
version:
7+
description: "Fern CLI version to use. 'auto' respects fern.config.json and falls back to latest, 'latest' always uses the newest release, 'inherit' uses whatever CLI is already on PATH, or pin to a specific version or npm tag (e.g. '0.15.0', 'beta')."
8+
required: false
9+
default: "auto"
10+
11+
outputs:
12+
fern-cmd:
13+
description: "The resolved fern command to use in subsequent steps."
14+
value: ${{ steps.resolve.outputs.fern-cmd }}
15+
16+
runs:
17+
using: composite
18+
steps:
19+
- name: Set FERN_RUN_ID
20+
shell: bash
21+
run: |
22+
if [ -z "${FERN_RUN_ID}" ]; then
23+
FERN_RUN_ID=$(node -e "console.log(require('crypto').randomUUID())")
24+
echo "FERN_RUN_ID=${FERN_RUN_ID}" >> "$GITHUB_ENV"
25+
echo "Generated new FERN_RUN_ID: ${FERN_RUN_ID}"
26+
else
27+
echo "Inheriting existing FERN_RUN_ID: ${FERN_RUN_ID}"
28+
fi
29+
30+
- name: Resolve Fern CLI version
31+
id: resolve
32+
shell: bash
33+
env:
34+
VERSION: ${{ inputs.version }}
35+
run: |
36+
# auto → npx fern-api@latest (let CLI resolve from fern.config.json)
37+
# latest → npx fern-api@latest with FERN_NO_VERSION_REDIRECTION
38+
# inherit → bare `fern` from PATH with FERN_NO_VERSION_REDIRECTION
39+
# <other> → npx fern-api@<version> with FERN_NO_VERSION_REDIRECTION
40+
if [ "$VERSION" = "auto" ]; then
41+
FERN_CMD="npx --yes fern-api@latest"
42+
elif [ "$VERSION" = "inherit" ]; then
43+
if ! command -v fern &>/dev/null; then
44+
echo "::error::version is 'inherit' but fern is not on PATH."
45+
exit 1
46+
fi
47+
FERN_CMD="fern"
48+
echo "FERN_NO_VERSION_REDIRECTION=true" >> "$GITHUB_ENV"
49+
else
50+
FERN_CMD="npx --yes fern-api@$VERSION"
51+
echo "FERN_NO_VERSION_REDIRECTION=true" >> "$GITHUB_ENV"
52+
fi
53+
54+
echo "fern-cmd=$FERN_CMD" >> "$GITHUB_OUTPUT"
55+
echo "Resolved fern command: $FERN_CMD"

actions/setup-cli/action.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: Setup Fern CLI
2-
description: "Install the Fern CLI in your GitHub Actions workflow. Requires Node.js and npm — add actions/setup-node before this step if your runner doesn't include them."
2+
description: "Install the Fern CLI globally in your GitHub Actions workflow. Requires Node.js and npm — add actions/setup-node before this step if your runner doesn't include them."
33
author: "Fern"
44

55
branding:
@@ -8,7 +8,7 @@ branding:
88

99
inputs:
1010
version:
11-
description: "Fern CLI version to install. Use 'latest' to always get the newest release, or pin to a specific version (e.g. '0.15.0') for reproducible builds."
11+
description: "Fern CLI version to install globally. Use 'latest' to get the newest release, or pin to a specific version or npm tag (e.g. '0.15.0', 'beta')."
1212
required: false
1313
default: "latest"
1414

@@ -39,11 +39,11 @@ runs:
3939
VERSION: ${{ inputs.version }}
4040
run: |
4141
if ! command -v npm &>/dev/null; then
42-
echo "Error: npm is not available. Please add a Node.js setup step before this action."
42+
echo "::error::npm is not available. Please add a Node.js setup step before this action."
4343
exit 1
4444
fi
4545
if ! command -v node &>/dev/null; then
46-
echo "Error: node is not available. Please add a Node.js setup step before this action."
46+
echo "::error::node is not available. Please add a Node.js setup step before this action."
4747
exit 1
4848
fi
4949

actions/sync-openapi/action.yml

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ description: "Keep your Fern config up to date — pull OpenAPI specs from a URL
33
author: "Fern"
44

55
inputs:
6+
version:
7+
description: "Fern CLI version to use. Use 'latest' to always get the newest release, a specific version (e.g. '0.15.0') for reproducible builds, or 'inherit' to use whatever CLI is already installed."
8+
required: false
9+
default: "latest"
610
token:
711
description: "GitHub token with contents: write and pull-requests: write on the target repository."
812
required: true
913
update_from_source:
10-
description: "Set to 'true' to run 'fern api update' and pull the latest spec from the URL configured in generators.yml. Use this for Case 1 (public URL). Set to 'false' to sync files between repositories (Case 2)."
14+
description: "Set to 'true' to run 'fern api pull' and fetch the latest spec from the URL configured in generators.yml. Set to 'false' to sync files between repositories."
1115
required: false
1216
default: "false"
1317
repository:
@@ -17,17 +21,55 @@ inputs:
1721
description: "YAML or JSON array of file/folder mappings to sync from this repo into the target repository. Each entry must have 'from' and 'to' fields (relative to repo roots), and an optional 'exclude' list of glob patterns. Only used when update_from_source is 'false'."
1822
required: false
1923
branch:
20-
description: "Branch name to create or update in the target repository. Must be a stable name — do not use dynamic or timestamped names, or PR deduplication will break. Commits accumulate on this branch across runs."
24+
description: "Branch name to create or update. Must be a stable name — do not use dynamic or timestamped names, or PR deduplication will break. Defaults to 'fern/pull-spec' for update_from_source mode and 'fern/sync-specs' for sync mode."
2125
required: false
22-
default: "fern/sync-openapi"
2326
auto_merge:
24-
description: "Set to 'true' to push directly to the branch without opening a PR. Set to 'false' to open a PR from the branch onto main. You must use 'true' when branch is 'main'."
27+
description: "Set to 'true' to push directly to the branch without opening a PR. Set to 'false' to open a PR from the branch onto main."
2528
required: false
2629
default: "false"
2730

2831
runs:
29-
using: "node20"
30-
main: "dist/index.js"
32+
using: composite
33+
steps:
34+
- name: Resolve Fern CLI
35+
id: cli
36+
uses: fern-api/actions/resolve-cli@main
37+
with:
38+
version: ${{ inputs.version }}
39+
40+
- name: Pull spec from origin
41+
if: inputs.update_from_source == 'true'
42+
shell: bash
43+
env:
44+
FERN_TOKEN: ${{ inputs.token }}
45+
TOKEN: ${{ inputs.token }}
46+
BRANCH: ${{ inputs.branch }}
47+
AUTO_MERGE: ${{ inputs.auto_merge }}
48+
run: |
49+
ARGS=()
50+
ARGS+=(--token "$TOKEN")
51+
[ -n "$BRANCH" ] && ARGS+=(--branch "$BRANCH")
52+
[ "$AUTO_MERGE" = "true" ] && ARGS+=(--auto-merge)
53+
${{ steps.cli.outputs.fern-cmd }} gha pull-spec "${ARGS[@]}"
54+
55+
- name: Sync files to target repository
56+
if: inputs.update_from_source != 'true'
57+
shell: bash
58+
env:
59+
FERN_TOKEN: ${{ inputs.token }}
60+
REPOSITORY: ${{ inputs.repository }}
61+
SOURCES: ${{ inputs.sources }}
62+
TOKEN: ${{ inputs.token }}
63+
BRANCH: ${{ inputs.branch }}
64+
AUTO_MERGE: ${{ inputs.auto_merge }}
65+
run: |
66+
ARGS=()
67+
ARGS+=(--repository "$REPOSITORY")
68+
ARGS+=(--sources "$SOURCES")
69+
ARGS+=(--token "$TOKEN")
70+
[ -n "$BRANCH" ] && ARGS+=(--branch "$BRANCH")
71+
[ "$AUTO_MERGE" = "true" ] && ARGS+=(--auto-merge)
72+
${{ steps.cli.outputs.fern-cmd }} gha sync-specs "${ARGS[@]}"
3173
3274
branding:
3375
icon: "refresh-cw"

0 commit comments

Comments
 (0)