diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9874951939317..1ba159471f0d7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,15 +1,18 @@ name: publish -env: - DEBUG: napi:* - NX_RUN_GROUP: ${{ github.run_id }}-${{ github.run_attempt }} - NPM_CONFIG_PROVENANCE: true + on: + schedule: + - cron: "0 3 * * 2-6" # Tuesdays - Saturdays, at 3am UTC workflow_dispatch: release: types: [published] + +env: + DEBUG: napi:* + NX_RUN_GROUP: ${{ github.run_id }}-${{ github.run_attempt }} + jobs: build: - if: "!contains(github.event.head_commit.message, 'skip ci')" strategy: fail-fast: false matrix: @@ -82,14 +85,14 @@ jobs: name: stable - ${{ matrix.settings.target }} - node@18 runs-on: ${{ matrix.settings.host }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 with: version: 8.2 - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 if: ${{ !matrix.settings.docker }} with: node-version: 18 @@ -128,7 +131,7 @@ jobs: run: pnpm install --frozen-lockfile timeout-minutes: 30 - name: Setup node x86 - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 if: matrix.settings.target == 'i686-pc-windows-msvc' with: node-version: 18 @@ -152,12 +155,13 @@ jobs: name: bindings-${{ matrix.settings.target }} path: packages/**/*.node if-no-files-found: error + build-freebsd: runs-on: macos-12 name: Build FreeBSD timeout-minutes: 45 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: ${{ !contains(github.ref, '-') }} - name: Build id: build @@ -208,6 +212,7 @@ jobs: name: bindings-freebsd path: packages/**/*.node if-no-files-found: error + publish: if: ${{ github.event_name == 'release' && github.repository_owner == 'nrwl' }} name: Publish @@ -218,14 +223,15 @@ jobs: - build-freebsd - build steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 with: version: 8.2 - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 18 + registry-url: 'https://registry.npmjs.org' check-latest: true cache: 'pnpm' - name: Install dependencies @@ -240,8 +246,14 @@ jobs: - name: Publish run: | git checkout -b publish/$GITHUB_REF_NAME - npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN - pnpm nx-release --local=false $GITHUB_REF_NAME + # If triggered by the cron, create a canary release + if [ "${{ github.event_name }}" = "schedule" ]; then + VERSION="canary" + else + # Otherwise, use the tag name (if triggered via release), or explicit version (if triggered via workflow_dispatch) + VERSION="${GITHUB_REF_NAME}" + fi + pnpm nx-release --local=false $VERSION - name: Trigger Docs Release # Publish docs only on a full release if: ${{ !github.event.release.prerelease }} @@ -254,4 +266,5 @@ jobs: fi env: GH_TOKEN: ${{ github.token }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: true diff --git a/scripts/nx-release.ts b/scripts/nx-release.ts index 372cbdf07189e..2a2ecad0bdcbf 100755 --- a/scripts/nx-release.ts +++ b/scripts/nx-release.ts @@ -5,7 +5,7 @@ import { rmSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { URL } from 'node:url'; import { isRelativeVersionKeyword } from 'nx/src/command-line/release/utils/semver'; -import { ReleaseType, parse } from 'semver'; +import { ReleaseType, inc, major, parse } from 'semver'; import * as yargs from 'yargs'; const LARGE_BUFFER = 1024 * 1000000; @@ -204,6 +204,60 @@ function parseArgs() { description: 'The version to publish. This does not need to be passed and can be inferred.', default: 'minor', + coerce: (version) => { + if (version !== 'canary') { + return version; + } + /** + * Handle the special case of `canary` + */ + + const currentLatestVersion = execSync('npm view nx@latest version') + .toString() + .trim(); + const currentNextVersion = execSync('npm view nx@next version') + .toString() + .trim(); + + let canaryBaseVersion: string | null = null; + + // If the latest and next are not on the same major version, then we need to publish a canary version of the next major + if (major(currentLatestVersion) !== major(currentNextVersion)) { + canaryBaseVersion = `${major(currentNextVersion)}.0.0`; + } else { + // Determine next minor version above the currentLatestVersion + const nextMinorRelease = inc( + currentLatestVersion, + 'minor', + undefined + ); + canaryBaseVersion = nextMinorRelease; + } + + if (!canaryBaseVersion) { + throw new Error(`Unable to determine a base for the canary version.`); + } + + // Create YYYYMMDD string + const date = new Date(); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed + const day = String(date.getDate()).padStart(2, '0'); + const YYYYMMDD = `${year}${month}${day}`; + + // Get the current short git sha + const gitSha = execSync('git rev-parse --short HEAD').toString().trim(); + + const canaryVersion = `${canaryBaseVersion}-canary.${YYYYMMDD}-${gitSha}`; + + console.log(`\nDerived canary version dynamically`, { + currentLatestVersion, + currentNextVersion, + canaryVersion, + }); + + return canaryVersion; + }, }) .option('gitRemote', { type: 'string', @@ -265,7 +319,14 @@ function getRegistry() { return new URL(execSync('npm config get registry').toString().trim()); } -function determineDistTag(newVersion: string): 'latest' | 'next' | 'previous' { +function determineDistTag( + newVersion: string +): 'latest' | 'next' | 'previous' | 'canary' { + // Special case of canary + if (newVersion.includes('canary')) { + return 'canary'; + } + // For a relative version keyword, it cannot be previous if (isRelativeVersionKeyword(newVersion)) { const prereleaseKeywords: ReleaseType[] = [