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
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ updates:
versioning-strategy: increase
directories:
- "/"
- "/actions/**/*"
- "/tests/npm"
- "/tests/pnpm"
- "/tests/pnpm-package-manager"
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/__shared-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ jobs:
security-events: write
statuses: write

test-release:
name: Test release preparation and recovery
uses: $/.github/workflows/__test-release.yml
permissions:
actions: read
contents: read
id-token: write
packages: read
pull-requests: write
security-events: write

test-action-dependencies-cache:
name: Test action "dependencies-cache"
needs: linter
Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/__test-action-package-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,26 @@ jobs:
dry-run: "yes"
provenance: "false"

- id: wrong-identity
name: Act - Reject an unintended release package
continue-on-error: true
uses: $/actions/package/publish
with:
package-tarball-artifact-id: ${{ needs.package.outputs.package-tarball-artifact-id }}
name: another-package
version: 0.1.0
dry-run: "true"
provenance: "false"

- name: Assert - Invalid inputs fail before publishing
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
INVALID_ARTIFACT_OUTCOME: ${{ steps.invalid-artifact.outcome }}
WRONG_IDENTITY_OUTCOME: ${{ steps.wrong-identity.outcome }}
INVALID_DRY_RUN_OUTCOME: ${{ steps.invalid-dry-run.outcome }}
with:
script: |
for (const outcome of [process.env.INVALID_ARTIFACT_OUTCOME, process.env.INVALID_DRY_RUN_OUTCOME]) {
for (const outcome of [process.env.INVALID_ARTIFACT_OUTCOME, process.env.INVALID_DRY_RUN_OUTCOME, process.env.WRONG_IDENTITY_OUTCOME]) {
if (outcome !== 'failure') {
core.setFailed('Expected invalid publish inputs to fail');
}
Expand Down
179 changes: 179 additions & 0 deletions .github/workflows/__test-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
name: Internal - Tests for package and release actions

on:
workflow_call:

permissions: {}

jobs:
continuous-integration:
name: Unit tests for ${{ matrix.action }}
strategy:
fail-fast: false
matrix:
action:
[package/restore, release/prepare, release/verify, release/finalize]
uses: $/.github/workflows/continuous-integration.yml
permissions:
contents: read
id-token: write
packages: read
pull-requests: write
security-events: write
with:
working-directory: actions/${{ matrix.action }}
build: null
lint: null
test: '{ "coverage": null }'

integration-tests:
name: Test release action inputs, outputs and failures
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
steps:
- name: Arrange - Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: $/actions/setup-node
with:
working-directory: actions/release/prepare
install-dependencies: "false"

- name: Act - Reject preparation of a private package
id: private-project
continue-on-error: true
uses: $/actions/release/prepare
with:
working-directory: actions/release/prepare
- name: Assert - Preparation failed
env:
OUTCOME: ${{ steps.private-project.outcome }}
run: test "$OUTCOME" = failure

- name: Arrange - Create an installable release tarball
id: fixture
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const fs = require('node:fs');
const path = require('node:path');
const { createHash } = require('node:crypto');
const directory = fs.mkdtempSync(path.join(process.env.RUNNER_TEMP, 'release-fixture-'));
const name = `hoverkraft-release-test-${context.runId}-${process.env.GITHUB_RUN_ATTEMPT}`;
fs.writeFileSync(path.join(directory, 'package.json'), JSON.stringify({
name, version: '1.2.3', bin: { 'release-fixture': 'cli.js' },
scripts: { prepare: 'exit 91', prepack: 'exit 91', postpack: 'exit 91' },
}));
fs.writeFileSync(path.join(directory, 'cli.js'), '#!/usr/bin/env node\nconsole.log("1.2.3");\n', { mode: 0o755 });
const { stdout } = await exec.getExecOutput('npm', ['pack', '--json', '--ignore-scripts'], { cwd: directory });
const tarball = path.join(directory, JSON.parse(stdout)[0].filename);
core.setOutput('name', name);
core.setOutput('tarball', tarball);
core.setOutput('digest', createHash('sha256').update(fs.readFileSync(tarball)).digest('hex'));
core.setOutput('packages', [{ name, slug: name, version: '1.2.3', tag: 'v1.2.3', directory: '.' }]);

- name: Act - Reject an invalid package artifact ID
id: invalid-artifact
continue-on-error: true
uses: $/actions/release/verify
with:
packages: ${{ steps.fixture.outputs.packages }}
package-tarball-artifact-id: "0"
- name: Act - Reject a mismatched tarball version
id: mismatched-version
continue-on-error: true
uses: $/actions/package/restore
with:
name: ${{ steps.fixture.outputs.name }}
version: 9.9.9
tarball-path: ${{ steps.fixture.outputs.tarball }}
- name: Act - Check an unpublished npm version
id: registry
uses: $/actions/package/restore
with:
name: ${{ steps.fixture.outputs.name }}
version: 1.2.3
tarball-path: ${{ steps.fixture.outputs.tarball }}
- name: Assert - Rejections and registry output
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
ARTIFACT_OUTCOME: ${{ steps.invalid-artifact.outcome }}
VERSION_OUTCOME: ${{ steps.mismatched-version.outcome }}
PUBLISHED: ${{ steps.registry.outputs.published }}
TARBALL: ${{ steps.fixture.outputs.tarball }}
EXPECTED_DIGEST: ${{ steps.fixture.outputs.digest }}
with:
script: |
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { createHash } = require('node:crypto');
assert.equal(process.env.ARTIFACT_OUTCOME, 'failure');
assert.equal(process.env.VERSION_OUTCOME, 'failure');
assert.equal(process.env.PUBLISHED, 'false');
assert.equal(createHash('sha256').update(fs.readFileSync(process.env.TARBALL)).digest('hex'), process.env.EXPECTED_DIGEST);

- name: Arrange - Upload the unpublished package
id: unpublished-package
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: unpublished-package-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.fixture.outputs.tarball }}
archive: false
if-no-files-found: error
- name: Act - Publish dry run with package recovery enabled
id: publish
uses: $/actions/package/publish
with:
package-tarball-artifact-id: ${{ steps.unpublished-package.outputs.artifact-id }}
recover-existing: "true"
dry-run: "true"
provenance: "false"
- name: Assert - Publish preserved the unpublished tarball
env:
ORIGINAL_TARBALL: ${{ steps.fixture.outputs.tarball }}
PUBLISH_TARBALL: ${{ steps.publish.outputs.package-tarball-path }}
run: cmp "$ORIGINAL_TARBALL" "$PUBLISH_TARBALL"

- name: Act - Install and run the release package
id: smoke
uses: $/actions/package/test
env:
EXPECTED_PACKAGE_NAME: ${{ steps.fixture.outputs.name }}
with:
working-directory: actions/package/test
tarball-path: ${{ steps.fixture.outputs.tarball }}
commands: |
test "$PWD" = "$GITHUB_WORKSPACE/actions/package/test"
test "$PACKAGE_NAME" = "$EXPECTED_PACKAGE_NAME"
test "$PACKAGE_VERSION" = 1.2.3
consumer=$(mktemp -d)
npm install --prefix "$consumer" --ignore-scripts --offline --no-audit --no-fund "$PACKAGE_TARBALL_PATH"
test "$("$consumer/node_modules/.bin/release-fixture")" = "$PACKAGE_VERSION"
touch "$RUNNER_TEMP/release-smoke-ran"
- name: Act - Reject a failing package command
id: failing-command
continue-on-error: true
uses: $/actions/package/test
with:
tarball-path: ${{ steps.fixture.outputs.tarball }}
commands: exit 7
- name: Act - Reject modified tarball bytes
id: modified-tarball
continue-on-error: true
uses: $/actions/package/test
with:
tarball-path: ${{ steps.fixture.outputs.tarball }}
commands: printf changed >> "$PACKAGE_TARBALL_PATH"
- name: Assert - Smoke checks ran and rejected both failures
env:
SMOKE_OUTCOME: ${{ steps.smoke.outcome }}
COMMAND_OUTCOME: ${{ steps.failing-command.outcome }}
TARBALL_OUTCOME: ${{ steps.modified-tarball.outcome }}
run: |
test "$SMOKE_OUTCOME" = success
test -f "$RUNNER_TEMP/release-smoke-ran"
test "$COMMAND_OUTCOME" = failure
test "$TARBALL_OUTCOME" = failure
50 changes: 50 additions & 0 deletions .github/workflows/__test-workflow-continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ jobs:
security-events: write
with:
working-directory: tests/npm
package: true
package-tests: |
tar -xOf "$PACKAGE_TARBALL_PATH" package/dist/test.txt > "$RUNNER_TEMP/package-build.txt"
test "$(cat "$RUNNER_TEMP/package-build.txt")" = 'build test'
test "$PACKAGE_NAME" = test-for-ci-with-npm
consumer=$(mktemp -d)
npm install --prefix "$consumer" --ignore-scripts --offline --no-audit --no-fund "$PACKAGE_TARBALL_PATH"
test -f "$consumer/node_modules/$PACKAGE_NAME/dist/test.txt"
code-ql: |
{
"languages": "javascript-typescript",
Expand Down Expand Up @@ -46,6 +54,22 @@ jobs:
- name: Check the build artifacts
run: test -f tests/npm/dist/test.txt

- name: Act - Consume the validated CI package without repacking
id: validated-package
uses: $/actions/package/publish
with:
package-tarball-artifact-id: ${{ needs.act-without-container.outputs.package-tarball-artifact-id }}
name: test-for-ci-with-npm
version: 0.1.0
dry-run: "true"
provenance: "false"
- name: Assert - The CI package contains the compiled output
env:
TARBALL: ${{ steps.validated-package.outputs.package-tarball-path }}
run: |
tar -xOf "$TARBALL" package/dist/test.txt > "$RUNNER_TEMP/ci-packaged-build.txt"
cmp tests/npm/dist/test.txt "$RUNNER_TEMP/ci-packaged-build.txt"

- name: Arrange packaging hooks that must not run
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
Expand Down Expand Up @@ -82,6 +106,22 @@ jobs:
tar -xOf "$TARBALL" package/dist/test.txt > "$RUNNER_TEMP/packaged-build.txt"
cmp "$RUNNER_TEMP/prepared-fixture/dist/test.txt" "$RUNNER_TEMP/packaged-build.txt"
test ! -d tests/npm/node_modules
- name: Run package smoke checks with the project runtime
uses: $/actions/package/test
with:
working-directory: tests/npm
tarball-path: ${{ steps.package.outputs.package-tarball-path }}
commands: |
node <<'NODE'
const assert = require('node:assert/strict');
const fs = require('node:fs');
const expected = fs.readFileSync('.node-version', 'utf8').trim().split('.');
assert.deepEqual(process.versions.node.split('.').slice(0, expected.length), expected);
NODE
test "$PACKAGE_NAME" = prepared-fixture
test "$PACKAGE_VERSION" = 1.2.3
tar -xOf "$PACKAGE_TARBALL_PATH" package/dist/test.txt > "$RUNNER_TEMP/smoke-output.txt"
cmp "$RUNNER_TEMP/prepared-fixture/dist/test.txt" "$RUNNER_TEMP/smoke-output.txt"
- name: Validate publishing the prepared tarball
uses: $/actions/package/publish
with:
Expand Down Expand Up @@ -111,6 +151,16 @@ jobs:
{"coverage": "github"}
# jscpd:ignore-end

assert-package-disabled:
name: Assert - Package creation is opt-in
needs: act-without-container-and-github-reports
runs-on: ubuntu-latest
permissions: {}
steps:
- env:
PACKAGE_ARTIFACT_ID: ${{ needs.act-without-container-and-github-reports.outputs.package-tarball-artifact-id }}
run: test -z "$PACKAGE_ARTIFACT_ID"

arrange-with-container:
name: Arrange - Build container image for CI workflow
permissions:
Expand Down
Loading
Loading