Files
HRM-System/node_modules/@react-grab/cli/dist/install-DlD3sLtm.cjs

1005 lines
40 KiB
JavaScript

#!/usr/bin/env node
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let node_path = require("node:path");
let node_fs = require("node:fs");
let package_manager_detector_detect = require("package-manager-detector/detect");
let ignore = require("ignore");
ignore = __toESM(ignore, 1);
let agent_install_skill = require("agent-install/skill");
let node_url = require("node:url");
let tinyexec = require("tinyexec");
//#region src/utils/react-grab-code.ts
const REACT_GRAB_SPECIFIER_PATTERN = String.raw`react-grab(?:\/[^"']+)?`;
const stripComments = (content) => content.replace(/<!--[\s\S]*?-->/g, "").replace(/\{\/\*[\s\S]*?\*\/\}/g, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/.*$/gm, "$1");
const stripTypeOnlyReactGrabImports = (content) => {
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"), "");
};
const hasReactGrabSetupCode = (content) => {
const setupCandidateContent = stripTypeOnlyReactGrabImports(stripComments(content));
return [
new RegExp(String.raw`import\s*\(\s*["']${REACT_GRAB_SPECIFIER_PATTERN}["']\s*\)`),
new RegExp(String.raw`import\s+(?!type\b)(?:[^"';]+from\s+)?["']${REACT_GRAB_SPECIFIER_PATTERN}["']`),
new RegExp(String.raw`require\s*\(\s*["']${REACT_GRAB_SPECIFIER_PATTERN}["']\s*\)`),
/<Script[\s\S]*?src\s*=\s*(?:["'][^"']*react-grab[^"']*["']|\{(?:["'][^"']*react-grab[^"']*["']|`[^`]*react-grab[^`]*`)\})/i,
/<script[\s\S]*?src\s*=\s*["'][^"']*react-grab[^"']*["']/i
].some((pattern) => pattern.test(setupCandidateContent));
};
//#endregion
//#region src/utils/react-grab-setup-files.ts
const COMPONENT_EXTENSIONS = [
"tsx",
"jsx",
"ts",
"js"
];
const INSTRUMENTATION_EXTENSIONS = [
"ts",
"tsx",
"js",
"jsx",
"mts",
"cts",
"mjs",
"cjs"
];
const ROUTE_EXTENSIONS = ["tsx", "jsx"];
const createFileCandidates = (projectRoot, directories, baseName, extensions) => {
const fileCandidates = [];
for (const directory of directories) for (const extension of extensions) fileCandidates.push((0, node_path.join)(projectRoot, directory, `${baseName}.${extension}`));
return fileCandidates;
};
const findExistingFile = (fileCandidates) => {
for (const filePath of fileCandidates) if ((0, node_fs.existsSync)(filePath)) return filePath;
return null;
};
const getLayoutFileCandidates = (projectRoot) => createFileCandidates(projectRoot, ["app", "src/app"], "layout", COMPONENT_EXTENSIONS);
const getDocumentFileCandidates = (projectRoot) => createFileCandidates(projectRoot, ["pages", "src/pages"], "_document", COMPONENT_EXTENSIONS);
const getInstrumentationFileCandidates = (projectRoot) => createFileCandidates(projectRoot, ["", "src"], "instrumentation-client", INSTRUMENTATION_EXTENSIONS);
const getIndexHtmlCandidates = (projectRoot) => [(0, node_path.join)(projectRoot, "index.html"), (0, node_path.join)(projectRoot, "public", "index.html")];
const getEntryFileCandidates = (projectRoot) => [...createFileCandidates(projectRoot, ["src"], "index", COMPONENT_EXTENSIONS), ...createFileCandidates(projectRoot, ["src"], "main", COMPONENT_EXTENSIONS)];
const getTanStackRootFileCandidates = (projectRoot) => createFileCandidates(projectRoot, ["src/routes", "app/routes"], "__root", ROUTE_EXTENSIONS);
const getReactGrabSetupFileCandidates = (projectRoot) => [
...getLayoutFileCandidates(projectRoot),
...getDocumentFileCandidates(projectRoot),
...getInstrumentationFileCandidates(projectRoot),
...getIndexHtmlCandidates(projectRoot),
...getEntryFileCandidates(projectRoot),
...getTanStackRootFileCandidates(projectRoot)
];
const findLayoutFile = (projectRoot) => findExistingFile(getLayoutFileCandidates(projectRoot));
const findDocumentFile = (projectRoot) => findExistingFile(getDocumentFileCandidates(projectRoot));
const findIndexHtml = (projectRoot) => findExistingFile(getIndexHtmlCandidates(projectRoot));
const findEntryFile = (projectRoot) => findExistingFile(getEntryFileCandidates(projectRoot));
const findTanStackRootFile = (projectRoot) => findExistingFile(getTanStackRootFileCandidates(projectRoot));
const isInstrumentationFile = (filePath) => /(?:^|[/\\])instrumentation-client\.[cm]?[jt]sx?$/.test(filePath);
//#endregion
//#region src/utils/detect.ts
const VALID_PACKAGE_MANAGERS = new Set([
"npm",
"yarn",
"pnpm",
"bun"
]);
const detectPackageManager = async (projectRoot) => {
const result = await (0, package_manager_detector_detect.detect)({ cwd: projectRoot });
if (result?.agent) {
const managerName = result.agent.split("@")[0];
if (VALID_PACKAGE_MANAGERS.has(managerName)) return managerName;
}
return "npm";
};
const CONFIG_EXTENSIONS = [
"ts",
"mts",
"cts",
"js",
"mjs",
"cjs"
];
const hasConfigFile = (projectRoot, configBaseName) => CONFIG_EXTENSIONS.some((extension) => (0, node_fs.existsSync)((0, node_path.join)(projectRoot, `${configBaseName}.${extension}`)));
const readMergedDependencies = (projectRoot) => {
const packageJsonPath = (0, node_path.join)(projectRoot, "package.json");
if (!(0, node_fs.existsSync)(packageJsonPath)) return null;
try {
const packageJson = JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8"));
return {
...packageJson.dependencies,
...packageJson.devDependencies
};
} catch {
return null;
}
};
const detectFrameworkFromDependencies = (dependencies) => {
if (!dependencies) return "unknown";
if (dependencies["next"]) return "next";
if (dependencies["@tanstack/react-start"]) return "tanstack";
if (dependencies["vite"]) return "vite";
if (dependencies["webpack"]) return "webpack";
return "unknown";
};
const detectFrameworkFromConfigFiles = (projectRoot) => {
if (hasConfigFile(projectRoot, "next.config")) return "next";
if (hasConfigFile(projectRoot, "app.config")) return "tanstack";
if (hasConfigFile(projectRoot, "vite.config")) return "vite";
if (hasConfigFile(projectRoot, "webpack.config")) return "webpack";
return "unknown";
};
const findEnclosingMonorepoRoot = (projectRoot) => {
let currentDirectory = (0, node_path.dirname)(projectRoot);
while (currentDirectory !== (0, node_path.dirname)(currentDirectory)) {
if (detectMonorepo(currentDirectory)) return currentDirectory;
currentDirectory = (0, node_path.dirname)(currentDirectory);
}
return null;
};
const detectFramework = (projectRoot) => {
const localFramework = detectFrameworkFromDependencies(readMergedDependencies(projectRoot));
if (localFramework !== "unknown") return localFramework;
return detectFrameworkFromConfigFiles(projectRoot);
};
const detectFrameworkFromMonorepoRoot = (projectRoot) => {
const monorepoRoot = findEnclosingMonorepoRoot(projectRoot);
if (!monorepoRoot) return "unknown";
return detectFrameworkFromDependencies(readMergedDependencies(monorepoRoot));
};
const detectNextRouterType = (projectRoot) => {
const hasAppDir = (0, node_fs.existsSync)((0, node_path.join)(projectRoot, "app"));
const hasSrcAppDir = (0, node_fs.existsSync)((0, node_path.join)(projectRoot, "src", "app"));
const hasPagesDir = (0, node_fs.existsSync)((0, node_path.join)(projectRoot, "pages"));
const hasSrcPagesDir = (0, node_fs.existsSync)((0, node_path.join)(projectRoot, "src", "pages"));
if (hasAppDir || hasSrcAppDir) return "app";
if (hasPagesDir || hasSrcPagesDir) return "pages";
return "unknown";
};
const detectMonorepo = (projectRoot) => {
if ((0, node_fs.existsSync)((0, node_path.join)(projectRoot, "pnpm-workspace.yaml"))) return true;
if ((0, node_fs.existsSync)((0, node_path.join)(projectRoot, "lerna.json"))) return true;
const packageJsonPath = (0, node_path.join)(projectRoot, "package.json");
if ((0, node_fs.existsSync)(packageJsonPath)) try {
if (JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8")).workspaces) return true;
} catch {
return false;
}
return false;
};
const getWorkspacePatterns = (projectRoot) => {
const patterns = [];
const pnpmWorkspacePath = (0, node_path.join)(projectRoot, "pnpm-workspace.yaml");
if ((0, node_fs.existsSync)(pnpmWorkspacePath)) {
const lines = (0, node_fs.readFileSync)(pnpmWorkspacePath, "utf-8").split("\n");
let inPackages = false;
for (const line of lines) {
if (line.match(/^packages:\s*$/)) {
inPackages = true;
continue;
}
if (inPackages) {
if (line.match(/^[a-zA-Z]/) || line.trim() === "") {
if (line.match(/^[a-zA-Z]/)) inPackages = false;
continue;
}
const match = line.match(/^\s*-\s*['"]?([^'"#\n]+?)['"]?\s*$/);
if (match) patterns.push(match[1].trim());
}
}
}
const lernaJsonPath = (0, node_path.join)(projectRoot, "lerna.json");
if ((0, node_fs.existsSync)(lernaJsonPath)) try {
const lernaJson = JSON.parse((0, node_fs.readFileSync)(lernaJsonPath, "utf-8"));
if (Array.isArray(lernaJson.packages)) patterns.push(...lernaJson.packages);
} catch {}
const packageJsonPath = (0, node_path.join)(projectRoot, "package.json");
if ((0, node_fs.existsSync)(packageJsonPath)) try {
const packageJson = JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8"));
if (Array.isArray(packageJson.workspaces)) patterns.push(...packageJson.workspaces);
else if (packageJson.workspaces?.packages) patterns.push(...packageJson.workspaces.packages);
} catch {}
return [...new Set(patterns)];
};
const expandWorkspacePattern = (projectRoot, pattern) => {
const isGlob = pattern.endsWith("/*");
const basePath = (0, node_path.join)(projectRoot, pattern.replace(/\/\*$/, ""));
if (!(0, node_fs.existsSync)(basePath)) return [];
if (!isGlob) return (0, node_fs.existsSync)((0, node_path.join)(basePath, "package.json")) ? [basePath] : [];
const results = [];
try {
const entries = (0, node_fs.readdirSync)(basePath, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
if ((0, node_fs.existsSync)((0, node_path.join)(basePath, entry.name, "package.json"))) results.push((0, node_path.join)(basePath, entry.name));
}
} catch {
return results;
}
return results;
};
const hasReactDependency = (projectPath) => {
const dependencies = readMergedDependencies(projectPath);
if (!dependencies) return false;
return Boolean(dependencies["react"] || dependencies["react-dom"]);
};
const buildReactProject = (projectPath) => {
const framework = detectFramework(projectPath);
if (!hasReactDependency(projectPath) && framework === "unknown") return null;
let name = (0, node_path.basename)(projectPath);
const packageJsonPath = (0, node_path.join)(projectPath, "package.json");
try {
name = JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8")).name || name;
} catch {}
return {
name,
path: projectPath,
framework
};
};
const findWorkspaceProjects = (projectRoot) => {
const patterns = getWorkspacePatterns(projectRoot);
const projects = [];
for (const pattern of patterns) for (const projectPath of expandWorkspacePattern(projectRoot, pattern)) {
const project = buildReactProject(projectPath);
if (project) projects.push(project);
}
return projects;
};
const ALWAYS_IGNORED_DIRECTORIES = [
"node_modules",
".git",
".next",
".cache",
".turbo",
"dist",
"build",
"coverage",
"test-results"
];
const loadGitignore = (projectRoot) => {
const ignorer = (0, ignore.default)().add(ALWAYS_IGNORED_DIRECTORIES);
const gitignorePath = (0, node_path.join)(projectRoot, ".gitignore");
if ((0, node_fs.existsSync)(gitignorePath)) try {
ignorer.add((0, node_fs.readFileSync)(gitignorePath, "utf-8"));
} catch {}
return ignorer;
};
const scanDirectoryForProjects = (rootDirectory, ignorer, maxDepth, currentDepth = 0) => {
if (currentDepth >= maxDepth) return [];
if (!(0, node_fs.existsSync)(rootDirectory)) return [];
const projects = [];
try {
const entries = (0, node_fs.readdirSync)(rootDirectory, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
if (ignorer.ignores(entry.name)) continue;
const entryPath = (0, node_path.join)(rootDirectory, entry.name);
if ((0, node_fs.existsSync)((0, node_path.join)(entryPath, "package.json"))) {
const project = buildReactProject(entryPath);
if (project) {
projects.push(project);
continue;
}
}
projects.push(...scanDirectoryForProjects(entryPath, ignorer, maxDepth, currentDepth + 1));
}
} catch {
return projects;
}
return projects;
};
const MAX_SCAN_DEPTH = 2;
const normalizePathForComparison = (filePath) => filePath.replace(/\\/g, "/");
const findReactProjects = (projectRoot) => {
const monorepoRoot = detectMonorepo(projectRoot) ? projectRoot : findEnclosingMonorepoRoot(projectRoot);
if (monorepoRoot) {
const workspaceProjects = findWorkspaceProjects(monorepoRoot);
const localProject = projectRoot === monorepoRoot ? null : buildReactProject(projectRoot);
const projects = localProject ? [localProject, ...workspaceProjects.filter((project) => normalizePathForComparison(project.path) !== normalizePathForComparison(localProject.path))] : workspaceProjects;
if (projects.length > 0) return projects;
}
const scannedProjects = scanDirectoryForProjects(projectRoot, loadGitignore(projectRoot), MAX_SCAN_DEPTH);
if (scannedProjects.length > 0) return scannedProjects;
let currentDirectory = (0, node_path.dirname)(projectRoot);
while (currentDirectory !== (0, node_path.dirname)(currentDirectory)) {
const parentProject = buildReactProject(currentDirectory);
if (parentProject) return [parentProject];
currentDirectory = (0, node_path.dirname)(currentDirectory);
}
return [];
};
const hasReactGrabSetupInFile = (filePath) => {
if (!(0, node_fs.existsSync)(filePath)) return false;
try {
return hasReactGrabSetupCode((0, node_fs.readFileSync)(filePath, "utf-8"));
} catch {
return false;
}
};
const detectReactGrabDependency = (projectRoot) => {
const dependencies = readMergedDependencies(projectRoot);
return Boolean(dependencies?.["react-grab"]);
};
const detectReactGrabConfigured = (projectRoot) => {
return getReactGrabSetupFileCandidates(projectRoot).some(hasReactGrabSetupInFile);
};
const detectReactGrab = (projectRoot) => detectReactGrabDependency(projectRoot) || detectReactGrabConfigured(projectRoot);
const detectUnsupportedFramework = (projectRoot) => {
const dependencies = readMergedDependencies(projectRoot);
if (!dependencies) return null;
if (dependencies["@remix-run/react"] || dependencies["remix"]) return "remix";
if (dependencies["astro"]) return "astro";
if (dependencies["@sveltejs/kit"]) return "sveltekit";
if (dependencies["gatsby"]) return "gatsby";
return null;
};
const detectReactGrabVersion = (projectRoot) => {
const installedPackageJsonPath = (0, node_path.join)(projectRoot, "node_modules", "react-grab", "package.json");
if ((0, node_fs.existsSync)(installedPackageJsonPath)) try {
return JSON.parse((0, node_fs.readFileSync)(installedPackageJsonPath, "utf-8")).version ?? null;
} catch {}
return null;
};
const detectProject = async (projectRoot = process.cwd()) => {
const localFramework = detectFramework(projectRoot);
const framework = localFramework === "unknown" ? detectFrameworkFromMonorepoRoot(projectRoot) : localFramework;
const packageManager = await detectPackageManager(projectRoot);
const isMonorepo = detectMonorepo(projectRoot) || findEnclosingMonorepoRoot(projectRoot) !== null;
const isReactGrabConfigured = detectReactGrabConfigured(projectRoot);
return {
packageManager,
framework,
nextRouterType: framework === "next" ? detectNextRouterType(projectRoot) : "unknown",
isMonorepo,
projectRoot,
hasReactGrab: detectReactGrabDependency(projectRoot) || isReactGrabConfigured,
isReactGrabConfigured,
reactGrabVersion: detectReactGrabVersion(projectRoot),
unsupportedFramework: detectUnsupportedFramework(projectRoot)
};
};
//#endregion
//#region src/utils/detect-agents.ts
const PATH_BINARIES = {
"claude-code": ["claude"],
codex: ["codex"],
cursor: ["cursor", "cursor-agent"],
droid: ["droid"],
"gemini-cli": ["gemini"],
"github-copilot": ["copilot"],
opencode: ["opencode"],
pi: ["pi", "omegon"]
};
const isCommandAvailable = (command) => {
const pathDirectories = (process.env.PATH ?? "").split(node_path.delimiter).filter(Boolean);
for (const directory of pathDirectories) {
const binaryPath = (0, node_path.join)(directory, command);
try {
if ((0, node_fs.statSync)(binaryPath).isFile()) {
(0, node_fs.accessSync)(binaryPath, node_fs.constants.X_OK);
return true;
}
} catch {}
}
return false;
};
const detectAvailableAgents = async () => {
const installedAgents = new Set(await (0, agent_install_skill.detectInstalledSkillAgents)());
return (0, agent_install_skill.getSkillAgentTypes)().filter((agent) => {
if (agent === "universal") return false;
if (installedAgents.has(agent)) return true;
return PATH_BINARIES[agent]?.some(isCommandAvailable) ?? false;
});
};
//#endregion
//#region src/utils/install-skill.ts
const SKILL_NAME = "react-grab";
const SKILL_SOURCE = (0, node_url.fileURLToPath)(new URL("../skills/react-grab", require("url").pathToFileURL(__filename).href));
const agentLabel = (agent) => (0, agent_install_skill.getSkillAgentConfig)(agent).displayName;
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, {
global,
cwd
}), SKILL_NAME);
const installSkill = async ({ agents, global = false, cwd = process.cwd() } = {}) => {
return (0, agent_install_skill.add)({
source: SKILL_SOURCE,
agents: agents ?? await detectAvailableAgents(),
global,
cwd,
mode: "copy"
});
};
const removeSkill = async ({ cwd = process.cwd(), global = false } = {}) => {
const agents = await detectAvailableAgents();
const removedAgents = [];
const dirsToRemove = /* @__PURE__ */ new Set();
for (const agent of agents) {
const skillDir = installedSkillDir(agent, global, cwd);
if (!(0, node_fs.existsSync)(skillDir)) continue;
removedAgents.push(agent);
dirsToRemove.add(skillDir);
}
for (const skillDir of dirsToRemove) (0, node_fs.rmSync)(skillDir, {
recursive: true,
force: true
});
return removedAgents;
};
//#endregion
//#region src/utils/templates.ts
const NEXT_APP_ROUTER_SCRIPT = `{process.env.NODE_ENV === "development" && (
<Script
src="//unpkg.com/react-grab/dist/index.global.js"
crossOrigin="anonymous"
strategy="beforeInteractive"
/>
)}`;
const VITE_IMPORT = `if (import.meta.env.DEV) {
import("react-grab");
}`;
const WEBPACK_IMPORT = `if (process.env.NODE_ENV === "development") {
import("react-grab");
}`;
const TANSTACK_EFFECT = `useEffect(() => {
if (import.meta.env.DEV) {
void import("react-grab");
}
}, []);`;
const SCRIPT_IMPORT = "import Script from \"next/script\";";
//#endregion
//#region src/utils/transform.ts
const hasReactGrabInInstrumentation = (projectRoot) => {
return findFileWithReactGrabSetup(getInstrumentationFileCandidates(projectRoot)) !== null;
};
const findFileWithReactGrabSetup = (fileCandidates) => {
for (const filePath of fileCandidates) {
if (!(0, node_fs.existsSync)(filePath)) continue;
if (hasReactGrabSetupCode((0, node_fs.readFileSync)(filePath, "utf-8"))) return filePath;
}
return null;
};
const alreadyConfiguredResult = (filePath) => ({
success: true,
filePath,
message: "React Grab is already configured",
noChanges: true
});
const transformNextAppRouter = (projectRoot, reactGrabAlreadyConfigured) => {
const layoutPath = findLayoutFile(projectRoot);
if (!layoutPath) return {
success: false,
filePath: "",
message: "Could not find app/layout.tsx, app/layout.jsx, app/layout.ts, or app/layout.js"
};
const originalContent = (0, node_fs.readFileSync)(layoutPath, "utf-8");
let newContent = originalContent;
const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
if (hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(layoutPath);
if (hasReactGrabInFile || hasReactGrabInInstrumentationFile) return {
success: true,
filePath: layoutPath,
message: "React Grab is already installed" + (hasReactGrabInInstrumentationFile ? " in instrumentation-client" : " in this file"),
noChanges: true
};
if (!newContent.includes("import Script from \"next/script\"")) {
const importMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
if (importMatch) newContent = newContent.replace(importMatch[0], `${importMatch[0]}\n${SCRIPT_IMPORT}`);
else newContent = `${SCRIPT_IMPORT}\n\n${newContent}`;
}
const headMatch = newContent.match(/<head[^>]*>/);
if (headMatch) newContent = newContent.replace(headMatch[0], `${headMatch[0]}\n ${NEXT_APP_ROUTER_SCRIPT}`);
else {
const htmlMatch = newContent.match(/<html[^>]*>/);
if (htmlMatch) newContent = newContent.replace(htmlMatch[0], `${htmlMatch[0]}\n <head>\n ${NEXT_APP_ROUTER_SCRIPT}\n </head>`);
}
return {
success: true,
filePath: layoutPath,
message: "Add React Grab",
originalContent,
newContent
};
};
const transformNextPagesRouter = (projectRoot, reactGrabAlreadyConfigured) => {
const documentPath = findDocumentFile(projectRoot);
if (!documentPath) return {
success: false,
filePath: "",
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 }"
};
const originalContent = (0, node_fs.readFileSync)(documentPath, "utf-8");
let newContent = originalContent;
const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
const hasReactGrabInInstrumentationFile = hasReactGrabInInstrumentation(projectRoot);
if (hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(documentPath);
if (hasReactGrabInFile || hasReactGrabInInstrumentationFile) return {
success: true,
filePath: documentPath,
message: "React Grab is already installed" + (hasReactGrabInInstrumentationFile ? " in instrumentation-client" : " in this file"),
noChanges: true
};
if (!newContent.includes("import Script from \"next/script\"")) {
const importMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
if (importMatch) newContent = newContent.replace(importMatch[0], `${importMatch[0]}\n${SCRIPT_IMPORT}`);
}
const headMatch = newContent.match(/<Head[^>]*>/);
if (headMatch) newContent = newContent.replace(headMatch[0], `${headMatch[0]}\n ${NEXT_APP_ROUTER_SCRIPT}`);
return {
success: true,
filePath: documentPath,
message: "Add React Grab",
originalContent,
newContent
};
};
const checkExistingInstallation = (filePath, reactGrabAlreadyConfigured) => {
if (!hasReactGrabSetupCode((0, node_fs.readFileSync)(filePath, "utf-8"))) return null;
return {
success: true,
filePath,
message: reactGrabAlreadyConfigured ? "React Grab is already configured" : "React Grab is already installed in this file",
noChanges: true
};
};
const transformVite = (projectRoot, reactGrabAlreadyConfigured) => {
const entryPath = findEntryFile(projectRoot);
const indexPath = findIndexHtml(projectRoot);
if (indexPath) {
const existingResult = checkExistingInstallation(indexPath, reactGrabAlreadyConfigured);
if (existingResult) return existingResult;
}
if (!entryPath) return {
success: false,
filePath: "",
message: "Could not find entry file (src/index.tsx, src/main.tsx, etc.)"
};
const existingResult = checkExistingInstallation(entryPath, reactGrabAlreadyConfigured);
if (existingResult) return existingResult;
const originalContent = (0, node_fs.readFileSync)(entryPath, "utf-8");
return {
success: true,
filePath: entryPath,
message: "Add React Grab",
originalContent,
newContent: `${VITE_IMPORT}\n\n${originalContent}`
};
};
const transformWebpack = (projectRoot, reactGrabAlreadyConfigured) => {
const entryPath = findEntryFile(projectRoot);
if (!entryPath) return {
success: false,
filePath: "",
message: "Could not find entry file (src/index.tsx, src/main.tsx, etc.)"
};
const existingResult = checkExistingInstallation(entryPath, reactGrabAlreadyConfigured);
if (existingResult) return existingResult;
const originalContent = (0, node_fs.readFileSync)(entryPath, "utf-8");
return {
success: true,
filePath: entryPath,
message: "Add React Grab",
originalContent,
newContent: `${WEBPACK_IMPORT}\n\n${originalContent}`
};
};
const transformTanStack = (projectRoot, reactGrabAlreadyConfigured) => {
const rootPath = findTanStackRootFile(projectRoot);
if (!rootPath) return {
success: false,
filePath: "",
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 }, []);"
};
const originalContent = (0, node_fs.readFileSync)(rootPath, "utf-8");
let newContent = originalContent;
const hasReactGrabInFile = hasReactGrabSetupCode(originalContent);
if (hasReactGrabInFile && reactGrabAlreadyConfigured) return alreadyConfiguredResult(rootPath);
if (hasReactGrabInFile) return {
success: true,
filePath: rootPath,
message: "React Grab is already installed in this file",
noChanges: true
};
if (!/import\s+\{[^}]*useEffect[^}]*\}\s+from\s+["']react["']/.test(newContent)) {
const reactImportMatch = newContent.match(/import\s+\{([^}]*)\}\s+from\s+["']react["'];?/);
if (reactImportMatch) {
const existingImports = reactImportMatch[1];
newContent = newContent.replace(reactImportMatch[0], `import { ${existingImports.trim()}, useEffect } from "react";`);
} else {
const firstImportMatch = newContent.match(/^import .+ from ['"].+['"];?\s*$/m);
if (firstImportMatch) newContent = newContent.replace(firstImportMatch[0], `import { useEffect } from "react";\n${firstImportMatch[0]}`);
else newContent = `import { useEffect } from "react";\n\n${newContent}`;
}
}
const componentMatch = newContent.match(/function\s+(\w+)\s*\([^)]*\)\s*\{/);
if (componentMatch) {
const insertPosition = componentMatch.index + componentMatch[0].length;
newContent = newContent.slice(0, insertPosition) + `\n ${TANSTACK_EFFECT}\n` + newContent.slice(insertPosition);
} else return {
success: false,
filePath: rootPath,
message: "Could not find a component function in the root file"
};
return {
success: true,
filePath: rootPath,
message: "Add React Grab",
originalContent,
newContent
};
};
const hasFrameworkEntryPoint = (projectRoot, framework, nextRouterType) => {
switch (framework) {
case "next": return nextRouterType === "app" ? findLayoutFile(projectRoot) !== null : findDocumentFile(projectRoot) !== null;
case "vite":
case "webpack": return findEntryFile(projectRoot) !== null;
case "tanstack": return findTanStackRootFile(projectRoot) !== null;
default: return false;
}
};
const previewTransform = (projectRoot, framework, nextRouterType, reactGrabAlreadyConfigured = false) => {
switch (framework) {
case "next":
if (nextRouterType === "app") return transformNextAppRouter(projectRoot, reactGrabAlreadyConfigured);
return transformNextPagesRouter(projectRoot, reactGrabAlreadyConfigured);
case "vite": return transformVite(projectRoot, reactGrabAlreadyConfigured);
case "tanstack": return transformTanStack(projectRoot, reactGrabAlreadyConfigured);
case "webpack": return transformWebpack(projectRoot, reactGrabAlreadyConfigured);
default: return {
success: false,
filePath: "",
message: `Unknown framework: ${framework}. Please add React Grab manually.`
};
}
};
const canWriteToFile = (filePath) => {
try {
(0, node_fs.accessSync)(filePath, node_fs.constants.W_OK);
return true;
} catch {
return false;
}
};
const applyTransform = (result) => {
if (result.success && result.newContent && result.filePath) {
if (!canWriteToFile(result.filePath)) return {
success: false,
error: `Cannot write to ${result.filePath}. Check file permissions.`
};
try {
(0, node_fs.writeFileSync)(result.filePath, result.newContent);
return { success: true };
} catch (error) {
return {
success: false,
error: `Failed to write to ${result.filePath}: ${error instanceof Error ? error.message : "Unknown error"}`
};
}
}
return { success: true };
};
const formatOptionsForNextjs = (options) => {
const parts = [];
if (options.activationKey) parts.push(`activationKey: ${JSON.stringify(options.activationKey)}`);
if (options.activationMode) parts.push(`activationMode: "${options.activationMode}"`);
if (options.keyHoldDuration !== void 0) parts.push(`keyHoldDuration: ${options.keyHoldDuration}`);
if (options.allowActivationInsideInput !== void 0) parts.push(`allowActivationInsideInput: ${options.allowActivationInsideInput}`);
if (options.maxContextLines !== void 0) parts.push(`maxContextLines: ${options.maxContextLines}`);
return `{ ${parts.join(", ")} }`;
};
const formatOptionsAsJson = (options) => {
const cleanOptions = {};
if (options.activationKey) cleanOptions.activationKey = options.activationKey;
if (options.activationMode) cleanOptions.activationMode = options.activationMode;
if (options.keyHoldDuration !== void 0) cleanOptions.keyHoldDuration = options.keyHoldDuration;
if (options.allowActivationInsideInput !== void 0) cleanOptions.allowActivationInsideInput = options.allowActivationInsideInput;
if (options.maxContextLines !== void 0) cleanOptions.maxContextLines = options.maxContextLines;
return JSON.stringify(cleanOptions);
};
const findReactGrabFile = (projectRoot, framework, nextRouterType) => {
switch (framework) {
case "next": {
const primaryFile = nextRouterType === "app" ? findLayoutFile(projectRoot) : findDocumentFile(projectRoot);
const primarySetupFile = findFileWithReactGrabSetup(nextRouterType === "app" ? getLayoutFileCandidates(projectRoot) : getDocumentFileCandidates(projectRoot));
if (primarySetupFile) return primarySetupFile;
const instrumentationFile = findFileWithReactGrabSetup(getInstrumentationFileCandidates(projectRoot));
if (instrumentationFile) return instrumentationFile;
return primaryFile;
}
case "vite": {
const entryFile = findEntryFile(projectRoot);
const entrySetupFile = findFileWithReactGrabSetup(getEntryFileCandidates(projectRoot));
if (entrySetupFile) return entrySetupFile;
const indexHtml = findFileWithReactGrabSetup(getIndexHtmlCandidates(projectRoot));
if (indexHtml) return indexHtml;
return entryFile;
}
case "tanstack": return findFileWithReactGrabSetup(getTanStackRootFileCandidates(projectRoot)) ?? findTanStackRootFile(projectRoot);
case "webpack": return findFileWithReactGrabSetup(getEntryFileCandidates(projectRoot)) ?? findEntryFile(projectRoot);
default: return null;
}
};
const addOptionsToNextScript = (originalContent, options, filePath) => {
const reactGrabScriptMatch = originalContent.match(/(<Script[\s\S]*?react-grab[\s\S]*?)\s*(\/?>)/i);
if (!reactGrabScriptMatch) return {
success: false,
filePath,
message: "Could not find React Grab Script tag"
};
const scriptTag = reactGrabScriptMatch[0];
const scriptOpening = reactGrabScriptMatch[1];
const scriptClosing = reactGrabScriptMatch[2];
const existingDataOptionsMatch = scriptTag.match(/data-options=\{JSON\.stringify\([^)]+\)\}/);
const dataOptionsAttr = `data-options={JSON.stringify(\n ${formatOptionsForNextjs(options)}\n )}`;
let newScriptTag;
if (existingDataOptionsMatch) newScriptTag = scriptTag.replace(existingDataOptionsMatch[0], dataOptionsAttr);
else newScriptTag = `${scriptOpening}\n ${dataOptionsAttr}\n ${scriptClosing}`;
return {
success: true,
filePath,
message: "Update React Grab options",
originalContent,
newContent: originalContent.replace(scriptTag, newScriptTag)
};
};
const addOptionsToDynamicImport = (originalContent, options, filePath) => {
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*\))?/);
if (!reactGrabImportWithInitMatch) return {
success: false,
filePath,
message: "Could not find React Grab import"
};
const optionsJson = formatOptionsAsJson(options);
const newImport = `${reactGrabImportWithInitMatch[1] ?? ""}import("react-grab").then((m) => m.init(${optionsJson}))`;
return {
success: true,
filePath,
message: "Update React Grab options",
originalContent,
newContent: originalContent.replace(reactGrabImportWithInitMatch[0], newImport)
};
};
const addOptionsToTanStackImport = (originalContent, options, filePath) => {
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*\))/);
if (!reactGrabImportWithInitMatch) return {
success: false,
filePath,
message: "Could not find React Grab import"
};
const optionsJson = formatOptionsAsJson(options);
const newImport = `${reactGrabImportWithInitMatch[1] ?? reactGrabImportWithInitMatch[2] ?? ""}import("react-grab/core").then(({ init }) => init(${optionsJson}))`;
return {
success: true,
filePath,
message: "Update React Grab options",
originalContent,
newContent: originalContent.replace(reactGrabImportWithInitMatch[0], newImport)
};
};
const addOptionsToAnyImport = (originalContent, options, filePath) => {
const dynamicImportResult = addOptionsToDynamicImport(originalContent, options, filePath);
if (dynamicImportResult.success) return dynamicImportResult;
return addOptionsToTanStackImport(originalContent, options, filePath);
};
const previewOptionsTransform = (projectRoot, framework, nextRouterType, options) => {
const filePath = findReactGrabFile(projectRoot, framework, nextRouterType);
if (!filePath) return {
success: false,
filePath: "",
message: "Could not find file containing React Grab configuration"
};
const originalContent = (0, node_fs.readFileSync)(filePath, "utf-8");
if (!hasReactGrabSetupCode(originalContent)) return {
success: false,
filePath,
message: "Could not find React Grab code in the file"
};
switch (framework) {
case "next":
if (isInstrumentationFile(filePath)) return addOptionsToAnyImport(originalContent, options, filePath);
return addOptionsToNextScript(originalContent, options, filePath);
case "vite": return addOptionsToDynamicImport(originalContent, options, filePath);
case "tanstack": return addOptionsToTanStackImport(originalContent, options, filePath);
case "webpack": return addOptionsToDynamicImport(originalContent, options, filePath);
default: return {
success: false,
filePath,
message: `Unknown framework: ${framework}`
};
}
};
const previewCdnTransform = (projectRoot, framework, nextRouterType, targetCdnDomain) => {
const filePath = findReactGrabFile(projectRoot, framework, nextRouterType);
if (!filePath) return {
success: false,
filePath: "",
message: "Could not find React Grab file"
};
const originalContent = (0, node_fs.readFileSync)(filePath, "utf-8");
const newContent = originalContent.replace(/(https?:)?\/\/[^/\s"']+(?=\/(?:@?react-grab))/g, `//${targetCdnDomain}`).replace(/(https?:)?\/\/[^/\s"']*react-grab[^/\s"']*\.com(?=\/script\.js)/g, `//${targetCdnDomain}`);
if (newContent === originalContent) return {
success: true,
filePath,
message: "CDN already set",
noChanges: true
};
return {
success: true,
filePath,
message: "Update CDN",
originalContent,
newContent
};
};
//#endregion
//#region src/utils/install.ts
const installPackages = async (packages, options = {}) => {
if (packages.length === 0) return;
const detectedAgent = options.packageManager ?? await detectPackageManager(options.cwd ?? process.cwd());
const args = [];
if (options.preferOffline) args.push("--prefer-offline");
if (detectedAgent === "pnpm") {
args.push("--prod=false");
if ((0, node_fs.existsSync)((0, node_path.resolve)(options.cwd ?? process.cwd(), "pnpm-workspace.yaml"))) args.push("-w");
}
if (options.additionalArgs) args.push(...options.additionalArgs);
await (0, tinyexec.x)(detectedAgent, [
detectedAgent === "npm" ? "install" : "add",
...options.isDev !== false ? ["-D"] : [],
...args,
...packages
], {
nodeOptions: {
stdio: options.silent ? "ignore" : "inherit",
cwd: options.cwd,
env: {
...process.env,
REACT_GRAB_INIT: "1"
}
},
throwOnError: true
});
};
const getPackagesToInstall = (includeReactGrab = true) => {
return includeReactGrab ? ["react-grab"] : [];
};
//#endregion
Object.defineProperty(exports, "__toESM", {
enumerable: true,
get: function() {
return __toESM;
}
});
Object.defineProperty(exports, "agentLabel", {
enumerable: true,
get: function() {
return agentLabel;
}
});
Object.defineProperty(exports, "applyTransform", {
enumerable: true,
get: function() {
return applyTransform;
}
});
Object.defineProperty(exports, "detectAvailableAgents", {
enumerable: true,
get: function() {
return detectAvailableAgents;
}
});
Object.defineProperty(exports, "detectFramework", {
enumerable: true,
get: function() {
return detectFramework;
}
});
Object.defineProperty(exports, "detectNextRouterType", {
enumerable: true,
get: function() {
return detectNextRouterType;
}
});
Object.defineProperty(exports, "detectPackageManager", {
enumerable: true,
get: function() {
return detectPackageManager;
}
});
Object.defineProperty(exports, "detectProject", {
enumerable: true,
get: function() {
return detectProject;
}
});
Object.defineProperty(exports, "detectReactGrab", {
enumerable: true,
get: function() {
return detectReactGrab;
}
});
Object.defineProperty(exports, "detectReactGrabConfigured", {
enumerable: true,
get: function() {
return detectReactGrabConfigured;
}
});
Object.defineProperty(exports, "detectUnsupportedFramework", {
enumerable: true,
get: function() {
return detectUnsupportedFramework;
}
});
Object.defineProperty(exports, "findReactProjects", {
enumerable: true,
get: function() {
return findReactProjects;
}
});
Object.defineProperty(exports, "getPackagesToInstall", {
enumerable: true,
get: function() {
return getPackagesToInstall;
}
});
Object.defineProperty(exports, "hasFrameworkEntryPoint", {
enumerable: true,
get: function() {
return hasFrameworkEntryPoint;
}
});
Object.defineProperty(exports, "installPackages", {
enumerable: true,
get: function() {
return installPackages;
}
});
Object.defineProperty(exports, "installSkill", {
enumerable: true,
get: function() {
return installSkill;
}
});
Object.defineProperty(exports, "previewCdnTransform", {
enumerable: true,
get: function() {
return previewCdnTransform;
}
});
Object.defineProperty(exports, "previewOptionsTransform", {
enumerable: true,
get: function() {
return previewOptionsTransform;
}
});
Object.defineProperty(exports, "previewTransform", {
enumerable: true,
get: function() {
return previewTransform;
}
});
Object.defineProperty(exports, "removeSkill", {
enumerable: true,
get: function() {
return removeSkill;
}
});