Brought to you by Allons-y Studio

opx init

Scan the committed tree and interactively wire up opx.

Synopsis

opx init [--auto] [--quiet] [--cleanup]

Options

Flag Default Description
--auto false Only proceed in an interactive terminal. Intended for postinstall scripts.
--quiet false Suppress informational output when --auto exits early
--cleanup false Opt in to the cleanup suggestions from your plugins, and the offer to delete empty files

What it does

  1. Creates .opx/config.json if there isn't one.
  2. For each suggested plugin not yet enabled, asks Yes, No, or Later. "Later" records a deferral in .opx/state.json for the current branch.
  3. Saves your choices and offers to install any missing packages with the project's package manager (npm, Yarn, pnpm, or Bun).
  4. Scaffolds a root eslint.config.js composed from your enabled lint plugins. It never overwrites an existing file.
  5. Offers to add "lint": "opx lint" to your package.json scripts.
  6. Installs the post-commit husky hook and updates .gitignore to ignore .opx/* except .opx/config.json, which stays tracked.
  7. With --cleanup, shows the cleanup suggestions your enabled plugins make, and offers to delete empty files they replace.

Package manager

opx works out which package manager the project uses, in this order:

  1. The packageManager field in package.json, such as pnpm@9.0.0.
  2. A lockfile: yarn.lock, pnpm-lock.yaml, bun.lock or bun.lockb, package-lock.json or npm-shrinkwrap.json.
  3. The manager that launched opx (pnpm dlx, bunx, and so on). It ranks last because npx reports npm in any project.
  4. npm.

opx looks in the project folder and then up to the repository root, so a workspace member finds the root's lockfile. It never looks above the repository root.

At a workspace root it also adds the flag some versions require: -w for pnpm (pnpm 9 and earlier refuse without it; pnpm 10 accepts it) and -W for Yarn Classic.

If the signals disagree (the packageManager field names one manager and a lockfile another, or several managers have lockfiles), opx doesn't guess. It saves your choices, then prints the install command for you to run instead of running it, and says what conflicts.

Cleanup

Cleanup is off unless asked for, because it can delete files. Run opx init --cleanup to see what each enabled plugin makes unnecessary. opx init prints those suggestions and does very little itself. It deletes a file only when all of these hold:

  1. The plugin suggested it, and it is a regular file inside the repository (not a symlink or a folder).
  2. It is empty: nothing but whitespace, comments, or {}.
  3. git can restore it: it is tracked and unchanged since HEAD.

It asks once before deleting. Everything else is advice for you to act on. opx never edits package.json, never edits your eslint.config.js, and never runs a command a plugin suggests, including your package manager.

For example, with lint-js, lint-json, lint-md, or lint-ts enabled:

What What opx does
An empty .eslintrc* or .eslintignore that git can restore Offers to delete it.
A legacy ESLint config with content (.eslintrc*, .eslintignore, eslintConfig or eslintIgnore in package.json) Tells you opx does not apply it, and where to move what you still want: your override file if you set lint.<name>, otherwise a new flat config you point it at. For an .eslintrc* file it prints npx @eslint/migrate-config <file>, unless a flat config already exists, since that command overwrites one. You run it and delete the file.
A package the plugin bundles (eslint, globals, …) that you list in devDependencies Prints the remove command for your package manager, with the version the plugin ships, if nothing in the project appears to use it.

A package is held back, and shown under "Keeping", when:

A plugin is only consulted when it is enabled and installed. Because the suggestions ship with the plugin, they match the version you have. See Writing a detector to add them to your own plugin.

Non-interactive use

With no TTY, CI set, or OPX_NONINTERACTIVE set, init prints the suggestions and the commands to run instead of prompting. Use opx enable to apply them. With --cleanup it also prints the cleanup suggestions, and changes nothing. init --auto (postinstall) never runs cleanup, even with --cleanup.