Skip to content
Open
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
15 changes: 13 additions & 2 deletions .github/workflows/bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ on:
bumped_version:
description: "The version after bumping"
value: ${{ jobs.bump.outputs.bumped_version }}
commit_sha:
description: "The commit this job pushed, for downstream jobs to build and tag"
value: ${{ jobs.bump.outputs.commit_sha }}

jobs:
bump:
runs-on: ubuntu-22.04
environment: ${{ inputs.environment }}
outputs:
bumped_version: ${{ steps.get-version.outputs.bumped_version }}
commit_sha: ${{ steps.commit.outputs.commit_sha }}
steps:
- name: Create deployooor app token
uses: actions/create-github-app-token@v1
Expand Down Expand Up @@ -93,8 +97,11 @@ jobs:
run: echo "bumped_version=$(jq -r .version package.json)" >> $GITHUB_OUTPUT

- name: Commit and push
id: commit
env:
LOCKFILE: ${{ steps.pm.outputs.lockfile }}
VERSION: ${{ steps.get-version.outputs.bumped_version }}
REF: ${{ inputs.ref }}
run: |
git config --local user.email "deployooor@nexusmutual.io"
git config --local user.name "Version bumpooor"
Expand All @@ -107,5 +114,9 @@ jobs:
fi

git add package.json "$LOCKFILE" $(git ls-files "**/package.json" "**/$LOCKFILE")
git commit -m "chore(release): v${{ steps.get-version.outputs.bumped_version }} [skip ci]"
git push origin ${{ inputs.ref }}
git commit -m "chore(release): v${VERSION} [skip ci]"
git push origin "$REF"

# Downstream jobs build and tag this exact commit, rather than whatever the
# branch tip holds by the time they start.
echo "commit_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
5 changes: 3 additions & 2 deletions .github/workflows/check-tag-version-equality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ jobs:
with:
token: ${{ steps.app-token.outputs.token }}
ref: ${{ inputs.ref }}
# Need git tags to determine version comparison
fetch-tags: true
# git describe walks back from HEAD to find a tag, so it needs the history.
# Depth 0 brings the tags along with it.
fetch-depth: 0

- name: Get package.json version
id: package-version
Expand Down
93 changes: 80 additions & 13 deletions .github/workflows/check-workflow-status.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ on:
workflow-status:
type: string
required: true
description: "Status to check for (e.g., in_progress, queued, completed)"
description: "Status to check for (e.g., in_progress, queued, completed), or non-terminal for any run still going"
created-within-minutes:
type: number
default: 0
description: "Only consider runs started within this many minutes. 0 considers every run."
fail-if-exists:
type: boolean
default: true
Expand All @@ -30,22 +34,85 @@ jobs:
steps:
- name: Check workflow status
uses: actions/github-script@v7
# Inputs reach the script through the environment, keeping them out of the source.
env:
WORKFLOW_NAME: ${{ inputs.workflow-name }}
WORKFLOW_STATUS: ${{ inputs.workflow-status }}
CREATED_WITHIN_MINUTES: ${{ inputs.created-within-minutes }}
FAIL_IF_EXISTS: ${{ inputs.fail-if-exists }}
ERROR_MESSAGE: ${{ inputs.error-message }}
with:
github-token: ${{ inputs.token || github.token }}
script: |
const workflows = await github.rest.actions.listWorkflowRunsForRepo({
const workflowName = process.env.WORKFLOW_NAME;
const status = process.env.WORKFLOW_STATUS;
const windowMinutes = Number(process.env.CREATED_WITHIN_MINUTES) || 0;
const failIfExists = process.env.FAIL_IF_EXISTS === 'true';

// A run blocked by a concurrency group reports queued or pending depending on
// how it was triggered, so non-terminal asks for each of these in turn.
const NON_TERMINAL = ['queued', 'in_progress', 'waiting', 'pending', 'requested'];

// The API returns an empty list for a status it fails to recognise, which would
// pass this guard silently. Accept only the values it documents.
const ALLOWED = NON_TERMINAL.concat([
'completed',
'action_required',
'cancelled',
'failure',
'neutral',
'skipped',
'stale',
'success',
'timed_out',
]);

if (status !== 'non-terminal' && !ALLOWED.includes(status)) {
core.setFailed(`Unknown workflow-status "${status}". Use non-terminal, or one of: ${ALLOWED.join(', ')}`);
return;
}

const params = {
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: '${{ inputs.workflow-name }}',
status: '${{ inputs.workflow-status }}',
});

if (workflows.data.total_count > 0) {
if (${{ inputs.fail-if-exists }}) {
core.setFailed(`${{ inputs.workflow-name }}: ${{ inputs.error-message }}`);
} else {
console.log(`Found ${{ inputs.workflow-status }} workflows for ${{ inputs.workflow-name }}. Continuing as configured.`);
workflow_id: workflowName,
per_page: 100,
};

if (windowMinutes > 0) {
params.created = `>=${new Date(Date.now() - windowMinutes * 60 * 1000).toISOString()}`;
}

// listWorkflowRuns scopes to workflow_id. The repo-level endpoint drops it and
// returns runs for every workflow, dependabot included.
// total_count covers every page, so the tally stays exact.
let total = 0;
const found = [];

for (const runStatus of status === 'non-terminal' ? NON_TERMINAL : [status]) {
let data;
try {
({ data } = await github.rest.actions.listWorkflowRuns({ ...params, status: runStatus }));
} catch (error) {
if (error.status === 404) {
core.setFailed(`Workflow ${workflowName} not found in ${context.repo.owner}/${context.repo.repo}`);
return;
}
throw error;
}
total += data.total_count;
found.push(...data.workflow_runs.map(run => `${run.name} ${run.status} (${run.html_url})`));
}

if (total === 0) {
console.log(`No ${status} runs found for ${workflowName}. Proceeding.`);
return;
}

const detail = found.join('\n');

if (failIfExists) {
core.setFailed(`${workflowName}: ${process.env.ERROR_MESSAGE}\n${detail}`);
} else {
console.log(`No ${{ inputs.workflow-status }} workflows found for ${{ inputs.workflow-name }}. Proceeding.`);
}
console.log(`Found ${total} ${status} run(s) for ${workflowName}. Continuing as configured.\n${detail}`);
}
34 changes: 34 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: pull-request

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
NODE_VERSION: '22'

jobs:
checks:
if: github.event.pull_request.draft == false
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: package-lock.json

- name: Install dependencies
run: npm ci --ignore-scripts --prefer-offline --no-audit --no-fund

- name: Test
run: npm test
27 changes: 23 additions & 4 deletions .github/workflows/tag-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ on:
required: true
ref:
type: string
required: true
default: ""
description: "Ref to read version and sha from. Set this or source-tag."
image:
type: string
required: true
target-tag:
type: string
required: true
source-tag:
type: string
default: ""
description: "Tag to promote, e.g. needs.build-image.outputs.image-tag. Defaults to <version>-<sha> read from ref."
secrets:
TOKEN:
required: true
Expand All @@ -28,17 +33,31 @@ jobs:
runs-on: ubuntu-22.04
environment: ${{ inputs.environment }}
steps:
# Only a caller relying on ref needs the checkout, to read version and sha from it.
- uses: actions/checkout@v4
if: inputs.source-tag == ''
with:
token: ${{ secrets.TOKEN }}
ref: ${{ inputs.ref }}

- name: Get version and revision
id: vars
env:
SOURCE_TAG: ${{ inputs.source-tag }}
REF: ${{ inputs.ref }}
run: |
VERSION=$(jq -r .version package.json)
SHA_SHORT=$(git rev-parse --short HEAD)
echo "SOURCE_TAG=${VERSION}-${SHA_SHORT}" >> $GITHUB_OUTPUT
# Reading version and sha from ref resolves against the branch tip, which moves.
# Callers that already know the tag they pushed pass it in instead.
if [ -z "$SOURCE_TAG" ]; then
if [ -z "$REF" ]; then
echo "::error::Set either ref or source-tag"
exit 1
fi
VERSION=$(jq -r .version package.json)
SHA_SHORT=$(git rev-parse --short HEAD)
SOURCE_TAG="${VERSION}-${SHA_SHORT}"
fi
echo "SOURCE_TAG=${SOURCE_TAG}" >> $GITHUB_OUTPUT
echo 'IMAGE=${{ inputs.image }}' | tr '[:upper:]' '[:lower:]' >> $GITHUB_OUTPUT

- name: Tag image
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea
.DS_Store
node_modules
45 changes: 45 additions & 0 deletions package-lock.json

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

12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "@nexusmutual/workflows",
"version": "0.0.0",
"private": true,
"description": "Reusable GitHub Actions workflows for NexusMutual repositories",
"scripts": {
"test": "node --test"
},
"devDependencies": {
"js-yaml": "^4.1.0"
}
}
Loading
Loading