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
29 changes: 29 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,32 @@ updates:
directory: /
schedule:
interval: monthly

# Dev tooling only — the package itself has one runtime dependency and a peer range
# that is deliberately wide, so those are grouped separately from the noisy ones.
- package-ecosystem: npm
directory: /
schedule:
interval: monthly
versioning-strategy: increase
groups:
dev-tooling:
patterns:
- "oxlint"
- "oxfmt"
- "typescript"
- "@types/node"
- "publint"
- "@arethetypeswrong/cli"
ignore:
# The supported Node floor is a deliberate choice in `engines`, not something to
# follow @types/node upstream on.
- dependency-name: "@types/node"
update-types: ["version-update:semver-major"]

# The fixture is a real consumer of the plugin; keeping its Vite and Tailwind current
# is how the integration job notices an upstream break.
- package-ecosystem: npm
directory: /test/fixture
schedule:
interval: monthly
57 changes: 52 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,57 @@ concurrency:
cancel-in-progress: false

jobs:
test:
# Cheap, hermetic gates, across the whole supported Node range. `engines` says >=22,
# so 22 is the floor that must keep working and 26 is the newest that must not break.
checks:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [22, 24, 26]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node-version }}
cache: pnpm

- name: Install
run: pnpm install --frozen-lockfile

- name: Lint
run: pnpm run lint

- name: Typecheck
run: pnpm run typecheck

- name: Unit tests
run: pnpm test

# V8's coverage numbers shift slightly between Node releases, so the thresholds
# are enforced against the floor version only — the matrix still runs the tests.
- name: Coverage thresholds
if: matrix.node-version == 22
run: pnpm run test:coverage

# The package ships raw .mjs behind a hand-written index.d.ts, so the exports map
# and the type resolution are the parts most likely to break silently.
- name: Package and type resolution
if: matrix.node-version == 22
run: |
pnpm run publint
pnpm run types:lint

- name: Audit
if: matrix.node-version == 22
run: pnpm audit --audit-level moderate

# Everything that needs the network, a real Vite build, or the shadcn CLI.
integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -34,9 +84,6 @@ jobs:
- name: Install
run: pnpm install --frozen-lockfile

- name: Unit tests
run: pnpm test

- name: Registry builds and is valid JSON
run: |
pnpm registry:build
Expand Down Expand Up @@ -109,7 +156,7 @@ jobs:
# A separate job so `contents: write` is granted to nothing but the note push.
notes:
if: github.event_name == 'push'
needs: test
needs: [checks, integration]
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Release

# Publishing is triggered by *publishing a GitHub release*, not by pushing a tag — a
# tag is cheap to create by accident, a release is a deliberate act.
on:
release:
types: [published]

permissions:
contents: read
# npm trusted publishing (OIDC). There is no NPM_TOKEN anywhere in this workflow;
# the trust relationship lives in the package settings on npmjs.com.
id-token: write

concurrency:
group: release-${{ github.event.release.tag_name }}
cancel-in-progress: false

jobs:
publish:
name: Publish to npm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.release.tag_name }}
persist-credentials: false

- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: pnpm

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows/release.yml'
printf '%s\n' '--- release workflow ---'
cat -n .github/workflows/release.yml

Repository: hbmartin/tailwind-vite-font-kit

Length of output: 3052


🌐 Web query:

npm trusted publishing package manager caching release builds setup-node cache pnpm guidance

💡 Result:

To set up npm trusted publishing with dependency caching in your CI/CD workflows, follow this guidance based on current best practices for August 2026. Trusted Publishing Overview Trusted publishing replaces long-lived NPM_TOKEN secrets with short-lived OpenID Connect (OIDC) tokens [1][2]. You must configure the trust relationship on npmjs.com under your package settings, specifying your CI provider, repository, and workflow constraints [3][2]. Your CI job requires id-token: write permissions [4][5][2]. When configured, the npm CLI automatically detects the environment and uses OIDC to authenticate, removing the need for an explicit authentication token [1][2]. Caching Guidance For release builds, it is common practice to disable automatic package manager caching to ensure the environment is clean and reproducible [4][5]. However, for general CI jobs or if you choose to cache dependencies for speed, note the following configurations: 1. Using setup-node with pnpm: The actions/setup-node action supports caching the pnpm store [6][7]. To implement this, set cache: 'pnpm' in your setup-node configuration [8][7]. Ensure your workflow defines cache-dependency-path: pnpm-lock.yaml to target the correct lockfile [7]. Example Configuration: - uses: actions/setup-node@v7 with: node-version: '24' cache: 'pnpm' cache-dependency-path: pnpm-lock.yaml - run: pnpm install --frozen-lockfile 2. Important Caching Considerations: - Caching for Release Builds: Official documentation often recommends setting package-manager-cache: false during the publish job to ensure build isolation [4][5]. - pnpm and setup-node: Always ensure pnpm is available (e.g., via corepack or pnpm/action-setup) before the setup-node step, as setup-node may fail if it cannot locate the pnpm executable [8][6][9]. - Cache Contents: Using cache: 'pnpm' caches the pnpm store, not the node_modules directory [6][7]. Running pnpm install --frozen-lockfile is still necessary to reconstruct the project layout and validate dependencies against the lockfile [7]. Provenance Trusted publishing automatically generates provenance attestations for your packages [4][10]. If you encounter issues, verify your npm CLI version (v11.15.0 or later is recommended) [1][10]. While automatic in many cases, you can explicitly ensure provenance by adding --provenance to your publish command or setting "provenance": true in your package.json under publishConfig [4][10].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from urllib.request import urlopen
from html import unescape
import re

url = "https://docs.npmjs.com/trusted-publishers/"
try:
    text = urlopen(url, timeout=15).read().decode("utf-8", "replace")
except Exception as exc:
    print(f"fetch failed: {exc}")
    raise SystemExit(0)

text = re.sub(r"<script\b[^>]*>.*?</script>", " ", text, flags=re.I|re.S)
text = re.sub(r"<style\b[^>]*>.*?</style>", " ", text, flags=re.I|re.S)
text = re.sub(r"<[^>]+>", " ", text)
text = re.sub(r"\s+", " ", unescape(text))
for term in ("cache", "cach", "package-manager-cache", "release build", "npm 11"):
    print(f"--- {term} ---")
    for m in re.finditer(term, text, flags=re.I):
        print(text[max(0, m.start()-250):m.end()+350])
PY

Repository: hbmartin/tailwind-vite-font-kit

Length of output: 318


🏁 Script executed:

#!/bin/bash
set -eu
curl -k -L --max-time 20 -sS https://docs.npmjs.com/trusted-publishers/ |
  python3 -c 'import sys,re,html; s=sys.stdin.read(); s=re.sub(r"<script\b[^>]*>.*?</script>"," ",s,flags=re.I|re.S); s=re.sub(r"<style\b[^>]*>.*?</style>"," ",s,flags=re.I|re.S); s=re.sub(r"<[^>]+>"," ",s); s=re.sub(r"\s+"," ",html.unescape(s)); terms=("cache","package-manager-cache","release build","publishing workflow"); [(print(f"--- {t} ---\n"+s[max(0,m.start()-300):m.end()+500])) for t in terms for m in list(re.finditer(t,s,re.I))[:10]]'

Repository: hbmartin/tailwind-vite-font-kit

Length of output: 6005


🏁 Script executed:

#!/bin/bash
set -eu
curl -k -L --max-time 20 -sS https://raw.githubusercontent.com/actions/setup-node/main/action.yml |
  sed -n '1,180p'
printf '%s\n' '--- setup-node cache documentation ---'
curl -k -L --max-time 20 -sS https://raw.githubusercontent.com/actions/setup-node/main/docs/advanced-usage.md |
  rg -n -C 4 'package-manager-cache|cache:.*pnpm|cache' | head -120

Repository: hbmartin/tailwind-vite-font-kit

Length of output: 8157


🏁 Script executed:

#!/bin/bash
set -eu
for url in \
  https://raw.githubusercontent.com/actions/setup-node/main/src/main.ts \
  https://raw.githubusercontent.com/actions/setup-node/main/src/cache-restore.ts \
  https://raw.githubusercontent.com/actions/setup-node/main/src/cache-save.ts
do
  printf '%s\n' "--- $url ---"
  curl -k -L --max-time 20 -sS "$url" | rg -n -C 5 'packageManagerCache|cache|restoreCache|saveCache' | head -100
done

Repository: hbmartin/tailwind-vite-font-kit

Length of output: 8002


Remove cache: pnpm from the publishing job.

package-manager-cache: false does not disable an explicitly configured cache input. The job must not restore or save the pnpm store in this release path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 33, Remove the explicit cache: pnpm
setting from the publishing job in the release workflow, leaving
package-manager-cache: false intact so the release path neither restores nor
saves the pnpm store.

Source: Linters/SAST tools

registry-url: https://registry.npmjs.org

# Node 22 ships npm 10, which predates trusted publishing.
- name: Upgrade npm
run: |
npm config set prefix ~/.local
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
PATH="$HOME/.local/bin:$PATH" npm install -g npm@latest
Comment on lines +32 to +41

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- workflow ---'
cat -n .github/workflows/release.yml | sed -n '1,90p'

printf '%s\n' '--- repository toolchain declarations ---'
rg -n --hidden -S 'node-version|npm@|packageManager|engines|trusted|provenance|npm publish|actions/setup-node' \
  .github package.json pnpm-lock.yaml 2>/dev/null || true

printf '%s\n' '--- local npm/node availability ---'
node --version 2>/dev/null || true
npm --version 2>/dev/null || true

Repository: hbmartin/tailwind-vite-font-kit

Length of output: 12923


🌐 Web query:

npm trusted publishing requirements npm 11.5.1 Node 22.14.0 official documentation

💡 Result:

Trusted publishing in npm allows you to publish packages directly from CI/CD workflows using OpenID Connect (OIDC) authentication, removing the requirement for long-lived npm access tokens [1][2]. As of the specified versions (npm 11.5.1 and Node.js 22.14.0), the requirements and details for using trusted publishing are: Core Version Requirements - npm CLI: Version 11.5.1 or later [1][2]. - Node.js: Version 22.14.0 or higher [1][2]. General Prerequisites - Existing Package: The npm package must already exist on the registry before a trusted publisher can be configured for it [3][4]. - Permissions: You must have write access (maintainer or owner) to the package [3][4]. - Account Security: Two-factor authentication (2FA) must be enabled at the npm account level [3][5]. - CI/CD Provider: You must use a supported cloud-based CI/CD provider (such as GitHub Actions, GitLab CI/CD, or CircleCI) with cloud-hosted runners [1][6][4]. Self-hosted runners are not supported [1]. Configuration and Usage - OIDC Authentication: Trusted publishing works by exchanging a short-lived OIDC token for an npm publish token at runtime [4][2]. - Provenance: When using trusted publishing, provenance attestations are automatically generated for your packages without requiring the --provenance flag [6]. - Command Flags: When configuring trust relationships, you must specify at least one permission flag: --allow-publish or --allow-stage-publish [3][5]. - Important Note: If you encounter an ENEEDAUTH error, ensure that all fields in your configuration (such as workflow filenames and IDs) are exact and case-sensitive [1]. Additionally, for GitHub Actions, verify that the id-token: write permission is set in your workflow file [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- package metadata ---'
cat -n package.json | sed -n '18,75p'

printf '%s\n' '--- npm 11.5.1 metadata ---'
curl -fsSL https://registry.npmjs.org/npm/11.5.1 |
  jq '{version, engines, dist: {tarball, integrity}}'

printf '%s\n' '--- npm latest metadata ---'
curl -fsSL https://registry.npmjs.org/npm/latest |
  jq '{version, engines}'

printf '%s\n' '--- setup-node version-resolution documentation references ---'
rg -n -S 'node-version: 22|22\.14\.0|semver|latest|check-latest' .github README.md package.json 2>/dev/null || true

Repository: hbmartin/tailwind-vite-font-kit

Length of output: 3800


Pin the npm trusted-publishing toolchain.

Set node-version to 22.14.0 and install npm@11.5.1 instead of npm@latest. Both current version selectors can change independently and can alter or break the release workflow.

🧰 Tools
🪛 zizmor (1.29.0)

[warning] 41-41: ad-hoc installation of packages (adhoc-packages): installs a package outside of a lockfile

(adhoc-packages)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 32 - 41, Pin the release workflow
toolchain by changing node-version from 22 to 22.14.0 and replacing npm@latest
in the Upgrade npm step with npm@11.5.1. Keep the existing npm installation and
PATH setup unchanged.

Source: Linters/SAST tools


- name: Install
run: pnpm install --frozen-lockfile

- name: Tag matches package.json version
run: |
tag="${{ github.event.release.tag_name }}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not interpolate the release tag into shell source.

GitHub expands this expression before the shell parses the script. A release tag containing shell metacharacters can alter the command in this OIDC-enabled publish job. Pass the value through env and read the environment variable.

Proposed fix
       - name: Tag matches package.json version
+        env:
+          RELEASE_TAG: ${{ github.event.release.tag_name }}
         run: |
-          tag="${{ github.event.release.tag_name }}"
+          tag="$RELEASE_TAG"
           pkg="v$(node -p "require('./package.json').version")"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tag="${{ github.event.release.tag_name }}"
- name: Tag matches package.json version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
tag="$RELEASE_TAG"
pkg="v$(node -p "require('./package.json').version")"
🧰 Tools
🪛 zizmor (1.29.0)

[error] 48-48: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 48, Update the release workflow
command assigning tag to avoid interpolating github.event.release.tag_name
directly into shell source; pass the release tag through the step or job env and
read it from an environment variable inside the shell script, preserving the
existing tag value used by the publish flow.

Source: Linters/SAST tools

pkg="v$(node -p "require('./package.json').version")"
if [ "$tag" != "$pkg" ]; then
echo "::error::release tag $tag does not match package.json ($pkg)"
exit 1
fi

- name: Release checks
run: pnpm run release:check

- name: Report packed size
run: pnpm run size:report

# `release:check` just ran `prepublishOnly` in full, so re-running it here would
# only double the wall clock. There is no build step and no prepack hook: `files`
# ships the sources as-is, so nothing is skipped by --ignore-scripts.
- name: Publish
run: |
tag=latest
if [ "${{ github.event.release.prerelease }}" = "true" ]; then tag=next; fi
npm publish --tag "$tag" --access public --provenance --ignore-scripts
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ node_modules
test/fixture/dist
metrics.json
cls-metrics.json
coverage
*.tgz
*.tsbuildinfo
15 changes: 15 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"semi": false,
"singleQuote": true,
"printWidth": 100,
"ignorePatterns": [
"**/*.json",
"**/*.jsonc",
"**/*.yml",
"**/*.md",
"**/*.css",
"**/*.html",
"test/fixture/dist/**"
]
}
14 changes: 14 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"ignorePatterns": ["registry/r/**", "test/fixture/dist/**"],
"plugins": ["typescript", "unicorn", "oxc"],
"categories": {
"correctness": "error"
},
"env": {
"builtin": true,
"node": true,
"es2024": true
},
"rules": {}
}
65 changes: 51 additions & 14 deletions bin/tss-fonts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
import { existsSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs'
import { join, relative, resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import { detectTailwindEntry, detectViteConfig, scanCssFonts, buildFontPlan, GENERIC_STACK_RE } from '../src/detect.mjs'
import {
detectTailwindEntry,
detectViteConfig,
scanCssFonts,
buildFontPlan,
GENERIC_STACK_RE,
} from '../src/detect.mjs'
import { insertFontsPlugin } from '../src/codemod-vite.mjs'
import { codemodCss } from '../src/codemod-css.mjs'
import { unifiedDiff } from '../src/diff.mjs'
Expand All @@ -28,8 +34,20 @@ const opt = (n, d) => {
const DRY = flag('dry-run')
const root = resolve(opt('cwd', process.cwd()))

const c = { dim: (s) => `\x1b[2m${s}\x1b[0m`, b: (s) => `\x1b[1m${s}\x1b[0m`, g: (s) => `\x1b[32m${s}\x1b[0m`, y: (s) => `\x1b[33m${s}\x1b[0m`, r: (s) => `\x1b[31m${s}\x1b[0m` }
const die = (m) => { console.error(`${c.r('error')} ${m}`); process.exit(1) }
const c = {
dim: (s) => `\x1b[2m${s}\x1b[0m`,
b: (s) => `\x1b[1m${s}\x1b[0m`,
g: (s) => `\x1b[32m${s}\x1b[0m`,
y: (s) => `\x1b[33m${s}\x1b[0m`,
r: (s) => `\x1b[31m${s}\x1b[0m`,
}
// Annotated on the binding, not the arrow: TS only narrows control flow past a
// never-returning call when the *variable* carries the type.
/** @type {(m: string) => never} */
const die = (m) => {
console.error(`${c.r('error')} ${m}`)
process.exit(1)
}

if (!cmd || flag('help') || cmd === 'help') {
console.log(`
Expand Down Expand Up @@ -64,7 +82,10 @@ console.log(`${c.dim('tailwind entry')} ${relative(root, entry)}`)
if (found.all.length > 1) {
console.log(
`${c.y('!')} ${found.all.length} stylesheets import tailwindcss; using the shallowest. ` +
`Others: ${found.all.slice(1).map((f) => relative(root, f)).join(', ')}`,
`Others: ${found.all
.slice(1)
.map((f) => relative(root, f))
.join(', ')}`,
)
}

Expand All @@ -73,23 +94,27 @@ if (found.all.length > 1) {
// ---------------------------------------------------------------------------

const configPath = join(root, 'fonts.config.mjs')
let families = null
// Assigned in both branches below; the else branch exits when it finds nothing.
/** @type {import('../index.d.ts').FontFamily[]} */
let families
let detectedFromConfig = false

if (existsSync(configPath) && !flag('from-css')) {
const mod = await import(pathToFileURL(configPath).href + `?t=${Date.now()}`)
families = (mod.default ?? mod).families
detectedFromConfig = true
console.log(`${c.dim('config')} fonts.config.mjs (${families.map((f) => f.name).join(', ')})`)
console.log(
`${c.dim('config')} fonts.config.mjs (${families.map((f) => f.name).join(', ')})`,
)

// `shadcn add` always drops the template config, so on a project that already uses
// different fonts the config and the CSS disagree. Say so rather than silently
// migrating the CSS to a family the project never used.
const inCss = scanCssFonts(entryText)
const cssFamilies = new Set(
[...inCss.googleUrls.flatMap((u) => u.match(/family=([^:&]+)/g) ?? [])].map((m) =>
decodeURIComponent(m.slice('family='.length)).replace(/\+/g, ' '),
),
inCss.googleUrls
.flatMap((u) => u.match(/family=([^:&]+)/g) ?? [])
.map((m) => decodeURIComponent(m.slice('family='.length)).replace(/\+/g, ' ')),
)
// The --font-* declarations the codemod deletes (the ones the config owns) also name
// families; one naming a real family the config doesn't know is the same hazard.
Expand All @@ -110,7 +135,9 @@ if (existsSync(configPath) && !flag('from-css')) {
` Adopting would delete the CSS that names ${missing.length > 1 ? 'them' : 'it'}, so this is almost certainly not what you want.\n\n` +
` ${c.b('--from-css')} adopt what the project actually uses (overwrites fonts.config.mjs)\n` +
` ${c.b('--force')} proceed with fonts.config.mjs as written\n\n` +
c.dim(` If fonts.config.mjs is still the template \`shadcn add\` installed, you want --from-css.`),
c.dim(
` If fonts.config.mjs is still the template \`shadcn add\` installed, you want --from-css.`,
),
)
process.exit(1)
}
Expand All @@ -121,7 +148,12 @@ if (existsSync(configPath) && !flag('from-css')) {
// Zero-config: adopt whatever the project already uses.
const plan = buildFontPlan({
cssText: entryText,
flags: { sans: opt('sans'), display: opt('display'), mono: opt('mono'), preload: opt('preload') },
flags: {
sans: opt('sans'),
display: opt('display'),
mono: opt('mono'),
preload: opt('preload'),
},
})
families = plan.assigned
if (!families?.length) {
Expand All @@ -135,7 +167,9 @@ if (existsSync(configPath) && !flag('from-css')) {
)
process.exit(0)
}
console.log(`${c.dim('detected')} ${families.map((f) => f.name).join(', ')} ${c.dim('(from your CSS)')}`)
console.log(
`${c.dim('detected')} ${families.map((f) => f.name).join(', ')} ${c.dim('(from your CSS)')}`,
)
}

const ownedVars = families.map((f) => f.themeVar)
Expand All @@ -155,7 +189,9 @@ if (res.css !== entryText) {
for (const ch of res.changes) console.log(` ${c.g('•')} ${ch}`)
console.log(unifiedDiff(entryText, res.css, relative(root, entry)))
} else {
console.log(`\n${c.dim('nothing to change in')} ${relative(root, entry)} ${c.dim('(already adopted)')}`)
console.log(
`\n${c.dim('nothing to change in')} ${relative(root, entry)} ${c.dim('(already adopted)')}`,
)
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -221,7 +257,8 @@ for (const [file, before, after] of edits) {
writeFileSync(file, after)
}
console.log(`\n${c.g('✓')} wrote ${edits.length} file(s). Backups: *.bak`)
if (cmd === 'adopt') console.log(c.dim(' If you have not added fonts() to vite.config.ts yet, see `tss-fonts init`.'))
if (cmd === 'adopt')
console.log(c.dim(' If you have not added fonts() to vite.config.ts yet, see `tss-fonts init`.'))

// ---------------------------------------------------------------------------

Expand Down
Loading