@react-grab/cli 0.1.14 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.cjs +180 -84
  2. package/dist/cli.js +181 -86
  3. package/package.json +2 -1
package/dist/cli.cjs CHANGED
@@ -8,6 +8,7 @@ var child_process = require('child_process');
8
8
  var fs = require('fs');
9
9
  var path = require('path');
10
10
  var ni = require('@antfu/ni');
11
+ var ignore = require('ignore');
11
12
  var os = require('os');
12
13
  var process2 = require('process');
13
14
  var ora = require('ora');
@@ -18,10 +19,20 @@ var pc__default = /*#__PURE__*/_interopDefault(pc);
18
19
  var basePrompts__default = /*#__PURE__*/_interopDefault(basePrompts);
19
20
  var fs__default = /*#__PURE__*/_interopDefault(fs);
20
21
  var path__default = /*#__PURE__*/_interopDefault(path);
22
+ var ignore__default = /*#__PURE__*/_interopDefault(ignore);
21
23
  var os__default = /*#__PURE__*/_interopDefault(os);
22
24
  var process2__default = /*#__PURE__*/_interopDefault(process2);
23
25
  var ora__default = /*#__PURE__*/_interopDefault(ora);
24
26
 
27
+ // src/utils/is-non-interactive.ts
28
+ var NON_INTERACTIVE_ENVIRONMENT_VARIABLES = [
29
+ "CI",
30
+ "CLAUDECODE",
31
+ "AMI"
32
+ ];
33
+ var detectNonInteractive = (yesFlag) => yesFlag || NON_INTERACTIVE_ENVIRONMENT_VARIABLES.some(
34
+ (variable) => process.env[variable] === "true"
35
+ ) || !process.stdin.isTTY;
25
36
  var highlighter = {
26
37
  error: pc__default.default.red,
27
38
  warn: pc__default.default.yellow,
@@ -191,18 +202,22 @@ var getWorkspacePatterns = (projectRoot) => {
191
202
  return [...new Set(patterns)];
192
203
  };
193
204
  var expandWorkspacePattern = (projectRoot, pattern) => {
194
- const results = [];
205
+ const isGlob = pattern.endsWith("/*");
195
206
  const cleanPattern = pattern.replace(/\/\*$/, "");
196
207
  const basePath = path.join(projectRoot, cleanPattern);
197
- if (!fs.existsSync(basePath)) return results;
208
+ if (!fs.existsSync(basePath)) return [];
209
+ if (!isGlob) {
210
+ const hasPackageJson = fs.existsSync(path.join(basePath, "package.json"));
211
+ return hasPackageJson ? [basePath] : [];
212
+ }
213
+ const results = [];
198
214
  try {
199
215
  const entries = fs.readdirSync(basePath, { withFileTypes: true });
200
216
  for (const entry of entries) {
201
- if (entry.isDirectory()) {
202
- const packageJsonPath = path.join(basePath, entry.name, "package.json");
203
- if (fs.existsSync(packageJsonPath)) {
204
- results.push(path.join(basePath, entry.name));
205
- }
217
+ if (!entry.isDirectory()) continue;
218
+ const packageJsonPath = path.join(basePath, entry.name, "package.json");
219
+ if (fs.existsSync(packageJsonPath)) {
220
+ results.push(path.join(basePath, entry.name));
206
221
  }
207
222
  }
208
223
  } catch {
@@ -224,35 +239,92 @@ var hasReactDependency = (projectPath) => {
224
239
  return false;
225
240
  }
226
241
  };
242
+ var buildReactProject = (projectPath) => {
243
+ const framework = detectFramework(projectPath);
244
+ const hasReact = hasReactDependency(projectPath);
245
+ if (!hasReact && framework === "unknown") return null;
246
+ let name = path.basename(projectPath);
247
+ const packageJsonPath = path.join(projectPath, "package.json");
248
+ try {
249
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
250
+ name = packageJson.name || name;
251
+ } catch {
252
+ }
253
+ return { name, path: projectPath, framework, hasReact };
254
+ };
227
255
  var findWorkspaceProjects = (projectRoot) => {
228
256
  const patterns = getWorkspacePatterns(projectRoot);
229
257
  const projects = [];
230
258
  for (const pattern of patterns) {
231
- const projectPaths = expandWorkspacePattern(projectRoot, pattern);
232
- for (const projectPath of projectPaths) {
233
- const framework = detectFramework(projectPath);
234
- const hasReact = hasReactDependency(projectPath);
235
- if (hasReact || framework !== "unknown") {
236
- const packageJsonPath = path.join(projectPath, "package.json");
237
- let name = path.basename(projectPath);
238
- try {
239
- const packageJson = JSON.parse(
240
- fs.readFileSync(packageJsonPath, "utf-8")
241
- );
242
- name = packageJson.name || name;
243
- } catch {
259
+ for (const projectPath of expandWorkspacePattern(projectRoot, pattern)) {
260
+ const project = buildReactProject(projectPath);
261
+ if (project) projects.push(project);
262
+ }
263
+ }
264
+ return projects;
265
+ };
266
+ var ALWAYS_IGNORED_DIRECTORIES = [
267
+ "node_modules",
268
+ ".git",
269
+ ".next",
270
+ ".cache",
271
+ ".turbo",
272
+ "dist",
273
+ "build",
274
+ "coverage",
275
+ "test-results"
276
+ ];
277
+ var loadGitignore = (projectRoot) => {
278
+ const ignorer = ignore__default.default().add(ALWAYS_IGNORED_DIRECTORIES);
279
+ const gitignorePath = path.join(projectRoot, ".gitignore");
280
+ if (fs.existsSync(gitignorePath)) {
281
+ try {
282
+ ignorer.add(fs.readFileSync(gitignorePath, "utf-8"));
283
+ } catch {
284
+ }
285
+ }
286
+ return ignorer;
287
+ };
288
+ var scanDirectoryForProjects = (rootDirectory, ignorer, maxDepth, currentDepth = 0) => {
289
+ if (currentDepth >= maxDepth) return [];
290
+ if (!fs.existsSync(rootDirectory)) return [];
291
+ const projects = [];
292
+ try {
293
+ const entries = fs.readdirSync(rootDirectory, { withFileTypes: true });
294
+ for (const entry of entries) {
295
+ if (!entry.isDirectory()) continue;
296
+ if (ignorer.ignores(entry.name)) continue;
297
+ const entryPath = path.join(rootDirectory, entry.name);
298
+ const hasPackageJson = fs.existsSync(path.join(entryPath, "package.json"));
299
+ if (hasPackageJson) {
300
+ const project = buildReactProject(entryPath);
301
+ if (project) {
302
+ projects.push(project);
303
+ continue;
244
304
  }
245
- projects.push({
246
- name,
247
- path: projectPath,
248
- framework,
249
- hasReact
250
- });
251
305
  }
306
+ projects.push(
307
+ ...scanDirectoryForProjects(
308
+ entryPath,
309
+ ignorer,
310
+ maxDepth,
311
+ currentDepth + 1
312
+ )
313
+ );
252
314
  }
315
+ } catch {
316
+ return projects;
253
317
  }
254
318
  return projects;
255
319
  };
320
+ var MAX_SCAN_DEPTH = 2;
321
+ var findReactProjects = (projectRoot) => {
322
+ if (detectMonorepo(projectRoot)) {
323
+ return findWorkspaceProjects(projectRoot);
324
+ }
325
+ const ignorer = loadGitignore(projectRoot);
326
+ return scanDirectoryForProjects(projectRoot, ignorer, MAX_SCAN_DEPTH);
327
+ };
256
328
  var hasReactGrabInFile = (filePath) => {
257
329
  if (!fs.existsSync(filePath)) return false;
258
330
  try {
@@ -2194,7 +2266,7 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
2194
2266
  };
2195
2267
 
2196
2268
  // src/commands/add.ts
2197
- var VERSION = "0.1.14";
2269
+ var VERSION = "0.1.15";
2198
2270
  var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
2199
2271
  var add = new commander.Command().name("add").alias("install").description("connect React Grab to your agent").argument("[agent]", `agent to connect (${AGENTS.join(", ")}, mcp)`).option("-y, --yes", "skip confirmation prompts", false).option(
2200
2272
  "-c, --cwd <cwd>",
@@ -2207,7 +2279,7 @@ var add = new commander.Command().name("add").alias("install").description("conn
2207
2279
  console.log();
2208
2280
  try {
2209
2281
  const cwd = opts.cwd;
2210
- const isNonInteractive = opts.yes;
2282
+ const isNonInteractive = detectNonInteractive(opts.yes);
2211
2283
  const preflightSpinner = spinner("Preflight checks.").start();
2212
2284
  const projectInfo = await detectProject(cwd);
2213
2285
  if (!projectInfo.hasReactGrab) {
@@ -2555,7 +2627,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
2555
2627
  var MAX_CONTEXT_LINES = 50;
2556
2628
 
2557
2629
  // src/commands/configure.ts
2558
- var VERSION2 = "0.1.14";
2630
+ var VERSION2 = "0.1.15";
2559
2631
  var isMac = process.platform === "darwin";
2560
2632
  var META_LABEL = isMac ? "Cmd" : "Win";
2561
2633
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -3111,7 +3183,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
3111
3183
  };
3112
3184
 
3113
3185
  // src/commands/init.ts
3114
- var VERSION3 = "0.1.14";
3186
+ var VERSION3 = "0.1.15";
3115
3187
  var REPORT_URL = "https://react-grab.com/api/report-cli";
3116
3188
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
3117
3189
  var reportToCli = (type, config, error) => {
@@ -3148,6 +3220,34 @@ var UNSUPPORTED_FRAMEWORK_NAMES = {
3148
3220
  gatsby: "Gatsby"
3149
3221
  };
3150
3222
  var getAgentName = getAgentDisplayName;
3223
+ var sortProjectsByFramework = (projects) => [...projects].sort((projectA, projectB) => {
3224
+ if (projectA.framework === "unknown" && projectB.framework !== "unknown")
3225
+ return 1;
3226
+ if (projectA.framework !== "unknown" && projectB.framework === "unknown")
3227
+ return -1;
3228
+ return 0;
3229
+ });
3230
+ var printSubprojects = (searchRoot, sortedProjects) => {
3231
+ logger.break();
3232
+ logger.log("Found the following projects:");
3233
+ logger.break();
3234
+ for (const project of sortedProjects) {
3235
+ const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
3236
+ const relativePath = path.relative(searchRoot, project.path);
3237
+ logger.log(
3238
+ ` ${highlighter.info(project.name)}${frameworkLabel} ${highlighter.dim(relativePath)}`
3239
+ );
3240
+ }
3241
+ logger.break();
3242
+ logger.log(
3243
+ `Re-run with ${highlighter.info("-c <path>")} to specify a project:`
3244
+ );
3245
+ logger.break();
3246
+ logger.log(
3247
+ ` ${highlighter.dim("$")} npx -y grab@latest init -c ${path.relative(searchRoot, sortedProjects[0].path)}`
3248
+ );
3249
+ logger.break();
3250
+ };
3151
3251
  var formatActivationKeyDisplay2 = (activationKey) => {
3152
3252
  if (!activationKey) return "Default (Option/Alt)";
3153
3253
  return activationKey.split("+").map((part) => {
@@ -3176,8 +3276,14 @@ var init = new commander.Command().name("init").description("initialize React Gr
3176
3276
  );
3177
3277
  console.log();
3178
3278
  try {
3179
- const cwd = opts.cwd;
3180
- const isNonInteractive = opts.yes;
3279
+ const cwd = path.resolve(opts.cwd);
3280
+ const isNonInteractive = detectNonInteractive(opts.yes);
3281
+ if (!fs.existsSync(cwd)) {
3282
+ logger.break();
3283
+ logger.error(`Directory does not exist: ${highlighter.info(cwd)}`);
3284
+ logger.break();
3285
+ process.exit(1);
3286
+ }
3181
3287
  const preflightSpinner = spinner("Preflight checks.").start();
3182
3288
  const projectInfo = await detectProject(cwd);
3183
3289
  const removeAgents = async (agentsToRemove2, skipInstall = false) => {
@@ -3704,58 +3810,48 @@ var init = new commander.Command().name("init").description("initialize React Gr
3704
3810
  process.exit(1);
3705
3811
  }
3706
3812
  if (projectInfo.framework === "unknown") {
3707
- if (projectInfo.isMonorepo && !isNonInteractive) {
3708
- frameworkSpinner.info("Verifying framework. Found monorepo.");
3709
- const workspaceProjects = findWorkspaceProjects(
3710
- projectInfo.projectRoot
3711
- );
3712
- const reactProjects = workspaceProjects.filter(
3713
- (project) => project.hasReact || project.framework !== "unknown"
3813
+ let searchRoot = cwd;
3814
+ let reactProjects = findReactProjects(searchRoot);
3815
+ if (reactProjects.length === 0 && cwd !== process.cwd()) {
3816
+ searchRoot = process.cwd();
3817
+ reactProjects = findReactProjects(searchRoot);
3818
+ }
3819
+ if (reactProjects.length > 0) {
3820
+ frameworkSpinner.info(
3821
+ `Verifying framework. Found ${reactProjects.length} project${reactProjects.length === 1 ? "" : "s"}.`
3714
3822
  );
3715
- if (reactProjects.length > 0) {
3716
- logger.break();
3717
- const sortedProjects = [...reactProjects].sort(
3718
- (projectA, projectB) => {
3719
- if (projectA.framework === "unknown" && projectB.framework !== "unknown")
3720
- return 1;
3721
- if (projectA.framework !== "unknown" && projectB.framework === "unknown")
3722
- return -1;
3723
- return 0;
3724
- }
3725
- );
3726
- const { selectedProject } = await prompts({
3727
- type: "select",
3728
- name: "selectedProject",
3729
- message: "Select a project to install React Grab:",
3730
- choices: [
3731
- ...sortedProjects.map((project) => {
3732
- const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
3733
- return {
3734
- title: `${project.name}${frameworkLabel}`,
3735
- value: project.path
3736
- };
3737
- }),
3738
- { title: "Skip", value: "skip" }
3739
- ]
3740
- });
3741
- if (!selectedProject || selectedProject === "skip") {
3742
- logger.break();
3743
- process.exit(0);
3744
- }
3745
- process.chdir(selectedProject);
3746
- const newProjectInfo = await detectProject(selectedProject);
3747
- Object.assign(projectInfo, newProjectInfo);
3748
- const newFrameworkSpinner = spinner("Verifying framework.").start();
3749
- newFrameworkSpinner.succeed(
3750
- `Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES[newProjectInfo.framework])}.`
3751
- );
3752
- } else {
3753
- frameworkSpinner.fail("Could not detect a supported framework.");
3754
- logger.break();
3755
- logger.log(`Visit ${highlighter.info(DOCS_URL)} for manual setup.`);
3756
- logger.break();
3823
+ const sortedProjects = sortProjectsByFramework(reactProjects);
3824
+ if (isNonInteractive) {
3825
+ printSubprojects(searchRoot, sortedProjects);
3757
3826
  process.exit(1);
3758
3827
  }
3828
+ logger.break();
3829
+ const { selectedProject } = await prompts({
3830
+ type: "select",
3831
+ name: "selectedProject",
3832
+ message: "Select a project to install React Grab:",
3833
+ choices: [
3834
+ ...sortedProjects.map((project) => {
3835
+ const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
3836
+ return {
3837
+ title: `${project.name}${frameworkLabel}`,
3838
+ value: project.path
3839
+ };
3840
+ }),
3841
+ { title: "Skip", value: "skip" }
3842
+ ]
3843
+ });
3844
+ if (!selectedProject || selectedProject === "skip") {
3845
+ logger.break();
3846
+ process.exit(0);
3847
+ }
3848
+ process.chdir(selectedProject);
3849
+ const newProjectInfo = await detectProject(selectedProject);
3850
+ Object.assign(projectInfo, newProjectInfo);
3851
+ const newFrameworkSpinner = spinner("Verifying framework.").start();
3852
+ newFrameworkSpinner.succeed(
3853
+ `Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES[newProjectInfo.framework])}.`
3854
+ );
3759
3855
  } else {
3760
3856
  frameworkSpinner.fail("Could not detect a supported framework.");
3761
3857
  logger.break();
@@ -3945,7 +4041,7 @@ var init = new commander.Command().name("init").description("initialize React Gr
3945
4041
  reportToCli("error", void 0, error);
3946
4042
  }
3947
4043
  });
3948
- var VERSION4 = "0.1.14";
4044
+ var VERSION4 = "0.1.15";
3949
4045
  var remove = new commander.Command().name("remove").description("disconnect React Grab from your agent").argument("[agent]", `agent to disconnect (${AGENTS.join(", ")}, mcp)`).option("-y, --yes", "skip confirmation prompts", false).option(
3950
4046
  "-c, --cwd <cwd>",
3951
4047
  "working directory (defaults to current directory)",
@@ -3957,7 +4053,7 @@ var remove = new commander.Command().name("remove").description("disconnect Reac
3957
4053
  console.log();
3958
4054
  try {
3959
4055
  const cwd = opts.cwd;
3960
- const isNonInteractive = opts.yes;
4056
+ const isNonInteractive = detectNonInteractive(opts.yes);
3961
4057
  const preflightSpinner = spinner("Preflight checks.").start();
3962
4058
  const projectInfo = await detectProject(cwd);
3963
4059
  if (!projectInfo.hasReactGrab) {
@@ -4121,7 +4217,7 @@ var remove = new commander.Command().name("remove").description("disconnect Reac
4121
4217
  });
4122
4218
 
4123
4219
  // src/cli.ts
4124
- var VERSION5 = "0.1.14";
4220
+ var VERSION5 = "0.1.15";
4125
4221
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
4126
4222
  process.on("SIGINT", () => process.exit(0));
4127
4223
  process.on("SIGTERM", () => process.exit(0));
package/dist/cli.js CHANGED
@@ -3,13 +3,23 @@ import { Command } from 'commander';
3
3
  import pc from 'picocolors';
4
4
  import basePrompts from 'prompts';
5
5
  import { execSync } from 'child_process';
6
- import fs, { readFileSync, existsSync, writeFileSync, accessSync, constants, readdirSync } from 'fs';
7
- import path, { join, basename } from 'path';
6
+ import fs, { existsSync, readFileSync, writeFileSync, accessSync, constants, readdirSync } from 'fs';
7
+ import path, { resolve, join, relative, basename } from 'path';
8
8
  import { detect } from '@antfu/ni';
9
+ import ignore from 'ignore';
9
10
  import os from 'os';
10
11
  import process2 from 'process';
11
12
  import ora from 'ora';
12
13
 
14
+ // src/utils/is-non-interactive.ts
15
+ var NON_INTERACTIVE_ENVIRONMENT_VARIABLES = [
16
+ "CI",
17
+ "CLAUDECODE",
18
+ "AMI"
19
+ ];
20
+ var detectNonInteractive = (yesFlag) => yesFlag || NON_INTERACTIVE_ENVIRONMENT_VARIABLES.some(
21
+ (variable) => process.env[variable] === "true"
22
+ ) || !process.stdin.isTTY;
13
23
  var highlighter = {
14
24
  error: pc.red,
15
25
  warn: pc.yellow,
@@ -179,18 +189,22 @@ var getWorkspacePatterns = (projectRoot) => {
179
189
  return [...new Set(patterns)];
180
190
  };
181
191
  var expandWorkspacePattern = (projectRoot, pattern) => {
182
- const results = [];
192
+ const isGlob = pattern.endsWith("/*");
183
193
  const cleanPattern = pattern.replace(/\/\*$/, "");
184
194
  const basePath = join(projectRoot, cleanPattern);
185
- if (!existsSync(basePath)) return results;
195
+ if (!existsSync(basePath)) return [];
196
+ if (!isGlob) {
197
+ const hasPackageJson = existsSync(join(basePath, "package.json"));
198
+ return hasPackageJson ? [basePath] : [];
199
+ }
200
+ const results = [];
186
201
  try {
187
202
  const entries = readdirSync(basePath, { withFileTypes: true });
188
203
  for (const entry of entries) {
189
- if (entry.isDirectory()) {
190
- const packageJsonPath = join(basePath, entry.name, "package.json");
191
- if (existsSync(packageJsonPath)) {
192
- results.push(join(basePath, entry.name));
193
- }
204
+ if (!entry.isDirectory()) continue;
205
+ const packageJsonPath = join(basePath, entry.name, "package.json");
206
+ if (existsSync(packageJsonPath)) {
207
+ results.push(join(basePath, entry.name));
194
208
  }
195
209
  }
196
210
  } catch {
@@ -212,35 +226,92 @@ var hasReactDependency = (projectPath) => {
212
226
  return false;
213
227
  }
214
228
  };
229
+ var buildReactProject = (projectPath) => {
230
+ const framework = detectFramework(projectPath);
231
+ const hasReact = hasReactDependency(projectPath);
232
+ if (!hasReact && framework === "unknown") return null;
233
+ let name = basename(projectPath);
234
+ const packageJsonPath = join(projectPath, "package.json");
235
+ try {
236
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
237
+ name = packageJson.name || name;
238
+ } catch {
239
+ }
240
+ return { name, path: projectPath, framework, hasReact };
241
+ };
215
242
  var findWorkspaceProjects = (projectRoot) => {
216
243
  const patterns = getWorkspacePatterns(projectRoot);
217
244
  const projects = [];
218
245
  for (const pattern of patterns) {
219
- const projectPaths = expandWorkspacePattern(projectRoot, pattern);
220
- for (const projectPath of projectPaths) {
221
- const framework = detectFramework(projectPath);
222
- const hasReact = hasReactDependency(projectPath);
223
- if (hasReact || framework !== "unknown") {
224
- const packageJsonPath = join(projectPath, "package.json");
225
- let name = basename(projectPath);
226
- try {
227
- const packageJson = JSON.parse(
228
- readFileSync(packageJsonPath, "utf-8")
229
- );
230
- name = packageJson.name || name;
231
- } catch {
246
+ for (const projectPath of expandWorkspacePattern(projectRoot, pattern)) {
247
+ const project = buildReactProject(projectPath);
248
+ if (project) projects.push(project);
249
+ }
250
+ }
251
+ return projects;
252
+ };
253
+ var ALWAYS_IGNORED_DIRECTORIES = [
254
+ "node_modules",
255
+ ".git",
256
+ ".next",
257
+ ".cache",
258
+ ".turbo",
259
+ "dist",
260
+ "build",
261
+ "coverage",
262
+ "test-results"
263
+ ];
264
+ var loadGitignore = (projectRoot) => {
265
+ const ignorer = ignore().add(ALWAYS_IGNORED_DIRECTORIES);
266
+ const gitignorePath = join(projectRoot, ".gitignore");
267
+ if (existsSync(gitignorePath)) {
268
+ try {
269
+ ignorer.add(readFileSync(gitignorePath, "utf-8"));
270
+ } catch {
271
+ }
272
+ }
273
+ return ignorer;
274
+ };
275
+ var scanDirectoryForProjects = (rootDirectory, ignorer, maxDepth, currentDepth = 0) => {
276
+ if (currentDepth >= maxDepth) return [];
277
+ if (!existsSync(rootDirectory)) return [];
278
+ const projects = [];
279
+ try {
280
+ const entries = readdirSync(rootDirectory, { withFileTypes: true });
281
+ for (const entry of entries) {
282
+ if (!entry.isDirectory()) continue;
283
+ if (ignorer.ignores(entry.name)) continue;
284
+ const entryPath = join(rootDirectory, entry.name);
285
+ const hasPackageJson = existsSync(join(entryPath, "package.json"));
286
+ if (hasPackageJson) {
287
+ const project = buildReactProject(entryPath);
288
+ if (project) {
289
+ projects.push(project);
290
+ continue;
232
291
  }
233
- projects.push({
234
- name,
235
- path: projectPath,
236
- framework,
237
- hasReact
238
- });
239
292
  }
293
+ projects.push(
294
+ ...scanDirectoryForProjects(
295
+ entryPath,
296
+ ignorer,
297
+ maxDepth,
298
+ currentDepth + 1
299
+ )
300
+ );
240
301
  }
302
+ } catch {
303
+ return projects;
241
304
  }
242
305
  return projects;
243
306
  };
307
+ var MAX_SCAN_DEPTH = 2;
308
+ var findReactProjects = (projectRoot) => {
309
+ if (detectMonorepo(projectRoot)) {
310
+ return findWorkspaceProjects(projectRoot);
311
+ }
312
+ const ignorer = loadGitignore(projectRoot);
313
+ return scanDirectoryForProjects(projectRoot, ignorer, MAX_SCAN_DEPTH);
314
+ };
244
315
  var hasReactGrabInFile = (filePath) => {
245
316
  if (!existsSync(filePath)) return false;
246
317
  try {
@@ -2182,7 +2253,7 @@ var previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDoma
2182
2253
  };
2183
2254
 
2184
2255
  // src/commands/add.ts
2185
- var VERSION = "0.1.14";
2256
+ var VERSION = "0.1.15";
2186
2257
  var formatInstalledAgentNames = (agents) => agents.map((agent) => AGENT_NAMES[agent] || agent).join(", ");
2187
2258
  var add = new Command().name("add").alias("install").description("connect React Grab to your agent").argument("[agent]", `agent to connect (${AGENTS.join(", ")}, mcp)`).option("-y, --yes", "skip confirmation prompts", false).option(
2188
2259
  "-c, --cwd <cwd>",
@@ -2195,7 +2266,7 @@ var add = new Command().name("add").alias("install").description("connect React
2195
2266
  console.log();
2196
2267
  try {
2197
2268
  const cwd = opts.cwd;
2198
- const isNonInteractive = opts.yes;
2269
+ const isNonInteractive = detectNonInteractive(opts.yes);
2199
2270
  const preflightSpinner = spinner("Preflight checks.").start();
2200
2271
  const projectInfo = await detectProject(cwd);
2201
2272
  if (!projectInfo.hasReactGrab) {
@@ -2543,7 +2614,7 @@ var MAX_KEY_HOLD_DURATION_MS = 2e3;
2543
2614
  var MAX_CONTEXT_LINES = 50;
2544
2615
 
2545
2616
  // src/commands/configure.ts
2546
- var VERSION2 = "0.1.14";
2617
+ var VERSION2 = "0.1.15";
2547
2618
  var isMac = process.platform === "darwin";
2548
2619
  var META_LABEL = isMac ? "Cmd" : "Win";
2549
2620
  var ALT_LABEL = isMac ? "Option" : "Alt";
@@ -3099,7 +3170,7 @@ var uninstallPackagesWithFeedback = (packages, packageManager, projectRoot) => {
3099
3170
  };
3100
3171
 
3101
3172
  // src/commands/init.ts
3102
- var VERSION3 = "0.1.14";
3173
+ var VERSION3 = "0.1.15";
3103
3174
  var REPORT_URL = "https://react-grab.com/api/report-cli";
3104
3175
  var DOCS_URL = "https://github.com/aidenybai/react-grab";
3105
3176
  var reportToCli = (type, config, error) => {
@@ -3136,6 +3207,34 @@ var UNSUPPORTED_FRAMEWORK_NAMES = {
3136
3207
  gatsby: "Gatsby"
3137
3208
  };
3138
3209
  var getAgentName = getAgentDisplayName;
3210
+ var sortProjectsByFramework = (projects) => [...projects].sort((projectA, projectB) => {
3211
+ if (projectA.framework === "unknown" && projectB.framework !== "unknown")
3212
+ return 1;
3213
+ if (projectA.framework !== "unknown" && projectB.framework === "unknown")
3214
+ return -1;
3215
+ return 0;
3216
+ });
3217
+ var printSubprojects = (searchRoot, sortedProjects) => {
3218
+ logger.break();
3219
+ logger.log("Found the following projects:");
3220
+ logger.break();
3221
+ for (const project of sortedProjects) {
3222
+ const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
3223
+ const relativePath = relative(searchRoot, project.path);
3224
+ logger.log(
3225
+ ` ${highlighter.info(project.name)}${frameworkLabel} ${highlighter.dim(relativePath)}`
3226
+ );
3227
+ }
3228
+ logger.break();
3229
+ logger.log(
3230
+ `Re-run with ${highlighter.info("-c <path>")} to specify a project:`
3231
+ );
3232
+ logger.break();
3233
+ logger.log(
3234
+ ` ${highlighter.dim("$")} npx -y grab@latest init -c ${relative(searchRoot, sortedProjects[0].path)}`
3235
+ );
3236
+ logger.break();
3237
+ };
3139
3238
  var formatActivationKeyDisplay2 = (activationKey) => {
3140
3239
  if (!activationKey) return "Default (Option/Alt)";
3141
3240
  return activationKey.split("+").map((part) => {
@@ -3164,8 +3263,14 @@ var init = new Command().name("init").description("initialize React Grab in your
3164
3263
  );
3165
3264
  console.log();
3166
3265
  try {
3167
- const cwd = opts.cwd;
3168
- const isNonInteractive = opts.yes;
3266
+ const cwd = resolve(opts.cwd);
3267
+ const isNonInteractive = detectNonInteractive(opts.yes);
3268
+ if (!existsSync(cwd)) {
3269
+ logger.break();
3270
+ logger.error(`Directory does not exist: ${highlighter.info(cwd)}`);
3271
+ logger.break();
3272
+ process.exit(1);
3273
+ }
3169
3274
  const preflightSpinner = spinner("Preflight checks.").start();
3170
3275
  const projectInfo = await detectProject(cwd);
3171
3276
  const removeAgents = async (agentsToRemove2, skipInstall = false) => {
@@ -3692,58 +3797,48 @@ var init = new Command().name("init").description("initialize React Grab in your
3692
3797
  process.exit(1);
3693
3798
  }
3694
3799
  if (projectInfo.framework === "unknown") {
3695
- if (projectInfo.isMonorepo && !isNonInteractive) {
3696
- frameworkSpinner.info("Verifying framework. Found monorepo.");
3697
- const workspaceProjects = findWorkspaceProjects(
3698
- projectInfo.projectRoot
3699
- );
3700
- const reactProjects = workspaceProjects.filter(
3701
- (project) => project.hasReact || project.framework !== "unknown"
3800
+ let searchRoot = cwd;
3801
+ let reactProjects = findReactProjects(searchRoot);
3802
+ if (reactProjects.length === 0 && cwd !== process.cwd()) {
3803
+ searchRoot = process.cwd();
3804
+ reactProjects = findReactProjects(searchRoot);
3805
+ }
3806
+ if (reactProjects.length > 0) {
3807
+ frameworkSpinner.info(
3808
+ `Verifying framework. Found ${reactProjects.length} project${reactProjects.length === 1 ? "" : "s"}.`
3702
3809
  );
3703
- if (reactProjects.length > 0) {
3704
- logger.break();
3705
- const sortedProjects = [...reactProjects].sort(
3706
- (projectA, projectB) => {
3707
- if (projectA.framework === "unknown" && projectB.framework !== "unknown")
3708
- return 1;
3709
- if (projectA.framework !== "unknown" && projectB.framework === "unknown")
3710
- return -1;
3711
- return 0;
3712
- }
3713
- );
3714
- const { selectedProject } = await prompts({
3715
- type: "select",
3716
- name: "selectedProject",
3717
- message: "Select a project to install React Grab:",
3718
- choices: [
3719
- ...sortedProjects.map((project) => {
3720
- const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
3721
- return {
3722
- title: `${project.name}${frameworkLabel}`,
3723
- value: project.path
3724
- };
3725
- }),
3726
- { title: "Skip", value: "skip" }
3727
- ]
3728
- });
3729
- if (!selectedProject || selectedProject === "skip") {
3730
- logger.break();
3731
- process.exit(0);
3732
- }
3733
- process.chdir(selectedProject);
3734
- const newProjectInfo = await detectProject(selectedProject);
3735
- Object.assign(projectInfo, newProjectInfo);
3736
- const newFrameworkSpinner = spinner("Verifying framework.").start();
3737
- newFrameworkSpinner.succeed(
3738
- `Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES[newProjectInfo.framework])}.`
3739
- );
3740
- } else {
3741
- frameworkSpinner.fail("Could not detect a supported framework.");
3742
- logger.break();
3743
- logger.log(`Visit ${highlighter.info(DOCS_URL)} for manual setup.`);
3744
- logger.break();
3810
+ const sortedProjects = sortProjectsByFramework(reactProjects);
3811
+ if (isNonInteractive) {
3812
+ printSubprojects(searchRoot, sortedProjects);
3745
3813
  process.exit(1);
3746
3814
  }
3815
+ logger.break();
3816
+ const { selectedProject } = await prompts({
3817
+ type: "select",
3818
+ name: "selectedProject",
3819
+ message: "Select a project to install React Grab:",
3820
+ choices: [
3821
+ ...sortedProjects.map((project) => {
3822
+ const frameworkLabel = project.framework !== "unknown" ? ` ${highlighter.dim(`(${FRAMEWORK_NAMES[project.framework]})`)}` : "";
3823
+ return {
3824
+ title: `${project.name}${frameworkLabel}`,
3825
+ value: project.path
3826
+ };
3827
+ }),
3828
+ { title: "Skip", value: "skip" }
3829
+ ]
3830
+ });
3831
+ if (!selectedProject || selectedProject === "skip") {
3832
+ logger.break();
3833
+ process.exit(0);
3834
+ }
3835
+ process.chdir(selectedProject);
3836
+ const newProjectInfo = await detectProject(selectedProject);
3837
+ Object.assign(projectInfo, newProjectInfo);
3838
+ const newFrameworkSpinner = spinner("Verifying framework.").start();
3839
+ newFrameworkSpinner.succeed(
3840
+ `Verifying framework. Found ${highlighter.info(FRAMEWORK_NAMES[newProjectInfo.framework])}.`
3841
+ );
3747
3842
  } else {
3748
3843
  frameworkSpinner.fail("Could not detect a supported framework.");
3749
3844
  logger.break();
@@ -3933,7 +4028,7 @@ var init = new Command().name("init").description("initialize React Grab in your
3933
4028
  reportToCli("error", void 0, error);
3934
4029
  }
3935
4030
  });
3936
- var VERSION4 = "0.1.14";
4031
+ var VERSION4 = "0.1.15";
3937
4032
  var remove = new Command().name("remove").description("disconnect React Grab from your agent").argument("[agent]", `agent to disconnect (${AGENTS.join(", ")}, mcp)`).option("-y, --yes", "skip confirmation prompts", false).option(
3938
4033
  "-c, --cwd <cwd>",
3939
4034
  "working directory (defaults to current directory)",
@@ -3945,7 +4040,7 @@ var remove = new Command().name("remove").description("disconnect React Grab fro
3945
4040
  console.log();
3946
4041
  try {
3947
4042
  const cwd = opts.cwd;
3948
- const isNonInteractive = opts.yes;
4043
+ const isNonInteractive = detectNonInteractive(opts.yes);
3949
4044
  const preflightSpinner = spinner("Preflight checks.").start();
3950
4045
  const projectInfo = await detectProject(cwd);
3951
4046
  if (!projectInfo.hasReactGrab) {
@@ -4109,7 +4204,7 @@ var remove = new Command().name("remove").description("disconnect React Grab fro
4109
4204
  });
4110
4205
 
4111
4206
  // src/cli.ts
4112
- var VERSION5 = "0.1.14";
4207
+ var VERSION5 = "0.1.15";
4113
4208
  var VERSION_API_URL = "https://www.react-grab.com/api/version";
4114
4209
  process.on("SIGINT", () => process.exit(0));
4115
4210
  process.on("SIGTERM", () => process.exit(0));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-grab/cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "bin": {
5
5
  "react-grab": "./dist/cli.js"
6
6
  },
@@ -18,6 +18,7 @@
18
18
  "dependencies": {
19
19
  "@antfu/ni": "^0.23.0",
20
20
  "commander": "^14.0.0",
21
+ "ignore": "^7.0.5",
21
22
  "ora": "^8.2.0",
22
23
  "picocolors": "^1.1.1",
23
24
  "prompts": "^2.4.2"