104 lines
5.3 KiB
Markdown
104 lines
5.3 KiB
Markdown
# 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:
|
|
|
|
```yaml
|
|
- 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`.
|
|
|
|
```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.
|