Skip to content

Latest commit

 

History

History
326 lines (239 loc) · 15.1 KB

File metadata and controls

326 lines (239 loc) · 15.1 KB

Phase 2 — First Deploy

Related: Deployment Overview · Firebase Setup · Updates

Overview

After phase 1 the cloud resources exist but are empty. This phase builds the Angular app and pushes code, rules and configuration into them, then creates the first admin user.

npm run localess:deploy

That one command picks a project, checks it is one Localess manages, regenerates the local project files from the live project, installs, builds and deploys. The rest of this page explains what it does and how to configure it.

Two things it will refuse to do:

  • Deploy to a project without the localess-managed label. A mistyped project id would otherwise install a CMS over something unrelated. If deploy refuses, adopt the project first with npm run localess:setup -- --project <id> — setup is idempotent and will not change existing infrastructure.
  • Deploy without asking. It prints the project, region and targets and defaults to no. Pass --yes to skip the question, which requires --project.

You do not need a local .env.<project-id> to deploy. If one is missing it is regenerated from the live project, and the four hand-edited settings start empty with a warning naming them.


Step by step

1. Install Functions dependencies

cd functions && npm install && cd ..

functions/ is a separate npm package with its own package.json. The root npm install does not cover it.

Run it from inside functions/, not as npm --prefix functions install: npm treats the current directory's project as a dependency of the prefix target, so --prefix silently adds "localess": "file:.." to the tracked functions/package.json. npm run localess:deploy does the same for this reason.

You do not need to compile functions yourself — firebase.json declares:

"functions": {
  "predeploy": "npm --prefix functions run build",
  "source": "functions"
}

so firebase deploy runs tsc for you.

2. Generate version metadata

npm run version:generate

Writes src/assets/version.json with the version from package.json, the build date, and COMMIT_SHA / GITHUB_SHA if present (otherwise local-build). Optional — the file is committed, so a stale one only means stale version metadata in the UI. npm run localess:deploy does not run this step (cloudbuild.yaml does); run it yourself first if you want fresh metadata.

3. Build for production

There are two production builds, and the difference matters:

Command Firebase config it compiles against
npm run build:prod The tracked demo-localess-dev placeholder
npm run build:deploy src/environments/firebase-config.build.json, written by sync for the selected project
npm run build:deploy

npm run localess:deploy and cloudbuild.yaml both use build:deploy. Use build:prod only to check that a production build compiles — its output points at the demo project and must not be deployed by hand. build:deploy fails loudly if firebase-config.build.json is absent, so you cannot accidentally get a demo build out of it.

The swap happens through the deploy configuration in angular.json, which is why that configuration also repeats the environment.prod.ts replacement: combining configurations is a shallow override, so production,deploy would otherwise drop it. A test in scripts/localess/build-config.test.mjs guards that.

Output goes to dist/localess/browser, which is what firebase.json serves as hosting.public.

Build-time configuration

The LOCALESS_* settings are compile-time constants, inlined into the bundle by esbuild through Angular's define builder option. There is no generated source file: the identifiers are declared in src/environments/build-constants.d.ts and read directly by environment.prod.ts.

Defaults live in the production configuration in angular.json, so a build with no overrides always works:

Constant Default Example
LOCALESS_REGION europe-west6 us-central1
LOCALESS_AUTH_PROVIDERS empty — login page shows Email/Password only GOOGLE,MICROSOFT
LOCALESS_AUTH_CUSTOM_DOMAIN empty — no custom auth domain restriction auth.example.com
LOCALESS_LOGIN_MESSAGE empty — no message on the login page Welcome to Localess
LOCALESS_UNSPLASH_ENABLE empty — Unsplash plugin disabled true

npm run localess:deploy overrides them per project, reading .env.<project-id> and passing each one as a --define flag:

npm run build:deploy -- --define LOCALESS_REGION=\"us-central1\" --define LOCALESS_LOGIN_MESSAGE=\"Welcome\"

You do not normally do this by hand — put the values in .env.<project-id> and let npm run localess:deploy assemble the flags. Because they are passed as arguments rather than read from the environment, a stale exported shell variable cannot change what gets built.

Note this is not import.meta.env. Angular uses Vite only for the dev server; production builds go through esbuild, which has no .env file support. define is the Angular-native equivalent, and it constant-folds — LOCALESS_UNSPLASH_ENABLE === 'true' is resolved to a literal at build time and the dead branch is dropped.

LOCALESS_REGION sets the Functions region in four places at once: functions/.env.<project-id>, the /api/v1/** Hosting rewrite, the Angular client's callable region, and the Cloud Build _REGION substitution. Firestore and Storage must be in the same region — Localess uses gen-2 triggers, which have to be co-located with the resource they listen to.

This is the most common first-deploy surprise. Enabling Google sign-in in the Firebase console enables it in the backend; the button only appears if LOCALESS_AUTH_PROVIDERS includes GOOGLE at build time. src/app/auth/login/login.component.ts derives isGoogleAuthEnabled / isMicrosoftAuthEnabled from that string.

Because these are build-time values, changing them requires a rebuild and redeploy — not just a config change in the console.

4. Deploy

npm run localess:deploy

Omit --project and you get the same annotated picker setup uses, minus the create option.

Flag Effect
--project <id> Skip the picker
--only <targets> Override the default targets (see below)
--skip-install Reuse the installed node_modules
--skip-build Reuse dist/localess/browser
--dry-run Regenerate the local files (and correct the localess-region label), print the build and deploy commands, stop
--yes Skip the confirmation. Requires --project.

By default it pushes hosting,functions,storage,firestore,auth — the same set as cloudbuild.yaml, so a local deploy and a CI deploy do the same thing. auth is included because setup no longer provisions Identity Platform: setup enables the service, deploy applies the configuration.

One target is excluded on purpose:

Target Why it is excluded
remoteconfig Would overwrite console-side edits on every deploy

Push it explicitly when you need to: npm run localess:deploy -- --only remoteconfig.

--only also accepts extensions and database, which firebase deploy understands but Localess does not configure in firebase.json.

An unknown target is rejected before anything is touched, so a typo costs nothing.

Deploy never modifies a tracked file: it runs against a generated firebase.<project-id>.json via the CLI's --config flag, so git status stays clean.

A bare deploy covers every target configured in firebase.json:

Target What is pushed
auth Identity Platform providers (idempotent — runs on every deploy)
firestore firestore.rules and firestore.indexes.json
storage storage.rules
functions functions/ — compiled by the predeploy hook
hosting dist/localess/browser + headers and rewrites
remoteconfig remoteconfig.template.json

The first functions deploy is the slowest part: Cloud Build has to build container images for every function.

Required APIs

Setup enables the 15 Google Cloud APIs Localess needs, but a project can drift after that — it may have been provisioned by an older release that required fewer, or had an API switched off by hand. So deploy re-checks them, and enables whatever is off, before it installs or builds.

The check is a single Service Usage call listing every enabled API, so the normal case — a project that is already correct — costs one round trip rather than fifteen. It runs after the confirmation, so a cancelled or --dry-run deploy enables nothing, and before the build, so a drifted project costs seconds instead of a full production build followed by a failure worded in terms of the resource that could not be created.

A cancelled or --dry-run deploy is not entirely side-effect free, though: the local project files have already been regenerated, and the remote localess-region label corrected if it disagreed with the live Firestore location, before the dry-run exit or the confirmation prompt.

translate.googleapis.com is the one that most needs this: nothing in a deploy touches it — it is used at runtime by functions/src/services/translate.service.ts — so if it were off, the first sign would be the translate feature failing in production.

Storage CORS

The browser fetches assets straight from Storage, so the default bucket needs CORS rules or those downloads fail. Deploy checks for them alongside the APIs and applies read-only rules — GET and HEAD from any origin — when the bucket has none.

Only an empty configuration is filled in. Any existing rule is left untouched, because someone chose it: a tighter origin list for a locked-down install should not be silently widened to *.

This used to be done by the setup callable in functions/, which set it while creating the first admin user. That placed it behind the configs/setup guard, so it ran exactly once in a project's lifetime and no later change to the rules could ever reach an existing install — and it left a window between deploy and whenever a human opened /setup in which assets simply did not load. It is ordinary infrastructure, so it lives with the rest of it now.

Setting CORS needs storage.buckets.update. Without it setup warns and carries on rather than failing, and deploy re-checks every run, so the project heals itself once the role is granted.

First-deploy retries

A first deploy races the provisioning that precedes it. The gcf-v2-sources bucket and the Eventarc service agent's permissions are still propagating, so event-triggered functions and publicv1 can fail to be created. firebase deploy reports these as warnings and still exits 0, so the exit code alone cannot be trusted — deploy reads the output instead, and retries the whole command up to three times, 30 seconds apart.

The whole command is retried rather than --only functions on purpose: hosting released against a missing publicv1 serves a dead /api/v1/** rewrite until it is released again.

If functions are still failing after the third attempt, deploy stops and names them. They almost always succeed on a later run — wait a few minutes and repeat the same command.

Function image cleanup policy

When functions are among the targets, deploy first checks the Artifact Registry cleanup policy for the region, and sets it if it is missing or different, so old container images do not accumulate into a slow monthly bill. Without a policy the CLI wants to ask about one on every deploy, and --non-interactive turns that question into a failed deploy of functions that in fact deployed fine. The step reports what it found:

Checking the function image cleanup policy
    + europe-west6: already deleting images after 1 day(s)

Retention is 1 day, the firebase-tools default — deploy passes no --days, and reports the number the CLI itself prints rather than keeping a second copy of it. Anything older than 24 hours in gcf-artifacts is deleted, tagged or not.

Deleting them is safe. These are build artifacts: Cloud Build produces the image, and Cloud Run functions takes its own copy at deploy time. Firebase's own documentation is explicit — "these images are not required for your deployed functions to run" — so cold starts and scale-ups are unaffected, and a 1-day default would otherwise take down every Firebase project a day after it deployed. What you give up is the build cache (the next deploy rebuilds cold) and the ability to pull the exact image that shipped. Rollback does not depend on them either: localess:deploy always rebuilds from source. The function source in the gcf-v2-sources-* bucket is a separate store and is not touched.

It is a no-op on a brand-new project — the gcf-artifacts repository does not exist until functions have been deployed once, and the step says no function images yet; nothing to configure — so the policy takes effect from the second deploy onward.

Failures here are logged and swallowed. The images are already live, so a policy that could not be set is a billing footnote, not a reason to fail a deploy that worked.

5. Create the first admin user

npm run localess:check -- --project <project-id> --fix

It asks for an email, a password and a display name, then creates the account, grants it the role: admin custom claim, and writes its users/{uid} document alongside a seeded Hello World space.

There is no browser route for this. The /setup wizard and the setup Cloud Function behind it were removed — that endpoint could not require authentication, so on a freshly deployed project anyone who knew the project id could claim the administrator account. See why the CLI creates the admin.

For a scripted deploy, pass --admin-email and set LOCALESS_ADMIN_PASSWORD. There is deliberately no password flag.


Verifying the deploy

npm run localess:check -- --project <project-id>

Reports everything below in one pass, plus the things the table cannot see — most usefully the allUsers invoker bindings, which a partially failed first deploy silently omits and no later deploy restores. See Checking an installation.

Check How
App loads https://<project-id>.web.app
Public API responds https://<project-id>.web.app/api/v1/... — see V1 API
Functions deployed npx firebase functions:list --project <id>
Blocking functions registered Firebase console → Authentication → Settings → Blocking functions
Logs npx firebase functions:log --project <id>

The /api/v1/** path is a Hosting rewrite to the publicv1 function. Both the rewrite and functions/.env.<project-id> are generated from the same LOCALESS_REGION, so they cannot drift apart — if they did, the rewrite could not resolve the function and every API request would fail.


Next step

Phase 3 — Pushing Updates