React Deploy to Firebase Hosting
react-deploy-firebase@1.0.1 Template Build a React web app and publish it to Firebase Hosting, live or to a preview channel.
Start from it
flightstack template use react-deploy-firebase What it does
Install, build and deploy a React web app to Firebase Hosting in one job, asserting the built bundle before it can ship.
Inputs
-
projectIdstring required - Firebase project id, e.g. postwire-ea1e8.
-
sitestring - Hosting site name. Naming it is load-bearing: an unnamed deploy targets the project's DEFAULT site, and a project can host more than one app, so leaving this empty lets a deploy overwrite a sibling site by omission.
-
channelstring - Preview channel name. Empty deploys to the live site.
-
expiresstring default7d - How long a preview channel lives. Ignored for live deploys.
-
workingDirstring default. - Directory holding package.json and firebase.json.
-
outputDirstring defaultdist - Directory the build writes to. Only used for the bundle assertions - what actually gets uploaded is whatever firebase.json declares.
-
buildboolean defaulttrue - Install dependencies and build before deploying.
-
buildScriptstring defaultbuild - The npm script that produces the production bundle.
-
packageManagerauto | npm | pnpm | yarn | bun defaultauto - Which package manager to use.
-
envobject - Build-time environment variables, compiled INTO the bundle and therefore public. Never put a secret here.
-
mustContainarray - Strings that must appear in the built JavaScript.
-
mustNotContainarray - Strings that must NOT appear, e.g. "localhost:". Checked before the upload, so a bad bundle never reaches the site.
-
includestring default*.js - Filename pattern the assertions are checked against.
-
firebaseToolsVersionstring defaultlatest - Version of firebase-tools to run via npx.
Definition
name: React Deploy to Firebase Hosting
description: >-
Build a React web app and publish it to Firebase Hosting, to the live site or
to a preview channel.
triggers:
- type: push
branches: [main]
jobs:
deploy:
name: Deploy console
type: plugin/react-deploy-firebase
with:
projectId: postwire-ea1e8
site: postwire-console-app
outputDir: apps/console/dist
env:
VITE_API_URL: https://api.postwire.ai
mustContain:
- api.postwire.ai
plugin:
name: react-deploy-firebase
displayName: React Deploy to Firebase Hosting
description: >-
Install, build and deploy a React web app to Firebase Hosting. Does the
whole thing in one job because a job gets a fresh workspace - a deploy job
that expected an earlier job's dist/ would find nothing.
category: deploy
requiredTools:
- name: nodejs
version: ">=20.0.0"
supportedPlatforms:
- linux
- macos
- windows
parameters:
type: object
properties:
projectId:
type: string
description: Firebase project id, e.g. postwire-ea1e8.
site:
type: string
default: ""
description: >-
Hosting site name. Naming it is load-bearing: an unnamed deploy
targets the project's DEFAULT site, and a project can host more than
one app, so leaving this empty lets a deploy overwrite a sibling site
by omission.
channel:
type: string
default: ""
description: >-
Preview channel name. Empty deploys to the live site.
expires:
type: string
default: 7d
description: How long a preview channel lives. Ignored for live deploys.
workingDir:
type: string
default: "."
description: Directory holding package.json and firebase.json.
outputDir:
type: string
default: dist
description: >-
Directory the build writes to. Only used for the bundle assertions -
what actually gets uploaded is whatever firebase.json declares.
build:
type: boolean
default: true
description: Install dependencies and build before deploying.
buildScript:
type: string
default: build
description: The npm script that produces the production bundle.
packageManager:
type: string
enum: [auto, npm, pnpm, yarn, bun]
default: auto
description: Which package manager to use.
env:
type: object
description: >-
Build-time environment variables, compiled INTO the bundle and
therefore public. Never put a secret here.
mustContain:
type: array
description: Strings that must appear in the built JavaScript.
mustNotContain:
type: array
description: >-
Strings that must NOT appear, e.g. "localhost:". Checked before the
upload, so a bad bundle never reaches the site.
include:
type: string
default: "*.js"
description: Filename pattern the assertions are checked against.
firebaseToolsVersion:
type: string
default: latest
description: Version of firebase-tools to run via npx.
required: [projectId]
script: |
set -euo pipefail
cd "{{workingDir}}"
# Said up front rather than discovered after a full build - but NOT fatal.
# A self-hosted agent may already hold credentials from `firebase login` or
# from application-default credentials on the machine, and refusing to try
# would break a setup that works. If there is genuinely no auth the CLI says
# so, clearly, at the point it matters.
if [ -z "${FIREBASE_TOKEN:-}" ] && [ -z "${GOOGLE_APPLICATION_CREDENTIALS:-}" ]; then
echo "note: neither FIREBASE_TOKEN nor GOOGLE_APPLICATION_CREDENTIALS is set."
echo "note: relying on credentials the agent already holds."
echo "note: if the deploy cannot authenticate, set one as a pipeline secret."
fi
pm="{{packageManager}}"
if [ "$pm" = "auto" ]; then
if [ -f pnpm-lock.yaml ]; then pm=pnpm
elif [ -f yarn.lock ]; then pm=yarn
elif [ -f bun.lockb ] || [ -f bun.lock ]; then pm=bun
else pm=npm
fi
fi
case "$pm" in
pnpm|yarn) corepack enable >/dev/null 2>&1 || true ;;
esac
{{#build}}
echo "--- installing dependencies"
case "$pm" in
npm) if [ -f package-lock.json ]; then npm ci; else npm install; fi ;;
pnpm) pnpm install --frozen-lockfile ;;
yarn) yarn install --immutable || yarn install --frozen-lockfile ;;
bun) bun install --frozen-lockfile ;;
esac
echo "--- building"
{{#each env}}
export {{@key}}="{{this}}"
{{/each}}
"$pm" run "{{buildScript}}"
{{/build}}
out="{{outputDir}}"
failed=0
{{#each mustContain}}
if grep -rqF --include="{{include}}" -- '{{this}}' "$out"; then
echo "ok: bundle contains '{{this}}'"
else
echo "FAIL: '{{this}}' is missing from the bundle - refusing to deploy" >&2
failed=1
fi
{{/each}}
{{#each mustNotContain}}
if grep -rqF --include="{{include}}" -- '{{this}}' "$out"; then
echo "FAIL: '{{this}}' appears in the bundle - refusing to deploy" >&2
failed=1
else
echo "ok: bundle is free of '{{this}}'"
fi
{{/each}}
[ "$failed" = "0" ] || exit 1
FIREBASE="npx --yes firebase-tools@{{firebaseToolsVersion}}"
site="{{site}}"
channel="{{channel}}"
if [ -n "$channel" ]; then
echo "--- deploying to preview channel '$channel'"
if [ -n "$site" ]; then
$FIREBASE hosting:channel:deploy "$channel" \
--only "$site" \
--project "{{projectId}}" \
--expires "{{expires}}" \
--non-interactive
else
$FIREBASE hosting:channel:deploy "$channel" \
--project "{{projectId}}" \
--expires "{{expires}}" \
--non-interactive
fi
else
# An `if` rather than `[ -n "$site" ] && target=...`: under `set -e` that
# one-liner exits the script when site is empty, which is the default.
target="hosting"
if [ -n "$site" ]; then
target="hosting:$site"
fi
echo "--- deploying to $target"
$FIREBASE deploy --only "$target" \
--project "{{projectId}}" \
--non-interactive
fi