@react-grab/cli 0.1.44-dev.f610e2f → 0.1.45-dev.3c3f835

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.
package/dist/cli.cjs CHANGED
@@ -53,6 +53,71 @@ const AGENT_ENVIRONMENT_VARIABLES = [
53
53
  const isEnvironmentVariableSet = (variable) => Boolean(process.env[variable]);
54
54
  const detectNonInteractive = (yesFlag) => yesFlag || AGENT_ENVIRONMENT_VARIABLES.some(isEnvironmentVariableSet) || !process.stdin.isTTY;
55
55
  //#endregion
56
+ //#region src/utils/react-grab-code.ts
57
+ const REACT_GRAB_SPECIFIER_PATTERN = String.raw`react-grab(?:\/[^"']+)?`;
58
+ const stripComments = (content) => content.replace(/<!--[\s\S]*?-->/g, "").replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/.*$/gm, "$1");
59
+ const stripTypeOnlyReactGrabImports = (content) => {
60
+ return content.replace(new RegExp(String.raw`import\s+type\s+[^;]+from\s+["']${REACT_GRAB_SPECIFIER_PATTERN}["'];?`, "g"), "").replace(new RegExp(String.raw`import\s*\{\s*type\s+[^,}]+(?:\s*,\s*type\s+[^,}]+)*\s*,?\s*\}\s*from\s+["']${REACT_GRAB_SPECIFIER_PATTERN}["'];?`, "g"), "");
61
+ };
62
+ const hasReactGrabSetupCode = (content) => {
63
+ const setupCandidateContent = stripTypeOnlyReactGrabImports(stripComments(content));
64
+ return [
65
+ new RegExp(String.raw`import\s*\(\s*["']${REACT_GRAB_SPECIFIER_PATTERN}["']\s*\)`),
66
+ new RegExp(String.raw`import\s+(?!type\b)(?:[^"';]+from\s+)?["']${REACT_GRAB_SPECIFIER_PATTERN}["']`),
67
+ new RegExp(String.raw`require\s*\(\s*["']${REACT_GRAB_SPECIFIER_PATTERN}["']\s*\)`),
68
+ /<Script[\s\S]*?src\s*=\s*(?:["'][^"']*react-grab[^"']*["']|\{(?:["'][^"']*react-grab[^"']*["']|`[^`]*react-grab[^`]*`)\})/i,
69
+ /<script[\s\S]*?src\s*=\s*["'][^"']*react-grab[^"']*["']/i
70
+ ].some((pattern) => pattern.test(setupCandidateContent));
71
+ };
72
+ //#endregion
73
+ //#region src/utils/react-grab-setup-files.ts
74
+ const COMPONENT_EXTENSIONS = [
75
+ "tsx",
76
+ "jsx",
77
+ "ts",
78
+ "js"
79
+ ];
80
+ const INSTRUMENTATION_EXTENSIONS = [
81
+ "ts",
82
+ "tsx",
83
+ "js",
84
+ "jsx",
85
+ "mts",
86
+ "cts",
87
+ "mjs",
88
+ "cjs"
89
+ ];
90
+ const ROUTE_EXTENSIONS = ["tsx", "jsx"];
91
+ const createFileCandidates = (projectRoot, directories, baseName, extensions) => {
92
+ const fileCandidates = [];
93
+ for (const directory of directories) for (const extension of extensions) fileCandidates.push((0, node_path.join)(projectRoot, directory, `${baseName}.${extension}`));
94
+ return fileCandidates;
95
+ };
96
+ const findExistingFile = (fileCandidates) => {
97
+ for (const filePath of fileCandidates) if ((0, node_fs.existsSync)(filePath)) return filePath;
98
+ return null;
99
+ };
100
+ const getLayoutFileCandidates = (projectRoot) => createFileCandidates(projectRoot, ["app", "src/app"], "layout", COMPONENT_EXTENSIONS);
101
+ const getDocumentFileCandidates = (projectRoot) => createFileCandidates(projectRoot, ["pages", "src/pages"], "_document", COMPONENT_EXTENSIONS);
102
+ const getInstrumentationFileCandidates = (projectRoot) => createFileCandidates(projectRoot, ["", "src"], "instrumentation-client", INSTRUMENTATION_EXTENSIONS);
103
+ const getIndexHtmlCandidates = (projectRoot) => [(0, node_path.join)(projectRoot, "index.html"), (0, node_path.join)(projectRoot, "public", "index.html")];
104
+ const getEntryFileCandidates = (projectRoot) => [...createFileCandidates(projectRoot, ["src"], "index", COMPONENT_EXTENSIONS), ...createFileCandidates(projectRoot, ["src"], "main", COMPONENT_EXTENSIONS)];
105
+ const getTanStackRootFileCandidates = (projectRoot) => createFileCandidates(projectRoot, ["src/routes", "app/routes"], "__root", ROUTE_EXTENSIONS);
106
+ const getReactGrabSetupFileCandidates = (projectRoot) => [
107
+ ...getLayoutFileCandidates(projectRoot),
108
+ ...getDocumentFileCandidates(projectRoot),
109
+ ...getInstrumentationFileCandidates(projectRoot),
110
+ ...getIndexHtmlCandidates(projectRoot),
111
+ ...getEntryFileCandidates(projectRoot),
112
+ ...getTanStackRootFileCandidates(projectRoot)
113
+ ];
114
+ const findLayoutFile = (projectRoot) => findExistingFile(getLayoutFileCandidates(projectRoot));
115
+ const findDocumentFile = (projectRoot) => findExistingFile(getDocumentFileCandidates(projectRoot));
116
+ const findIndexHtml = (projectRoot) => findExistingFile(getIndexHtmlCandidates(projectRoot));
117
+ const findEntryFile = (projectRoot) => findExistingFile(getEntryFileCandidates(projectRoot));
118
+ const findTanStackRootFile = (projectRoot) => findExistingFile(getTanStackRootFileCandidates(projectRoot));
119
+ const isInstrumentationFile = (filePath) => /(?:^|[/\\])instrumentation-client\.[cm]?[jt]sx?$/.test(filePath);
120
+ //#endregion
56
121
  //#region src/utils/detect.ts
57
122
  const VALID_PACKAGE_MANAGERS = new Set([
58
123
  "npm",
@@ -266,10 +331,14 @@ const scanDirectoryForProjects = (rootDirectory, ignorer, maxDepth, currentDepth
266
331
  return projects;
267
332
  };
268
333
  const MAX_SCAN_DEPTH = 2;
334
+ const normalizePathForComparison = (filePath) => filePath.replace(/\\/g, "/");
269
335
  const findReactProjects = (projectRoot) => {
270
- if (detectMonorepo(projectRoot)) {
271
- const workspaceProjects = findWorkspaceProjects(projectRoot);
272
- if (workspaceProjects.length > 0) return workspaceProjects;
336
+ const monorepoRoot = detectMonorepo(projectRoot) ? projectRoot : findEnclosingMonorepoRoot(projectRoot);
337
+ if (monorepoRoot) {
338
+ const workspaceProjects = findWorkspaceProjects(monorepoRoot);
339
+ const localProject = projectRoot === monorepoRoot ? null : buildReactProject(projectRoot);
340
+ const projects = localProject ? [localProject, ...workspaceProjects.filter((project) => normalizePathForComparison(project.path) !== normalizePathForComparison(localProject.path))] : workspaceProjects;
341
+ if (projects.length > 0) return projects;
273
342
  }
274
343
  const scannedProjects = scanDirectoryForProjects(projectRoot, loadGitignore(projectRoot), MAX_SCAN_DEPTH);
275
344
  if (scannedProjects.length > 0) return scannedProjects;
@@ -281,47 +350,20 @@ const findReactProjects = (projectRoot) => {
281
350
  }
282
351
  return [];
283
352
  };
284
- const hasReactGrabInFile = (filePath) => {
353
+ const hasReactGrabSetupInFile = (filePath) => {
285
354
  if (!(0, node_fs.existsSync)(filePath)) return false;
286
355
  try {
287
- const content = (0, node_fs.readFileSync)(filePath, "utf-8");
288
- return [
289
- /["'`][^"'`]*react-grab/,
290
- /react-grab[^"'`]*["'`]/,
291
- /<[^>]*react-grab/i,
292
- /import[^;]*react-grab/i,
293
- /require[^)]*react-grab/i,
294
- /from\s+[^;]*react-grab/i,
295
- /src[^>]*react-grab/i
296
- ].some((pattern) => pattern.test(content));
356
+ return hasReactGrabSetupCode((0, node_fs.readFileSync)(filePath, "utf-8"));
297
357
  } catch {
298
358
  return false;
299
359
  }
300
360
  };
301
- const detectReactGrab = (projectRoot) => {
302
- if (readMergedDependencies(projectRoot)?.["react-grab"]) return true;
303
- return [
304
- (0, node_path.join)(projectRoot, "app", "layout.tsx"),
305
- (0, node_path.join)(projectRoot, "app", "layout.jsx"),
306
- (0, node_path.join)(projectRoot, "src", "app", "layout.tsx"),
307
- (0, node_path.join)(projectRoot, "src", "app", "layout.jsx"),
308
- (0, node_path.join)(projectRoot, "pages", "_document.tsx"),
309
- (0, node_path.join)(projectRoot, "pages", "_document.jsx"),
310
- (0, node_path.join)(projectRoot, "instrumentation-client.ts"),
311
- (0, node_path.join)(projectRoot, "instrumentation-client.js"),
312
- (0, node_path.join)(projectRoot, "src", "instrumentation-client.ts"),
313
- (0, node_path.join)(projectRoot, "src", "instrumentation-client.js"),
314
- (0, node_path.join)(projectRoot, "index.html"),
315
- (0, node_path.join)(projectRoot, "public", "index.html"),
316
- (0, node_path.join)(projectRoot, "src", "index.tsx"),
317
- (0, node_path.join)(projectRoot, "src", "index.ts"),
318
- (0, node_path.join)(projectRoot, "src", "main.tsx"),
319
- (0, node_path.join)(projectRoot, "src", "main.ts"),
320
- (0, node_path.join)(projectRoot, "src", "routes", "__root.tsx"),
321
- (0, node_path.join)(projectRoot, "src", "routes", "__root.jsx"),
322
- (0, node_path.join)(projectRoot, "app", "routes", "__root.tsx"),
323
- (0, node_path.join)(projectRoot, "app", "routes", "__root.jsx")
324
- ].some(hasReactGrabInFile);
361
+ const detectReactGrabDependency = (projectRoot) => {
362
+ const dependencies = readMergedDependencies(projectRoot);
363
+ return Boolean(dependencies?.["react-grab"]);
364
+ };
365
+ const detectReactGrabConfigured = (projectRoot) => {
366
+ return getReactGrabSetupFileCandidates(projectRoot).some(hasReactGrabSetupInFile);
325
367
  };
326
368
  const detectUnsupportedFramework = (projectRoot) => {
327
369
  const dependencies = readMergedDependencies(projectRoot);
@@ -342,13 +384,17 @@ const detectReactGrabVersion = (projectRoot) => {
342
384
  const detectProject = async (projectRoot = process.cwd()) => {
343
385
  const localFramework = detectFramework(projectRoot);
344
386
  const framework = localFramework === "unknown" ? detectFrameworkFromMonorepoRoot(projectRoot) : localFramework;
387
+ const packageManager = await detectPackageManager(projectRoot);
388
+ const isMonorepo = detectMonorepo(projectRoot) || findEnclosingMonorepoRoot(projectRoot) !== null;
389
+ const isReactGrabConfigured = detectReactGrabConfigured(projectRoot);
345
390
  return {
346
- packageManager: await detectPackageManager(projectRoot),
391
+ packageManager,
347
392
  framework,
348
393
  nextRouterType: framework === "next" ? detectNextRouterType(projectRoot) : "unknown",
349
- isMonorepo: detectMonorepo(projectRoot),
394
+ isMonorepo,
350
395
  projectRoot,
351
- hasReactGrab: detectReactGrab(projectRoot),
396
+ hasReactGrab: detectReactGrabDependency(projectRoot) || isReactGrabConfigured,
397
+ isReactGrabConfigured,
352
398
  reactGrabVersion: detectReactGrabVersion(projectRoot),
353
399
  unsupportedFramework: detectUnsupportedFramework(projectRoot)
354
400
  };
@@ -519,7 +565,7 @@ const removeSkill = async ({ cwd = process.cwd(), global = false } = {}) => {
519
565
  };
520
566
  //#endregion
521
567
  //#region src/commands/add.ts
522
- const VERSION$5 = "0.1.44-dev.f610e2f";
568
+ const VERSION$5 = "0.1.45-dev.3c3f835";
523
569
  const add = new commander.Command().name("add").alias("install").description("install the React Grab skill for your agent").option("-y, --yes", "skip confirmation prompts", false).option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).option("-g, --global", "install the skill globally instead of in the project", false).action(async (opts) => {
524
570
  console.log(`${picocolors.default.magenta("✿")} ${picocolors.default.bold("React Grab")} ${picocolors.default.gray(VERSION$5)}`);
525
571
  console.log();
@@ -674,80 +720,14 @@ const TANSTACK_EFFECT = `useEffect(() => {
674
720
  const SCRIPT_IMPORT = "import Script from \"next/script\";";
675
721
  //#endregion
676
722
  //#region src/utils/transform.ts
677
- const hasReactGrabCode = (content) => {
678
- return [
679
- /["'`][^"'`]*react-grab/,
680
- /react-grab[^"'`]*["'`]/,
681
- /<[^>]*react-grab/i,
682
- /import[^;]*react-grab/i,
683
- /require[^)]*react-grab/i,
684
- /from\s+[^;]*react-grab/i,
685
- /src[^>]*react-grab/i,
686
- /href[^>]*react-grab/i
687
- ].some((pattern) => pattern.test(content));
688
- };
689
- const findLayoutFile = (projectRoot) => {
690
- const possiblePaths = [
691
- (0, node_path.join)(projectRoot, "app", "layout.tsx"),
692
- (0, node_path.join)(projectRoot, "app", "layout.jsx"),
693
- (0, node_path.join)(projectRoot, "src", "app", "layout.tsx"),
694
- (0, node_path.join)(projectRoot, "src", "app", "layout.jsx")
695
- ];
696
- for (const filePath of possiblePaths) if ((0, node_fs.existsSync)(filePath)) return filePath;
697
- return null;
698
- };
699
- const findInstrumentationFile = (projectRoot) => {
700
- const possiblePaths = [
701
- (0, node_path.join)(projectRoot, "instrumentation-client.ts"),
702
- (0, node_path.join)(projectRoot, "instrumentation-client.js"),
703
- (0, node_path.join)(projectRoot, "src", "instrumentation-client.ts"),
704
- (0, node_path.join)(projectRoot, "src", "instrumentation-client.js")
705
- ];
706
- for (const filePath of possiblePaths) if ((0, node_fs.existsSync)(filePath)) return filePath;
707
- return null;
708
- };
709
723
  const hasReactGrabInInstrumentation = (projectRoot) => {
710
- const instrumentationPath = findInstrumentationFile(projectRoot);
711
- if (!instrumentationPath) return false;
712
- return hasReactGrabCode((0, node_fs.readFileSync)(instrumentationPath, "utf-8"));
713
- };
714
- const findDocumentFile = (projectRoot) => {
715
- const possiblePaths = [
716
- (0, node_path.join)(projectRoot, "pages", "_document.tsx"),
717
- (0, node_path.join)(projectRoot, "pages", "_document.jsx"),
718
- (0, node_path.join)(projectRoot, "src", "pages", "_document.tsx"),
719
- (0, node_path.join)(projectRoot, "src", "pages", "_document.jsx")
720
- ];
721
- for (const filePath of possiblePaths) if ((0, node_fs.existsSync)(filePath)) return filePath;
722
- return null;
723
- };
724
- const findIndexHtml = (projectRoot) => {
725
- const possiblePaths = [(0, node_path.join)(projectRoot, "index.html"), (0, node_path.join)(projectRoot, "public", "index.html")];
726
- for (const filePath of possiblePaths) if ((0, node_fs.existsSync)(filePath)) return filePath;
727
- return null;
728
- };
729
- const findEntryFile = (projectRoot) => {
730
- const possiblePaths = [
731
- (0, node_path.join)(projectRoot, "src", "index.tsx"),
732
- (0, node_path.join)(projectRoot, "src", "index.jsx"),
733
- (0, node_path.join)(projectRoot, "src", "index.ts"),
734
- (0, node_path.join)(projectRoot, "src", "index.js"),
735
- (0, node_path.join)(projectRoot, "src", "main.tsx"),
736
- (0, node_path.join)(projectRoot, "src", "main.jsx"),
737
- (0, node_path.join)(projectRoot, "src", "main.ts"),
738
- (0, node_path.join)(projectRoot, "src", "main.js")
739
- ];
740
- for (const filePath of possiblePaths) if ((0, node_fs.existsSync)(filePath)) return filePath;
741
- return null;
724
+ return findFileWithReactGrabSetup(getInstrumentationFileCandidates(projectRoot)) !== null;
742
725
  };
743
- const findTanStackRootFile = (projectRoot) => {
744
- const possiblePaths = [
745
- (0, node_path.join)(projectRoot, "src", "routes", "__root.tsx"),
746
- (0, node_path.join)(projectRoot, "src", "routes", "__root.jsx"),
747
- (0, node_path.join)(projectRoot, "app", "routes", "__root.tsx"),
748
- (0, node_path.join)(projectRoot, "app", "routes", "__root.jsx")
749
- ];
750
- for (const filePath of possiblePaths) if ((0, node_fs.existsSync)(filePath)) return filePath;
726
+ const findFileWithReactGrabSetup = (fileCandidates) => {
727
+ for (const filePath of fileCandidates) {
728
+ if (!(0, node_fs.existsSync)(filePath)) continue;
729
+ if (hasReactGrabSetupCode((0, node_fs.readFileSync)(filePath, "utf-8"))) return filePath;
730
+ }
751
731
  return null;
752
732
  };
753
733
  const alreadyConfiguredResult = (filePath) => ({
@@ -756,19 +736,19 @@ const alreadyConfiguredResult = (filePath) => ({
756
736
  message: "React Grab is already configured",
757
737
  noChanges: true
758
738
  });
759
- const transformNextAppRouter = (projectRoot, reactGrabAlreadyConfigured, force = false) => {
739
+ const transformNextAppRouter = (projectRoot, reactGrabAlreadyConfigured) => {
760
740
  const layoutPath = findLayoutFile(projectRoot);
761
741
  if (!layoutPath) return {
762
742
  success: false,
763
743
  filePath: "",
764
- message: "Could not find app/layout.tsx or app/layout.jsx"
744
+ message: "Could not find app/layout.tsx, app/layout.jsx, app/layout.ts, or app/layout.js"
765
745
  };
766
746
  const originalContent = (0, node_fs.readFileSync)(layoutPath, "utf-8");
767
747
  let newContent = originalContent;
768
- const hasReactGrabInFile = hasReactGrabCode(originalContent);
748
+ const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
769
749
  const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
770
- if (!force && hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(layoutPath);
771
- if (!force && (hasReactGrabInFile || hasReactGrabInInstrumentationFile)) return {
750
+ if (hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(layoutPath);
751
+ if (hasReactGrabInFile || hasReactGrabInInstrumentationFile) return {
772
752
  success: true,
773
753
  filePath: layoutPath,
774
754
  message: "React Grab is already installed" + (hasReactGrabInInstrumentationFile ? " in instrumentation-client" : " in this file"),
@@ -793,19 +773,19 @@ const transformNextAppRouter = (projectRoot, reactGrabAlreadyConfigured, force =
793
773
  newContent
794
774
  };
795
775
  };
796
- const transformNextPagesRouter = (projectRoot, reactGrabAlreadyConfigured, force = false) => {
776
+ const transformNextPagesRouter = (projectRoot, reactGrabAlreadyConfigured) => {
797
777
  const documentPath = findDocumentFile(projectRoot);
798
778
  if (!documentPath) return {
799
779
  success: false,
800
780
  filePath: "",
801
- message: "Could not find pages/_document.tsx or pages/_document.jsx.\n\nTo set up React Grab with Pages Router, create pages/_document.tsx with:\n\n import { Html, Head, Main, NextScript } from \"next/document\";\n import Script from \"next/script\";\n\n export default function Document() {\n return (\n <Html>\n <Head>\n {process.env.NODE_ENV === \"development\" && (\n <Script src=\"//unpkg.com/react-grab/dist/index.global.js\" strategy=\"beforeInteractive\" />\n )}\n </Head>\n <body>\n <Main />\n <NextScript />\n </body>\n </Html>\n );\n }"
781
+ message: "Could not find pages/_document.tsx, pages/_document.jsx, pages/_document.ts, or pages/_document.js.\n\nTo set up React Grab with Pages Router, create pages/_document.tsx with:\n\n import { Html, Head, Main, NextScript } from \"next/document\";\n import Script from \"next/script\";\n\n export default function Document() {\n return (\n <Html>\n <Head>\n {process.env.NODE_ENV === \"development\" && (\n <Script src=\"//unpkg.com/react-grab/dist/index.global.js\" strategy=\"beforeInteractive\" />\n )}\n </Head>\n <body>\n <Main />\n <NextScript />\n </body>\n </Html>\n );\n }"
802
782
  };
803
783
  const originalContent = (0, node_fs.readFileSync)(documentPath, "utf-8");
804
784
  let newContent = originalContent;
805
- const hasReactGrabInFile = hasReactGrabCode(originalContent);
785
+ const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
806
786
  const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
807
- if (!force && hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(documentPath);
808
- if (!force && (hasReactGrabInFile || hasReactGrabInInstrumentationFile)) return {
787
+ if (hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(documentPath);
788
+ if (hasReactGrabInFile || hasReactGrabInInstrumentationFile) return {
809
789
  success: true,
810
790
  filePath: documentPath,
811
791
  message: "React Grab is already installed" + (hasReactGrabInInstrumentationFile ? " in instrumentation-client" : " in this file"),
@@ -826,7 +806,7 @@ const transformNextPagesRouter = (projectRoot, reactGrabAlreadyConfigured, force
826
806
  };
827
807
  };
828
808
  const checkExistingInstallation = (filePath, reactGrabAlreadyConfigured) => {
829
- if (!hasReactGrabCode((0, node_fs.readFileSync)(filePath, "utf-8"))) return null;
809
+ if (!hasReactGrabSetupCode((0, node_fs.readFileSync)(filePath, "utf-8"))) return null;
830
810
  return {
831
811
  success: true,
832
812
  filePath,
@@ -834,24 +814,20 @@ const checkExistingInstallation = (filePath, reactGrabAlreadyConfigured) => {
834
814
  noChanges: true
835
815
  };
836
816
  };
837
- const transformVite = (projectRoot, reactGrabAlreadyConfigured, force = false) => {
817
+ const transformVite = (projectRoot, reactGrabAlreadyConfigured) => {
838
818
  const entryPath = findEntryFile(projectRoot);
839
- if (!force) {
840
- const indexPath = findIndexHtml(projectRoot);
841
- if (indexPath) {
842
- const existingResult = checkExistingInstallation(indexPath, reactGrabAlreadyConfigured);
843
- if (existingResult) return existingResult;
844
- }
819
+ const indexPath = findIndexHtml(projectRoot);
820
+ if (indexPath) {
821
+ const existingResult = checkExistingInstallation(indexPath, reactGrabAlreadyConfigured);
822
+ if (existingResult) return existingResult;
845
823
  }
846
824
  if (!entryPath) return {
847
825
  success: false,
848
826
  filePath: "",
849
827
  message: "Could not find entry file (src/index.tsx, src/main.tsx, etc.)"
850
828
  };
851
- if (!force) {
852
- const existingResult = checkExistingInstallation(entryPath, reactGrabAlreadyConfigured);
853
- if (existingResult) return existingResult;
854
- }
829
+ const existingResult = checkExistingInstallation(entryPath, reactGrabAlreadyConfigured);
830
+ if (existingResult) return existingResult;
855
831
  const originalContent = (0, node_fs.readFileSync)(entryPath, "utf-8");
856
832
  return {
857
833
  success: true,
@@ -861,17 +837,15 @@ const transformVite = (projectRoot, reactGrabAlreadyConfigured, force = false) =
861
837
  newContent: `${VITE_IMPORT}\n\n${originalContent}`
862
838
  };
863
839
  };
864
- const transformWebpack = (projectRoot, reactGrabAlreadyConfigured, force = false) => {
840
+ const transformWebpack = (projectRoot, reactGrabAlreadyConfigured) => {
865
841
  const entryPath = findEntryFile(projectRoot);
866
842
  if (!entryPath) return {
867
843
  success: false,
868
844
  filePath: "",
869
845
  message: "Could not find entry file (src/index.tsx, src/main.tsx, etc.)"
870
846
  };
871
- if (!force) {
872
- const existingResult = checkExistingInstallation(entryPath, reactGrabAlreadyConfigured);
873
- if (existingResult) return existingResult;
874
- }
847
+ const existingResult = checkExistingInstallation(entryPath, reactGrabAlreadyConfigured);
848
+ if (existingResult) return existingResult;
875
849
  const originalContent = (0, node_fs.readFileSync)(entryPath, "utf-8");
876
850
  return {
877
851
  success: true,
@@ -881,7 +855,7 @@ const transformWebpack = (projectRoot, reactGrabAlreadyConfigured, force = false
881
855
  newContent: `${WEBPACK_IMPORT}\n\n${originalContent}`
882
856
  };
883
857
  };
884
- const transformTanStack = (projectRoot, reactGrabAlreadyConfigured, force = false) => {
858
+ const transformTanStack = (projectRoot, reactGrabAlreadyConfigured) => {
885
859
  const rootPath = findTanStackRootFile(projectRoot);
886
860
  if (!rootPath) return {
887
861
  success: false,
@@ -890,9 +864,9 @@ const transformTanStack = (projectRoot, reactGrabAlreadyConfigured, force = fals
890
864
  };
891
865
  const originalContent = (0, node_fs.readFileSync)(rootPath, "utf-8");
892
866
  let newContent = originalContent;
893
- const hasReactGrabInFile = hasReactGrabCode(originalContent);
894
- if (!force && hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(rootPath);
895
- if (!force && hasReactGrabInFile) return {
867
+ const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
868
+ if (hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(rootPath);
869
+ if (hasReactGrabInFile) return {
896
870
  success: true,
897
871
  filePath: rootPath,
898
872
  message: "React Grab is already installed in this file",
@@ -935,14 +909,14 @@ const hasFrameworkEntryPoint = (projectRoot, framework, nextRouterType) => {
935
909
  default: return false;
936
910
  }
937
911
  };
938
- const previewTransform = (projectRoot, framework, nextRouterType, reactGrabAlreadyConfigured = false, force = false) => {
912
+ const previewTransform = (projectRoot, framework, nextRouterType, reactGrabAlreadyConfigured = false) => {
939
913
  switch (framework) {
940
914
  case "next":
941
- if (nextRouterType === "app") return transformNextAppRouter(projectRoot, reactGrabAlreadyConfigured, force);
942
- return transformNextPagesRouter(projectRoot, reactGrabAlreadyConfigured, force);
943
- case "vite": return transformVite(projectRoot, reactGrabAlreadyConfigured, force);
944
- case "tanstack": return transformTanStack(projectRoot, reactGrabAlreadyConfigured, force);
945
- case "webpack": return transformWebpack(projectRoot, reactGrabAlreadyConfigured, force);
915
+ if (nextRouterType === "app") return transformNextAppRouter(projectRoot, reactGrabAlreadyConfigured);
916
+ return transformNextPagesRouter(projectRoot, reactGrabAlreadyConfigured);
917
+ case "vite": return transformVite(projectRoot, reactGrabAlreadyConfigured);
918
+ case "tanstack": return transformTanStack(projectRoot, reactGrabAlreadyConfigured);
919
+ case "webpack": return transformWebpack(projectRoot, reactGrabAlreadyConfigured);
946
920
  default: return {
947
921
  success: false,
948
922
  filePath: "",
@@ -996,18 +970,24 @@ const formatOptionsAsJson = (options) => {
996
970
  };
997
971
  const findReactGrabFile = (projectRoot, framework, nextRouterType) => {
998
972
  switch (framework) {
999
- case "next":
1000
- if (nextRouterType === "app") return findLayoutFile(projectRoot);
1001
- return findDocumentFile(projectRoot);
973
+ case "next": {
974
+ const primaryFile = nextRouterType === "app" ? findLayoutFile(projectRoot) : findDocumentFile(projectRoot);
975
+ const primarySetupFile = findFileWithReactGrabSetup(nextRouterType === "app" ? getLayoutFileCandidates(projectRoot) : getDocumentFileCandidates(projectRoot));
976
+ if (primarySetupFile) return primarySetupFile;
977
+ const instrumentationFile = findFileWithReactGrabSetup(getInstrumentationFileCandidates(projectRoot));
978
+ if (instrumentationFile) return instrumentationFile;
979
+ return primaryFile;
980
+ }
1002
981
  case "vite": {
1003
982
  const entryFile = findEntryFile(projectRoot);
1004
- if (entryFile && hasReactGrabCode((0, node_fs.readFileSync)(entryFile, "utf-8"))) return entryFile;
1005
- const indexHtml = findIndexHtml(projectRoot);
1006
- if (indexHtml && hasReactGrabCode((0, node_fs.readFileSync)(indexHtml, "utf-8"))) return indexHtml;
983
+ const entrySetupFile = findFileWithReactGrabSetup(getEntryFileCandidates(projectRoot));
984
+ if (entrySetupFile) return entrySetupFile;
985
+ const indexHtml = findFileWithReactGrabSetup(getIndexHtmlCandidates(projectRoot));
986
+ if (indexHtml) return indexHtml;
1007
987
  return entryFile;
1008
988
  }
1009
- case "tanstack": return findTanStackRootFile(projectRoot);
1010
- case "webpack": return findEntryFile(projectRoot);
989
+ case "tanstack": return findFileWithReactGrabSetup(getTanStackRootFileCandidates(projectRoot)) ?? findTanStackRootFile(projectRoot);
990
+ case "webpack": return findFileWithReactGrabSetup(getEntryFileCandidates(projectRoot)) ?? findEntryFile(projectRoot);
1011
991
  default: return null;
1012
992
  }
1013
993
  };
@@ -1035,13 +1015,14 @@ const addOptionsToNextScript = (originalContent, options, filePath) => {
1035
1015
  };
1036
1016
  };
1037
1017
  const addOptionsToDynamicImport = (originalContent, options, filePath) => {
1038
- const reactGrabImportWithInitMatch = originalContent.match(/import\s*\(\s*["']react-grab["']\s*\)(?:\.then\s*\(\s*\(m\)\s*=>\s*m\.init\s*\([^)]*\)\s*\))?/);
1018
+ const reactGrabImportWithInitMatch = originalContent.match(/(void\s+)?import\s*\(\s*["']react-grab(?:\/[^"']+)?["']\s*\)(?:\.then\s*\(\s*(?:\(m\)\s*=>\s*m\.init\s*\([^)]*\)|\(\{\s*init\s*\}\)\s*=>\s*init\s*\([^)]*\))\s*\))?/);
1039
1019
  if (!reactGrabImportWithInitMatch) return {
1040
1020
  success: false,
1041
1021
  filePath,
1042
1022
  message: "Could not find React Grab import"
1043
1023
  };
1044
- const newImport = `import("react-grab").then((m) => m.init(${formatOptionsAsJson(options)}))`;
1024
+ const optionsJson = formatOptionsAsJson(options);
1025
+ const newImport = `${reactGrabImportWithInitMatch[1] ?? ""}import("react-grab").then((m) => m.init(${optionsJson}))`;
1045
1026
  return {
1046
1027
  success: true,
1047
1028
  filePath,
@@ -1051,13 +1032,14 @@ const addOptionsToDynamicImport = (originalContent, options, filePath) => {
1051
1032
  };
1052
1033
  };
1053
1034
  const addOptionsToTanStackImport = (originalContent, options, filePath) => {
1054
- const reactGrabImportWithInitMatch = originalContent.match(/(?:void\s+import\s*\(\s*["']react-grab["']\s*\)|import\s*\(\s*["']react-grab\/core["']\s*\)\.then\s*\(\s*\(\s*\{\s*init\s*\}\s*\)\s*=>\s*init\s*\([^)]*\)\s*\))/);
1035
+ const reactGrabImportWithInitMatch = originalContent.match(/(?:(void\s+)?import\s*\(\s*["']react-grab\/core["']\s*\)\.then\s*\(\s*(?:\(\s*\{\s*init\s*\}\s*\)\s*=>\s*init\s*\([^)]*\)|\(m\)\s*=>\s*m\.init\s*\([^)]*\))\s*\)|(void\s+)?import\s*\(\s*["']react-grab(?!\/core)(?:\/[^"']+)?["']\s*\))/);
1055
1036
  if (!reactGrabImportWithInitMatch) return {
1056
1037
  success: false,
1057
1038
  filePath,
1058
1039
  message: "Could not find React Grab import"
1059
1040
  };
1060
- const newImport = `import("react-grab/core").then(({ init }) => init(${formatOptionsAsJson(options)}))`;
1041
+ const optionsJson = formatOptionsAsJson(options);
1042
+ const newImport = `${reactGrabImportWithInitMatch[1] ?? reactGrabImportWithInitMatch[2] ?? ""}import("react-grab/core").then(({ init }) => init(${optionsJson}))`;
1061
1043
  return {
1062
1044
  success: true,
1063
1045
  filePath,
@@ -1066,6 +1048,11 @@ const addOptionsToTanStackImport = (originalContent, options, filePath) => {
1066
1048
  newContent: originalContent.replace(reactGrabImportWithInitMatch[0], newImport)
1067
1049
  };
1068
1050
  };
1051
+ const addOptionsToAnyImport = (originalContent, options, filePath) => {
1052
+ const dynamicImportResult = addOptionsToDynamicImport(originalContent, options, filePath);
1053
+ if (dynamicImportResult.success) return dynamicImportResult;
1054
+ return addOptionsToTanStackImport(originalContent, options, filePath);
1055
+ };
1069
1056
  const previewOptionsTransform = (projectRoot, framework, nextRouterType, options) => {
1070
1057
  const filePath = findReactGrabFile(projectRoot, framework, nextRouterType);
1071
1058
  if (!filePath) return {
@@ -1074,13 +1061,15 @@ const previewOptionsTransform = (projectRoot, framework, nextRouterType, options
1074
1061
  message: "Could not find file containing React Grab configuration"
1075
1062
  };
1076
1063
  const originalContent = (0, node_fs.readFileSync)(filePath, "utf-8");
1077
- if (!hasReactGrabCode(originalContent)) return {
1064
+ if (!hasReactGrabSetupCode(originalContent)) return {
1078
1065
  success: false,
1079
1066
  filePath,
1080
1067
  message: "Could not find React Grab code in the file"
1081
1068
  };
1082
1069
  switch (framework) {
1083
- case "next": return addOptionsToNextScript(originalContent, options, filePath);
1070
+ case "next":
1071
+ if (isInstrumentationFile(filePath)) return addOptionsToAnyImport(originalContent, options, filePath);
1072
+ return addOptionsToNextScript(originalContent, options, filePath);
1084
1073
  case "vite": return addOptionsToDynamicImport(originalContent, options, filePath);
1085
1074
  case "tanstack": return addOptionsToTanStackImport(originalContent, options, filePath);
1086
1075
  case "webpack": return addOptionsToDynamicImport(originalContent, options, filePath);
@@ -1138,7 +1127,7 @@ const formatActivationKeyDisplay = (activationKey) => {
1138
1127
  };
1139
1128
  //#endregion
1140
1129
  //#region src/commands/configure.ts
1141
- const VERSION$4 = "0.1.44-dev.f610e2f";
1130
+ const VERSION$4 = "0.1.45-dev.3c3f835";
1142
1131
  const isMac = process.platform === "darwin";
1143
1132
  const META_LABEL = isMac ? "Cmd" : "Win";
1144
1133
  const ALT_LABEL = isMac ? "Option" : "Alt";
@@ -1458,6 +1447,13 @@ const configure = new commander.Command().name("configure").alias("config").desc
1458
1447
  logger.break();
1459
1448
  process.exit(1);
1460
1449
  }
1450
+ if (!projectInfo.isReactGrabConfigured) {
1451
+ preflightSpinner.fail("React Grab is installed, but setup is missing.");
1452
+ logger.break();
1453
+ logger.error(`Run ${highlighter.info("react-grab init")} to add the setup script/import before configuring options.`);
1454
+ logger.break();
1455
+ process.exit(1);
1456
+ }
1461
1457
  preflightSpinner.succeed();
1462
1458
  if (opts.cdn) {
1463
1459
  const result = previewCdnTransform(projectInfo.projectRoot, projectInfo.framework, projectInfo.nextRouterType, opts.cdn);
@@ -1763,7 +1759,7 @@ const isTelemetryEnabled = () => {
1763
1759
  };
1764
1760
  //#endregion
1765
1761
  //#region src/commands/init.ts
1766
- const VERSION$3 = "0.1.44-dev.f610e2f";
1762
+ const VERSION$3 = "0.1.45-dev.3c3f835";
1767
1763
  const REPORT_URL = "https://react-grab.com/api/report-cli";
1768
1764
  const DOCS_URL = "https://github.com/aidenybai/react-grab";
1769
1765
  const reportToCli = (type, config, error) => {
@@ -1831,7 +1827,7 @@ const failWithManualSetup = (failingSpinner, message, { listSupportedFrameworks
1831
1827
  logger.break();
1832
1828
  process.exit(1);
1833
1829
  };
1834
- const init = new commander.Command().name("init").alias("setup").description("initialize React Grab in your project").option("-y, --yes", "skip confirmation prompts", false).option("-f, --force", "force overwrite existing config", false).option("-k, --key <key>", "shortcut (e.g., Meta+K, Ctrl+Shift+G, Space)").option("--skip-install", "skip package installation", false).option("--pkg <pkg>", "custom package URL for CLI (e.g., grab)").option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).option("-g, --global", "install the skill globally instead of in the project", false).action(async (opts) => {
1830
+ const init = new commander.Command().name("init").alias("setup").description("initialize React Grab in your project").option("-y, --yes", "skip confirmation prompts", false).option("-f, --force", "re-run setup checks even when React Grab is already configured", false).option("-k, --key <key>", "shortcut (e.g., Meta+K, Ctrl+Shift+G, Space)").option("--skip-install", "skip package installation", false).option("--pkg <pkg>", "custom package URL for CLI (e.g., grab)").option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).option("-g, --global", "install the skill globally instead of in the project", false).action(async (opts) => {
1835
1831
  console.log(`${picocolors.default.magenta("✿")} ${picocolors.default.bold("React Grab")} ${picocolors.default.gray(VERSION$3)}`);
1836
1832
  console.log();
1837
1833
  try {
@@ -1845,12 +1841,12 @@ const init = new commander.Command().name("init").alias("setup").description("in
1845
1841
  }
1846
1842
  const preflightSpinner = spinner("Preflight checks.").start();
1847
1843
  const projectInfo = await detectProject(cwd);
1848
- if (projectInfo.hasReactGrab && !opts.force) {
1844
+ if (projectInfo.isReactGrabConfigured && !opts.force) {
1849
1845
  preflightSpinner.succeed();
1850
1846
  if (isNonInteractive) {
1851
1847
  logger.break();
1852
1848
  logger.warn("React Grab is already installed.");
1853
- logger.log(`Use ${highlighter.info("--force")} to reconfigure, or remove ${highlighter.info("--yes")} for interactive mode.`);
1849
+ logger.log(`Use ${highlighter.info("--force")} to re-run setup checks, or remove ${highlighter.info("--yes")} for interactive mode.`);
1854
1850
  logger.break();
1855
1851
  process.exit(0);
1856
1852
  }
@@ -2065,7 +2061,7 @@ const init = new commander.Command().name("init").alias("setup").description("in
2065
2061
  cwd
2066
2062
  });
2067
2063
  }
2068
- const result = previewTransform(projectInfo.projectRoot, finalFramework, finalNextRouterType, false, opts.force);
2064
+ const result = previewTransform(projectInfo.projectRoot, finalFramework, finalNextRouterType, projectInfo.isReactGrabConfigured);
2069
2065
  if (!result.success) {
2070
2066
  logger.break();
2071
2067
  logger.error(result.message);
@@ -2100,7 +2096,8 @@ const init = new commander.Command().name("init").alias("setup").description("in
2100
2096
  if (!opts.skipInstall && shouldInstallReactGrab) await installPackagesWithFeedback(getPackagesToInstall(shouldInstallReactGrab), finalPackageManager, projectInfo.projectRoot);
2101
2097
  if (hasLayoutChanges) applyTransformWithFeedback(result);
2102
2098
  logger.break();
2103
- logger.log(`${highlighter.success("Success!")} React Grab has been installed.`);
2099
+ if (hasLayoutChanges) logger.log(`${highlighter.success("Success!")} React Grab has been installed.`);
2100
+ else logger.log(`${highlighter.success("Success!")} ${result.message}.`);
2104
2101
  logger.log("You may now start your development server.");
2105
2102
  logger.break();
2106
2103
  reportToCli("completed", {
@@ -2713,7 +2710,7 @@ const pull = new commander.Command().name("pull").description("start the watcher
2713
2710
  });
2714
2711
  //#endregion
2715
2712
  //#region src/commands/remove.ts
2716
- const VERSION$2 = "0.1.44-dev.f610e2f";
2713
+ const VERSION$2 = "0.1.45-dev.3c3f835";
2717
2714
  const remove = new commander.Command().name("remove").description("uninstall the React Grab skill from your agent").option("-c, --cwd <cwd>", "working directory (defaults to current directory)", process.cwd()).option("-g, --global", "remove the globally-installed skill instead of the project's", false).action(async (opts) => {
2718
2715
  console.log(`${picocolors.default.magenta("✿")} ${picocolors.default.bold("React Grab")} ${picocolors.default.gray(VERSION$2)}`);
2719
2716
  console.log();
@@ -2741,7 +2738,7 @@ const stop = new commander.Command().name("stop").description("stop the React Gr
2741
2738
  });
2742
2739
  //#endregion
2743
2740
  //#region src/commands/upgrade.ts
2744
- const VERSION$1 = "0.1.44-dev.f610e2f";
2741
+ const VERSION$1 = "0.1.45-dev.3c3f835";
2745
2742
  const NPM_REGISTRY_URL = "https://registry.npmjs.org/react-grab/latest";
2746
2743
  const fetchLatestVersion = async () => {
2747
2744
  try {
@@ -2853,7 +2850,7 @@ const watch = new commander.Command().name("watch").description("run the React G
2853
2850
  });
2854
2851
  //#endregion
2855
2852
  //#region src/cli.ts
2856
- const VERSION = "0.1.44-dev.f610e2f";
2853
+ const VERSION = "0.1.45-dev.3c3f835";
2857
2854
  const VERSION_API_URL = "https://www.react-grab.com/api/version";
2858
2855
  process.on("SIGINT", () => process.exit(0));
2859
2856
  process.on("SIGTERM", () => process.exit(0));