@gurulu/cli 0.4.7 → 1.0.1
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/LICENSE +92 -0
- package/README.md +35 -106
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +25751 -0
- package/dist/commands/auth.d.ts +23 -20
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/doctor.d.ts +20 -6
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/init.d.ts +33 -11
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/pull.d.ts +13 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/push.d.ts +40 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/validate.d.ts +36 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25326 -876
- package/dist/lib/api.d.ts +139 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/codegen.d.ts +4 -0
- package/dist/lib/codegen.d.ts.map +1 -0
- package/dist/lib/config.d.ts +43 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/detect.d.ts +27 -0
- package/dist/lib/detect.d.ts.map +1 -0
- package/dist/lib/detect.js +106 -0
- package/dist/lib/exec-install.d.ts +21 -0
- package/dist/lib/exec-install.d.ts.map +1 -0
- package/dist/lib/install-plan.d.ts +25 -0
- package/dist/lib/install-plan.d.ts.map +1 -0
- package/dist/lib/install-plan.js +161 -0
- package/package.json +51 -20
- package/bin/gurulu.js +0 -2
- package/dist/api-client.d.ts +0 -33
- package/dist/api-client.js +0 -175
- package/dist/commands/add-server.d.ts +0 -9
- package/dist/commands/add-server.js +0 -162
- package/dist/commands/alerts.d.ts +0 -27
- package/dist/commands/alerts.js +0 -309
- package/dist/commands/api-keys.d.ts +0 -20
- package/dist/commands/api-keys.js +0 -130
- package/dist/commands/attribution.d.ts +0 -22
- package/dist/commands/attribution.js +0 -111
- package/dist/commands/audiences.d.ts +0 -23
- package/dist/commands/audiences.js +0 -243
- package/dist/commands/audit.d.ts +0 -20
- package/dist/commands/audit.js +0 -130
- package/dist/commands/auth.js +0 -249
- package/dist/commands/chat.d.ts +0 -19
- package/dist/commands/chat.js +0 -118
- package/dist/commands/config.d.ts +0 -10
- package/dist/commands/config.js +0 -92
- package/dist/commands/consent.d.ts +0 -27
- package/dist/commands/consent.js +0 -233
- package/dist/commands/conversion-paths.d.ts +0 -19
- package/dist/commands/conversion-paths.js +0 -55
- package/dist/commands/db.d.ts +0 -25
- package/dist/commands/db.js +0 -330
- package/dist/commands/destinations.d.ts +0 -20
- package/dist/commands/destinations.js +0 -191
- package/dist/commands/doctor.js +0 -360
- package/dist/commands/errors.d.ts +0 -27
- package/dist/commands/errors.js +0 -121
- package/dist/commands/events.d.ts +0 -33
- package/dist/commands/events.js +0 -371
- package/dist/commands/experiments.d.ts +0 -22
- package/dist/commands/experiments.js +0 -264
- package/dist/commands/funnels.d.ts +0 -17
- package/dist/commands/funnels.js +0 -203
- package/dist/commands/goals.d.ts +0 -18
- package/dist/commands/goals.js +0 -214
- package/dist/commands/heatmap.d.ts +0 -27
- package/dist/commands/heatmap.js +0 -112
- package/dist/commands/identity.d.ts +0 -29
- package/dist/commands/identity.js +0 -328
- package/dist/commands/init.js +0 -215
- package/dist/commands/insights.d.ts +0 -10
- package/dist/commands/insights.js +0 -77
- package/dist/commands/install.d.ts +0 -259
- package/dist/commands/install.js +0 -1590
- package/dist/commands/login.d.ts +0 -20
- package/dist/commands/login.js +0 -170
- package/dist/commands/logout.d.ts +0 -10
- package/dist/commands/logout.js +0 -41
- package/dist/commands/playground.d.ts +0 -11
- package/dist/commands/playground.js +0 -47
- package/dist/commands/releases.d.ts +0 -17
- package/dist/commands/releases.js +0 -54
- package/dist/commands/replay.d.ts +0 -18
- package/dist/commands/replay.js +0 -64
- package/dist/commands/secrets.d.ts +0 -19
- package/dist/commands/secrets.js +0 -145
- package/dist/commands/setup.d.ts +0 -21
- package/dist/commands/setup.js +0 -67
- package/dist/commands/sites.d.ts +0 -18
- package/dist/commands/sites.js +0 -139
- package/dist/commands/skad.d.ts +0 -18
- package/dist/commands/skad.js +0 -53
- package/dist/commands/sourcemap.d.ts +0 -33
- package/dist/commands/sourcemap.js +0 -204
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.js +0 -136
- package/dist/commands/upgrade.d.ts +0 -21
- package/dist/commands/upgrade.js +0 -183
- package/dist/commands/warehouse.d.ts +0 -20
- package/dist/commands/warehouse.js +0 -65
- package/dist/commands/warehouses.d.ts +0 -17
- package/dist/commands/warehouses.js +0 -182
- package/dist/commands/watch.d.ts +0 -45
- package/dist/commands/watch.js +0 -258
- package/dist/commands/whoami.d.ts +0 -9
- package/dist/commands/whoami.js +0 -50
- package/dist/config.d.ts +0 -75
- package/dist/config.js +0 -329
- package/dist/frameworks/detect.d.ts +0 -8
- package/dist/frameworks/detect.js +0 -458
- package/dist/install-intent-proposal.d.ts +0 -99
- package/dist/install-intent-proposal.js +0 -202
- package/dist/utils/api.d.ts +0 -20
- package/dist/utils/api.js +0 -47
- package/dist/utils/config.d.ts +0 -13
- package/dist/utils/config.js +0 -30
- package/dist/utils/confirm.d.ts +0 -17
- package/dist/utils/confirm.js +0 -40
- package/dist/utils/dry-run.d.ts +0 -20
- package/dist/utils/dry-run.js +0 -67
- package/dist/utils/from-file.d.ts +0 -9
- package/dist/utils/from-file.js +0 -72
- package/dist/utils/redact.d.ts +0 -14
- package/dist/utils/redact.js +0 -48
- package/dist/utils/ui.d.ts +0 -14
- package/dist/utils/ui.js +0 -59
- package/scripts/.gitkeep +0 -0
- package/scripts/README-gurulu-agentic-install.md +0 -114
- package/scripts/README-gurulu-scan.md +0 -98
- package/scripts/audit-cli-scopes.mjs +0 -204
- package/scripts/backfill-tenant-id.mjs +0 -172
- package/scripts/backfill-tenant-links.ts +0 -252
- package/scripts/backup-clickhouse.sh +0 -27
- package/scripts/backup-postgres.sh +0 -19
- package/scripts/bootstrap-runtime-schema.mjs +0 -87
- package/scripts/bootstrap-stripe.mjs +0 -158
- package/scripts/gurulu-agentic-install.lib.cjs +0 -762
- package/scripts/gurulu-agentic-install.mjs +0 -623
- package/scripts/gurulu-scan.lib.cjs +0 -1509
- package/scripts/gurulu-scan.mjs +0 -91
- package/scripts/gurulu-verify-install.lib.cjs +0 -334
- package/scripts/gurulu-verify-install.mjs +0 -59
- package/scripts/init-ssl.sh +0 -26
- package/scripts/migrate-flow-graph-enums.sh +0 -86
- package/scripts/monitor-disk.sh +0 -24
- package/scripts/patches/astro.patch.cjs +0 -74
- package/scripts/patches/auto-instrument/ast-helper.cjs +0 -480
- package/scripts/patches/auto-instrument/astro.cjs +0 -273
- package/scripts/patches/auto-instrument/express.cjs +0 -383
- package/scripts/patches/auto-instrument/fastify.cjs +0 -262
- package/scripts/patches/auto-instrument/hono.cjs +0 -392
- package/scripts/patches/auto-instrument/index.cjs +0 -80
- package/scripts/patches/auto-instrument/nestjs.cjs +0 -286
- package/scripts/patches/auto-instrument/nextjs-app-router.cjs +0 -345
- package/scripts/patches/auto-instrument/nextjs-pages.cjs +0 -361
- package/scripts/patches/auto-instrument/remix.cjs +0 -168
- package/scripts/patches/auto-instrument/sdk-helper-map.cjs +0 -241
- package/scripts/patches/auto-instrument/singleton-helper.cjs +0 -193
- package/scripts/patches/auto-instrument/sveltekit.cjs +0 -161
- package/scripts/patches/auto-instrument/vite-react.cjs +0 -37
- package/scripts/patches/auto-instrument/vue.cjs +0 -196
- package/scripts/patches/express.patch.cjs +0 -99
- package/scripts/patches/fastify.patch.cjs +0 -108
- package/scripts/patches/index.cjs +0 -300
- package/scripts/patches/nestjs.patch.cjs +0 -112
- package/scripts/patches/nextjs-app-router.patch.cjs +0 -97
- package/scripts/patches/nextjs-pages.patch.cjs +0 -97
- package/scripts/patches/remix.patch.cjs +0 -75
- package/scripts/patches/sveltekit.patch.cjs +0 -72
- package/scripts/patches/vite-react.patch.cjs +0 -73
- package/scripts/patches/vue.patch.cjs +0 -82
- package/scripts/renew-ssl.sh +0 -14
- package/scripts/resolve-migration.sh +0 -23
- package/scripts/seed-cli-dev-keys.mjs +0 -130
- package/scripts/seed-test-data.mjs +0 -391
- package/scripts/spike-browserless.ts +0 -65
- package/scripts/tenant-pivot-consistency-check.mjs +0 -205
- package/scripts/tenant-pivot-phase-3-cleanup.lib.cjs +0 -258
- package/scripts/tenant-pivot-phase-3-cleanup.mjs +0 -98
- package/scripts/test-identity-resolution.ts +0 -804
- package/scripts/validate-gurulu-schemas.mjs +0 -79
package/scripts/gurulu-scan.mjs
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// gurulu-scan.mjs — standalone repo scanner CLI (Phase 13 B2).
|
|
3
|
-
//
|
|
4
|
-
// Detection logic lives in `scripts/gurulu-scan.lib.cjs` so it can be reused
|
|
5
|
-
// by ts-jest tests via `require()`. This wrapper exposes the same API as an
|
|
6
|
-
// ESM module and, when invoked directly, runs the CLI.
|
|
7
|
-
//
|
|
8
|
-
// Usage:
|
|
9
|
-
// node scripts/gurulu-scan.mjs <repo-path> [--output <file>] [--quiet]
|
|
10
|
-
|
|
11
|
-
import { createRequire } from 'node:module';
|
|
12
|
-
import { promises as fs } from 'node:fs';
|
|
13
|
-
import path from 'node:path';
|
|
14
|
-
import { fileURLToPath } from 'node:url';
|
|
15
|
-
|
|
16
|
-
const require = createRequire(import.meta.url);
|
|
17
|
-
const lib = require('./gurulu-scan.lib.cjs');
|
|
18
|
-
|
|
19
|
-
export const SCAN_VERSION = lib.SCAN_VERSION;
|
|
20
|
-
export const detectFramework = lib.detectFramework;
|
|
21
|
-
export const detectORM = lib.detectORM;
|
|
22
|
-
export const detectAuth = lib.detectAuth;
|
|
23
|
-
export const detectRoutes = lib.detectRoutes;
|
|
24
|
-
export const detectMutations = lib.detectMutations;
|
|
25
|
-
export const scanRepo = lib.scanRepo;
|
|
26
|
-
// Phase 18.6 A1
|
|
27
|
-
export const extractSchemaTables = lib.extractSchemaTables;
|
|
28
|
-
export const extractDependencyFingerprint = lib.extractDependencyFingerprint;
|
|
29
|
-
export const extractUiStrings = lib.extractUiStrings;
|
|
30
|
-
export const extractI18nKeys = lib.extractI18nKeys;
|
|
31
|
-
export const extractFolderStructureHints = lib.extractFolderStructureHints;
|
|
32
|
-
export const extractEnvVarHints = lib.extractEnvVarHints;
|
|
33
|
-
export const extractCommentBlockHints = lib.extractCommentBlockHints;
|
|
34
|
-
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// CLI entry
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
function parseArgs(argv) {
|
|
39
|
-
const args = { _: [], output: null, quiet: false };
|
|
40
|
-
for (let i = 0; i < argv.length; i++) {
|
|
41
|
-
const a = argv[i];
|
|
42
|
-
if (a === '--output' || a === '-o' || a === '--out') {
|
|
43
|
-
args.output = argv[++i];
|
|
44
|
-
} else if (a === '--quiet' || a === '-q') {
|
|
45
|
-
args.quiet = true;
|
|
46
|
-
} else if (a === '--help' || a === '-h') {
|
|
47
|
-
args.help = true;
|
|
48
|
-
} else {
|
|
49
|
-
args._.push(a);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return args;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function main() {
|
|
56
|
-
const args = parseArgs(process.argv.slice(2));
|
|
57
|
-
if (args.help || args._.length === 0) {
|
|
58
|
-
process.stdout.write(
|
|
59
|
-
'Usage: node scripts/gurulu-scan.mjs <repo-path> [--output <file>] [--quiet]\n',
|
|
60
|
-
);
|
|
61
|
-
process.exit(args.help ? 0 : 1);
|
|
62
|
-
}
|
|
63
|
-
const rootPath = args._[0];
|
|
64
|
-
const log = args.quiet ? () => {} : (m) => process.stderr.write(m + '\n');
|
|
65
|
-
const plan = await scanRepo(rootPath, { log });
|
|
66
|
-
const json = JSON.stringify(plan, null, 2);
|
|
67
|
-
if (args.output) {
|
|
68
|
-
await fs.writeFile(args.output, json + '\n', 'utf8');
|
|
69
|
-
if (!args.quiet) process.stderr.write(`[scan] wrote ${args.output}\n`);
|
|
70
|
-
} else {
|
|
71
|
-
process.stdout.write(json + '\n');
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const invokedDirectly =
|
|
76
|
-
typeof process !== 'undefined' &&
|
|
77
|
-
process.argv[1] &&
|
|
78
|
-
(() => {
|
|
79
|
-
try {
|
|
80
|
-
return fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
|
|
81
|
-
} catch {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
})();
|
|
85
|
-
|
|
86
|
-
if (invokedDirectly) {
|
|
87
|
-
main().catch((err) => {
|
|
88
|
-
process.stderr.write(`[scan] ERROR: ${err.stack || err.message || err}\n`);
|
|
89
|
-
process.exit(1);
|
|
90
|
-
});
|
|
91
|
-
}
|
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
// gurulu-verify-install.lib.cjs
|
|
2
|
-
//
|
|
3
|
-
// Pure-CJS library used by both the `.mjs` CLI wrapper and the ts-jest
|
|
4
|
-
// test suite. Contains the entire verification loop implementation so
|
|
5
|
-
// no jest runtime has to pull in ESM.
|
|
6
|
-
//
|
|
7
|
-
// See scripts/gurulu-verify-install.mjs for the CLI front-end.
|
|
8
|
-
|
|
9
|
-
'use strict';
|
|
10
|
-
|
|
11
|
-
const { createServer } = require('node:http');
|
|
12
|
-
const { spawn } = require('node:child_process');
|
|
13
|
-
const fs = require('node:fs');
|
|
14
|
-
const path = require('node:path');
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Argv parsing
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
function parseArgs(argv) {
|
|
21
|
-
const args = {
|
|
22
|
-
path: null,
|
|
23
|
-
framework: null,
|
|
24
|
-
siteId: null,
|
|
25
|
-
tenantId: null,
|
|
26
|
-
ingestUrl: null,
|
|
27
|
-
port: 3000,
|
|
28
|
-
timeout: 30000,
|
|
29
|
-
dryRun: false,
|
|
30
|
-
};
|
|
31
|
-
const positional = [];
|
|
32
|
-
for (let i = 0; i < argv.length; i++) {
|
|
33
|
-
const a = argv[i];
|
|
34
|
-
if (a === '--framework') args.framework = argv[++i];
|
|
35
|
-
else if (a === '--site-id') args.siteId = argv[++i];
|
|
36
|
-
else if (a === '--tenant-id') args.tenantId = argv[++i];
|
|
37
|
-
else if (a === '--ingest-url') args.ingestUrl = argv[++i];
|
|
38
|
-
else if (a === '--port') args.port = Number(argv[++i]);
|
|
39
|
-
else if (a === '--timeout') args.timeout = Number(argv[++i]);
|
|
40
|
-
else if (a === '--dry-run') args.dryRun = true;
|
|
41
|
-
else if (!a.startsWith('--')) positional.push(a);
|
|
42
|
-
}
|
|
43
|
-
if (positional.length > 0) args.path = positional[0];
|
|
44
|
-
return args;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// Package manager detection (inline, mirrors the CLI helper)
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
function detectPackageManager(repoRoot) {
|
|
52
|
-
if (fs.existsSync(path.join(repoRoot, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
53
|
-
if (fs.existsSync(path.join(repoRoot, 'yarn.lock'))) return 'yarn';
|
|
54
|
-
return 'npm';
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
// Dev command resolution
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
|
|
61
|
-
function resolveDevCommand(framework, repoRoot, pm = 'npm') {
|
|
62
|
-
const fw = (framework || '').toLowerCase();
|
|
63
|
-
if (fw.startsWith('next') || fw === 'nextjs' || fw === 'nextjs-app' || fw === 'nextjs-pages') {
|
|
64
|
-
return { cmd: pm, args: ['run', 'dev'] };
|
|
65
|
-
}
|
|
66
|
-
if (fw === 'vite' || fw === 'vite-react' || fw === 'vite-vue') {
|
|
67
|
-
return { cmd: pm, args: ['run', 'dev'] };
|
|
68
|
-
}
|
|
69
|
-
if (fw === 'express' || fw === 'node') {
|
|
70
|
-
let entry = 'index.js';
|
|
71
|
-
const pkgPath = path.join(repoRoot, 'package.json');
|
|
72
|
-
if (fs.existsSync(pkgPath)) {
|
|
73
|
-
try {
|
|
74
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
75
|
-
if (pkg.main) entry = pkg.main;
|
|
76
|
-
else if (pkg.scripts && pkg.scripts.start) {
|
|
77
|
-
const m = /node\s+([^\s]+)/.exec(pkg.scripts.start);
|
|
78
|
-
if (m) entry = m[1];
|
|
79
|
-
} else if (fs.existsSync(path.join(repoRoot, 'app.js'))) entry = 'app.js';
|
|
80
|
-
else if (fs.existsSync(path.join(repoRoot, 'server.js'))) entry = 'server.js';
|
|
81
|
-
} catch {
|
|
82
|
-
// ignore
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return { cmd: 'node', args: [entry] };
|
|
86
|
-
}
|
|
87
|
-
return { cmd: pm, args: ['run', 'dev'] };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function healthPathFor(framework) {
|
|
91
|
-
const fw = (framework || '').toLowerCase();
|
|
92
|
-
if (fw === 'express' || fw === 'node') return '/health';
|
|
93
|
-
return '/';
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// Capture server
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
|
|
100
|
-
function startCaptureServer() {
|
|
101
|
-
return new Promise((resolve, reject) => {
|
|
102
|
-
const events = [];
|
|
103
|
-
const server = createServer((req, res) => {
|
|
104
|
-
if (req.method === 'POST' && req.url && req.url.startsWith('/api/ingest/v1/collect')) {
|
|
105
|
-
let body = '';
|
|
106
|
-
req.on('data', (chunk) => (body += chunk.toString()));
|
|
107
|
-
req.on('end', () => {
|
|
108
|
-
let parsed = null;
|
|
109
|
-
try {
|
|
110
|
-
parsed = JSON.parse(body);
|
|
111
|
-
} catch {
|
|
112
|
-
parsed = { _raw: body };
|
|
113
|
-
}
|
|
114
|
-
if (Array.isArray(parsed)) {
|
|
115
|
-
for (const ev of parsed) events.push(ev);
|
|
116
|
-
} else if (parsed && Array.isArray(parsed.events)) {
|
|
117
|
-
for (const ev of parsed.events) events.push(ev);
|
|
118
|
-
} else if (parsed) {
|
|
119
|
-
events.push(parsed);
|
|
120
|
-
}
|
|
121
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
122
|
-
res.end(JSON.stringify({ ok: true }));
|
|
123
|
-
});
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
if (req.method === 'POST' && req.url && req.url.startsWith('/api/ingest/v1/health')) {
|
|
127
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
128
|
-
res.end(JSON.stringify({ ok: true }));
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
res.writeHead(404);
|
|
132
|
-
res.end();
|
|
133
|
-
});
|
|
134
|
-
server.on('error', reject);
|
|
135
|
-
server.listen(0, '127.0.0.1', () => {
|
|
136
|
-
const addr = server.address();
|
|
137
|
-
const port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
138
|
-
resolve({
|
|
139
|
-
url: `http://127.0.0.1:${port}`,
|
|
140
|
-
port,
|
|
141
|
-
events,
|
|
142
|
-
close: () => new Promise((r) => server.close(() => r())),
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ---------------------------------------------------------------------------
|
|
149
|
-
// Polling
|
|
150
|
-
// ---------------------------------------------------------------------------
|
|
151
|
-
|
|
152
|
-
function defaultSleep(ms) {
|
|
153
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async function waitForUrl(url, timeoutMs, fetchImpl = globalThis.fetch, sleep = defaultSleep) {
|
|
157
|
-
const deadline = Date.now() + timeoutMs;
|
|
158
|
-
let lastErr = null;
|
|
159
|
-
while (Date.now() < deadline) {
|
|
160
|
-
try {
|
|
161
|
-
const res = await fetchImpl(url);
|
|
162
|
-
if (res && res.status && res.status < 500) return true;
|
|
163
|
-
} catch (err) {
|
|
164
|
-
lastErr = err;
|
|
165
|
-
}
|
|
166
|
-
await sleep(250);
|
|
167
|
-
}
|
|
168
|
-
throw new Error(`Timeout waiting for ${url}${lastErr ? `: ${lastErr.message || lastErr}` : ''}`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
// Event assertion
|
|
173
|
-
// ---------------------------------------------------------------------------
|
|
174
|
-
|
|
175
|
-
function findPageViewEvent(events, siteId, tenantId) {
|
|
176
|
-
for (const ev of events) {
|
|
177
|
-
if (!ev) continue;
|
|
178
|
-
const name = ev.event_name || ev.eventName || ev.name || ev.type;
|
|
179
|
-
if (name !== '$page_view') continue;
|
|
180
|
-
const sid = ev.site_id || ev.siteId || (ev.context && ev.context.site_id);
|
|
181
|
-
const tid = ev.tenant_id || ev.tenantId || (ev.context && ev.context.tenant_id);
|
|
182
|
-
if (sid !== siteId) continue;
|
|
183
|
-
if (tid !== tenantId) continue;
|
|
184
|
-
return ev;
|
|
185
|
-
}
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// ---------------------------------------------------------------------------
|
|
190
|
-
// Main verifier
|
|
191
|
-
// ---------------------------------------------------------------------------
|
|
192
|
-
|
|
193
|
-
async function runVerify(args, deps) {
|
|
194
|
-
const result = {
|
|
195
|
-
ok: false,
|
|
196
|
-
framework: args.framework,
|
|
197
|
-
capturedEventCount: 0,
|
|
198
|
-
pageViewSeen: false,
|
|
199
|
-
reason: null,
|
|
200
|
-
dryRun: !!args.dryRun,
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
if (!args.path) {
|
|
204
|
-
result.reason = 'missing repo path';
|
|
205
|
-
return result;
|
|
206
|
-
}
|
|
207
|
-
if (!args.siteId || !args.tenantId) {
|
|
208
|
-
result.reason = 'missing --site-id / --tenant-id';
|
|
209
|
-
return result;
|
|
210
|
-
}
|
|
211
|
-
if (!args.framework) {
|
|
212
|
-
result.reason = 'missing --framework';
|
|
213
|
-
return result;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const repoRoot = path.resolve(args.path);
|
|
217
|
-
const pm = detectPackageManager(repoRoot);
|
|
218
|
-
const dev = resolveDevCommand(args.framework, repoRoot, pm);
|
|
219
|
-
const healthPath = healthPathFor(args.framework);
|
|
220
|
-
|
|
221
|
-
if (args.dryRun) {
|
|
222
|
-
result.ok = true;
|
|
223
|
-
result.reason = 'dry-run';
|
|
224
|
-
result.plan = {
|
|
225
|
-
pm,
|
|
226
|
-
dev,
|
|
227
|
-
healthPath,
|
|
228
|
-
port: args.port,
|
|
229
|
-
timeout: args.timeout,
|
|
230
|
-
};
|
|
231
|
-
if (deps.log && deps.log.info) {
|
|
232
|
-
deps.log.info(
|
|
233
|
-
`[verify] dry-run plan: ${dev.cmd} ${dev.args.join(' ')} in ${repoRoot} ` +
|
|
234
|
-
`(pm=${pm}, port=${args.port}, health=${healthPath})`,
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
return result;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const capture = await deps.startCapture();
|
|
241
|
-
if (deps.log && deps.log.info) {
|
|
242
|
-
deps.log.info(`[verify] capture server listening at ${capture.url}`);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
let child = null;
|
|
246
|
-
let browser = null;
|
|
247
|
-
try {
|
|
248
|
-
const envOverrides = {
|
|
249
|
-
NEXT_PUBLIC_GURULU_INGEST_URL: capture.url,
|
|
250
|
-
GURULU_INGEST_URL: capture.url,
|
|
251
|
-
VITE_GURULU_INGEST_URL: capture.url,
|
|
252
|
-
NEXT_PUBLIC_GURULU_SITE_ID: args.siteId,
|
|
253
|
-
NEXT_PUBLIC_GURULU_TENANT_ID: args.tenantId,
|
|
254
|
-
PORT: String(args.port),
|
|
255
|
-
};
|
|
256
|
-
child = deps.spawnDev({
|
|
257
|
-
cmd: dev.cmd,
|
|
258
|
-
args: dev.args,
|
|
259
|
-
cwd: repoRoot,
|
|
260
|
-
env: Object.assign({}, process.env, envOverrides),
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
const targetUrl = `http://127.0.0.1:${args.port}${healthPath}`;
|
|
264
|
-
await deps.waitForUrl(targetUrl, args.timeout);
|
|
265
|
-
|
|
266
|
-
browser = await deps.launchBrowser();
|
|
267
|
-
const context = await browser.newContext();
|
|
268
|
-
const page = await context.newPage();
|
|
269
|
-
await page.goto(`http://127.0.0.1:${args.port}/`, { waitUntil: 'networkidle' });
|
|
270
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
271
|
-
|
|
272
|
-
result.capturedEventCount = capture.events.length;
|
|
273
|
-
const hit = findPageViewEvent(capture.events, args.siteId, args.tenantId);
|
|
274
|
-
if (!hit) {
|
|
275
|
-
result.reason = 'no page_view received';
|
|
276
|
-
return result;
|
|
277
|
-
}
|
|
278
|
-
result.pageViewSeen = true;
|
|
279
|
-
result.ok = true;
|
|
280
|
-
return result;
|
|
281
|
-
} catch (err) {
|
|
282
|
-
result.reason = err && err.message ? err.message : String(err);
|
|
283
|
-
return result;
|
|
284
|
-
} finally {
|
|
285
|
-
try {
|
|
286
|
-
if (browser && typeof browser.close === 'function') await browser.close();
|
|
287
|
-
} catch {}
|
|
288
|
-
try {
|
|
289
|
-
if (child && typeof child.kill === 'function') child.kill('SIGTERM');
|
|
290
|
-
} catch {}
|
|
291
|
-
try {
|
|
292
|
-
await capture.close();
|
|
293
|
-
} catch {}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// ---------------------------------------------------------------------------
|
|
298
|
-
// Default deps (real implementations)
|
|
299
|
-
// ---------------------------------------------------------------------------
|
|
300
|
-
|
|
301
|
-
function createDefaultDeps() {
|
|
302
|
-
return {
|
|
303
|
-
startCapture: startCaptureServer,
|
|
304
|
-
spawnDev: ({ cmd, args, cwd, env }) =>
|
|
305
|
-
spawn(cmd, args, {
|
|
306
|
-
cwd,
|
|
307
|
-
env,
|
|
308
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
309
|
-
shell: process.platform === 'win32',
|
|
310
|
-
}),
|
|
311
|
-
waitForUrl: (url, timeout) => waitForUrl(url, timeout),
|
|
312
|
-
launchBrowser: async () => {
|
|
313
|
-
const { chromium } = require('playwright-core');
|
|
314
|
-
return chromium.launch({ headless: true });
|
|
315
|
-
},
|
|
316
|
-
log: {
|
|
317
|
-
info: (m) => process.stderr.write(`${m}\n`),
|
|
318
|
-
warn: (m) => process.stderr.write(`${m}\n`),
|
|
319
|
-
error: (m) => process.stderr.write(`${m}\n`),
|
|
320
|
-
},
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
module.exports = {
|
|
325
|
-
parseArgs,
|
|
326
|
-
detectPackageManager,
|
|
327
|
-
resolveDevCommand,
|
|
328
|
-
healthPathFor,
|
|
329
|
-
startCaptureServer,
|
|
330
|
-
waitForUrl,
|
|
331
|
-
findPageViewEvent,
|
|
332
|
-
runVerify,
|
|
333
|
-
createDefaultDeps,
|
|
334
|
-
};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// gurulu-verify-install.mjs — standalone post-install smoke test CLI
|
|
3
|
-
// (Phase 16 A3).
|
|
4
|
-
//
|
|
5
|
-
// Verification logic lives in `scripts/gurulu-verify-install.lib.cjs` so
|
|
6
|
-
// it can be reused by ts-jest tests via `require()`. This wrapper exposes
|
|
7
|
-
// the same API as an ESM module and, when invoked directly, runs the CLI.
|
|
8
|
-
//
|
|
9
|
-
// Usage:
|
|
10
|
-
// node scripts/gurulu-verify-install.mjs <repo-path> \
|
|
11
|
-
// --framework nextjs-app --site-id demo --tenant-id demo \
|
|
12
|
-
// [--ingest-url <url>] [--port 3000] [--timeout 30000] [--dry-run]
|
|
13
|
-
|
|
14
|
-
import { createRequire } from 'node:module';
|
|
15
|
-
import path from 'node:path';
|
|
16
|
-
import { fileURLToPath } from 'node:url';
|
|
17
|
-
|
|
18
|
-
const require = createRequire(import.meta.url);
|
|
19
|
-
const lib = require('./gurulu-verify-install.lib.cjs');
|
|
20
|
-
|
|
21
|
-
export const parseArgs = lib.parseArgs;
|
|
22
|
-
export const detectPackageManager = lib.detectPackageManager;
|
|
23
|
-
export const resolveDevCommand = lib.resolveDevCommand;
|
|
24
|
-
export const healthPathFor = lib.healthPathFor;
|
|
25
|
-
export const startCaptureServer = lib.startCaptureServer;
|
|
26
|
-
export const waitForUrl = lib.waitForUrl;
|
|
27
|
-
export const findPageViewEvent = lib.findPageViewEvent;
|
|
28
|
-
export const runVerify = lib.runVerify;
|
|
29
|
-
export const createDefaultDeps = lib.createDefaultDeps;
|
|
30
|
-
|
|
31
|
-
const isMain = (() => {
|
|
32
|
-
try {
|
|
33
|
-
const thisFile = fileURLToPath(import.meta.url);
|
|
34
|
-
return process.argv[1] && path.resolve(process.argv[1]) === path.resolve(thisFile);
|
|
35
|
-
} catch {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
})();
|
|
39
|
-
|
|
40
|
-
if (isMain) {
|
|
41
|
-
const args = lib.parseArgs(process.argv.slice(2));
|
|
42
|
-
const deps = lib.createDefaultDeps();
|
|
43
|
-
lib
|
|
44
|
-
.runVerify(args, deps)
|
|
45
|
-
.then((result) => {
|
|
46
|
-
process.stdout.write(`VERIFY_RESULT ${JSON.stringify(result)}\n`);
|
|
47
|
-
process.exit(result.ok ? 0 : 1);
|
|
48
|
-
})
|
|
49
|
-
.catch((err) => {
|
|
50
|
-
process.stderr.write(`[verify] fatal: ${err && err.stack ? err.stack : err}\n`);
|
|
51
|
-
process.stdout.write(
|
|
52
|
-
`VERIFY_RESULT ${JSON.stringify({
|
|
53
|
-
ok: false,
|
|
54
|
-
reason: String(err && err.message ? err.message : err),
|
|
55
|
-
})}\n`,
|
|
56
|
-
);
|
|
57
|
-
process.exit(2);
|
|
58
|
-
});
|
|
59
|
-
}
|
package/scripts/init-ssl.sh
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -e
|
|
3
|
-
|
|
4
|
-
DOMAIN="${DOMAIN:-gurulu.io}"
|
|
5
|
-
EMAIL="${SSL_EMAIL:-admin@gurulu.io}"
|
|
6
|
-
|
|
7
|
-
echo "[ssl] Initializing SSL for ${DOMAIN}..."
|
|
8
|
-
|
|
9
|
-
# Create certbot webroot
|
|
10
|
-
mkdir -p /var/www/certbot
|
|
11
|
-
|
|
12
|
-
# Request certificate
|
|
13
|
-
docker run --rm \
|
|
14
|
-
-v "$(pwd)/certbot/conf:/etc/letsencrypt" \
|
|
15
|
-
-v "$(pwd)/certbot/www:/var/www/certbot" \
|
|
16
|
-
certbot/certbot certonly \
|
|
17
|
-
--webroot \
|
|
18
|
-
--webroot-path=/var/www/certbot \
|
|
19
|
-
--email "$EMAIL" \
|
|
20
|
-
--agree-tos \
|
|
21
|
-
--no-eff-email \
|
|
22
|
-
-d "$DOMAIN" \
|
|
23
|
-
-d "www.${DOMAIN}"
|
|
24
|
-
|
|
25
|
-
echo "[ssl] Certificate obtained for ${DOMAIN}"
|
|
26
|
-
echo "[ssl] Files at: certbot/conf/live/${DOMAIN}/"
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# =============================================================================
|
|
3
|
-
# Migration: flow-graph enum extension (W2.1)
|
|
4
|
-
#
|
|
5
|
-
# Extends ClickHouse enums on gurulu.flow_nodes.node_type and
|
|
6
|
-
# gurulu.flow_edges.edge_type with new values needed by the flow-graph
|
|
7
|
-
# processor rework in W2.2.
|
|
8
|
-
#
|
|
9
|
-
# flow_nodes.node_type: + 'step'=9, 'state_change'=10, 'drawer'=11, 'tab'=12
|
|
10
|
-
# flow_edges.edge_type: + 'advances_to'=9, 'interacts_with'=10, 'verifies_as'=11
|
|
11
|
-
#
|
|
12
|
-
# Idempotent: checks DESCRIBE TABLE output for the new enum values before
|
|
13
|
-
# running; if already present, exits 0. Existing rows are untouched.
|
|
14
|
-
# =============================================================================
|
|
15
|
-
set -euo pipefail
|
|
16
|
-
|
|
17
|
-
CONTAINER="${CLICKHOUSE_CONTAINER:-gurulu-clickhouse}"
|
|
18
|
-
DB="${CLICKHOUSE_DB:-gurulu}"
|
|
19
|
-
|
|
20
|
-
ch_query() {
|
|
21
|
-
docker exec "$CONTAINER" clickhouse-client --query "$1"
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
log() { printf '[migrate-flow-graph-enums] %s\n' "$*"; }
|
|
25
|
-
die() { printf '[migrate-flow-graph-enums] ERROR: %s\n' "$*" >&2; exit 1; }
|
|
26
|
-
|
|
27
|
-
# --- Preflight ---------------------------------------------------------------
|
|
28
|
-
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER}$"; then
|
|
29
|
-
die "ClickHouse container '${CONTAINER}' is not running"
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
log "Inspecting current enum definitions..."
|
|
33
|
-
NODE_DESC=$(ch_query "DESCRIBE TABLE ${DB}.flow_nodes FORMAT TSVRaw" | awk -F'\t' '$1=="node_type"{print $2}')
|
|
34
|
-
EDGE_DESC=$(ch_query "DESCRIBE TABLE ${DB}.flow_edges FORMAT TSVRaw" | awk -F'\t' '$1=="edge_type"{print $2}')
|
|
35
|
-
|
|
36
|
-
if [[ -z "$NODE_DESC" ]]; then die "Could not read flow_nodes.node_type definition"; fi
|
|
37
|
-
if [[ -z "$EDGE_DESC" ]]; then die "Could not read flow_edges.edge_type definition"; fi
|
|
38
|
-
|
|
39
|
-
log "flow_nodes.node_type = ${NODE_DESC}"
|
|
40
|
-
log "flow_edges.edge_type = ${EDGE_DESC}"
|
|
41
|
-
|
|
42
|
-
NODE_NEEDED=("'step' = 9" "'state_change' = 10" "'drawer' = 11" "'tab' = 12")
|
|
43
|
-
EDGE_NEEDED=("'advances_to' = 9" "'interacts_with' = 10" "'verifies_as' = 11")
|
|
44
|
-
|
|
45
|
-
node_has_all=true
|
|
46
|
-
for v in "${NODE_NEEDED[@]}"; do
|
|
47
|
-
if [[ "$NODE_DESC" != *"$v"* ]]; then node_has_all=false; break; fi
|
|
48
|
-
done
|
|
49
|
-
|
|
50
|
-
edge_has_all=true
|
|
51
|
-
for v in "${EDGE_NEEDED[@]}"; do
|
|
52
|
-
if [[ "$EDGE_DESC" != *"$v"* ]]; then edge_has_all=false; break; fi
|
|
53
|
-
done
|
|
54
|
-
|
|
55
|
-
if $node_has_all && $edge_has_all; then
|
|
56
|
-
log "already migrated — new enum values are present on both tables"
|
|
57
|
-
log "flow_nodes count: $(ch_query "SELECT count() FROM ${DB}.flow_nodes")"
|
|
58
|
-
log "flow_edges count: $(ch_query "SELECT count() FROM ${DB}.flow_edges")"
|
|
59
|
-
exit 0
|
|
60
|
-
fi
|
|
61
|
-
|
|
62
|
-
# --- Apply ALTERs ------------------------------------------------------------
|
|
63
|
-
NODE_ENUM="Enum8('page' = 1, 'modal' = 2, 'form' = 3, 'action' = 4, 'api_call' = 5, 'success' = 6, 'error' = 7, 'milestone' = 8, 'step' = 9, 'state_change' = 10, 'drawer' = 11, 'tab' = 12)"
|
|
64
|
-
EDGE_ENUM="Enum8('navigates' = 1, 'opens' = 2, 'submits' = 3, 'triggers' = 4, 'resolves' = 5, 'fails' = 6, 'completes' = 7, 'abandons' = 8, 'advances_to' = 9, 'interacts_with' = 10, 'verifies_as' = 11)"
|
|
65
|
-
|
|
66
|
-
if ! $node_has_all; then
|
|
67
|
-
log "ALTER flow_nodes.node_type -> extended enum"
|
|
68
|
-
ch_query "ALTER TABLE ${DB}.flow_nodes MODIFY COLUMN node_type ${NODE_ENUM} DEFAULT 'page'"
|
|
69
|
-
fi
|
|
70
|
-
|
|
71
|
-
if ! $edge_has_all; then
|
|
72
|
-
log "ALTER flow_edges.edge_type -> extended enum"
|
|
73
|
-
ch_query "ALTER TABLE ${DB}.flow_edges MODIFY COLUMN edge_type ${EDGE_ENUM} DEFAULT 'navigates'"
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
# --- Verify ------------------------------------------------------------------
|
|
77
|
-
log "Post-migration DESCRIBE:"
|
|
78
|
-
ch_query "DESCRIBE TABLE ${DB}.flow_nodes" | awk -F'\t' '$1=="node_type"{print " node_type: "$2}'
|
|
79
|
-
ch_query "DESCRIBE TABLE ${DB}.flow_edges" | awk -F'\t' '$1=="edge_type"{print " edge_type: "$2}'
|
|
80
|
-
|
|
81
|
-
NODE_COUNT=$(ch_query "SELECT count() FROM ${DB}.flow_nodes")
|
|
82
|
-
EDGE_COUNT=$(ch_query "SELECT count() FROM ${DB}.flow_edges")
|
|
83
|
-
|
|
84
|
-
log "Sanity check — flow_nodes count: ${NODE_COUNT}"
|
|
85
|
-
log "Sanity check — flow_edges count: ${EDGE_COUNT}"
|
|
86
|
-
log "Migration complete."
|
package/scripts/monitor-disk.sh
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Disk usage monitor — runs via cron every 6 hours
|
|
3
|
-
# Logs to /data/backups/monitor.log
|
|
4
|
-
# Exit codes: 0=OK, 1=WARNING, 2=CRITICAL
|
|
5
|
-
|
|
6
|
-
THRESHOLD_WARN=80
|
|
7
|
-
THRESHOLD_CRIT=90
|
|
8
|
-
|
|
9
|
-
USAGE=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
|
|
10
|
-
DATA_USAGE=$(df /data | tail -1 | awk '{print $5}' | tr -d '%' 2>/dev/null || echo "0")
|
|
11
|
-
|
|
12
|
-
TIMESTAMP=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
|
|
13
|
-
|
|
14
|
-
if [ "$USAGE" -ge "$THRESHOLD_CRIT" ] || [ "$DATA_USAGE" -ge "$THRESHOLD_CRIT" ]; then
|
|
15
|
-
echo "[$TIMESTAMP] CRITICAL: Disk usage root=${USAGE}% data=${DATA_USAGE}%"
|
|
16
|
-
# TODO: send alert (Discord webhook or email)
|
|
17
|
-
exit 2
|
|
18
|
-
elif [ "$USAGE" -ge "$THRESHOLD_WARN" ] || [ "$DATA_USAGE" -ge "$THRESHOLD_WARN" ]; then
|
|
19
|
-
echo "[$TIMESTAMP] WARNING: Disk usage root=${USAGE}% data=${DATA_USAGE}%"
|
|
20
|
-
exit 1
|
|
21
|
-
else
|
|
22
|
-
echo "[$TIMESTAMP] OK: Disk usage root=${USAGE}% data=${DATA_USAGE}%"
|
|
23
|
-
exit 0
|
|
24
|
-
fi
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
// astro.patch.cjs — Phase 18.7 A1
|
|
2
|
-
//
|
|
3
|
-
// Detects an Astro project by the presence of a layout or index page
|
|
4
|
-
// `.astro` file. Astro processes `<script>` tags by default (Vite bundle);
|
|
5
|
-
// we use the `is:inline` directive so Astro emits our tracker tag exactly
|
|
6
|
-
// as written, without touching it.
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
const NAME = 'astro';
|
|
12
|
-
const CANDIDATES = [
|
|
13
|
-
'src/layouts/Layout.astro',
|
|
14
|
-
'src/layouts/BaseLayout.astro',
|
|
15
|
-
'src/layouts/MainLayout.astro',
|
|
16
|
-
'src/pages/index.astro',
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
const MARKER = 'data-gurulu-install="1"';
|
|
20
|
-
|
|
21
|
-
function detect(repoRoot) {
|
|
22
|
-
for (const rel of CANDIDATES) {
|
|
23
|
-
if (fs.existsSync(path.join(repoRoot, rel))) {
|
|
24
|
-
return { framework: NAME, files: [rel] };
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function buildScriptTag(injection) {
|
|
31
|
-
const pkAttr = injection.publishableKey
|
|
32
|
-
? ` data-gurulu-publishable-key="${injection.publishableKey}"`
|
|
33
|
-
: '';
|
|
34
|
-
return (
|
|
35
|
-
` <script\n` +
|
|
36
|
-
` is:inline\n` +
|
|
37
|
-
` src="${injection.scriptSrc}"\n` +
|
|
38
|
-
` data-gurulu-site-id="${injection.siteId}"\n` +
|
|
39
|
-
` data-gurulu-tenant-id="${injection.tenantId}"${pkAttr}\n` +
|
|
40
|
-
` data-features="errors,replay,advanced"\n` +
|
|
41
|
-
` ${MARKER}\n` +
|
|
42
|
-
` async\n` +
|
|
43
|
-
` ></script>\n`
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function injectScriptTag(source, injection) {
|
|
48
|
-
if (source.includes(MARKER)) return source;
|
|
49
|
-
const tag = buildScriptTag(injection);
|
|
50
|
-
if (/<\/body>/.test(source)) {
|
|
51
|
-
return source.replace(/<\/body>/, `${tag} </body>`);
|
|
52
|
-
}
|
|
53
|
-
if (/<\/head>/.test(source)) {
|
|
54
|
-
return source.replace(/<\/head>/, `${tag} </head>`);
|
|
55
|
-
}
|
|
56
|
-
return source + '\n' + tag;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function plan(ctx) {
|
|
60
|
-
const detection = detect(ctx.repoRoot);
|
|
61
|
-
if (!detection) return { changes: [], notes: ['no-astro-layout'] };
|
|
62
|
-
const changes = [];
|
|
63
|
-
for (const rel of detection.files) {
|
|
64
|
-
const abs = path.join(ctx.repoRoot, rel);
|
|
65
|
-
const before = fs.readFileSync(abs, 'utf8');
|
|
66
|
-
const after = injectScriptTag(before, ctx.injection);
|
|
67
|
-
if (after !== before) {
|
|
68
|
-
changes.push({ relPath: rel, before, after, reason: 'inject-script-tag' });
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return { changes, notes: [] };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
module.exports = { name: NAME, detect, plan };
|