AFFiNE/.github/workflows/nx-cloud-main.yml
2022-08-08 18:44:45 +08:00

300 lines
13 KiB
YAML

name: Nx Cloud Main
on:
workflow_call:
secrets:
NX_CLOUD_ACCESS_TOKEN:
required: false
NX_CLOUD_AUTH_TOKEN:
required: false
NX_CYPRESS_KEY:
required: false
inputs:
number-of-agents:
required: false
type: number
environment-variables:
required: false
type: string
init-commands:
required: false
type: string
final-commands:
required: false
type: string
parallel-commands:
required: false
type: string
parallel-commands-on-agents:
required: false
type: string
node-version:
required: false
type: string
yarn-version:
required: false
type: string
npm-version:
required: false
type: string
pnpm-version:
required: false
type: string
install-commands:
required: false
type: string
main-branch-name:
required: false
type: string
default: main
runs-on:
required: false
type: string
default: ubuntu-latest
# We needed this input in order to be able to configure out integration tests for this repo, it is not documented
# so as to not cause confusion/add noise, but technically any consumer of the workflow can use it if they want to.
working-directory:
required: false
type: string
env:
NX_CLOUD_DISTRIBUTED_EXECUTION: true
NX_CLOUD_DISTRIBUTED_EXECUTION_AGENT_COUNT: ${{ inputs.number-of-agents }}
NX_BRANCH: ${{ github.event.number || github.ref_name }}
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }}
CYPRESS_RECORD_KEY: ${{ secrets.NX_CYPRESS_KEY }}
jobs:
main:
runs-on: ${{ inputs.runs-on }}
# The name of the job which will invoke this one is expected to be "Nx Cloud - Main Job", and whatever we call this will be appended
# to that one after a forward slash, so we keep this one intentionally short to produce "Nx Cloud - Main Job / Run" in the Github UI
name: Run
defaults:
run:
working-directory: ${{ inputs.working-directory || github.workspace }}
# Specify shell to help normalize across different operating systems
shell: bash
steps:
- uses: actions/checkout@v2
name: Checkout [Pull Request]
if: ${{ github.event_name == 'pull_request' }}
with:
# By default, PRs will be checked-out based on the Merge Commit, but we want the actual branch HEAD.
ref: ${{ github.event.pull_request.head.sha }}
# We need to fetch all branches and commits so that Nx affected has a base to compare against.
fetch-depth: 0
- uses: actions/checkout@v2
name: Checkout [Default Branch]
if: ${{ github.event_name != 'pull_request' }}
with:
# We need to fetch all branches and commits so that Nx affected has a base to compare against.
fetch-depth: 0
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v2
with:
main-branch-name: ${{ inputs.main-branch-name }}
- name: Detect package manager
id: package_manager
shell: bash
run: |
echo "::set-output name=name::$([[ -f ./yarn.lock ]] && echo "yarn" || ([[ -f ./pnpm-lock.yaml ]] && echo "pnpm") || echo "npm")"
# Set node/npm/yarn versions using volta, with optional overrides provided by the consumer
- uses: volta-cli/action@fdf4cf319494429a105efaa71d0e5ec67f338c6e
with:
node-version: "${{ inputs.node-version }}"
npm-version: "${{ inputs.npm-version }}"
yarn-version: "${{ inputs.yarn-version }}"
# Install pnpm with exact version provided by consumer or fallback to latest
- name: Install PNPM
if: steps.package_manager.outputs.name == 'pnpm'
uses: pnpm/action-setup@v2.2.1
with:
version: ${{ inputs.pnpm-version || 'latest' }}
- name: Print node/npm/yarn versions
id: versions
run: |
node_ver=$( node --version )
yarn_ver=$( yarn --version || true )
pnpm_ver=$( pnpm --version || true )
echo "Node: ${node_ver:1}"
echo "NPM: $( npm --version )"
if [[ $yarn_ver != '' ]]; then echo "Yarn: $yarn_ver"; fi
if [[ $pnpm_ver != '' ]]; then echo "PNPM: $pnpm_ver"; fi
echo "::set-output name=node_version::${node_ver:1}"
- name: Use the node_modules cache if available [npm]
if: steps.package_manager.outputs.name == 'npm'
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-
- name: Use the node_modules cache if available [pnpm]
if: steps.package_manager.outputs.name == 'pnpm'
uses: actions/cache@v2
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-
- name: Get yarn cache directory path
if: steps.package_manager.outputs.name == 'yarn'
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Use the node_modules cache if available [yarn]
if: steps.package_manager.outputs.name == 'yarn'
uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-yarn-
- name: Process environment-variables
if: ${{ inputs.environment-variables != '' }}
uses: actions/github-script@v6
env:
ENV_VARS: ${{ inputs.environment-variables }}
with:
script: |
const { appendFileSync } = require('fs');
// trim spaces and escape quotes
const cleanStr = str => str
.trim()
.replaceAll(/`/g, "\`");
// parse variable to correct type
const parseStr = str =>
str === 'true' || str === 'TRUE'
? true
: str === 'false' || str === 'FALSE'
? false
: isNaN(str)
? str
: parseFloat(str);
const varsStr = process.env.ENV_VARS || '';
const vars = varsStr
.split('\n')
.map(variable => variable.trim())
.filter(variable => variable.indexOf('=') > 0)
.map(variable => ({
name: cleanStr(variable.split('=')[0]),
value: cleanStr(variable.slice(variable.indexOf('=') + 1))
}));
for (const v of vars) {
console.log(`Appending environment variable \`${v.name}\` with value \`${v.value}\` to ${process.env.GITHUB_ENV}`);
appendFileSync(process.env.GITHUB_ENV, `${v.name}=${parseStr(v.value)}\n`);
}
- name: Run any configured install-commands
if: ${{ inputs.install-commands != '' }}
run: |
${{ inputs.install-commands }}
- name: Install dependencies
if: ${{ inputs.install-commands == '' }}
run: |
if [ "${{ steps.package_manager.outputs.name == 'yarn' }}" == "true" ]; then
echo "Running yarn install --frozen-lockfile"
yarn install --frozen-lockfile
elif [ "${{ steps.package_manager.outputs.name == 'pnpm' }}" == "true" ]; then
echo "Running pnpm install --frozen-lockfile"
pnpm install --frozen-lockfile
else
echo "Running npm ci"
npm ci
fi
# An unfortunate side-effect of the way reusable workflows work is that by the time they are pulled into the "caller"
# repo, they are effectively completely embedded in that context. This means that we cannot reference any files which
# are local to this repo which defines the workflow, and we therefore need to work around this by embedding the contents
# of the shell utilities for executing commands into the workflow directly.
- name: Create command utils
uses: actions/github-script@v6
with:
script: |
const { writeFileSync } = require('fs');
const runCommandsInParallelScript = `
# Extract the provided commands from the stringified JSON array.
IFS=$'\n' read -d '' -a userCommands < <((jq -c -r '.[]') <<<"$1")
# Invoke the provided commands in parallel and collect their exit codes.
pids=()
for userCommand in "\${userCommands[@]}"; do
eval "$userCommand" & pids+=($!)
done
# If any one of the invoked commands exited with a non-zero exit code, exit the whole thing with code 1.
for pid in \${pids[*]}; do
if ! wait $pid; then
exit 1
fi
done
# All the invoked commands must have exited with code zero.
exit 0
`;
writeFileSync('./.github/workflows/run-commands-in-parallel.sh', runCommandsInParallelScript);
- name: Prepare command utils
# We need to escape the workspace path to be consistent cross-platform: https://github.com/actions/runner/issues/1066
run: chmod +x ${GITHUB_WORKSPACE//\\//}/.github/workflows/run-commands-in-parallel.sh
- name: Initialize the Nx Cloud distributed CI run
run: npx nx-cloud start-ci-run
# The good thing about the multi-line string input for sequential commands is that we can simply forward it on as is to the bash shell and it will behave
# how we want it to in terms of quote escaping, variable assignment etc
- name: Run any configured init-commands sequentially
if: ${{ inputs.init-commands != '' }}
run: |
${{ inputs.init-commands }}
- name: Process parallel commands configuration
uses: actions/github-script@v6
id: parallel_commands_config
env:
PARALLEL_COMMANDS: ${{ inputs.parallel-commands }}
PARALLEL_COMMANDS_ON_AGENTS: ${{ inputs.parallel-commands-on-agents }}
with:
# For the ones configured for main, explicitly set NX_CLOUD_DISTRIBUTED_EXECUTION to false, taking into account commands chained with &&
# within the strings. In order to properly escape single quotes we need to do some manual replacing and escaping so that the commands
# are forwarded onto the run-commands-in-parallel.sh script appropriately.
script: |
const parallelCommandsOnMainStr = process.env.PARALLEL_COMMANDS || '';
const parallelCommandsOnAgentsStr = process.env.PARALLEL_COMMANDS_ON_AGENTS || '';
const parallelCommandsOnMain = parallelCommandsOnMainStr
.split('\n')
.map(command => command.trim())
.filter(command => command.length > 0)
.map(s => s.replace(/'/g, '%27'));
const parallelCommandsOnAgents = parallelCommandsOnAgentsStr
.split('\n')
.map(command => command.trim())
.filter(command => command.length > 0)
.map(s => s.replace(/'/g, '%27'));
const formattedArrayOfCommands = [
...parallelCommandsOnMain.map(s => s
.split(' && ')
.map(s => `NX_CLOUD_DISTRIBUTED_EXECUTION=false ${s}`)
.join(' && ')
),
...parallelCommandsOnAgents,
];
const stringifiedEncodedArrayOfCommands = JSON.stringify(formattedArrayOfCommands)
.replace(/%27/g, "'\\''");
return stringifiedEncodedArrayOfCommands
result-encoding: string
- name: Run any configured parallel commands on main and agent jobs
# We need to escape the workspace path to be consistent cross-platform: https://github.com/actions/runner/issues/1066
run: ${GITHUB_WORKSPACE//\\//}/.github/workflows/run-commands-in-parallel.sh '${{ steps.parallel_commands_config.outputs.result }}'
# The good thing about the multi-line string input for sequential commands is that we can simply forward it on as is to the bash shell and it will behave
# how we want it to in terms of quote escaping, variable assignment etc
- name: Run any configured final-commands sequentially
if: ${{ inputs.final-commands != '' }}
run: |
${{ inputs.final-commands }}
- name: Stop all running agents for this CI run
# It's important that we always run this step, otherwise in the case of any failures in preceding non-Nx steps, the agents will keep running and waste billable minutes
if: ${{ always() }}
run: npx nx-cloud stop-all-agents