This repository contains the necessary code for launching a cloud-gov application with two separate functions:
- Building and deploying a preview website (using Astro) which is based on content and settings from a pages-editor instance.
- Deploying a sidecar proxy application to ensure that the site is only accessible to certain users.
This site itself will prioritize standardization and consistency above deep customization.
Both portion of the application can be run locally. They require a number of environment variables to be set, shown in .env.example. If those are availble, the site runs in preview mode:
npm run devand the proxy runs with:
node oauth/index.jsThis project uses Vitest and Astro Container for testing Astro components. See docs/TESTING.md for detailed information on writing and running tests.
You can auto format your code by using the package.json script:
npm run formatYou can check your if the code is formatted by using the package.json script:
npm run format:checkThis check is also used in CI to verify the code is formatted in a pull request before the pull request is merged.
Fetch Implementation Locations
- Site-wide queries for global collections (like site configuration, menu, footer, etc.) are implemented at Layout level (
src/pages/index.astro,src/layouts). - Content collection queries (like news, posts, pages, etc.) are implemented at
src/pageslevel:- for collection in
src/pages/COLLECTION_NAME/page/index.astro; - for collection item in
src/pages/COLLECTION_NAME/[slug].astro.
- for collection in
- Fetch functions are:
- implemented in
src/utilities/fetch/directory; - re-exported via barrel pattern in
src/utilities/fetch/index.ts.
- implemented in
Functions
- To fetch a global collection add new function similar to
fetchSiteConfig(),fetchHomePage()fromsrc/utilities/fetch/queries.ts. - To fetch collection items use
fetchCollection(collectionName)fromsrc/utilities/fetch/queries.ts. - To fetch single items by slug use
fetchSlug(collectionName, slug)fromsrc/utilities/fetch/queries.ts. - Static paths (
getStaticPaths()) should be implemented insrc/utilities/fetch/staticPaths.ts. - Response mapping should be implemented:
- for small mappers in
src/utilities/fetch/contentMappers.ts. - for large mappers in
src/utilities/fetchindividual files as needed.
- for small mappers in
Configuration
- Render/Preview modes are handled automatically in
payloadFetch()(src/utilities/fetch/payload-fetch.ts) and reflect state of environment-specific variablesRENDER_MODEandPREVIEW_MODE.
The CI pipeline is used to deploy the sites to allow pages-editor users to preview their content updates in the same layout, configuration, and theme as their production site. This repository contains the defintion for a single Concourse pipeline. This pipeline is responsible for reading from a specific S3 bucket and deploying one application per JSON file found there. Those JSON files correspond to sites created by pages-editor and contain at least the following properties:
name: The name of the siteapiKey: The API key corresponding to a "bot" user for the site. This key has read-only access to the site's contents
The deployment process is orchestrated through a Concourse CI pipeline that automatically deploys multiple sites from a centralized S3 bucket. Here's the complete flow:
1. Pipeline Setup (set-pipeline job)
- The pipeline first boots up and sets itself using environment-specific configuration
- It uses the
deploy-envvariable to determine which environment to deploy to (likely staging/production)
2. Code Checks (test job)
- The pipeline runs the automated tests and formatting check whenever the repository source is updated
- If the test or the formatting check fails, the pull request status checks will fail and a developer must fix the issues before being able to merge the pull request
2. Site Discovery (new-deploys job)
- The pipeline monitors an S3 bucket for changes to site configurations
- It runs the
ls-sitestask which scans the S3 bucket for JSON files in the_sites/directory - Each JSON file represents a site created by the
pages-editor - Combines all site configurations into a single
sites.jsonfile
3. Parallel Site Deployment
- For each site discovered, the pipeline runs a
deploy-site-gantrytask - Up to 20 sites can deploy simultaneously (
max_in_flight: 20) - Each site gets its own Cloud Foundry application named
{site-name}-site-gantry
4. Site Configuration Requirements Each site JSON file must contain:
name: The site identifierapiKey: A read-only API key for accessing site content
5. Infrastructure Details
- Cloud Provider: Cloud.gov (Cloud Foundry)
- Deployment Strategy: Rolling deployment for zero-downtime updates
- Configuration: Environment-specific variables and manifests stored in
.cloudgov/directory - Authentication: Uses GPG keys and GitHub SSH keys for secure access
6. Triggering Deployments
- Deployments are automatically triggered when:
- New site configurations are added to the S3 bucket
- Existing site configurations are updated
- The source code repository changes
The deployment uses the following environment variables, configured via the Cloud Foundry manifest and vars files:
Core Application Settings:
NODE_ENV: Node.js environment (development, staging, production)LOG_LEVEL: Logging verbosity levelNPM_CONFIG_PRODUCTION: Set tofalsefor development dependenciesNODE_MODULES_CACHE: Set tofalseto disable module cachingOPTIMIZE_MEMORY: Set totruefor memory optimization
Site Configuration:
SITE: The site identifier slugAPP_ENV: The deployment environment (staging, production)PREVIEW_MODE: (Set totrue) Tells the site it is running in preview mode
External Service URLs:
EDITOR_APP_URL: URL to the pages-editor application (e.g.,https://pages-editor-staging.app.cloud.gov)ASTRO_ENDPOINT: Local Astro development server endpoint (http://localhost:4321)
Authentication & API:
PAYLOAD_API_KEY: API key for accessing the site's content via Payload CMS
Build & Performance:
ASTRO_TELEMETRY_DISABLED: Set to1to disable Astro telemetry collection
Cloud Foundry Configuration:
CF_APP_NAME: Application name in format{site-name}-site-gantryCF_MANIFEST: Path to the Cloud Foundry manifest fileCF_VARS_FILE: Environment-specific variables fileCF_PATH: Source code path for deploymentCF_API: Cloud Foundry API endpointCF_ORG: Target Cloud Foundry organizationCF_SPACE: Target Cloud Foundry spaceCF_STACK: Cloud Foundry stack (e.g., cflinuxfs4)
This site is designed to render both statically and on-demand, depending on the context. This is controlled via the RENDER_MODE environment variable; if not supplied, the site will run in server mode (on-demand).
staticrender: this is the default rendering method for Astro and how this site will be rendered for production use. Content is fetched insrc/content.config.tsfrom the pages-editor API at build-time. Pages paths are generated viasrc/utilities/createGetStaticPathfor dynamically-generated paths. Pages get data via thegetCollectionandgetEntryfunctions fromastro:content. The resulting data will be typed and validated viazodas defined on the collection loaders insrc/content.config.tsserverrender: this is how we render the site for use in "live-preview" mode. Content is fetched from pages-editor viapayloadFetchat run-time. This calls provides page paths and data. The resulting data is not typed (i.e.data = await reponse.json() as any) but is assigned to the same zod schema assuming that the structure matches.
Related notes:
- By convention, globals fetched from pages-editor are assigned to a single-element collection with id
"main" - Site configuration is fetched in the above fashion or dynamically via
payloadFetch. This pattern is captured insrc/config.astro. Note that this function exports an empty fragment that must be present in the rendered page. Any subsequent component can use the site configuration data withimport { data } from "@/config.astro" - dynamically serving is currently done via the development server (
npm run dev) but should eventually be upgraded to the built server files (npm run buildthennode _site/server/entry.mjs).
A gantry is the mobile portion of the structure used to assemble and launch rockets.
