@react-grab/cli 0.1.46 → 0.1.47-dev.bc3a591

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
@@ -1,43 +1,17 @@
1
1
  #!/usr/bin/env node
2
- //#region \0rolldown/runtime.js
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
- get: ((k) => from[k]).bind(null, key),
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
- value: mod,
21
- enumerable: true
22
- }) : target, mod));
23
- //#endregion
2
+ const require_install = require("./install-DlD3sLtm.cjs");
24
3
  let commander = require("commander");
25
4
  let node_path = require("node:path");
26
- node_path = __toESM(node_path, 1);
5
+ node_path = require_install.__toESM(node_path, 1);
27
6
  let picocolors = require("picocolors");
28
- picocolors = __toESM(picocolors, 1);
7
+ picocolors = require_install.__toESM(picocolors, 1);
29
8
  let node_fs = require("node:fs");
30
- node_fs = __toESM(node_fs, 1);
31
- let package_manager_detector_detect = require("package-manager-detector/detect");
32
- let ignore = require("ignore");
33
- ignore = __toESM(ignore, 1);
9
+ node_fs = require_install.__toESM(node_fs, 1);
34
10
  let node_url = require("node:url");
35
- let agent_install_skill = require("agent-install/skill");
36
11
  let prompts = require("prompts");
37
- prompts = __toESM(prompts, 1);
12
+ prompts = require_install.__toESM(prompts, 1);
38
13
  let ora = require("ora");
39
- ora = __toESM(ora, 1);
40
- let tinyexec = require("tinyexec");
14
+ ora = require_install.__toESM(ora, 1);
41
15
  let node_child_process = require("node:child_process");
42
16
  let node_crypto = require("node:crypto");
43
17
  //#region src/utils/is-non-interactive.ts
@@ -53,353 +27,6 @@ const AGENT_ENVIRONMENT_VARIABLES = [
53
27
  const isEnvironmentVariableSet = (variable) => Boolean(process.env[variable]);
54
28
  const detectNonInteractive = (yesFlag) => yesFlag || AGENT_ENVIRONMENT_VARIABLES.some(isEnvironmentVariableSet) || !process.stdin.isTTY;
55
29
  //#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
121
- //#region src/utils/detect.ts
122
- const VALID_PACKAGE_MANAGERS = new Set([
123
- "npm",
124
- "yarn",
125
- "pnpm",
126
- "bun"
127
- ]);
128
- const detectPackageManager = async (projectRoot) => {
129
- const result = await (0, package_manager_detector_detect.detect)({ cwd: projectRoot });
130
- if (result?.agent) {
131
- const managerName = result.agent.split("@")[0];
132
- if (VALID_PACKAGE_MANAGERS.has(managerName)) return managerName;
133
- }
134
- return "npm";
135
- };
136
- const CONFIG_EXTENSIONS = [
137
- "ts",
138
- "mts",
139
- "cts",
140
- "js",
141
- "mjs",
142
- "cjs"
143
- ];
144
- const hasConfigFile = (projectRoot, configBaseName) => CONFIG_EXTENSIONS.some((extension) => (0, node_fs.existsSync)((0, node_path.join)(projectRoot, `${configBaseName}.${extension}`)));
145
- const readMergedDependencies = (projectRoot) => {
146
- const packageJsonPath = (0, node_path.join)(projectRoot, "package.json");
147
- if (!(0, node_fs.existsSync)(packageJsonPath)) return null;
148
- try {
149
- const packageJson = JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8"));
150
- return {
151
- ...packageJson.dependencies,
152
- ...packageJson.devDependencies
153
- };
154
- } catch {
155
- return null;
156
- }
157
- };
158
- const detectFrameworkFromDependencies = (dependencies) => {
159
- if (!dependencies) return "unknown";
160
- if (dependencies["next"]) return "next";
161
- if (dependencies["@tanstack/react-start"]) return "tanstack";
162
- if (dependencies["vite"]) return "vite";
163
- if (dependencies["webpack"]) return "webpack";
164
- return "unknown";
165
- };
166
- const detectFrameworkFromConfigFiles = (projectRoot) => {
167
- if (hasConfigFile(projectRoot, "next.config")) return "next";
168
- if (hasConfigFile(projectRoot, "app.config")) return "tanstack";
169
- if (hasConfigFile(projectRoot, "vite.config")) return "vite";
170
- if (hasConfigFile(projectRoot, "webpack.config")) return "webpack";
171
- return "unknown";
172
- };
173
- const findEnclosingMonorepoRoot = (projectRoot) => {
174
- let currentDirectory = (0, node_path.dirname)(projectRoot);
175
- while (currentDirectory !== (0, node_path.dirname)(currentDirectory)) {
176
- if (detectMonorepo(currentDirectory)) return currentDirectory;
177
- currentDirectory = (0, node_path.dirname)(currentDirectory);
178
- }
179
- return null;
180
- };
181
- const detectFramework = (projectRoot) => {
182
- const localFramework = detectFrameworkFromDependencies(readMergedDependencies(projectRoot));
183
- if (localFramework !== "unknown") return localFramework;
184
- return detectFrameworkFromConfigFiles(projectRoot);
185
- };
186
- const detectFrameworkFromMonorepoRoot = (projectRoot) => {
187
- const monorepoRoot = findEnclosingMonorepoRoot(projectRoot);
188
- if (!monorepoRoot) return "unknown";
189
- return detectFrameworkFromDependencies(readMergedDependencies(monorepoRoot));
190
- };
191
- const detectNextRouterType = (projectRoot) => {
192
- const hasAppDir = (0, node_fs.existsSync)((0, node_path.join)(projectRoot, "app"));
193
- const hasSrcAppDir = (0, node_fs.existsSync)((0, node_path.join)(projectRoot, "src", "app"));
194
- const hasPagesDir = (0, node_fs.existsSync)((0, node_path.join)(projectRoot, "pages"));
195
- const hasSrcPagesDir = (0, node_fs.existsSync)((0, node_path.join)(projectRoot, "src", "pages"));
196
- if (hasAppDir || hasSrcAppDir) return "app";
197
- if (hasPagesDir || hasSrcPagesDir) return "pages";
198
- return "unknown";
199
- };
200
- const detectMonorepo = (projectRoot) => {
201
- if ((0, node_fs.existsSync)((0, node_path.join)(projectRoot, "pnpm-workspace.yaml"))) return true;
202
- if ((0, node_fs.existsSync)((0, node_path.join)(projectRoot, "lerna.json"))) return true;
203
- const packageJsonPath = (0, node_path.join)(projectRoot, "package.json");
204
- if ((0, node_fs.existsSync)(packageJsonPath)) try {
205
- if (JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8")).workspaces) return true;
206
- } catch {
207
- return false;
208
- }
209
- return false;
210
- };
211
- const getWorkspacePatterns = (projectRoot) => {
212
- const patterns = [];
213
- const pnpmWorkspacePath = (0, node_path.join)(projectRoot, "pnpm-workspace.yaml");
214
- if ((0, node_fs.existsSync)(pnpmWorkspacePath)) {
215
- const lines = (0, node_fs.readFileSync)(pnpmWorkspacePath, "utf-8").split("\n");
216
- let inPackages = false;
217
- for (const line of lines) {
218
- if (line.match(/^packages:\s*$/)) {
219
- inPackages = true;
220
- continue;
221
- }
222
- if (inPackages) {
223
- if (line.match(/^[a-zA-Z]/) || line.trim() === "") {
224
- if (line.match(/^[a-zA-Z]/)) inPackages = false;
225
- continue;
226
- }
227
- const match = line.match(/^\s*-\s*['"]?([^'"#\n]+?)['"]?\s*$/);
228
- if (match) patterns.push(match[1].trim());
229
- }
230
- }
231
- }
232
- const lernaJsonPath = (0, node_path.join)(projectRoot, "lerna.json");
233
- if ((0, node_fs.existsSync)(lernaJsonPath)) try {
234
- const lernaJson = JSON.parse((0, node_fs.readFileSync)(lernaJsonPath, "utf-8"));
235
- if (Array.isArray(lernaJson.packages)) patterns.push(...lernaJson.packages);
236
- } catch {}
237
- const packageJsonPath = (0, node_path.join)(projectRoot, "package.json");
238
- if ((0, node_fs.existsSync)(packageJsonPath)) try {
239
- const packageJson = JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8"));
240
- if (Array.isArray(packageJson.workspaces)) patterns.push(...packageJson.workspaces);
241
- else if (packageJson.workspaces?.packages) patterns.push(...packageJson.workspaces.packages);
242
- } catch {}
243
- return [...new Set(patterns)];
244
- };
245
- const expandWorkspacePattern = (projectRoot, pattern) => {
246
- const isGlob = pattern.endsWith("/*");
247
- const basePath = (0, node_path.join)(projectRoot, pattern.replace(/\/\*$/, ""));
248
- if (!(0, node_fs.existsSync)(basePath)) return [];
249
- if (!isGlob) return (0, node_fs.existsSync)((0, node_path.join)(basePath, "package.json")) ? [basePath] : [];
250
- const results = [];
251
- try {
252
- const entries = (0, node_fs.readdirSync)(basePath, { withFileTypes: true });
253
- for (const entry of entries) {
254
- if (!entry.isDirectory()) continue;
255
- if ((0, node_fs.existsSync)((0, node_path.join)(basePath, entry.name, "package.json"))) results.push((0, node_path.join)(basePath, entry.name));
256
- }
257
- } catch {
258
- return results;
259
- }
260
- return results;
261
- };
262
- const hasReactDependency = (projectPath) => {
263
- const dependencies = readMergedDependencies(projectPath);
264
- if (!dependencies) return false;
265
- return Boolean(dependencies["react"] || dependencies["react-dom"]);
266
- };
267
- const buildReactProject = (projectPath) => {
268
- const framework = detectFramework(projectPath);
269
- if (!hasReactDependency(projectPath) && framework === "unknown") return null;
270
- let name = (0, node_path.basename)(projectPath);
271
- const packageJsonPath = (0, node_path.join)(projectPath, "package.json");
272
- try {
273
- name = JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8")).name || name;
274
- } catch {}
275
- return {
276
- name,
277
- path: projectPath,
278
- framework
279
- };
280
- };
281
- const findWorkspaceProjects = (projectRoot) => {
282
- const patterns = getWorkspacePatterns(projectRoot);
283
- const projects = [];
284
- for (const pattern of patterns) for (const projectPath of expandWorkspacePattern(projectRoot, pattern)) {
285
- const project = buildReactProject(projectPath);
286
- if (project) projects.push(project);
287
- }
288
- return projects;
289
- };
290
- const ALWAYS_IGNORED_DIRECTORIES = [
291
- "node_modules",
292
- ".git",
293
- ".next",
294
- ".cache",
295
- ".turbo",
296
- "dist",
297
- "build",
298
- "coverage",
299
- "test-results"
300
- ];
301
- const loadGitignore = (projectRoot) => {
302
- const ignorer = (0, ignore.default)().add(ALWAYS_IGNORED_DIRECTORIES);
303
- const gitignorePath = (0, node_path.join)(projectRoot, ".gitignore");
304
- if ((0, node_fs.existsSync)(gitignorePath)) try {
305
- ignorer.add((0, node_fs.readFileSync)(gitignorePath, "utf-8"));
306
- } catch {}
307
- return ignorer;
308
- };
309
- const scanDirectoryForProjects = (rootDirectory, ignorer, maxDepth, currentDepth = 0) => {
310
- if (currentDepth >= maxDepth) return [];
311
- if (!(0, node_fs.existsSync)(rootDirectory)) return [];
312
- const projects = [];
313
- try {
314
- const entries = (0, node_fs.readdirSync)(rootDirectory, { withFileTypes: true });
315
- for (const entry of entries) {
316
- if (!entry.isDirectory()) continue;
317
- if (ignorer.ignores(entry.name)) continue;
318
- const entryPath = (0, node_path.join)(rootDirectory, entry.name);
319
- if ((0, node_fs.existsSync)((0, node_path.join)(entryPath, "package.json"))) {
320
- const project = buildReactProject(entryPath);
321
- if (project) {
322
- projects.push(project);
323
- continue;
324
- }
325
- }
326
- projects.push(...scanDirectoryForProjects(entryPath, ignorer, maxDepth, currentDepth + 1));
327
- }
328
- } catch {
329
- return projects;
330
- }
331
- return projects;
332
- };
333
- const MAX_SCAN_DEPTH = 2;
334
- const normalizePathForComparison = (filePath) => filePath.replace(/\\/g, "/");
335
- const findReactProjects = (projectRoot) => {
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;
342
- }
343
- const scannedProjects = scanDirectoryForProjects(projectRoot, loadGitignore(projectRoot), MAX_SCAN_DEPTH);
344
- if (scannedProjects.length > 0) return scannedProjects;
345
- let currentDirectory = (0, node_path.dirname)(projectRoot);
346
- while (currentDirectory !== (0, node_path.dirname)(currentDirectory)) {
347
- const parentProject = buildReactProject(currentDirectory);
348
- if (parentProject) return [parentProject];
349
- currentDirectory = (0, node_path.dirname)(currentDirectory);
350
- }
351
- return [];
352
- };
353
- const hasReactGrabSetupInFile = (filePath) => {
354
- if (!(0, node_fs.existsSync)(filePath)) return false;
355
- try {
356
- return hasReactGrabSetupCode((0, node_fs.readFileSync)(filePath, "utf-8"));
357
- } catch {
358
- return false;
359
- }
360
- };
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);
367
- };
368
- const detectUnsupportedFramework = (projectRoot) => {
369
- const dependencies = readMergedDependencies(projectRoot);
370
- if (!dependencies) return null;
371
- if (dependencies["@remix-run/react"] || dependencies["remix"]) return "remix";
372
- if (dependencies["astro"]) return "astro";
373
- if (dependencies["@sveltejs/kit"]) return "sveltekit";
374
- if (dependencies["gatsby"]) return "gatsby";
375
- return null;
376
- };
377
- const detectReactGrabVersion = (projectRoot) => {
378
- const installedPackageJsonPath = (0, node_path.join)(projectRoot, "node_modules", "react-grab", "package.json");
379
- if ((0, node_fs.existsSync)(installedPackageJsonPath)) try {
380
- return JSON.parse((0, node_fs.readFileSync)(installedPackageJsonPath, "utf-8")).version ?? null;
381
- } catch {}
382
- return null;
383
- };
384
- const detectProject = async (projectRoot = process.cwd()) => {
385
- const localFramework = detectFramework(projectRoot);
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);
390
- return {
391
- packageManager,
392
- framework,
393
- nextRouterType: framework === "next" ? detectNextRouterType(projectRoot) : "unknown",
394
- isMonorepo,
395
- projectRoot,
396
- hasReactGrab: detectReactGrabDependency(projectRoot) || isReactGrabConfigured,
397
- isReactGrabConfigured,
398
- reactGrabVersion: detectReactGrabVersion(projectRoot),
399
- unsupportedFramework: detectUnsupportedFramework(projectRoot)
400
- };
401
- };
402
- //#endregion
403
30
  //#region src/utils/highlighter.ts
404
31
  const highlighter = {
405
32
  error: picocolors.default.red,
@@ -439,39 +66,6 @@ const handleError = (error) => {
439
66
  process.exit(1);
440
67
  };
441
68
  //#endregion
442
- //#region src/utils/detect-agents.ts
443
- const PATH_BINARIES = {
444
- "claude-code": ["claude"],
445
- codex: ["codex"],
446
- cursor: ["cursor", "cursor-agent"],
447
- droid: ["droid"],
448
- "gemini-cli": ["gemini"],
449
- "github-copilot": ["copilot"],
450
- opencode: ["opencode"],
451
- pi: ["pi", "omegon"]
452
- };
453
- const isCommandAvailable = (command) => {
454
- const pathDirectories = (process.env.PATH ?? "").split(node_path.delimiter).filter(Boolean);
455
- for (const directory of pathDirectories) {
456
- const binaryPath = (0, node_path.join)(directory, command);
457
- try {
458
- if ((0, node_fs.statSync)(binaryPath).isFile()) {
459
- (0, node_fs.accessSync)(binaryPath, node_fs.constants.X_OK);
460
- return true;
461
- }
462
- } catch {}
463
- }
464
- return false;
465
- };
466
- const detectAvailableAgents = async () => {
467
- const installedAgents = new Set(await (0, agent_install_skill.detectInstalledSkillAgents)());
468
- return (0, agent_install_skill.getSkillAgentTypes)().filter((agent) => {
469
- if (agent === "universal") return false;
470
- if (installedAgents.has(agent)) return true;
471
- return PATH_BINARIES[agent]?.some(isCommandAvailable) ?? false;
472
- });
473
- };
474
- //#endregion
475
69
  //#region src/utils/unref-stdin.ts
476
70
  const unrefStdin = () => {
477
71
  if (process.stdin.isTTY) return;
@@ -492,16 +86,9 @@ const prompts$1 = (questions) => {
492
86
  //#region src/utils/spinner.ts
493
87
  const spinner = (text) => (0, ora.default)({ text });
494
88
  //#endregion
495
- //#region src/utils/install-skill.ts
496
- const SKILL_NAME = "react-grab";
497
- const SKILL_SOURCE = (0, node_url.fileURLToPath)(new URL("../skills/react-grab", require("url").pathToFileURL(__filename).href));
498
- const agentLabel = (agent) => (0, agent_install_skill.getSkillAgentConfig)(agent).displayName;
499
- const installedSkillDir = (agent, global, cwd) => (0, node_path.join)((0, agent_install_skill.isUniversalSkillAgent)(agent) ? (0, agent_install_skill.getCanonicalSkillsDir)(global, cwd) : (0, agent_install_skill.getSkillAgentDir)(agent, {
500
- global,
501
- cwd
502
- }), SKILL_NAME);
89
+ //#region src/utils/prompt-skill-install.ts
503
90
  const promptSkillInstall = async ({ yes = false, global = false, cwd = process.cwd() } = {}) => {
504
- const detectedAgents = await detectAvailableAgents();
91
+ const detectedAgents = await require_install.detectAvailableAgents();
505
92
  if (detectedAgents.length === 0) {
506
93
  logger.warn("No supported agents detected.");
507
94
  return false;
@@ -520,7 +107,7 @@ const promptSkillInstall = async ({ yes = false, global = false, cwd = process.c
520
107
  name: "agents",
521
108
  message: `Install the React Grab skill (${global ? "global" : "this project"}) for:`,
522
109
  choices: detectedAgents.map((agent) => ({
523
- title: agentLabel(agent),
110
+ title: require_install.agentLabel(agent),
524
111
  value: agent,
525
112
  selected: true
526
113
  })),
@@ -531,48 +118,29 @@ const promptSkillInstall = async ({ yes = false, global = false, cwd = process.c
531
118
  if (selectedAgents.length === 0) return false;
532
119
  }
533
120
  const installSpinner = spinner("Installing React Grab skill.").start();
534
- const { installed, failed } = await (0, agent_install_skill.add)({
535
- source: SKILL_SOURCE,
121
+ const { installed, failed } = await require_install.installSkill({
536
122
  agents: selectedAgents,
537
123
  global,
538
- cwd,
539
- mode: "copy"
124
+ cwd
540
125
  });
541
126
  if (installed.length === 0) {
542
127
  installSpinner.fail("Failed to install React Grab skill.");
543
128
  return false;
544
129
  }
545
- installSpinner.succeed(`Installed React Grab skill for ${installed.map((record) => agentLabel(record.agent)).join(", ")}.`);
546
- for (const record of failed) logger.log(` ${highlighter.error("✗")} ${agentLabel(record.agent)} ${record.error}`);
130
+ installSpinner.succeed(`Installed React Grab skill for ${installed.map((record) => require_install.agentLabel(record.agent)).join(", ")}.`);
131
+ for (const record of failed) logger.log(` ${highlighter.error("✗")} ${require_install.agentLabel(record.agent)} ${record.error}`);
547
132
  return true;
548
133
  };
549
- const removeSkill = async ({ cwd = process.cwd(), global = false } = {}) => {
550
- const agents = await detectAvailableAgents();
551
- const removedAgents = [];
552
- const dirsToRemove = /* @__PURE__ */ new Set();
553
- for (const agent of agents) {
554
- const skillDir = installedSkillDir(agent, global, cwd);
555
- if (!(0, node_fs.existsSync)(skillDir)) continue;
556
- removedAgents.push(agent);
557
- dirsToRemove.add(skillDir);
558
- }
559
- for (const skillDir of dirsToRemove) (0, node_fs.rmSync)(skillDir, {
560
- recursive: true,
561
- force: true
562
- });
563
- for (const agent of removedAgents) logger.log(` ${highlighter.success("✓")} ${agentLabel(agent)}`);
564
- return removedAgents.length;
565
- };
566
134
  //#endregion
567
135
  //#region src/commands/add.ts
568
- const VERSION$5 = "0.1.46";
136
+ const VERSION$5 = "0.1.47-dev.bc3a591";
569
137
  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) => {
570
138
  console.log(`${picocolors.default.magenta("✿")} ${picocolors.default.bold("React Grab")} ${picocolors.default.gray(VERSION$5)}`);
571
139
  console.log();
572
140
  try {
573
141
  const isNonInteractive = detectNonInteractive(opts.yes);
574
142
  const preflightSpinner = spinner("Preflight checks.").start();
575
- if (!(await detectProject(opts.cwd)).hasReactGrab) {
143
+ if (!(await require_install.detectProject(opts.cwd)).hasReactGrab) {
576
144
  preflightSpinner.fail("React Grab is not installed.");
577
145
  logger.break();
578
146
  logger.error(`Run ${highlighter.info("react-grab init")} first to install React Grab.`);
@@ -698,412 +266,6 @@ const printDiff = (filePath, originalContent, newContent) => {
698
266
  console.log("─".repeat(60));
699
267
  };
700
268
  //#endregion
701
- //#region src/utils/templates.ts
702
- const NEXT_APP_ROUTER_SCRIPT = `{process.env.NODE_ENV === "development" && (
703
- <Script
704
- src="//unpkg.com/react-grab/dist/index.global.js"
705
- crossOrigin="anonymous"
706
- strategy="beforeInteractive"
707
- />
708
- )}`;
709
- const VITE_IMPORT = `if (import.meta.env.DEV) {
710
- import("react-grab");
711
- }`;
712
- const WEBPACK_IMPORT = `if (process.env.NODE_ENV === "development") {
713
- import("react-grab");
714
- }`;
715
- const TANSTACK_EFFECT = `useEffect(() => {
716
- if (import.meta.env.DEV) {
717
- void import("react-grab");
718
- }
719
- }, []);`;
720
- const SCRIPT_IMPORT = "import Script from \"next/script\";";
721
- //#endregion
722
- //#region src/utils/transform.ts
723
- const hasReactGrabInInstrumentation = (projectRoot) => {
724
- return findFileWithReactGrabSetup(getInstrumentationFileCandidates(projectRoot)) !== null;
725
- };
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
- }
731
- return null;
732
- };
733
- const alreadyConfiguredResult = (filePath) => ({
734
- success: true,
735
- filePath,
736
- message: "React Grab is already configured",
737
- noChanges: true
738
- });
739
- const transformNextAppRouter = (projectRoot, reactGrabAlreadyConfigured) => {
740
- const layoutPath = findLayoutFile(projectRoot);
741
- if (!layoutPath) return {
742
- success: false,
743
- filePath: "",
744
- message: "Could not find app/layout.tsx, app/layout.jsx, app/layout.ts, or app/layout.js"
745
- };
746
- const originalContent = (0, node_fs.readFileSync)(layoutPath, "utf-8");
747
- let newContent = originalContent;
748
- const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
749
- const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
750
- if (hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(layoutPath);
751
- if (hasReactGrabInFile || hasReactGrabInInstrumentationFile) return {
752
- success: true,
753
- filePath: layoutPath,
754
- message: "React Grab is already installed" + (hasReactGrabInInstrumentationFile ? " in instrumentation-client" : " in this file"),
755
- noChanges: true
756
- };
757
- if (!newContent.includes("import Script from \"next/script\"")) {
758
- const importMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
759
- if (importMatch) newContent = newContent.replace(importMatch[0], `${importMatch[0]}\n${SCRIPT_IMPORT}`);
760
- else newContent = `${SCRIPT_IMPORT}\n\n${newContent}`;
761
- }
762
- const headMatch = newContent.match(/<head[^>]*>/);
763
- if (headMatch) newContent = newContent.replace(headMatch[0], `${headMatch[0]}\n ${NEXT_APP_ROUTER_SCRIPT}`);
764
- else {
765
- const htmlMatch = newContent.match(/<html[^>]*>/);
766
- if (htmlMatch) newContent = newContent.replace(htmlMatch[0], `${htmlMatch[0]}\n <head>\n ${NEXT_APP_ROUTER_SCRIPT}\n </head>`);
767
- }
768
- return {
769
- success: true,
770
- filePath: layoutPath,
771
- message: "Add React Grab",
772
- originalContent,
773
- newContent
774
- };
775
- };
776
- const transformNextPagesRouter = (projectRoot, reactGrabAlreadyConfigured) => {
777
- const documentPath = findDocumentFile(projectRoot);
778
- if (!documentPath) return {
779
- success: false,
780
- filePath: "",
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 }"
782
- };
783
- const originalContent = (0, node_fs.readFileSync)(documentPath, "utf-8");
784
- let newContent = originalContent;
785
- const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
786
- const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
787
- if (hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(documentPath);
788
- if (hasReactGrabInFile || hasReactGrabInInstrumentationFile) return {
789
- success: true,
790
- filePath: documentPath,
791
- message: "React Grab is already installed" + (hasReactGrabInInstrumentationFile ? " in instrumentation-client" : " in this file"),
792
- noChanges: true
793
- };
794
- if (!newContent.includes("import Script from \"next/script\"")) {
795
- const importMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
796
- if (importMatch) newContent = newContent.replace(importMatch[0], `${importMatch[0]}\n${SCRIPT_IMPORT}`);
797
- }
798
- const headMatch = newContent.match(/<Head[^>]*>/);
799
- if (headMatch) newContent = newContent.replace(headMatch[0], `${headMatch[0]}\n ${NEXT_APP_ROUTER_SCRIPT}`);
800
- return {
801
- success: true,
802
- filePath: documentPath,
803
- message: "Add React Grab",
804
- originalContent,
805
- newContent
806
- };
807
- };
808
- const checkExistingInstallation = (filePath, reactGrabAlreadyConfigured) => {
809
- if (!hasReactGrabSetupCode((0, node_fs.readFileSync)(filePath, "utf-8"))) return null;
810
- return {
811
- success: true,
812
- filePath,
813
- message: reactGrabAlreadyConfigured ? "React Grab is already configured" : "React Grab is already installed in this file",
814
- noChanges: true
815
- };
816
- };
817
- const transformVite = (projectRoot, reactGrabAlreadyConfigured) => {
818
- const entryPath = findEntryFile(projectRoot);
819
- const indexPath = findIndexHtml(projectRoot);
820
- if (indexPath) {
821
- const existingResult = checkExistingInstallation(indexPath, reactGrabAlreadyConfigured);
822
- if (existingResult) return existingResult;
823
- }
824
- if (!entryPath) return {
825
- success: false,
826
- filePath: "",
827
- message: "Could not find entry file (src/index.tsx, src/main.tsx, etc.)"
828
- };
829
- const existingResult = checkExistingInstallation(entryPath, reactGrabAlreadyConfigured);
830
- if (existingResult) return existingResult;
831
- const originalContent = (0, node_fs.readFileSync)(entryPath, "utf-8");
832
- return {
833
- success: true,
834
- filePath: entryPath,
835
- message: "Add React Grab",
836
- originalContent,
837
- newContent: `${VITE_IMPORT}\n\n${originalContent}`
838
- };
839
- };
840
- const transformWebpack = (projectRoot, reactGrabAlreadyConfigured) => {
841
- const entryPath = findEntryFile(projectRoot);
842
- if (!entryPath) return {
843
- success: false,
844
- filePath: "",
845
- message: "Could not find entry file (src/index.tsx, src/main.tsx, etc.)"
846
- };
847
- const existingResult = checkExistingInstallation(entryPath, reactGrabAlreadyConfigured);
848
- if (existingResult) return existingResult;
849
- const originalContent = (0, node_fs.readFileSync)(entryPath, "utf-8");
850
- return {
851
- success: true,
852
- filePath: entryPath,
853
- message: "Add React Grab",
854
- originalContent,
855
- newContent: `${WEBPACK_IMPORT}\n\n${originalContent}`
856
- };
857
- };
858
- const transformTanStack = (projectRoot, reactGrabAlreadyConfigured) => {
859
- const rootPath = findTanStackRootFile(projectRoot);
860
- if (!rootPath) return {
861
- success: false,
862
- filePath: "",
863
- message: "Could not find src/routes/__root.tsx or app/routes/__root.tsx.\n\nTo set up React Grab with TanStack Start, add this to your root route component:\n\n import { useEffect } from \"react\";\n\n useEffect(() => {\n if (import.meta.env.DEV) {\n void import(\"react-grab\");\n }\n }, []);"
864
- };
865
- const originalContent = (0, node_fs.readFileSync)(rootPath, "utf-8");
866
- let newContent = originalContent;
867
- const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
868
- if (hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(rootPath);
869
- if (hasReactGrabInFile) return {
870
- success: true,
871
- filePath: rootPath,
872
- message: "React Grab is already installed in this file",
873
- noChanges: true
874
- };
875
- if (!/import\s+\{[^}]*useEffect[^}]*\}\s+from\s+["']react["']/.test(newContent)) {
876
- const reactImportMatch = newContent.match(/import\s+\{([^}]*)\}\s+from\s+["']react["'];?/);
877
- if (reactImportMatch) {
878
- const existingImports = reactImportMatch[1];
879
- newContent = newContent.replace(reactImportMatch[0], `import { ${existingImports.trim()}, useEffect } from "react";`);
880
- } else {
881
- const firstImportMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
882
- if (firstImportMatch) newContent = newContent.replace(firstImportMatch[0], `import { useEffect } from "react";\n${firstImportMatch[0]}`);
883
- else newContent = `import { useEffect } from "react";\n\n${newContent}`;
884
- }
885
- }
886
- const componentMatch = newContent.match(/function\s+(\w+)\s*\([^)]*\)\s*\{/);
887
- if (componentMatch) {
888
- const insertPosition = componentMatch.index + componentMatch[0].length;
889
- newContent = newContent.slice(0, insertPosition) + `\n ${TANSTACK_EFFECT}\n` + newContent.slice(insertPosition);
890
- } else return {
891
- success: false,
892
- filePath: rootPath,
893
- message: "Could not find a component function in the root file"
894
- };
895
- return {
896
- success: true,
897
- filePath: rootPath,
898
- message: "Add React Grab",
899
- originalContent,
900
- newContent
901
- };
902
- };
903
- const hasFrameworkEntryPoint = (projectRoot, framework, nextRouterType) => {
904
- switch (framework) {
905
- case "next": return nextRouterType === "app" ? findLayoutFile(projectRoot) !== null : findDocumentFile(projectRoot) !== null;
906
- case "vite":
907
- case "webpack": return findEntryFile(projectRoot) !== null;
908
- case "tanstack": return findTanStackRootFile(projectRoot) !== null;
909
- default: return false;
910
- }
911
- };
912
- const previewTransform = (projectRoot, framework, nextRouterType, reactGrabAlreadyConfigured = false) => {
913
- switch (framework) {
914
- case "next":
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);
920
- default: return {
921
- success: false,
922
- filePath: "",
923
- message: `Unknown framework: ${framework}. Please add React Grab manually.`
924
- };
925
- }
926
- };
927
- const canWriteToFile = (filePath) => {
928
- try {
929
- (0, node_fs.accessSync)(filePath, node_fs.constants.W_OK);
930
- return true;
931
- } catch {
932
- return false;
933
- }
934
- };
935
- const applyTransform = (result) => {
936
- if (result.success && result.newContent && result.filePath) {
937
- if (!canWriteToFile(result.filePath)) return {
938
- success: false,
939
- error: `Cannot write to ${result.filePath}. Check file permissions.`
940
- };
941
- try {
942
- (0, node_fs.writeFileSync)(result.filePath, result.newContent);
943
- return { success: true };
944
- } catch (error) {
945
- return {
946
- success: false,
947
- error: `Failed to write to ${result.filePath}: ${error instanceof Error ? error.message : "Unknown error"}`
948
- };
949
- }
950
- }
951
- return { success: true };
952
- };
953
- const formatOptionsForNextjs = (options) => {
954
- const parts = [];
955
- if (options.activationKey) parts.push(`activationKey: ${JSON.stringify(options.activationKey)}`);
956
- if (options.activationMode) parts.push(`activationMode: "${options.activationMode}"`);
957
- if (options.keyHoldDuration !== void 0) parts.push(`keyHoldDuration: ${options.keyHoldDuration}`);
958
- if (options.allowActivationInsideInput !== void 0) parts.push(`allowActivationInsideInput: ${options.allowActivationInsideInput}`);
959
- if (options.maxContextLines !== void 0) parts.push(`maxContextLines: ${options.maxContextLines}`);
960
- return `{ ${parts.join(", ")} }`;
961
- };
962
- const formatOptionsAsJson = (options) => {
963
- const cleanOptions = {};
964
- if (options.activationKey) cleanOptions.activationKey = options.activationKey;
965
- if (options.activationMode) cleanOptions.activationMode = options.activationMode;
966
- if (options.keyHoldDuration !== void 0) cleanOptions.keyHoldDuration = options.keyHoldDuration;
967
- if (options.allowActivationInsideInput !== void 0) cleanOptions.allowActivationInsideInput = options.allowActivationInsideInput;
968
- if (options.maxContextLines !== void 0) cleanOptions.maxContextLines = options.maxContextLines;
969
- return JSON.stringify(cleanOptions);
970
- };
971
- const findReactGrabFile = (projectRoot, framework, nextRouterType) => {
972
- switch (framework) {
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
- }
981
- case "vite": {
982
- const entryFile = findEntryFile(projectRoot);
983
- const entrySetupFile = findFileWithReactGrabSetup(getEntryFileCandidates(projectRoot));
984
- if (entrySetupFile) return entrySetupFile;
985
- const indexHtml = findFileWithReactGrabSetup(getIndexHtmlCandidates(projectRoot));
986
- if (indexHtml) return indexHtml;
987
- return entryFile;
988
- }
989
- case "tanstack": return findFileWithReactGrabSetup(getTanStackRootFileCandidates(projectRoot)) ?? findTanStackRootFile(projectRoot);
990
- case "webpack": return findFileWithReactGrabSetup(getEntryFileCandidates(projectRoot)) ?? findEntryFile(projectRoot);
991
- default: return null;
992
- }
993
- };
994
- const addOptionsToNextScript = (originalContent, options, filePath) => {
995
- const reactGrabScriptMatch = originalContent.match(/(<Script[\s\S]*?react-grab[\s\S]*?)\s*(\/?>)/i);
996
- if (!reactGrabScriptMatch) return {
997
- success: false,
998
- filePath,
999
- message: "Could not find React Grab Script tag"
1000
- };
1001
- const scriptTag = reactGrabScriptMatch[0];
1002
- const scriptOpening = reactGrabScriptMatch[1];
1003
- const scriptClosing = reactGrabScriptMatch[2];
1004
- const existingDataOptionsMatch = scriptTag.match(/data-options=\{JSON\.stringify\([^)]+\)\}/);
1005
- const dataOptionsAttr = `data-options={JSON.stringify(\n ${formatOptionsForNextjs(options)}\n )}`;
1006
- let newScriptTag;
1007
- if (existingDataOptionsMatch) newScriptTag = scriptTag.replace(existingDataOptionsMatch[0], dataOptionsAttr);
1008
- else newScriptTag = `${scriptOpening}\n ${dataOptionsAttr}\n ${scriptClosing}`;
1009
- return {
1010
- success: true,
1011
- filePath,
1012
- message: "Update React Grab options",
1013
- originalContent,
1014
- newContent: originalContent.replace(scriptTag, newScriptTag)
1015
- };
1016
- };
1017
- const addOptionsToDynamicImport = (originalContent, options, filePath) => {
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*\))?/);
1019
- if (!reactGrabImportWithInitMatch) return {
1020
- success: false,
1021
- filePath,
1022
- message: "Could not find React Grab import"
1023
- };
1024
- const optionsJson = formatOptionsAsJson(options);
1025
- const newImport = `${reactGrabImportWithInitMatch[1] ?? ""}import("react-grab").then((m) => m.init(${optionsJson}))`;
1026
- return {
1027
- success: true,
1028
- filePath,
1029
- message: "Update React Grab options",
1030
- originalContent,
1031
- newContent: originalContent.replace(reactGrabImportWithInitMatch[0], newImport)
1032
- };
1033
- };
1034
- const addOptionsToTanStackImport = (originalContent, options, filePath) => {
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*\))/);
1036
- if (!reactGrabImportWithInitMatch) return {
1037
- success: false,
1038
- filePath,
1039
- message: "Could not find React Grab import"
1040
- };
1041
- const optionsJson = formatOptionsAsJson(options);
1042
- const newImport = `${reactGrabImportWithInitMatch[1] ?? reactGrabImportWithInitMatch[2] ?? ""}import("react-grab/core").then(({ init }) => init(${optionsJson}))`;
1043
- return {
1044
- success: true,
1045
- filePath,
1046
- message: "Update React Grab options",
1047
- originalContent,
1048
- newContent: originalContent.replace(reactGrabImportWithInitMatch[0], newImport)
1049
- };
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
- };
1056
- const previewOptionsTransform = (projectRoot, framework, nextRouterType, options) => {
1057
- const filePath = findReactGrabFile(projectRoot, framework, nextRouterType);
1058
- if (!filePath) return {
1059
- success: false,
1060
- filePath: "",
1061
- message: "Could not find file containing React Grab configuration"
1062
- };
1063
- const originalContent = (0, node_fs.readFileSync)(filePath, "utf-8");
1064
- if (!hasReactGrabSetupCode(originalContent)) return {
1065
- success: false,
1066
- filePath,
1067
- message: "Could not find React Grab code in the file"
1068
- };
1069
- switch (framework) {
1070
- case "next":
1071
- if (isInstrumentationFile(filePath)) return addOptionsToAnyImport(originalContent, options, filePath);
1072
- return addOptionsToNextScript(originalContent, options, filePath);
1073
- case "vite": return addOptionsToDynamicImport(originalContent, options, filePath);
1074
- case "tanstack": return addOptionsToTanStackImport(originalContent, options, filePath);
1075
- case "webpack": return addOptionsToDynamicImport(originalContent, options, filePath);
1076
- default: return {
1077
- success: false,
1078
- filePath,
1079
- message: `Unknown framework: ${framework}`
1080
- };
1081
- }
1082
- };
1083
- const previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDomain) => {
1084
- const filePath = findReactGrabFile(projectRoot, framework, nextRouterType);
1085
- if (!filePath) return {
1086
- success: false,
1087
- filePath: "",
1088
- message: "Could not find React Grab file"
1089
- };
1090
- const originalContent = (0, node_fs.readFileSync)(filePath, "utf-8");
1091
- const newContent = originalContent.replace(/(https?:)?\/\/[^/\s"']+(?=\/(?:@?react-grab))/g, `//${targetCdnDomain}`).replace(/(https?:)?\/\/[^/\s"']*react-grab[^/\s"']*\.com(?=\/script\.js)/g, `//${targetCdnDomain}`);
1092
- if (newContent === originalContent) return {
1093
- success: true,
1094
- filePath,
1095
- message: "CDN already set",
1096
- noChanges: true
1097
- };
1098
- return {
1099
- success: true,
1100
- filePath,
1101
- message: "Update CDN",
1102
- originalContent,
1103
- newContent
1104
- };
1105
- };
1106
- //#endregion
1107
269
  //#region src/utils/constants.ts
1108
270
  const MAX_KEY_HOLD_DURATION_MS = 2e3;
1109
271
  const DEFAULT_WATCH_DIR = ".react-grab";
@@ -1127,7 +289,7 @@ const formatActivationKeyDisplay = (activationKey) => {
1127
289
  };
1128
290
  //#endregion
1129
291
  //#region src/commands/configure.ts
1130
- const VERSION$4 = "0.1.46";
292
+ const VERSION$4 = "0.1.47-dev.bc3a591";
1131
293
  const isMac = process.platform === "darwin";
1132
294
  const META_LABEL = isMac ? "Cmd" : "Win";
1133
295
  const ALT_LABEL = isMac ? "Option" : "Alt";
@@ -1418,7 +580,7 @@ const CONFIG_OPTIONS = [
1418
580
  {
1419
581
  id: "maxContextLines",
1420
582
  title: "Max Context Lines",
1421
- description: "Number of surrounding code lines to include in context"
583
+ description: "Max source-location lines in copied context (raise for large apps)"
1422
584
  }
1423
585
  ];
1424
586
  const comboToString = (combo) => {
@@ -1439,7 +601,7 @@ const configure = new commander.Command().name("configure").alias("config").desc
1439
601
  try {
1440
602
  const cwd = opts.cwd;
1441
603
  const preflightSpinner = spinner("Preflight checks.").start();
1442
- const projectInfo = await detectProject(cwd);
604
+ const projectInfo = await require_install.detectProject(cwd);
1443
605
  if (!projectInfo.hasReactGrab) {
1444
606
  preflightSpinner.fail("React Grab is not installed.");
1445
607
  logger.break();
@@ -1456,7 +618,7 @@ const configure = new commander.Command().name("configure").alias("config").desc
1456
618
  }
1457
619
  preflightSpinner.succeed();
1458
620
  if (opts.cdn) {
1459
- const result = previewCdnTransform(projectInfo.projectRoot, projectInfo.framework, projectInfo.nextRouterType, opts.cdn);
621
+ const result = require_install.previewCdnTransform(projectInfo.projectRoot, projectInfo.framework, projectInfo.nextRouterType, opts.cdn);
1460
622
  if (!result.success) {
1461
623
  logger.break();
1462
624
  logger.error(result.message);
@@ -1487,7 +649,7 @@ const configure = new commander.Command().name("configure").alias("config").desc
1487
649
  }
1488
650
  }
1489
651
  const writeSpinner = spinner(`Applying changes to ${result.filePath}.`).start();
1490
- const writeResult = applyTransform(result);
652
+ const writeResult = require_install.applyTransform(result);
1491
653
  if (!writeResult.success) {
1492
654
  writeSpinner.fail();
1493
655
  logger.break();
@@ -1640,7 +802,7 @@ const configure = new commander.Command().name("configure").alias("config").desc
1640
802
  collectedOptions.maxContextLines = maxContextLines;
1641
803
  }
1642
804
  }
1643
- const result = previewOptionsTransform(projectInfo.projectRoot, projectInfo.framework, projectInfo.nextRouterType, collectedOptions);
805
+ const result = require_install.previewOptionsTransform(projectInfo.projectRoot, projectInfo.framework, projectInfo.nextRouterType, collectedOptions);
1644
806
  if (!result.success) {
1645
807
  logger.break();
1646
808
  logger.warn(result.message);
@@ -1671,7 +833,7 @@ const configure = new commander.Command().name("configure").alias("config").desc
1671
833
  }
1672
834
  }
1673
835
  const writeSpinner = spinner(`Applying changes to ${result.filePath}.`).start();
1674
- const writeResult = applyTransform(result);
836
+ const writeResult = require_install.applyTransform(result);
1675
837
  if (!writeResult.success) {
1676
838
  writeSpinner.fail();
1677
839
  logger.break();
@@ -1692,42 +854,10 @@ const configure = new commander.Command().name("configure").alias("config").desc
1692
854
  }
1693
855
  });
1694
856
  //#endregion
1695
- //#region src/utils/install.ts
1696
- const installPackages = async (packages, options = {}) => {
1697
- if (packages.length === 0) return;
1698
- const detectedAgent = options.packageManager ?? await detectPackageManager(options.cwd ?? process.cwd());
1699
- const args = [];
1700
- if (options.preferOffline) args.push("--prefer-offline");
1701
- if (detectedAgent === "pnpm") {
1702
- args.push("--prod=false");
1703
- if ((0, node_fs.existsSync)((0, node_path.resolve)(options.cwd ?? process.cwd(), "pnpm-workspace.yaml"))) args.push("-w");
1704
- }
1705
- if (options.additionalArgs) args.push(...options.additionalArgs);
1706
- await (0, tinyexec.x)(detectedAgent, [
1707
- detectedAgent === "npm" ? "install" : "add",
1708
- ...options.isDev !== false ? ["-D"] : [],
1709
- ...args,
1710
- ...packages
1711
- ], {
1712
- nodeOptions: {
1713
- stdio: options.silent ? "ignore" : "inherit",
1714
- cwd: options.cwd,
1715
- env: {
1716
- ...process.env,
1717
- REACT_GRAB_INIT: "1"
1718
- }
1719
- },
1720
- throwOnError: true
1721
- });
1722
- };
1723
- const getPackagesToInstall = (includeReactGrab = true) => {
1724
- return includeReactGrab ? ["react-grab"] : [];
1725
- };
1726
- //#endregion
1727
857
  //#region src/utils/cli-helpers.ts
1728
858
  const applyTransformWithFeedback = (result, message) => {
1729
859
  const writeSpinner = spinner(message ?? `Applying changes to ${result.filePath}.`).start();
1730
- const writeResult = applyTransform(result);
860
+ const writeResult = require_install.applyTransform(result);
1731
861
  if (!writeResult.success) {
1732
862
  writeSpinner.fail();
1733
863
  logger.break();
@@ -1741,7 +871,7 @@ const installPackagesWithFeedback = async (packages, packageManager, projectRoot
1741
871
  if (packages.length === 0) return;
1742
872
  const installSpinner = spinner(`Installing ${packages.join(", ")}.`).start();
1743
873
  try {
1744
- await installPackages(packages, {
874
+ await require_install.installPackages(packages, {
1745
875
  packageManager,
1746
876
  cwd: projectRoot
1747
877
  });
@@ -1759,7 +889,7 @@ const isTelemetryEnabled = () => {
1759
889
  };
1760
890
  //#endregion
1761
891
  //#region src/commands/init.ts
1762
- const VERSION$3 = "0.1.46";
892
+ const VERSION$3 = "0.1.47-dev.bc3a591";
1763
893
  const REPORT_URL = "https://react-grab.com/api/report-cli";
1764
894
  const DOCS_URL = "https://github.com/aidenybai/react-grab";
1765
895
  const reportToCli = (type, config, error) => {
@@ -1840,7 +970,7 @@ const init = new commander.Command().name("init").alias("setup").description("in
1840
970
  process.exit(1);
1841
971
  }
1842
972
  const preflightSpinner = spinner("Preflight checks.").start();
1843
- const projectInfo = await detectProject(cwd);
973
+ const projectInfo = await require_install.detectProject(cwd);
1844
974
  if (projectInfo.isReactGrabConfigured && !opts.force) {
1845
975
  preflightSpinner.succeed();
1846
976
  if (isNonInteractive) {
@@ -1954,7 +1084,7 @@ const init = new commander.Command().name("init").alias("setup").description("in
1954
1084
  process.exit(1);
1955
1085
  }
1956
1086
  collectedOptions.maxContextLines = maxContextLines;
1957
- const optionsResult = previewOptionsTransform(projectInfo.projectRoot, projectInfo.framework, projectInfo.nextRouterType, collectedOptions);
1087
+ const optionsResult = require_install.previewOptionsTransform(projectInfo.projectRoot, projectInfo.framework, projectInfo.nextRouterType, collectedOptions);
1958
1088
  if (!optionsResult.success) {
1959
1089
  logger.break();
1960
1090
  logger.error(optionsResult.message);
@@ -2004,12 +1134,12 @@ const init = new commander.Command().name("init").alias("setup").description("in
2004
1134
  logger.break();
2005
1135
  process.exit(1);
2006
1136
  }
2007
- if (projectInfo.framework === "unknown" || projectInfo.isMonorepo && !hasFrameworkEntryPoint(projectInfo.projectRoot, projectInfo.framework, projectInfo.nextRouterType)) {
1137
+ if (projectInfo.framework === "unknown" || projectInfo.isMonorepo && !require_install.hasFrameworkEntryPoint(projectInfo.projectRoot, projectInfo.framework, projectInfo.nextRouterType)) {
2008
1138
  let searchRoot = cwd;
2009
- let reactProjects = findReactProjects(searchRoot);
1139
+ let reactProjects = require_install.findReactProjects(searchRoot);
2010
1140
  if (reactProjects.length === 0 && cwd !== process.cwd()) {
2011
1141
  searchRoot = process.cwd();
2012
- reactProjects = findReactProjects(searchRoot);
1142
+ reactProjects = require_install.findReactProjects(searchRoot);
2013
1143
  }
2014
1144
  if (reactProjects.length > 0) {
2015
1145
  frameworkSpinner.info(`Verifying framework. Found ${reactProjects.length} project${reactProjects.length === 1 ? "" : "s"}.`);
@@ -2039,7 +1169,7 @@ const init = new commander.Command().name("init").alias("setup").description("in
2039
1169
  process.exit(0);
2040
1170
  }
2041
1171
  process.chdir(selectedProject);
2042
- const newProjectInfo = await detectProject(selectedProject);
1172
+ const newProjectInfo = await require_install.detectProject(selectedProject);
2043
1173
  Object.assign(projectInfo, newProjectInfo);
2044
1174
  const newFrameworkSpinner = spinner("Verifying framework.").start();
2045
1175
  if (newProjectInfo.framework === "unknown") failWithManualSetup(newFrameworkSpinner, "Could not detect a supported framework in this project.", { listSupportedFrameworks: true });
@@ -2061,7 +1191,7 @@ const init = new commander.Command().name("init").alias("setup").description("in
2061
1191
  cwd
2062
1192
  });
2063
1193
  }
2064
- const result = previewTransform(projectInfo.projectRoot, finalFramework, finalNextRouterType, projectInfo.isReactGrabConfigured);
1194
+ const result = require_install.previewTransform(projectInfo.projectRoot, finalFramework, finalNextRouterType, projectInfo.isReactGrabConfigured);
2065
1195
  if (!result.success) {
2066
1196
  logger.break();
2067
1197
  logger.error(result.message);
@@ -2093,7 +1223,7 @@ const init = new commander.Command().name("init").alias("setup").description("in
2093
1223
  }
2094
1224
  }
2095
1225
  const shouldInstallReactGrab = !projectInfo.hasReactGrab;
2096
- if (!opts.skipInstall && shouldInstallReactGrab) await installPackagesWithFeedback(getPackagesToInstall(shouldInstallReactGrab), finalPackageManager, projectInfo.projectRoot);
1226
+ if (!opts.skipInstall && shouldInstallReactGrab) await installPackagesWithFeedback(require_install.getPackagesToInstall(shouldInstallReactGrab), finalPackageManager, projectInfo.projectRoot);
2097
1227
  if (hasLayoutChanges) applyTransformWithFeedback(result);
2098
1228
  logger.break();
2099
1229
  if (hasLayoutChanges) logger.log(`${highlighter.success("Success!")} React Grab has been installed.`);
@@ -2710,19 +1840,20 @@ const pull = new commander.Command().name("pull").description("start the watcher
2710
1840
  });
2711
1841
  //#endregion
2712
1842
  //#region src/commands/remove.ts
2713
- const VERSION$2 = "0.1.46";
1843
+ const VERSION$2 = "0.1.47-dev.bc3a591";
2714
1844
  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) => {
2715
1845
  console.log(`${picocolors.default.magenta("✿")} ${picocolors.default.bold("React Grab")} ${picocolors.default.gray(VERSION$2)}`);
2716
1846
  console.log();
2717
1847
  try {
2718
1848
  logger.break();
2719
- const removedCount = await removeSkill({
1849
+ const removedAgents = await require_install.removeSkill({
2720
1850
  cwd: (0, node_path.resolve)(opts.cwd),
2721
1851
  global: opts.global
2722
1852
  });
1853
+ for (const agent of removedAgents) logger.log(` ${highlighter.success("✓")} ${require_install.agentLabel(agent)}`);
2723
1854
  logger.break();
2724
- if (removedCount === 0) logger.log("React Grab skill is not installed in any detected agent.");
2725
- else logger.log(`${highlighter.success("Removed")} the React Grab skill from ${removedCount} agent${removedCount === 1 ? "" : "s"}.`);
1855
+ if (removedAgents.length === 0) logger.log("React Grab skill is not installed in any detected agent.");
1856
+ else logger.log(`${highlighter.success("Removed")} the React Grab skill from ${removedAgents.length} agent${removedAgents.length === 1 ? "" : "s"}.`);
2726
1857
  logger.break();
2727
1858
  } catch (error) {
2728
1859
  handleError(error);
@@ -2738,7 +1869,7 @@ const stop = new commander.Command().name("stop").description("stop the React Gr
2738
1869
  });
2739
1870
  //#endregion
2740
1871
  //#region src/commands/upgrade.ts
2741
- const VERSION$1 = "0.1.46";
1872
+ const VERSION$1 = "0.1.47-dev.bc3a591";
2742
1873
  const NPM_REGISTRY_URL = "https://registry.npmjs.org/react-grab/latest";
2743
1874
  const fetchLatestVersion = async () => {
2744
1875
  try {
@@ -2763,7 +1894,7 @@ const upgrade = new commander.Command().name("upgrade").alias("update").descript
2763
1894
  try {
2764
1895
  const cwd = (0, node_path.resolve)(opts.cwd);
2765
1896
  const detectSpinner = spinner("Detecting project.").start();
2766
- const projectInfo = await detectProject(cwd);
1897
+ const projectInfo = await require_install.detectProject(cwd);
2767
1898
  if (!projectInfo.hasReactGrab) {
2768
1899
  detectSpinner.fail("React Grab is not installed.");
2769
1900
  logger.break();
@@ -2791,7 +1922,7 @@ const upgrade = new commander.Command().name("upgrade").alias("update").descript
2791
1922
  versionSpinner.succeed(`Update available: ${highlighter.dim(fromLabel)} → ${highlighter.info(`v${latestVersion}`)}.`);
2792
1923
  const upgradeSpinner = spinner("Upgrading react-grab.").start();
2793
1924
  try {
2794
- await installPackages(["react-grab@latest"], {
1925
+ await require_install.installPackages(["react-grab@latest"], {
2795
1926
  packageManager: projectInfo.packageManager,
2796
1927
  cwd: projectInfo.projectRoot,
2797
1928
  isDev: isDevDependency(projectInfo.projectRoot)
@@ -2850,7 +1981,7 @@ const watch = new commander.Command().name("watch").description("run the React G
2850
1981
  });
2851
1982
  //#endregion
2852
1983
  //#region src/cli.ts
2853
- const VERSION = "0.1.46";
1984
+ const VERSION = "0.1.47-dev.bc3a591";
2854
1985
  const VERSION_API_URL = "https://www.react-grab.com/api/version";
2855
1986
  process.on("SIGINT", () => process.exit(0));
2856
1987
  process.on("SIGTERM", () => process.exit(0));