Skip to content
Open
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
148 changes: 148 additions & 0 deletions .github/workflows/publish-base-ui.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
name: publish-base-ui

# Publishes libs/base-ui to GitHub Packages as @unownhash/rotom-base-ui.
#
# Before this, the only way to reuse the library outside the repository was to
# unpack a source tarball at a pinned commit. That gave no version history, no
# integrity check, and no way to see which revision was in use without reading
# a stamp file.
#
# Two ways in, deliberately:
#
# - A base-ui-v<version> tag publishes that version under the "latest" tag.
# The tag has to agree with package.json, so a release cannot be labelled
# with a version the package will not report.
# - A push to main that touches the library publishes a prerelease under the
# "main" tag, versioned <version>-main.<short sha>. This is what replaces
# pinning a commit: a consumer can track main without waiting for a
# release and still record exactly what it installed.
#
# No paths: filter on the trigger. A paths filter applies to tag pushes too,
# and tags usually land on a commit that changed nothing under libs/base-ui --
# which would silently skip the release the tag was created to make. The
# branch case is gated inside the job instead, where it can be spelled out.

on:
push:
branches: [main]
tags:
- 'base-ui-v*'
workflow_dispatch:

concurrency:
group: publish-base-ui-${{ github.ref }}
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
# Two commits, so a branch push can diff against its predecessor.
fetch-depth: 2

- name: Decide whether to publish
id: gate
env:
REF_TYPE: ${{ github.ref_type }}
EVENT_NAME: ${{ github.event_name }}
run: |
set -eu
if [ "$REF_TYPE" = 'tag' ] || [ "$EVENT_NAME" = 'workflow_dispatch' ]; then
echo "publish=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# Branch push: only when the library itself moved. Everything else on
# main -- Go changes, workflow edits, docs -- leaves the published
# package alone.
if git diff --quiet HEAD^ HEAD -- libs/base-ui; then
echo "libs/base-ui unchanged in this push; nothing to publish."
echo "publish=false" >> "$GITHUB_OUTPUT"
else
echo "publish=true" >> "$GITHUB_OUTPUT"
fi

- name: Setup Bun
if: steps.gate.outputs.publish == 'true'
uses: oven-sh/setup-bun@v2

- name: Install node dependencies
if: steps.gate.outputs.publish == 'true'
run: bun install --frozen-lockfile

# The package ships TypeScript source, so consumers compile it rather
# than importing a prebuilt bundle. That makes the application build the
# only thing standing between a broken library and a broken consumer --
# run it before publishing, not after someone installs it.
- name: Build the UI against the library
if: steps.gate.outputs.publish == 'true'
run: bun run build

- name: Lint
if: steps.gate.outputs.publish == 'true'
run: bun run lint

- name: Setup Node for publishing
if: steps.gate.outputs.publish == 'true'
uses: actions/setup-node@v5
with:
node-version: '22'
registry-url: 'https://npm.pkg.github.com'
scope: '@unownhash'

- name: Resolve the version to publish
id: version
if: steps.gate.outputs.publish == 'true'
working-directory: libs/base-ui
env:
REF_TYPE: ${{ github.ref_type }}
REF_NAME: ${{ github.ref_name }}
run: |
set -eu
pkg=$(node -p "require('./package.json').version")

if [ "$REF_TYPE" = 'tag' ]; then
tag_version="${REF_NAME#base-ui-v}"
if [ "$tag_version" != "$pkg" ]; then
echo "::error::tag '${REF_NAME}' implies version '${tag_version}', but libs/base-ui/package.json says '${pkg}'. Bump the manifest or retag."
exit 1
fi
printf 'value=%s\ndist_tag=latest\n' "$pkg" >> "$GITHUB_OUTPUT"
else
printf 'value=%s-main.%s\ndist_tag=main\n' "$pkg" "$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
fi

# npm version rather than editing the manifest by hand, so an invalid
# version fails here instead of producing a package nobody can install.
- name: Stamp the prerelease version
if: steps.gate.outputs.publish == 'true' && github.ref_type != 'tag'
working-directory: libs/base-ui
env:
VERSION: ${{ steps.version.outputs.value }}
run: npm version "$VERSION" --no-git-tag-version --allow-same-version

- name: Publish to GitHub Packages
if: steps.gate.outputs.publish == 'true'
working-directory: libs/base-ui
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DIST_TAG: ${{ steps.version.outputs.dist_tag }}
run: npm publish --tag "$DIST_TAG"

- name: Summarise
if: steps.gate.outputs.publish == 'true'
env:
VERSION: ${{ steps.version.outputs.value }}
DIST_TAG: ${{ steps.version.outputs.dist_tag }}
run: |
{
printf '### Published\n\n'
printf '```\n@unownhash/rotom-base-ui@%s\ndist-tag: %s\n```\n\n' "$VERSION" "$DIST_TAG"
printf 'Install with:\n\n'
printf '```bash\nbun add @unownhash/rotom-base-ui@%s\n```\n' "$DIST_TAG"
} >> "$GITHUB_STEP_SUMMARY"
2 changes: 1 addition & 1 deletion apps/rotom-ng-ui/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
TooltipProvider,
useConfig,
WorkersPage,
} from "@rotom-ng/base-ui";
} from "@unownhash/rotom-base-ui";
import { QueryClientProvider } from "@tanstack/react-query";
import { useMemo } from "react";
import { Navigate, Route, Routes } from "react-router";
Expand Down
Binary file modified apps/rotom-ng-ui/src/assets/rotom-ng.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions apps/rotom-ng-ui/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import "@rotom-ng/base-ui/styles/globals.css";
import "./styles.css";

import { StrictMode } from "react";
import * as ReactDOM from "react-dom/client";
Expand All @@ -9,7 +9,7 @@ import { App } from "./app/app";
async function enableMocks() {
if (!import.meta.env.DEV) return;
if (import.meta.env.VITE_MOCK !== "true") return;
const { worker } = await import("@rotom-ng/base-ui/mocks");
const { worker } = await import("@unownhash/rotom-base-ui/mocks");
await worker.start({ onUnhandledRequest: "bypass" });
}

Expand Down
12 changes: 12 additions & 0 deletions apps/rotom-ng-ui/src/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* The application's stylesheet.
*
* The shared library's stylesheet carries the theme, the Tailwind import, and
* an @source for its own components. It deliberately does not reach up into
* this application's tree: once base-ui is installed from a registry rather
* than read out of the workspace, a relative path out of the package stops
* resolving and every class used only here would be dropped from the build.
*
* So each side scans itself. This @source is relative to this file. */
@import "@unownhash/rotom-base-ui/styles/globals.css";

@source "./**/*.{ts,tsx}";
4 changes: 2 additions & 2 deletions apps/rotom-ng-ui/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"baseUrl": ".",
"paths": {
"@/*": ["../../libs/base-ui/src/*"],
"@rotom-ng/base-ui": ["../../libs/base-ui/src/index.ts"],
"@rotom-ng/base-ui/*": ["../../libs/base-ui/src/*"]
"@unownhash/rotom-base-ui": ["../../libs/base-ui/src/index.ts"],
"@unownhash/rotom-base-ui/*": ["../../libs/base-ui/src/*"]
}
},
"files": [],
Expand Down
4 changes: 2 additions & 2 deletions apps/rotom-ng-ui/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"baseUrl": ".",
"paths": {
"@/*": ["../../libs/base-ui/src/*"],
"@rotom-ng/base-ui": ["../../libs/base-ui/src/index.ts"],
"@rotom-ng/base-ui/*": ["../../libs/base-ui/src/*"]
"@unownhash/rotom-base-ui": ["../../libs/base-ui/src/index.ts"],
"@unownhash/rotom-base-ui/*": ["../../libs/base-ui/src/*"]
}
},
"files": [],
Expand Down
31 changes: 26 additions & 5 deletions apps/rotom-ng-ui/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,32 @@ export default defineConfig({
cacheDir: '../../node_modules/.vite/apps/rotom-ng-ui',
plugins: [react(), tailwindcss(), nxViteTsPaths()],
resolve: {
alias: {
'@': path.resolve(__dirname, '../../libs/base-ui/src'),
react: path.resolve(__dirname, '../../node_modules/react'),
'react-dom': path.resolve(__dirname, '../../node_modules/react-dom'),
},
// Array form, because order matters: the bare '@' alias is a prefix of
// '@unownhash/...' and would otherwise claim it first. Listing the
// package name ahead of it keeps the workspace resolving base-ui by the
// same specifier a consumer installing it from the registry would use --
// for stylesheets as well as modules, which the tsconfig paths plugin
// does not cover.
alias: [
{
find: /^@unownhash\/rotom-base-ui$/,
replacement: path.resolve(__dirname, '../../libs/base-ui/src/index.ts'),
},
{
find: /^@unownhash\/rotom-base-ui\//,
replacement: path.resolve(__dirname, '../../libs/base-ui/src') + '/',
},
{ find: /^@\//, replacement: path.resolve(__dirname, '../../libs/base-ui/src') + '/' },
// Both the bare specifier and its subpaths (react-dom/client,
// react/jsx-runtime), so every import lands in the one copy.
{ find: /^react$/, replacement: path.resolve(__dirname, '../../node_modules/react') },
{ find: /^react\//, replacement: path.resolve(__dirname, '../../node_modules/react') + '/' },
{ find: /^react-dom$/, replacement: path.resolve(__dirname, '../../node_modules/react-dom') },
{
find: /^react-dom\//,
replacement: path.resolve(__dirname, '../../node_modules/react-dom') + '/',
},
],
dedupe: ['react', 'react-dom'],
},
optimizeDeps: {
Expand Down
Loading