Files
ci-actions/README.md
T
2026-09-26 05:57:32 +00:00

5.3 KiB

ci-actions — the platform-owned custom-app pipeline

This directory is synced verbatim into the dealsage-platform/ci-actions repo on each environment's Gitea by infra/gitea/gitea-org.sh (the same way templates/custom-app is synced into the template repo), and tagged v1 plus v<VERSION> from the VERSION file. App repos call it from a one-step workflow:

- uses: https://git.dealsage.io/dealsage-platform/ci-actions/pipeline@v1
  with:
    publish-token: ${{ secrets.PUBLISH_TOKEN }}

Why it exists: an app repo receives its workflow once, when it is generated from the template, and never again. Keeping the pipeline here means a fix ships to every app on its next push, instead of being patched repo by repo.

What pipeline does

Step Script Notes
Tooling scripts/ensure-tools.sh curl + jq; AWS CLI only for the S3 upload path
Manifest scripts/manifest.sh validates .dealsage/app.json, exports APP_ID, CHECKS_JSON
Build record scripts/start-build.sh posts the run to record-app-build (commit, branch, author); every later exit closes it with an outcome, so failed runs show in the app hub
Set up Node.js actions/setup-node@v4 node-version input
Install scripts/install.sh runs the install-command input (npm install --no-audit --no-fund by default); a failure closes the run as build_failed with the output's tail
Checks scripts/run-checks.sh runs each declared check, posts every result to record-app-check; a failed required check stops here
Build scripts/build.sh npm run build with VITE_SUPABASE_URL/ANON_KEY from org variables; recorded as the build check
Publish scripts/publish.sh allocate → upload → promote request; DealSage applies the app's publish policy
Close (no publish) scripts/finish-build.sh with skip-publish, records the run as checked
Report the steps scripts/finish-steps.sh if: always(): settles the step list and closes a run no script closed (see below)

Steps on the build page

Gitea shows this whole action as a single step of the job, so the scripts report their own steps to record-app-build (action steps) and the app hub's build page lists them while the run is going: status, duration, a one-line summary and, for a step that failed, the tail of its output. The step list is PIPELINE_STEPS in scripts/lib.sh, in action.yml order (keep the two in step); each script marks its step running when it starts and passed, failed or skipped when it ends (step_start / step_done; fail_build and die fail the running step). Set up Node.js is a stock action, so the step before it starts it and the step after it ends it.

State lives in a file under $RUNNER_TEMP that every step of the job shares, so a step that dies still leaves what it wrote. The last step, finish-steps.sh, runs whatever happened: a step still running failed, steps that never started were skipped, and if nothing closed the build record (Set up Node.js failed, a script crashed) it closes the run as failed, naming the step, instead of leaving it "running" until the hub calls it lost. Reporting is best effort: posts time out after 20 s, and after the first refused post the run stops sending steps; the build itself never waits on or fails because of them.

The action never decides whether a build goes live. record-app-version's promote action does, from the app's publish_policy and default branch, and answers promoted: true|false plus the channel the version landed on (live or branch/<name>). Every build of every branch becomes a version, so any of them can be opened by URL and promoted later.

The manifest (.dealsage/app.json)

Owned by the app's team; schema in manifest.schema.json.

{
  "appId": "…",
  "checks": [
    { "id": "typecheck", "name": "TypeScript", "run": "npm run typecheck" },
    { "id": "test", "name": "Unit tests", "run": "npm test -- --run", "required": true },
    { "id": "conventions", "uses": "conventions", "required": false }
  ],
  "dependencies": { "objectTypes": [ { "apiName": "contract_agreement", "schemaVersion": 2 } ] }
}

required defaults to true. Platform checks (uses) live in pipeline/checks/ and run inside the same job; conventions verifies the repo still has the shape the platform relies on.

Environment the caller provides

From org variables: DEALSAGE_FUNCTIONS_URL, SUPABASE_URL, SUPABASE_ANON_KEY, and either CUSTOM_APPS_WORKER_URL (bundles are PUT through the serving Worker with the short-lived upload token from allocate, so CI holds no R2 key) or R2_ACCOUNT_ID + R2_BUCKET with the R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY secrets (S3 sync, the path prod uses today). PUBLISH_TOKEN is the app's own repo secret.

Testing locally

test/run-local.sh builds a throwaway app repo and runs the scripts against a stub DealSage (test/stub_server.py) for three scenarios: auto policy, a failing required check, and manual policy. Run it after any change here; it needs bash, jq, git, node and python3.

Releasing a change

Bump VERSION for anything user-visible. Non-breaking changes keep the v1 tag moving; a breaking change (new required input, different env contract) gets v2 and a stub update in the template plus a platform-files refresh for existing apps.