Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1077,7 +1077,11 @@ jobs:
fi
- name: Promote verified Website artifact unchanged
if: steps.freshness.outputs.stale != 'true' && steps.affected.outputs.website == 'true' && steps.website_promotion_freshness.outputs.fresh == 'true'
run: npx vercel promote "${{ steps.deploy_website.outputs.deployment_url }}" --yes --token=${{ secrets.VERCEL_TOKEN }}
# `promote` takes a bare deployment URL, so unlike build/deploy/pull it
# cannot read the team from .vercel/project.json and falls back to the
# token's default team — a personal one here, which fails with
# "Deployment doesn't belong to current team". Scope it explicitly.
run: npx vercel promote "${{ steps.deploy_website.outputs.deployment_url }}" --scope=${{ secrets.VERCEL_ORG_ID }} --yes --token=${{ secrets.VERCEL_TOKEN }}
- name: Verify deployed website
if: steps.freshness.outputs.stale != 'true' && ((steps.affected.outputs.website == 'true' && steps.website_promotion_freshness.outputs.fresh == 'true') || steps.affected.outputs.cockpit == 'true')
run: npx nx e2e website --skip-nx-cache
Expand Down Expand Up @@ -1125,7 +1129,11 @@ jobs:
fi
- name: Promote verified cockpit artifact unchanged
if: steps.freshness.outputs.stale != 'true' && steps.affected.outputs.cockpit == 'true' && steps.cockpit_promotion_freshness.outputs.fresh == 'true'
run: npx vercel promote "${{ steps.deploy_cockpit.outputs.deployment_url }}" --yes --token=${{ secrets.VERCEL_TOKEN }}
# `promote` takes a bare deployment URL, so unlike build/deploy/pull it
# cannot read the team from .vercel/project.json and falls back to the
# token's default team — a personal one here, which fails with
# "Deployment doesn't belong to current team". Scope it explicitly.
run: npx vercel promote "${{ steps.deploy_cockpit.outputs.deployment_url }}" --scope=${{ secrets.VERCEL_ORG_ID }} --yes --token=${{ secrets.VERCEL_TOKEN }}
- name: Verify production cockpit redirects
if: steps.freshness.outputs.stale != 'true' && steps.affected.outputs.cockpit == 'true' && steps.cockpit_promotion_freshness.outputs.fresh == 'true'
run: npx tsx apps/cockpit/scripts/deploy-smoke.ts --url https://cockpit.threadplane.ai --mode production --retries 20 --retry-delay-ms 5000
Expand Down
19 changes: 17 additions & 2 deletions scripts/ci-workflow.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ function readNamedStep(job, name) {
}

describe('CI workflow', () => {
it('scopes every vercel promote to the team that owns the deployment', async () => {
const workflow = await readFile('.github/workflows/ci.yml', 'utf8');
const promotes = workflow
.split('\n')
.filter((line) => line.includes('vercel promote'));

assert.ok(promotes.length >= 2, 'expected Website and cockpit promotions');
for (const line of promotes) {
// `promote` takes a bare URL and cannot read .vercel/project.json, so
// without --scope it uses the token's default team and fails.
assert.match(line, /--scope=/, `unscoped vercel promote: ${line.trim()}`);
}
});


it('runs in a merge queue and reports the required context there', async () => {
const workflow = await readFile('.github/workflows/ci.yml', 'utf8');

Expand Down Expand Up @@ -368,7 +383,7 @@ describe('CI workflow', () => {
assert.match(freshnessStep, /fresh=true.*GITHUB_OUTPUT/);
assert.match(
promoteStep,
/vercel promote "\$\{\{ steps\.deploy_website\.outputs\.deployment_url \}\}" --yes/
/vercel promote "\$\{\{ steps\.deploy_website\.outputs\.deployment_url \}\}" --scope=\$\{\{ secrets\.VERCEL_ORG_ID \}\} --yes/
);
assert.match(
promoteStep,
Expand Down Expand Up @@ -519,7 +534,7 @@ describe('CI workflow', () => {
assert.match(promotionFreshnessStep, /fresh=true.*GITHUB_OUTPUT/);
assert.match(
promoteStep,
/vercel promote "\$\{\{ steps\.deploy_cockpit\.outputs\.deployment_url \}\}" --yes/
/vercel promote "\$\{\{ steps\.deploy_cockpit\.outputs\.deployment_url \}\}" --scope=\$\{\{ secrets\.VERCEL_ORG_ID \}\} --yes/
);
assert.match(
promoteStep,
Expand Down
Loading