@react-grab/cli 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.cjs +495 -18
  2. package/dist/cli.js +494 -17
  3. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  var commander = require('commander');
5
- var pc = require('picocolors');
5
+ var pc5 = require('picocolors');
6
6
  var prompts3 = require('prompts');
7
7
  var child_process = require('child_process');
8
8
  var fs = require('fs');
@@ -12,7 +12,7 @@ var ora = require('ora');
12
12
 
13
13
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
14
 
15
- var pc__default = /*#__PURE__*/_interopDefault(pc);
15
+ var pc5__default = /*#__PURE__*/_interopDefault(pc5);
16
16
  var prompts3__default = /*#__PURE__*/_interopDefault(prompts3);
17
17
  var ora__default = /*#__PURE__*/_interopDefault(ora);
18
18
 
@@ -261,6 +261,85 @@ var AGENT_PACKAGES = [
261
261
  "@react-grab/amp",
262
262
  "@react-grab/ami"
263
263
  ];
264
+ var REACT_SCAN_DETECTION_PATTERNS = [
265
+ /["'`][^"'`]*react-scan/,
266
+ /react-scan[^"'`]*["'`]/,
267
+ /unpkg\.com\/react-scan/,
268
+ /import\s*\(?["']react-scan/,
269
+ /require\s*\(["']react-scan/,
270
+ /from\s+["']react-scan/,
271
+ /<Script[^>]*react-scan/i,
272
+ /<script[^>]*react-scan/i
273
+ ];
274
+ var REACT_SCAN_FILE_PATTERNS = [
275
+ ["app", "layout"],
276
+ ["src", "app", "layout"],
277
+ ["pages", "_document"],
278
+ ["pages", "_app"],
279
+ ["src", "pages", "_document"],
280
+ ["src", "pages", "_app"],
281
+ ["index"],
282
+ ["public", "index"],
283
+ ["src", "index"],
284
+ ["src", "main"],
285
+ ["src", "routes", "__root"],
286
+ ["app", "routes", "__root"]
287
+ ];
288
+ var SCRIPT_EXTENSIONS = ["tsx", "jsx", "ts", "js"];
289
+ var getReactScanFilesToCheck = (projectRoot) => REACT_SCAN_FILE_PATTERNS.flatMap((segments) => {
290
+ const baseName = segments[segments.length - 1];
291
+ const isHtmlFile = baseName === "index" && segments.length <= 2;
292
+ const extensions = isHtmlFile ? [...SCRIPT_EXTENSIONS, "html"] : SCRIPT_EXTENSIONS;
293
+ return extensions.map(
294
+ (extension) => path.join(projectRoot, ...segments) + `.${extension}`
295
+ );
296
+ });
297
+ var hasReactScanInFile = (filePath) => {
298
+ if (!fs.existsSync(filePath)) return false;
299
+ try {
300
+ const content = fs.readFileSync(filePath, "utf-8");
301
+ return REACT_SCAN_DETECTION_PATTERNS.some(
302
+ (pattern) => pattern.test(content)
303
+ );
304
+ } catch {
305
+ return false;
306
+ }
307
+ };
308
+ var detectReactScan = (projectRoot) => {
309
+ const result = {
310
+ hasReactScan: false,
311
+ hasReactScanMonitoring: false,
312
+ isPackageInstalled: false,
313
+ detectedFiles: []
314
+ };
315
+ const packageJsonPath = path.join(projectRoot, "package.json");
316
+ if (fs.existsSync(packageJsonPath)) {
317
+ try {
318
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
319
+ const allDependencies = {
320
+ ...packageJson.dependencies,
321
+ ...packageJson.devDependencies
322
+ };
323
+ if (allDependencies["react-scan"]) {
324
+ result.isPackageInstalled = true;
325
+ result.hasReactScan = true;
326
+ }
327
+ if (allDependencies["@react-scan/monitoring"]) {
328
+ result.hasReactScanMonitoring = true;
329
+ result.hasReactScan = true;
330
+ }
331
+ } catch {
332
+ }
333
+ }
334
+ const filesToCheck = getReactScanFilesToCheck(projectRoot);
335
+ for (const filePath of filesToCheck) {
336
+ if (hasReactScanInFile(filePath)) {
337
+ result.hasReactScan = true;
338
+ result.detectedFiles.push(filePath);
339
+ }
340
+ }
341
+ return result;
342
+ };
264
343
  var detectUnsupportedFramework = (projectRoot) => {
265
344
  const packageJsonPath = path.join(projectRoot, "package.json");
266
345
  if (!fs.existsSync(packageJsonPath)) {
@@ -422,11 +501,11 @@ ${BOLD}File: ${filePath}${RESET}`);
422
501
  console.log("\u2500".repeat(60));
423
502
  };
424
503
  var highlighter = {
425
- error: pc__default.default.red,
426
- warn: pc__default.default.yellow,
427
- info: pc__default.default.cyan,
428
- success: pc__default.default.green,
429
- dim: pc__default.default.dim
504
+ error: pc5__default.default.red,
505
+ warn: pc5__default.default.yellow,
506
+ info: pc5__default.default.cyan,
507
+ success: pc5__default.default.green,
508
+ dim: pc5__default.default.dim
430
509
  };
431
510
 
432
511
  // src/utils/logger.ts
@@ -1820,9 +1899,120 @@ var previewPackageJsonAgentRemoval = (projectRoot, agent) => {
1820
1899
  };
1821
1900
  }
1822
1901
  };
1902
+ var hasReactScanCode = (content) => REACT_SCAN_DETECTION_PATTERNS.some((pattern) => pattern.test(content));
1903
+ var createRemovalTransform = (removalPatterns) => {
1904
+ return (originalContent, filePath) => {
1905
+ if (!hasReactScanCode(originalContent)) {
1906
+ return {
1907
+ success: true,
1908
+ filePath,
1909
+ message: "React Scan is not configured in this file",
1910
+ noChanges: true
1911
+ };
1912
+ }
1913
+ let newContent = originalContent;
1914
+ for (const pattern of removalPatterns) {
1915
+ newContent = newContent.replace(pattern, "");
1916
+ }
1917
+ if (newContent === originalContent) {
1918
+ return {
1919
+ success: true,
1920
+ filePath,
1921
+ message: "Could not remove React Scan code",
1922
+ noChanges: true
1923
+ };
1924
+ }
1925
+ return {
1926
+ success: true,
1927
+ filePath,
1928
+ message: "Remove React Scan",
1929
+ originalContent,
1930
+ newContent
1931
+ };
1932
+ };
1933
+ };
1934
+ var NEXT_APP_REMOVAL_PATTERNS = [
1935
+ /\s*\{process\.env\.NODE_ENV\s*===\s*["']development["']\s*&&\s*\(\s*<Script[^>]*react-scan[^>]*\/>\s*\)\}/gs,
1936
+ /\s*<Script[^>]*react-scan[^>]*\/>/gi,
1937
+ /\s*<script[^>]*>\s*(?:if\s*\([^)]*\)\s*\{)?\s*import\s*\(\s*["']react-scan["']\s*\)[^<]*<\/script>/gi,
1938
+ /\s*<script[^>]*react-scan[^>]*>[^<]*<\/script>/gi,
1939
+ /\s*<script[^>]*react-scan[^>]*\/>/gi
1940
+ ];
1941
+ var VITE_REMOVAL_PATTERNS = [
1942
+ /\s*import\s*\(\s*["']react-scan["']\s*\)\s*\.then\s*\([^)]*\)[^;]*;?/g,
1943
+ /\s*import\s*\(\s*["']react-scan["']\s*\);?/g,
1944
+ /^\s*import\s+(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+["']react-scan["'];?\s*$/gm,
1945
+ /<script[^>]*type="module"[^>]*>\s*if\s*\(\s*import\.meta\.env\.DEV\s*\)\s*\{\s*\}\s*<\/script>/gi
1946
+ ];
1947
+ var WEBPACK_REMOVAL_PATTERNS = [
1948
+ /\s*import\s*\(\s*["']react-scan["']\s*\)\s*\.then\s*\([^)]*\)[^;]*;?/g,
1949
+ /\s*import\s*\(\s*["']react-scan["']\s*\);?/g,
1950
+ /^\s*import\s+(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+["']react-scan["'];?\s*$/gm,
1951
+ /if\s*\(\s*process\.env\.NODE_ENV\s*===\s*["']development["']\s*\)\s*\{\s*\}/g
1952
+ ];
1953
+ var TANSTACK_REMOVAL_PATTERNS = [
1954
+ /\s*void\s+import\s*\(\s*["']react-scan["']\s*\);?/g,
1955
+ /\s*import\s*\(\s*["']react-scan["']\s*\);?/g,
1956
+ /^\s*import\s+(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+["']react-scan["'];?\s*$/gm
1957
+ ];
1958
+ var removeReactScanFromNextApp = createRemovalTransform(
1959
+ NEXT_APP_REMOVAL_PATTERNS
1960
+ );
1961
+ var removeReactScanFromVite = createRemovalTransform(VITE_REMOVAL_PATTERNS);
1962
+ var removeReactScanFromWebpack = createRemovalTransform(
1963
+ WEBPACK_REMOVAL_PATTERNS
1964
+ );
1965
+ var removeReactScanFromTanStack = createRemovalTransform(
1966
+ TANSTACK_REMOVAL_PATTERNS
1967
+ );
1968
+ var findReactScanFile = (projectRoot, framework, nextRouterType) => {
1969
+ switch (framework) {
1970
+ case "next":
1971
+ if (nextRouterType === "app") {
1972
+ return findLayoutFile(projectRoot);
1973
+ }
1974
+ return findDocumentFile(projectRoot);
1975
+ case "vite":
1976
+ return findIndexHtml(projectRoot);
1977
+ case "tanstack":
1978
+ return findTanStackRootFile(projectRoot);
1979
+ case "webpack":
1980
+ return findEntryFile(projectRoot);
1981
+ default:
1982
+ return null;
1983
+ }
1984
+ };
1985
+ var previewReactScanRemoval = (projectRoot, framework, nextRouterType) => {
1986
+ const filePath = findReactScanFile(projectRoot, framework, nextRouterType);
1987
+ if (!filePath) {
1988
+ return {
1989
+ success: true,
1990
+ filePath: "",
1991
+ message: "Could not find file containing React Scan configuration",
1992
+ noChanges: true
1993
+ };
1994
+ }
1995
+ const originalContent = fs.readFileSync(filePath, "utf-8");
1996
+ switch (framework) {
1997
+ case "next":
1998
+ return removeReactScanFromNextApp(originalContent, filePath);
1999
+ case "vite":
2000
+ return removeReactScanFromVite(originalContent, filePath);
2001
+ case "tanstack":
2002
+ return removeReactScanFromTanStack(originalContent, filePath);
2003
+ case "webpack":
2004
+ return removeReactScanFromWebpack(originalContent, filePath);
2005
+ default:
2006
+ return {
2007
+ success: false,
2008
+ filePath,
2009
+ message: `Unknown framework: ${framework}`
2010
+ };
2011
+ }
2012
+ };
1823
2013
 
1824
2014
  // src/commands/add.ts
1825
- var VERSION = "0.1.0";
2015
+ var VERSION = "0.1.1";
1826
2016
  var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
1827
2017
  var add = new commander.Command().name("add").alias("install").description("add an agent integration").argument("[agent]", `agent to add (${AGENTS.join(", ")})`).option("-y, --yes", "skip confirmation prompts", false).option(
1828
2018
  "-c, --cwd <cwd>",
@@ -1830,7 +2020,7 @@ var add = new commander.Command().name("add").alias("install").description("add
1830
2020
  process.cwd()
1831
2021
  ).action(async (agentArg, opts) => {
1832
2022
  console.log(
1833
- `${pc__default.default.magenta("\u273F")} ${pc__default.default.bold("React Grab")} ${pc__default.default.gray(VERSION)}`
2023
+ `${pc5__default.default.magenta("\u273F")} ${pc5__default.default.bold("React Grab")} ${pc5__default.default.gray(VERSION)}`
1834
2024
  );
1835
2025
  console.log();
1836
2026
  try {
@@ -2158,7 +2348,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
2158
2348
  var MAX_CONTEXT_LINES = 50;
2159
2349
 
2160
2350
  // src/commands/configure.ts
2161
- var VERSION2 = "0.1.0";
2351
+ var VERSION2 = "0.1.1";
2162
2352
  var isMac = process.platform === "darwin";
2163
2353
  var META_LABEL = isMac ? "Cmd" : "Win";
2164
2354
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -2368,7 +2558,7 @@ var configure = new commander.Command().name("configure").alias("config").descri
2368
2558
  process.cwd()
2369
2559
  ).action(async (opts) => {
2370
2560
  console.log(
2371
- `${pc__default.default.magenta("\u273F")} ${pc__default.default.bold("React Grab")} ${pc__default.default.gray(VERSION2)}`
2561
+ `${pc5__default.default.magenta("\u273F")} ${pc5__default.default.bold("React Grab")} ${pc5__default.default.gray(VERSION2)}`
2372
2562
  );
2373
2563
  console.log();
2374
2564
  try {
@@ -2555,7 +2745,7 @@ var configure = new commander.Command().name("configure").alias("config").descri
2555
2745
  `Add this to your ${highlighter.info("init()")} call or ${highlighter.info("data-options")} attribute:`
2556
2746
  );
2557
2747
  logger.break();
2558
- console.log(` ${pc__default.default.cyan(configJson)}`);
2748
+ console.log(` ${pc5__default.default.cyan(configJson)}`);
2559
2749
  logger.break();
2560
2750
  process.exit(1);
2561
2751
  }
@@ -2658,7 +2848,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
2658
2848
  };
2659
2849
 
2660
2850
  // src/commands/init.ts
2661
- var VERSION3 = "0.1.0";
2851
+ var VERSION3 = "0.1.1";
2662
2852
  var REPORT_URL = "https://react-grab.com/api/report-cli";
2663
2853
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
2664
2854
  var reportToCli = (type, config, error) => {
@@ -2724,7 +2914,7 @@ var init = new commander.Command().name("init").description("initialize React Gr
2724
2914
  process.cwd()
2725
2915
  ).action(async (opts) => {
2726
2916
  console.log(
2727
- `${pc__default.default.magenta("\u273F")} ${pc__default.default.bold("React Grab")} ${pc__default.default.gray(VERSION3)}`
2917
+ `${pc5__default.default.magenta("\u273F")} ${pc5__default.default.bold("React Grab")} ${pc5__default.default.gray(VERSION3)}`
2728
2918
  );
2729
2919
  console.log();
2730
2920
  try {
@@ -3325,7 +3515,293 @@ var init = new commander.Command().name("init").description("initialize React Gr
3325
3515
  reportToCli("error", void 0, error);
3326
3516
  }
3327
3517
  });
3328
- var VERSION4 = "0.1.0";
3518
+ var VERSION4 = "0.1.1";
3519
+ var DOCS_URL2 = "https://github.com/aidenybai/react-grab";
3520
+ var exitWithMessage = (message, code = 0) => {
3521
+ if (message) logger.log(message);
3522
+ logger.break();
3523
+ process.exit(code);
3524
+ };
3525
+ var confirmOrExit = async (message, isNonInteractive) => {
3526
+ if (isNonInteractive) return;
3527
+ const { proceed } = await prompts3__default.default({
3528
+ type: "confirm",
3529
+ name: "proceed",
3530
+ message,
3531
+ initial: true
3532
+ });
3533
+ if (!proceed) exitWithMessage("Migration cancelled.");
3534
+ };
3535
+ var hasTransformChanges = (result) => result.success && !result.noChanges && Boolean(result.originalContent) && Boolean(result.newContent);
3536
+ var MIGRATION_SOURCES = ["react-scan"];
3537
+ var MIGRATION_SOURCE_NAMES = {
3538
+ "react-scan": "React Scan"
3539
+ };
3540
+ var FRAMEWORK_NAMES2 = {
3541
+ next: "Next.js",
3542
+ vite: "Vite",
3543
+ tanstack: "TanStack Start",
3544
+ webpack: "Webpack",
3545
+ unknown: "Unknown"
3546
+ };
3547
+ var migrate = new commander.Command().name("migrate").description("migrate to React Grab from another tool").option("-y, --yes", "skip confirmation prompts", false).option("-f, --from <source>", "migration source (react-scan)").option(
3548
+ "-c, --cwd <cwd>",
3549
+ "working directory (defaults to current directory)",
3550
+ process.cwd()
3551
+ ).action(async (opts) => {
3552
+ console.log(
3553
+ `${pc5__default.default.magenta("\u273F")} ${pc5__default.default.bold("React Grab")} ${pc5__default.default.gray(VERSION4)}`
3554
+ );
3555
+ console.log();
3556
+ try {
3557
+ const cwd = opts.cwd;
3558
+ const isNonInteractive = opts.yes;
3559
+ let migrationSource = opts.from;
3560
+ if (!migrationSource) {
3561
+ if (isNonInteractive) {
3562
+ logger.error(
3563
+ "Migration source is required in non-interactive mode. Use --from <source>"
3564
+ );
3565
+ logger.log(
3566
+ `Available sources: ${MIGRATION_SOURCES.map((innerSource) => highlighter.info(innerSource)).join(", ")}`
3567
+ );
3568
+ logger.break();
3569
+ process.exit(1);
3570
+ }
3571
+ const { source } = await prompts3__default.default({
3572
+ type: "select",
3573
+ name: "source",
3574
+ message: `What would you like to migrate ${highlighter.info("from")}?`,
3575
+ choices: MIGRATION_SOURCES.map((innerSource) => ({
3576
+ title: MIGRATION_SOURCE_NAMES[innerSource],
3577
+ value: innerSource
3578
+ }))
3579
+ });
3580
+ if (!source) exitWithMessage();
3581
+ migrationSource = source;
3582
+ }
3583
+ if (!MIGRATION_SOURCES.includes(migrationSource)) {
3584
+ logger.error(`Unknown migration source: ${migrationSource}`);
3585
+ logger.log(
3586
+ `Available sources: ${MIGRATION_SOURCES.map((innerSource) => highlighter.info(innerSource)).join(", ")}`
3587
+ );
3588
+ logger.break();
3589
+ process.exit(1);
3590
+ }
3591
+ logger.break();
3592
+ logger.log(
3593
+ `Migrating from ${highlighter.info(MIGRATION_SOURCE_NAMES[migrationSource])} to ${highlighter.info("React Grab")}...`
3594
+ );
3595
+ logger.break();
3596
+ const preflightSpinner = spinner("Preflight checks.").start();
3597
+ const projectInfo = await detectProject(cwd);
3598
+ preflightSpinner.succeed();
3599
+ if (projectInfo.framework === "unknown") {
3600
+ logger.break();
3601
+ logger.error("Could not detect a supported framework.");
3602
+ logger.log(
3603
+ "React Grab supports Next.js, Vite, TanStack Start, and Webpack projects."
3604
+ );
3605
+ logger.log(`Visit ${highlighter.info(DOCS_URL2)} for manual setup.`);
3606
+ logger.break();
3607
+ process.exit(1);
3608
+ }
3609
+ const frameworkSpinner = spinner("Verifying framework.").start();
3610
+ frameworkSpinner.succeed(
3611
+ `Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES2[projectInfo.framework])}.`
3612
+ );
3613
+ if (projectInfo.framework === "next") {
3614
+ const routerSpinner = spinner("Detecting router type.").start();
3615
+ routerSpinner.succeed(
3616
+ `Detecting router type. Found ${highlighter.info(projectInfo.nextRouterType === "app" ? "App Router" : "Pages Router")}.`
3617
+ );
3618
+ }
3619
+ const sourceSpinner = spinner(
3620
+ `Checking for ${MIGRATION_SOURCE_NAMES[migrationSource]}.`
3621
+ ).start();
3622
+ const reactScanInfo = detectReactScan(cwd);
3623
+ const sourceName = MIGRATION_SOURCE_NAMES[migrationSource];
3624
+ if (!reactScanInfo.hasReactScan) {
3625
+ sourceSpinner.fail(`${sourceName} is not installed in this project.`);
3626
+ exitWithMessage(
3627
+ `Use ${highlighter.info("npx grab init")} to install React Grab directly.`
3628
+ );
3629
+ }
3630
+ sourceSpinner.succeed(
3631
+ `Checking for ${sourceName}. Found ${highlighter.info(reactScanInfo.isPackageInstalled ? "npm package" : "script reference")}.`
3632
+ );
3633
+ if (reactScanInfo.hasReactScanMonitoring) {
3634
+ logger.break();
3635
+ logger.warn(
3636
+ `${sourceName} Monitoring (@react-scan/monitoring) detected.`
3637
+ );
3638
+ logger.warn(
3639
+ "Monitoring features are not available in React Grab. You may need to remove it manually."
3640
+ );
3641
+ }
3642
+ if (projectInfo.hasReactGrab) {
3643
+ logger.break();
3644
+ logger.success("React Grab is already installed.");
3645
+ logger.log(
3646
+ `This migration will only remove ${sourceName} from your project.`
3647
+ );
3648
+ logger.break();
3649
+ const removalResult2 = previewReactScanRemoval(
3650
+ projectInfo.projectRoot,
3651
+ projectInfo.framework,
3652
+ projectInfo.nextRouterType
3653
+ );
3654
+ if (removalResult2.noChanges) {
3655
+ logger.log(`No ${sourceName} code found in configuration files.`);
3656
+ logger.break();
3657
+ if (reactScanInfo.isPackageInstalled) {
3658
+ if (reactScanInfo.detectedFiles.length > 0) {
3659
+ logger.warn(
3660
+ `${sourceName} was detected in files that cannot be automatically cleaned:`
3661
+ );
3662
+ for (const file of reactScanInfo.detectedFiles) {
3663
+ logger.log(` - ${file}`);
3664
+ }
3665
+ logger.warn(
3666
+ "Please remove React Scan references manually before uninstalling the package."
3667
+ );
3668
+ logger.break();
3669
+ process.exit(1);
3670
+ }
3671
+ await confirmOrExit(
3672
+ `Uninstall ${migrationSource} package?`,
3673
+ isNonInteractive
3674
+ );
3675
+ uninstallPackagesWithFeedback(
3676
+ [migrationSource],
3677
+ projectInfo.packageManager,
3678
+ projectInfo.projectRoot
3679
+ );
3680
+ logger.break();
3681
+ logger.success(`${sourceName} has been removed.`);
3682
+ }
3683
+ exitWithMessage();
3684
+ }
3685
+ if (hasTransformChanges(removalResult2)) {
3686
+ logger.break();
3687
+ printDiff(
3688
+ removalResult2.filePath,
3689
+ removalResult2.originalContent,
3690
+ removalResult2.newContent
3691
+ );
3692
+ logger.break();
3693
+ await confirmOrExit("Apply these changes?", isNonInteractive);
3694
+ applyTransformWithFeedback(
3695
+ removalResult2,
3696
+ `Removing ${sourceName} from ${removalResult2.filePath}.`
3697
+ );
3698
+ if (reactScanInfo.isPackageInstalled) {
3699
+ uninstallPackagesWithFeedback(
3700
+ [migrationSource],
3701
+ projectInfo.packageManager,
3702
+ projectInfo.projectRoot
3703
+ );
3704
+ }
3705
+ logger.break();
3706
+ logger.success(`Migration complete! ${sourceName} has been removed.`);
3707
+ }
3708
+ exitWithMessage();
3709
+ }
3710
+ const removalResult = previewReactScanRemoval(
3711
+ projectInfo.projectRoot,
3712
+ projectInfo.framework,
3713
+ projectInfo.nextRouterType
3714
+ );
3715
+ const addResult = previewTransform(
3716
+ projectInfo.projectRoot,
3717
+ projectInfo.framework,
3718
+ projectInfo.nextRouterType,
3719
+ "none",
3720
+ false
3721
+ );
3722
+ const hasRemovalChanges = hasTransformChanges(removalResult);
3723
+ const hasAddChanges = hasTransformChanges(addResult);
3724
+ if (!hasRemovalChanges && !hasAddChanges) {
3725
+ exitWithMessage("No changes needed.");
3726
+ }
3727
+ logger.break();
3728
+ logger.log("Migration will perform the following changes:");
3729
+ logger.break();
3730
+ if (hasRemovalChanges) {
3731
+ logger.log(
3732
+ ` ${pc5__default.default.red("\u2212")} Remove ${sourceName} from ${removalResult.filePath}`
3733
+ );
3734
+ }
3735
+ if (reactScanInfo.isPackageInstalled) {
3736
+ logger.log(` ${pc5__default.default.red("\u2212")} Uninstall ${migrationSource} package`);
3737
+ }
3738
+ logger.log(` ${pc5__default.default.green("+")} Install react-grab package`);
3739
+ if (hasAddChanges) {
3740
+ logger.log(
3741
+ ` ${pc5__default.default.green("+")} Add React Grab to ${addResult.filePath}`
3742
+ );
3743
+ }
3744
+ if (hasRemovalChanges && hasAddChanges && removalResult.filePath === addResult.filePath) {
3745
+ const combinedOriginal = removalResult.originalContent;
3746
+ const combinedNew = addResult.newContent;
3747
+ logger.break();
3748
+ printDiff(removalResult.filePath, combinedOriginal, combinedNew);
3749
+ } else {
3750
+ if (hasRemovalChanges) {
3751
+ logger.break();
3752
+ printDiff(
3753
+ removalResult.filePath,
3754
+ removalResult.originalContent,
3755
+ removalResult.newContent
3756
+ );
3757
+ }
3758
+ if (hasAddChanges) {
3759
+ logger.break();
3760
+ printDiff(
3761
+ addResult.filePath,
3762
+ addResult.originalContent,
3763
+ addResult.newContent
3764
+ );
3765
+ }
3766
+ }
3767
+ logger.break();
3768
+ logger.warn("Auto-detection may not be 100% accurate.");
3769
+ logger.warn("Please verify the changes before committing.");
3770
+ logger.break();
3771
+ await confirmOrExit("Apply these changes?", isNonInteractive);
3772
+ if (reactScanInfo.isPackageInstalled && (hasRemovalChanges || reactScanInfo.detectedFiles.length === 0)) {
3773
+ uninstallPackagesWithFeedback(
3774
+ [migrationSource],
3775
+ projectInfo.packageManager,
3776
+ projectInfo.projectRoot
3777
+ );
3778
+ }
3779
+ installPackagesWithFeedback(
3780
+ getPackagesToInstall("none", true),
3781
+ projectInfo.packageManager,
3782
+ projectInfo.projectRoot
3783
+ );
3784
+ if (hasRemovalChanges) {
3785
+ applyTransformWithFeedback(
3786
+ removalResult,
3787
+ `Removing ${sourceName} from ${removalResult.filePath}.`
3788
+ );
3789
+ }
3790
+ if (hasAddChanges) {
3791
+ applyTransformWithFeedback(
3792
+ addResult,
3793
+ `Adding React Grab to ${addResult.filePath}.`
3794
+ );
3795
+ }
3796
+ logger.break();
3797
+ logger.log(`${highlighter.success("Success!")} Migration complete.`);
3798
+ logger.log("You may now start your development server.");
3799
+ logger.break();
3800
+ } catch (error) {
3801
+ handleError(error);
3802
+ }
3803
+ });
3804
+ var VERSION5 = "0.1.1";
3329
3805
  var remove = new commander.Command().name("remove").description("remove an agent integration").argument(
3330
3806
  "[agent]",
3331
3807
  "agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami)"
@@ -3335,7 +3811,7 @@ var remove = new commander.Command().name("remove").description("remove an agent
3335
3811
  process.cwd()
3336
3812
  ).action(async (agentArg, opts) => {
3337
3813
  console.log(
3338
- `${pc__default.default.magenta("\u273F")} ${pc__default.default.bold("React Grab")} ${pc__default.default.gray(VERSION4)}`
3814
+ `${pc5__default.default.magenta("\u273F")} ${pc5__default.default.bold("React Grab")} ${pc5__default.default.gray(VERSION5)}`
3339
3815
  );
3340
3816
  console.log();
3341
3817
  try {
@@ -3504,7 +3980,7 @@ var remove = new commander.Command().name("remove").description("remove an agent
3504
3980
  });
3505
3981
 
3506
3982
  // src/cli.ts
3507
- var VERSION5 = "0.1.0";
3983
+ var VERSION6 = "0.1.1";
3508
3984
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
3509
3985
  process.on("SIGINT", () => process.exit(0));
3510
3986
  process.on("SIGTERM", () => process.exit(0));
@@ -3513,11 +3989,12 @@ try {
3513
3989
  });
3514
3990
  } catch {
3515
3991
  }
3516
- var program = new commander.Command().name("grab").description("add React Grab to your project").version(VERSION5, "-v, --version", "display the version number");
3992
+ var program = new commander.Command().name("grab").description("add React Grab to your project").version(VERSION6, "-v, --version", "display the version number");
3517
3993
  program.addCommand(init);
3518
3994
  program.addCommand(add);
3519
3995
  program.addCommand(remove);
3520
3996
  program.addCommand(configure);
3997
+ program.addCommand(migrate);
3521
3998
  var main = async () => {
3522
3999
  await program.parseAsync();
3523
4000
  };
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import pc from 'picocolors';
3
+ import pc5 from 'picocolors';
4
4
  import prompts3 from 'prompts';
5
5
  import { execSync } from 'child_process';
6
6
  import { readFileSync, existsSync, writeFileSync, accessSync, constants, readdirSync } from 'fs';
@@ -253,6 +253,85 @@ var AGENT_PACKAGES = [
253
253
  "@react-grab/amp",
254
254
  "@react-grab/ami"
255
255
  ];
256
+ var REACT_SCAN_DETECTION_PATTERNS = [
257
+ /["'`][^"'`]*react-scan/,
258
+ /react-scan[^"'`]*["'`]/,
259
+ /unpkg\.com\/react-scan/,
260
+ /import\s*\(?["']react-scan/,
261
+ /require\s*\(["']react-scan/,
262
+ /from\s+["']react-scan/,
263
+ /<Script[^>]*react-scan/i,
264
+ /<script[^>]*react-scan/i
265
+ ];
266
+ var REACT_SCAN_FILE_PATTERNS = [
267
+ ["app", "layout"],
268
+ ["src", "app", "layout"],
269
+ ["pages", "_document"],
270
+ ["pages", "_app"],
271
+ ["src", "pages", "_document"],
272
+ ["src", "pages", "_app"],
273
+ ["index"],
274
+ ["public", "index"],
275
+ ["src", "index"],
276
+ ["src", "main"],
277
+ ["src", "routes", "__root"],
278
+ ["app", "routes", "__root"]
279
+ ];
280
+ var SCRIPT_EXTENSIONS = ["tsx", "jsx", "ts", "js"];
281
+ var getReactScanFilesToCheck = (projectRoot) => REACT_SCAN_FILE_PATTERNS.flatMap((segments) => {
282
+ const baseName = segments[segments.length - 1];
283
+ const isHtmlFile = baseName === "index" && segments.length <= 2;
284
+ const extensions = isHtmlFile ? [...SCRIPT_EXTENSIONS, "html"] : SCRIPT_EXTENSIONS;
285
+ return extensions.map(
286
+ (extension) => join(projectRoot, ...segments) + `.${extension}`
287
+ );
288
+ });
289
+ var hasReactScanInFile = (filePath) => {
290
+ if (!existsSync(filePath)) return false;
291
+ try {
292
+ const content = readFileSync(filePath, "utf-8");
293
+ return REACT_SCAN_DETECTION_PATTERNS.some(
294
+ (pattern) => pattern.test(content)
295
+ );
296
+ } catch {
297
+ return false;
298
+ }
299
+ };
300
+ var detectReactScan = (projectRoot) => {
301
+ const result = {
302
+ hasReactScan: false,
303
+ hasReactScanMonitoring: false,
304
+ isPackageInstalled: false,
305
+ detectedFiles: []
306
+ };
307
+ const packageJsonPath = join(projectRoot, "package.json");
308
+ if (existsSync(packageJsonPath)) {
309
+ try {
310
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
311
+ const allDependencies = {
312
+ ...packageJson.dependencies,
313
+ ...packageJson.devDependencies
314
+ };
315
+ if (allDependencies["react-scan"]) {
316
+ result.isPackageInstalled = true;
317
+ result.hasReactScan = true;
318
+ }
319
+ if (allDependencies["@react-scan/monitoring"]) {
320
+ result.hasReactScanMonitoring = true;
321
+ result.hasReactScan = true;
322
+ }
323
+ } catch {
324
+ }
325
+ }
326
+ const filesToCheck = getReactScanFilesToCheck(projectRoot);
327
+ for (const filePath of filesToCheck) {
328
+ if (hasReactScanInFile(filePath)) {
329
+ result.hasReactScan = true;
330
+ result.detectedFiles.push(filePath);
331
+ }
332
+ }
333
+ return result;
334
+ };
256
335
  var detectUnsupportedFramework = (projectRoot) => {
257
336
  const packageJsonPath = join(projectRoot, "package.json");
258
337
  if (!existsSync(packageJsonPath)) {
@@ -414,11 +493,11 @@ ${BOLD}File: ${filePath}${RESET}`);
414
493
  console.log("\u2500".repeat(60));
415
494
  };
416
495
  var highlighter = {
417
- error: pc.red,
418
- warn: pc.yellow,
419
- info: pc.cyan,
420
- success: pc.green,
421
- dim: pc.dim
496
+ error: pc5.red,
497
+ warn: pc5.yellow,
498
+ info: pc5.cyan,
499
+ success: pc5.green,
500
+ dim: pc5.dim
422
501
  };
423
502
 
424
503
  // src/utils/logger.ts
@@ -1812,9 +1891,120 @@ var previewPackageJsonAgentRemoval = (projectRoot, agent) => {
1812
1891
  };
1813
1892
  }
1814
1893
  };
1894
+ var hasReactScanCode = (content) => REACT_SCAN_DETECTION_PATTERNS.some((pattern) => pattern.test(content));
1895
+ var createRemovalTransform = (removalPatterns) => {
1896
+ return (originalContent, filePath) => {
1897
+ if (!hasReactScanCode(originalContent)) {
1898
+ return {
1899
+ success: true,
1900
+ filePath,
1901
+ message: "React Scan is not configured in this file",
1902
+ noChanges: true
1903
+ };
1904
+ }
1905
+ let newContent = originalContent;
1906
+ for (const pattern of removalPatterns) {
1907
+ newContent = newContent.replace(pattern, "");
1908
+ }
1909
+ if (newContent === originalContent) {
1910
+ return {
1911
+ success: true,
1912
+ filePath,
1913
+ message: "Could not remove React Scan code",
1914
+ noChanges: true
1915
+ };
1916
+ }
1917
+ return {
1918
+ success: true,
1919
+ filePath,
1920
+ message: "Remove React Scan",
1921
+ originalContent,
1922
+ newContent
1923
+ };
1924
+ };
1925
+ };
1926
+ var NEXT_APP_REMOVAL_PATTERNS = [
1927
+ /\s*\{process\.env\.NODE_ENV\s*===\s*["']development["']\s*&&\s*\(\s*<Script[^>]*react-scan[^>]*\/>\s*\)\}/gs,
1928
+ /\s*<Script[^>]*react-scan[^>]*\/>/gi,
1929
+ /\s*<script[^>]*>\s*(?:if\s*\([^)]*\)\s*\{)?\s*import\s*\(\s*["']react-scan["']\s*\)[^<]*<\/script>/gi,
1930
+ /\s*<script[^>]*react-scan[^>]*>[^<]*<\/script>/gi,
1931
+ /\s*<script[^>]*react-scan[^>]*\/>/gi
1932
+ ];
1933
+ var VITE_REMOVAL_PATTERNS = [
1934
+ /\s*import\s*\(\s*["']react-scan["']\s*\)\s*\.then\s*\([^)]*\)[^;]*;?/g,
1935
+ /\s*import\s*\(\s*["']react-scan["']\s*\);?/g,
1936
+ /^\s*import\s+(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+["']react-scan["'];?\s*$/gm,
1937
+ /<script[^>]*type="module"[^>]*>\s*if\s*\(\s*import\.meta\.env\.DEV\s*\)\s*\{\s*\}\s*<\/script>/gi
1938
+ ];
1939
+ var WEBPACK_REMOVAL_PATTERNS = [
1940
+ /\s*import\s*\(\s*["']react-scan["']\s*\)\s*\.then\s*\([^)]*\)[^;]*;?/g,
1941
+ /\s*import\s*\(\s*["']react-scan["']\s*\);?/g,
1942
+ /^\s*import\s+(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+["']react-scan["'];?\s*$/gm,
1943
+ /if\s*\(\s*process\.env\.NODE_ENV\s*===\s*["']development["']\s*\)\s*\{\s*\}/g
1944
+ ];
1945
+ var TANSTACK_REMOVAL_PATTERNS = [
1946
+ /\s*void\s+import\s*\(\s*["']react-scan["']\s*\);?/g,
1947
+ /\s*import\s*\(\s*["']react-scan["']\s*\);?/g,
1948
+ /^\s*import\s+(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+["']react-scan["'];?\s*$/gm
1949
+ ];
1950
+ var removeReactScanFromNextApp = createRemovalTransform(
1951
+ NEXT_APP_REMOVAL_PATTERNS
1952
+ );
1953
+ var removeReactScanFromVite = createRemovalTransform(VITE_REMOVAL_PATTERNS);
1954
+ var removeReactScanFromWebpack = createRemovalTransform(
1955
+ WEBPACK_REMOVAL_PATTERNS
1956
+ );
1957
+ var removeReactScanFromTanStack = createRemovalTransform(
1958
+ TANSTACK_REMOVAL_PATTERNS
1959
+ );
1960
+ var findReactScanFile = (projectRoot, framework, nextRouterType) => {
1961
+ switch (framework) {
1962
+ case "next":
1963
+ if (nextRouterType === "app") {
1964
+ return findLayoutFile(projectRoot);
1965
+ }
1966
+ return findDocumentFile(projectRoot);
1967
+ case "vite":
1968
+ return findIndexHtml(projectRoot);
1969
+ case "tanstack":
1970
+ return findTanStackRootFile(projectRoot);
1971
+ case "webpack":
1972
+ return findEntryFile(projectRoot);
1973
+ default:
1974
+ return null;
1975
+ }
1976
+ };
1977
+ var previewReactScanRemoval = (projectRoot, framework, nextRouterType) => {
1978
+ const filePath = findReactScanFile(projectRoot, framework, nextRouterType);
1979
+ if (!filePath) {
1980
+ return {
1981
+ success: true,
1982
+ filePath: "",
1983
+ message: "Could not find file containing React Scan configuration",
1984
+ noChanges: true
1985
+ };
1986
+ }
1987
+ const originalContent = readFileSync(filePath, "utf-8");
1988
+ switch (framework) {
1989
+ case "next":
1990
+ return removeReactScanFromNextApp(originalContent, filePath);
1991
+ case "vite":
1992
+ return removeReactScanFromVite(originalContent, filePath);
1993
+ case "tanstack":
1994
+ return removeReactScanFromTanStack(originalContent, filePath);
1995
+ case "webpack":
1996
+ return removeReactScanFromWebpack(originalContent, filePath);
1997
+ default:
1998
+ return {
1999
+ success: false,
2000
+ filePath,
2001
+ message: `Unknown framework: ${framework}`
2002
+ };
2003
+ }
2004
+ };
1815
2005
 
1816
2006
  // src/commands/add.ts
1817
- var VERSION = "0.1.0";
2007
+ var VERSION = "0.1.1";
1818
2008
  var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
1819
2009
  var add = new Command().name("add").alias("install").description("add an agent integration").argument("[agent]", `agent to add (${AGENTS.join(", ")})`).option("-y, --yes", "skip confirmation prompts", false).option(
1820
2010
  "-c, --cwd <cwd>",
@@ -1822,7 +2012,7 @@ var add = new Command().name("add").alias("install").description("add an agent i
1822
2012
  process.cwd()
1823
2013
  ).action(async (agentArg, opts) => {
1824
2014
  console.log(
1825
- `${pc.magenta("\u273F")} ${pc.bold("React Grab")} ${pc.gray(VERSION)}`
2015
+ `${pc5.magenta("\u273F")} ${pc5.bold("React Grab")} ${pc5.gray(VERSION)}`
1826
2016
  );
1827
2017
  console.log();
1828
2018
  try {
@@ -2150,7 +2340,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
2150
2340
  var MAX_CONTEXT_LINES = 50;
2151
2341
 
2152
2342
  // src/commands/configure.ts
2153
- var VERSION2 = "0.1.0";
2343
+ var VERSION2 = "0.1.1";
2154
2344
  var isMac = process.platform === "darwin";
2155
2345
  var META_LABEL = isMac ? "Cmd" : "Win";
2156
2346
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -2360,7 +2550,7 @@ var configure = new Command().name("configure").alias("config").description("con
2360
2550
  process.cwd()
2361
2551
  ).action(async (opts) => {
2362
2552
  console.log(
2363
- `${pc.magenta("\u273F")} ${pc.bold("React Grab")} ${pc.gray(VERSION2)}`
2553
+ `${pc5.magenta("\u273F")} ${pc5.bold("React Grab")} ${pc5.gray(VERSION2)}`
2364
2554
  );
2365
2555
  console.log();
2366
2556
  try {
@@ -2547,7 +2737,7 @@ var configure = new Command().name("configure").alias("config").description("con
2547
2737
  `Add this to your ${highlighter.info("init()")} call or ${highlighter.info("data-options")} attribute:`
2548
2738
  );
2549
2739
  logger.break();
2550
- console.log(` ${pc.cyan(configJson)}`);
2740
+ console.log(` ${pc5.cyan(configJson)}`);
2551
2741
  logger.break();
2552
2742
  process.exit(1);
2553
2743
  }
@@ -2650,7 +2840,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
2650
2840
  };
2651
2841
 
2652
2842
  // src/commands/init.ts
2653
- var VERSION3 = "0.1.0";
2843
+ var VERSION3 = "0.1.1";
2654
2844
  var REPORT_URL = "https://react-grab.com/api/report-cli";
2655
2845
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
2656
2846
  var reportToCli = (type, config, error) => {
@@ -2716,7 +2906,7 @@ var init = new Command().name("init").description("initialize React Grab in your
2716
2906
  process.cwd()
2717
2907
  ).action(async (opts) => {
2718
2908
  console.log(
2719
- `${pc.magenta("\u273F")} ${pc.bold("React Grab")} ${pc.gray(VERSION3)}`
2909
+ `${pc5.magenta("\u273F")} ${pc5.bold("React Grab")} ${pc5.gray(VERSION3)}`
2720
2910
  );
2721
2911
  console.log();
2722
2912
  try {
@@ -3317,7 +3507,293 @@ var init = new Command().name("init").description("initialize React Grab in your
3317
3507
  reportToCli("error", void 0, error);
3318
3508
  }
3319
3509
  });
3320
- var VERSION4 = "0.1.0";
3510
+ var VERSION4 = "0.1.1";
3511
+ var DOCS_URL2 = "https://github.com/aidenybai/react-grab";
3512
+ var exitWithMessage = (message, code = 0) => {
3513
+ if (message) logger.log(message);
3514
+ logger.break();
3515
+ process.exit(code);
3516
+ };
3517
+ var confirmOrExit = async (message, isNonInteractive) => {
3518
+ if (isNonInteractive) return;
3519
+ const { proceed } = await prompts3({
3520
+ type: "confirm",
3521
+ name: "proceed",
3522
+ message,
3523
+ initial: true
3524
+ });
3525
+ if (!proceed) exitWithMessage("Migration cancelled.");
3526
+ };
3527
+ var hasTransformChanges = (result) => result.success && !result.noChanges && Boolean(result.originalContent) && Boolean(result.newContent);
3528
+ var MIGRATION_SOURCES = ["react-scan"];
3529
+ var MIGRATION_SOURCE_NAMES = {
3530
+ "react-scan": "React Scan"
3531
+ };
3532
+ var FRAMEWORK_NAMES2 = {
3533
+ next: "Next.js",
3534
+ vite: "Vite",
3535
+ tanstack: "TanStack Start",
3536
+ webpack: "Webpack",
3537
+ unknown: "Unknown"
3538
+ };
3539
+ var migrate = new Command().name("migrate").description("migrate to React Grab from another tool").option("-y, --yes", "skip confirmation prompts", false).option("-f, --from <source>", "migration source (react-scan)").option(
3540
+ "-c, --cwd <cwd>",
3541
+ "working directory (defaults to current directory)",
3542
+ process.cwd()
3543
+ ).action(async (opts) => {
3544
+ console.log(
3545
+ `${pc5.magenta("\u273F")} ${pc5.bold("React Grab")} ${pc5.gray(VERSION4)}`
3546
+ );
3547
+ console.log();
3548
+ try {
3549
+ const cwd = opts.cwd;
3550
+ const isNonInteractive = opts.yes;
3551
+ let migrationSource = opts.from;
3552
+ if (!migrationSource) {
3553
+ if (isNonInteractive) {
3554
+ logger.error(
3555
+ "Migration source is required in non-interactive mode. Use --from <source>"
3556
+ );
3557
+ logger.log(
3558
+ `Available sources: ${MIGRATION_SOURCES.map((innerSource) => highlighter.info(innerSource)).join(", ")}`
3559
+ );
3560
+ logger.break();
3561
+ process.exit(1);
3562
+ }
3563
+ const { source } = await prompts3({
3564
+ type: "select",
3565
+ name: "source",
3566
+ message: `What would you like to migrate ${highlighter.info("from")}?`,
3567
+ choices: MIGRATION_SOURCES.map((innerSource) => ({
3568
+ title: MIGRATION_SOURCE_NAMES[innerSource],
3569
+ value: innerSource
3570
+ }))
3571
+ });
3572
+ if (!source) exitWithMessage();
3573
+ migrationSource = source;
3574
+ }
3575
+ if (!MIGRATION_SOURCES.includes(migrationSource)) {
3576
+ logger.error(`Unknown migration source: ${migrationSource}`);
3577
+ logger.log(
3578
+ `Available sources: ${MIGRATION_SOURCES.map((innerSource) => highlighter.info(innerSource)).join(", ")}`
3579
+ );
3580
+ logger.break();
3581
+ process.exit(1);
3582
+ }
3583
+ logger.break();
3584
+ logger.log(
3585
+ `Migrating from ${highlighter.info(MIGRATION_SOURCE_NAMES[migrationSource])} to ${highlighter.info("React Grab")}...`
3586
+ );
3587
+ logger.break();
3588
+ const preflightSpinner = spinner("Preflight checks.").start();
3589
+ const projectInfo = await detectProject(cwd);
3590
+ preflightSpinner.succeed();
3591
+ if (projectInfo.framework === "unknown") {
3592
+ logger.break();
3593
+ logger.error("Could not detect a supported framework.");
3594
+ logger.log(
3595
+ "React Grab supports Next.js, Vite, TanStack Start, and Webpack projects."
3596
+ );
3597
+ logger.log(`Visit ${highlighter.info(DOCS_URL2)} for manual setup.`);
3598
+ logger.break();
3599
+ process.exit(1);
3600
+ }
3601
+ const frameworkSpinner = spinner("Verifying framework.").start();
3602
+ frameworkSpinner.succeed(
3603
+ `Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES2[projectInfo.framework])}.`
3604
+ );
3605
+ if (projectInfo.framework === "next") {
3606
+ const routerSpinner = spinner("Detecting router type.").start();
3607
+ routerSpinner.succeed(
3608
+ `Detecting router type. Found ${highlighter.info(projectInfo.nextRouterType === "app" ? "App Router" : "Pages Router")}.`
3609
+ );
3610
+ }
3611
+ const sourceSpinner = spinner(
3612
+ `Checking for ${MIGRATION_SOURCE_NAMES[migrationSource]}.`
3613
+ ).start();
3614
+ const reactScanInfo = detectReactScan(cwd);
3615
+ const sourceName = MIGRATION_SOURCE_NAMES[migrationSource];
3616
+ if (!reactScanInfo.hasReactScan) {
3617
+ sourceSpinner.fail(`${sourceName} is not installed in this project.`);
3618
+ exitWithMessage(
3619
+ `Use ${highlighter.info("npx grab init")} to install React Grab directly.`
3620
+ );
3621
+ }
3622
+ sourceSpinner.succeed(
3623
+ `Checking for ${sourceName}. Found ${highlighter.info(reactScanInfo.isPackageInstalled ? "npm package" : "script reference")}.`
3624
+ );
3625
+ if (reactScanInfo.hasReactScanMonitoring) {
3626
+ logger.break();
3627
+ logger.warn(
3628
+ `${sourceName} Monitoring (@react-scan/monitoring) detected.`
3629
+ );
3630
+ logger.warn(
3631
+ "Monitoring features are not available in React Grab. You may need to remove it manually."
3632
+ );
3633
+ }
3634
+ if (projectInfo.hasReactGrab) {
3635
+ logger.break();
3636
+ logger.success("React Grab is already installed.");
3637
+ logger.log(
3638
+ `This migration will only remove ${sourceName} from your project.`
3639
+ );
3640
+ logger.break();
3641
+ const removalResult2 = previewReactScanRemoval(
3642
+ projectInfo.projectRoot,
3643
+ projectInfo.framework,
3644
+ projectInfo.nextRouterType
3645
+ );
3646
+ if (removalResult2.noChanges) {
3647
+ logger.log(`No ${sourceName} code found in configuration files.`);
3648
+ logger.break();
3649
+ if (reactScanInfo.isPackageInstalled) {
3650
+ if (reactScanInfo.detectedFiles.length > 0) {
3651
+ logger.warn(
3652
+ `${sourceName} was detected in files that cannot be automatically cleaned:`
3653
+ );
3654
+ for (const file of reactScanInfo.detectedFiles) {
3655
+ logger.log(` - ${file}`);
3656
+ }
3657
+ logger.warn(
3658
+ "Please remove React Scan references manually before uninstalling the package."
3659
+ );
3660
+ logger.break();
3661
+ process.exit(1);
3662
+ }
3663
+ await confirmOrExit(
3664
+ `Uninstall ${migrationSource} package?`,
3665
+ isNonInteractive
3666
+ );
3667
+ uninstallPackagesWithFeedback(
3668
+ [migrationSource],
3669
+ projectInfo.packageManager,
3670
+ projectInfo.projectRoot
3671
+ );
3672
+ logger.break();
3673
+ logger.success(`${sourceName} has been removed.`);
3674
+ }
3675
+ exitWithMessage();
3676
+ }
3677
+ if (hasTransformChanges(removalResult2)) {
3678
+ logger.break();
3679
+ printDiff(
3680
+ removalResult2.filePath,
3681
+ removalResult2.originalContent,
3682
+ removalResult2.newContent
3683
+ );
3684
+ logger.break();
3685
+ await confirmOrExit("Apply these changes?", isNonInteractive);
3686
+ applyTransformWithFeedback(
3687
+ removalResult2,
3688
+ `Removing ${sourceName} from ${removalResult2.filePath}.`
3689
+ );
3690
+ if (reactScanInfo.isPackageInstalled) {
3691
+ uninstallPackagesWithFeedback(
3692
+ [migrationSource],
3693
+ projectInfo.packageManager,
3694
+ projectInfo.projectRoot
3695
+ );
3696
+ }
3697
+ logger.break();
3698
+ logger.success(`Migration complete! ${sourceName} has been removed.`);
3699
+ }
3700
+ exitWithMessage();
3701
+ }
3702
+ const removalResult = previewReactScanRemoval(
3703
+ projectInfo.projectRoot,
3704
+ projectInfo.framework,
3705
+ projectInfo.nextRouterType
3706
+ );
3707
+ const addResult = previewTransform(
3708
+ projectInfo.projectRoot,
3709
+ projectInfo.framework,
3710
+ projectInfo.nextRouterType,
3711
+ "none",
3712
+ false
3713
+ );
3714
+ const hasRemovalChanges = hasTransformChanges(removalResult);
3715
+ const hasAddChanges = hasTransformChanges(addResult);
3716
+ if (!hasRemovalChanges && !hasAddChanges) {
3717
+ exitWithMessage("No changes needed.");
3718
+ }
3719
+ logger.break();
3720
+ logger.log("Migration will perform the following changes:");
3721
+ logger.break();
3722
+ if (hasRemovalChanges) {
3723
+ logger.log(
3724
+ ` ${pc5.red("\u2212")} Remove ${sourceName} from ${removalResult.filePath}`
3725
+ );
3726
+ }
3727
+ if (reactScanInfo.isPackageInstalled) {
3728
+ logger.log(` ${pc5.red("\u2212")} Uninstall ${migrationSource} package`);
3729
+ }
3730
+ logger.log(` ${pc5.green("+")} Install react-grab package`);
3731
+ if (hasAddChanges) {
3732
+ logger.log(
3733
+ ` ${pc5.green("+")} Add React Grab to ${addResult.filePath}`
3734
+ );
3735
+ }
3736
+ if (hasRemovalChanges && hasAddChanges && removalResult.filePath === addResult.filePath) {
3737
+ const combinedOriginal = removalResult.originalContent;
3738
+ const combinedNew = addResult.newContent;
3739
+ logger.break();
3740
+ printDiff(removalResult.filePath, combinedOriginal, combinedNew);
3741
+ } else {
3742
+ if (hasRemovalChanges) {
3743
+ logger.break();
3744
+ printDiff(
3745
+ removalResult.filePath,
3746
+ removalResult.originalContent,
3747
+ removalResult.newContent
3748
+ );
3749
+ }
3750
+ if (hasAddChanges) {
3751
+ logger.break();
3752
+ printDiff(
3753
+ addResult.filePath,
3754
+ addResult.originalContent,
3755
+ addResult.newContent
3756
+ );
3757
+ }
3758
+ }
3759
+ logger.break();
3760
+ logger.warn("Auto-detection may not be 100% accurate.");
3761
+ logger.warn("Please verify the changes before committing.");
3762
+ logger.break();
3763
+ await confirmOrExit("Apply these changes?", isNonInteractive);
3764
+ if (reactScanInfo.isPackageInstalled && (hasRemovalChanges || reactScanInfo.detectedFiles.length === 0)) {
3765
+ uninstallPackagesWithFeedback(
3766
+ [migrationSource],
3767
+ projectInfo.packageManager,
3768
+ projectInfo.projectRoot
3769
+ );
3770
+ }
3771
+ installPackagesWithFeedback(
3772
+ getPackagesToInstall("none", true),
3773
+ projectInfo.packageManager,
3774
+ projectInfo.projectRoot
3775
+ );
3776
+ if (hasRemovalChanges) {
3777
+ applyTransformWithFeedback(
3778
+ removalResult,
3779
+ `Removing ${sourceName} from ${removalResult.filePath}.`
3780
+ );
3781
+ }
3782
+ if (hasAddChanges) {
3783
+ applyTransformWithFeedback(
3784
+ addResult,
3785
+ `Adding React Grab to ${addResult.filePath}.`
3786
+ );
3787
+ }
3788
+ logger.break();
3789
+ logger.log(`${highlighter.success("Success!")} Migration complete.`);
3790
+ logger.log("You may now start your development server.");
3791
+ logger.break();
3792
+ } catch (error) {
3793
+ handleError(error);
3794
+ }
3795
+ });
3796
+ var VERSION5 = "0.1.1";
3321
3797
  var remove = new Command().name("remove").description("remove an agent integration").argument(
3322
3798
  "[agent]",
3323
3799
  "agent to remove (claude-code, cursor, opencode, codex, gemini, amp, ami)"
@@ -3327,7 +3803,7 @@ var remove = new Command().name("remove").description("remove an agent integrati
3327
3803
  process.cwd()
3328
3804
  ).action(async (agentArg, opts) => {
3329
3805
  console.log(
3330
- `${pc.magenta("\u273F")} ${pc.bold("React Grab")} ${pc.gray(VERSION4)}`
3806
+ `${pc5.magenta("\u273F")} ${pc5.bold("React Grab")} ${pc5.gray(VERSION5)}`
3331
3807
  );
3332
3808
  console.log();
3333
3809
  try {
@@ -3496,7 +3972,7 @@ var remove = new Command().name("remove").description("remove an agent integrati
3496
3972
  });
3497
3973
 
3498
3974
  // src/cli.ts
3499
- var VERSION5 = "0.1.0";
3975
+ var VERSION6 = "0.1.1";
3500
3976
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
3501
3977
  process.on("SIGINT", () => process.exit(0));
3502
3978
  process.on("SIGTERM", () => process.exit(0));
@@ -3505,11 +3981,12 @@ try {
3505
3981
  });
3506
3982
  } catch {
3507
3983
  }
3508
- var program = new Command().name("grab").description("add React Grab to your project").version(VERSION5, "-v, --version", "display the version number");
3984
+ var program = new Command().name("grab").description("add React Grab to your project").version(VERSION6, "-v, --version", "display the version number");
3509
3985
  program.addCommand(init);
3510
3986
  program.addCommand(add);
3511
3987
  program.addCommand(remove);
3512
3988
  program.addCommand(configure);
3989
+ program.addCommand(migrate);
3513
3990
  var main = async () => {
3514
3991
  await program.parseAsync();
3515
3992
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-grab/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "bin": {
5
5
  "react-grab": "./dist/cli.js"
6
6
  },