diff --git a/.github/actions/check-connect-version-match/action.yml b/.github/actions/check-connect-version-match/action.yml new file mode 100644 index 000000000000..1ba6f00aacfc --- /dev/null +++ b/.github/actions/check-connect-version-match/action.yml @@ -0,0 +1,34 @@ +name: "Check Connect Version Match with Branch" +description: "Check if the version in package.json matches the version in the branch name" +inputs: + branch_ref: + description: "The full ref of the branch" + required: true + type: string + extracted_version: + description: "The version extracted from the package.json or other source" + required: true + type: string + +runs: + using: "composite" + steps: + - name: Extract branch version + id: extract-branch-version + run: | + BRANCH_REF="${{ inputs.branch_ref }}" + BRANCH_VERSION="${BRANCH_REF#refs/heads/release/connect/}" + echo "branch_version=$BRANCH_VERSION" >> $GITHUB_OUTPUT + + - name: Check if version in package.json matches the one in branch name + run: | + BRANCH_VERSION="${{ steps.extract-branch-version.outputs.branch_version }}" + EXTRACTED_VERSION="${{ inputs.extracted_version }}" + echo "Branch Version: $BRANCH_VERSION" + echo "Extracted Version: $EXTRACTED_VERSION" + if [[ "$BRANCH_VERSION" != "$EXTRACTED_VERSION" ]]; then + echo "The extracted version ($EXTRACTED_VERSION) does not match the version in the branch name ($BRANCH_VERSION)" + exit 1 # Fail the job if versions don't match + else + echo "Version check passed: $BRANCH_VERSION matches $EXTRACTED_VERSION" + fi diff --git a/.github/workflows/legacy-connect-release-init.yml b/.github/workflows/legacy-connect-release-init.yml deleted file mode 100644 index 8bd33392c00a..000000000000 --- a/.github/workflows/legacy-connect-release-init.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: "[Legacy Release] connect init" -# TODO: this is still used by the GitLab workflow -# TODO: let's leave it here until we are confident GitHub release works. - -on: - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - release: - if: github.repository == 'trezor/trezor-suite' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - # Number of commits to fetch. 0 indicates all history for all branches and tags. - fetch-depth: 0 - - name: Setup node - uses: actions/setup-node@v4 - with: - node-version-file: ".nvmrc" - - name: Install dependencies - run: | - yarn install - - name: Run @trezor/connect create v9 release branch - run: | - git config --global user.name "trezor-ci" - git config --global user.email "${{ secrets.TREZOR_BOT_EMAIL }}" - gh config set prompt disabled - node ./ci/scripts/connect-release-init-v9.js - env: - GITHUB_TOKEN: ${{ secrets.TREZOR_BOT_TOKEN }} diff --git a/.github/workflows/release-connect-bump-versions.yml b/.github/workflows/release-connect-bump-versions.yml new file mode 100644 index 000000000000..0647d7ccedd3 --- /dev/null +++ b/.github/workflows/release-connect-bump-versions.yml @@ -0,0 +1,37 @@ +name: "[Release] connect bump versions" + +on: + workflow_dispatch: + inputs: + semver: + type: choice + description: semver + options: + - patch + - minor + - prerelease + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + bump-versions: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - name: Install dependencies + run: yarn install + + - name: Check dependencies to update + run: | + yarn tsx ./ci/scripts/connect-bump-versions.ts ${{ github.event.inputs.semver }} diff --git a/.github/workflows/release-connect-init.yml b/.github/workflows/release-connect-init.yml new file mode 100644 index 000000000000..94771bba03cb --- /dev/null +++ b/.github/workflows/release-connect-init.yml @@ -0,0 +1,92 @@ +name: "[Release] connect NPM and v9" + +permissions: + id-token: write # for fetching the OIDC token + contents: read # for actions/checkout + +on: + workflow_dispatch: + inputs: + commit_sha: + description: "The commit SHA to checkout" + required: true + type: string + deploymentType: + description: "Select the deployment type. (example: canary, stable)" + required: true + type: choice + options: + - canary + - stable + +jobs: + # Version should have been bumped by now thanks to ./ci/scripts/connect-release-init-npm.js + extract-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.set-version.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # Number of commits to fetch. 0 indicates all history for all branches and tags. + fetch-depth: 0 + # Checkout the specified commit + ref: ${{ github.event.inputs.commit_sha }} + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - name: Extract connect version + id: set-version + run: echo "version=$(node ./ci/scripts/get-connect-version.js)" >> $GITHUB_OUTPUT + + create-push-release-branch: + needs: [extract-version] + name: "Create release branch for version ${{ needs.extract-version.outputs.version }}" + runs-on: ubuntu-latest + outputs: + branch_name: ${{ steps.push-branch.outputs.branch_name }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # Ensure the full commit history is available is required to get specific `ref`. + fetch-depth: 0 + # Checkout the specified commit + ref: ${{ github.event.inputs.commit_sha }} + + - name: Create and push new branch + env: + BRANCH_NAME: "release/connect/${{ needs.extract-version.outputs.version }}" + run: | + echo ${{ env.BRANCH_NAME }} + git checkout -b ${{ env.BRANCH_NAME }} + git push origin ${{ env.BRANCH_NAME }} + echo "branch_name=${{ env.BRANCH_NAME }}" >> $GITHUB_OUTPUT + + trigger-v9-staging-release: + runs-on: ubuntu-latest + steps: + - run: gh workflow run .github/workflows/release-connect-v9-staging.yml --ref $BRANCH_NAME + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH_NAME: ${{ needs.create-push-release-branch.outputs.branch_name }} + + trigger-v9-production-release: + runs-on: ubuntu-latest + steps: + - run: gh workflow run .github/workflows/release-connect-v9-production.yml --ref ${{ env.BRANCH_NAME }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH_NAME: ${{ needs.create-push-release-branch.outputs.branch_name }} + + trigger-npm-release: + runs-on: ubuntu-latest + steps: + - run: gh workflow run .github/workflows/release-connect-npm-init.yml --ref ${{ env.BRANCH_NAME }} --field deploymentType=${{ github.event.inputs.deploymentType }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH_NAME: ${{ needs.create-push-release-branch.outputs.branch_name }} diff --git a/.github/workflows/release-connect-npm-init.yml b/.github/workflows/release-connect-npm-init.yml index ff644f2865ae..cbf6f489eac1 100644 --- a/.github/workflows/release-connect-npm-init.yml +++ b/.github/workflows/release-connect-npm-init.yml @@ -3,28 +3,57 @@ name: "[Release] connect npm init" on: workflow_dispatch: inputs: - semver: + deploymentType: + description: "Select the deployment type. (example: canary, stable)" + required: true type: choice - description: semver options: - - patch - - minor - - prerelease + - canary + - stable concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: - pre-release: - if: github.repository == 'trezor/trezor-suite' + extract-version-from-package-json: runs-on: ubuntu-latest + outputs: + version: ${{ steps.set-version.outputs.version }} steps: - name: Checkout uses: actions/checkout@v4 + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - name: Extract connect version + id: set-version + run: echo "version=$(node ./ci/scripts/get-connect-version.js)" >> $GITHUB_OUTPUT + + check-version-match: + runs-on: ubuntu-latest + needs: [extract-version] + steps: + - uses: actions/checkout@v4 + + - name: Check connect version match + uses: ./.github/actions/check-connect-version-match with: - # Number of commits to fetch. 0 indicates all history for all branches and tags. - fetch-depth: 0 + branch_ref: "${{ github.ref }}" + extracted_version: "${{ needs.extract-version.outputs.version }}" + + trigger-npm-release-connect-dependencies: + needs: [check-version-match] + if: startsWith(github.ref, 'refs/heads/release/connect/') + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # Running `build:libs` requires action to checkout with `submodules: true`. submodules: true - name: Setup node @@ -36,15 +65,21 @@ jobs: run: yarn install - name: Build dependencies - # Running `build:libs` requires action to checkout with `submodules: true`. run: yarn build:libs - name: Run @trezor/connect create npm release branch run: | - git config --global user.name "trezor-ci" - git config --global user.email "${{ secrets.TREZOR_BOT_EMAIL }}" - gh config set prompt disabled - gh api /user --jq .login - node ./ci/scripts/connect-release-init-npm.js ${{ github.event.inputs.semver }} + yarn tsx ./ci/scripts/connect-release-npm-init.ts ${{ github.event.inputs.deploymentType }} ${{ env.BRANCH_NAME }} + env: + BRANCH_NAME: release/connect/${{ needs.extract-version.outputs.version }} + + trigger-npm-release-connect: + needs: [check-version-match, trigger-npm-release-connect-dependencies] + if: startsWith(github.ref, 'refs/heads/release/connect/') + runs-on: ubuntu-latest + steps: + - run: gh workflow run .github/workflows/release-connect-npm.yml --ref ${{ env.BRANCH_NAME }} --field packages=${{ env.PACKAGES}} --field deploymentType=${{ github.event.inputs.deploymentType }} env: - GITHUB_TOKEN: ${{ secrets.TREZOR_BOT_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH_NAME: release/connect/${{ needs.extract-version.outputs.version }} + PACKAGES: ["connect", "connect-web", "connect-webextension"] diff --git a/.github/workflows/release-connect-npm.yml b/.github/workflows/release-connect-npm.yml index 3bd898dff26b..036f0caed760 100644 --- a/.github/workflows/release-connect-npm.yml +++ b/.github/workflows/release-connect-npm.yml @@ -16,6 +16,7 @@ on: jobs: deploy-npm: + name: Deploy NPM ${{ inputs.deploymentType }} environment: production-connect runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/release-connect-v9-poduction.yml b/.github/workflows/release-connect-v9-production.yml similarity index 85% rename from .github/workflows/release-connect-v9-poduction.yml rename to .github/workflows/release-connect-v9-production.yml index c91d6bc46596..7d33b458b457 100644 --- a/.github/workflows/release-connect-v9-poduction.yml +++ b/.github/workflows/release-connect-v9-production.yml @@ -36,8 +36,21 @@ jobs: id: set-version run: echo "version=$(node ./ci/scripts/get-connect-version.js)" >> $GITHUB_OUTPUT + check-version-match: + runs-on: ubuntu-latest + needs: [extract-version] + steps: + - uses: actions/checkout@v4 + + - name: Check connect version match + uses: ./.github/actions/check-connect-version-match + with: + branch_ref: "${{ github.ref }}" + extracted_version: "${{ needs.extract-version.outputs.version }}" + # set the rollback sync-rollback-connect-v9: + needs: [extract-version, check-version-match] if: startsWith(github.ref, 'refs/heads/release/connect/') environment: production-connect name: "Backing up current production version ${{ needs.extract-version.outputs.version }} to rollback bucket" @@ -48,7 +61,7 @@ jobs: - name: Configure aws credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: arn:aws:iam::538326561891:role/gh_actions_trezor_suite_prod_deploy + role-to-assume: arn:aws:iam::538326561891:role/gh_actions_connect_prod_deploy aws-region: eu-central-1 - name: Synching rollback bucket with current production @@ -56,6 +69,7 @@ jobs: aws s3 sync "s3://connect.trezor.io/${{ env.LATEST_VERSION }}/" "s3://rollback-connect.trezor.io/${{ env.LATEST_VERSION }}/" deploy-production-semantic-version: + needs: [extract-version, check-version-match] if: startsWith(github.ref, 'refs/heads/release/connect/') environment: production-connect name: "Deploying to connect.trezor.io/9.x.x" @@ -66,7 +80,7 @@ jobs: - name: Configure aws credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: arn:aws:iam::538326561891:role/gh_actions_trezor_suite_prod_deploy + role-to-assume: arn:aws:iam::538326561891:role/gh_actions_connect_prod_deploy aws-region: eu-central-1 - name: Synching production bucket with current staging @@ -77,7 +91,7 @@ jobs: # From staging move it to production deploy-production-v9: # We deploy to production only if rollback sync was successful. - needs: [sync-rollback-connect-v9] + needs: [extract-version, check-version-match, sync-rollback-connect-v9] if: startsWith(github.ref, 'refs/heads/release/connect/') && github.event.inputs.deploymentType == 'stable' environment: production-connect name: "Deploying to connect.trezor.io/9/" diff --git a/.github/workflows/release-connect-v9-staging.yml b/.github/workflows/release-connect-v9-staging.yml index 1d126c52bb39..fe6b7aa852d7 100644 --- a/.github/workflows/release-connect-v9-staging.yml +++ b/.github/workflows/release-connect-v9-staging.yml @@ -33,19 +33,13 @@ jobs: runs-on: ubuntu-latest needs: [extract-version] steps: - - name: Check if version in package.json matches the one in branch name - run: | - # Extract the version from the branch name, assuming format 'refs/heads/release/connect/9.2.4-beta.1' - BRANCH_VERSION="${GITHUB_REF#*release/connect/}" # This strips everything before and including 'release/connect/' - EXTRACTED_VERSION="${{ needs.extract-version.outputs.version }}" - echo "Branch Version: $BRANCH_VERSION" - echo "Extracted Version: $EXTRACTED_VERSION" - if [[ "$BRANCH_VERSION" != "$EXTRACTED_VERSION" ]]; then - echo "The extracted version ($EXTRACTED_VERSION) does not match the version in the branch name ($BRANCH_VERSION)" - exit 1 # Fail the job if versions don't match - else - echo "Version check passed: $BRANCH_VERSION matches $EXTRACTED_VERSION" - fi + - uses: actions/checkout@v4 + + - name: Check connect version match + uses: ./.github/actions/check-connect-version-match + with: + branch_ref: "${{ github.ref }}" + extracted_version: "${{ needs.extract-version.outputs.version }}" # This job deploys to staging-connect.trezor.io/9.x.x deploy-staging-semantic-version: @@ -68,7 +62,7 @@ jobs: - name: Build and deploy to staging-connect.trezor.io/9.x.x uses: ./.github/actions/release-connect with: - awsRoleToAssume: "arn:aws:iam::538326561891:role/gh_actions_trezor_suite_prod_deploy" + awsRoleToAssume: "arn:aws:iam::538326561891:role/gh_actions_connect_staging_deploy" awsRegion: "eu-central-1" serverHostname: "staging-connect.trezor.io" serverPath: ${{ needs.extract-version.outputs.version }} @@ -94,7 +88,7 @@ jobs: - name: Build and deploy to staging-connect.trezor.io/9 uses: ./.github/actions/release-connect with: - awsRoleToAssume: "arn:aws:iam::538326561891:role/gh_actions_trezor_suite_prod_deploy" + awsRoleToAssume: "arn:aws:iam::538326561891:role/gh_actions_connect_staging_deploy" awsRegion: "eu-central-1" serverHostname: "staging-connect.trezor.io" serverPath: "9" diff --git a/ci/scripts/check-npm-and-local.js b/ci/scripts/check-npm-and-local.js index 19b88c6bf8bf..7ef7ee0fdaa3 100644 --- a/ci/scripts/check-npm-and-local.js +++ b/ci/scripts/check-npm-and-local.js @@ -2,7 +2,6 @@ const { execSync } = require('child_process'); const fs = require('fs'); const util = require('util'); -const https = require('https'); const fetch = require('cross-fetch'); const tar = require('tar'); const path = require('path'); diff --git a/ci/scripts/connect-release-init-npm.js b/ci/scripts/connect-bump-versions.ts similarity index 68% rename from ci/scripts/connect-release-init-npm.js rename to ci/scripts/connect-bump-versions.ts index be11ba64964d..fefca539b69f 100644 --- a/ci/scripts/connect-release-init-npm.js +++ b/ci/scripts/connect-bump-versions.ts @@ -1,11 +1,13 @@ -const path = require('path'); -const fs = require('fs'); +import path from 'path'; +import fs from 'fs'; const { checkPackageDependencies, exec, commit, comment } = require('./helpers'); const args = process.argv.slice(2); -if (args.length < 1) throw new Error('Check npm dependencies requires 1 parameter: semver'); +if (args.length < 1) { + throw new Error('Check npm dependencies requires 1 parameter: semver'); +} const [semver] = args; const allowedSemvers = ['patch', 'minor', 'prerelease']; @@ -17,7 +19,7 @@ const deploymentType = semver === 'prerelease' ? 'canary' : 'stable'; const ROOT = path.join(__dirname, '..', '..'); -const getGitCommitByPackageName = (packageName, maxCount = 10) => +const getGitCommitByPackageName = (packageName: string, maxCount = 10) => exec('git', [ 'log', '--oneline', @@ -28,39 +30,16 @@ const getGitCommitByPackageName = (packageName, maxCount = 10) => `./packages/${packageName}`, ]); -const ghWorkflowRunReleaseAction = (branch, packages, deployment) => - exec('gh', [ - 'workflow', - 'run', - '.github/workflows/release-connect-npm.yml', - '--ref', - branch, - '--field', - `packages=${packages}`, - '--field', - `deploymentType=${deployment}`, - ]); - -const splitByNewlines = input => input.split('\n'); +const splitByNewlines = (input: string): string[] => input.split('\n'); -const findIndexByCommit = (commitArr, searchString) => +const findIndexByCommit = (commitArr: string[], searchString: string): number => commitArr.findIndex(commit => commit.includes(searchString)); -const initConnectRelease = async () => { - console.log('Using GitHub Token:', process.env.GITHUB_TOKEN ? 'Yes' : 'No'); - - if (process.env.GITHUB_TOKEN) { - // Making sure we use the proper GITHUB_TOKEN - exec('gh', ['auth', 'setup-git']); - exec('gh', ['config', 'set', '-h', 'github.com', 'oauth_token', process.env.GITHUB_TOKEN]); - } else { - throw new Error('Missing GITHUB_TOKEN'); - } - +const bumpConnect = async () => { const checkResult = await checkPackageDependencies('connect', deploymentType); - const update = checkResult.update.map(package => package.replace('@trezor/', '')); - const errors = checkResult.errors.map(package => package.replace('@trezor/', '')); + const update = checkResult.update.map((pkg: string) => pkg.replace('@trezor/', '')); + const errors = checkResult.errors.map((pkg: string) => pkg.replace('@trezor/', '')); if (update) { update.forEach(packageName => { @@ -69,7 +48,7 @@ const initConnectRelease = async () => { exec('yarn', ['bump', semver, `./packages/${packageName}/package.json`]); - const rawPackageJSON = fs.readFileSync(PACKAGE_JSON_PATH); + const rawPackageJSON = fs.readFileSync(PACKAGE_JSON_PATH, 'utf-8'); const packageJSON = JSON.parse(rawPackageJSON); const { version } = packageJSON; @@ -79,7 +58,7 @@ const initConnectRelease = async () => { const CHANGELOG_PATH = path.join(PACKAGE_PATH, 'CHANGELOG.md'); - const newCommits = []; + const newCommits: string[] = []; for (const commit of commitsArr) { if (commit.includes(`npm-release: @trezor/${packageName}`)) { break; @@ -110,18 +89,18 @@ const initConnectRelease = async () => { const CONNECT_PACKAGE_PATH = path.join(ROOT, 'packages', 'connect'); const CONNECT_PACKAGE_JSON_PATH = path.join(CONNECT_PACKAGE_PATH, 'package.json'); - const preBumpRawPackageJSON = fs.readFileSync(CONNECT_PACKAGE_JSON_PATH); + const preBumpRawPackageJSON = fs.readFileSync(CONNECT_PACKAGE_JSON_PATH, 'utf-8'); const preBumpPackageJSON = JSON.parse(preBumpRawPackageJSON); const { version: preBumpVersion } = preBumpPackageJSON; exec('yarn', ['workspace', '@trezor/connect', `version:${semver}`]); - const rawPackageJSON = fs.readFileSync(CONNECT_PACKAGE_JSON_PATH); + const rawPackageJSON = fs.readFileSync(CONNECT_PACKAGE_JSON_PATH, 'utf-8'); const packageJSON = JSON.parse(rawPackageJSON); const { version } = packageJSON; const commitMessage = `npm-release: @trezor/connect ${version}`; - const branchName = `npm-release/connect-${version}`; + const branchName = `bump-versions/connect-${version}`; // Check if branch exists and if so, delete it. const branchExists = exec('git', ['branch', '--list', branchName]).stdout; @@ -207,34 +186,6 @@ const initConnectRelease = async () => { body: connectGitLogText, }); } - - // At this point we have created the commit with the bumped versions, - // and a pull request including all the changes. - // Now we want to trigger the action that will trigger the actual release, - // after approval form authorized member. - const dependenciesToRelease = JSON.stringify(update); - console.log('dependenciesToRelease:', dependenciesToRelease); - console.log('deploymentType:', deploymentType); - console.log('branchName:', branchName); - - const releaseDependencyActionOutput = ghWorkflowRunReleaseAction( - branchName, - dependenciesToRelease, - deploymentType, - ); - - console.log('releaseDependencyActionOutput output:', releaseDependencyActionOutput.stdout); - - // We trigger this second action to release connect, so we can just not approve it in case - // the release of the dependencies to NPM was not successful. - console.log('Triggering action to release connect.'); - const releaseConnectActionOutput = ghWorkflowRunReleaseAction( - branchName, - JSON.stringify(['connect', 'connect-web', 'connect-webextension']), - deploymentType, - ); - - console.log('releaseConnectActionOutput output:', releaseConnectActionOutput.stdout); }; -initConnectRelease(); +bumpConnect(); diff --git a/ci/scripts/connect-release-init-v9.js b/ci/scripts/connect-release-init-v9.js deleted file mode 100644 index 19a39bea750c..000000000000 --- a/ci/scripts/connect-release-init-v9.js +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable camelcase */ - -// TODO: this is still used by the GitLab workflow -// TODO: let's leave it here until we are confident GitHub release works. - -const path = require('path'); -const fs = require('fs'); - -const { exec } = require('./helpers'); - -const ROOT = path.join(__dirname, '..', '..'); - -const init = () => { - const PACKAGE_PATH = path.join(ROOT, 'packages', 'connect'); - const PACKAGE_JSON_PATH = path.join(PACKAGE_PATH, 'package.json'); - const rawPackageJSON = fs.readFileSync(PACKAGE_JSON_PATH); - const packageJSON = JSON.parse(rawPackageJSON); - const { version } = packageJSON; - - // Version should have been bumped by now thanks to ./ci/scripts/connect-release-init-npm.js - const branchName = `release/connect/${version}`; - - exec('git', ['checkout', '-b', branchName]); - - exec('git', ['push', 'origin', branchName]); -}; - -init(); diff --git a/ci/scripts/connect-release-npm-init.ts b/ci/scripts/connect-release-npm-init.ts new file mode 100644 index 000000000000..bcc039bffe3e --- /dev/null +++ b/ci/scripts/connect-release-npm-init.ts @@ -0,0 +1,144 @@ +// This script should check what packages are from the repository have different most recent version in NPM +// as the on e in the package.json and trigger the workflow to release to NPM those packages. + +import { exec } from './helpers'; +import fs from 'fs'; +import util from 'util'; +import path from 'path'; +import fetch from 'cross-fetch'; +import semver from 'semver'; + +const args = process.argv.slice(2); + +if (args.length < 2) { + throw new Error('Check npm dependencies requires 2 parameters: deploymentType branchName'); +} +const [deploymentType, branchName] = args; + +const allowedDeploymentType = ['canary', 'stable']; +if (!allowedDeploymentType.includes(deploymentType)) { + throw new Error( + `provided semver: ${deploymentType} must be one of ${allowedDeploymentType.join(', ')}`, + ); +} + +const readFile = util.promisify(fs.readFile); + +const ROOT = path.join(__dirname, '..', '..'); + +const triggerReleaseNpmWorkflow = (branch, packages, type) => + exec('gh', [ + 'workflow', + 'run', + '.github/workflows/release-connect-npm.yml', + '--ref', + branch, + '--field', + `packages=${packages}`, + '--field', + `deploymentType=${type}`, + ]); + +const getNpmRemoteGreatestVersion = async (moduleName: string) => { + const [_prefix] = moduleName.split('/'); + const npmRegistryUrl = `https://registry.npmjs.org/${moduleName}`; + + try { + console.log(`fetching npm registry info from: ${npmRegistryUrl}`); + const response = await fetch(npmRegistryUrl); + const data = await response.json(); + if (data.error) { + return { success: false }; + } + + const distributionTags = data['dist-tags']; + const versionArray: string[] = Object.values(distributionTags); + const greatestVersion = versionArray.reduce((max, current) => { + return semver.gt(current, max) ? current : max; + }); + + return greatestVersion; + } catch (error) { + console.error('error:', error); + throw new Error('Not possible to get remote greatest version'); + } +}; + +const nonReleaseDependencies: string[] = []; + +const checkNonReleasedDependencies = async (packageName: string) => { + const rawPackageJSON = await readFile( + path.join(ROOT, 'packages', packageName, 'package.json'), + 'utf-8', + ); + + const packageJSON = JSON.parse(rawPackageJSON); + const { + version, + dependencies, + // devDependencies // We should ignore devDependencies. + } = packageJSON; + + const remoteGreatestVersion = await getNpmRemoteGreatestVersion(`@trezor/${packageName}`); + + // If local version is greatest than the greatest one in NPM we add it to the release. + if (semver.gt(version, remoteGreatestVersion as string)) { + const index = nonReleaseDependencies.indexOf(packageName); + if (index > -1) { + nonReleaseDependencies.splice(index, 1); + } + nonReleaseDependencies.push(packageName); + } + + if (!dependencies || !Object.keys(dependencies)) { + return; + } + + // eslint-disable-next-line no-restricted-syntax + for await (const [dependency] of Object.entries(dependencies)) { + // is not a dependency released from monorepo. we don't care + if (!dependency.startsWith('@trezor')) { + // eslint-disable-next-line no-continue + continue; + } + const [_prefix, name] = dependency.split('/'); + + console.log('name', name); + + await checkNonReleasedDependencies(name); + } + console.log('nonReleaseDependencies', nonReleaseDependencies); +}; + +const initConnectNpmRelease = async () => { + // We check what dependencies need to be released because they have version bumped locally + // and remote greatest version is lower than the local one. + await checkNonReleasedDependencies('connect'); + await checkNonReleasedDependencies('connect-web'); + await checkNonReleasedDependencies('connect-webextension'); + console.log('Final nonReleaseDependencies', nonReleaseDependencies); + + // We do not want to include `connect`, `connect-web` and `connect-webextension` since we want + // to release those separately and we always want to release them. + const onlyDependenciesToRelease = nonReleaseDependencies.filter(item => { + return !['connect', 'connect-web', 'connect-webextension'].includes(item); + }); + + // We use `onlyDependenciesToRelease` to trigger NPM releases + const dependenciesToRelease = JSON.stringify(onlyDependenciesToRelease); + console.log('dependenciesToRelease:', dependenciesToRelease); + console.log('deploymentType:', deploymentType); + console.log('branchName:', branchName); + + // Now we want to trigger the action that will trigger the actual release, + // after approval form authorized member. + const releaseDependencyActionOutput = triggerReleaseNpmWorkflow( + branchName, + dependenciesToRelease, + deploymentType, + ); + + console.log('releaseDependencyActionOutput output:', releaseDependencyActionOutput.stdout); +}; + +initConnectNpmRelease(); diff --git a/ci/scripts/helpers.js b/ci/scripts/helpers.js index 7fef683d399f..86849ce7a62e 100644 --- a/ci/scripts/helpers.js +++ b/ci/scripts/helpers.js @@ -10,13 +10,6 @@ const readFile = util.promisify(fs.readFile); const { getLocalAndRemoteChecksums } = require('./check-npm-and-local'); -const rootPath = path.join(__dirname, '..', '..'); -const packagesPath = path.join(rootPath, 'packages'); - -const packages = fs.readdirSync(packagesPath, { - encoding: 'utf-8', -}); - const ROOT = path.join(__dirname, '..', '..'); const updateNeeded = [];