Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 121 additions & 10 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,19 @@ on:
workflow_dispatch:
inputs:
tag:
description: 'NPM dist-tag (e.g., latest, beta, snapshot)'
description: '1st-gen NPM dist-tag (e.g., next, latest). 2nd-gen always publishes as beta.'
required: false
default: 'next'
beta_2nd_gen:
description: 'Also cut a 2nd-gen beta (2.0.0-beta.N under the beta tag) when 2nd-gen changesets exist. Ignored for the latest tag.'
required: false
type: boolean
default: true
dry_run:
description: 'Dry run the 2nd-gen beta: build, version, and pack but use npm publish --dry-run (no registry writes).'
required: false
type: boolean
default: false
pull_request:
types: [labeled, synchronize]
push:
Expand All @@ -28,6 +38,7 @@ jobs:
outputs:
has_changesets: ${{ steps.check.outputs.has_changesets }}
has_1st_gen_changesets: ${{ steps.check.outputs.has_1st_gen_changesets }}
has_2nd_gen_changesets: ${{ steps.check.outputs.has_2nd_gen_changesets }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand All @@ -38,6 +49,7 @@ jobs:
if [ -z "$(ls -A .changeset/*.md 2>/dev/null | grep -v README)" ]; then
echo "has_changesets=false" >> $GITHUB_OUTPUT
echo "has_1st_gen_changesets=false" >> $GITHUB_OUTPUT
echo "has_2nd_gen_changesets=false" >> $GITHUB_OUTPUT
echo "No changesets found - skipping publish"
else
echo "has_changesets=true" >> $GITHUB_OUTPUT
Expand All @@ -47,23 +59,32 @@ jobs:
# Check if any changeset mentions 1st-gen packages
# 1st-gen packages are @spectrum-web-components/* except core
HAS_1ST_GEN=false
HAS_2ND_GEN=false
for file in .changeset/*.md; do
if [ "$(basename "$file")" != "README.md" ]; then
PACKAGES=$(grep -oP "@spectrum-web-components/[a-z-]+" "$file")
NON_CORE=$(echo "$PACKAGES" | grep -v "^@spectrum-web-components/core$")
if [ -n "$NON_CORE" ]; then
HAS_1ST_GEN=true
break
fi
if grep -qE "@spectrum-web-components/core|@adobe/spectrum-wc" "$file"; then

@Rajdeepc Rajdeepc Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2nd-gen changeset detection

has_2nd_gen_changesets is set when any changeset file mentions @spectrum-web-components/core or @adobe/spectrum-wc. A single mixed changeset (1st-gen + 2nd-gen packages) still enables the beta path.

Need confirmation here on this model: one snapshot version pass consumes everything, but 2nd-gen only ships when this flag is true and the manual gate below fires.

HAS_2ND_GEN=true
fi
fi
done

echo "has_1st_gen_changesets=$HAS_1ST_GEN" >> $GITHUB_OUTPUT
echo "has_2nd_gen_changesets=$HAS_2ND_GEN" >> $GITHUB_OUTPUT
if [ "$HAS_1ST_GEN" = "true" ]; then
echo "Found 1st-gen changesets - React wrappers will be built and published"
else
echo "No 1st-gen changesets found - React wrappers will be skipped"
fi
if [ "$HAS_2ND_GEN" = "true" ]; then
echo "Found 2nd-gen changesets - 2nd-gen beta step can run when requested"
else
echo "No 2nd-gen changesets found - 2nd-gen beta step will be skipped"
fi
fi

publish:
Expand Down Expand Up @@ -95,6 +116,8 @@ jobs:
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_TAG: ${{ github.event.inputs.tag }}
INPUT_BETA_2ND_GEN: ${{ github.event.inputs.beta_2nd_gen }}
HAS_2ND_GEN_CHANGESETS: ${{ needs.check-changesets.outputs.has_2nd_gen_changesets }}
run: |
if [ "$EVENT_NAME" == "pull_request" ]; then
# PRs with the snapshot-release label always use snapshot-test
Expand All @@ -107,8 +130,31 @@ jobs:
WORKFLOW_TAG="next"
fi

# 2nd-gen (core + @adobe/spectrum-wc) never rides the 1st-gen next/latest
# train; it ships on its own beta cadence. Exclude it from the changeset
# publish for every real release (push + manual dispatch). PR snapshot-test
# runs still publish everything so 2nd-gen changes can be smoke-tested.
if [ "$EVENT_NAME" == "pull_request" ]; then
EXCLUDE_2ND_GEN="false"
else
EXCLUDE_2ND_GEN="true"

@Rajdeepc Rajdeepc Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exclude vs include 2nd-gen

exclude_2nd_gen=true for push + manual dispatch marks core/swc private so changeset publish skips them. PR snapshot-release runs set this to false so 2nd-gen can still be smoke-tested with everything published.

Should PR snapshot behavior should remain unchanged?

fi

# Cut 2.0.0-beta.N for 2nd-gen only on a deliberate manual dispatch when
# pending changesets mention core or @adobe/spectrum-wc. Never during a
# latest prod run or when there is nothing new for 2nd-gen to ship.
if [ "$EVENT_NAME" == "workflow_dispatch" ] && [ "$INPUT_BETA_2ND_GEN" != "false" ] && [ "$WORKFLOW_TAG" != "latest" ] && [ "$HAS_2ND_GEN_CHANGESETS" == "true" ]; then

@Rajdeepc Rajdeepc Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the main gate (read this first)

run_2nd_gen_beta is only true when all of these hold:

  1. workflow_dispatch (manual — not push to main)
  2. beta_2nd_gen input is not false
  3. tag ≠ latest
  4. has_2nd_gen_changesets == true

2nd-gen beta never goes out automatically on push.

RUN_2ND_GEN_BETA="true"
else
RUN_2ND_GEN_BETA="false"
fi

echo "tag=$WORKFLOW_TAG" >> $GITHUB_OUTPUT
echo "exclude_2nd_gen=$EXCLUDE_2ND_GEN" >> $GITHUB_OUTPUT
echo "run_2nd_gen_beta=$RUN_2ND_GEN_BETA" >> $GITHUB_OUTPUT
echo "Using npm tag: $WORKFLOW_TAG"
echo "Exclude 2nd-gen from 1st-gen publish: $EXCLUDE_2ND_GEN"
echo "Cut 2nd-gen beta this run: $RUN_2ND_GEN_BETA"

- name: Validate release tag
env:
Expand Down Expand Up @@ -171,14 +217,27 @@ jobs:
yarn install --refresh-lockfile
yarn build

- name: Publish all packages
- name: Exclude 2nd-gen from the 1st-gen publish
if: steps.extract-tag.outputs.exclude_2nd_gen == 'true'
run: |
set -euo pipefail
# 2nd-gen (core + @adobe/spectrum-wc) releases on its own beta cadence,
# never as next/latest. Marking them private makes `changeset publish`
# skip them; the dedicated 2nd-gen beta step publishes them afterwards.
# These edits are never committed (see the restore in later steps).
(cd 2nd-gen/packages/core && npm pkg set private=true --json)

@Rajdeepc Rajdeepc Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ephemeral private flag

This is the mechanism that keeps 2nd-gen off the next/latest train during changeset publish. The private=true edits are never committed — restored via git checkout -- on the latest commit step (and discarded on next manual runs).

Note: this step must run before changeset publish.

(cd 2nd-gen/packages/swc && npm pkg set private=true --json)

- name: Publish 1st-gen packages
if: steps.npm-auth.outcome == 'success'
env:
NODE_AUTH_TOKEN: ${{ secrets.ADOBE_BOT_NPM_TOKEN }}
TAG: ${{ steps.extract-tag.outputs.tag }}
run: |
# Changeset publishes all packages (1st-gen, core, and 2nd-gen)
# npm CLI automatically uses:
# Changeset publishes every non-private package. For real releases the
# 2nd-gen packages are marked private just above, so this publishes 1st-gen
# only; PR snapshot-test runs leave them public and publish everything.
# npm CLI selects auth automatically:
# - OIDC trusted publishing for 1st-gen packages (configured on npmjs.com)
# - Token authentication for 2nd-gen (from .npmrc)
if [ "$TAG" == "latest" ]; then
Expand All @@ -187,6 +246,44 @@ jobs:
yarn changeset publish --no-git-tag --tag $TAG
fi

- name: Prepare 2nd-gen beta release
id: prepare-2nd-gen-beta
if: >-

@Rajdeepc Rajdeepc Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double guard on beta prep

This step requires both run_2nd_gen_beta and has_2nd_gen_changesets. Redundant but intentional — even if the extract-tag logic changes, the changeset check is a second safety net.

The script assumes changeset version --snapshot already ran and wrote 0.x-next.* changelog headers.

steps.npm-auth.outcome == 'success' &&
steps.extract-tag.outputs.run_2nd_gen_beta == 'true' &&
needs.check-changesets.outputs.has_2nd_gen_changesets == 'true'
run: node scripts/main-2nd-gen-beta-release.js

- name: Rebuild after 2nd-gen beta versioning
if: steps.prepare-2nd-gen-beta.outcome == 'success'
run: |
yarn install --refresh-lockfile
yarn build

- name: Publish 2nd-gen beta (core + @adobe/spectrum-wc)
id: publish-2nd-gen
if: steps.prepare-2nd-gen-beta.outcome == 'success'
env:
NODE_AUTH_TOKEN: ${{ secrets.ADOBE_BOT_NPM_TOKEN }}
DRY_RUN: ${{ github.event.inputs.dry_run }}
BETA_VERSION: ${{ steps.prepare-2nd-gen-beta.outputs.beta }}
run: |
set -euo pipefail
echo "Publishing 2nd-gen beta: $BETA_VERSION"

PUBLISH_FLAGS="--tag beta --access public"
if [ "$DRY_RUN" == "true" ]; then

@Rajdeepc Rajdeepc Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dry_run scope

dry_run=true only adds --dry-run to the 2nd-gen npm publish calls. 1st-gen still publishes for real on the same dispatch unless we extend the input.

Will be removed once we confirm everything is working.

PUBLISH_FLAGS="$PUBLISH_FLAGS --dry-run"
echo "::notice::DRY RUN - packing only, nothing published to npm"
fi

# Changelog entries were written by changeset version --snapshot and
# retitled to 2.0.0-beta.N by scripts/main-2nd-gen-beta-release.js.
yarn workspace @spectrum-web-components/core pack --out /tmp/swc-core.tgz
yarn workspace @adobe/spectrum-wc pack --out /tmp/swc-wc.tgz
npm publish /tmp/swc-core.tgz $PUBLISH_FLAGS
npm publish /tmp/swc-wc.tgz $PUBLISH_FLAGS

- name: Build React wrappers
if: needs.check-changesets.outputs.has_1st_gen_changesets == 'true'
run: yarn workspace @spectrum-web-components/1st-gen build:react
Expand Down Expand Up @@ -249,6 +346,7 @@ jobs:
CORE_VERSION: ${{ steps.versions.outputs.core }}
BUNDLE_VERSION: ${{ steps.versions.outputs.bundle }}
SWC_VERSION: ${{ steps.versions.outputs.swc }}
BETA_VERSION: ${{ steps.prepare-2nd-gen-beta.outputs.beta }}
run: |
echo "## Publish summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
Expand Down Expand Up @@ -277,14 +375,24 @@ jobs:
echo "| **Commit** | \`${COMMIT_SHA}\` |" >> $GITHUB_STEP_SUMMARY
fi

# The 2nd-gen beta step (when it runs) publishes the real beta version;
# otherwise 2nd-gen was excluded from this run and only 1st-gen shipped.
if [ -n "$BETA_VERSION" ]; then

@Rajdeepc Rajdeepc Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

summary table accuracy

When beta did not run, core/swc versions are cleared from the summary so we don't show ephemeral snapshot versions (0.x-next.*) as if they were released.

When beta did run, BETA_VERSION from the prepare step overrides the earlier Capture released versions outputs.

CORE_VERSION="$BETA_VERSION"
SWC_VERSION="$BETA_VERSION"
else
CORE_VERSION=""
SWC_VERSION=""
fi

if [ -n "$CORE_VERSION" ] || [ -n "$BUNDLE_VERSION" ] || [ -n "$SWC_VERSION" ]; then
echo "## Versions released" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Generation | Package | Version |" >> $GITHUB_STEP_SUMMARY
echo "|------------|---------|---------|" >> $GITHUB_STEP_SUMMARY
[ -n "$CORE_VERSION" ] && echo "| Core | \`@spectrum-web-components/core\` | \`${CORE_VERSION}\` |" >> $GITHUB_STEP_SUMMARY
[ -n "$BUNDLE_VERSION" ] && echo "| 1st gen | \`@spectrum-web-components/bundle\` | \`${BUNDLE_VERSION}\` |" >> $GITHUB_STEP_SUMMARY
[ -n "$SWC_VERSION" ] && echo "| 2nd gen | \`@adobe/spectrum-wc\` | \`${SWC_VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Generation | Package | Version | Tag |" >> $GITHUB_STEP_SUMMARY
echo "|------------|---------|---------|-----|" >> $GITHUB_STEP_SUMMARY
[ -n "$CORE_VERSION" ] && echo "| Core | \`@spectrum-web-components/core\` | \`${CORE_VERSION}\` | \`beta\` |" >> $GITHUB_STEP_SUMMARY
[ -n "$BUNDLE_VERSION" ] && echo "| 1st gen | \`@spectrum-web-components/bundle\` | \`${BUNDLE_VERSION}\` | \`${TAG}\` |" >> $GITHUB_STEP_SUMMARY
[ -n "$SWC_VERSION" ] && echo "| 2nd gen | \`@adobe/spectrum-wc\` | \`${SWC_VERSION}\` | \`beta\` |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi

Expand All @@ -297,6 +405,9 @@ jobs:
- name: Commit and push changes
if: steps.extract-tag.outputs.tag == 'latest'
run: |
# 2nd-gen manifests may carry an ephemeral private flag / version bump from
# this run; restore them so only 1st-gen version + changelog changes land.
git checkout -- 2nd-gen/packages/core 2nd-gen/packages/swc || true

@Rajdeepc Rajdeepc Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

latest commit hygiene

Restores 2nd-gen manifests before commit so only 1st-gen version/changelog bumps land on main for prod releases.

Known separate issue on main: git pull --rebase here can fail when the working tree is dirty from changeset version (#10058). Not introduced by this PR, but affects latest runs — may need commit-before-rebase ordering as a follow-up.

git pull --rebase origin main
git add .
git commit --no-verify -m "chore: release packages #publish" || echo "No changes to commit"
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"postinstall": "husky || true",
"publish": "node ./scripts/publish.js",
"publish:snapshot": "node ./scripts/publish.js --tag snapshot",
"release:main-2nd-gen-beta": "node ./scripts/main-2nd-gen-beta-release.js",
"start": "run-p start:2nd-gen start:1st-gen",
"start:1st-gen": "yarn workspace @spectrum-web-components/1st-gen start",
"start:2nd-gen": "yarn workspace @spectrum-web-components/2nd-gen start",
Expand Down
147 changes: 147 additions & 0 deletions scripts/main-2nd-gen-beta-release.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env node

/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/**
* Prepares a 2nd-gen beta publish on main after `changeset version --snapshot`.
*
* The single-trigger publish workflow versions every package together for 1st-gen
* `next` snapshots. That step also consumes 2nd-gen changesets and writes their
* changelog entries, but under a `0.x-next.*` header. This script retitles those
* entries to `2.0.0-beta.N`, sets core + @adobe/spectrum-wc to that beta version,
* and refreshes the 2nd-gen version.ts file.
*
* Run only when pending changesets mention `@spectrum-web-components/core` or
* `@adobe/spectrum-wc`. The workflow gates on that check before calling this script.
*/

import { execSync } from 'child_process';
import { appendFileSync, readFileSync, writeFileSync } from 'fs';

const CORE_PKG = '2nd-gen/packages/core/package.json';
const SWC_PKG = '2nd-gen/packages/swc/package.json';
const CORE_CHANGELOG = '2nd-gen/packages/core/CHANGELOG.md';
const SWC_CHANGELOG = '2nd-gen/packages/swc/CHANGELOG.md';
const FIRST_GEN_VERSION_TS = '1st-gen/tools/base/src/version.ts';

function readJson(file) {
return JSON.parse(readFileSync(file, 'utf8'));
}

function writeJson(file, data) {
writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`);
}

function escapeRegex(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function getNextBetaVersion() {

@Rajdeepc Rajdeepc Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beta version selection

Queries npm for the highest existing 2.0.0-beta.N on @adobe/spectrum-wc and increments. Two concurrent manual dispatches could theoretically race to the same N (same risk exists on gen2-beta branch).

First combined run from main may also bundle many pending 2nd-gen changesets into one beta.N if they weren't already released via gen2-beta.

const raw = execSync(
'npm view @adobe/spectrum-wc versions --json 2>/dev/null',
{
encoding: 'utf8',
}
);
let versions = [];
try {
versions = JSON.parse(raw || '[]');
} catch {
versions = [];
}
if (!Array.isArray(versions)) {
versions = [versions];
}
const re = /^2\.0\.0-beta\.(\d+)$/;
const betaNumbers = versions
.map((version) => {
const match = re.exec(version);
return match ? Number(match[1]) : -1;
})
.filter((n) => n >= 0);
const nextN = (betaNumbers.length ? Math.max(...betaNumbers) : -1) + 1;
return `2.0.0-beta.${nextN}`;
}

function retitleChangelog(path, snapshotVersion, betaVersion) {
const header = `## ${snapshotVersion}`;
let content = readFileSync(path, 'utf8');
if (!content.includes(header)) {
throw new Error(
`${path} is missing the expected snapshot section "## ${snapshotVersion}". ` +
'Was changeset version run with 2nd-gen changesets present?'
);
}
content = content.replace(header, `## ${betaVersion}`);

@Rajdeepc Rajdeepc Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changelog retitle

Replaces the snapshot ## {version} header and @spectrum-web-components/core@{snapshot} dependency refs in changelog bodies. Uses a single .replace() on the header — sufficient today because changeset version adds one new section per run.

Fails loudly if the expected snapshot section is missing (guard at line 102 also checks for -next. in core version).

content = content.replace(
new RegExp(
`@spectrum-web-components/core@${escapeRegex(snapshotVersion)}`,
'g'
),
`@spectrum-web-components/core@${betaVersion}`
);
writeFileSync(path, content);
console.log(` ✓ Retitled ${path} -> ${betaVersion}`);
}

function main() {
const corePkg = readJson(CORE_PKG);
const swcPkg = readJson(SWC_PKG);
const snapshotVersion = corePkg.version;

if (!snapshotVersion.includes('-next.')) {
console.error(
`❌ Expected a snapshot version on core (got "${snapshotVersion}"). ` +
'Run changeset version --snapshot before this script.'
);
process.exit(1);
}
if (swcPkg.version !== snapshotVersion) {
console.error(
`❌ core (${snapshotVersion}) and swc (${swcPkg.version}) snapshot versions differ.`
);
process.exit(1);
}

const betaVersion = getNextBetaVersion();
console.log(`\nℹ️ Snapshot version: ${snapshotVersion}`);
console.log(`ℹ️ Next beta version: ${betaVersion}`);

retitleChangelog(CORE_CHANGELOG, snapshotVersion, betaVersion);
retitleChangelog(SWC_CHANGELOG, snapshotVersion, betaVersion);

corePkg.version = betaVersion;
corePkg.private = false;
swcPkg.version = betaVersion;
swcPkg.private = false;
swcPkg.dependencies['@spectrum-web-components/core'] = betaVersion;
writeJson(CORE_PKG, corePkg);
writeJson(SWC_PKG, swcPkg);
console.log(' ✓ Updated core + swc package.json');

execSync('node scripts/generate-versions.js', { stdio: 'inherit' });
execSync(`git checkout -- '${FIRST_GEN_VERSION_TS}'`, { stdio: 'inherit' });

@Rajdeepc Rajdeepc Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1st-gen version.ts must not change

generate-versions.js updates all version files, then we immediately git checkout -- the 1st-gen version.ts. Same pattern used on gen2-beta.

Note: on manual next runs, 2nd-gen changelog retitles and version bumps exist only in the CI workspace — they are not committed back to main (unlike latest which commits 1st-gen only). Confirm team is OK with beta changelog living in npm/registry but not on the branch until a future doc/process addresses it.

console.log(
` ✓ Refreshed 2nd-gen version.ts (left ${FIRST_GEN_VERSION_TS} on main)`
);

if (process.env.GITHUB_OUTPUT) {
appendFileSync(process.env.GITHUB_OUTPUT, `beta=${betaVersion}\n`);
}

console.log('\n✅ 2nd-gen beta prepared for publish:');
console.log(` @spectrum-web-components/core ${betaVersion}`);
console.log(` @adobe/spectrum-wc ${betaVersion}`);
}

main();
Loading