Skip to content

feat(cli): expose programmatic Node install API#479

Merged
aidenybai merged 7 commits into
mainfrom
cursor/expose-node-install-api-f365
Jun 18, 2026
Merged

feat(cli): expose programmatic Node install API#479
aidenybai merged 7 commits into
mainfrom
cursor/expose-node-install-api-f365

Conversation

@aidenybai

@aidenybai aidenybai commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Summary

Today, all of React Grab's install/setup logic is locked behind the CLI: importing @react-grab/cli immediately parses argv and runs the program, and there are no subpath exports. There's no way for an external tool to detect a project, install the package, and apply the framework setup programmatically.

This PR adds a side-effect-free Node API at @react-grab/cli/api so external consumers can build their own installers/CLIs around React Grab.

Answer to "does it expose a Node API?"

It did not. Now it does — via @react-grab/cli/api.

What's added

  • installReactGrab(options?) — a high-level, non-interactive orchestrator that detects the project, installs the react-grab package with the detected package manager, and applies the framework-specific dev-only setup. Returns a structured InstallReactGrabResult instead of printing/exiting. Supports overrides (framework, nextRouterType, packageManager), and force / skipPackageInstall / skipTransform / dryRun flags.
  • ReactGrabInstallError with a discriminated code (unsupported-framework, unknown-framework, transform-failed, install-failed, write-failed) so callers can branch on failure cause instead of parsing messages or hitting process.exit. The underlying error is preserved on error.cause.
  • Low-level building blocks re-exported for full control: detectProject, detectFramework, detectPackageManager, detectNextRouterType, detectReactGrab, detectReactGrabConfigured, detectUnsupportedFramework, findReactProjects, previewTransform, previewOptionsTransform, previewCdnTransform, applyTransform, hasFrameworkEntryPoint, installPackages, getPackagesToInstall, installSkill, removeSkill — plus all relevant types.
  • installSkill(options?) — a non-interactive agent-skill install extracted from promptSkillInstall (which now reuses it; no behavior change to the interactive flow).

Implementation notes

  • New programmatic entry packages/cli/src/api.ts (no side effects on import) — distinct from the existing CLI entry . which still parses argv.
  • packages/cli/src/utils/install-react-grab.ts houses the orchestrator + error type.
  • ProjectInfo is now exported from detect.ts for typed consumers.
  • package.json gains an ./api subpath export (types/import/require); vite.config.ts builds src/api.ts alongside src/cli.ts. The existing . export and bins are untouched.

Usage

import { installReactGrab } from "@react-grab/cli/api";

const result = await installReactGrab({ cwd: process.cwd() });
// result.framework / result.didInstallPackage / result.didChangeFile / result.transform.filePath

Or compose the primitives directly (detectProjectpreviewTransforminstallPackagesapplyTransforminstallSkill). See the updated packages/cli/README.md for the full table and examples. installReactGrab targets a single project at cwd (use findReactProjects to locate apps in a monorepo first), and mutates the project unless dryRun: true.

Review hardening

A second pass (code-quality, performance, reuse, and security review) tightened the orchestrator:

  • Derive the Next.js router type when framework is overridden to next, so the Pages-router path isn't wrongly selected.
  • Pin cwd/packageManager over installPackageOptions (and Omit them from its type) so the install directory can't diverge from the edited file.
  • Wrap package-manager failures as install-failed (preserving error.cause) so the structured error contract covers the most likely runtime failure.
  • Don't throw transform-failed when skipTransform is set; resolve cwd to an absolute path; reuse getPackagesToInstall(); align the pending-change predicate with init's invariant.
  • Keep interactive deps out of the API bundle: promptSkillInstall (and its prompts/ora imports) moved to utils/prompt-skill-install.ts, so import "@react-grab/cli/api" no longer eagerly loads the interactive prompt/spinner stack. Verified the interactive code is absent from the built api graph.
  • Documented accurate force semantics, the monorepo/cwd + mutation expectations, and corrected the low-level README example.

Deferred (noted, out of scope)

  • Refactoring the init command to delegate to installReactGrabinit interleaves an interactive diff/confirm between preview and apply and uses spinner-wrapped install/apply, so delegating would risk the CLI UX. The shared gating predicates (getPackagesToInstall, !hasReactGrab, the pending-change check) are now aligned to minimize drift.
  • Built-in monorepo discovery inside the orchestrator (kept non-interactive; callers use findReactProjects).

Testing

  • pnpm --filter @react-grab/cli build — both cli and api entries build, .d.ts generated.
  • Verified import/require of the built api expose the full surface and that the interactive prompt/spinner code is no longer in the api graph.
  • pnpm test — 225 passing (test/install-react-grab.test.ts covers fresh install, already-installed, dry-run, no-op, framework/router + package-manager overrides, skip flags, installPackageOptions precedence, and every error code).
  • lint + format clean (run under Node 22.18, since the repo's vite.config.ts config loader requires Node ≥22.18 for default TS stripping).
Open in Web Open in Cursor 

Summary by cubic

Adds a side-effect-free Node API at @react-grab/cli/api to detect, install, and configure react-grab programmatically with structured results and errors. Keeps the API bundle lean by moving interactive prompts out and tightens the Node API README.

  • New Features

    • installReactGrab(options?): non-interactive detect/install/transform orchestrator with overrides and flags (force, skipPackageInstall, skipTransform, dryRun); returns a structured result. installPackageOptions excludes cwd/packageManager (set by the orchestrator).
    • ReactGrabInstallError with codes: unsupported-framework, unknown-framework, transform-failed, install-failed, write-failed.
    • Re-exports low-level helpers and types (detectProject, previewTransform, applyTransform, installPackages, getPackagesToInstall, installSkill, removeSkill, ProjectInfo, etc.). New ./api subpath and build entry; src/api.ts has no import side effects. README docs and tests added.
  • Refactors

    • Moved interactive promptSkillInstall to utils/prompt-skill-install.ts; API now exports lean installSkill/removeSkill (non-interactive). init/add updated; remove now logs per-agent and removeSkill returns the removed agents.
    • Write guard matches applyTransform: drop originalContent check so empty files still receive setup. README examples use process.cwd(), guard on !noChanges, reuse getPackagesToInstall(), and document precise installPackageOptions type.
    • Hardened install flow: derive Next.js router type when framework: "next" is overridden, pin cwd/packageManager, wrap package-manager failures as install-failed, skip throwing on failed preview when skipTransform is set, and resolve cwd to an absolute path.
    • Stop exporting internal detectMonorepo from the public API.

Written for commit fc377ce. Summary will update on new commits.

Review in cubic

Add @react-grab/cli/api entry so external tools can build their own
installers around React Grab without shelling out to the CLI.

- High-level installReactGrab() orchestrator (detect + install + transform)
  with structured result and ReactGrabInstallError codes
- Re-export low-level primitives (detect*, previewTransform, applyTransform,
  installPackages, installSkill/removeSkill) and their types
- Non-interactive installSkill() extracted from promptSkillInstall
- Export ProjectInfo, add ./api subpath export and api build entry
- Document the API and add tests

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-grab-storybook Ready Ready Preview, Comment Jun 18, 2026 3:59am
react-grab-website Ready Ready Preview, Comment Jun 18, 2026 3:59am

@pkg-pr-new

pkg-pr-new Bot commented Jun 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@react-grab/cli@479
npm i https://pkg.pr.new/grab@479
npm i https://pkg.pr.new/react-grab@479

commit: fc377ce

- Derive Next.js router type when framework is overridden to next, so the
  Pages-router path is not wrongly chosen
- Pin cwd/packageManager over installPackageOptions and Omit them from the
  option type so the install dir cannot diverge from the edited file
- Wrap package-manager failures in a ReactGrabInstallError (install-failed,
  preserves error.cause) so the structured error contract covers the most
  likely runtime failure
- Skip the transform-failed throw when skipTransform is set
- Resolve cwd to an absolute path; reuse getPackagesToInstall()
- Document accurate force semantics, monorepo/cwd expectations, and the
  mutating nature of the call
- Add tests for router derivation, skip flags, option precedence, and
  install-failed; type the project fixture as ProjectInfo

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Importing @react-grab/cli/api dragged in the interactive promptSkillInstall
(and its prompts/ora deps) because it lived in install-skill.ts, which the
API re-exports for installSkill/removeSkill.

- Move promptSkillInstall into utils/prompt-skill-install.ts; install-skill.ts
  now only carries the lean installSkill/removeSkill + light deps
- Point the init/add commands at the new module
- Align installReactGrab's pending-change predicate with init's invariant
  (originalContent + newContent + !noChanges) to prevent drift
- Fix the README low-level example to guard writes on !noChanges and reuse
  getPackagesToInstall()

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

4 issues found across 11 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/cli/src/utils/install-react-grab.ts Outdated
Comment thread packages/cli/README.md Outdated
Comment thread packages/cli/src/api.ts Outdated
Comment thread packages/cli/README.md Outdated
- Drop the originalContent truthiness check in installReactGrab so an empty
  source file still receives the injected setup (matches applyTransform's
  own write guard)
- Use process.cwd() in the README low-level example so the snippet is valid
  when copied as-is
- Stop exporting the internal detectMonorepo helper from the public API
  surface (detectProject already returns isMonorepo)

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Reflect that cwd/packageManager are excluded (Omit) since the orchestrator
controls them.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
- Active voice in the section opener and orchestrator summary
- Convert list-shaped prose (error codes, export surface) into lists
- Split the over-long low-level snippet into two blocks with prose between,
  moving inline code comments into prose
- Split a four-idea paragraph into scope and mutation paragraphs

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
@aidenybai aidenybai merged commit 26dcc77 into main Jun 18, 2026
21 checks passed
@aidenybai aidenybai deleted the cursor/expose-node-install-api-f365 branch June 18, 2026 04:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants