@mahe_pkm/buzl-capi 0.1.2
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/Buzl_GoogleAppsScript_Template.gs +948 -0
- package/CHANGELOG.md +83 -0
- package/LICENSE +21 -0
- package/README.md +253 -0
- package/assets/js/buzl-tracking.js +390 -0
- package/bin/cli.js +155 -0
- package/package.json +54 -0
- package/src/cli/terminal.js +388 -0
- package/src/core/injector.js +321 -0
- package/src/core/rollback.js +281 -0
- package/src/core/scanner.js +506 -0
- package/src/core/tester.js +357 -0
- package/src/gui/public/app.js +942 -0
- package/src/gui/public/index.html +634 -0
- package/src/gui/public/style.css +1130 -0
- package/src/gui/server.js +232 -0
- package/src/index.js +47 -0
- package/src/templates/GoogleAppsScript.gs +948 -0
- package/src/templates/buzl-tracking.js +475 -0
- package/src/templates/gtm-meta-snippets.js +149 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight Local Web GUI Server
|
|
3
|
+
* Zero dependencies — built purely with Node.js built-ins
|
|
4
|
+
*/
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { exec } = require('child_process');
|
|
9
|
+
const { scanProject } = require('../core/scanner');
|
|
10
|
+
const { applyInjection, removeTracking, removeService } = require('../core/injector');
|
|
11
|
+
const { runVerification, testDispatch, testIndividualForm } = require('../core/tester');
|
|
12
|
+
const { restoreBackup, restoreLatestBackup, deleteBackup, listBackups, manualBackup } = require('../core/rollback');
|
|
13
|
+
|
|
14
|
+
const PUBLIC_DIR = path.join(__dirname, 'public');
|
|
15
|
+
|
|
16
|
+
const MIME_TYPES = {
|
|
17
|
+
'.html': 'text/html',
|
|
18
|
+
'.css': 'text/css',
|
|
19
|
+
'.js': 'application/javascript',
|
|
20
|
+
'.json': 'application/json',
|
|
21
|
+
'.svg': 'image/svg+xml'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function openBrowser(url) {
|
|
25
|
+
const start = process.platform === 'darwin' ? 'open' :
|
|
26
|
+
process.platform === 'win32' ? 'start ""' : 'xdg-open';
|
|
27
|
+
exec(`${start} "${url}"`, (err) => {
|
|
28
|
+
if (err) {
|
|
29
|
+
console.log(`Could not automatically open browser. Please open: ${url}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseJsonBody(req) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
let body = '';
|
|
37
|
+
req.on('data', chunk => { body += chunk.toString(); });
|
|
38
|
+
req.on('end', () => {
|
|
39
|
+
try {
|
|
40
|
+
resolve(body ? JSON.parse(body) : {});
|
|
41
|
+
} catch (e) {
|
|
42
|
+
reject(e);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function startGuiServer(rootDir, port = 3333) {
|
|
49
|
+
const server = http.createServer(async (req, res) => {
|
|
50
|
+
const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
51
|
+
const pathname = parsedUrl.pathname;
|
|
52
|
+
|
|
53
|
+
// Set CORS headers
|
|
54
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
55
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
56
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
57
|
+
|
|
58
|
+
if (req.method === 'OPTIONS') {
|
|
59
|
+
res.writeHead(204);
|
|
60
|
+
res.end();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// 1. API: Scan project
|
|
66
|
+
if (pathname === '/api/scan' && req.method === 'GET') {
|
|
67
|
+
const scan = scanProject(rootDir);
|
|
68
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
69
|
+
res.end(JSON.stringify(scan));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 1.5. API: Live State of Site
|
|
74
|
+
if (pathname === '/api/live-state' && req.method === 'GET') {
|
|
75
|
+
const scan = scanProject(rootDir);
|
|
76
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
77
|
+
res.end(JSON.stringify(scan.liveState));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 2. API: Get Apps Script Code
|
|
82
|
+
if (pathname === '/api/apps-script' && req.method === 'GET') {
|
|
83
|
+
const gsPath = path.join(__dirname, '..', 'templates', 'GoogleAppsScript.gs');
|
|
84
|
+
const code = fs.readFileSync(gsPath, 'utf8');
|
|
85
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
86
|
+
res.end(JSON.stringify({ code }));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 3. API: Inject & Run Self-Tests
|
|
91
|
+
if (pathname === '/api/inject' && req.method === 'POST') {
|
|
92
|
+
const config = await parseJsonBody(req);
|
|
93
|
+
const scan = scanProject(rootDir);
|
|
94
|
+
const htmlFilePaths = scan.files.map(f => f.filePath);
|
|
95
|
+
|
|
96
|
+
if (htmlFilePaths.length === 0) {
|
|
97
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
98
|
+
res.end(JSON.stringify({ success: false, message: 'No HTML files found to inject.' }));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const injectResult = applyInjection(rootDir, htmlFilePaths, config);
|
|
103
|
+
const testReport = await runVerification(rootDir, htmlFilePaths, config);
|
|
104
|
+
|
|
105
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
106
|
+
res.end(JSON.stringify({
|
|
107
|
+
success: true,
|
|
108
|
+
injectResult,
|
|
109
|
+
testReport
|
|
110
|
+
}));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 4. API: Clean Uninstall / Remove All Tracking
|
|
115
|
+
if (pathname === '/api/remove-tracking' && req.method === 'POST') {
|
|
116
|
+
const scan = scanProject(rootDir);
|
|
117
|
+
const htmlFilePaths = scan.files.map(f => f.filePath);
|
|
118
|
+
const result = removeTracking(rootDir, htmlFilePaths);
|
|
119
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
120
|
+
res.end(JSON.stringify(result));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 4.5. API: Selectively Remove Single Tracking Service
|
|
125
|
+
if (pathname === '/api/remove-service' && req.method === 'POST') {
|
|
126
|
+
const body = await parseJsonBody(req);
|
|
127
|
+
const result = removeService(rootDir, body.service || '');
|
|
128
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
129
|
+
res.end(JSON.stringify(result));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 5. API: List Named Backups
|
|
134
|
+
if (pathname === '/api/backups' && req.method === 'GET') {
|
|
135
|
+
const backups = listBackups(rootDir);
|
|
136
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
137
|
+
res.end(JSON.stringify({ backups }));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 6. API: Create Named Backup
|
|
142
|
+
if (pathname === '/api/backup' && req.method === 'POST') {
|
|
143
|
+
const body = await parseJsonBody(req);
|
|
144
|
+
const backupResult = manualBackup(rootDir, body.name || '');
|
|
145
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
146
|
+
res.end(JSON.stringify(backupResult));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 7. API: Restore Backup (Specific or Latest)
|
|
151
|
+
if (pathname === '/api/restore' && req.method === 'POST') {
|
|
152
|
+
const body = await parseJsonBody(req);
|
|
153
|
+
const result = restoreBackup(rootDir, body.backupDirName || 'latest');
|
|
154
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
155
|
+
res.end(JSON.stringify(result));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 8. API: Delete Specific Backup
|
|
160
|
+
if (pathname === '/api/delete-backup' && req.method === 'POST') {
|
|
161
|
+
const body = await parseJsonBody(req);
|
|
162
|
+
const result = deleteBackup(rootDir, body.backupDirName);
|
|
163
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
164
|
+
res.end(JSON.stringify(result));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 9. API: Test Individual Form Submission
|
|
169
|
+
if (pathname === '/api/test-form' && req.method === 'POST') {
|
|
170
|
+
const body = await parseJsonBody(req);
|
|
171
|
+
const scan = scanProject(rootDir);
|
|
172
|
+
const existingConfig = (scan.files.find(f => f.existingConfig) || {}).existingConfig || {};
|
|
173
|
+
const mergedConfig = Object.assign({}, existingConfig, body.config || {});
|
|
174
|
+
const results = await testIndividualForm(rootDir, body.formId, body.formFields || {}, mergedConfig, { pagePath: body.pagePath });
|
|
175
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
176
|
+
res.end(JSON.stringify({ success: true, results }));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 10. API: Live Test Lead Submit (Global)
|
|
181
|
+
if (pathname === '/api/test-submit' && req.method === 'POST') {
|
|
182
|
+
const body = await parseJsonBody(req);
|
|
183
|
+
const scan = scanProject(rootDir);
|
|
184
|
+
const existingConfig = (scan.files.find(f => f.existingConfig) || {}).existingConfig || {};
|
|
185
|
+
const mergedConfig = Object.assign({}, existingConfig, body.config || {});
|
|
186
|
+
const results = await testDispatch(rootDir, mergedConfig, body.lead || {});
|
|
187
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
188
|
+
res.end(JSON.stringify({ success: true, results }));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 5. Static Files
|
|
193
|
+
let filePath = path.join(PUBLIC_DIR, pathname === '/' ? 'index.html' : pathname);
|
|
194
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
195
|
+
const ext = path.extname(filePath);
|
|
196
|
+
const contentType = MIME_TYPES[ext] || 'text/plain';
|
|
197
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
198
|
+
fs.createReadStream(filePath).pipe(res);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Not found
|
|
203
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
204
|
+
res.end('Not Found');
|
|
205
|
+
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.error('[GUI Server Error]', err);
|
|
208
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
209
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
server.listen(port, () => {
|
|
214
|
+
const localUrl = `http://localhost:${port}`;
|
|
215
|
+
console.log(`\n⚡ Buzl Tracker GUI is running at: ${localUrl}`);
|
|
216
|
+
console.log('Press Ctrl+C to stop the server.\n');
|
|
217
|
+
openBrowser(localUrl);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
server.on('error', (err) => {
|
|
221
|
+
if (err.code === 'EADDRINUSE') {
|
|
222
|
+
console.log(`Port ${port} is in use, trying ${port + 1}...`);
|
|
223
|
+
startGuiServer(rootDir, port + 1);
|
|
224
|
+
} else {
|
|
225
|
+
console.error('Server error:', err);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return server;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = { startGuiServer };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* BUZL TRACKER & CAPI SDK
|
|
4
|
+
* Programmatic Node.js API for automated analytics & CRM injection
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2026 Buzl Digital Solutions
|
|
7
|
+
* Licensed under the MIT License
|
|
8
|
+
* ============================================================================
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { scanProject, findHtmlFiles, analyzeHtmlFile } = require('./core/scanner');
|
|
12
|
+
const { injectHtml, applyInjection, removeTracking, removeService } = require('./core/injector');
|
|
13
|
+
const { runVerification, pingUrl, testDispatch, testIndividualForm } = require('./core/tester');
|
|
14
|
+
const { createBackup, restoreBackup, restoreLatestBackup, deleteBackup, listBackups, manualBackup } = require('./core/rollback');
|
|
15
|
+
const { startGuiServer } = require('./gui/server');
|
|
16
|
+
const { runTerminalWizard } = require('./cli/terminal');
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
// Scanner module: multi-page HTML parsing, live state detection, and form discovery
|
|
20
|
+
scanProject,
|
|
21
|
+
findHtmlFiles,
|
|
22
|
+
analyzeHtmlFile,
|
|
23
|
+
|
|
24
|
+
// Injector module: AST-safe HTML injection and selective service removal
|
|
25
|
+
injectHtml,
|
|
26
|
+
applyInjection,
|
|
27
|
+
removeTracking,
|
|
28
|
+
removeService,
|
|
29
|
+
|
|
30
|
+
// Tester module: tag verification, dispatch testing, and webhook reachability pings
|
|
31
|
+
runVerification,
|
|
32
|
+
pingUrl,
|
|
33
|
+
testDispatch,
|
|
34
|
+
testIndividualForm,
|
|
35
|
+
|
|
36
|
+
// Rollback module: snapshot creation, named backups, and zero-risk point-in-time restores
|
|
37
|
+
createBackup,
|
|
38
|
+
restoreBackup,
|
|
39
|
+
restoreLatestBackup,
|
|
40
|
+
deleteBackup,
|
|
41
|
+
listBackups,
|
|
42
|
+
manualBackup,
|
|
43
|
+
|
|
44
|
+
// User interfaces: web GUI server and terminal wizard
|
|
45
|
+
startGuiServer,
|
|
46
|
+
runTerminalWizard
|
|
47
|
+
};
|