CSS token rename codemod

Renames the nine CSS custom properties that the v3 token rename moved. It covers both spellings: the --pluma--prefixed names @customerio/pluma-components/css ships, and the bare names @customerio/pluma-css ships — that package went 0.5.7 → 1.0.0 in the same release, with the same renames.

This codemod touches raw custom properties only. The prop side of the same release (size="s" → size="sm", text="product-ps" → "product-psm") is the text-size-shorthand-rename codemod; the two don't overlap.

What it renames

Written below as flat token ids. Each is spelled with whichever prefix the pass matching it uses, so font-size-s is --pluma-font-size-s in a @customerio/pluma-components/css app and --font-size-s in a @customerio/pluma-css one.

Hard renames — the old name is gone

3.0 removed these ids outright. A stale read is a custom property that resolves to nothing, which makes the declaration holding it invalid, and an invalid declaration disappears at computed-value time. Nothing warns; the element just paints without it.

table
BeforeAfter
color-banner-error-bordercolor-banner-critical-border
font-size-sfont-size-sm
font-size-mfont-size-md
font-size-lfont-size-lg
text-product-pstext-product-psm
text-product-ps-activetext-product-psm-active

Soft renames — the old name survives

These ids still exist after 3.0 — they're the names Figma manages — but no component CSS reads them any more. A stale read still resolves to the colour it always did, so the rewrite is vocabulary alignment rather than a fix. Nothing was broken.

table
BeforeAfter
color-banner-error-backgroundcolor-banner-critical-background
color-snackbar-error-backgroundcolor-snackbar-critical-background
color-snackbar-error-close-background-hovercolor-snackbar-critical-close-background-hover

The run's summary says which kind each rewrite was, per file and per name, so a red diff line can be read as either "this was painting wrong" or "this still worked".

Running it

Run it through the Pluma CLI from inside your consuming app. Always start with a dry run to review the diff and the summary before applying:

# from the root of your consuming app

# Dry run first (no writes; per-file diff printed inline)
pnx @customerio/pluma-cli@latest upgrade --codemod css-token-rename react --dry ./src
pnx @customerio/pluma-cli@latest upgrade --codemod css-token-rename ember --dry ./app

# Then apply for real
pnx @customerio/pluma-cli@latest upgrade --codemod css-token-rename react ./src
pnx @customerio/pluma-cli@latest upgrade --codemod css-token-rename ember ./app ./addon

React paths are walked for .tsx, .ts, .jsx, .js, .mts, .cts, .mjs and .cjs, then again for .css, .scss and .astro. Ember paths are walked for .gts, .gjs, .ts, .js, .mts, .cts, .mjs, .cjs, .hbs, .scss and .css.

What gets matched

Nothing is parsed. A custom property name means the same thing in a stylesheet, a <template> body, a .css.ts vars:{} block and an inline style string, and the only thing the pass needs to know about a match is the characters around it.

Both edges are anchored, which is what keeps the near-miss names safe. --font-size-s is a prefix of --font-size-sm, so an unanchored match would corrupt an already-migrated file on a second run and would also walk over the xs / xl / xxs / xxl / xxxl steps that were never renamed. The same goes for --text-product-ps against --text-product-psm.

Typography tokens expand into members. text-product-ps isn't one custom property in a stylesheet — it's --text-product-ps-fontSize, --text-product-ps-lineHeight and five more. The match owns the token name and leaves the member suffix alone, so every member migrates from one entry, and --text-product-psm-fontSize still never matches.

The bare pass, and what arms it

Bare --color-*, --font-size-* and --text-* are not a Pluma namespace — a Tailwind @theme block, a design-system shim or an app's own :root will have names of that shape that Pluma never owned. Two rules keep the pass off everything else:

  1. A bare name is rewritten only where it is read through var(), never where it is declared. A declaration whose name is also one of ours is reported rather than rewritten, because every read of that name in the file is then ambiguous and only a human can tell the two apart.
  2. The pass is off unless a scanned file pulls in @customerio/pluma-css or the nearest package.json above your paths depends on it. Pass --bare-css-vars / --no-bare-css-vars to override; either way the run prints which it chose and why.

The --pluma- spelling has no such ambiguity, so it is rewritten wherever it appears — declarations included.

Working on the codemod itself

There's a test suite under tests/. It ships as part of @customerio/pluma-cli. After pnpm install at the repo root:

pnpm --filter @customerio/pluma-cli run test:transforms

It uses Node's built-in test runner (node --test).