@plumeria/init
Learn more about @plumeria/init.
@plumeria/init sets Plumeria up in a project that already exists. There are eight packages and a plugin per bundler; the choice between them is not a judgement, it is a detection, so the command makes it.
npx @plumeria/initIt reads the project, asks what it cannot read, shows the plan, and only then writes. Nothing is installed or patched before you answer.
✔ detected next pnpm typescript
➡︎ install @plumeria/core @plumeria/next-plugin @plumeria/eslint-plugin oxlint
+ write plumeria.d.ts classStyle
~ patch next.config.ts withPlumeria(...)
~ patch eslint.config.ts plumeria.configs.recommended
~ patch package.json build: plumerialint -- next build
Apply? (Y/n)Commit before running, so the writes can be reverted with git checkout.
What it detects
The package manager comes from packageManager, or from the lockfile when that field is absent. Both are looked for upwards from the directory init runs in, so a package inside a workspace is installed with the manager the workspace root uses rather than with npm.
The bundler comes from the dependencies, and from the config files when nothing is installed yet — next, vite, astro, webpack, rspack, rollup, rolldown, esbuild, farm and bun. Next.js is read before the bundler Next.js carries, so a Next project gets @plumeria/next-plugin; everything else gets @plumeria/unplugin with the factory that matches. Pass --bundler <name> to decide it yourself.
What it asks
Which spelling of a two-named property does this project write?
1: logical marginBlockStart, insetInlineStart
2: physical marginTop, left
3: both no policyOne answer reaches both levels. The bundler plugin receives withoutPhysicalProperties or withoutLogicalProperties, so the counterpart cannot compile, and ESLint receives @plumeria/no-physical-properties or @plumeria/no-logical-properties, so it is reported while you type. They are the same choice made twice, and setting them from one answer is what keeps their directions from disagreeing.
--sizes extends both to the size axis, where width and inlineSize are the pair.
On a codebase that already has styles, answer 3 and follow Optional rules instead: the rule offers a rename on every occurrence, which is what a migration needs, and the build option belongs afterwards.
Expand a border shorthand into the three declarations it sets?
Turns on @plumeria/expand-border-shorthands. A border shorthand crosses the axis shorthands without either containing the other, so specificity cannot rank the pair and the outcome depends on the order it was written in. Expanding removes the shape. On by default.
Which JSX prop carries styles?
classStyle unless you name another. A renamed prop is written into the plugin as styleProp and declared in plumeria.d.ts at the same time, so the two cannot disagree — a mismatch type-checks but is never compiled away.
Set up @plumeria/eslint-plugin and the plumerialint build guard?
Adds the plugin and oxlint, writes or extends the flat config, and puts plumerialint -- in front of the build script, so a style error stops the build before the bundler runs.
A renamed prop is rejected unless it is an identifier TypeScript can declare, and unless React leaves the name free — className, style, key, ref and children are refused, from the prompt and from --style-prop alike.
What it writes
| File | What lands in it |
|---|---|
plumeria.d.ts | the styling prop TypeScript reads |
| the bundler config | the plugin, and the options the answers chose |
eslint.config.ts | plumeria.configs.recommended and the rules that were asked for |
package.json | plumerialint -- in front of the build script, and rimraf .next before dev and build on Next.js |
An existing file is patched, not replaced. The import lands after the last import, the plugin joins the plugins array, and a Next config has its default export wrapped:
import type { NextConfig } from 'next';
import { withPlumeria } from '@plumeria/next-plugin';
const nextConfig: NextConfig = {
reactStrictMode: true,
};
export default nextConfig;
export default withPlumeria(nextConfig, { withoutLogicalProperties: true });import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import plumeria from '@plumeria/unplugin';
export default defineConfig({
plugins: [react(), plumeria.vite({ withoutLogicalProperties: true })],
});On Next.js a pre script clears the build cache, which is what keeps a version change from being read as a compile error. One is added for each of dev and build that the project actually has, so a project with no scripts gets none — and rimraf is only installed when one is added:
"scripts": {
"predev": "rimraf .next",
"prebuild": "rimraf .next",
"dev": "next dev",
"build": "plumerialint -- next build"
}A predev or prebuild the project already wrote is left as it is. Clearing the cache is a Next.js concern rather than a lint one, so --no-eslint leaves it in place and drops only plumerialint.
What counts as already set up is the plugin being called, not merely imported. A bare plumeria.version, and a call inside a comment, a string or a regex literal, all read as not set up, because none of them registers anything; an import nothing calls is completed rather than skipped, and the binding the file already named is the one that gets called. An import spanning several lines is read as one statement, so it is never duplicated. A second run writes nothing, and a half-finished one is finished.
The plugins array it extends is the one the config object owns. A plugins belonging to a nested key — test for Vitest, say — is never written into. Where the plugin cannot be placed safely — a config whose own object has no plugins array, a build script rather than a config, a legacy .eslintrc — nothing is touched, the snippet is printed, and the command exits with 1.
Options
npx @plumeria/init --dry-run # show the plan, write nothing
npx @plumeria/init --yes # take every default, ask nothing
npx @plumeria/init --physical --sizes # answer the spelling question up front
npx @plumeria/init --style-prop sx
npx @plumeria/init --bundler rollup
npx @plumeria/init --no-eslint # leave ESLint and the plumerialint guard out
npx @plumeria/init --no-install # write the configs, print the command
npx @plumeria/init --cwd packages/appWithout a TTY it never asks — every unanswered question takes its default, which is what makes it usable in CI. Colour comes from node:util's styleText, which leaves the text alone when the output is not a terminal, so a piped or logged run carries no escape codes.
Installation
None. Run it with npx and it is gone again afterwards. Install it only if you want it pinned in the repo, where the command is plumeria-init:
npm i --save-dev @plumeria/inityarn add -D @plumeria/initpnpm i --save-dev @plumeria/init