@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 +172 -175
- package/dist/cli.js +172 -175
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/skills/react-grab/SKILL.md +3 -3
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
|
353
|
+
const hasReactGrabSetupInFile = (filePath) => {
|
|
285
354
|
if (!(0, node_fs.existsSync)(filePath)) return false;
|
|
286
355
|
try {
|
|
287
|
-
|
|
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
|
|
302
|
-
|
|
303
|
-
return [
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
391
|
+
packageManager,
|
|
347
392
|
framework,
|
|
348
393
|
nextRouterType: framework === "next" ? detectNextRouterType(projectRoot) : "unknown",
|
|
349
|
-
isMonorepo
|
|
394
|
+
isMonorepo,
|
|
350
395
|
projectRoot,
|
|
351
|
-
hasReactGrab:
|
|
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.
|
|
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
|
-
|
|
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
|
|
744
|
-
const
|
|
745
|
-
(0,
|
|
746
|
-
(0,
|
|
747
|
-
|
|
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
|
|
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.
|
|
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 =
|
|
748
|
+
const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
|
|
769
749
|
const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
|
|
770
|
-
if (
|
|
771
|
-
if (
|
|
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
|
|
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.
|
|
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 =
|
|
785
|
+
const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
|
|
806
786
|
const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
|
|
807
|
-
if (
|
|
808
|
-
if (
|
|
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 (!
|
|
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
|
|
817
|
+
const transformVite = (projectRoot, reactGrabAlreadyConfigured) => {
|
|
838
818
|
const entryPath = findEntryFile(projectRoot);
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
-
|
|
852
|
-
|
|
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
|
|
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
|
-
|
|
872
|
-
|
|
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
|
|
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 =
|
|
894
|
-
if (
|
|
895
|
-
if (
|
|
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
|
|
912
|
+
const previewTransform = (projectRoot, framework, nextRouterType, reactGrabAlreadyConfigured = false) => {
|
|
939
913
|
switch (framework) {
|
|
940
914
|
case "next":
|
|
941
|
-
if (nextRouterType === "app") return transformNextAppRouter(projectRoot, reactGrabAlreadyConfigured
|
|
942
|
-
return transformNextPagesRouter(projectRoot, reactGrabAlreadyConfigured
|
|
943
|
-
case "vite": return transformVite(projectRoot, reactGrabAlreadyConfigured
|
|
944
|
-
case "tanstack": return transformTanStack(projectRoot, reactGrabAlreadyConfigured
|
|
945
|
-
case "webpack": return transformWebpack(projectRoot, reactGrabAlreadyConfigured
|
|
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
|
-
|
|
1001
|
-
|
|
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
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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
|
|
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
|
|
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+
|
|
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
|
|
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 (!
|
|
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":
|
|
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.
|
|
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.
|
|
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", "
|
|
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.
|
|
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
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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));
|