-
Notifications
You must be signed in to change notification settings - Fork 0
169 lines (148 loc) · 7.12 KB
/
Copy pathcli-ci.yml
File metadata and controls
169 lines (148 loc) · 7.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
name: CLI CI
on:
push:
branches: [ dev, production ]
pull_request:
# `production` is included so the dev→production promotion PR is also gated
# by lint/typecheck/build (and is required by the production ruleset).
branches: [ dev, production ]
workflow_dispatch:
permissions:
contents: read
jobs:
secret-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout (full history)
uses: actions/checkout@v7
with:
# Full history so gitleaks scans every commit, not just the tip.
fetch-depth: 0
- name: Run gitleaks
env:
# Pinned release; bump deliberately. Run the binary directly rather than
# gitleaks/gitleaks-action@v2, which requires a paid GITLEAKS_LICENSE for
# organization-owned repos.
GITLEAKS_VERSION: 8.30.1
run: |
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz gitleaks
# --log-opts=HEAD scopes the scan to commits reachable from what's
# checked out: the whole history of this branch (or of the PR merge
# commit, i.e. base + PR commits), but NOT unrelated branches.
# `fetch-depth: 0` fetches refs/heads/* — every branch — and gitleaks
# otherwise scans all of them, so an open branch that legitimately
# commits a high-entropy value plus its own .gitleaks.toml allowlist
# would fail every OTHER branch's scan, which is judged against the
# allowlist at its own tip. Each branch is still fully scanned by its
# own PR, and pushes to dev/production scan their full history.
./gitleaks git . --redact --verbose --no-banner --log-opts=HEAD
lint-and-test:
runs-on: ubuntu-latest
# This repo is PUBLIC and runs no step that reaches into the private
# devicecloud-dev/dcd repo. It used to check out that repo's mock-api over an
# SSH deploy key to run test/integration/*, which meant a private-repo
# credential lived in a public repo's secrets and the API's OpenAPI spec was
# pulled onto the runner on every same-repo PR. dcd#1036 deleted that mock-api;
# rather than re-point at it, the linkage is gone.
#
# The consequence is deliberate: test/integration/* does NOT run here, and
# neither does the swagger contract-drift check it provided (spec drift used to
# surface as a Prism 422). Only test/unit/* runs — pure, no backend. To run the
# integration suite locally, point MOCK_API_DIR at a mock; see CLAUDE.md.
steps:
- name: Checkout CLI
uses: actions/checkout@v7
with:
path: cli
- name: Setup pnpm
uses: pnpm/action-setup@v6.1.0
with:
version: 10
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: '22'
cache: 'pnpm'
cache-dependency-path: './cli/pnpm-lock.yaml'
- name: Install CLI dependencies
working-directory: ./cli
run: pnpm install --frozen-lockfile
- name: Run CLI linter
working-directory: ./cli
run: pnpm lint
- name: Type check (strict, src + tests)
working-directory: ./cli
run: pnpm typecheck
- name: Run CLI unit tests
working-directory: ./cli
run: pnpm test:unit
- name: Note skipped integration tests
run: echo "::notice::Integration tests are not run in CI — they need a mock of the dcd API, and this public repo does not reach into the private one. Lint, typecheck, unit tests, build and audit all ran."
- name: Build CLI
working-directory: ./cli
run: pnpm build
- name: Security audit
working-directory: ./cli
run: pnpm audit --audit-level moderate
# Promotions to `production` MUST pin the stable version with a `Release-As:`
# footer on a commit. Without it release-please derives the stable version
# from the most recent tag reachable from `production` — and because a
# promotion is a merge commit, every beta tag is reachable, so it picks up a
# `-beta` version. That is exactly how 5.6.0 ended up with `production`
# carrying 5.6.0-beta.1 in package.json and a release PR that could not
# publish. 5.5.0 got a pin (`chore: pin the 5.5.0 promotion`) and came out
# correct; 5.6.0's was abandoned and did not.
#
# It has to be on a NORMAL commit, not the merge commit — release-please's
# commit splitting is unreliable on merges.
promotion-pin:
if: github.event_name == 'pull_request' && github.base_ref == 'production'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Require a stable Release-As pin
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
git fetch --quiet origin dev
# Scope matters more than the pattern here. A promotion merges the whole
# of `dev`, whose history carries a `Release-As:` footer from every past
# promotion (5.0.0, 5.1.0, 5.1.1, 5.2.0, 5.3.0, 5.3.1 …). Scanning
# `base..head` therefore always finds one and passes vacuously — checked
# against the real #176, which it waved through. Only commits unique to
# this promotion branch count: everything on `production` and everything
# on `dev` is excluded. `--no-merges` because release-please's commit
# splitting is unreliable on merge commits, so a pin has to sit on an
# ordinary one.
#
# NB `^ref` not `--not ref`: --not is a TOGGLE over everything that
# follows, so `--not A --not B` excludes A and re-includes B.
MESSAGES=$(git log --no-merges --format=%B "$HEAD_SHA" "^$BASE_SHA" "^origin/dev")
if echo "$MESSAGES" | grep -qE '^Release-As:[[:space:]]*[0-9]+\.[0-9]+\.[0-9]+[[:space:]]*$'; then
echo "Found $(echo "$MESSAGES" | grep -oE '^Release-As:[[:space:]]*[0-9]+\.[0-9]+\.[0-9]+' | head -1)"
exit 0
fi
if echo "$MESSAGES" | grep -qE '^Release-As:'; then
echo "::error::This promotion pins a PRERELEASE version. The stable line must be pinned to a plain X.Y.Z."
echo "$MESSAGES" | grep -E '^Release-As:' >&2
exit 1
fi
echo "::error::No 'Release-As: X.Y.Z' footer on any non-merge commit unique to this promotion."
{
echo "Add one as its own commit on the promotion branch:"
echo " git commit --allow-empty -m 'chore: pin the X.Y.Z promotion' -m 'Release-As: X.Y.Z'"
echo
echo "Do not skip it on the grounds that the conventional commits since the last"
echo "stable already imply the right bump. They do not: a promotion is a merge, so"
echo "every beta tag becomes reachable from production, and release-please picks the"
echo "newest reachable tag as its base. That is how the 5.6.0 promotion — which"
echo "reasoned exactly that way — produced a 'chore(production): release 5.6.0-beta.1'"
echo "release PR and left production carrying a prerelease in package.json."
} >&2
exit 1