Sitelet https://github.com/limitless-angular/limitless-angular/pull/38/files
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
129 changes: 24 additions & 105 deletions .github/workflows/release-and-publish.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
name: Release and Publish

on:
# Run manually using the GitHub UI
workflow_dispatch:
inputs:
version:
description: 'Version to publish (optional - will use conventional commits if not specified)'
required: false
default: ''
dryRun:
description: 'Perform a dry run without publishing'
required: false
default: false
type: boolean
verbose:
description: 'Enable verbose logging'
required: false
Expand All @@ -22,23 +16,24 @@ on:
env:
TURBO_TELEMETRY_DISABLED: '1'

concurrency:
group: release-publish-${{ github.ref }}
cancel-in-progress: false

jobs:
release-and-publish:
# prevents this action from running on forks
if: github.repository == 'limitless-angular/limitless-angular'
name: Release and Publish to npm
runs-on: ubuntu-22.04
permissions:
contents: write # needed for creating GitHub releases
id-token: write # needed for provenance data generation
outputs:
release_successful: ${{ steps.release.outcome == 'success' }}
build_successful: ${{ steps.build.outcome == 'success' }}
contents: write
id-token: write
environment: npm-release
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0 # include tags
fetch-depth: 0

- uses: pnpm/action-setup@v6
name: Install pnpm
Expand All @@ -48,7 +43,6 @@ jobs:
- name: Install Node.js per package.json
uses: actions/setup-node@v6
with:
# Use the .nvmrc file as the source of truth
node-version-file: .nvmrc
cache: 'pnpm'
registry-url: https://registry.npmjs.org/
Expand All @@ -68,109 +62,34 @@ jobs:
- name: Check for lint errors
run: pnpm turbo run lint --filter=@limitless-angular/sanity

- name: Check release compatibility pipeline
run: pnpm turbo run compat:release-parity --filter=@limitless-angular/angular-compat

- name: Set up Git user
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"

- name: Execute release process
id: release
run: |
# Show dry run status clearly
if [ "${{ inputs.dryRun }}" == "true" ]; then
echo "::notice::Running in DRY RUN mode - no actual changes will be made"
fi

# Prepare version argument if specified
VERSION_ARG="${{ inputs.version != '' && format('--version={0}', inputs.version) || '' }}"

# Prepare dry run argument
DRY_RUN_ARG="${{ inputs.dryRun && '--dryRun=true' || '--dryRun=false' }}"

# Prepare verbose argument
VERBOSE_ARG="${{ inputs.verbose && '--verbose=true' || '' }}"

# Run the release script and capture its output
RELEASE_OUTPUT=$(pnpm tsx tools/scripts/release.ts $VERSION_ARG $DRY_RUN_ARG $VERBOSE_ARG)

# Extract the released version from the script output
RELEASED_VERSION=$(echo "$RELEASE_OUTPUT" | grep "RELEASED_VERSION=" | cut -d'=' -f2)

# If we got a version, save it as an environment variable
if [ -n "$RELEASED_VERSION" ]; then
echo "RELEASED_VERSION=$RELEASED_VERSION" >> $GITHUB_ENV
echo "Release version detected: $RELEASED_VERSION"
else
echo "::error::Failed to extract released version from script output"
exit 1
fi
- name: Validate and publish release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# Only proceed with building if release was successful
- name: Pack sanity project
id: build
if: steps.release.outcome == 'success'
run: pnpm turbo run compat:pack --filter=@limitless-angular/angular-compat

- name: Validate packed artifact shape
if: steps.build.outcome == 'success'
run: pnpm turbo run compat:artifact --filter=@limitless-angular/angular-compat

- name: Install Playwright browser
if: steps.build.outcome == 'success'
run: pnpm --dir tools/angular-compat exec playwright install --with-deps chromium

- name: Test packed artifact compatibility
if: steps.build.outcome == 'success'
run: pnpm turbo run compat:test --filter=@limitless-angular/angular-compat

# Validate build artifacts
- name: Validate build artifacts
if: steps.build.outcome == 'success'
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
NPM_CONFIG_PROVENANCE: true
RELEASE_VERBOSE: ${{ inputs.verbose }}
RELEASE_VERSION: ${{ inputs.version }}
run: |
TARBALL_COUNT=$(find .compat/artifacts -maxdepth 1 -name '*.tgz' | wc -l | tr -d ' ')
if [ "$TARBALL_COUNT" != "1" ]; then
echo "::error::Expected one packed artifact in .compat/artifacts, found $TARBALL_COUNT"
exit 1
ARGS=()
if [ -n "$RELEASE_VERSION" ]; then
ARGS+=(--version "$RELEASE_VERSION")
fi

TARBALL=$(find .compat/artifacts -maxdepth 1 -name '*.tgz' | head -n 1)
echo "TARBALL=$TARBALL" >> "$GITHUB_ENV"
ARTIFACT_VERSION=$(tar -xOf "$TARBALL" package/package.json | jq -r '.version')
EXPECTED_VERSION="${{ env.RELEASED_VERSION }}"
if [ "${{ inputs.dryRun }}" = "true" ]; then
EXPECTED_VERSION=$(jq -r '.version' packages/sanity/package.json)
if [ "$RELEASE_VERBOSE" = "true" ]; then
ARGS+=(--verbose)
fi

echo "Packed package version: $ARTIFACT_VERSION"
echo "Expected package version: $EXPECTED_VERSION"
pnpm turbo run release:publish --filter=@limitless-angular/release-tools -- "${ARGS[@]}"

if [ "$ARTIFACT_VERSION" != "$EXPECTED_VERSION" ]; then
echo "::error::Version mismatch! Package version ($ARTIFACT_VERSION) doesn't match expected version ($EXPECTED_VERSION)"
exit 1
fi

echo "Packed artifact validated successfully"

# Publish packages to npm if all previous steps succeeded and not a dry run
- name: Publish packages to npm
id: publish
if: ${{ !inputs.dryRun && steps.release.outcome == 'success' && steps.build.outcome == 'success' }}
run: npm publish "$TARBALL" --access public --registry https://registry.npmjs.org
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
NPM_CONFIG_PROVENANCE: true

# Create a summary of the release
- name: Generate release summary
if: always()
env:
RELEASE_VERSION: ${{ inputs.version }}
run: |
echo "## Release Process Summary" > $GITHUB_STEP_SUMMARY
echo "- Release step: ${{ steps.release.outcome }}" >> $GITHUB_STEP_SUMMARY
echo "- Build step: ${{ steps.build.outcome }}" >> $GITHUB_STEP_SUMMARY
echo "- Publish step: ${{ steps.publish.outcome || 'skipped' }}" >> $GITHUB_STEP_SUMMARY
echo "- Released version: ${{ env.RELEASED_VERSION || 'unknown' }}" >> $GITHUB_STEP_SUMMARY
echo "## Release Process Summary" > "$GITHUB_STEP_SUMMARY"
echo "- Mode: publish" >> "$GITHUB_STEP_SUMMARY"
echo "- Version input: ${RELEASE_VERSION:-conventional commits}" >> "$GITHUB_STEP_SUMMARY"
85 changes: 85 additions & 0 deletions .github/workflows/release-dry-run.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Release Dry Run

on:
workflow_dispatch:
inputs:
version:
description: 'Version to validate (optional - will use conventional commits if not specified)'
required: false
default: ''
verbose:
description: 'Enable verbose logging'
required: false
default: false
type: boolean

env:
TURBO_TELEMETRY_DISABLED: '1'

concurrency:
group: release-dry-run-${{ github.ref }}
cancel-in-progress: false

jobs:
release-dry-run:
if: github.repository == 'limitless-angular/limitless-angular'
name: Validate Release Dry Run
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- uses: pnpm/action-setup@v6
name: Install pnpm
with:
run_install: false

- name: Install Node.js per package.json
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: 'pnpm'
registry-url: https://registry.npmjs.org/

- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline

- name: Verify environment
run: |
echo "Node version: $(node -v)"
echo "pnpm version: $(pnpm -v)"
echo "Workspace directory: $(pwd)"

- name: Run tests
run: pnpm turbo run test

- name: Check for lint errors
run: pnpm turbo run lint --filter=@limitless-angular/sanity

- name: Validate release dry run
env:
RELEASE_VERBOSE: ${{ inputs.verbose }}
RELEASE_VERSION: ${{ inputs.version }}
run: |
ARGS=()
if [ -n "$RELEASE_VERSION" ]; then
ARGS+=(--version "$RELEASE_VERSION")
fi
if [ "$RELEASE_VERBOSE" = "true" ]; then
ARGS+=(--verbose)
fi

pnpm turbo run release:dry-run --filter=@limitless-angular/release-tools -- "${ARGS[@]}"

- name: Generate release dry-run summary
if: always()
env:
RELEASE_VERSION: ${{ inputs.version }}
run: |
echo "## Release Dry Run Summary" > "$GITHUB_STEP_SUMMARY"
echo "- Mode: dry-run" >> "$GITHUB_STEP_SUMMARY"
echo "- Version input: ${RELEASE_VERSION:-conventional commits}" >> "$GITHUB_STEP_SUMMARY"
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
"compat:release-parity": "pnpm turbo run compat:release-parity --filter=@limitless-angular/angular-compat",
"compat:test": "pnpm turbo run compat:test --filter=@limitless-angular/angular-compat --",
"format:write": "prettier --write .",
"release": "node tools/scripts/release.ts"
"release": "pnpm turbo run release --filter=@limitless-angular/release-tools --",
"release:dry-run": "pnpm turbo run release:dry-run --filter=@limitless-angular/release-tools --",
"release:plan": "pnpm --filter=@limitless-angular/release-tools run --silent release:plan",
"release:publish": "pnpm turbo run release:publish --filter=@limitless-angular/release-tools --"
},
"packageManager": "pnpm@11.5.2",
"volta": {
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tools/angular-compat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ compatibility jobs are relevant. The decision asks Turbo whether either
`@limitless-angular/sanity` or `@limitless-angular/angular-compat` has affected
package tasks. A small explicit contract list covers files that are outside
package ownership but still define the compatibility workflow, such as
CI/release workflows, `.nvmrc`, `turbo.json`, and the release script.
CI/release workflows, `.nvmrc`, `turbo.json`, and the release tooling package.
`workflow_dispatch` always runs the compatibility jobs.

When eligible, CI runs stable consumers as required jobs and `angular-next` as
Expand Down
39 changes: 31 additions & 8 deletions tools/angular-compat/assert-release-parity.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,50 @@ import { readFileSync } from 'node:fs';
import { join } from 'node:path';

import { workspaceRoot } from './lib.mjs';
import { compatibilityValidationSteps } from '../release/src/pipeline.mjs';

const requiredReleaseCommands = [
const requiredCompatibilitySteps = [
'pnpm turbo run compat:pack --filter=@limitless-angular/angular-compat',
'pnpm turbo run compat:artifact --filter=@limitless-angular/angular-compat',
'pnpm --dir tools/angular-compat exec playwright install --with-deps chromium',
'pnpm turbo run compat:test --filter=@limitless-angular/angular-compat',
'npm publish "$TARBALL"',
];

export function assertReleaseParity() {
const workflow = readFileSync(
const publishWorkflow = readFileSync(
join(workspaceRoot, '.github/workflows/release-and-publish.yml'),
'utf8',
);
const dryRunWorkflow = readFileSync(
join(workspaceRoot, '.github/workflows/release-dry-run.yml'),
'utf8',
);
const actualCompatibilitySteps = compatibilityValidationSteps.map(
({ args, command }) => [command, ...args].join(' '),
);

assertIncludes(publishWorkflow, [
'pnpm turbo run release:publish --filter=@limitless-angular/release-tools',
]);
assertIncludes(dryRunWorkflow, [
'pnpm turbo run release:dry-run --filter=@limitless-angular/release-tools',
]);
assertIncludes(
actualCompatibilitySteps.join('\n'),
requiredCompatibilitySteps,
);

for (const command of requiredReleaseCommands) {
if (!workflow.includes(command)) {
console.log(
'Release workflows delegate to release tools and validate the compatibility artifact.',
);
}

function assertIncludes(text, snippets) {
for (const snippet of snippets) {
if (!text.includes(snippet)) {
throw new Error(
`Release workflow must use the compatibility pipeline command: ${command}`,
`Release workflow must use the compatibility pipeline command: ${snippet}`,
);
}
}

console.log('Release workflow publishes the compatibility-tested tarball.');
}
6 changes: 5 additions & 1 deletion tools/angular-compat/eligibility.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ const compatibilityContractPathRules = [
path: '.github/workflows/release-and-publish.yml',
reason: 'release workflow changed',
},
{
path: '.github/workflows/release-dry-run.yml',
reason: 'release dry-run workflow changed',
},
{ path: '.nvmrc', reason: 'Node runtime changed' },
{ path: 'turbo.json', reason: 'Turbo pipeline changed' },
{ path: 'tools/scripts/release.ts', reason: 'release script changed' },
{ path: 'tools/release/', reason: 'release tooling changed' },
];

export function evaluateSanityCompatEligibility(options = {}) {
Expand Down
Loading
Loading