feat(ci): add workspace integrity, prerelease guard, and export smoke gates#691
Conversation
… gates Adds 3 CI health gates to squad-ci.yml. Closes #115 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds three additional CI “health gates” to the existing Squad GitHub Actions workflow to catch workspace/packaging issues earlier (especially around npm workspaces, versioning, and SDK exports).
Changes:
- Adds new CI jobs:
workspace-integrity,prerelease-version-guard, andexport-smoke-test. - Updates existing gates to support additional skip-label behavior and adds an in-file skip-label reference section.
- Adjusts
samples-buildinstallation behavior.
| # Prerelease Version Guard | ||
| # Purpose: Prevent prerelease version strings (-build, -alpha, -beta, | ||
| # -rc) from being committed to dev or main. | ||
| # Catches: Forgotten prerelease suffixes that break semver range | ||
| # resolution in workspace dependencies. | ||
| # Why: Added after PR #640 prerelease version incident where a | ||
| # -build.N suffix caused npm to skip the local workspace | ||
| # copy during dependency resolution. |
There was a problem hiding this comment.
The comments/error text say this guard prevents prerelease versions from merging to dev/main, but the workflow triggers on PRs targeting preview and insider too. Either update the documentation/message to match the actual behavior, or narrow the job condition (e.g., if: github.event_name == 'pull_request' && (github.base_ref == 'dev' || github.base_ref == 'main')).
| - name: Check skip label | ||
| if: steps.flag.outputs.skip == 'false' | ||
| id: label | ||
| run: | | ||
| LABELS=$(gh pr view ${{ github.event.pull_request.number }} --json labels --jq '.labels[].name' 2>/dev/null || echo "") | ||
| if echo "$LABELS" | grep -q "skip-changelog"; then | ||
| echo "skip=true" >> "$GITHUB_OUTPUT" | ||
| echo "Skipping CHANGELOG gate (skip-changelog label present)" | ||
| else | ||
| echo "skip=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} |
There was a problem hiding this comment.
The new skip-label check uses gh pr view ... to fetch PR labels, but this workflow sets permissions: contents: read only. With those permissions, GITHUB_TOKEN typically cannot read pull request metadata, so gh pr view will fail (403) and the skip label will never be honored.
Fix by either switching back to using the event payload (contains(github.event.pull_request.labels.*.name, 'skip-changelog') / toJSON(...) + grep), or updating workflow/job permissions to include pull-requests: read (and keep the GH_TOKEN env).
| if: steps.flag.outputs.skip == 'false' | ||
| id: label | ||
| run: | | ||
| LABELS=$(gh pr view ${{ github.event.pull_request.number }} --json labels --jq '.labels[].name' 2>/dev/null || echo "") | ||
| if echo "$LABELS" | grep -q "skip-exports-check"; then | ||
| echo "skip=true" >> "$GITHUB_OUTPUT" | ||
| echo "Skipping exports map check (skip-exports-check label present)" | ||
| else | ||
| echo "skip=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
|
|
There was a problem hiding this comment.
Same issue here: gh pr view needs pull request read permissions, but the workflow-level token permissions are limited to contents: read. If gh pr view fails, skip-exports-check will never be detected and the job can't be bypassed via label.
Prefer reading labels from github.event.pull_request.labels (no API call / no extra permissions) or add pull-requests: read to the workflow/job permissions.
| if: steps.flag.outputs.skip == 'false' | |
| id: label | |
| run: | | |
| LABELS=$(gh pr view ${{ github.event.pull_request.number }} --json labels --jq '.labels[].name' 2>/dev/null || echo "") | |
| if echo "$LABELS" | grep -q "skip-exports-check"; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "Skipping exports map check (skip-exports-check label present)" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| if: github.event_name == 'pull_request' && steps.flag.outputs.skip == 'false' | |
| id: label | |
| run: | | |
| if [[ "${{ contains(github.event.pull_request.labels.*.name, 'skip-exports-check') }}" == 'true' ]]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "Skipping exports map check (skip-exports-check label present)" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi |
| echo "[$sample] Installing dependencies..." | ||
| echo "=========================================" | ||
| if ! (cd "$sample_dir" && if [ -f package-lock.json ]; then npm ci; else npm install; fi 2>&1); then | ||
| if ! (cd "$sample_dir" && npm install --ignore-scripts 2>&1); then |
There was a problem hiding this comment.
Switching sample installs to npm install --ignore-scripts (and removing the prior patching behavior) means some samples will now install the published @bradygaster/squad-sdk instead of the local workspace copy. In this repo, at least samples/autonomous-pipeline and samples/cost-aware-router still depend on @bradygaster/squad-sdk: ^0.8.0, so SDK PR changes won’t actually be exercised by this gate.
To keep this job validating the current PR’s SDK, either restore the patch-to-local step or standardize all samples to use file:../../packages/squad-sdk (and then use npm ci --ignore-scripts when a lockfile is present for determinism).
| if ! (cd "$sample_dir" && npm install --ignore-scripts 2>&1); then | |
| # Ensure samples use the local workspace @bradygaster/squad-sdk instead of the published version | |
| node -e "const fs=require('fs'); const path='./$sample_dir/package.json'; const p=require(path); let changed=false; for (const section of ['dependencies','devDependencies']) { if (p[section] && p[section]['@bradygaster/squad-sdk']) { p[section]['@bradygaster/squad-sdk']='file:../../packages/squad-sdk'; changed=true; } } if (changed) { fs.writeFileSync(path, JSON.stringify(p, null, 2)); console.log('Patched', path, 'to use local @bradygaster/squad-sdk'); }" 2>/dev/null || true | |
| if [ -f "$sample_dir/package-lock.json" ]; then | |
| INSTALL_CMD="npm ci --ignore-scripts" | |
| else | |
| INSTALL_CMD="npm install --ignore-scripts" | |
| fi | |
| if ! (cd "$sample_dir" && $INSTALL_CMD 2>&1); then |
…#652) (bradygaster#691) Adds multi-squad.ts to squad-sdk with 7 core functions: getSquadRoot, resolveSquadPath (5-step resolution chain), listSquads, createSquad, deleteSquad, switchSquad, migrateIfNeeded. Plus 3 exported types (SquadConfig, SquadEntry, MultiSquadOptions). Non-destructive migration registers legacy ~/.squad as default. Ref bradygaster#652 (Phase 1 of 3)
Adds 3 CI health gates to squad-ci.yml: workspace integrity check, prerelease guard, and export smoke test.
Supersedes #688 with a clean branch from upstream/dev.
Fork PR: diberry#115.
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com