@scope-analytics/browser 0.0.4
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/README.md +109 -0
- package/bin/scope-analytics.js +103 -0
- package/package.json +34 -0
- package/src/index.js +7 -0
- package/src/install.js +158 -0
- package/src/platforms/detect.js +48 -0
- package/src/platforms/instructions.js +71 -0
- package/src/uninstall.js +68 -0
- package/src/validate.js +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Scope Analytics
|
|
2
|
+
|
|
3
|
+
Zero-code analytics with AI-powered insights. Install analytics in under 5 minutes without modifying your application code.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Run after building your application
|
|
9
|
+
npx @scope-analytics/browser install --key YOUR_PUBLIC_KEY
|
|
10
|
+
|
|
11
|
+
# Or use environment variable
|
|
12
|
+
export SCOPE_PUBLIC_KEY=your_public_key
|
|
13
|
+
npx @scope-analytics/browser install
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## How it Works
|
|
17
|
+
|
|
18
|
+
Scope Analytics modifies your built HTML files to inject our lightweight SDK. This approach:
|
|
19
|
+
- ✅ Works with any framework or build tool
|
|
20
|
+
- ✅ No code changes required
|
|
21
|
+
- ✅ Automatic tracking of all user interactions
|
|
22
|
+
- ✅ Compatible with all major hosting platforms
|
|
23
|
+
|
|
24
|
+
## Platform-Specific Setup
|
|
25
|
+
|
|
26
|
+
### Vercel
|
|
27
|
+
```json
|
|
28
|
+
// vercel.json
|
|
29
|
+
{
|
|
30
|
+
"buildCommand": "npm run build && npx @scope-analytics/browser install"
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Netlify
|
|
35
|
+
```toml
|
|
36
|
+
# netlify.toml
|
|
37
|
+
[build]
|
|
38
|
+
command = "npm run build && npx @scope-analytics/browser install"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Render
|
|
42
|
+
Set build command: `npm run build && npx @scope-analytics/browser install`
|
|
43
|
+
|
|
44
|
+
### Next.js
|
|
45
|
+
```json
|
|
46
|
+
// package.json
|
|
47
|
+
{
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "next build && npx @scope-analytics/browser install --dir .next"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Commands
|
|
55
|
+
|
|
56
|
+
### Install
|
|
57
|
+
```bash
|
|
58
|
+
npx @scope-analytics/browser install [options]
|
|
59
|
+
|
|
60
|
+
Options:
|
|
61
|
+
-k, --key <apiKey> Your Scope Analytics public key
|
|
62
|
+
-d, --dir <directory> Build output directory (default: dist)
|
|
63
|
+
-e, --endpoint <url> Custom API endpoint
|
|
64
|
+
--no-api-tracking Disable API call tracking
|
|
65
|
+
--debug Enable debug mode
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Uninstall
|
|
69
|
+
```bash
|
|
70
|
+
npx @scope-analytics/browser uninstall [options]
|
|
71
|
+
|
|
72
|
+
Options:
|
|
73
|
+
-d, --dir <directory> Build output directory (default: dist)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Validate
|
|
77
|
+
```bash
|
|
78
|
+
npx @scope-analytics/browser validate [options]
|
|
79
|
+
|
|
80
|
+
Options:
|
|
81
|
+
-d, --dir <directory> Build output directory (default: dist)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## What Gets Tracked
|
|
85
|
+
|
|
86
|
+
- 📊 Page views and navigation
|
|
87
|
+
- 🖱️ Clicks and interactions
|
|
88
|
+
- 📜 Scroll behavior and reading time
|
|
89
|
+
- ⏱️ Performance metrics
|
|
90
|
+
- 🔗 API calls (optional)
|
|
91
|
+
- ❌ Errors and exceptions
|
|
92
|
+
|
|
93
|
+
## Features
|
|
94
|
+
|
|
95
|
+
- **AI-Powered Insights**: Ask questions in natural language
|
|
96
|
+
- **Automatic Tracking**: No manual event setup required
|
|
97
|
+
- **Privacy-First**: No personal data collection
|
|
98
|
+
- **Lightweight**: < 30KB gzipped
|
|
99
|
+
- **Real-Time**: See events as they happen
|
|
100
|
+
|
|
101
|
+
## Support
|
|
102
|
+
|
|
103
|
+
- Documentation: Coming soon at https://scopeai.dev
|
|
104
|
+
- Email: Contact via https://scopeai.dev
|
|
105
|
+
- Issues: Please report issues via our website
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { install } from '../src/install.js';
|
|
6
|
+
import { uninstall } from '../src/uninstall.js';
|
|
7
|
+
import { validate } from '../src/validate.js';
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name('scope-analytics')
|
|
13
|
+
.description('Scope Analytics - Build-time integration for zero-code analytics')
|
|
14
|
+
.version('0.0.4');
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command('install')
|
|
18
|
+
.description('Install Scope Analytics SDK into your built application')
|
|
19
|
+
.option('-k, --key <apiKey>', 'Your Scope Analytics public key (or use SCOPE_PUBLIC_KEY env var)')
|
|
20
|
+
.option('-d, --dir <directory>', 'Build output directory', 'dist')
|
|
21
|
+
.option('-e, --endpoint <url>', 'Custom API endpoint', 'https://api.scopeai.dev')
|
|
22
|
+
.option('--no-api-tracking', 'Disable API call tracking')
|
|
23
|
+
.option('--channel <channel>', 'SDK channel (v1, beta, stable)', 'v1')
|
|
24
|
+
.option('--sdk-url <url>', 'Custom SDK URL (overrides channel)')
|
|
25
|
+
.option('--debug', 'Enable debug mode')
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
try {
|
|
28
|
+
const apiKey = options.key || process.env.SCOPE_PUBLIC_KEY;
|
|
29
|
+
|
|
30
|
+
if (!apiKey) {
|
|
31
|
+
console.error(chalk.red('Error: API key is required'));
|
|
32
|
+
console.error(chalk.yellow('Please provide it via --key flag or SCOPE_PUBLIC_KEY environment variable'));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(chalk.cyan('🚀 Installing Scope Analytics...'));
|
|
37
|
+
|
|
38
|
+
await install({
|
|
39
|
+
apiKey,
|
|
40
|
+
buildDir: options.dir,
|
|
41
|
+
endpoint: options.endpoint,
|
|
42
|
+
trackApiCalls: options.apiTracking,
|
|
43
|
+
channel: options.channel,
|
|
44
|
+
sdkUrl: options.sdkUrl,
|
|
45
|
+
debug: options.debug
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
console.log(chalk.green('✅ Scope Analytics installed successfully!'));
|
|
49
|
+
console.log(chalk.gray('Your application is now tracking analytics.'));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(chalk.red('Installation failed:'), error.message);
|
|
52
|
+
if (options.debug) {
|
|
53
|
+
console.error(error);
|
|
54
|
+
}
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
program
|
|
60
|
+
.command('uninstall')
|
|
61
|
+
.description('Remove Scope Analytics SDK from your application')
|
|
62
|
+
.option('-d, --dir <directory>', 'Build output directory', 'dist')
|
|
63
|
+
.action(async (options) => {
|
|
64
|
+
try {
|
|
65
|
+
console.log(chalk.cyan('🗑️ Uninstalling Scope Analytics...'));
|
|
66
|
+
|
|
67
|
+
await uninstall({
|
|
68
|
+
buildDir: options.dir
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
console.log(chalk.green('✅ Scope Analytics uninstalled successfully'));
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(chalk.red('Uninstall failed:'), error.message);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
program
|
|
79
|
+
.command('validate')
|
|
80
|
+
.description('Validate that Scope Analytics is properly installed')
|
|
81
|
+
.option('-d, --dir <directory>', 'Build output directory', 'dist')
|
|
82
|
+
.action(async (options) => {
|
|
83
|
+
try {
|
|
84
|
+
console.log(chalk.cyan('🔍 Validating Scope Analytics installation...'));
|
|
85
|
+
|
|
86
|
+
const result = await validate({
|
|
87
|
+
buildDir: options.dir
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (result.valid) {
|
|
91
|
+
console.log(chalk.green('✅ Scope Analytics is properly installed'));
|
|
92
|
+
console.log(chalk.gray(`Found in ${result.files.length} HTML files`));
|
|
93
|
+
} else {
|
|
94
|
+
console.log(chalk.yellow('⚠️ Scope Analytics is not installed'));
|
|
95
|
+
console.log(chalk.gray('Run "npx @scope-analytics/browser install" to add analytics to your application'));
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error(chalk.red('Validation failed:'), error.message);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scope-analytics/browser",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "Build-time integration for Scope Analytics - Zero-code analytics with AI insights",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"scope-analytics": "bin/scope-analytics.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"analytics",
|
|
13
|
+
"scope",
|
|
14
|
+
"tracking",
|
|
15
|
+
"ai",
|
|
16
|
+
"build-tool"
|
|
17
|
+
],
|
|
18
|
+
"author": "Scope AI",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"homepage": "https://scopeai.dev",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"chalk": "^5.3.0",
|
|
23
|
+
"commander": "^11.1.0",
|
|
24
|
+
"glob": "^10.3.10"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=14.0.0"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"bin/",
|
|
31
|
+
"src/",
|
|
32
|
+
"README.md"
|
|
33
|
+
]
|
|
34
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { install } from './install.js';
|
|
2
|
+
export { uninstall } from './uninstall.js';
|
|
3
|
+
export { validate } from './validate.js';
|
|
4
|
+
|
|
5
|
+
// Platform-specific helpers
|
|
6
|
+
export { detectPlatform } from './platforms/detect.js';
|
|
7
|
+
export { getInstallInstructions } from './platforms/instructions.js';
|
package/src/install.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
// Determine SDK URL based on environment
|
|
7
|
+
function getSDKUrl(options) {
|
|
8
|
+
// Allow explicit SDK URL override
|
|
9
|
+
if (options.sdkUrl) {
|
|
10
|
+
return options.sdkUrl;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Check for channel preference
|
|
14
|
+
const channel = options.channel || process.env.SCOPE_SDK_CHANNEL || 'v1';
|
|
15
|
+
const validChannels = ['v1', 'beta', 'stable'];
|
|
16
|
+
|
|
17
|
+
if (validChannels.includes(channel)) {
|
|
18
|
+
return `https://cdn.scopeai.dev/${channel}/sdk.js`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Default to stable (v1) with cache-busting version
|
|
22
|
+
const version = '0.0.2'; // Update this with each SDK release
|
|
23
|
+
return `https://cdn.scopeai.dev/v1/sdk.js?v=${version}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function install(options) {
|
|
27
|
+
const {
|
|
28
|
+
apiKey,
|
|
29
|
+
buildDir = 'dist',
|
|
30
|
+
endpoint = 'https://api.scopeai.dev',
|
|
31
|
+
trackApiCalls = true,
|
|
32
|
+
debug = false
|
|
33
|
+
} = options;
|
|
34
|
+
|
|
35
|
+
// Verify build directory exists
|
|
36
|
+
try {
|
|
37
|
+
await fs.access(buildDir);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new Error(`Build directory "${buildDir}" does not exist. Please build your application first.`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Find all HTML files
|
|
43
|
+
const htmlFiles = await glob(`${buildDir}/**/*.html`);
|
|
44
|
+
|
|
45
|
+
if (htmlFiles.length === 0) {
|
|
46
|
+
throw new Error(`No HTML files found in "${buildDir}". Is this the correct build directory?`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (debug) {
|
|
50
|
+
console.log(chalk.gray(`Found ${htmlFiles.length} HTML files to process`));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let modifiedCount = 0;
|
|
54
|
+
|
|
55
|
+
// Process each HTML file
|
|
56
|
+
for (const filePath of htmlFiles) {
|
|
57
|
+
try {
|
|
58
|
+
let content = await fs.readFile(filePath, 'utf8');
|
|
59
|
+
|
|
60
|
+
// Skip if already installed
|
|
61
|
+
if (content.includes('SCOPE_CONFIG') || content.includes('cdn.scopeai.dev/sdk.js')) {
|
|
62
|
+
if (debug) {
|
|
63
|
+
console.log(chalk.gray(`Skipping ${path.relative(buildDir, filePath)} (already installed)`));
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Create the injection script
|
|
69
|
+
const sdkUrl = getSDKUrl(options);
|
|
70
|
+
const injectionScript = createInjectionScript({
|
|
71
|
+
apiKey,
|
|
72
|
+
endpoint,
|
|
73
|
+
trackApiCalls,
|
|
74
|
+
sdkUrl
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Find the best injection point
|
|
78
|
+
const injectionPoint = findInjectionPoint(content);
|
|
79
|
+
|
|
80
|
+
if (!injectionPoint) {
|
|
81
|
+
console.warn(chalk.yellow(`Warning: Could not find </body> or </html> tag in ${path.relative(buildDir, filePath)}`));
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Inject the script
|
|
86
|
+
content = content.slice(0, injectionPoint.index) +
|
|
87
|
+
injectionScript +
|
|
88
|
+
content.slice(injectionPoint.index);
|
|
89
|
+
|
|
90
|
+
// Write the modified content back
|
|
91
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
92
|
+
modifiedCount++;
|
|
93
|
+
|
|
94
|
+
if (debug) {
|
|
95
|
+
console.log(chalk.gray(`✓ Modified ${path.relative(buildDir, filePath)}`));
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error(chalk.red(`Error processing ${filePath}:`), error.message);
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (modifiedCount === 0) {
|
|
104
|
+
console.log(chalk.yellow('⚠️ No files were modified. Scope Analytics may already be installed.'));
|
|
105
|
+
} else {
|
|
106
|
+
console.log(chalk.green(`✓ Modified ${modifiedCount} HTML file${modifiedCount === 1 ? '' : 's'}`));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Copy the SDK file if we're using a local version (for development)
|
|
110
|
+
if (process.env.SCOPE_DEV_MODE === 'true') {
|
|
111
|
+
await copyLocalSDK(buildDir);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function createInjectionScript({ apiKey, endpoint, trackApiCalls, sdkUrl }) {
|
|
116
|
+
// For the one-line install, we'll pass the public key as a URL parameter
|
|
117
|
+
const sdkUrlWithKey = `${sdkUrl}${sdkUrl.includes('?') ? '&' : '?'}token=${apiKey}`;
|
|
118
|
+
|
|
119
|
+
return `
|
|
120
|
+
<!-- Scope Analytics -->
|
|
121
|
+
<script src="${sdkUrlWithKey}" async></script>
|
|
122
|
+
<!-- End Scope Analytics -->
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function findInjectionPoint(html) {
|
|
127
|
+
// Try to find </body> first
|
|
128
|
+
const bodyMatch = html.match(/<\/body>/i);
|
|
129
|
+
if (bodyMatch) {
|
|
130
|
+
return {
|
|
131
|
+
index: bodyMatch.index,
|
|
132
|
+
tag: '</body>'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Fallback to </html>
|
|
137
|
+
const htmlMatch = html.match(/<\/html>/i);
|
|
138
|
+
if (htmlMatch) {
|
|
139
|
+
return {
|
|
140
|
+
index: htmlMatch.index,
|
|
141
|
+
tag: '</html>'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function copyLocalSDK(buildDir) {
|
|
149
|
+
try {
|
|
150
|
+
const sdkSource = path.join(process.cwd(), '../../sdk-new/core/dist/scope-sdk.js');
|
|
151
|
+
const sdkDest = path.join(buildDir, 'scope-sdk.js');
|
|
152
|
+
|
|
153
|
+
await fs.copyFile(sdkSource, sdkDest);
|
|
154
|
+
console.log(chalk.gray('✓ Copied local SDK for development'));
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.warn(chalk.yellow('Could not copy local SDK:', error.message));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export async function detectPlatform() {
|
|
5
|
+
const checks = [
|
|
6
|
+
{ file: 'vercel.json', platform: 'vercel' },
|
|
7
|
+
{ file: 'netlify.toml', platform: 'netlify' },
|
|
8
|
+
{ file: 'render.yaml', platform: 'render' },
|
|
9
|
+
{ file: 'railway.json', platform: 'railway' },
|
|
10
|
+
{ file: '.dockerignore', platform: 'docker' },
|
|
11
|
+
{ file: 'app.json', platform: 'heroku' }
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
// Check for platform-specific files
|
|
15
|
+
for (const check of checks) {
|
|
16
|
+
try {
|
|
17
|
+
await fs.access(check.file);
|
|
18
|
+
return check.platform;
|
|
19
|
+
} catch {
|
|
20
|
+
// File doesn't exist, continue
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check package.json for framework clues
|
|
25
|
+
try {
|
|
26
|
+
const packageJson = JSON.parse(await fs.readFile('package.json', 'utf8'));
|
|
27
|
+
|
|
28
|
+
if (packageJson.dependencies) {
|
|
29
|
+
if (packageJson.dependencies.next) return 'nextjs';
|
|
30
|
+
if (packageJson.dependencies.gatsby) return 'gatsby';
|
|
31
|
+
if (packageJson.dependencies.nuxt) return 'nuxt';
|
|
32
|
+
if (packageJson.dependencies['@angular/core']) return 'angular';
|
|
33
|
+
if (packageJson.dependencies.vue) return 'vue';
|
|
34
|
+
if (packageJson.dependencies.react) return 'react';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check scripts for build tools
|
|
38
|
+
if (packageJson.scripts) {
|
|
39
|
+
if (packageJson.scripts.build?.includes('vite')) return 'vite';
|
|
40
|
+
if (packageJson.scripts.build?.includes('webpack')) return 'webpack';
|
|
41
|
+
if (packageJson.scripts.build?.includes('parcel')) return 'parcel';
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// No package.json or error reading it
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return 'generic';
|
|
48
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export function getInstallInstructions(platform, apiKey) {
|
|
2
|
+
const instructions = {
|
|
3
|
+
vercel: {
|
|
4
|
+
name: 'Vercel',
|
|
5
|
+
steps: [
|
|
6
|
+
'1. Add SCOPE_PUBLIC_KEY to your environment variables in Vercel dashboard',
|
|
7
|
+
'2. Update your build command in vercel.json or dashboard:',
|
|
8
|
+
' "buildCommand": "npm run build && npx @scope-analytics/browser install"',
|
|
9
|
+
'3. Deploy your application'
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
netlify: {
|
|
13
|
+
name: 'Netlify',
|
|
14
|
+
steps: [
|
|
15
|
+
'1. Add SCOPE_PUBLIC_KEY to your environment variables in Netlify dashboard',
|
|
16
|
+
'2. Update your netlify.toml:',
|
|
17
|
+
' [build]',
|
|
18
|
+
' command = "npm run build && npx @scope-analytics/browser install"',
|
|
19
|
+
'3. Deploy your application'
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
render: {
|
|
23
|
+
name: 'Render',
|
|
24
|
+
steps: [
|
|
25
|
+
'1. Add SCOPE_PUBLIC_KEY to your environment variables in Render dashboard',
|
|
26
|
+
'2. Update your build command:',
|
|
27
|
+
' Build Command: npm run build && npx @scope-analytics/browser install',
|
|
28
|
+
'3. Deploy your application'
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
railway: {
|
|
32
|
+
name: 'Railway',
|
|
33
|
+
steps: [
|
|
34
|
+
'1. Add SCOPE_PUBLIC_KEY to your environment variables in Railway dashboard',
|
|
35
|
+
'2. Update your build command in railway.json:',
|
|
36
|
+
' "build": "npm run build && npx @scope-analytics/browser install"',
|
|
37
|
+
'3. Deploy your application'
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
nextjs: {
|
|
41
|
+
name: 'Next.js',
|
|
42
|
+
steps: [
|
|
43
|
+
'1. Add SCOPE_PUBLIC_KEY to your .env.local or deployment environment',
|
|
44
|
+
'2. Update your package.json scripts:',
|
|
45
|
+
' "build": "next build && npx @scope-analytics/browser install --dir .next"',
|
|
46
|
+
'3. Deploy as usual'
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
vite: {
|
|
50
|
+
name: 'Vite',
|
|
51
|
+
steps: [
|
|
52
|
+
'1. Add SCOPE_PUBLIC_KEY to your environment',
|
|
53
|
+
'2. Update your package.json scripts:',
|
|
54
|
+
' "build": "vite build && npx @scope-analytics/browser install"',
|
|
55
|
+
'3. Deploy the dist folder'
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
generic: {
|
|
59
|
+
name: 'Generic Setup',
|
|
60
|
+
steps: [
|
|
61
|
+
'1. Set your public key:',
|
|
62
|
+
` export SCOPE_PUBLIC_KEY="${apiKey || 'your-public-key'}"`,
|
|
63
|
+
'2. After building your app, run:',
|
|
64
|
+
' npx @scope-analytics/browser install',
|
|
65
|
+
'3. Deploy your built files'
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return instructions[platform] || instructions.generic;
|
|
71
|
+
}
|
package/src/uninstall.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
export async function uninstall(options) {
|
|
7
|
+
const { buildDir = 'dist' } = options;
|
|
8
|
+
|
|
9
|
+
// Verify build directory exists
|
|
10
|
+
try {
|
|
11
|
+
await fs.access(buildDir);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new Error(`Build directory "${buildDir}" does not exist.`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Find all HTML files
|
|
17
|
+
const htmlFiles = await glob(`${buildDir}/**/*.html`);
|
|
18
|
+
|
|
19
|
+
if (htmlFiles.length === 0) {
|
|
20
|
+
console.log(chalk.yellow('No HTML files found to uninstall from'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let modifiedCount = 0;
|
|
25
|
+
|
|
26
|
+
// Process each HTML file
|
|
27
|
+
for (const filePath of htmlFiles) {
|
|
28
|
+
try {
|
|
29
|
+
let content = await fs.readFile(filePath, 'utf8');
|
|
30
|
+
const originalLength = content.length;
|
|
31
|
+
|
|
32
|
+
// Remove Scope Analytics scripts
|
|
33
|
+
content = removeScopeAnalytics(content);
|
|
34
|
+
|
|
35
|
+
// Only write if content changed
|
|
36
|
+
if (content.length !== originalLength) {
|
|
37
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
38
|
+
modifiedCount++;
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(chalk.red(`Error processing ${filePath}:`), error.message);
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(chalk.green(`✓ Removed from ${modifiedCount} HTML file${modifiedCount === 1 ? '' : 's'}`));
|
|
47
|
+
|
|
48
|
+
// Remove local SDK file if exists
|
|
49
|
+
try {
|
|
50
|
+
const localSDK = path.join(buildDir, 'scope-sdk.js');
|
|
51
|
+
await fs.unlink(localSDK);
|
|
52
|
+
console.log(chalk.gray('✓ Removed local SDK file'));
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// Ignore if file doesn't exist
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function removeScopeAnalytics(html) {
|
|
59
|
+
// Remove the entire Scope Analytics section
|
|
60
|
+
const scopePattern = /<!-- Scope Analytics -->[\s\S]*?<!-- End Scope Analytics -->\n?/g;
|
|
61
|
+
html = html.replace(scopePattern, '');
|
|
62
|
+
|
|
63
|
+
// Also remove any standalone references (in case the comments were removed)
|
|
64
|
+
html = html.replace(/<script[^>]*>[\s\S]*?SCOPE_CONFIG[\s\S]*?<\/script>\n?/g, '');
|
|
65
|
+
html = html.replace(/<script[^>]*src="[^"]*cdn\.scopeai\.dev\/sdk\.js"[^>]*><\/script>\n?/g, '');
|
|
66
|
+
|
|
67
|
+
return html;
|
|
68
|
+
}
|
package/src/validate.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
|
|
5
|
+
export async function validate(options) {
|
|
6
|
+
const { buildDir = 'dist' } = options;
|
|
7
|
+
|
|
8
|
+
// Verify build directory exists
|
|
9
|
+
try {
|
|
10
|
+
await fs.access(buildDir);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
throw new Error(`Build directory "${buildDir}" does not exist.`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Find all HTML files
|
|
16
|
+
const htmlFiles = await glob(`${buildDir}/**/*.html`);
|
|
17
|
+
|
|
18
|
+
if (htmlFiles.length === 0) {
|
|
19
|
+
return {
|
|
20
|
+
valid: false,
|
|
21
|
+
files: [],
|
|
22
|
+
message: 'No HTML files found in build directory'
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const installedFiles = [];
|
|
27
|
+
|
|
28
|
+
// Check each HTML file
|
|
29
|
+
for (const filePath of htmlFiles) {
|
|
30
|
+
try {
|
|
31
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
32
|
+
|
|
33
|
+
// Check if Scope Analytics is installed
|
|
34
|
+
if (content.includes('SCOPE_CONFIG') && content.includes('cdn.scopeai.dev/sdk.js')) {
|
|
35
|
+
installedFiles.push(path.relative(buildDir, filePath));
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(`Error reading ${filePath}:`, error.message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
valid: installedFiles.length > 0,
|
|
44
|
+
files: installedFiles,
|
|
45
|
+
totalFiles: htmlFiles.length,
|
|
46
|
+
coverage: (installedFiles.length / htmlFiles.length) * 100
|
|
47
|
+
};
|
|
48
|
+
}
|