From 73bb2618aa627fa0e3d13bd02868d0d023c266c4 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 5 Apr 2023 16:01:48 -0700 Subject: [PATCH] Refactor Publish Pipeline Take 4 --- .ado/get-next-semver-version.js | 17 +------ .ado/gitTagRelease.js | 44 ++++++++++++++++ .ado/publish.yml | 51 +++++++++++++++++-- .ado/templates/apple-job-publish.yml | 40 +++++---------- ...le-release-setup.yml => configure-git.yml} | 7 +-- .ado/variables/vars.yml | 3 +- .ado/versionUtils.js | 26 ++++++++++ scripts/prepare-package-for-release.js | 3 +- scripts/publish-npm.js | 17 +++++-- scripts/release-utils.js | 2 - 10 files changed, 149 insertions(+), 61 deletions(-) create mode 100644 .ado/gitTagRelease.js rename .ado/templates/{apple-release-setup.yml => configure-git.yml} (58%) create mode 100644 .ado/versionUtils.js diff --git a/.ado/get-next-semver-version.js b/.ado/get-next-semver-version.js index 67b7336599a780..2a1a69a005af75 100644 --- a/.ado/get-next-semver-version.js +++ b/.ado/get-next-semver-version.js @@ -2,22 +2,7 @@ const fs = require("fs"); const path = require("path"); const semver = require('semver'); -const {execSync} = require('child_process'); - -const pkgJsonPath = path.resolve(__dirname, "../package.json"); -let publishBranchName = ''; -try { - publishBranchName = process.env.BUILD_SOURCEBRANCH.match(/refs\/heads\/(.*)/)[1]; -} catch (error) {} - -function gatherVersionInfo() { - let pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8")); - - let releaseVersion = pkgJson.version; - const branchVersionSuffix = (publishBranchName.match(/(fb.*merge)|(fabric)/) ? `-${publishBranchName}` : ''); - - return {pkgJson, releaseVersion, branchVersionSuffix}; -} +const {gatherVersionInfo} = require('./versionUtils'); function getNextVersion(patchVersionPrefix) { diff --git a/.ado/gitTagRelease.js b/.ado/gitTagRelease.js new file mode 100644 index 00000000000000..c314a7e5c1fb29 --- /dev/null +++ b/.ado/gitTagRelease.js @@ -0,0 +1,44 @@ +// @ts-check +// Used to apply the package updates: the git tag for the published release. + +const execSync = require("child_process").execSync; +const {publishBranchName, gatherVersionInfo} = require('./versionUtils'); + +function exec(command) { + try { + console.log(`Running command: ${command}`); + return execSync(command, { + stdio: "inherit" + }); + } catch (err) { + process.exitCode = 1; + console.log(`Failure running: ${command}`); + throw err; + } +} + +function doPublish() { + console.log(`Target branch to publish to: ${publishBranchName}`); + + const {releaseVersion} = gatherVersionInfo() + + const tempPublishBranch = `publish-temp-${Date.now()}`; + exec(`git checkout -b ${tempPublishBranch}`); + + exec(`git add .`); + exec(`git commit -m "Applying package update to ${releaseVersion} ***NO_CI***"`); + exec(`git tag v${releaseVersion}`); + exec(`git push origin HEAD:${tempPublishBranch} --follow-tags --verbose`); + exec(`git push origin tag v${releaseVersion}`); + + exec(`git checkout ${publishBranchName}`); + exec(`git pull origin ${publishBranchName}`); + exec(`git merge ${tempPublishBranch} --no-edit`); + exec( + `git push origin HEAD:${publishBranchName} --follow-tags --verbose` + ); + exec(`git branch -d ${tempPublishBranch}`); + exec(`git push origin --delete -d ${tempPublishBranch}`); +} + +doPublish(); \ No newline at end of file diff --git a/.ado/publish.yml b/.ado/publish.yml index 41e249bc147560..54738fef3056e5 100644 --- a/.ado/publish.yml +++ b/.ado/publish.yml @@ -30,6 +30,8 @@ jobs: value: true timeoutInMinutes: 90 # how long to run the job before automatically cancelling cancelTimeoutInMinutes: 5 # how much time to give 'run always even if cancelled tasks' before killing them + dependsOn: + - Compliance steps: - checkout: self # self represents the repo where the initial Pipelines YAML file was found clean: true # whether to fetch clean each time @@ -38,11 +40,16 @@ jobs: submodules: recursive # set to 'true' for a single level of submodules or 'recursive' to get submodules of submodules persistCredentials: true # set to 'true' to leave the OAuth token in the Git config after the initial fetch - - ${{ if eq(variables['isMain'], true) }}: + # Setup the repo to be ready for release. This includes: + # - Autogenerating the next version number + # - Calling the approprate scripts that upstream React Native uses to prepare a release + # - Skipping the actual `git tag`, `git push`, and `npm publish steps as we do that here instead + + - ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}: - template: templates/apple-job-publish.yml parameters: build_type: nightly - - ${{ elseif eq(variables['isReleaseBranch'], true) }}: + - ${{ elseif endsWith(variables['Build.SourceBranchName'], '-stable') }}: - template: templates/apple-job-publish.yml parameters: build_type: release @@ -54,6 +61,8 @@ jobs: echo "Skipping publish for branch $(Build.SourceBranchName)" exit 1 + # Generate and publish the SBOM + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: 📒 Generate Manifest inputs: @@ -65,6 +74,42 @@ jobs: artifactName: SBom-RNGithubNpmJSPublish-$(System.JobAttempt) targetPath: $(System.DefaultWorkingDirectory)/_manifest + # Set the NPM dist-tag and do the actual NPM publish + + - bash: echo "##vso[task.setvariable variable=npmDistTag]latest" + displayName: Set dist-tag to latest + condition: eq(variables['Build.SourceBranchName'], variables.latestStableBranch) + + - bash: echo "##vso[task.setvariable variable=npmDistTag]canary" + displayName: Set dist-tag to canary + condition: eq(variables['Build.SourceBranchName'], 'main') + + - bash: echo "##vso[task.setvariable variable=npmDistTag]v${{variables['Build.SourceBranchName']}}" + displayName: Set dist-tag to v0.x-stable + condition: and(ne(variables['Build.SourceBranchName'], 'main'), ne(variables['Build.SourceBranchName'], variables.latestStableBranch)) + + - task: CmdLine@2 + displayName: Actual NPM Publish + inputs: + script: | + npm publish --tag $(npmDistTag) --registry https://registry.npmjs.org/ --//registry.npmjs.org/:_authToken=$(npmAuthToken) + + # Set the git tag and push the version update back to Github + + - template: templates/configure-git.yml + + - task: CmdLine@2 + displayName: 'Tag and push to Github' + inputs: + script: node .ado/gitTagRelease.js + env: + BUILD_STAGINGDIRECTORY: $(Build.StagingDirectory) + BUILD_SOURCEBRANCH: $(Build.SourceBranch) + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + githubAuthToken: $(githubAuthToken) + condition: and(succeeded(), ne(variables['Build.SourceBranchName'], 'main')) + + - job: RNMacOSInitNpmJSPublish displayName: NPM Publish react-native-macos-init pool: cxeiss-ubuntu-20-04-large @@ -83,7 +128,7 @@ jobs: - template: templates/apple-install-dependencies.yml - - template: templates/apple-release-setup.yml + - template: templates/configure-git.yml - task: CmdLine@2 displayName: Build react-native-macos-init diff --git a/.ado/templates/apple-job-publish.yml b/.ado/templates/apple-job-publish.yml index ba57a2c574cbaf..66b80dad087b9e 100644 --- a/.ado/templates/apple-job-publish.yml +++ b/.ado/templates/apple-job-publish.yml @@ -6,52 +6,36 @@ steps: - template: apple-install-dependencies.yml - # Only set up our release environment if we're not doing a dry run - - ${{ if ne( parameters['build_type'], 'dry-run') }}: - - template: apple-release-setup.yml - - - task: CmdLine@2 - displayName: Set build type to ${{parameters.build_type}} - inputs: - script: | - BUILD_TYPE=${{parameters.build_type}} - echo "Set build type: $BUILD_TYPE" - # Extra steps needed for *-stable releases - ${{ if eq( parameters['build_type'], 'release') }}: - - task: CmdLine@2 - displayName: Set next version - inputs: - script: | - VERSION=$(node .ado/get-next-semver-version.js) - echo "Set version: $VERSION" - - - task: CmdLine@2 - displayName: Set build type to ${{parameters.build_type}} - inputs: - script: | - BUILD_TYPE=release - echo "Set build type: $BUILD_TYPE" - - task: CmdLine@2 displayName: Set latest tag inputs: script: | LATEST=true echo "Set latest to: $LATEST" + condition: eq(variables['Build.SourceBranchName'], variables.latestStableBranch) + # Note, This won't do the actual `git tag` and `git push` as we're doing a dry run. + # We do that as a separate step in `.ado/publish.yml`. - task: CmdLine@2 - displayName: Prepare and tag package for release + displayName: Prepare package for release inputs: script: | + VERSION=$(node .ado/get-next-semver-version.js) if [[ -z "$VERSION" ]]; then VERSION=$(grep '"version"' package.json | cut -d '"' -f 4 | head -1) echo "Using the version from the package.json: $VERSION" fi - node ./scripts/prepare-package-for-release.js -v "$VERSION" -l $LATEST + node ./scripts/prepare-package-for-release.js -v "$VERSION" --dry-run + env: + # Map the corresponding variable since `prepare-package-for-release.js` depends on it. + CIRCLE_BRANCH: $(Build.SourceBranchName) + # Note: This won't actually publish to NPM as we've commented that bit out. + # We do that as a separate step in `.ado/publish.yml`. - task: CmdLine@2 - displayName: NPM Publish + displayName: Run publish-npm.js inputs: script: | node ./scripts/publish-npm.js --${{ parameters.build_type }} diff --git a/.ado/templates/apple-release-setup.yml b/.ado/templates/configure-git.yml similarity index 58% rename from .ado/templates/apple-release-setup.yml rename to .ado/templates/configure-git.yml index 1472d4b0a78c86..d70d8855fa0b99 100644 --- a/.ado/templates/apple-release-setup.yml +++ b/.ado/templates/configure-git.yml @@ -6,8 +6,5 @@ steps: git config --global user.email "53619745+rnbot@users.noreply.github.com" git config --global user.name "React-Native Bot" - - task: CmdLine@2 - displayName: Set NPM Auth Token - inputs: - script: | - echo "//registry.npmjs.org/:_authToken=${npmAuthToken}" > ~/.npmrc + - script: git remote set-url origin https://rnbot:$(githubAuthToken)@github.com/microsoft/react-native-macos + displayName: Set Permissions to push diff --git a/.ado/variables/vars.yml b/.ado/variables/vars.yml index 8296c044193edf..a5011344a084b4 100644 --- a/.ado/variables/vars.yml +++ b/.ado/variables/vars.yml @@ -2,5 +2,4 @@ variables: VmImageApple: internal-macos12 slice_name: 'Xcode_14.2' xcode_version: '/Applications/Xcode_14.2.app' - isMain: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')] - isReleaseBranch: $[endsWith(variables['Build.SourceBranchName'], '-stable')] \ No newline at end of file + latestStableBranch: '0.71-stable' diff --git a/.ado/versionUtils.js b/.ado/versionUtils.js new file mode 100644 index 00000000000000..89bb3f112d8794 --- /dev/null +++ b/.ado/versionUtils.js @@ -0,0 +1,26 @@ +// @ts-check +const fs = require("fs"); +const path = require("path"); +const semver = require('semver'); +const {execSync} = require('child_process'); + +const pkgJsonPath = path.resolve(__dirname, "../package.json"); +let publishBranchName = ''; +try { + publishBranchName = process.env.BUILD_SOURCEBRANCH.match(/refs\/heads\/(.*)/)[1]; +} catch (error) {} + +function gatherVersionInfo() { + let pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8")); + + let releaseVersion = pkgJson.version; + const branchVersionSuffix = (publishBranchName.match(/(fb.*merge)|(fabric)/) ? `-${publishBranchName}` : ''); + + return {pkgJson, releaseVersion, branchVersionSuffix}; +} + +module.exports = { + gatherVersionInfo, + publishBranchName, + pkgJsonPath, +} \ No newline at end of file diff --git a/scripts/prepare-package-for-release.js b/scripts/prepare-package-for-release.js index 43a74d85106b1a..e446ae14342375 100755 --- a/scripts/prepare-package-for-release.js +++ b/scripts/prepare-package-for-release.js @@ -22,7 +22,6 @@ const {echo, exec, exit} = require('shelljs'); const yargs = require('yargs'); const {isReleaseBranch, parseVersion} = require('./version-utils'); const {failIfTagExists} = require('./release-utils'); -const {getBranchName} = require('./scm-utils'); // [macOS] const argv = yargs .option('r', { @@ -45,7 +44,7 @@ const argv = yargs default: false, }).argv; -const branch = getBranchName(); // [macOS] Don't rely on CircleCI environment variables. +const branch = process.env.CIRCLE_BRANCH; const remote = argv.remote; const releaseVersion = argv.toVersion; const isLatest = argv.latest; diff --git a/scripts/publish-npm.js b/scripts/publish-npm.js index b5bc7ac5cef807..a3211953091d85 100755 --- a/scripts/publish-npm.js +++ b/scripts/publish-npm.js @@ -52,7 +52,7 @@ const os = require('os'); const path = require('path'); const yargs = require('yargs'); -const buildTag = exec('git tag --points-at HEAD'); // [macOS] Don't rely on CircleCI environment variables. +// const buildTag = process.env.CIRCLE_TAG; // [macOS] We can't use the CircleCI build tag. const otp = process.env.NPM_CONFIG_OTP; const tmpPublishingFolder = fs.mkdtempSync( path.join(os.tmpdir(), 'rn-publish-'), @@ -96,6 +96,15 @@ saveFilesToRestore(tmpPublishingFolder); const currentCommit = getCurrentCommit(); const shortCommit = currentCommit.slice(0, 9); +// [macOS] Function to get our version from package.json instead of the CircleCI build tag. +function getPkgJsonVersion() { + const pkgJsonPath = path.resolve(__dirname, '../package.json'); + const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); + const pkgJsonVersion = pkgJson.version; + return pkgJsonVersion; +} +// macOS] + const rawVersion = // 0.0.0 triggers issues with cocoapods for codegen when building template project. dryRunBuild @@ -104,7 +113,7 @@ const rawVersion = nightlyBuild ? '0.0.0' : // For pre-release and stable releases, we use the git tag of the version we're releasing (set in set-rn-version) - buildTag.substring(0, buildTag.indexOf('-microsft')); // [macOS] Strip off the "-microsoft" suffix from the tag name. + getPkgJsonVersion(); // [macOS] We can't use the CircleCI build tag, so we use the version argument instead. let version, major, @@ -170,6 +179,7 @@ const isLatest = exitIfNotOnGit( publishAndroidArtifactsToMaven(releaseVersion, nightlyBuild); macOS] */ +/* [macOS Comment the NPM publish out as we do this separately const releaseBranch = `${major}.${minor}-stable`; // Set the right tag for nightly and prerelease builds @@ -186,10 +196,11 @@ const tagFlag = nightlyBuild // use otp from envvars if available const otpFlag = otp ? `--otp ${otp}` : ''; -if (exec(`npm publish ${tagFlag} ${otpFlag}`).code) { +if (exec(`npm publish ${tagFlag} ${otpFlag}`, {cwd: RN_PACKAGE_DIR}).code) { echo('Failed to publish package to npm'); exit(1); } else { echo(`Published to npm ${releaseVersion}`); exit(0); } +macOS] */ diff --git a/scripts/release-utils.js b/scripts/release-utils.js index 7e03e212312d7a..9cb69c4c5ea1d9 100644 --- a/scripts/release-utils.js +++ b/scripts/release-utils.js @@ -17,9 +17,7 @@ const {createHermesPrebuiltArtifactsTarball} = require('./hermes/hermes-utils'); function saveFilesToRestore(tmpPublishingFolder) { const filesToSaveAndRestore = [ 'template/Gemfile', - 'template/_ruby-version', 'template/package.json', - '.ruby-version', 'Gemfile.lock', 'Gemfile', 'package.json',