{"id":39075621,"url":"https://github.com/cloud-gov/pages-site-gantry","last_synced_at":"2026-01-17T18:26:28.152Z","repository":{"id":292002166,"uuid":"902546812","full_name":"cloud-gov/pages-site-gantry","owner":"cloud-gov","description":null,"archived":false,"fork":false,"pushed_at":"2026-01-12T21:34:45.000Z","size":5867,"stargazers_count":4,"open_issues_count":26,"forks_count":3,"subscribers_count":6,"default_branch":"main","last_synced_at":"2026-01-12T21:57:37.253Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cloud-gov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-12-12T19:27:50.000Z","updated_at":"2026-01-12T16:23:28.000Z","dependencies_parsed_at":"2025-05-07T16:51:23.615Z","dependency_job_id":"95503f07-e805-4a30-aab1-11fd7697603f","html_url":"https://github.com/cloud-gov/pages-site-gantry","commit_stats":null,"previous_names":["cloud-gov/pages-site-gantry"],"tags_count":0,"template":false,"template_full_name":"cloud-gov/.github","purl":"pkg:github/cloud-gov/pages-site-gantry","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloud-gov%2Fpages-site-gantry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloud-gov%2Fpages-site-gantry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloud-gov%2Fpages-site-gantry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloud-gov%2Fpages-site-gantry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cloud-gov","download_url":"https://codeload.github.com/cloud-gov/pages-site-gantry/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloud-gov%2Fpages-site-gantry/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28515471,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T17:57:59.192Z","status":"ssl_error","status_checked_at":"2026-01-17T17:57:52.527Z","response_time":85,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-17T18:26:27.515Z","updated_at":"2026-01-17T18:26:28.141Z","avatar_url":"https://github.com/cloud-gov.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pages-site-gantry\n\nThis repository contains the necessary code for launching a cloud-gov application with two separate functions:\n\n- Building and deploying a preview website (using [Astro](https://astro.build/)) which is based on content and settings from a [pages-editor](https://github.com/cloud-gov/pages-editor/) instance.\n- Deploying a [sidecar](https://docs.cloudfoundry.org/devguide/sidecars.html) proxy application to ensure that the site is only accessible to certain users.\n\nThis site itself will prioritize standardization and consistency above deep customization.\n\n## Running locally\n\nBoth 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:\n\n```sh\nnpm run dev\n```\n\nand the proxy runs on port 3030 with:\n\n```sh\nnode --watch oauth/index.js\n```\n\n## Tests\n\nThis project uses Vitest and Astro Container for testing Astro components. See [docs/TESTING.md](docs/TESTING.md) for detailed information on writing and running tests.\n\n## Formatting\n\nYou can auto format your code by using the package.json script:\n\n```sh\nnpm run format\n```\n\nYou can check your if the code is formatted by using the package.json script:\n\n```sh\nnpm run format:check\n```\n\nThis check is also used in CI to verify the code is formatted in a pull request before the pull request is merged.\n\n\n## Payload API Calls\n\n**Fetch Implementation Locations**\n- Site-wide queries for global collections (like site configuration, menu, footer, etc.) are implemented at Layout level (`src/pages/index.astro`, `src/layouts`).\n- Content collection queries (like news, posts, pages, etc.) are implemented at `src/pages` level:\n  * for collection in `src/pages/COLLECTION_NAME/page/index.astro`;\n  * for collection item in `src/pages/COLLECTION_NAME/[slug].astro`.\n- Fetch functions are:\n  - implemented in `src/utilities/fetch/` directory;\n  - re-exported via barrel pattern in `src/utilities/fetch/index.ts`.\n\n**Functions**\n- To fetch a global collection add new function similar to `fetchSiteConfig()`, `fetchHomePage()` from `src/utilities/fetch/queries.ts`.\n- To fetch collection items use `fetchCollection(collectionName)` from `src/utilities/fetch/queries.ts`.\n- To fetch single items by slug use `fetchSlug(collectionName, slug)` from `src/utilities/fetch/queries.ts`.\n- Static paths (`getStaticPaths()`) should be implemented in `src/utilities/fetch/staticPaths.ts`.\n- Response mapping should be implemented:\n    - for small mappers in `src/utilities/fetch/contentMappers.ts`.\n    - for large mappers in `src/utilities/fetch` individual files as needed.\n\n**Configuration**\n- Render/Preview modes are handled automatically in `payloadFetch()` (`src/utilities/fetch/payload-fetch.ts`) and\nreflect state of environment-specific variables `RENDER_MODE` and `PREVIEW_MODE`.\n\n\n\n## CI\n\nThe 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:\n\n- `name`: The name of the site\n- `apiKey`: The API key corresponding to a \"bot\" user for the site. This key has read-only access to the site's contents\n\nThe deployment process is orchestrated through a **Concourse CI pipeline** that automatically deploys multiple sites from a centralized S3 bucket. Here's the complete flow:\n\n**1. Pipeline Setup (`set-pipeline` job)**\n- The pipeline first boots up and sets itself using environment-specific configuration\n- It uses the `deploy-env` variable to determine which environment to deploy to (likely staging/production)\n\n**2. Code Checks (`test` job)**\n- The pipeline runs the automated tests and formatting check whenever the repository source is updated\n- 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\n\n**2. Site Discovery (`new-deploys` job)**\n- The pipeline monitors an S3 bucket for changes to site configurations\n- It runs the `ls-sites` task which scans the S3 bucket for JSON files in the `_sites/` directory\n- Each JSON file represents a site created by the `pages-editor`\n- Combines all site configurations into a single `sites.json` file\n\n**3. Parallel Site Deployment**\n- For each site discovered, the pipeline runs a `deploy-site-gantry` task\n- **Up to 20 sites can deploy simultaneously** (`max_in_flight: 20`)\n- Each site gets its own Cloud Foundry application named `{site-name}-site-gantry`\n\n**4. Site Configuration Requirements**\nEach site JSON file must contain:\n- `name`: The site identifier\n- `apiKey`: A read-only API key for accessing site content\n\n**5. Infrastructure Details**\n- **Cloud Provider**: Cloud.gov (Cloud Foundry)\n- **Deployment Strategy**: Rolling deployment for zero-downtime updates\n- **Configuration**: Environment-specific variables and manifests stored in `.cloudgov/` directory\n- **Authentication**: Uses GPG keys and GitHub SSH keys for secure access\n\n**6. Triggering Deployments**\n- Deployments are automatically triggered when:\n  - New site configurations are added to the S3 bucket\n  - Existing site configurations are updated\n  - The source code repository changes\n\n### Environment Variables\n\nThe deployment uses the following environment variables, configured via the Cloud Foundry manifest and vars files:\n\n**Core Application Settings:**\n- `NODE_ENV`: Node.js environment (development, staging, production)\n- `LOG_LEVEL`: Logging verbosity level\n- `NPM_CONFIG_PRODUCTION`: Set to `false` for development dependencies\n- `NODE_MODULES_CACHE`: Set to `false` to disable module caching\n- `OPTIMIZE_MEMORY`: Set to `true` for memory optimization\n\n**Site Configuration:**\n- `SITE`: The site identifier slug\n- `APP_ENV`: The deployment environment (staging, production)\n- `PREVIEW_MODE`: (Set to `true`) Tells the site it is running in preview mode\n\n**External Service URLs:**\n- `EDITOR_APP_URL`: URL to the pages-editor application (e.g., `https://pages-editor-staging.app.cloud.gov`)\n- `ASTRO_ENDPOINT`: Local Astro development server endpoint (`http://localhost:4321`)\n\n**Authentication \u0026 API:**\n- `PAYLOAD_API_KEY`: API key for accessing the site's content via Payload CMS\n\n**Build \u0026 Performance:**\n- `ASTRO_TELEMETRY_DISABLED`: Set to `1` to disable Astro telemetry collection\n\n**Cloud Foundry Configuration:**\n- `CF_APP_NAME`: Application name in format `{site-name}-site-gantry`\n- `CF_MANIFEST`: Path to the Cloud Foundry manifest file\n- `CF_VARS_FILE`: Environment-specific variables file\n- `CF_PATH`: Source code path for deployment\n- `CF_API`: Cloud Foundry API endpoint\n- `CF_ORG`: Target Cloud Foundry organization\n- `CF_SPACE`: Target Cloud Foundry space\n- `CF_STACK`: Cloud Foundry stack (e.g., cflinuxfs4)\n\n## Rendering Patterns\n\nThis site is designed to render both statically and [on-demand](https://docs.astro.build/en/guides/on-demand-rendering/), 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).\n\n- `static` render: this is the default rendering method for Astro and how this site will be rendered for production use. Content is fetched in `src/content.config.ts` from the pages-editor API at build-time. Pages paths are generated via `src/utilities/createGetStaticPath` for dynamically-generated paths. Pages get data via the `getCollection` and `getEntry` functions from `astro:content`. The resulting data will be typed and validated via `zod` as defined on the collection loaders in `src/content.config.ts`\n- `server` render: this is how we render the site for use in \"live-preview\" mode. Content is fetched from pages-editor via `payloadFetch` at 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.\n\nRelated notes:\n\n- By convention, globals fetched from pages-editor are assigned to a single-element collection with id `\"main\"`\n- Site configuration is fetched in the above fashion or dynamically via `payloadFetch`. This pattern is captured in `src/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 with `import { data } from \"@/config.astro\"`\n- dynamically serving is currently done via the development server (`npm run dev`) but should eventually be upgraded to the built server files (`npm run build` then `node _site/server/entry.mjs`).\n\n### Naming\n\n![\"Launch Complex 22 (Gantry Crane) with a Hermes A-1 missile Photo courtesy of White Sands Missile Range Museum\"](https://www.nps.gov/common/uploads/stories/images/nri/20161107/articles/844B4226-1DD8-B71B-0B8061E3C5ABA93C/844B4226-1DD8-B71B-0B8061E3C5ABA93C.jpg)\n\nA gantry is the mobile portion of the structure used to assemble and launch rockets.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloud-gov%2Fpages-site-gantry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcloud-gov%2Fpages-site-gantry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloud-gov%2Fpages-site-gantry/lists"}