UNPKG

humanbehavior-js

Version:

SDK for HumanBehavior session and event recording

1,152 lines (1,128 loc) 82.8 kB
#!/usr/bin/env node import * as fs from 'fs'; import * as path from 'path'; import * as clack from '@clack/prompts'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; /** * HumanBehavior SDK Auto-Installation Wizard * * This wizard automatically detects the user's framework and modifies their codebase * to integrate the SDK with minimal user intervention. */ class AutoInstallationWizard { constructor(apiKey, projectRoot = process.cwd()) { this.framework = null; this.apiKey = apiKey; this.projectRoot = projectRoot; } /** * Simple version comparison utility */ compareVersions(version1, version2) { const v1Parts = version1.split('.').map(Number); const v2Parts = version2.split('.').map(Number); for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) { const v1 = v1Parts[i] || 0; const v2 = v2Parts[i] || 0; if (v1 > v2) return 1; if (v1 < v2) return -1; } return 0; } isVersionGte(version, target) { return this.compareVersions(version, target) >= 0; } getMajorVersion(version) { return parseInt(version.split('.')[0]) || 0; } /** * Main installation method - detects framework and auto-installs */ install() { return __awaiter(this, void 0, void 0, function* () { try { // Step 1: Detect framework this.framework = yield this.detectFramework(); // Step 2: Install package yield this.installPackage(); // Step 3: Generate and apply code modifications const modifications = yield this.generateModifications(); yield this.applyModifications(modifications); // Step 4: Generate next steps const nextSteps = this.generateNextSteps(); return { success: true, framework: this.framework, modifications, errors: [], nextSteps }; } catch (error) { return { success: false, framework: this.framework || { name: 'unknown', type: 'vanilla' }, modifications: [], errors: [error instanceof Error ? error.message : 'Unknown error'], nextSteps: [] }; } }); } /** * Detect the current framework and project setup */ detectFramework() { return __awaiter(this, void 0, void 0, function* () { const packageJsonPath = path.join(this.projectRoot, 'package.json'); if (!fs.existsSync(packageJsonPath)) { return { name: 'vanilla', type: 'vanilla', projectRoot: this.projectRoot }; } const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); const dependencies = Object.assign(Object.assign({}, packageJson.dependencies), packageJson.devDependencies); // Detect framework with version information let framework = { name: 'vanilla', type: 'vanilla', projectRoot: this.projectRoot, features: {} }; if (dependencies.nuxt) { const nuxtVersion = dependencies.nuxt; const isNuxt3 = this.isVersionGte(nuxtVersion, '3.0.0'); framework = { name: 'nuxt', type: 'nuxt', version: nuxtVersion, majorVersion: this.getMajorVersion(nuxtVersion), hasTypeScript: !!dependencies.typescript, hasRouter: true, projectRoot: this.projectRoot, features: { hasNuxt3: isNuxt3 } }; } else if (dependencies.next) { const nextVersion = dependencies.next; const isNext13 = this.isVersionGte(nextVersion, '13.0.0'); framework = { name: 'nextjs', type: 'nextjs', version: nextVersion, majorVersion: this.getMajorVersion(nextVersion), hasTypeScript: !!dependencies.typescript || !!dependencies['@types/node'], hasRouter: true, projectRoot: this.projectRoot, features: { hasNextAppRouter: isNext13 } }; } else if (dependencies['@remix-run/react'] || dependencies['@remix-run/dev']) { const remixVersion = dependencies['@remix-run/react'] || dependencies['@remix-run/dev']; framework = { name: 'remix', type: 'remix', version: remixVersion, majorVersion: this.getMajorVersion(remixVersion), hasTypeScript: !!dependencies.typescript || !!dependencies['@types/react'], hasRouter: true, projectRoot: this.projectRoot, features: {} }; } else if (dependencies.react) { const reactVersion = dependencies.react; const isReact18 = this.isVersionGte(reactVersion, '18.0.0'); framework = { name: 'react', type: 'react', version: reactVersion, majorVersion: this.getMajorVersion(reactVersion), hasTypeScript: !!dependencies.typescript || !!dependencies['@types/react'], hasRouter: !!dependencies['react-router-dom'] || !!dependencies['react-router'], projectRoot: this.projectRoot, features: { hasReact18: isReact18 } }; } else if (dependencies.vue) { const vueVersion = dependencies.vue; const isVue3 = this.isVersionGte(vueVersion, '3.0.0'); framework = { name: 'vue', type: 'vue', version: vueVersion, majorVersion: this.getMajorVersion(vueVersion), hasTypeScript: !!dependencies.typescript || !!dependencies['@vue/cli-service'], hasRouter: !!dependencies['vue-router'], projectRoot: this.projectRoot, features: { hasVue3: isVue3 } }; } else if (dependencies['@angular/core']) { const angularVersion = dependencies['@angular/core']; const isAngular17 = this.isVersionGte(angularVersion, '17.0.0'); framework = { name: 'angular', type: 'angular', version: angularVersion, majorVersion: this.getMajorVersion(angularVersion), hasTypeScript: true, hasRouter: true, projectRoot: this.projectRoot, features: { hasAngularStandalone: isAngular17 } }; } else if (dependencies.svelte) { const svelteVersion = dependencies.svelte; const isSvelteKit = !!dependencies['@sveltejs/kit']; framework = { name: 'svelte', type: 'svelte', version: svelteVersion, majorVersion: this.getMajorVersion(svelteVersion), hasTypeScript: !!dependencies.typescript || !!dependencies['svelte-check'], hasRouter: !!dependencies['svelte-routing'] || !!dependencies['@sveltejs/kit'], projectRoot: this.projectRoot, features: { hasSvelteKit: isSvelteKit } }; } else if (dependencies.astro) { const astroVersion = dependencies.astro; framework = { name: 'astro', type: 'astro', version: astroVersion, majorVersion: this.getMajorVersion(astroVersion), hasTypeScript: !!dependencies.typescript || !!dependencies['@astrojs/ts-plugin'], hasRouter: true, projectRoot: this.projectRoot, features: {} }; } else if (dependencies.gatsby) { const gatsbyVersion = dependencies.gatsby; framework = { name: 'gatsby', type: 'gatsby', version: gatsbyVersion, majorVersion: this.getMajorVersion(gatsbyVersion), hasTypeScript: !!dependencies.typescript || !!dependencies['@types/react'], hasRouter: true, projectRoot: this.projectRoot, features: {} }; } // Detect bundler if (dependencies.vite) { framework.bundler = 'vite'; } else if (dependencies.webpack) { framework.bundler = 'webpack'; } else if (dependencies.esbuild) { framework.bundler = 'esbuild'; } else if (dependencies.rollup) { framework.bundler = 'rollup'; } // Detect package manager if (fs.existsSync(path.join(this.projectRoot, 'yarn.lock'))) { framework.packageManager = 'yarn'; } else if (fs.existsSync(path.join(this.projectRoot, 'pnpm-lock.yaml'))) { framework.packageManager = 'pnpm'; } else { framework.packageManager = 'npm'; } return framework; }); } /** * Install the SDK package */ installPackage() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d; const { execSync } = yield import('child_process'); // Build base command let command = ((_a = this.framework) === null || _a === void 0 ? void 0 : _a.packageManager) === 'yarn' ? 'yarn add humanbehavior-js' : ((_b = this.framework) === null || _b === void 0 ? void 0 : _b.packageManager) === 'pnpm' ? 'pnpm add humanbehavior-js' : 'npm install humanbehavior-js'; // Add legacy peer deps flag for npm to handle dependency conflicts if (((_c = this.framework) === null || _c === void 0 ? void 0 : _c.packageManager) !== 'yarn' && ((_d = this.framework) === null || _d === void 0 ? void 0 : _d.packageManager) !== 'pnpm') { command += ' --legacy-peer-deps'; } try { execSync(command, { cwd: this.projectRoot, stdio: 'inherit' }); } catch (error) { throw new Error(`Failed to install humanbehavior-js: ${error}`); } }); } /** * Generate code modifications based on framework */ generateModifications() { return __awaiter(this, void 0, void 0, function* () { var _a; const modifications = []; switch ((_a = this.framework) === null || _a === void 0 ? void 0 : _a.type) { case 'react': modifications.push(...yield this.generateReactModifications()); break; case 'nextjs': modifications.push(...yield this.generateNextJSModifications()); break; case 'nuxt': modifications.push(...yield this.generateNuxtModifications()); break; case 'astro': modifications.push(...yield this.generateAstroModifications()); break; case 'gatsby': modifications.push(...yield this.generateGatsbyModifications()); break; case 'remix': modifications.push(...yield this.generateRemixModifications()); break; case 'vue': modifications.push(...yield this.generateVueModifications()); break; case 'angular': modifications.push(...yield this.generateAngularModifications()); break; case 'svelte': modifications.push(...yield this.generateSvelteModifications()); break; default: modifications.push(...yield this.generateVanillaModifications()); } return modifications; }); } /** * Generate React-specific modifications */ generateReactModifications() { return __awaiter(this, void 0, void 0, function* () { const modifications = []; // Find main App component or index file const appFile = this.findReactAppFile(); if (appFile) { const content = fs.readFileSync(appFile, 'utf8'); const modifiedContent = this.injectReactProvider(content, appFile); modifications.push({ filePath: appFile, action: 'modify', content: modifiedContent, description: 'Added HumanBehaviorProvider to React app' }); } // Create or append to environment file modifications.push(this.createEnvironmentModification(this.framework)); return modifications; }); } /** * Generate Next.js-specific modifications */ generateNextJSModifications() { return __awaiter(this, void 0, void 0, function* () { const modifications = []; // Check for App Router const appLayoutFile = path.join(this.projectRoot, 'src', 'app', 'layout.tsx'); const pagesLayoutFile = path.join(this.projectRoot, 'src', 'pages', '_app.tsx'); if (fs.existsSync(appLayoutFile)) { // Create providers.tsx file for App Router modifications.push({ filePath: path.join(this.projectRoot, 'src', 'app', 'providers.tsx'), action: 'create', content: `'use client'; import { HumanBehaviorProvider } from 'humanbehavior-js/react'; export function Providers({ children }: { children: React.ReactNode }) { return ( <HumanBehaviorProvider apiKey={process.env.NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY}> {children} </HumanBehaviorProvider> ); }`, description: 'Created providers.tsx file for Next.js App Router' }); // Modify layout.tsx to use the provider const content = fs.readFileSync(appLayoutFile, 'utf8'); const modifiedContent = this.injectNextJSAppRouter(content); modifications.push({ filePath: appLayoutFile, action: 'modify', content: modifiedContent, description: 'Added Providers wrapper to Next.js App Router layout' }); } else if (fs.existsSync(pagesLayoutFile)) { // Create providers.tsx file for Pages Router modifications.push({ filePath: path.join(this.projectRoot, 'src', 'components', 'providers.tsx'), action: 'create', content: `'use client'; import { HumanBehaviorProvider } from 'humanbehavior-js/react'; export function Providers({ children }: { children: React.ReactNode }) { return ( <HumanBehaviorProvider apiKey={process.env.NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY}> {children} </HumanBehaviorProvider> ); }`, description: 'Created providers.tsx file for Pages Router' }); // Modify _app.tsx to use the provider const content = fs.readFileSync(pagesLayoutFile, 'utf8'); const modifiedContent = this.injectNextJSPagesRouter(content); modifications.push({ filePath: pagesLayoutFile, action: 'modify', content: modifiedContent, description: 'Added Providers wrapper to Next.js Pages Router' }); } // Create or append to environment file modifications.push(this.createEnvironmentModification(this.framework)); return modifications; }); } /** * Generate Astro-specific modifications */ generateAstroModifications() { return __awaiter(this, void 0, void 0, function* () { const modifications = []; // Create Astro component for HumanBehavior const astroComponentPath = path.join(this.projectRoot, 'src', 'components', 'HumanBehavior.astro'); const astroComponentContent = `--- // This component will only run on the client side --- <script> import { HumanBehaviorTracker } from 'humanbehavior-js'; // Get API key from environment variable const apiKey = import.meta.env.PUBLIC_HUMANBEHAVIOR_API_KEY; console.log('HumanBehavior: API key found:', apiKey ? 'Yes' : 'No'); if (apiKey) { try { const tracker = HumanBehaviorTracker.init(apiKey); console.log('HumanBehavior: Tracker initialized successfully'); // Test event to verify tracking is working setTimeout(() => { tracker.customEvent('astro_page_view', { page: window.location.pathname, framework: 'astro' }).then(() => { console.log('HumanBehavior: Test event sent successfully'); }).catch((error) => { console.error('HumanBehavior: Failed to send test event:', error); }); }, 1000); } catch (error) { console.error('HumanBehavior: Failed to initialize tracker:', error); } } else { console.error('HumanBehavior: No API key found'); } </script>`; modifications.push({ filePath: astroComponentPath, action: 'create', content: astroComponentContent, description: 'Created Astro component for HumanBehavior SDK' }); // Find and update layout file const layoutFiles = [ path.join(this.projectRoot, 'src', 'layouts', 'Layout.astro'), path.join(this.projectRoot, 'src', 'layouts', 'layout.astro'), path.join(this.projectRoot, 'src', 'layouts', 'BaseLayout.astro') ]; let layoutFile = null; for (const file of layoutFiles) { if (fs.existsSync(file)) { layoutFile = file; break; } } if (layoutFile) { const content = fs.readFileSync(layoutFile, 'utf8'); const modifiedContent = this.injectAstroLayout(content); modifications.push({ filePath: layoutFile, action: 'modify', content: modifiedContent, description: 'Added HumanBehavior component to Astro layout' }); } // Add environment variable modifications.push(this.createEnvironmentModification(this.framework)); return modifications; }); } /** * Generate Nuxt-specific modifications */ generateNuxtModifications() { return __awaiter(this, void 0, void 0, function* () { const modifications = []; // Create plugin file for Nuxt (in app directory) const pluginFile = path.join(this.projectRoot, 'app', 'plugins', 'humanbehavior.client.ts'); modifications.push({ filePath: pluginFile, action: 'create', content: `import { HumanBehaviorTracker } from 'humanbehavior-js'; export default defineNuxtPlugin(() => { const config = useRuntimeConfig(); // Initialize HumanBehavior SDK (client-side only) if (typeof window !== 'undefined') { const apiKey = config.public.humanBehaviorApiKey; console.log('HumanBehavior: API key:', apiKey ? 'present' : 'missing'); if (apiKey) { try { const tracker = HumanBehaviorTracker.init(apiKey); console.log('HumanBehavior: Tracker initialized successfully'); } catch (error) { console.error('HumanBehavior: Failed to initialize tracker:', error); } } else { console.error('HumanBehavior: No API key found in runtime config'); } } });`, description: 'Created Nuxt plugin for HumanBehavior SDK in app directory' }); // Create environment configuration const nuxtConfigFile = path.join(this.projectRoot, 'nuxt.config.ts'); if (fs.existsSync(nuxtConfigFile)) { const content = fs.readFileSync(nuxtConfigFile, 'utf8'); const modifiedContent = this.injectNuxtConfig(content); modifications.push({ filePath: nuxtConfigFile, action: 'modify', content: modifiedContent, description: 'Added HumanBehavior runtime config to Nuxt config' }); } // Create or append to environment file modifications.push(this.createEnvironmentModification(this.framework)); return modifications; }); } /** * Generate Remix-specific modifications */ generateRemixModifications() { return __awaiter(this, void 0, void 0, function* () { const modifications = []; // Find root.tsx file const rootFile = path.join(this.projectRoot, 'app', 'root.tsx'); if (fs.existsSync(rootFile)) { const content = fs.readFileSync(rootFile, 'utf8'); const modifiedContent = this.injectRemixProvider(content); modifications.push({ filePath: rootFile, action: 'modify', content: modifiedContent, description: 'Added HumanBehaviorProvider to Remix root component' }); } // Create or append to environment file modifications.push(this.createEnvironmentModification(this.framework)); return modifications; }); } /** * Generate Vue-specific modifications */ generateVueModifications() { return __awaiter(this, void 0, void 0, function* () { const modifications = []; // Find main.js or main.ts const mainFile = this.findVueMainFile(); if (mainFile) { const content = fs.readFileSync(mainFile, 'utf8'); const modifiedContent = this.injectVuePlugin(content); modifications.push({ filePath: mainFile, action: 'modify', content: modifiedContent, description: 'Added HumanBehaviorPlugin to Vue app' }); } // Create or append to environment file modifications.push(this.createEnvironmentModification(this.framework)); return modifications; }); } /** * Generate Angular-specific modifications */ generateAngularModifications() { return __awaiter(this, void 0, void 0, function* () { const modifications = []; // Check for modern Angular (standalone components) vs legacy (NgModule) const appModuleFile = path.join(this.projectRoot, 'src', 'app', 'app.module.ts'); const appComponentFile = path.join(this.projectRoot, 'src', 'app', 'app.ts'); const mainFile = path.join(this.projectRoot, 'src', 'main.ts'); const isModernAngular = fs.existsSync(appComponentFile) && !fs.existsSync(appModuleFile); if (isModernAngular) { // Modern Angular 17+ with standalone components if (fs.existsSync(mainFile)) { const content = fs.readFileSync(mainFile, 'utf8'); const modifiedContent = this.injectAngularStandaloneInit(content); modifications.push({ filePath: mainFile, action: 'modify', content: modifiedContent, description: 'Added HumanBehavior initialization to Angular main.ts' }); } } else if (fs.existsSync(appModuleFile)) { // Legacy Angular with NgModule const content = fs.readFileSync(appModuleFile, 'utf8'); const modifiedContent = this.injectAngularModule(content); modifications.push({ filePath: appModuleFile, action: 'modify', content: modifiedContent, description: 'Added HumanBehaviorModule to Angular app' }); } // Handle Angular environment file (legacy structure) const envFile = path.join(this.projectRoot, 'src', 'environments', 'environment.ts'); if (fs.existsSync(envFile)) { const content = fs.readFileSync(envFile, 'utf8'); if (!content.includes('humanBehaviorApiKey')) { const modifiedContent = content.replace(/export const environment = {([\s\S]*?)};/, `export const environment = { $1, humanBehaviorApiKey: process.env['HUMANBEHAVIOR_API_KEY'] || '' };`); modifications.push({ filePath: envFile, action: 'modify', content: modifiedContent, description: 'Added API key to Angular environment' }); } } // Create or append to environment file modifications.push(this.createEnvironmentModification(this.framework)); return modifications; }); } /** * Generate Svelte-specific modifications */ generateSvelteModifications() { return __awaiter(this, void 0, void 0, function* () { const modifications = []; // Check for SvelteKit const svelteConfigFile = path.join(this.projectRoot, 'svelte.config.js'); const isSvelteKit = fs.existsSync(svelteConfigFile); if (isSvelteKit) { // SvelteKit - create layout file const layoutFile = path.join(this.projectRoot, 'src', 'routes', '+layout.svelte'); if (fs.existsSync(layoutFile)) { const content = fs.readFileSync(layoutFile, 'utf8'); const modifiedContent = this.injectSvelteKitLayout(content); modifications.push({ filePath: layoutFile, action: 'modify', content: modifiedContent, description: 'Added HumanBehavior store to SvelteKit layout' }); } } else { // Regular Svelte - modify main file const mainFile = this.findSvelteMainFile(); if (mainFile) { const content = fs.readFileSync(mainFile, 'utf8'); const modifiedContent = this.injectSvelteStore(content); modifications.push({ filePath: mainFile, action: 'modify', content: modifiedContent, description: 'Added HumanBehavior store to Svelte app' }); } } // Create or append to environment file modifications.push(this.createEnvironmentModification(this.framework)); return modifications; }); } /** * Generate vanilla JS/TS modifications */ generateVanillaModifications() { return __awaiter(this, void 0, void 0, function* () { const modifications = []; // Find HTML file to inject script const htmlFile = this.findHTMLFile(); if (htmlFile) { const content = fs.readFileSync(htmlFile, 'utf8'); const modifiedContent = this.injectVanillaScript(content); modifications.push({ filePath: htmlFile, action: 'modify', content: modifiedContent, description: 'Added HumanBehavior CDN script to HTML file' }); } // Create or append to environment file modifications.push(this.createEnvironmentModification(this.framework)); return modifications; }); } /** * Generate Gatsby-specific modifications */ generateGatsbyModifications() { return __awaiter(this, void 0, void 0, function* () { const modifications = []; // Modify or create gatsby-browser.js for Gatsby const gatsbyBrowserFile = path.join(this.projectRoot, 'gatsby-browser.js'); if (fs.existsSync(gatsbyBrowserFile)) { const content = fs.readFileSync(gatsbyBrowserFile, 'utf8'); const modifiedContent = this.injectGatsbyBrowser(content); modifications.push({ filePath: gatsbyBrowserFile, action: 'modify', content: modifiedContent, description: 'Added HumanBehavior initialization to Gatsby browser' }); } else { // Create gatsby-browser.js if it doesn't exist modifications.push({ filePath: gatsbyBrowserFile, action: 'create', content: `import { HumanBehaviorTracker } from 'humanbehavior-js'; export const onClientEntry = () => { console.log('Gatsby browser entry point loaded'); const apiKey = process.env.GATSBY_HUMANBEHAVIOR_API_KEY; console.log('API Key found:', apiKey ? 'Yes' : 'No'); if (apiKey) { const tracker = HumanBehaviorTracker.init(apiKey); console.log('HumanBehavior SDK initialized for Gatsby'); } else { console.log('No API key found in environment variables'); } };`, description: 'Created gatsby-browser.js with HumanBehavior initialization' }); } // Create or append to environment file modifications.push(this.createEnvironmentModification(this.framework)); return modifications; }); } /** * Apply modifications to the codebase */ applyModifications(modifications) { return __awaiter(this, void 0, void 0, function* () { for (const modification of modifications) { try { const dir = path.dirname(modification.filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } switch (modification.action) { case 'create': fs.writeFileSync(modification.filePath, modification.content); break; case 'modify': fs.writeFileSync(modification.filePath, modification.content); break; case 'append': fs.appendFileSync(modification.filePath, '\n' + modification.content); break; } } catch (error) { throw new Error(`Failed to apply modification to ${modification.filePath}: ${error}`); } } }); } /** * Generate next steps for the user */ generateNextSteps() { var _a, _b; const steps = [ '✅ SDK installed and configured automatically!', '🚀 Your app is now tracking user behavior', '📊 View sessions in your HumanBehavior dashboard', '🔧 Customize tracking in your code as needed' ]; if (((_a = this.framework) === null || _a === void 0 ? void 0 : _a.type) === 'react' || ((_b = this.framework) === null || _b === void 0 ? void 0 : _b.type) === 'nextjs') { steps.push('💡 Use the useHumanBehavior() hook to track custom events'); } return steps; } // Helper methods for file detection and content injection findReactAppFile() { const possibleFiles = [ 'src/App.jsx', 'src/App.js', 'src/App.tsx', 'src/App.ts', 'src/index.js', 'src/index.tsx', 'src/main.js', 'src/main.tsx' ]; for (const file of possibleFiles) { const fullPath = path.join(this.projectRoot, file); if (fs.existsSync(fullPath)) { return fullPath; } } return null; } findVueMainFile() { const possibleFiles = [ 'src/main.js', 'src/main.ts', 'src/main.jsx', 'src/main.tsx' ]; for (const file of possibleFiles) { const fullPath = path.join(this.projectRoot, file); if (fs.existsSync(fullPath)) { return fullPath; } } return null; } findSvelteMainFile() { const possibleFiles = [ 'src/main.js', 'src/main.ts', 'src/main.svelte' ]; for (const file of possibleFiles) { const fullPath = path.join(this.projectRoot, file); if (fs.existsSync(fullPath)) { return fullPath; } } return null; } findHTMLFile() { const possibleFiles = ['index.html', 'public/index.html', 'dist/index.html']; for (const file of possibleFiles) { const fullPath = path.join(this.projectRoot, file); if (fs.existsSync(fullPath)) { return fullPath; } } return null; } injectReactProvider(content, filePath) { var _a, _b, _c; filePath.endsWith('.tsx') || filePath.endsWith('.ts'); // Check if already has HumanBehaviorProvider if (content.includes('HumanBehaviorProvider')) { return content; } // Determine the correct environment variable syntax based on bundler const isVite = ((_a = this.framework) === null || _a === void 0 ? void 0 : _a.bundler) === 'vite'; const envVar = isVite ? 'import.meta.env.VITE_HUMANBEHAVIOR_API_KEY!' : 'process.env.HUMANBEHAVIOR_API_KEY!'; const importStatement = `import { HumanBehaviorProvider } from 'humanbehavior-js/react';`; // Enhanced parsing for React 18+ features const hasReact18 = (_c = (_b = this.framework) === null || _b === void 0 ? void 0 : _b.features) === null || _c === void 0 ? void 0 : _c.hasReact18; // Handle different React patterns if (content.includes('function App()') || content.includes('const App =')) { // Add import statement let modifiedContent = content.replace(/(import.*?from.*?['"]react['"];?)/, `$1\n${importStatement}`); // If no React import found, add it at the top if (!modifiedContent.includes(importStatement)) { modifiedContent = `${importStatement}\n\n${modifiedContent}`; } // Wrap the App component return with HumanBehaviorProvider modifiedContent = modifiedContent.replace(/(return\s*\([\s\S]*?\)\s*;)/, `return ( <HumanBehaviorProvider apiKey={${envVar}}> $1 </HumanBehaviorProvider> );`); return modifiedContent; } // Handle React 18+ createRoot pattern if (hasReact18 && content.includes('createRoot')) { let modifiedContent = content.replace(/(import.*?from.*?['"]react['"];?)/, `$1\n${importStatement}`); if (!modifiedContent.includes(importStatement)) { modifiedContent = `${importStatement}\n\n${modifiedContent}`; } // Wrap the root render with HumanBehaviorProvider modifiedContent = modifiedContent.replace(/(root\.render\s*\([\s\S]*?\)\s*;)/, `root.render( <HumanBehaviorProvider apiKey={${envVar}}> $1 </HumanBehaviorProvider> );`); return modifiedContent; } // Fallback: simple injection return `${importStatement}\n\n${content}`; } injectNextJSAppRouter(content) { if (content.includes('Providers')) { return content; } const importStatement = `import { Providers } from './providers';`; // First, add the import statement let modifiedContent = content.replace(/export default function RootLayout/, `${importStatement}\n\nexport default function RootLayout`); // Then wrap the body content with Providers // Use a more specific approach to handle the body content modifiedContent = modifiedContent.replace(/<body([^>]*)>([\s\S]*?)<\/body>/, (match, bodyAttrs, bodyContent) => { // Trim whitespace and newlines from bodyContent const trimmedContent = bodyContent.trim(); return `<body${bodyAttrs}> <Providers> ${trimmedContent} </Providers> </body>`; }); return modifiedContent; } injectNextJSPagesRouter(content) { if (content.includes('Providers')) { return content; } const importStatement = `import { Providers } from '../components/providers';`; return content.replace(/function MyApp/, `${importStatement}\n\nfunction MyApp`).replace(/return \(([\s\S]*?)\);/, `return ( <Providers> $1 </Providers> );`); } injectRemixProvider(content) { if (content.includes('HumanBehaviorProvider')) { return content; } const importStatement = `import { HumanBehaviorProvider, createHumanBehaviorLoader } from 'humanbehavior-js/remix';`; const useLoaderDataImport = `import { useLoaderData } from "@remix-run/react";`; // Add imports more robustly let modifiedContent = content; // Add HumanBehaviorProvider import - find the last import and add after it if (!content.includes('HumanBehaviorProvider')) { const lastImportIndex = modifiedContent.lastIndexOf('import'); if (lastImportIndex !== -1) { const nextLineIndex = modifiedContent.indexOf('\n', lastImportIndex); if (nextLineIndex !== -1) { modifiedContent = modifiedContent.slice(0, nextLineIndex + 1) + importStatement + '\n' + modifiedContent.slice(nextLineIndex + 1); } else { modifiedContent = modifiedContent + '\n' + importStatement; } } else { modifiedContent = importStatement + '\n' + modifiedContent; } } // Add useLoaderData import - find the last import and add after it if (!content.includes('useLoaderData')) { const lastImportIndex = modifiedContent.lastIndexOf('import'); if (lastImportIndex !== -1) { const nextLineIndex = modifiedContent.indexOf('\n', lastImportIndex); if (nextLineIndex !== -1) { modifiedContent = modifiedContent.slice(0, nextLineIndex + 1) + useLoaderDataImport + '\n' + modifiedContent.slice(nextLineIndex + 1); } else { modifiedContent = modifiedContent + '\n' + useLoaderDataImport; } } else { modifiedContent = useLoaderDataImport + '\n' + modifiedContent; } } // Add loader function before the App component if (!content.includes('export const loader')) { modifiedContent = modifiedContent.replace(/export default function App\(\)/, `export const loader = createHumanBehaviorLoader(); export default function App()`); } // Wrap the App component content with HumanBehaviorProvider modifiedContent = modifiedContent.replace(/export default function App\(\) \{[\s\S]*?return \(([\s\S]*?)\);[\s\S]*?\}/, `export default function App() { const data = useLoaderData<typeof loader>(); return ( <HumanBehaviorProvider apiKey={data.ENV.HUMANBEHAVIOR_API_KEY}> $1 </HumanBehaviorProvider> ); }`); return modifiedContent; } injectVuePlugin(content) { var _a, _b; if (content.includes('HumanBehaviorPlugin')) { return content; } const importStatement = `import { HumanBehaviorPlugin } from 'humanbehavior-js/vue';`; // Enhanced Vue 3 support with version detection const hasVue3 = (_b = (_a = this.framework) === null || _a === void 0 ? void 0 : _a.features) === null || _b === void 0 ? void 0 : _b.hasVue3; if (hasVue3) { // Vue 3 with Composition API const pluginUsage = `app.use(HumanBehaviorPlugin, { apiKey: import.meta.env.VITE_HUMANBEHAVIOR_API_KEY });`; let modifiedContent = content; // Add import statement if (!content.includes(importStatement)) { modifiedContent = content.replace(/(import.*?from.*?['"]vue['"];?)/, `$1\n${importStatement}`); // If no Vue import found, add it at the top if (!modifiedContent.includes(importStatement)) { modifiedContent = `${importStatement}\n\n${modifiedContent}`; } } // Handle createApp pattern if (content.includes('createApp')) { modifiedContent = modifiedContent.replace(/(app\.mount\(.*?\))/, `${pluginUsage}\n\n$1`); } return modifiedContent; } else { // Vue 2 with Options API const pluginUsage = `Vue.use(HumanBehaviorPlugin, { apiKey: process.env.VUE_APP_HUMANBEHAVIOR_API_KEY });`; let modifiedContent = content; // Add import statement if (!content.includes(importStatement)) { modifiedContent = content.replace(/(import.*?from.*?['"]vue['"];?)/, `$1\n${importStatement}`); if (!modifiedContent.includes(importStatement)) { modifiedContent = `${importStatement}\n\n${modifiedContent}`; } } // Handle new Vue pattern if (content.includes('new Vue')) { modifiedContent = modifiedContent.replace(/(new Vue\(.*?\))/, `${pluginUsage}\n\n$1`); } return modifiedContent; } } injectAngularModule(content) { if (content.includes('HumanBehaviorModule')) { return content; } const importStatement = `import { HumanBehaviorModule } from 'humanbehavior-js/angular';`; const environmentImport = `import { environment } from '../environments/environment';`; // Add environment import if not present let modifiedContent = content; if (!content.includes('environment')) { modifiedContent = content.replace(/import.*from.*['"]@angular/, `${environmentImport}\n$&`); } return modifiedContent.replace(/imports:\s*\[([\s\S]*?)\]/, `imports: [ $1, HumanBehaviorModule.forRoot({ apiKey: environment.humanBehaviorApiKey }) ]`).replace(/import.*from.*['"]@angular/, `$&\n${importStatement}`); } injectAngularStandaloneInit(content) { if (content.includes('initializeHumanBehavior')) { return content; } const importStatement = `import { initializeHumanBehavior } from 'humanbehavior-js/angular';`; // Add import at the top let modifiedContent = content.replace(/import.*from.*['"]@angular/, `${importStatement}\n$&`); // Add initialization after bootstrapApplication modifiedContent = modifiedContent.replace(/(bootstrapApplication\([^}]+\}?\)(?:\s*\.catch[^;]+;)?)/, `$1 // Initialize HumanBehavior SDK (client-side only) if (typeof window !== 'undefined') { const tracker = initializeHumanBehavior( '${this.apiKey}' ); }`); return modifiedContent; } injectSvelteStore(content) { if (content.includes('humanBehaviorStore')) { return content; } const importStatement = `import { humanBehaviorStore } from 'humanbehavior-js/svelte';`; const initCode = `humanBehaviorStore.init(process.env.PUBLIC_HUMANBEHAVIOR_API_KEY || '');`; return `${importStatement}\n${initCode}\n\n${content}`; } injectSvelteKitLayout(content) { if (content.includes('humanBehaviorStore')) { return content; } const importStatement = `import { humanBehaviorStore } from 'humanbehavior-js/svelte';`; const envImport = `import { PUBLIC_HUMANBEHAVIOR_API_KEY } from '$env/static/public';`; const initCode = `humanBehaviorStore.init(PUBLIC_HUMANBEHAVIOR_API_KEY || '');`; // Add to script section - handle different script tag patterns if (content.includes('<script lang="ts">')) { return content.replace(/<script lang="ts">/, `<script lang="ts">\n\t${envImport}\n\t${importStatement}\n\t${initCode}`); } else if (content.includes('<script>')) { return content.replace(/<script>/, `<script>\n\t${envImport}\n\t${importStatement}\n\t${initCode}`); } else if (content.includes('<script context="module">')) { return content.replace(/<script\s+context="module">/, `<script context="module">\n\t${envImport}\n\t${importStatement}\n\t${initCode}`); } else { // If no script tag found, add one at the beginning return content.replace(/<svelte:head>/, `<script lang="ts">\n\t${envImport}\n\t${importStatement}\n\t${initCode}\n</script>\n\n<svelte:head>`); } } injectVanillaScript(content) { if (content.includes('humanbehavior-js')) { return content; } const cdnScript = `<script src="https://unpkg.com/humanbehavior-js@latest/dist/index.min.js"></script>`; const initScript = `<script> // Initialize HumanBehavior SDK // Note: For vanilla HTML, the API key must be hardcoded since env vars aren't available const tracker = HumanBehaviorTracker.init('${this.apiKey}'); </script>`; return content.replace(/<\/head>/, ` ${cdnScript}\n ${initScript}\n</head>`); } /** * Inject Astro layout with HumanBehavior component */