Repository: vercel/next.js
Pull request: [release] fix: correctly set tag during publish

### Why?

The `process.env.RELEASE_TYPE` handling was set incorrectly. It should've been set to the "trigger" workflow, but instead, it was set to the "publish" workflow. Therefore, the "publish" workflow needs to know which dist tag to set for publish.

Changed files:
filename: .github/workflows/build_and_deploy.yml
patch: @@ -611,7 +611,6 @@ jobs:
         env:
           GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
           NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
-          RELEASE_TYPE: ${{ github.event.inputs.releaseType }}
 
       # Add label to verify the PR is created from this workflow.
       - name: Add label to PR

filename: .github/workflows/trigger_release_new.yml
patch: @@ -113,3 +113,4 @@ jobs:
           version: pnpm ci:version
         env:
           GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
+          RELEASE_TYPE: ${{ github.event.inputs.releaseType }}

filename: scripts/release/publish-npm.ts
patch: @@ -1,17 +1,97 @@
 import execa from 'execa'
+import semver from 'semver'
+import { existsSync } from 'fs'
+import { join } from 'path'
+import { readdir, readFile } from 'fs/promises'
+
+async function fetchTagsFromRegistry(packageName: string) {
+  const res = await fetch(
+    `https://registry.npmjs.org/-/package/${packageName}/dist-tags`
+  )
+  const tags = await res.json()
+  return tags
+}
+
+async function getTag({
+  name,
+  version,
+}: {
+  name: string
+  version: string
+}): Promise<string> {
+  const preConfigPath = join(process.cwd(), '.changeset', 'pre.json')
+
+  if (existsSync(preConfigPath)) {
+    const { tag, mode } = JSON.parse(await readFile(preConfigPath, 'utf-8'))
+    if (mode === 'pre') {
+      if (!version.includes('-')) {
+        throw new Error(
+          `The changeset is in pre mode, but the version of "${name}@${version}" is not prerelease. It is likely a bug from versioning the packages.`
+        )
+      }
+
+      return tag
+    }
+  }
+
+  if (version.includes('-')) {
+    throw new Error(
+      `The changeset is not in pre mode, but the version of "${name}@${version}" is prerelease. It is likely a bug from versioning the packages.`
+    )
+  }
+
+  return 'latest'
+}
 
 async function publishNpm() {
-  const releaseType = process.env.RELEASE_TYPE
-  const tag =
-    releaseType === 'canary'
-      ? 'canary'
-      : releaseType === 'release-candidate'
-        ? 'rc'
-        : 'latest'
-
-  await execa('pnpm', ['changeset', 'publish', '--tag', tag], {
-    stdio: 'inherit',
+  if (!process.env.NPM_TOKEN) {
+    throw new Error('NPM_TOKEN is not set')
+  }
+
+  const packagesDir = join(process.cwd(), 'packages')
+  const packageDirs = await readdir(packagesDir, {
+    withFileTypes: true,
   })
+
+  for (const packageDir of packageDirs) {
+    if (!packageDir.isDirectory()) {
+      continue
+    }
+
+    const pkgJson = JSON.parse(
+      await readFile(
+        join(process.cwd(), 'packages', packageDir.name, 'package.json'),
+        'utf-8'
+      )
+    )
+
+    if (pkgJson.private) {
+      continue
+    }
+
+    const tags = await fetchTagsFromRegistry(pkgJson.name)
+    // If the current version is already published in the
+    // registry, skip the process.
+    if (semver.eq(pkgJson.version, tags.latest)) {
+      console.log(
+        `Skipping ${pkgJson.name}@${pkgJson.version} because it is already published.`
+      )
+      continue
+    }
+
+    const tag = await getTag({
+      name: pkgJson.name,
+      version: pkgJson.version,
+    })
+
+    const packagePath = join(packagesDir, packageDir.name)
+    const args = ['publish', packagePath, '--tag', tag]
+
+    console.log(
+      `Running command: "pnpm ${args.join(' ')}" for ${pkgJson.name}@${pkgJson.version}`
+    )
+    await execa('pnpm', args, { stdio: 'inherit' })
+  }
 }
 
 publishNpm()