humanbehavior-js 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/WIZARD_USAGE_GUIDE.md +381 -0
- package/dist/cjs/angular/index.cjs +53 -0
- package/dist/cjs/angular/index.cjs.map +1 -0
- package/dist/cjs/{index.js → index.cjs} +5 -4
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/install-wizard.cjs +1157 -0
- package/dist/cjs/install-wizard.cjs.map +1 -0
- package/dist/cjs/react/index.cjs +14387 -0
- package/dist/cjs/react/index.cjs.map +1 -0
- package/dist/cjs/remix/index.cjs +57 -0
- package/dist/cjs/remix/index.cjs.map +1 -0
- package/dist/cjs/svelte/index.cjs +13 -0
- package/dist/cjs/svelte/index.cjs.map +1 -0
- package/dist/cjs/vue/index.cjs +16 -0
- package/dist/cjs/vue/index.cjs.map +1 -0
- package/dist/cli/auto-install.js +1172 -0
- package/dist/cli/auto-install.js.map +1 -0
- package/dist/esm/angular/index.js +49 -0
- package/dist/esm/angular/index.js.map +1 -0
- package/dist/esm/index.js +5 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/install-wizard.js +1134 -0
- package/dist/esm/install-wizard.js.map +1 -0
- package/dist/esm/react/index.js +14113 -70
- package/dist/esm/react/index.js.map +1 -1
- package/dist/esm/remix/index.js +47 -0
- package/dist/esm/remix/index.js.map +1 -0
- package/dist/esm/svelte/index.js +11 -0
- package/dist/esm/svelte/index.js.map +1 -0
- package/dist/esm/vue/index.js +14 -0
- package/dist/esm/vue/index.js.map +1 -0
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/types/angular/index.d.ts +240 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/install-wizard.d.ts +126 -0
- package/dist/types/react/index.d.ts +212 -3
- package/dist/types/remix/index.d.ts +10 -0
- package/dist/types/svelte/index.d.ts +216 -0
- package/dist/types/vue/index.d.ts +10 -0
- package/package.json +40 -7
- package/readme.md +70 -1
- package/rollup.config.js +263 -13
- package/src/angular/index.ts +54 -0
- package/src/cli/auto-install.ts +227 -0
- package/src/index.ts +5 -2
- package/src/install-wizard.ts +1304 -0
- package/src/react/AutoInstallWizard.tsx +557 -0
- package/src/react/browser.ts +8 -0
- package/src/react/index.tsx +2 -4
- package/src/remix/index.ts +16 -0
- package/src/svelte/index.ts +8 -0
- package/src/vue/index.ts +18 -0
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/react/index.js +0 -346
- package/dist/cjs/react/index.js.map +0 -1
|
@@ -0,0 +1,1304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HumanBehavior SDK Auto-Installation Wizard
|
|
3
|
+
*
|
|
4
|
+
* This wizard automatically detects the user's framework and modifies their codebase
|
|
5
|
+
* to integrate the SDK with minimal user intervention.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
|
|
11
|
+
export interface FrameworkInfo {
|
|
12
|
+
name: string;
|
|
13
|
+
type: 'react' | 'vue' | 'angular' | 'svelte' | 'nextjs' | 'nuxt' | 'remix' | 'vanilla' | 'node';
|
|
14
|
+
bundler?: 'vite' | 'webpack' | 'esbuild' | 'rollup';
|
|
15
|
+
packageManager?: 'npm' | 'yarn' | 'pnpm';
|
|
16
|
+
hasTypeScript?: boolean;
|
|
17
|
+
hasRouter?: boolean;
|
|
18
|
+
projectRoot?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CodeModification {
|
|
22
|
+
filePath: string;
|
|
23
|
+
action: 'create' | 'modify' | 'append';
|
|
24
|
+
content: string;
|
|
25
|
+
description: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface InstallationResult {
|
|
29
|
+
success: boolean;
|
|
30
|
+
framework: FrameworkInfo;
|
|
31
|
+
modifications: CodeModification[];
|
|
32
|
+
errors: string[];
|
|
33
|
+
nextSteps: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class AutoInstallationWizard {
|
|
37
|
+
private apiKey: string;
|
|
38
|
+
private projectRoot: string;
|
|
39
|
+
private framework: FrameworkInfo | null = null;
|
|
40
|
+
|
|
41
|
+
constructor(apiKey: string, projectRoot: string = process.cwd()) {
|
|
42
|
+
this.apiKey = apiKey;
|
|
43
|
+
this.projectRoot = projectRoot;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Main installation method - detects framework and auto-installs
|
|
48
|
+
*/
|
|
49
|
+
async install(): Promise<InstallationResult> {
|
|
50
|
+
try {
|
|
51
|
+
// Step 1: Detect framework
|
|
52
|
+
this.framework = await this.detectFramework();
|
|
53
|
+
|
|
54
|
+
// Step 2: Install package
|
|
55
|
+
await this.installPackage();
|
|
56
|
+
|
|
57
|
+
// Step 3: Generate and apply code modifications
|
|
58
|
+
const modifications = await this.generateModifications();
|
|
59
|
+
await this.applyModifications(modifications);
|
|
60
|
+
|
|
61
|
+
// Step 4: Generate next steps
|
|
62
|
+
const nextSteps = this.generateNextSteps();
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
success: true,
|
|
66
|
+
framework: this.framework,
|
|
67
|
+
modifications,
|
|
68
|
+
errors: [],
|
|
69
|
+
nextSteps
|
|
70
|
+
};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
framework: this.framework || { name: 'unknown', type: 'vanilla' },
|
|
75
|
+
modifications: [],
|
|
76
|
+
errors: [error instanceof Error ? error.message : 'Unknown error'],
|
|
77
|
+
nextSteps: []
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Detect the current framework and project setup
|
|
84
|
+
*/
|
|
85
|
+
private async detectFramework(): Promise<FrameworkInfo> {
|
|
86
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
87
|
+
|
|
88
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
89
|
+
return {
|
|
90
|
+
name: 'vanilla',
|
|
91
|
+
type: 'vanilla',
|
|
92
|
+
projectRoot: this.projectRoot
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
97
|
+
const dependencies = {
|
|
98
|
+
...packageJson.dependencies,
|
|
99
|
+
...packageJson.devDependencies
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Detect framework
|
|
103
|
+
let framework: FrameworkInfo = {
|
|
104
|
+
name: 'vanilla',
|
|
105
|
+
type: 'vanilla',
|
|
106
|
+
projectRoot: this.projectRoot
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (dependencies.nuxt) {
|
|
110
|
+
framework = {
|
|
111
|
+
name: 'nuxt',
|
|
112
|
+
type: 'nuxt',
|
|
113
|
+
hasTypeScript: !!dependencies.typescript,
|
|
114
|
+
hasRouter: true,
|
|
115
|
+
projectRoot: this.projectRoot
|
|
116
|
+
};
|
|
117
|
+
} else if (dependencies.next) {
|
|
118
|
+
framework = {
|
|
119
|
+
name: 'nextjs',
|
|
120
|
+
type: 'nextjs',
|
|
121
|
+
hasTypeScript: !!dependencies.typescript || !!dependencies['@types/node'],
|
|
122
|
+
hasRouter: true,
|
|
123
|
+
projectRoot: this.projectRoot
|
|
124
|
+
};
|
|
125
|
+
} else if (dependencies['@remix-run/react'] || dependencies['@remix-run/dev']) {
|
|
126
|
+
framework = {
|
|
127
|
+
name: 'remix',
|
|
128
|
+
type: 'remix',
|
|
129
|
+
hasTypeScript: !!dependencies.typescript || !!dependencies['@types/react'],
|
|
130
|
+
hasRouter: true,
|
|
131
|
+
projectRoot: this.projectRoot
|
|
132
|
+
};
|
|
133
|
+
} else if (dependencies.react) {
|
|
134
|
+
framework = {
|
|
135
|
+
name: 'react',
|
|
136
|
+
type: 'react',
|
|
137
|
+
hasTypeScript: !!dependencies.typescript || !!dependencies['@types/react'],
|
|
138
|
+
hasRouter: !!dependencies['react-router-dom'] || !!dependencies['react-router'],
|
|
139
|
+
projectRoot: this.projectRoot
|
|
140
|
+
};
|
|
141
|
+
} else if (dependencies.vue) {
|
|
142
|
+
framework = {
|
|
143
|
+
name: 'vue',
|
|
144
|
+
type: 'vue',
|
|
145
|
+
hasTypeScript: !!dependencies.typescript || !!dependencies['@vue/cli-service'],
|
|
146
|
+
hasRouter: !!dependencies['vue-router'],
|
|
147
|
+
projectRoot: this.projectRoot
|
|
148
|
+
};
|
|
149
|
+
} else if (dependencies['@angular/core']) {
|
|
150
|
+
framework = {
|
|
151
|
+
name: 'angular',
|
|
152
|
+
type: 'angular',
|
|
153
|
+
hasTypeScript: true,
|
|
154
|
+
hasRouter: true,
|
|
155
|
+
projectRoot: this.projectRoot
|
|
156
|
+
};
|
|
157
|
+
} else if (dependencies.svelte) {
|
|
158
|
+
framework = {
|
|
159
|
+
name: 'svelte',
|
|
160
|
+
type: 'svelte',
|
|
161
|
+
hasTypeScript: !!dependencies.typescript || !!dependencies['svelte-check'],
|
|
162
|
+
hasRouter: !!dependencies['svelte-routing'] || !!dependencies['@sveltejs/kit'],
|
|
163
|
+
projectRoot: this.projectRoot
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Detect bundler
|
|
168
|
+
if (dependencies.vite) {
|
|
169
|
+
framework.bundler = 'vite';
|
|
170
|
+
} else if (dependencies.webpack) {
|
|
171
|
+
framework.bundler = 'webpack';
|
|
172
|
+
} else if (dependencies.esbuild) {
|
|
173
|
+
framework.bundler = 'esbuild';
|
|
174
|
+
} else if (dependencies.rollup) {
|
|
175
|
+
framework.bundler = 'rollup';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Detect package manager
|
|
179
|
+
if (fs.existsSync(path.join(this.projectRoot, 'yarn.lock'))) {
|
|
180
|
+
framework.packageManager = 'yarn';
|
|
181
|
+
} else if (fs.existsSync(path.join(this.projectRoot, 'pnpm-lock.yaml'))) {
|
|
182
|
+
framework.packageManager = 'pnpm';
|
|
183
|
+
} else {
|
|
184
|
+
framework.packageManager = 'npm';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return framework;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Install the SDK package
|
|
192
|
+
*/
|
|
193
|
+
private async installPackage(): Promise<void> {
|
|
194
|
+
const { execSync } = await import('child_process');
|
|
195
|
+
const command = this.framework?.packageManager === 'yarn'
|
|
196
|
+
? 'yarn add humanbehavior-js'
|
|
197
|
+
: this.framework?.packageManager === 'pnpm'
|
|
198
|
+
? 'pnpm add humanbehavior-js'
|
|
199
|
+
: 'npm install humanbehavior-js';
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
execSync(command, { cwd: this.projectRoot, stdio: 'inherit' });
|
|
203
|
+
} catch (error) {
|
|
204
|
+
throw new Error(`Failed to install humanbehavior-js: ${error}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Generate code modifications based on framework
|
|
210
|
+
*/
|
|
211
|
+
private async generateModifications(): Promise<CodeModification[]> {
|
|
212
|
+
const modifications: CodeModification[] = [];
|
|
213
|
+
|
|
214
|
+
switch (this.framework?.type) {
|
|
215
|
+
case 'react':
|
|
216
|
+
modifications.push(...await this.generateReactModifications());
|
|
217
|
+
break;
|
|
218
|
+
case 'nextjs':
|
|
219
|
+
modifications.push(...await this.generateNextJSModifications());
|
|
220
|
+
break;
|
|
221
|
+
case 'nuxt':
|
|
222
|
+
modifications.push(...await this.generateNuxtModifications());
|
|
223
|
+
break;
|
|
224
|
+
case 'remix':
|
|
225
|
+
modifications.push(...await this.generateRemixModifications());
|
|
226
|
+
break;
|
|
227
|
+
case 'vue':
|
|
228
|
+
modifications.push(...await this.generateVueModifications());
|
|
229
|
+
break;
|
|
230
|
+
case 'angular':
|
|
231
|
+
modifications.push(...await this.generateAngularModifications());
|
|
232
|
+
break;
|
|
233
|
+
case 'svelte':
|
|
234
|
+
modifications.push(...await this.generateSvelteModifications());
|
|
235
|
+
break;
|
|
236
|
+
default:
|
|
237
|
+
modifications.push(...await this.generateVanillaModifications());
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return modifications;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Generate React-specific modifications
|
|
245
|
+
*/
|
|
246
|
+
private async generateReactModifications(): Promise<CodeModification[]> {
|
|
247
|
+
const modifications: CodeModification[] = [];
|
|
248
|
+
|
|
249
|
+
// Find main App component or index file
|
|
250
|
+
const appFile = this.findReactAppFile();
|
|
251
|
+
if (appFile) {
|
|
252
|
+
const content = fs.readFileSync(appFile, 'utf8');
|
|
253
|
+
const modifiedContent = this.injectReactProvider(content, appFile);
|
|
254
|
+
|
|
255
|
+
modifications.push({
|
|
256
|
+
filePath: appFile,
|
|
257
|
+
action: 'modify',
|
|
258
|
+
content: modifiedContent,
|
|
259
|
+
description: 'Added HumanBehaviorProvider to React app'
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Create or append to environment file
|
|
264
|
+
modifications.push(this.createEnvironmentModification(this.framework!));
|
|
265
|
+
|
|
266
|
+
return modifications;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Generate Next.js-specific modifications
|
|
271
|
+
*/
|
|
272
|
+
private async generateNextJSModifications(): Promise<CodeModification[]> {
|
|
273
|
+
const modifications: CodeModification[] = [];
|
|
274
|
+
|
|
275
|
+
// Check for App Router
|
|
276
|
+
const appLayoutFile = path.join(this.projectRoot, 'src', 'app', 'layout.tsx');
|
|
277
|
+
const pagesLayoutFile = path.join(this.projectRoot, 'src', 'pages', '_app.tsx');
|
|
278
|
+
|
|
279
|
+
if (fs.existsSync(appLayoutFile)) {
|
|
280
|
+
// Create providers.tsx file for App Router
|
|
281
|
+
modifications.push({
|
|
282
|
+
filePath: path.join(this.projectRoot, 'src', 'app', 'providers.tsx'),
|
|
283
|
+
action: 'create',
|
|
284
|
+
content: `'use client';
|
|
285
|
+
|
|
286
|
+
import { HumanBehaviorProvider } from 'humanbehavior-js/react';
|
|
287
|
+
|
|
288
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
289
|
+
return (
|
|
290
|
+
<HumanBehaviorProvider apiKey={process.env.NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY}>
|
|
291
|
+
{children}
|
|
292
|
+
</HumanBehaviorProvider>
|
|
293
|
+
);
|
|
294
|
+
}`,
|
|
295
|
+
description: 'Created providers.tsx file for Next.js App Router'
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Modify layout.tsx to use the provider
|
|
299
|
+
const content = fs.readFileSync(appLayoutFile, 'utf8');
|
|
300
|
+
const modifiedContent = this.injectNextJSAppRouter(content);
|
|
301
|
+
|
|
302
|
+
modifications.push({
|
|
303
|
+
filePath: appLayoutFile,
|
|
304
|
+
action: 'modify',
|
|
305
|
+
content: modifiedContent,
|
|
306
|
+
description: 'Added Providers wrapper to Next.js App Router layout'
|
|
307
|
+
});
|
|
308
|
+
} else if (fs.existsSync(pagesLayoutFile)) {
|
|
309
|
+
// Create providers.tsx file for Pages Router
|
|
310
|
+
modifications.push({
|
|
311
|
+
filePath: path.join(this.projectRoot, 'src', 'components', 'providers.tsx'),
|
|
312
|
+
action: 'create',
|
|
313
|
+
content: `'use client';
|
|
314
|
+
|
|
315
|
+
import { HumanBehaviorProvider } from 'humanbehavior-js/react';
|
|
316
|
+
|
|
317
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
318
|
+
return (
|
|
319
|
+
<HumanBehaviorProvider apiKey={process.env.NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY}>
|
|
320
|
+
{children}
|
|
321
|
+
</HumanBehaviorProvider>
|
|
322
|
+
);
|
|
323
|
+
}`,
|
|
324
|
+
description: 'Created providers.tsx file for Pages Router'
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Modify _app.tsx to use the provider
|
|
328
|
+
const content = fs.readFileSync(pagesLayoutFile, 'utf8');
|
|
329
|
+
const modifiedContent = this.injectNextJSPagesRouter(content);
|
|
330
|
+
|
|
331
|
+
modifications.push({
|
|
332
|
+
filePath: pagesLayoutFile,
|
|
333
|
+
action: 'modify',
|
|
334
|
+
content: modifiedContent,
|
|
335
|
+
description: 'Added Providers wrapper to Next.js Pages Router'
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Create or append to environment file
|
|
340
|
+
modifications.push(this.createEnvironmentModification(this.framework!));
|
|
341
|
+
|
|
342
|
+
return modifications;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Generate Nuxt-specific modifications
|
|
347
|
+
*/
|
|
348
|
+
private async generateNuxtModifications(): Promise<CodeModification[]> {
|
|
349
|
+
const modifications: CodeModification[] = [];
|
|
350
|
+
|
|
351
|
+
// Create plugin file for Nuxt (in app directory)
|
|
352
|
+
const pluginFile = path.join(this.projectRoot, 'app', 'plugins', 'humanbehavior.client.ts');
|
|
353
|
+
modifications.push({
|
|
354
|
+
filePath: pluginFile,
|
|
355
|
+
action: 'create',
|
|
356
|
+
content: `import { HumanBehaviorTracker } from 'humanbehavior-js';
|
|
357
|
+
|
|
358
|
+
export default defineNuxtPlugin(() => {
|
|
359
|
+
const config = useRuntimeConfig();
|
|
360
|
+
|
|
361
|
+
// Initialize HumanBehavior SDK (client-side only)
|
|
362
|
+
if (typeof window !== 'undefined') {
|
|
363
|
+
const apiKey = config.public.humanBehaviorApiKey;
|
|
364
|
+
console.log('HumanBehavior: API key:', apiKey ? 'present' : 'missing');
|
|
365
|
+
|
|
366
|
+
if (apiKey) {
|
|
367
|
+
try {
|
|
368
|
+
const tracker = HumanBehaviorTracker.init(apiKey);
|
|
369
|
+
console.log('HumanBehavior: Tracker initialized successfully');
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error('HumanBehavior: Failed to initialize tracker:', error);
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
console.error('HumanBehavior: No API key found in runtime config');
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
});`,
|
|
378
|
+
description: 'Created Nuxt plugin for HumanBehavior SDK in app directory'
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Create environment configuration
|
|
382
|
+
const nuxtConfigFile = path.join(this.projectRoot, 'nuxt.config.ts');
|
|
383
|
+
if (fs.existsSync(nuxtConfigFile)) {
|
|
384
|
+
const content = fs.readFileSync(nuxtConfigFile, 'utf8');
|
|
385
|
+
const modifiedContent = this.injectNuxtConfig(content);
|
|
386
|
+
|
|
387
|
+
modifications.push({
|
|
388
|
+
filePath: nuxtConfigFile,
|
|
389
|
+
action: 'modify',
|
|
390
|
+
content: modifiedContent,
|
|
391
|
+
description: 'Added HumanBehavior runtime config to Nuxt config'
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Create or append to environment file
|
|
396
|
+
modifications.push(this.createEnvironmentModification(this.framework!));
|
|
397
|
+
|
|
398
|
+
return modifications;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Generate Remix-specific modifications
|
|
403
|
+
*/
|
|
404
|
+
private async generateRemixModifications(): Promise<CodeModification[]> {
|
|
405
|
+
const modifications: CodeModification[] = [];
|
|
406
|
+
|
|
407
|
+
// Find root.tsx file
|
|
408
|
+
const rootFile = path.join(this.projectRoot, 'app', 'root.tsx');
|
|
409
|
+
if (fs.existsSync(rootFile)) {
|
|
410
|
+
const content = fs.readFileSync(rootFile, 'utf8');
|
|
411
|
+
const modifiedContent = this.injectRemixProvider(content);
|
|
412
|
+
|
|
413
|
+
modifications.push({
|
|
414
|
+
filePath: rootFile,
|
|
415
|
+
action: 'modify',
|
|
416
|
+
content: modifiedContent,
|
|
417
|
+
description: 'Added HumanBehaviorProvider to Remix root component'
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Create or append to environment file
|
|
422
|
+
modifications.push(this.createEnvironmentModification(this.framework!));
|
|
423
|
+
|
|
424
|
+
return modifications;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Generate Vue-specific modifications
|
|
429
|
+
*/
|
|
430
|
+
private async generateVueModifications(): Promise<CodeModification[]> {
|
|
431
|
+
const modifications: CodeModification[] = [];
|
|
432
|
+
|
|
433
|
+
// Find main.js or main.ts
|
|
434
|
+
const mainFile = this.findVueMainFile();
|
|
435
|
+
if (mainFile) {
|
|
436
|
+
const content = fs.readFileSync(mainFile, 'utf8');
|
|
437
|
+
const modifiedContent = this.injectVuePlugin(content);
|
|
438
|
+
|
|
439
|
+
modifications.push({
|
|
440
|
+
filePath: mainFile,
|
|
441
|
+
action: 'modify',
|
|
442
|
+
content: modifiedContent,
|
|
443
|
+
description: 'Added HumanBehaviorPlugin to Vue app'
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Create or append to environment file
|
|
448
|
+
modifications.push(this.createEnvironmentModification(this.framework!));
|
|
449
|
+
|
|
450
|
+
return modifications;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Generate Angular-specific modifications
|
|
455
|
+
*/
|
|
456
|
+
private async generateAngularModifications(): Promise<CodeModification[]> {
|
|
457
|
+
const modifications: CodeModification[] = [];
|
|
458
|
+
|
|
459
|
+
// Check for modern Angular (standalone components) vs legacy (NgModule)
|
|
460
|
+
const appModuleFile = path.join(this.projectRoot, 'src', 'app', 'app.module.ts');
|
|
461
|
+
const appComponentFile = path.join(this.projectRoot, 'src', 'app', 'app.ts');
|
|
462
|
+
const mainFile = path.join(this.projectRoot, 'src', 'main.ts');
|
|
463
|
+
|
|
464
|
+
const isModernAngular = fs.existsSync(appComponentFile) && !fs.existsSync(appModuleFile);
|
|
465
|
+
|
|
466
|
+
if (isModernAngular) {
|
|
467
|
+
// Modern Angular 17+ with standalone components
|
|
468
|
+
if (fs.existsSync(mainFile)) {
|
|
469
|
+
const content = fs.readFileSync(mainFile, 'utf8');
|
|
470
|
+
const modifiedContent = this.injectAngularStandaloneInit(content);
|
|
471
|
+
|
|
472
|
+
modifications.push({
|
|
473
|
+
filePath: mainFile,
|
|
474
|
+
action: 'modify',
|
|
475
|
+
content: modifiedContent,
|
|
476
|
+
description: 'Added HumanBehavior initialization to Angular main.ts'
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
} else if (fs.existsSync(appModuleFile)) {
|
|
480
|
+
// Legacy Angular with NgModule
|
|
481
|
+
const content = fs.readFileSync(appModuleFile, 'utf8');
|
|
482
|
+
const modifiedContent = this.injectAngularModule(content);
|
|
483
|
+
|
|
484
|
+
modifications.push({
|
|
485
|
+
filePath: appModuleFile,
|
|
486
|
+
action: 'modify',
|
|
487
|
+
content: modifiedContent,
|
|
488
|
+
description: 'Added HumanBehaviorModule to Angular app'
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Handle Angular environment file (legacy structure)
|
|
493
|
+
const envFile = path.join(this.projectRoot, 'src', 'environments', 'environment.ts');
|
|
494
|
+
if (fs.existsSync(envFile)) {
|
|
495
|
+
const content = fs.readFileSync(envFile, 'utf8');
|
|
496
|
+
if (!content.includes('humanBehaviorApiKey')) {
|
|
497
|
+
const modifiedContent = content.replace(
|
|
498
|
+
/export const environment = {([\s\S]*?)};/,
|
|
499
|
+
`export const environment = {
|
|
500
|
+
$1,
|
|
501
|
+
humanBehaviorApiKey: process.env['HUMANBEHAVIOR_API_KEY'] || ''
|
|
502
|
+
};`
|
|
503
|
+
);
|
|
504
|
+
modifications.push({
|
|
505
|
+
filePath: envFile,
|
|
506
|
+
action: 'modify',
|
|
507
|
+
content: modifiedContent,
|
|
508
|
+
description: 'Added API key to Angular environment'
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Create or append to environment file
|
|
514
|
+
modifications.push(this.createEnvironmentModification(this.framework!));
|
|
515
|
+
|
|
516
|
+
return modifications;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Generate Svelte-specific modifications
|
|
521
|
+
*/
|
|
522
|
+
private async generateSvelteModifications(): Promise<CodeModification[]> {
|
|
523
|
+
const modifications: CodeModification[] = [];
|
|
524
|
+
|
|
525
|
+
// Check for SvelteKit
|
|
526
|
+
const svelteConfigFile = path.join(this.projectRoot, 'svelte.config.js');
|
|
527
|
+
const isSvelteKit = fs.existsSync(svelteConfigFile);
|
|
528
|
+
|
|
529
|
+
if (isSvelteKit) {
|
|
530
|
+
// SvelteKit - create layout file
|
|
531
|
+
const layoutFile = path.join(this.projectRoot, 'src', 'routes', '+layout.svelte');
|
|
532
|
+
if (fs.existsSync(layoutFile)) {
|
|
533
|
+
const content = fs.readFileSync(layoutFile, 'utf8');
|
|
534
|
+
const modifiedContent = this.injectSvelteKitLayout(content);
|
|
535
|
+
|
|
536
|
+
modifications.push({
|
|
537
|
+
filePath: layoutFile,
|
|
538
|
+
action: 'modify',
|
|
539
|
+
content: modifiedContent,
|
|
540
|
+
description: 'Added HumanBehavior store to SvelteKit layout'
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
// Regular Svelte - modify main file
|
|
545
|
+
const mainFile = this.findSvelteMainFile();
|
|
546
|
+
if (mainFile) {
|
|
547
|
+
const content = fs.readFileSync(mainFile, 'utf8');
|
|
548
|
+
const modifiedContent = this.injectSvelteStore(content);
|
|
549
|
+
|
|
550
|
+
modifications.push({
|
|
551
|
+
filePath: mainFile,
|
|
552
|
+
action: 'modify',
|
|
553
|
+
content: modifiedContent,
|
|
554
|
+
description: 'Added HumanBehavior store to Svelte app'
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Create or append to environment file
|
|
560
|
+
modifications.push(this.createEnvironmentModification(this.framework!));
|
|
561
|
+
|
|
562
|
+
return modifications;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Generate vanilla JS/TS modifications
|
|
567
|
+
*/
|
|
568
|
+
private async generateVanillaModifications(): Promise<CodeModification[]> {
|
|
569
|
+
const modifications: CodeModification[] = [];
|
|
570
|
+
|
|
571
|
+
// Find HTML file to inject script
|
|
572
|
+
const htmlFile = this.findHTMLFile();
|
|
573
|
+
if (htmlFile) {
|
|
574
|
+
const content = fs.readFileSync(htmlFile, 'utf8');
|
|
575
|
+
const modifiedContent = this.injectVanillaScript(content);
|
|
576
|
+
|
|
577
|
+
modifications.push({
|
|
578
|
+
filePath: htmlFile,
|
|
579
|
+
action: 'modify',
|
|
580
|
+
content: modifiedContent,
|
|
581
|
+
description: 'Added HumanBehavior CDN script to HTML file'
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Create or append to environment file
|
|
586
|
+
modifications.push(this.createEnvironmentModification(this.framework!));
|
|
587
|
+
|
|
588
|
+
return modifications;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Apply modifications to the codebase
|
|
593
|
+
*/
|
|
594
|
+
private async applyModifications(modifications: CodeModification[]): Promise<void> {
|
|
595
|
+
for (const modification of modifications) {
|
|
596
|
+
try {
|
|
597
|
+
const dir = path.dirname(modification.filePath);
|
|
598
|
+
if (!fs.existsSync(dir)) {
|
|
599
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
switch (modification.action) {
|
|
603
|
+
case 'create':
|
|
604
|
+
fs.writeFileSync(modification.filePath, modification.content);
|
|
605
|
+
break;
|
|
606
|
+
case 'modify':
|
|
607
|
+
fs.writeFileSync(modification.filePath, modification.content);
|
|
608
|
+
break;
|
|
609
|
+
case 'append':
|
|
610
|
+
fs.appendFileSync(modification.filePath, '\n' + modification.content);
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
} catch (error) {
|
|
614
|
+
throw new Error(`Failed to apply modification to ${modification.filePath}: ${error}`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Generate next steps for the user
|
|
621
|
+
*/
|
|
622
|
+
private generateNextSteps(): string[] {
|
|
623
|
+
const steps = [
|
|
624
|
+
'✅ SDK installed and configured automatically!',
|
|
625
|
+
'🚀 Your app is now tracking user behavior',
|
|
626
|
+
'📊 View sessions in your HumanBehavior dashboard',
|
|
627
|
+
'🔧 Customize tracking in your code as needed'
|
|
628
|
+
];
|
|
629
|
+
|
|
630
|
+
if (this.framework?.type === 'react' || this.framework?.type === 'nextjs') {
|
|
631
|
+
steps.push('💡 Use the useHumanBehavior() hook to track custom events');
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return steps;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Helper methods for file detection and content injection
|
|
638
|
+
private findReactAppFile(): string | null {
|
|
639
|
+
const possibleFiles = [
|
|
640
|
+
'src/App.jsx', 'src/App.js', 'src/App.tsx', 'src/App.ts',
|
|
641
|
+
'src/index.js', 'src/index.tsx', 'src/main.js', 'src/main.tsx'
|
|
642
|
+
];
|
|
643
|
+
|
|
644
|
+
for (const file of possibleFiles) {
|
|
645
|
+
const fullPath = path.join(this.projectRoot, file);
|
|
646
|
+
if (fs.existsSync(fullPath)) {
|
|
647
|
+
return fullPath;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
private findVueMainFile(): string | null {
|
|
654
|
+
const possibleFiles = [
|
|
655
|
+
'src/main.js', 'src/main.ts', 'src/main.jsx', 'src/main.tsx'
|
|
656
|
+
];
|
|
657
|
+
|
|
658
|
+
for (const file of possibleFiles) {
|
|
659
|
+
const fullPath = path.join(this.projectRoot, file);
|
|
660
|
+
if (fs.existsSync(fullPath)) {
|
|
661
|
+
return fullPath;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
private findSvelteMainFile(): string | null {
|
|
668
|
+
const possibleFiles = [
|
|
669
|
+
'src/main.js', 'src/main.ts', 'src/main.svelte'
|
|
670
|
+
];
|
|
671
|
+
|
|
672
|
+
for (const file of possibleFiles) {
|
|
673
|
+
const fullPath = path.join(this.projectRoot, file);
|
|
674
|
+
if (fs.existsSync(fullPath)) {
|
|
675
|
+
return fullPath;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
private findHTMLFile(): string | null {
|
|
682
|
+
const possibleFiles = ['index.html', 'public/index.html', 'dist/index.html'];
|
|
683
|
+
|
|
684
|
+
for (const file of possibleFiles) {
|
|
685
|
+
const fullPath = path.join(this.projectRoot, file);
|
|
686
|
+
if (fs.existsSync(fullPath)) {
|
|
687
|
+
return fullPath;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
private injectReactProvider(content: string, filePath: string): string {
|
|
694
|
+
const isTypeScript = filePath.endsWith('.tsx') || filePath.endsWith('.ts');
|
|
695
|
+
|
|
696
|
+
// Check if already has HumanBehaviorProvider
|
|
697
|
+
if (content.includes('HumanBehaviorProvider')) {
|
|
698
|
+
return content;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Determine the correct environment variable syntax based on bundler
|
|
702
|
+
const isVite = this.framework?.bundler === 'vite';
|
|
703
|
+
const envVar = isVite
|
|
704
|
+
? 'import.meta.env.VITE_HUMANBEHAVIOR_API_KEY!'
|
|
705
|
+
: 'process.env.HUMANBEHAVIOR_API_KEY!';
|
|
706
|
+
|
|
707
|
+
const importStatement = `import { HumanBehaviorProvider } from 'humanbehavior-js/react';`;
|
|
708
|
+
const providerWrapper = `
|
|
709
|
+
function App() {
|
|
710
|
+
return (
|
|
711
|
+
<HumanBehaviorProvider apiKey={${envVar}}>
|
|
712
|
+
{/* Your existing app content */}
|
|
713
|
+
</HumanBehaviorProvider>
|
|
714
|
+
);
|
|
715
|
+
}`;
|
|
716
|
+
|
|
717
|
+
// Simple injection - in production, you'd want more sophisticated parsing
|
|
718
|
+
if (content.includes('function App()') || content.includes('const App =')) {
|
|
719
|
+
return content.replace(
|
|
720
|
+
/(function App\(\)|const App =)/,
|
|
721
|
+
`${importStatement}\n\n$1`
|
|
722
|
+
).replace(
|
|
723
|
+
/return \(([\s\S]*?)\);/,
|
|
724
|
+
`return (
|
|
725
|
+
<HumanBehaviorProvider apiKey={${envVar}}>
|
|
726
|
+
$1
|
|
727
|
+
</HumanBehaviorProvider>
|
|
728
|
+
);`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return `${importStatement}\n\n${content}`;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private injectNextJSAppRouter(content: string): string {
|
|
736
|
+
if (content.includes('Providers')) {
|
|
737
|
+
return content;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const importStatement = `import { Providers } from './providers';`;
|
|
741
|
+
|
|
742
|
+
// First, add the import statement
|
|
743
|
+
let modifiedContent = content.replace(
|
|
744
|
+
/export default function RootLayout/,
|
|
745
|
+
`${importStatement}\n\nexport default function RootLayout`
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
// Then wrap the body content with Providers
|
|
749
|
+
// Use a more specific approach to handle the body content
|
|
750
|
+
modifiedContent = modifiedContent.replace(
|
|
751
|
+
/<body([^>]*)>([\s\S]*?)<\/body>/,
|
|
752
|
+
(match, bodyAttrs, bodyContent) => {
|
|
753
|
+
// Trim whitespace and newlines from bodyContent
|
|
754
|
+
const trimmedContent = bodyContent.trim();
|
|
755
|
+
return `<body${bodyAttrs}>
|
|
756
|
+
<Providers>
|
|
757
|
+
${trimmedContent}
|
|
758
|
+
</Providers>
|
|
759
|
+
</body>`;
|
|
760
|
+
}
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
return modifiedContent;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
private injectNextJSPagesRouter(content: string): string {
|
|
767
|
+
if (content.includes('Providers')) {
|
|
768
|
+
return content;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const importStatement = `import { Providers } from '../components/providers';`;
|
|
772
|
+
|
|
773
|
+
return content.replace(
|
|
774
|
+
/function MyApp/,
|
|
775
|
+
`${importStatement}\n\nfunction MyApp`
|
|
776
|
+
).replace(
|
|
777
|
+
/return \(([\s\S]*?)\);/,
|
|
778
|
+
`return (
|
|
779
|
+
<Providers>
|
|
780
|
+
$1
|
|
781
|
+
</Providers>
|
|
782
|
+
);`
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
private injectRemixProvider(content: string): string {
|
|
787
|
+
if (content.includes('HumanBehaviorProvider')) {
|
|
788
|
+
return content;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const importStatement = `import { HumanBehaviorProvider, createHumanBehaviorLoader } from 'humanbehavior-js/remix';`;
|
|
792
|
+
const useLoaderDataImport = `import { useLoaderData } from "@remix-run/react";`;
|
|
793
|
+
|
|
794
|
+
// Add imports more robustly
|
|
795
|
+
let modifiedContent = content;
|
|
796
|
+
|
|
797
|
+
// Add HumanBehaviorProvider import - find the last import and add after it
|
|
798
|
+
if (!content.includes('HumanBehaviorProvider')) {
|
|
799
|
+
const lastImportIndex = modifiedContent.lastIndexOf('import');
|
|
800
|
+
if (lastImportIndex !== -1) {
|
|
801
|
+
const nextLineIndex = modifiedContent.indexOf('\n', lastImportIndex);
|
|
802
|
+
if (nextLineIndex !== -1) {
|
|
803
|
+
modifiedContent = modifiedContent.slice(0, nextLineIndex + 1) +
|
|
804
|
+
importStatement + '\n' +
|
|
805
|
+
modifiedContent.slice(nextLineIndex + 1);
|
|
806
|
+
} else {
|
|
807
|
+
modifiedContent = modifiedContent + '\n' + importStatement;
|
|
808
|
+
}
|
|
809
|
+
} else {
|
|
810
|
+
modifiedContent = importStatement + '\n' + modifiedContent;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Add useLoaderData import - find the last import and add after it
|
|
815
|
+
if (!content.includes('useLoaderData')) {
|
|
816
|
+
const lastImportIndex = modifiedContent.lastIndexOf('import');
|
|
817
|
+
if (lastImportIndex !== -1) {
|
|
818
|
+
const nextLineIndex = modifiedContent.indexOf('\n', lastImportIndex);
|
|
819
|
+
if (nextLineIndex !== -1) {
|
|
820
|
+
modifiedContent = modifiedContent.slice(0, nextLineIndex + 1) +
|
|
821
|
+
useLoaderDataImport + '\n' +
|
|
822
|
+
modifiedContent.slice(nextLineIndex + 1);
|
|
823
|
+
} else {
|
|
824
|
+
modifiedContent = modifiedContent + '\n' + useLoaderDataImport;
|
|
825
|
+
}
|
|
826
|
+
} else {
|
|
827
|
+
modifiedContent = useLoaderDataImport + '\n' + modifiedContent;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
// Add loader function before the App component
|
|
834
|
+
if (!content.includes('export const loader')) {
|
|
835
|
+
modifiedContent = modifiedContent.replace(
|
|
836
|
+
/export default function App\(\)/,
|
|
837
|
+
`export const loader = createHumanBehaviorLoader();
|
|
838
|
+
|
|
839
|
+
export default function App()`
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Wrap the App component content with HumanBehaviorProvider
|
|
844
|
+
modifiedContent = modifiedContent.replace(
|
|
845
|
+
/export default function App\(\) \{[\s\S]*?return \(([\s\S]*?)\);[\s\S]*?\}/,
|
|
846
|
+
`export default function App() {
|
|
847
|
+
const data = useLoaderData<typeof loader>();
|
|
848
|
+
|
|
849
|
+
return (
|
|
850
|
+
<HumanBehaviorProvider apiKey={data.ENV.HUMANBEHAVIOR_API_KEY}>
|
|
851
|
+
$1
|
|
852
|
+
</HumanBehaviorProvider>
|
|
853
|
+
);
|
|
854
|
+
}`
|
|
855
|
+
);
|
|
856
|
+
|
|
857
|
+
return modifiedContent;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
private injectVuePlugin(content: string): string {
|
|
861
|
+
if (content.includes('HumanBehaviorPlugin')) {
|
|
862
|
+
return content;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const importStatement = `import { HumanBehaviorPlugin } from 'humanbehavior-js/vue';`;
|
|
866
|
+
const pluginUsage = `app.use(HumanBehaviorPlugin, {
|
|
867
|
+
apiKey: import.meta.env.VITE_HUMANBEHAVIOR_API_KEY
|
|
868
|
+
});`;
|
|
869
|
+
|
|
870
|
+
// Handle both Vue 2 and Vue 3 patterns
|
|
871
|
+
if (content.includes('createApp')) {
|
|
872
|
+
// Vue 3
|
|
873
|
+
return content.replace(
|
|
874
|
+
/import.*from.*['"]vue['"]/,
|
|
875
|
+
`$&\n${importStatement}`
|
|
876
|
+
).replace(
|
|
877
|
+
/app\.mount\(/,
|
|
878
|
+
`${pluginUsage}\n\napp.mount(`
|
|
879
|
+
);
|
|
880
|
+
} else {
|
|
881
|
+
// Vue 2
|
|
882
|
+
return content.replace(
|
|
883
|
+
/import.*from.*['"]vue['"]/,
|
|
884
|
+
`$&\n${importStatement}`
|
|
885
|
+
).replace(
|
|
886
|
+
/new Vue\(/,
|
|
887
|
+
`${pluginUsage}\n\nnew Vue(`
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
private injectAngularModule(content: string): string {
|
|
893
|
+
if (content.includes('HumanBehaviorModule')) {
|
|
894
|
+
return content;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const importStatement = `import { HumanBehaviorModule } from 'humanbehavior-js/angular';`;
|
|
898
|
+
const environmentImport = `import { environment } from '../environments/environment';`;
|
|
899
|
+
|
|
900
|
+
// Add environment import if not present
|
|
901
|
+
let modifiedContent = content;
|
|
902
|
+
if (!content.includes('environment')) {
|
|
903
|
+
modifiedContent = content.replace(
|
|
904
|
+
/import.*from.*['"]@angular/,
|
|
905
|
+
`${environmentImport}\n$&`
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
return modifiedContent.replace(
|
|
910
|
+
/imports:\s*\[([\s\S]*?)\]/,
|
|
911
|
+
`imports: [
|
|
912
|
+
$1,
|
|
913
|
+
HumanBehaviorModule.forRoot({
|
|
914
|
+
apiKey: environment.humanBehaviorApiKey
|
|
915
|
+
})
|
|
916
|
+
]`
|
|
917
|
+
).replace(
|
|
918
|
+
/import.*from.*['"]@angular/,
|
|
919
|
+
`$&\n${importStatement}`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
private injectAngularStandaloneInit(content: string): string {
|
|
924
|
+
if (content.includes('initializeHumanBehavior')) {
|
|
925
|
+
return content;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const importStatement = `import { initializeHumanBehavior } from 'humanbehavior-js/angular';`;
|
|
929
|
+
|
|
930
|
+
// Add import at the top
|
|
931
|
+
let modifiedContent = content.replace(
|
|
932
|
+
/import.*from.*['"]@angular/,
|
|
933
|
+
`${importStatement}\n$&`
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
// Add initialization after bootstrapApplication
|
|
937
|
+
modifiedContent = modifiedContent.replace(
|
|
938
|
+
/(bootstrapApplication\([^}]+\}?\)(?:\s*\.catch[^;]+;)?)/,
|
|
939
|
+
`$1
|
|
940
|
+
|
|
941
|
+
// Initialize HumanBehavior SDK (client-side only)
|
|
942
|
+
if (typeof window !== 'undefined') {
|
|
943
|
+
const tracker = initializeHumanBehavior(
|
|
944
|
+
'${this.apiKey}'
|
|
945
|
+
);
|
|
946
|
+
}`
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
return modifiedContent;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
private injectSvelteStore(content: string): string {
|
|
953
|
+
if (content.includes('humanBehaviorStore')) {
|
|
954
|
+
return content;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const importStatement = `import { humanBehaviorStore } from 'humanbehavior-js/svelte';`;
|
|
958
|
+
const initCode = `humanBehaviorStore.init(process.env.PUBLIC_HUMANBEHAVIOR_API_KEY || '');`;
|
|
959
|
+
|
|
960
|
+
return `${importStatement}\n${initCode}\n\n${content}`;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
private injectSvelteKitLayout(content: string): string {
|
|
964
|
+
if (content.includes('humanBehaviorStore')) {
|
|
965
|
+
return content;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const importStatement = `import { humanBehaviorStore } from 'humanbehavior-js/svelte';`;
|
|
969
|
+
const envImport = `import { PUBLIC_HUMANBEHAVIOR_API_KEY } from '$env/static/public';`;
|
|
970
|
+
const initCode = `humanBehaviorStore.init(PUBLIC_HUMANBEHAVIOR_API_KEY || '');`;
|
|
971
|
+
|
|
972
|
+
// Add to script section - handle different script tag patterns
|
|
973
|
+
if (content.includes('<script lang="ts">')) {
|
|
974
|
+
return content.replace(
|
|
975
|
+
/<script lang="ts">/,
|
|
976
|
+
`<script lang="ts">\n\t${envImport}\n\t${importStatement}\n\t${initCode}`
|
|
977
|
+
);
|
|
978
|
+
} else if (content.includes('<script>')) {
|
|
979
|
+
return content.replace(
|
|
980
|
+
/<script>/,
|
|
981
|
+
`<script>\n\t${envImport}\n\t${importStatement}\n\t${initCode}`
|
|
982
|
+
);
|
|
983
|
+
} else if (content.includes('<script context="module">')) {
|
|
984
|
+
return content.replace(
|
|
985
|
+
/<script\s+context="module">/,
|
|
986
|
+
`<script context="module">\n\t${envImport}\n\t${importStatement}\n\t${initCode}`
|
|
987
|
+
);
|
|
988
|
+
} else {
|
|
989
|
+
// If no script tag found, add one at the beginning
|
|
990
|
+
return content.replace(
|
|
991
|
+
/<svelte:head>/,
|
|
992
|
+
`<script lang="ts">\n\t${envImport}\n\t${importStatement}\n\t${initCode}\n</script>\n\n<svelte:head>`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
private injectVanillaScript(content: string): string {
|
|
998
|
+
if (content.includes('humanbehavior-js')) {
|
|
999
|
+
return content;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const cdnScript = `<script src="https://unpkg.com/humanbehavior-js@latest/dist/index.min.js"></script>`;
|
|
1003
|
+
const initScript = `<script>
|
|
1004
|
+
// Initialize HumanBehavior SDK
|
|
1005
|
+
// Note: For vanilla HTML, the API key must be hardcoded since env vars aren't available
|
|
1006
|
+
const tracker = HumanBehaviorTracker.init('${this.apiKey}');
|
|
1007
|
+
</script>`;
|
|
1008
|
+
|
|
1009
|
+
return content.replace(
|
|
1010
|
+
/<\/head>/,
|
|
1011
|
+
` ${cdnScript}\n ${initScript}\n</head>`
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
private injectNuxtConfig(content: string): string {
|
|
1016
|
+
if (content.includes('humanBehaviorApiKey')) {
|
|
1017
|
+
return content;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Add runtime config by inserting it after the opening brace
|
|
1021
|
+
return content.replace(
|
|
1022
|
+
/export default defineNuxtConfig\(\{/,
|
|
1023
|
+
`export default defineNuxtConfig({
|
|
1024
|
+
runtimeConfig: {
|
|
1025
|
+
public: {
|
|
1026
|
+
humanBehaviorApiKey: process.env.NUXT_PUBLIC_HUMANBEHAVIOR_API_KEY
|
|
1027
|
+
}
|
|
1028
|
+
},`
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* Helper method to find the best environment file for a framework
|
|
1034
|
+
*/
|
|
1035
|
+
private findBestEnvFile(framework: FrameworkInfo): { filePath: string; envVarName: string } {
|
|
1036
|
+
const possibleEnvFiles = [
|
|
1037
|
+
'.env.local',
|
|
1038
|
+
'.env.development.local',
|
|
1039
|
+
'.env.development',
|
|
1040
|
+
'.env.local.development',
|
|
1041
|
+
'.env',
|
|
1042
|
+
'.env.production',
|
|
1043
|
+
'.env.staging'
|
|
1044
|
+
];
|
|
1045
|
+
|
|
1046
|
+
// Framework-specific environment variable names
|
|
1047
|
+
const getEnvVarName = (framework: FrameworkInfo) => {
|
|
1048
|
+
// Handle React+Vite specifically
|
|
1049
|
+
if (framework.type === 'react' && framework.bundler === 'vite') {
|
|
1050
|
+
return 'VITE_HUMANBEHAVIOR_API_KEY';
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Framework-specific mappings
|
|
1054
|
+
const envVarNames = {
|
|
1055
|
+
react: 'HUMANBEHAVIOR_API_KEY',
|
|
1056
|
+
nextjs: 'NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY',
|
|
1057
|
+
vue: 'VITE_HUMANBEHAVIOR_API_KEY',
|
|
1058
|
+
svelte: 'PUBLIC_HUMANBEHAVIOR_API_KEY',
|
|
1059
|
+
angular: 'HUMANBEHAVIOR_API_KEY',
|
|
1060
|
+
nuxt: 'NUXT_PUBLIC_HUMANBEHAVIOR_API_KEY',
|
|
1061
|
+
remix: 'HUMANBEHAVIOR_API_KEY',
|
|
1062
|
+
vanilla: 'HUMANBEHAVIOR_API_KEY',
|
|
1063
|
+
node: 'HUMANBEHAVIOR_API_KEY'
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
return envVarNames[framework.type] || 'HUMANBEHAVIOR_API_KEY';
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
const envVarName = getEnvVarName(framework);
|
|
1070
|
+
|
|
1071
|
+
// Check for existing files
|
|
1072
|
+
for (const envFile of possibleEnvFiles) {
|
|
1073
|
+
const fullPath = path.join(this.projectRoot, envFile);
|
|
1074
|
+
if (fs.existsSync(fullPath)) {
|
|
1075
|
+
return { filePath: fullPath, envVarName };
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// Framework-specific default file creation
|
|
1080
|
+
const defaultFiles = {
|
|
1081
|
+
react: '.env.local',
|
|
1082
|
+
nextjs: '.env.local',
|
|
1083
|
+
vue: '.env.local',
|
|
1084
|
+
svelte: '.env',
|
|
1085
|
+
angular: '.env',
|
|
1086
|
+
nuxt: '.env',
|
|
1087
|
+
remix: '.env.local',
|
|
1088
|
+
vanilla: '.env',
|
|
1089
|
+
node: '.env'
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
const defaultFile = defaultFiles[framework.type] || '.env';
|
|
1093
|
+
return {
|
|
1094
|
+
filePath: path.join(this.projectRoot, defaultFile),
|
|
1095
|
+
envVarName
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Helper method to create or append to environment files
|
|
1101
|
+
*/
|
|
1102
|
+
private createEnvironmentModification(framework: FrameworkInfo): CodeModification {
|
|
1103
|
+
const { filePath, envVarName } = this.findBestEnvFile(framework);
|
|
1104
|
+
|
|
1105
|
+
if (fs.existsSync(filePath)) {
|
|
1106
|
+
// Check if the variable already exists
|
|
1107
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
1108
|
+
if (content.includes(envVarName)) {
|
|
1109
|
+
// Variable exists, don't modify
|
|
1110
|
+
return {
|
|
1111
|
+
filePath,
|
|
1112
|
+
action: 'modify',
|
|
1113
|
+
content: content, // No change
|
|
1114
|
+
description: `API key already exists in ${path.basename(filePath)}`
|
|
1115
|
+
};
|
|
1116
|
+
} else {
|
|
1117
|
+
// Append to existing file
|
|
1118
|
+
return {
|
|
1119
|
+
filePath,
|
|
1120
|
+
action: 'append',
|
|
1121
|
+
content: `\n${envVarName}=${this.apiKey}`,
|
|
1122
|
+
description: `Added API key to existing ${path.basename(filePath)}`
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
} else {
|
|
1126
|
+
// Create new file
|
|
1127
|
+
return {
|
|
1128
|
+
filePath,
|
|
1129
|
+
action: 'create',
|
|
1130
|
+
content: `${envVarName}=${this.apiKey}`,
|
|
1131
|
+
description: `Created ${path.basename(filePath)} with API key`
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Browser-based auto-installation wizard
|
|
1139
|
+
*/
|
|
1140
|
+
export class BrowserAutoInstallationWizard {
|
|
1141
|
+
private apiKey: string;
|
|
1142
|
+
|
|
1143
|
+
constructor(apiKey: string) {
|
|
1144
|
+
this.apiKey = apiKey;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
async install(): Promise<InstallationResult> {
|
|
1148
|
+
try {
|
|
1149
|
+
// Detect framework in browser
|
|
1150
|
+
const framework = this.detectFramework();
|
|
1151
|
+
|
|
1152
|
+
// Generate installation instructions
|
|
1153
|
+
const modifications = this.generateBrowserModifications(framework);
|
|
1154
|
+
|
|
1155
|
+
return {
|
|
1156
|
+
success: true,
|
|
1157
|
+
framework,
|
|
1158
|
+
modifications,
|
|
1159
|
+
errors: [],
|
|
1160
|
+
nextSteps: [
|
|
1161
|
+
'✅ Framework detected automatically',
|
|
1162
|
+
'📋 Copy the generated code to your project',
|
|
1163
|
+
'🚀 Your app will be ready to track user behavior'
|
|
1164
|
+
]
|
|
1165
|
+
};
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
return {
|
|
1168
|
+
success: false,
|
|
1169
|
+
framework: { name: 'unknown', type: 'vanilla' },
|
|
1170
|
+
modifications: [],
|
|
1171
|
+
errors: [error instanceof Error ? error.message : 'Unknown error'],
|
|
1172
|
+
nextSteps: []
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
private detectFramework(): FrameworkInfo {
|
|
1178
|
+
if (typeof window !== 'undefined') {
|
|
1179
|
+
if ((window as any).React) {
|
|
1180
|
+
return { name: 'react', type: 'react' };
|
|
1181
|
+
}
|
|
1182
|
+
if ((window as any).Vue) {
|
|
1183
|
+
return { name: 'vue', type: 'vue' };
|
|
1184
|
+
}
|
|
1185
|
+
if ((window as any).angular) {
|
|
1186
|
+
return { name: 'angular', type: 'angular' };
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
return { name: 'vanilla', type: 'vanilla' };
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
private generateBrowserModifications(framework: FrameworkInfo): CodeModification[] {
|
|
1194
|
+
// Return code snippets for browser environment
|
|
1195
|
+
const modifications: CodeModification[] = [];
|
|
1196
|
+
|
|
1197
|
+
switch (framework.type) {
|
|
1198
|
+
case 'react':
|
|
1199
|
+
modifications.push({
|
|
1200
|
+
filePath: 'App.jsx',
|
|
1201
|
+
action: 'create',
|
|
1202
|
+
content: `import { HumanBehaviorProvider } from 'humanbehavior-js/react';
|
|
1203
|
+
|
|
1204
|
+
function App() {
|
|
1205
|
+
return (
|
|
1206
|
+
<HumanBehaviorProvider apiKey="${this.apiKey}">
|
|
1207
|
+
{/* Your app components */}
|
|
1208
|
+
</HumanBehaviorProvider>
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
export default App;`,
|
|
1213
|
+
description: 'React component with HumanBehaviorProvider'
|
|
1214
|
+
});
|
|
1215
|
+
break;
|
|
1216
|
+
|
|
1217
|
+
case 'remix':
|
|
1218
|
+
modifications.push({
|
|
1219
|
+
filePath: 'app/root.tsx',
|
|
1220
|
+
action: 'create',
|
|
1221
|
+
content: `import {
|
|
1222
|
+
Links,
|
|
1223
|
+
Meta,
|
|
1224
|
+
Outlet,
|
|
1225
|
+
Scripts,
|
|
1226
|
+
ScrollRestoration,
|
|
1227
|
+
useLoaderData,
|
|
1228
|
+
} from "@remix-run/react";
|
|
1229
|
+
import { HumanBehaviorProvider } from 'humanbehavior-js/react';
|
|
1230
|
+
import type { LoaderFunctionArgs } from "@remix-run/node";
|
|
1231
|
+
|
|
1232
|
+
export async function loader({ request }: LoaderFunctionArgs) {
|
|
1233
|
+
return {
|
|
1234
|
+
ENV: {
|
|
1235
|
+
HUMANBEHAVIOR_API_KEY: process.env.HUMANBEHAVIOR_API_KEY,
|
|
1236
|
+
},
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
export function Layout({ children }: { children: React.ReactNode }) {
|
|
1241
|
+
return (
|
|
1242
|
+
<html lang="en">
|
|
1243
|
+
<head>
|
|
1244
|
+
<meta charSet="utf-8" />
|
|
1245
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1246
|
+
<Meta />
|
|
1247
|
+
<Links />
|
|
1248
|
+
</head>
|
|
1249
|
+
<body>
|
|
1250
|
+
{children}
|
|
1251
|
+
<ScrollRestoration />
|
|
1252
|
+
<Scripts />
|
|
1253
|
+
</body>
|
|
1254
|
+
</html>
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
export default function App() {
|
|
1259
|
+
const data = useLoaderData<typeof loader>();
|
|
1260
|
+
|
|
1261
|
+
return (
|
|
1262
|
+
<HumanBehaviorProvider apiKey={data.ENV.HUMANBEHAVIOR_API_KEY}>
|
|
1263
|
+
<div className="min-h-screen bg-gray-50">
|
|
1264
|
+
{/* Your app content */}
|
|
1265
|
+
<Outlet />
|
|
1266
|
+
</div>
|
|
1267
|
+
</HumanBehaviorProvider>
|
|
1268
|
+
);
|
|
1269
|
+
}`,
|
|
1270
|
+
description: 'Remix root component with HumanBehaviorProvider'
|
|
1271
|
+
});
|
|
1272
|
+
break;
|
|
1273
|
+
|
|
1274
|
+
case 'vue':
|
|
1275
|
+
modifications.push({
|
|
1276
|
+
filePath: 'main.js',
|
|
1277
|
+
action: 'create',
|
|
1278
|
+
content: `import { createApp } from 'vue';
|
|
1279
|
+
import { HumanBehaviorPlugin } from 'humanbehavior-js/vue';
|
|
1280
|
+
import App from './App.vue';
|
|
1281
|
+
|
|
1282
|
+
const app = createApp(App);
|
|
1283
|
+
app.use(HumanBehaviorPlugin, {
|
|
1284
|
+
apiKey: '${this.apiKey}'
|
|
1285
|
+
});
|
|
1286
|
+
app.mount('#app');`,
|
|
1287
|
+
description: 'Vue app with HumanBehaviorPlugin'
|
|
1288
|
+
});
|
|
1289
|
+
break;
|
|
1290
|
+
|
|
1291
|
+
default:
|
|
1292
|
+
modifications.push({
|
|
1293
|
+
filePath: 'humanbehavior-init.js',
|
|
1294
|
+
action: 'create',
|
|
1295
|
+
content: `import { HumanBehaviorTracker } from 'humanbehavior-js';
|
|
1296
|
+
|
|
1297
|
+
const tracker = HumanBehaviorTracker.init('${this.apiKey}');`,
|
|
1298
|
+
description: 'Vanilla JS initialization'
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
return modifications;
|
|
1303
|
+
}
|
|
1304
|
+
}
|