diff --git a/.changeset/config.json b/.changeset/config.json index de483e8..48a14ed 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,6 +1,6 @@ { "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", - "changelog": ["@changesets/changelog-github", { "repo": "variableland/theme-toggle" }], + "changelog": ["@changesets/changelog-github", { "repo": "withvariablelab/theme-toggle" }], "commit": true, "fixed": [], "linked": [], diff --git a/.changeset/renamed-to-variablelab.md b/.changeset/renamed-to-variablelab.md new file mode 100644 index 0000000..d92e67d --- /dev/null +++ b/.changeset/renamed-to-variablelab.md @@ -0,0 +1,15 @@ +--- +"@variablelab/theme-toggle": major +--- + +Renamed from `@vlandoss/theme-toggle`. The package now lives at [withvariablelab/theme-toggle](https://github.com/withvariablelab/theme-toggle); `@vlandoss/theme-toggle` will not receive further releases. + +**Breaking changes** + +- Install and import `@variablelab/theme-toggle` instead of `@vlandoss/theme-toggle` (the inline script moves to `https://unpkg.com/@variablelab/theme-toggle/dist/initial.js`). +- The `localStorage` key is now `@variablelab/theme-toggle.colorMode` (was `@vlandoss/theme-toggle.colorMode`). +- The change event is now `@variablelab/theme-toggle.colorModeChange` (was `@vlandoss/theme-toggle.colorModeChange`). Update any `document.addEventListener(...)` listeners, or read the name from `window.__COLOR_MODE__.CUSTOM_EVENT`. + +**Automatic migration** + +Users keep their saved color mode. On init, both the inline script and `useColorMode` check for the 1.x key: if `@variablelab/theme-toggle.colorMode` is absent and `@vlandoss/theme-toggle.colorMode` exists, its value is copied to the new key. A value already stored under the new key always wins. Either way, the old key is removed. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 363f3c8..46c91c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: branches: - main +permissions: + contents: read # checkout + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -15,15 +18,18 @@ jobs: runs-on: ubuntu-latest steps: - name: ⬇️ Checkout repo - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false - name: 🟢 Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version-file: .node-version - name: ⌛ Setup pnpm - uses: variableland/gh-actions/actions/setup-pnpm@main + # First-party action, deliberately tracked at @main. + uses: withvariablelab/gh-actions/actions/setup-pnpm@main # zizmor: ignore[unpinned-uses] - name: 💅 Check run: pnpm rr check @@ -36,3 +42,20 @@ jobs: - name: 🔨 Build run: pnpm rr pack + + zizmor: + runs-on: ubuntu-latest + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + + # Audits .github/ (the repo has no other workflows or actions). Passing the + # repo root keeps the annotation paths repo-relative so they land on the diff. + - name: 🌈 Audit workflows (zizmor) + env: + # Read-only token so zizmor also runs its online audits + # (known-vulnerable-actions, impostor-commit, ref-confusion). + GH_TOKEN: ${{ github.token }} + run: pipx run --spec zizmor==1.30.1 zizmor --format=github . diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 55b2baf..ed68c36 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,9 @@ on: branches: - main +# The org default GITHUB_TOKEN is read-only; each job asks for exactly what it needs. +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -17,68 +20,103 @@ jobs: release: if: ${{ github.event_name == 'push' }} runs-on: ubuntu-latest + # Pushes, the version PR and the GitHub releases go through the vland-bot + # token minted below, not GITHUB_TOKEN. permissions: - id-token: write - contents: write - packages: write - pull-requests: write + id-token: write # npm trusted publishing (OIDC) + contents: read # checkout steps: - - name: 🔑 Mint vland-bot token - id: app-token - uses: actions/create-github-app-token@v1 - with: - app-id: ${{ secrets.VLAND_BOT_APP_ID }} - private-key: ${{ secrets.VLAND_BOT_PRIVATE_KEY }} - - name: ⬇️ Checkout repo - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: - token: ${{ steps.app-token.outputs.token }} + # changesets/action writes ~/.netrc with its GITHUB_TOKEN (the + # vland-bot token below) and pushes through it. + persist-credentials: false - name: 🟢 Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version-file: .node-version - name: ⌛ Setup pnpm - uses: variableland/gh-actions/actions/setup-pnpm@main + # First-party action, deliberately tracked at @main. + uses: withvariablelab/gh-actions/actions/setup-pnpm@main # zizmor: ignore[unpinned-uses] + + - name: 🔑 Mint vland-bot token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.VLAND_BOT_CLIENT_ID }} + private-key: ${{ secrets.VLAND_BOT_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write - name: 🧑💻 Configure git user + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} run: | - git config user.name "vland-bot[bot]" - git config user.email "282456776+vland-bot[bot]@users.noreply.github.com" + user_id="$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" + git config user.name "${APP_SLUG}[bot]" + git config user.email "${user_id}+${APP_SLUG}[bot]@users.noreply.github.com" - name: 🚀 Create release PR or publish versions - uses: changesets/action@v1 + uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0 with: commit: "chore: update versions" title: "chore: update versions" publish: pnpm changeset publish setupGitUser: false env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + # The bot opens the version PR, so CI runs on it. + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} LEFTHOOK: 0 + # Runs the PR's code and publishes the preview to npm via OIDC. It never sees + # the vland-bot key; it only hands the published package list to the next job. preview: if: ${{ github.event_name == 'pull_request' && (startsWith(github.head_ref, 'feat') || startsWith(github.head_ref, 'fix') || contains(github.event.pull_request.labels.*.name, 'preview')) }} runs-on: ubuntu-latest permissions: - id-token: write - contents: read + id-token: write # npm trusted publishing (OIDC) + contents: read # checkout + outputs: + packages: ${{ steps.preview.outputs.packages }} steps: - name: ⬇️ Checkout repo - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false - name: 🟢 Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version-file: .node-version - name: ⌛ Setup CI - uses: variableland/gh-actions/actions/setup-pnpm@main + # First-party action, deliberately tracked at @main. + uses: withvariablelab/gh-actions/actions/setup-pnpm@main # zizmor: ignore[unpinned-uses] - - name: 🚀 Preview release - uses: variableland/gh-actions/actions/preview-release@main + - name: 🚀 Publish preview + id: preview + # First-party action, deliberately tracked at @main. + uses: withvariablelab/gh-actions/actions/preview-release@main # zizmor: ignore[unpinned-uses] with: - npm_token: ${{ secrets.NPM_TOKEN }} + mode: publish + # Keep this explicit while the repo has a project .npmrc: preview-release + # auto-detects any .npmrc as token-only, and there is no NPM_TOKEN here. + publish_mode: oidc-only + + # Never checks out or runs PR code: the reusable workflow mints the vland-bot + # token (pull-requests: write, this repo only) and upserts the PR comment. + preview-comment: + needs: preview + if: ${{ needs.preview.result == 'success' }} + permissions: {} + # First-party reusable workflow, deliberately tracked at @main. + uses: withvariablelab/gh-actions/.github/workflows/preview-comment.yml@main # zizmor: ignore[unpinned-uses] + with: + action: preview-release + packages: ${{ needs.preview.outputs.packages }} + secrets: + VLAND_BOT_PRIVATE_KEY: ${{ secrets.VLAND_BOT_PRIVATE_KEY }} diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..36f3e84 --- /dev/null +++ b/.npmrc @@ -0,0 +1,18 @@ +# Supply-chain guard: never resolve a dependency version published less than +# 24 h ago (most malicious releases are pulled within the first hour). Requires +# pnpm >= 10.16. +# +# No exclude list on purpose: our own scopes (@variablelab/*, @rrlab/*) wait +# 24 h too. In .npmrc, `minimum-release-age-exclude[]` breaks whenever pnpm is +# launched through npm (`npx pnpm`, `npm exec`, `npm run`). npm re-exports the +# array as a single string, and pnpm 10.30.3 then exempts every package. To +# adopt a fresh own release right away, pass +# `--config.minimum-release-age=0` to that one `pnpm add`. +# +# This lives here instead of pnpm-workspace.yaml because @rrlab/cli (through +# @vlandoss/clibuddy 0.7.1) treats any repo with a pnpm-workspace.yaml as a +# monorepo, and `rr tsc` then fails ("Invalid workspace patterns") or, with +# `packages: ["."]`, silently skips this package. Once +# https://github.com/withvariablelab/dx/issues/249 ships, move it there with +# minimumReleaseAgeExclude, and delete this file. +minimum-release-age=1440 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c0963d..b4ea43a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ -# @vlandoss/theme-toggle +# @variablelab/theme-toggle ## 1.0.0 ### Major Changes -- [#2](https://github.com/variableland/theme-toggle/pull/2) [`dd42ff8`](https://github.com/variableland/theme-toggle/commit/dd42ff8bd1ca2b96c6484f8a323d17aa916e158a) Thanks [@rqbazan](https://github.com/rqbazan)! - Initial stable release. Ships the `useColorMode` React hook and an inline pre-paint script for `light` / `dark` / `system` color modes. The `system` mode resolves against the OS `prefers-color-scheme` and tracks it live, the choice is persisted to `localStorage`, a `dark` class is toggled on ``, and a `CustomEvent` is dispatched on every change. +- [#2](https://github.com/withvariablelab/theme-toggle/pull/2) [`dd42ff8`](https://github.com/withvariablelab/theme-toggle/commit/dd42ff8bd1ca2b96c6484f8a323d17aa916e158a) Thanks [@rqbazan](https://github.com/rqbazan)! - Initial stable release. Ships the `useColorMode` React hook and an inline pre-paint script for `light` / `dark` / `system` color modes. The `system` mode resolves against the OS `prefers-color-scheme` and tracks it live, the choice is persisted to `localStorage`, a `dark` class is toggled on ``, and a `CustomEvent` is dispatched on every change. diff --git a/README.md b/README.md index 42f8a2d..e38d372 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# @vlandoss/theme-toggle +# @variablelab/theme-toggle Light/dark/system color-mode toggle for React apps, with a tiny inline script that sets the theme **before first paint** to avoid the flash of the wrong theme (FOUC). @@ -12,7 +12,7 @@ sets the theme **before first paint** to avoid the flash of the wrong theme (FOU ## Installation ```sh -pnpm add @vlandoss/theme-toggle +pnpm add @variablelab/theme-toggle ``` ## Usage @@ -26,7 +26,7 @@ Load the prebuilt IIFE **before your app renders** — ideally as the first thin ```html
- + ``` @@ -38,7 +38,7 @@ exposes its runtime contract on `window.__COLOR_MODE__`. ### 2. React hook (read & control the mode) ```tsx -import { useColorMode } from "@vlandoss/theme-toggle"; +import { useColorMode } from "@variablelab/theme-toggle"; function ThemeSwitch() { const { colorMode, setColorMode } = useColorMode(); @@ -81,17 +81,17 @@ body { ## How it works -| Concern | Value | -| -------------- | ------------------------------------------ | -| DOM class | `dark` on `document.documentElement` | -| Storage key | `@vlandoss/theme-toggle.colorMode` | -| Change event | `@vlandoss/theme-toggle.colorModeChange` | +| Concern | Value | +| ------------ | ------------------------------------------- | +| DOM class | `dark` on `document.documentElement` | +| Storage key | `@variablelab/theme-toggle.colorMode` | +| Change event | `@variablelab/theme-toggle.colorModeChange` | The change event is a `CustomEvent` dispatched on `document`, with the new value in `event.detail.colorMode`: ```ts -document.addEventListener("@vlandoss/theme-toggle.colorModeChange", (event) => { +document.addEventListener("@variablelab/theme-toggle.colorModeChange", (event) => { console.log(event.detail.colorMode); // "light" | "dark" | "system" }); ``` @@ -100,6 +100,19 @@ The inline script also publishes these names on `window.__COLOR_MODE__` (`STORAGE_KEY`, `CUSTOM_EVENT`, `COLOR_MODE`) so other scripts can read them without importing the package. +## Upgrading from `@vlandoss/theme-toggle` 1.x + +The package was renamed to `@variablelab/theme-toggle` in 2.0.0: + +1. Replace the dependency and imports: `pnpm remove @vlandoss/theme-toggle && pnpm add @variablelab/theme-toggle`, + and update the inline script URL to `https://unpkg.com/@variablelab/theme-toggle/dist/initial.js`. +2. Listeners of the change event must use the new name, `@variablelab/theme-toggle.colorModeChange` + (or read it from `window.__COLOR_MODE__.CUSTOM_EVENT`). + +Saved preferences carry over automatically. On init, if the old `@vlandoss/theme-toggle.colorMode` +key exists, its value is copied to the new storage key, unless the new key already holds a value +(that value wins). The old key is then removed either way. + ## Development ```sh diff --git a/mise.toml b/mise.toml index bb9552b..f5d8d74 100644 --- a/mise.toml +++ b/mise.toml @@ -3,4 +3,4 @@ _.path = ["{{config_root}}/node_modules/.bin"] [tools] node = "24.16.0" -pnpm = "10.6.4" +pnpm = "10.30.3" diff --git a/package.json b/package.json index 65a5e37..c0b50c3 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { - "name": "@vlandoss/theme-toggle", + "name": "@variablelab/theme-toggle", "version": "1.0.0", "description": "Light/dark/system color-mode toggle for React with a no-flash inline script", - "homepage": "https://github.com/variableland/theme-toggle#readme", + "homepage": "https://github.com/withvariablelab/theme-toggle#readme", "bugs": { - "url": "https://github.com/variableland/theme-toggle/issues" + "url": "https://github.com/withvariablelab/theme-toggle/issues" }, "repository": { "type": "git", - "url": "git+https://github.com/variableland/theme-toggle.git" + "url": "git+https://github.com/withvariablelab/theme-toggle.git" }, "license": "MIT", "author": "rcrd