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
37 changes: 29 additions & 8 deletions .github/workflows/resolve-env.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: Resolve env

# Resolves the deploy environment (dev/staging/prod) for the current branch
# via the branch-env-map action, exposing it as a job output so callers can
# reference it in a downstream job's `name:` (e.g. `Deploy [${{ needs.resolve-env.outputs.env }}]`).
# using the same mapping as the branch-env-map action, exposing it as a job
# output so callers can reference it in a downstream job's `name:` (e.g.
# `Deploy [${{ needs.resolve-env.outputs.env }}]`).
# Job names may use the `needs` context, but not composite action outputs
# directly, so this indirection is what makes that possible.

Expand Down Expand Up @@ -34,10 +35,30 @@ jobs:
outputs:
env: ${{ steps.map.outputs.env }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Same mapping as actions/branch-env-map (a test keeps them identical).
# Inlined because a reusable workflow can't reach `./actions/...` in its
# own repo: checkout gives it the caller's tree, not voidflow's.
- id: map
uses: ./actions/branch-env-map
with:
single-environment: ${{ inputs.single-environment }}
shell: bash
env:
REF_NAME: ${{ github.ref_name }}
EVENT_NAME: ${{ github.event_name }}
SINGLE: ${{ inputs.single-environment }}
run: |
if [[ "$SINGLE" == "true" ]]; then
if [[ "$EVENT_NAME" == pull_request* ]]; then
echo "env=preview" >> "$GITHUB_OUTPUT"
elif [[ "$REF_NAME" == main ]]; then
echo "env=prod" >> "$GITHUB_OUTPUT"
else
echo "::error::Refusing to deploy unknown ref: $REF_NAME (expected main or a pull request)" >&2
exit 1
fi
exit 0
fi
case "$REF_NAME" in
main) echo "env=dev" >> "$GITHUB_OUTPUT" ;;
next) echo "env=staging" >> "$GITHUB_OUTPUT" ;;
live) echo "env=prod" >> "$GITHUB_OUTPUT" ;;
*) echo "::error::Refusing to deploy unknown branch: $REF_NAME (expected main, next, or live)" >&2; exit 1 ;;
esac
71 changes: 71 additions & 0 deletions test/branch-env-map.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, expect, test } from 'bun:test'
import { mkdtempSync, readFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'

const read = (p: string) => readFileSync(new URL(`../${p}`, import.meta.url), 'utf8')

const actionScript: string = Bun.YAML.parse(read('actions/branch-env-map/action.yml')).runs.steps.find(
(s: any) => s.id === 'map',
).run
const workflowScript: string = Bun.YAML.parse(read('.github/workflows/resolve-env.yml')).jobs[
'resolve-env'
].steps.find((s: any) => s.id === 'map').run

async function map(script: string, ref: string, event: string, single: boolean) {
const out = join(mkdtempSync(join(tmpdir(), 'voidflow-env-')), 'out')
await Bun.write(out, '')
const proc = Bun.spawn(['bash', '-c', script], {
env: {
PATH: process.env.PATH,
REF_NAME: ref,
EVENT_NAME: event,
SINGLE: String(single),
GITHUB_OUTPUT: out,
},
stdout: 'pipe',
stderr: 'pipe',
})
const code = await proc.exited
return { code, env: readFileSync(out, 'utf8').trim() }
}

describe('resolve-env.yml carries the same mapping as branch-env-map', () => {
test('scripts are identical (no drift)', () => {
expect(workflowScript).toBe(actionScript)
})

test('resolve-env.yml has no `uses` steps (a local ./actions path or checkout cannot work cross-repo)', () => {
const steps = Bun.YAML.parse(read('.github/workflows/resolve-env.yml')).jobs['resolve-env'].steps
expect(steps.filter((s: any) => s.uses)).toEqual([])
})
})

describe.each([
['action', actionScript],
['workflow', workflowScript],
])('%s script', (_name, script) => {
test.each([
['main', 'push', 'env=dev'],
['next', 'push', 'env=staging'],
['live', 'push', 'env=prod'],
])('default mode: %s on %s', async (ref, event, expected) => {
expect(await map(script, ref, event, false)).toEqual({ code: 0, env: expected })
})

test('default mode rejects unknown branches', async () => {
expect((await map(script, 'feature/x', 'push', false)).code).toBe(1)
})

test.each([
['main', 'push', 'env=prod'],
['123/merge', 'pull_request', 'env=preview'],
['feature/x', 'pull_request', 'env=preview'],
])('single-environment: %s on %s', async (ref, event, expected) => {
expect(await map(script, ref, event, true)).toEqual({ code: 0, env: expected })
})

test('single-environment rejects non-main pushes', async () => {
expect((await map(script, 'feature/x', 'push', true)).code).toBe(1)
})
})
Loading