diff --git a/.ado/azure-pipelines.publish.yml b/.ado/azure-pipelines.publish.yml index 62541cb73d..7f0bd283da 100644 --- a/.ado/azure-pipelines.publish.yml +++ b/.ado/azure-pipelines.publish.yml @@ -78,14 +78,19 @@ extends: displayName: 'Pack all public packages' - script: | - echo "Packed tarballs:" ls -la $(System.DefaultWorkingDirectory)/_packed/ + if ls $(System.DefaultWorkingDirectory)/_packed/*.tgz > /dev/null 2>&1; then + echo "##vso[task.setvariable variable=hasTarballs;isOutput=true]true" + else + echo "##vso[task.setvariable variable=hasTarballs;isOutput=true]false" + fi + name: check displayName: 'List packed tarballs' - stage: Publish displayName: Publish to NPM dependsOn: Build - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'), not(${{ parameters.skipNpmPublish }})) + condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'), not(${{ parameters.skipNpmPublish }}), eq(stageDependencies.Build.BuildAndPack.outputs['check.hasTarballs'], 'true')) jobs: - job: PublishPackages displayName: Publish NPM Packages diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 20b1655c9b..6cc7ec073c 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -301,10 +301,12 @@ jobs: - name: Validate changesets run: yarn change:check - publish-dry-run: - name: NPM Publish Dry Run + publish-dry-run-pack: + name: NPM Publish Dry Run — Pack runs-on: ubuntu-latest timeout-minutes: 60 + outputs: + has-tarballs: ${{ steps.check.outputs.has-tarballs }} steps: - name: Checkout uses: actions/checkout@v4 @@ -323,6 +325,47 @@ jobs: - name: Pack packages run: yarn lage pack --verbose --grouped + - name: List packed tarballs + id: check + run: | + ls -la _packed/ + if ls _packed/*.tgz > /dev/null 2>&1; then + echo "has-tarballs=true" >> $GITHUB_OUTPUT + else + echo "has-tarballs=false" >> $GITHUB_OUTPUT + fi + + - name: Upload packed tarballs + if: steps.check.outputs.has-tarballs == 'true' + uses: actions/upload-artifact@v4 + with: + name: packed-tarballs-dry-run + path: _packed/ + + publish-dry-run: + name: NPM Publish Dry Run — Publish + runs-on: ubuntu-latest + timeout-minutes: 60 + needs: publish-dry-run-pack + if: needs.publish-dry-run-pack.outputs.has-tarballs == 'true' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up toolchain + uses: microsoft/react-native-test-app/.github/actions/setup-toolchain@5.0.14 + with: + node-version: 22 + + - name: Install dependencies + run: yarn + + - name: Download packed tarballs + uses: actions/download-artifact@v4 + with: + name: packed-tarballs-dry-run + path: _packed/ + - name: Simulate publish run: yarn lage publish-dry-run --verbose --grouped @@ -357,6 +400,7 @@ jobs: - windows - win32 - check-changesets + - publish-dry-run-pack - publish-dry-run - test-links steps: diff --git a/scripts/src/worker/pack.mts b/scripts/src/worker/pack.mts index 00e9890e3c..c3dd9c0352 100644 --- a/scripts/src/worker/pack.mts +++ b/scripts/src/worker/pack.mts @@ -27,6 +27,13 @@ export const run: WorkerRunnerFunction = async ({ target }) => { const stagingDir = resolve(outputDir); await fs.mkdirp(stagingDir); + // Skip if this version is already published + const result = await $`npm view ${pkg.name}@${pkg.version} version`.nothrow().quiet(); + if (result.exitCode === 0) { + console.log(`Skipping ${pkg.name}@${pkg.version} — already published`); + return; + } + // Build a safe filename: @fluentui-react-native/button@1.0.0 -> fluentui-react-native-button-1.0.0.tgz const safeName = (pkg.name as string).replace(/@/g, '').replace(/\//g, '-'); const tgzFilename = `${safeName}-${pkg.version}.tgz`; diff --git a/scripts/src/worker/publish.mts b/scripts/src/worker/publish.mts index 2ffa5ff92a..260e10dd10 100644 --- a/scripts/src/worker/publish.mts +++ b/scripts/src/worker/publish.mts @@ -38,17 +38,15 @@ export const run: WorkerRunnerFunction = async ({ target }) => { const dryRun = target.options?.dryRun ?? false; const outputDir = target.options?.outputDir as string | undefined; - - // If an outputDir is configured, look for a pre-packed tarball const tarball = outputDir ? await findTarball(pkg, outputDir) : undefined; - if (tarball) { - // yarn npm publish doesn't support tarballs, so use npm directly - const args = ['publish', tarball, '--access', 'public', ...(dryRun ? ['--dry-run'] : [])]; - await $({ cwd: target.cwd, verbose: true })`npm ${args}`; - } else { - // No tarball found — publish from source (local dev / dry-run) - const args = ['--tolerate-republish', ...(dryRun ? ['--dry-run'] : [])]; - await $({ cwd: target.cwd, verbose: true })`yarn npm publish ${args}`; + if (!tarball) { + // No tarball means pack skipped this package (already published) — nothing to do + console.log(`Skipping ${pkg.name}@${pkg.version} — no tarball found`); + return; } + + // yarn npm publish doesn't support tarballs, so use npm directly + const args = ['publish', tarball, '--access', 'public', ...(dryRun ? ['--dry-run'] : [])]; + await $({ cwd: target.cwd, verbose: true })`npm ${args}`; };