*Primitive rename codemod

Renames the *Primitive components introduced in v3. The genuinely unstyled primitives adopt the Plain* prefix; SelectPrimitive — the trigger-less select behavior layer, not an unstyled primitive — becomes SelectController:

table
BeforeAfter
PopoverPrimitivePlainPopover
ModalPrimitivePlainModal
SelectPrimitiveSelectController

The stem is renamed wherever it appears, so every subcomponent, prop type, and context export migrates with it (PopoverPrimitiveContentPlainPopoverContent, ModalPrimitiveDialogPropsPlainModalDialogProps, and so on). It's a pure rename — no props or behavior change.

// Before
import { PopoverPrimitive, PopoverPrimitiveContent } from '@customerio/pluma-components/react';
const x = (
	<PopoverPrimitive>
		<PopoverPrimitiveContent />
	</PopoverPrimitive>
);

// After
import { PlainPopover, PlainPopoverContent } from '@customerio/pluma-components/react';
const x = (
	<PlainPopover>
		<PlainPopoverContent />
	</PlainPopover>
);

Running it

Run it through the Pluma CLI from inside your consuming app. Always start with a dry run to review the diff 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 primitive-rename react --dry ./src
pnx @customerio/pluma-cli@latest upgrade --codemod primitive-rename ember --dry ./app

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

Both frameworks accept --dry / -d. The React path also forwards standard jscodeshift flags.

What gets matched

React. Names that enter the file through an import of @customerio/pluma-components, @customerio/pluma-components/react, or the per-component subpaths @customerio/pluma-components/react/popover-primitive / .../react/modal-primitive. Those subpath sources are themselves rewritten to .../react/plain-popover / .../react/plain-modal. The transform renames the import specifiers, JSX tags, and type references (e.g. PopoverPrimitiveProps). Aliased imports (import { PopoverPrimitive as PP } from …) rename the imported name but keep the local alias, so usages stay valid. A local PopoverPrimitive that wasn't imported from Pluma is left alone.

Ember. <PlumaPopoverPrimitive…> / <PlumaModalPrimitive…> / <PlumaSelectPrimitive> tags in .gts/.gjs/.hbs templates, the strict-mode named imports in .gts/.gjs (including the renamed @customerio/pluma-components/ember/{popover,modal}-primitive subpaths), and any type references are all rewritten. Because every affected token is Pluma-namespaced, the Ember transform is a formatting-preserving textual rename.

Working on the codemod itself

The React transform is a bespoke jscodeshift transform; the Ember transform is a textual rename that reuses the shared CLI runner under ../../_shared/. There's a test suite under tests/:

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

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