-
Notifications
You must be signed in to change notification settings - Fork 0
331 lines (321 loc) · 17.6 KB
/
Copy pathci.yml
File metadata and controls
331 lines (321 loc) · 17.6 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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#
# @Project: @cldmv/git-embedded
# @Filename: /.github/workflows/ci.yml
# @Date: 2026-05-20 00:00:00 -07:00 (1779606000)
# @Author: Nate Corcoran <CLDMV>
# @Email: <Shinrai@users.noreply.github.com>
# @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
#
# Individual repo: .github/workflows/ci.yml
name: 🧪 CI Tests & Build
on:
# Note: do NOT add `paths:` / `paths-ignore:` at the trigger level. Doing
# that makes GitHub skip the workflow entirely for docs-only changes, which
# means `Required PR Check` never posts and the ruleset blocks the merge.
# The reusable workflow's `paths-gate` job does the same job from inside,
# and exposes a `docs_only` output so this workflow can still green-light
# the required check for docs-only PRs (see `required-check` below). The
# ignore globs themselves are passed via the `paths_ignore:` input below
# — override there if your repo needs different rules.
#
# `push` fires for branches in this repo only (forks push to their own remote,
# not ours). Branch protection on the PR reads the status check from the
# commit SHA, so this single trigger covers both pre-PR pushes and PR head
# updates without duplicating runs.
push:
# Bot-managed branches (badges, gh-pages) carry no source to test.
branches-ignore: [badges, gh-pages]
# `pull_request` covers two cases:
# - Fork PRs (push doesn't fire upstream for fork commits).
# - Release PRs from `next` / `hotfixes` → `master`. Their head SHA is
# a bot `chore: bump version` commit that workflow-ci.yml's
# `commit-gate` job filters out on the push path, so without the
# pull_request fallback the release PR's `Required PR Check`
# status never gets posted and the ruleset blocks the merge.
# `branches:` includes the v4 integration branches so PRs targeting
# `next` / `hotfixes` get CI too — feature PRs from forks would
# otherwise get nothing. Non-fork feature PRs still skip the
# pull_request `ci` job (push covers them); see the `if:` on the job.
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches: [master, main, next, hotfixes]
workflow_dispatch:
inputs:
debug:
description: "Enable debug logging for troubleshooting"
type: boolean
required: false
default: false
node_version:
description: "Node.js version to use (default: lts/*)"
type: string
required: false
default: "lts/*"
min_node_version:
description: "Minimum Node.js version for matrix testing (default: 22, oldest non-EOL LTS)"
type: string
required: false
default: "22"
max_node_major:
description: "Override max Node.js major version (default: 22)"
type: string
required: false
default: "22"
lts_only_matrix:
description: "Only include even-numbered (LTS) Node.js major versions in the test matrix"
type: boolean
required: false
default: true
package_manager:
description: "Package manager (npm or yarn)"
type: string
required: false
default: "npm"
test_environment:
description: "Environment for tests (affects NODE_ENV and NODE_OPTIONS --conditions flag)"
type: string
required: false
default: "development"
# ── Coverage badge ───────────────────────────────────────────────
enable_coverage_badge:
description: "Run the coverage + badge-push job after CI passes"
type: boolean
required: false
default: true
coverage_command:
description: "Command to run tests and generate coverage data"
type: string
required: false
default: "npm run ci:coverage"
coverage_summary_path:
description: "Path to the coverage-summary.json produced by Jest / c8"
type: string
required: false
default: "coverage/coverage-summary.json"
badges_branch:
description: "Branch where the badge JSON is published"
type: string
required: false
default: "badges"
badge_filename:
description: "Filename for the badge JSON committed to the badges branch"
type: string
required: false
default: "coverage.json"
upload_coverage_artifact:
description: "Upload the full coverage/ directory as a workflow artifact"
type: boolean
required: false
default: true
# ── Type check ──────────────────────────────────────────────────
type_check_command:
description: "Command to run type checking"
type: string
required: false
default: "npm run test:types"
skip_type_check:
description: "Skip the type-check step in the coverage-badge job"
type: boolean
required: false
default: false
default_branch:
description: "Default branch name — badge is only pushed on pushes to this branch"
type: string
required: false
default: "master"
enable_coverage_pr_comment:
description: "Inject a coverage badge into the PR description on pull request events"
type: boolean
required: false
default: true
# Concurrency policy, by context:
# - FEATURE branches / feature PRs → cancel superseded runs (per-ref group +
# cancel-in-progress): a newer push makes the older run redundant.
# - RELEASE-relevant contexts → NEVER superseded. Pushes to the release base
# branch (derived: the CLDMV_RELEASE_BASE var → the repo's default branch),
# to next/hotfixes, and the next/hotfixes → base release PRs each get a
# UNIQUE group per run (run_id appended), so nothing cancels them. During
# the burst of pushes a release makes to next/hotfixes (the feature squash,
# the post-hotfix base→next sync merge, the bot's `chore: bump version`),
# every run completes and posts a GREEN check instead of the earlier one
# being cancelled into a red X on the release PR. A bare
# `cancel-in-progress: false` is NOT enough — GitHub still cancels the
# middle PENDING run when a newer one queues; a unique group avoids it.
# The base is NOT hardcoded to master/main — it derives the same way
# resolve-release-base does (CLDMV_RELEASE_BASE override → default_branch).
# next/hotfixes are the flow's fixed integration-branch names. github.head_ref
# is set only on pull_request (the release PR's head → next/hotfixes);
# github.ref carries the branch on push.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}${{ (github.ref == format('refs/heads/{0}', vars.CLDMV_RELEASE_BASE != '' && vars.CLDMV_RELEASE_BASE || github.event.repository.default_branch) || github.ref == 'refs/heads/next' || github.ref == 'refs/heads/hotfixes' || github.head_ref == 'next' || github.head_ref == 'hotfixes') && format('-{0}', github.run_id) || '' }}
cancel-in-progress: true
# Workflow-level: matches the broadest write surface the called
# `workflow-ci.yml` reaches across its branches:
# - coverage-badge: contents:write (push to `badges` branch)
# - coverage-pr-comment: pull-requests:write (edit PR description body)
# Jobs that don't need write (CI matrix, commit-gate, the mirror below)
# inherit but never exercise the surface. The mirror job overrides to
# `permissions: {}` since it's pure shell.
permissions:
contents: write
pull-requests: write
jobs:
ci:
name: 🏗️ Continuous Integration
# Run on pull_request when:
# - The PR is from a fork (push doesn't fire upstream for fork commits).
# - The PR is a v4 release PR — head ref is `next` or `hotfixes`
# targeting `master`/`main`. Push-event CI on the head SHA is
# unreliable for these because workflow-ci.yml's `commit-gate`
# filters out the bot's `chore: bump version` commit, so without
# this fallback the release PR's `Required PR Check` never posts.
# Other (in-repo, non-release) PRs skip — the push event on the head
# branch already ran CI and posted status to the SHA.
if: |
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.fork == true ||
github.event.pull_request.head.ref == 'next' ||
github.event.pull_request.head.ref == 'hotfixes'
uses: CLDMV/.github/.github/workflows/workflow-ci.yml@v4
with:
package_name: "@cldmv/git-embedded" # Required: replace with your NPM package name
# Globs that should NOT trigger the heavy CI matrix. When every changed
# file matches one of these, `docs_only=true` flows out of the reusable
# and `required-check` below posts a green Required PR Check without
# running CI. The default in the reusable matches these — override only
# if your repo needs different rules.
paths_ignore: |
**.md
docs/**
*.md
LICENSE
.gitignore
debug: ${{ github.event.inputs.debug == 'true' }}
node_version: ${{ github.event.inputs.node_version || 'lts/*' }}
min_node_version: ${{ github.event.inputs.min_node_version || '22' }}
max_node_major: ${{ github.event.inputs.max_node_major || '22' }}
# LTS-only matrix (even majors: 22, 24, …) on every event. Odd majors
# (23, 25, …) are non-LTS interim releases, and the native-binding test
# toolchain (vitest 4 / rolldown / vite 8) excludes them via `engines`
# (`>=22.12.0`), so a "full matrix" on them only re-discovers a
# known toolchain gap ("Cannot find native binding") rather than a real
# per-version regression. workflow_dispatch can still opt out (set false).
lts_only_matrix: ${{ github.event.inputs.lts_only_matrix != 'false' }}
package_manager: ${{ github.event.inputs.package_manager || 'npm' }}
test_command: "npm test" # Use defaults: NODE_ENV=development, NODE_OPTIONS=--conditions=development
# test_command: "NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override NODE_OPTIONS only
# test_command: "NODE_ENV=test npm test" # Override NODE_ENV only
# test_command: "NODE_ENV=test NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override both
test_environment: ${{ github.event.inputs.test_environment || 'development' }} # Alternative to setting in test_command
build_command: "echo '✓ no build step'"
skip_performance_tests: false
skip_matrix_tests: false
# ── Coverage badge ─────────────────────────────────────────────────────
# Runs after a successful CI build; pushes a Shields.io-compatible badge
# JSON to the `badges` branch (signed commit via bot GPG).
# Only runs on direct pushes to default_branch — PRs and feature branches
# are automatically skipped so coverage always reflects merged master code.
# Requires: the coverage_command produces coverage/coverage-summary.json
enable_coverage_badge: ${{ github.event.inputs.enable_coverage_badge != 'false' }}
default_branch: ${{ github.event.inputs.default_branch || 'master' }} # Badge only pushed when a push lands on this branch
coverage_command: ${{ github.event.inputs.coverage_command || 'npm run ci:coverage' }}
coverage_summary_path: ${{ github.event.inputs.coverage_summary_path || 'coverage/coverage-summary.json' }}
badges_branch: ${{ github.event.inputs.badges_branch || 'badges' }}
badge_filename: ${{ github.event.inputs.badge_filename || 'coverage.json' }}
upload_coverage_artifact: ${{ github.event.inputs.upload_coverage_artifact != 'false' }}
# ── Type check (runs inside the coverage-badge job) ────────────────────
# Skipped deliberately: git-embedded is a dynamic slothlet-composed API
# (self.* / context.* resolved at runtime). tsc can't statically type that
# surface, slothlet's typegen only emits an all-`any` structural interface
# (no real types), and a checkJs pass is ~260 untypeable dynamic-API errors —
# there is no meaningful JS type-check to run. ESLint is the static-analysis
# net. (Investigated 2026-07-19; revisit if the API gains real generated types.)
type_check_command: ${{ github.event.inputs.type_check_command || 'npm run test:types' }}
skip_type_check: true
# ── PR coverage badge ─────────────────────────────────────────────────
# Injects a Shields.io badge + breakdown table directly into the PR body
# on every push to the PR branch. Only fires on pull_request events;
# skipped automatically on push and workflow_dispatch. No files committed.
enable_coverage_pr_comment: ${{ github.event.inputs.enable_coverage_pr_comment != 'false' }}
# Authentication & Bot Configuration
# The workflow supports automatic App token detection for enhanced permissions and proper attribution:
# - WITH App secrets: Operations attributed to CLDMV bot, enhanced permissions for workflow repositories
# - WITHOUT App secrets: Falls back to GitHub Actions bot with standard permissions
# Note: CI workflow currently only runs build/test jobs, but App secrets are included for consistency
# To set up App authentication, add these secrets to your repository settings:
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# Optional: CLDMV Bot credentials for enhanced permissions and proper attribution
# If not provided, will use default GITHUB_TOKEN with GitHub Actions bot attribution
BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }}
BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }}
# Required when enable_coverage_badge: true
BOT_NAME: ${{ secrets.CLDMV_BOT_NAME }}
BOT_EMAIL: ${{ secrets.CLDMV_BOT_EMAIL }}
BOT_GPG_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_GPG_PRIVATE_KEY }}
BOT_GPG_PASSPHRASE: ${{ secrets.CLDMV_BOT_GPG_PASSPHRASE }}
# ✅ Stable check that mirrors the `ci` result so branch protection has a
# single, predictable status name to require. The push event runs on the
# same SHA that becomes the PR head, so the status attaches to the PR
# automatically — no `pull_request` round-trip needed for non-fork
# non-release PRs.
required-check:
name: ✅ Required PR Check
needs: ci
# Mirror the `ci` job's gating exactly. The four cases that run:
# 1. push events (job needs CI run)
# 2. fork PRs (push doesn't cover forks)
# 3. release PRs from `next` → master/main (push covers SHA but commit-gate skips chore-bump)
# 4. release PRs from `hotfixes` → master/main (same reason)
# In-repo feature PRs targeting `next` / `hotfixes` skip on
# pull_request — push on the head branch already posted the status
# on the SHA, and mirroring here would overwrite it.
if: |
always() && (
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.fork == true ||
github.event.pull_request.head.ref == 'next' ||
github.event.pull_request.head.ref == 'hotfixes'
)
# Match the reusable's runner routing (workflow-ci.yml): private CLDMV
# repos run on self-hosted cldmv-runners (GitHub-hosted Actions budget is
# private-metered and exhausted), public repos use free GitHub-hosted, and
# RUNS_ON_DEFAULT overrides. Hardcoding ubuntu-latest here made this
# required check fail to provision a runner on private repos once the
# reusable moved its own jobs to cldmv-runners in v4.19.1 (see CLDMV/.github#208).
runs-on: ${{ vars.RUNS_ON_DEFAULT != '' && vars.RUNS_ON_DEFAULT || ((github.repository_owner == 'CLDMV' && github.event.repository.private) && 'cldmv-runners' || 'ubuntu-latest') }}
# Pure shell mirror — no GitHub API access. Strip the workflow's
# write defaults to zero for this job.
permissions: {}
steps:
- name: Mirror reusable result
env:
IS_MASTER_SYNC: ${{ needs.ci.outputs.is_master_sync }}
DOCS_ONLY: ${{ needs.ci.outputs.docs_only }}
CI_RESULT: ${{ needs.ci.result }}
run: |
echo "ci.result=$CI_RESULT docs_only=$DOCS_ONLY is_master_sync=$IS_MASTER_SYNC"
# next/hotfixes was force-synced to master — head SHA matches the
# default branch, nothing new to test, green-light without running CI.
if [ "$IS_MASTER_SYNC" = "true" ]; then
echo "Branch tip matches master — Required PR Check passes without running CI."
exit 0
fi
# Docs-only PR — the reusable skipped the heavy chain and exported
# docs_only=true. Green-light Required PR Check so the ruleset
# doesn't block a docs change.
if [ "$DOCS_ONLY" = "true" ]; then
echo "Docs-only change — Required PR Check passes without running CI."
exit 0
fi
if [ "$CI_RESULT" = "success" ]; then
echo "Reusable CI passed."
exit 0
elif [ "$CI_RESULT" = "failure" ] || [ "$CI_RESULT" = "cancelled" ]; then
echo "Reusable CI did not pass."
exit 1
else
# covers 'skipped' or undefined; force red to avoid silent green
echo "Reusable CI produced no pass/fail; treating as failure."
exit 1
fi