Skip to content
Merged
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
153 changes: 113 additions & 40 deletions .github/workflows/release-js.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
name: Release Solid Layouts JavaScript packages

# A version bump is the release. Landing a bump on master publishes that
# package, and a push that changes no version does nothing, so the number in
# package.json stays the thing a human decided rather than something a bot
# inferred. Tags still work for an explicit or a repeat release.
on:
push:
branches: [master]
tags:
- "solid-layouts-v*"
- "rsbuild-plugin-solid-layouts-v*"
Expand All @@ -21,79 +26,147 @@ permissions:
contents: read

jobs:
package:
decide:
name: decide
runs-on: ubicloud-standard-2
outputs:
packages: ${{ steps.decide.outputs.packages }}
publish: ${{ steps.decide.outputs.publish }}
any: ${{ steps.decide.outputs.any }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: 22

- name: Select package
id: package
shell: bash
- name: Decide which packages this push releases
id: decide
env:
REQUESTED_PACKAGE: ${{ inputs.package }}
run: |
selector="${REQUESTED_PACKAGE:-$GITHUB_REF_NAME}"
case "$selector" in
solid-layouts|solid-layouts-v*)
echo "directory=packages/solid-layouts" >> "$GITHUB_OUTPUT"
echo "tag-prefix=solid-layouts" >> "$GITHUB_OUTPUT"
;;
rsbuild-plugin-solid-layouts|rsbuild-plugin-solid-layouts-v*)
echo "directory=packages/rsbuild-plugin-solid-layouts" >> "$GITHUB_OUTPUT"
echo "tag-prefix=rsbuild-plugin-solid-layouts" >> "$GITHUB_OUTPUT"
;;
pathscale-test-ui|pathscale-test-ui-v*)
echo "directory=Test-UI/bundle" >> "$GITHUB_OUTPUT"
echo "tag-prefix=pathscale-test-ui" >> "$GITHUB_OUTPUT"
;;
*) exit 1 ;;
esac

- name: Match release tag to package version
if: github.event_name == 'push'
working-directory: ${{ steps.package.outputs.directory }}
env:
TAG_PREFIX: ${{ steps.package.outputs.tag-prefix }}
run: |
version="$(node -p "require('./package.json').version")"
expected="$TAG_PREFIX-v$version"
if [ "$GITHUB_REF_NAME" != "$expected" ]; then
echo "expected tag $expected, received $GITHUB_REF_NAME"
exit 1
fi
node --input-type=commonjs -e '
const { execFileSync } = require("node:child_process");
const { appendFileSync, readFileSync } = require("node:fs");

// `auto` marks the packages a version bump on master releases.
// pathscale-test-ui is a private fixture, so it ships only when a
// tag or a dispatch names it.
const known = [
{ name: "solid-layouts", directory: "packages/solid-layouts", auto: true },
{ name: "rsbuild-plugin-solid-layouts", directory: "packages/rsbuild-plugin-solid-layouts", auto: true },
{ name: "pathscale-test-ui", directory: "Test-UI/bundle", auto: false },
];
const versionOf = (p) =>
JSON.parse(readFileSync(`${p.directory}/package.json`, "utf8")).version;
const publishedNameOf = (p) =>
JSON.parse(readFileSync(`${p.directory}/package.json`, "utf8")).name;

const out = (key, value) => appendFileSync(process.env.GITHUB_OUTPUT, `${key}=${value}\n`);
const emit = (selected, publish) => {
out("packages", JSON.stringify(selected));
out("publish", String(publish));
out("any", String(selected.length > 0));
};

const refType = process.env.GITHUB_REF_TYPE;
const refName = process.env.GITHUB_REF_NAME;

if (refType === "tag") {
const match = known.find((p) => refName.startsWith(`${p.name}-v`));
if (!match) {
console.log(`::error::no package matches tag ${refName}`);
process.exit(1);
}
const version = versionOf(match);
const expected = `${match.name}-v${version}`;
if (refName !== expected) {
console.log(`::error::expected tag ${expected}, received ${refName}`);
process.exit(1);
}
emit([{ ...match, version }], true);
return;
}

if (process.env.GITHUB_EVENT_NAME === "workflow_dispatch") {
const match = known.find((p) => p.name === process.env.REQUESTED_PACKAGE);
if (!match) {
console.log(`::error::unknown package ${process.env.REQUESTED_PACKAGE}`);
process.exit(1);
}
emit([{ ...match, version: versionOf(match) }], false);
return;
}

// npm is the source of truth rather than the diff, so a rerun or a
// publish that failed half way is safe to repeat, and a merge that
// touched no version costs nothing.
const selected = [];
for (const p of known.filter((p) => p.auto)) {
const version = versionOf(p);
const published = publishedNameOf(p);
let onNpm = true;
try {
execFileSync("npm", ["view", `${published}@${version}`, "version"], { stdio: "ignore" });
} catch {
onNpm = false;
}
if (onNpm) {
console.log(`::notice::${published} ${version} is already on npm`);
} else {
console.log(`::notice::releasing ${published} ${version}`);
selected.push({ ...p, version });
}
}
emit(selected, selected.length > 0);
'

package:
needs: decide
if: needs.decide.outputs.any == 'true'
runs-on: ubicloud-standard-2
strategy:
fail-fast: false
matrix:
package: ${{ fromJSON(needs.decide.outputs.packages) }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: 22

- name: Install runtime build dependencies
if: steps.package.outputs.directory == 'packages/solid-layouts'
if: matrix.package.directory == 'packages/solid-layouts'
working-directory: packages/solid-layouts
run: bun install --frozen-lockfile

- name: Pack
working-directory: ${{ steps.package.outputs.directory }}
working-directory: ${{ matrix.package.directory }}
run: |
mkdir -p "$RUNNER_TEMP/npm-package"
npm pack --pack-destination "$RUNNER_TEMP/npm-package"

- uses: actions/upload-artifact@v4
with:
name: npm-package
name: npm-package-${{ matrix.package.name }}
path: ${{ runner.temp }}/npm-package/*.tgz
if-no-files-found: error

publish:
needs: package
if: github.event_name == 'push'
needs: [decide, package]
if: needs.decide.outputs.publish == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
package: ${{ fromJSON(needs.decide.outputs.packages) }}
permissions:
contents: read
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: npm-package
name: npm-package-${{ matrix.package.name }}
path: npm-package

- uses: actions/setup-node@v4
Expand Down
80 changes: 66 additions & 14 deletions .github/workflows/release-oxc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ name: Release solid-layouts-oxc
# carry a binary per platform, and a macOS laptop can build exactly one of
# them. Publishing from here rather than locally is what stops a version being
# burned on an artifact that fails on first install under Linux.
#
# A version bump is the release. Landing a bump on master publishes it, and a
# push that does not change the version does nothing, so the number in
# package.json stays the thing a human decided rather than something a bot
# inferred. Tags still work for an explicit or a repeat release.
on:
push:
branches: [master]
tags:
- "solid-layouts-oxc-v*"
workflow_dispatch:
Expand All @@ -14,8 +20,65 @@ permissions:
contents: read

jobs:
decide:
name: decide
runs-on: ubicloud-standard-2
outputs:
build: ${{ steps.decide.outputs.build }}
publish: ${{ steps.decide.outputs.publish }}
version: ${{ steps.decide.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22

- name: Decide whether this push is a release
id: decide
working-directory: packages/solid-layouts-oxc
run: |
version="$(node -p "require('./package.json').version")"
echo "version=$version" >> "$GITHUB_OUTPUT"

# A tag names the version it releases, and disagreeing with
# package.json is a mistake worth failing on rather than guessing at.
if [ "$GITHUB_REF_TYPE" = tag ]; then
expected="solid-layouts-oxc-v$version"
if [ "$GITHUB_REF_NAME" != "$expected" ]; then
echo "expected tag $expected, received $GITHUB_REF_NAME"
exit 1
fi
echo "build=true" >> "$GITHUB_OUTPUT"
echo "publish=true" >> "$GITHUB_OUTPUT"
exit 0
fi

# Dispatch is the bootstrap path: build the artifact, publish nothing.
if [ "$GITHUB_EVENT_NAME" = workflow_dispatch ]; then
echo "build=true" >> "$GITHUB_OUTPUT"
echo "publish=false" >> "$GITHUB_OUTPUT"
echo "::notice::building $version without publishing"
exit 0
fi

# npm is the source of truth rather than the diff, so a rerun or a
# publish that failed half way is safe to repeat and a merge that
# touched no version costs nothing.
if npm view "solid-layouts-oxc@$version" version >/dev/null 2>&1; then
echo "build=false" >> "$GITHUB_OUTPUT"
echo "publish=false" >> "$GITHUB_OUTPUT"
echo "::notice::solid-layouts-oxc $version is already on npm"
exit 0
fi

echo "build=true" >> "$GITHUB_OUTPUT"
echo "publish=true" >> "$GITHUB_OUTPUT"
echo "::notice::releasing solid-layouts-oxc $version"

build:
name: ${{ matrix.target }}
needs: decide
if: needs.decide.outputs.build == 'true'
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
Expand Down Expand Up @@ -79,7 +142,7 @@ jobs:
path: packages/solid-layouts-oxc/*.node

package:
needs: build
needs: [decide, build]
runs-on: ubicloud-standard-2
steps:
- uses: actions/checkout@v4
Expand All @@ -102,17 +165,6 @@ jobs:
exit 1
fi

- name: Match release tag to package version
if: github.event_name == 'push'
working-directory: packages/solid-layouts-oxc
run: |
version="$(node -p "require('./package.json').version")"
expected="solid-layouts-oxc-v$version"
if [ "$GITHUB_REF_NAME" != "$expected" ]; then
echo "expected tag $expected, received $GITHUB_REF_NAME"
exit 1
fi

- name: Pack
working-directory: packages/solid-layouts-oxc
run: |
Expand All @@ -126,8 +178,8 @@ jobs:
if-no-files-found: error

publish:
needs: package
if: github.event_name == 'push'
needs: [decide, package]
if: needs.decide.outputs.publish == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
19 changes: 18 additions & 1 deletion docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,24 @@ automation tokens from bypassing the OIDC-only release path.

## Normal releases

Every release tag must exactly match the package version:
**A version bump is the release.** Land a commit on `master` that changes a
package's `version`, and that package publishes. A push that changes no version
publishes nothing and skips its build entirely, so the number in `package.json`
stays the thing a human decided rather than something a bot inferred from commit
messages.

npm is what the workflow checks, not the diff. It publishes when the version on
`master` is not on the registry, which means a rerun, or a publish that failed
after packing, is safe to repeat, and a merge that touched no version costs
nothing.

`@pathscale/test-ui` is excluded: it is a private fixture and ships only when a
tag or a dispatch names it.

### Releasing by tag

Tags still work, for an explicit release or to repeat one. Every release tag must
exactly match the package version:

| Package | Workflow | Tag |
| --- | --- | --- |
Expand Down
Loading