@gurulu/cli 0.4.6 → 1.0.0
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 +25410 -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 +25 -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 +24985 -853
- 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/package.json +40 -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 -18
- package/dist/commands/chat.js +0 -117
- 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 -349
- 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 -65
- 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/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 -444
- 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
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
// scripts/patches/auto-instrument/sveltekit.cjs — Phase 20 W1 A3.
|
|
2
|
-
//
|
|
3
|
-
// SvelteKit routes live under `src/routes/**`. API routes use
|
|
4
|
-
// `+server.ts` and export one function per HTTP verb (`GET`, `POST`, ...).
|
|
5
|
-
// Form actions live in `+page.server.ts` and export a `load` function plus
|
|
6
|
-
// `actions` object. This patcher targets the `+server.ts` + `+page.server.ts`
|
|
7
|
-
// shape and inserts `gurulu.track(...)` before the last return of the
|
|
8
|
-
// matching handler.
|
|
9
|
-
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
|
|
13
|
-
const ast = require('./ast-helper.cjs');
|
|
14
|
-
const singleton = require('./singleton-helper.cjs');
|
|
15
|
-
|
|
16
|
-
const NAME = 'auto-instrument-sveltekit';
|
|
17
|
-
const SUPPORTED = ['sveltekit'];
|
|
18
|
-
|
|
19
|
-
function parseEventRoute(routeStr) {
|
|
20
|
-
if (!routeStr || typeof routeStr !== 'string') return null;
|
|
21
|
-
const m = routeStr.trim().match(/^([A-Z]+)\s+(\/.*)$/);
|
|
22
|
-
if (!m) return null;
|
|
23
|
-
return { method: m[1].toUpperCase(), urlPath: m[2].replace(/\/+$/, '') || '/' };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function routeToCandidates(urlPath) {
|
|
27
|
-
const cleaned = urlPath.replace(/^\//, '').replace(/\/+$/, '');
|
|
28
|
-
const segments = cleaned.split('/').filter(Boolean);
|
|
29
|
-
const exts = ['ts', 'js'];
|
|
30
|
-
const out = [];
|
|
31
|
-
for (const ext of exts) {
|
|
32
|
-
out.push(path.posix.join('src', 'routes', ...segments, `+server.${ext}`));
|
|
33
|
-
out.push(path.posix.join('src', 'routes', ...segments, `+page.server.${ext}`));
|
|
34
|
-
}
|
|
35
|
-
return out;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function astInstrumentFile(source, method, events, opts = {}) {
|
|
39
|
-
const tree = ast.parseSource(source);
|
|
40
|
-
const fns = ast.findExportedFunction(tree, method);
|
|
41
|
-
if (fns.length === 0) return { ok: false, reason: `${method}-not-found` };
|
|
42
|
-
const target = fns[0];
|
|
43
|
-
if (ast.hasInstrumentedMarker(target.fn)) {
|
|
44
|
-
return {
|
|
45
|
-
ok: true,
|
|
46
|
-
after: source,
|
|
47
|
-
instrumented: [],
|
|
48
|
-
skipped: events.map((e) => ({ event: e.name, reason: 'already-instrumented' })),
|
|
49
|
-
changed: false,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
const stmts = events.map((e) => ast.buildTrackStatement(e.name, e.autoProperties));
|
|
53
|
-
ast.injectTrackBeforeLastReturn(target.fn, target.body, stmts);
|
|
54
|
-
// Sprint D / D4 — alias-aware import.
|
|
55
|
-
const specifier = ast.resolveGuruluImportSpecifier(opts.repoRoot, opts.relPath);
|
|
56
|
-
ast.ensureGuruluImport(tree, specifier);
|
|
57
|
-
const after = ast.generateSource(tree, source);
|
|
58
|
-
return {
|
|
59
|
-
ok: true,
|
|
60
|
-
after,
|
|
61
|
-
instrumented: events.map((e) => e.name),
|
|
62
|
-
skipped: [],
|
|
63
|
-
changed: true,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function instrumentEvents(ctx, events) {
|
|
68
|
-
const helper = singleton.ensureSingletonHelper(ctx, 'sveltekit');
|
|
69
|
-
const changes = [...helper.changes];
|
|
70
|
-
const notes = [...helper.notes];
|
|
71
|
-
let eventsInstrumented = 0;
|
|
72
|
-
let eventsSkipped = 0;
|
|
73
|
-
|
|
74
|
-
if (helper.collision) {
|
|
75
|
-
return {
|
|
76
|
-
changes: [],
|
|
77
|
-
notes,
|
|
78
|
-
filesModified: 0,
|
|
79
|
-
eventsInstrumented: 0,
|
|
80
|
-
eventsSkipped: events ? events.length : 0,
|
|
81
|
-
collision: true,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const groups = new Map();
|
|
86
|
-
for (const e of events || []) {
|
|
87
|
-
const parsed = parseEventRoute(e && e.source && e.source.route);
|
|
88
|
-
if (!parsed) {
|
|
89
|
-
notes.push(`skip:${e && e.name}:no-source-route`);
|
|
90
|
-
eventsSkipped++;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
const candidates = routeToCandidates(parsed.urlPath);
|
|
94
|
-
const found = candidates.find((rel) => fs.existsSync(path.join(ctx.repoRoot, rel)));
|
|
95
|
-
if (!found) {
|
|
96
|
-
notes.push(`skip:${e.name}:route-file-not-found`);
|
|
97
|
-
eventsSkipped++;
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
const key = `${found}::${parsed.method}`;
|
|
101
|
-
if (!groups.has(key)) groups.set(key, { relPath: found, method: parsed.method, events: [] });
|
|
102
|
-
groups.get(key).events.push(e);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
for (const group of groups.values()) {
|
|
106
|
-
const abs = path.join(ctx.repoRoot, group.relPath);
|
|
107
|
-
const before = fs.readFileSync(abs, 'utf8');
|
|
108
|
-
// Sprint D / D4 — pass repoRoot + relPath to the import resolver.
|
|
109
|
-
const fileOpts = { repoRoot: (ctx && ctx.repoRoot) || null, relPath: group.relPath };
|
|
110
|
-
let res;
|
|
111
|
-
try {
|
|
112
|
-
res = astInstrumentFile(before, group.method, group.events, fileOpts);
|
|
113
|
-
} catch (err) {
|
|
114
|
-
const msg = (err && err.message) || String(err);
|
|
115
|
-
// eslint-disable-next-line no-console
|
|
116
|
-
console.warn(`[auto-instrument] patch.fallback ${group.relPath}: ${msg}`);
|
|
117
|
-
notes.push(`patch.fallback:${group.relPath}:${msg}`);
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (!res.ok) {
|
|
121
|
-
for (const e of group.events) {
|
|
122
|
-
notes.push(`skip:${e.name}:${res.reason}`);
|
|
123
|
-
eventsSkipped++;
|
|
124
|
-
}
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
for (const s of res.skipped || []) {
|
|
128
|
-
notes.push(`skip:${s.event}:${s.reason}`);
|
|
129
|
-
eventsSkipped++;
|
|
130
|
-
}
|
|
131
|
-
if (res.changed) {
|
|
132
|
-
changes.push({
|
|
133
|
-
relPath: group.relPath,
|
|
134
|
-
before,
|
|
135
|
-
after: res.after,
|
|
136
|
-
reason: `auto-instrument-sveltekit`,
|
|
137
|
-
type: 'auto-instrument',
|
|
138
|
-
});
|
|
139
|
-
eventsInstrumented += (res.instrumented || []).length;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const filesModified = changes.filter((c) => c.type === 'auto-instrument').length;
|
|
144
|
-
return { changes, notes, filesModified, eventsInstrumented, eventsSkipped, collision: false };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function ensureSingletonHelper(ctx) {
|
|
148
|
-
return singleton.ensureSingletonHelper(ctx, 'sveltekit');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
module.exports = {
|
|
152
|
-
name: NAME,
|
|
153
|
-
supportedFrameworks: SUPPORTED,
|
|
154
|
-
ensureSingletonHelper,
|
|
155
|
-
instrumentEvents,
|
|
156
|
-
_internals: {
|
|
157
|
-
parseEventRoute,
|
|
158
|
-
routeToCandidates,
|
|
159
|
-
astInstrumentFile,
|
|
160
|
-
},
|
|
161
|
-
};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// scripts/patches/auto-instrument/vite-react.cjs — Phase 18.7 B4.
|
|
2
|
-
//
|
|
3
|
-
// Vite+React is a pure-frontend framework, so there are no server-side
|
|
4
|
-
// route handlers to instrument. This module is a graceful no-op so the
|
|
5
|
-
// shared dispatcher in `scripts/gurulu-agentic-install.mjs` can still
|
|
6
|
-
// resolve a module for the framework and receive a well-formed result.
|
|
7
|
-
|
|
8
|
-
const NAME = 'auto-instrument-vite-react';
|
|
9
|
-
const SUPPORTED = ['vite-react'];
|
|
10
|
-
|
|
11
|
-
function ensureSingletonHelper() {
|
|
12
|
-
return {
|
|
13
|
-
changes: [],
|
|
14
|
-
notes: ['vite-react is client-only; no singleton helper needed'],
|
|
15
|
-
collision: false,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function instrumentEvents(_ctx, events) {
|
|
20
|
-
return {
|
|
21
|
-
changes: [],
|
|
22
|
-
notes: [
|
|
23
|
-
'vite-react is client-only; install client-side track calls via SDK automatically',
|
|
24
|
-
],
|
|
25
|
-
filesModified: 0,
|
|
26
|
-
eventsInstrumented: 0,
|
|
27
|
-
eventsSkipped: events ? events.length : 0,
|
|
28
|
-
collision: false,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
module.exports = {
|
|
33
|
-
name: NAME,
|
|
34
|
-
supportedFrameworks: SUPPORTED,
|
|
35
|
-
ensureSingletonHelper,
|
|
36
|
-
instrumentEvents,
|
|
37
|
-
};
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
// scripts/patches/auto-instrument/vue.cjs — Phase 20 W1 A3.
|
|
2
|
-
//
|
|
3
|
-
// Nuxt 3 / H3 server handlers. Routes live at
|
|
4
|
-
// `server/api/**.{ts,js}` (or `server/routes/**`) and are declared with:
|
|
5
|
-
//
|
|
6
|
-
// export default defineEventHandler(async (event) => { ... });
|
|
7
|
-
//
|
|
8
|
-
// We parse the file, find the `export default defineEventHandler(...)` call,
|
|
9
|
-
// and inject `gurulu.track(...)` into the inner arrow/function body before
|
|
10
|
-
// the last return.
|
|
11
|
-
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const path = require('path');
|
|
14
|
-
|
|
15
|
-
const ast = require('./ast-helper.cjs');
|
|
16
|
-
const singleton = require('./singleton-helper.cjs');
|
|
17
|
-
|
|
18
|
-
const NAME = 'auto-instrument-vue';
|
|
19
|
-
const SUPPORTED = ['vue', 'nuxt'];
|
|
20
|
-
|
|
21
|
-
function parseEventRoute(routeStr) {
|
|
22
|
-
if (!routeStr || typeof routeStr !== 'string') return null;
|
|
23
|
-
const m = routeStr.trim().match(/^([A-Z]+)\s+(\/.*)$/);
|
|
24
|
-
if (!m) return null;
|
|
25
|
-
return { method: m[1].toUpperCase(), urlPath: m[2].replace(/\/+$/, '') || '/' };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function routeToCandidates(urlPath, method) {
|
|
29
|
-
// Nuxt files can be:
|
|
30
|
-
// server/api/users.ts — any method
|
|
31
|
-
// server/api/users.post.ts — POST only
|
|
32
|
-
// server/api/users/index.ts — any method
|
|
33
|
-
// server/api/users/[id].ts — any method (dynamic)
|
|
34
|
-
const cleaned = urlPath.replace(/^\//, '').replace(/\/+$/, '').replace(/^api\//, '');
|
|
35
|
-
const segments = cleaned.split('/').filter(Boolean);
|
|
36
|
-
const exts = ['ts', 'js', 'mjs'];
|
|
37
|
-
const out = [];
|
|
38
|
-
const lowerMethod = method.toLowerCase();
|
|
39
|
-
for (const ext of exts) {
|
|
40
|
-
out.push(path.posix.join('server', 'api', ...segments) + `.${lowerMethod}.${ext}`);
|
|
41
|
-
out.push(path.posix.join('server', 'api', ...segments) + `.${ext}`);
|
|
42
|
-
out.push(path.posix.join('server', 'api', ...segments, `index.${ext}`));
|
|
43
|
-
out.push(path.posix.join('server', 'routes', ...segments) + `.${ext}`);
|
|
44
|
-
}
|
|
45
|
-
return out;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Locate the inner handler function inside `export default defineEventHandler(fn)`.
|
|
50
|
-
* Returns `{ fn, body }` or null.
|
|
51
|
-
*/
|
|
52
|
-
function findEventHandler(tree) {
|
|
53
|
-
const t = ast.t;
|
|
54
|
-
let found = null;
|
|
55
|
-
ast.traverse(tree, {
|
|
56
|
-
ExportDefaultDeclaration(p) {
|
|
57
|
-
const decl = p.node.declaration;
|
|
58
|
-
if (!t.isCallExpression(decl)) return;
|
|
59
|
-
if (!t.isIdentifier(decl.callee) || decl.callee.name !== 'defineEventHandler') return;
|
|
60
|
-
const handler = decl.arguments[0];
|
|
61
|
-
if (!handler) return;
|
|
62
|
-
if (
|
|
63
|
-
(t.isArrowFunctionExpression(handler) || t.isFunctionExpression(handler)) &&
|
|
64
|
-
t.isBlockStatement(handler.body)
|
|
65
|
-
) {
|
|
66
|
-
found = { fn: handler, body: handler.body };
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
return found;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function astInstrumentFile(source, events, opts = {}) {
|
|
74
|
-
const tree = ast.parseSource(source);
|
|
75
|
-
const target = findEventHandler(tree);
|
|
76
|
-
if (!target) return { ok: false, reason: 'event-handler-not-found' };
|
|
77
|
-
if (ast.hasInstrumentedMarker(target.fn)) {
|
|
78
|
-
return {
|
|
79
|
-
ok: true,
|
|
80
|
-
after: source,
|
|
81
|
-
instrumented: [],
|
|
82
|
-
skipped: events.map((e) => ({ event: e.name, reason: 'already-instrumented' })),
|
|
83
|
-
changed: false,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
const stmts = events.map((e) => ast.buildTrackStatement(e.name, e.autoProperties));
|
|
87
|
-
ast.injectTrackBeforeLastReturn(target.fn, target.body, stmts);
|
|
88
|
-
// Sprint D / D4 — alias-aware import.
|
|
89
|
-
const specifier = ast.resolveGuruluImportSpecifier(opts.repoRoot, opts.relPath);
|
|
90
|
-
ast.ensureGuruluImport(tree, specifier);
|
|
91
|
-
const after = ast.generateSource(tree, source);
|
|
92
|
-
return {
|
|
93
|
-
ok: true,
|
|
94
|
-
after,
|
|
95
|
-
instrumented: events.map((e) => e.name),
|
|
96
|
-
skipped: [],
|
|
97
|
-
changed: true,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function instrumentEvents(ctx, events) {
|
|
102
|
-
const helper = singleton.ensureSingletonHelper(ctx, 'vue');
|
|
103
|
-
const changes = [...helper.changes];
|
|
104
|
-
const notes = [...helper.notes];
|
|
105
|
-
let eventsInstrumented = 0;
|
|
106
|
-
let eventsSkipped = 0;
|
|
107
|
-
|
|
108
|
-
if (helper.collision) {
|
|
109
|
-
return {
|
|
110
|
-
changes: [],
|
|
111
|
-
notes,
|
|
112
|
-
filesModified: 0,
|
|
113
|
-
eventsInstrumented: 0,
|
|
114
|
-
eventsSkipped: events ? events.length : 0,
|
|
115
|
-
collision: true,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const groups = new Map();
|
|
120
|
-
for (const e of events || []) {
|
|
121
|
-
const parsed = parseEventRoute(e && e.source && e.source.route);
|
|
122
|
-
if (!parsed) {
|
|
123
|
-
notes.push(`skip:${e && e.name}:no-source-route`);
|
|
124
|
-
eventsSkipped++;
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
const candidates = routeToCandidates(parsed.urlPath, parsed.method);
|
|
128
|
-
const found = candidates.find((rel) => fs.existsSync(path.join(ctx.repoRoot, rel)));
|
|
129
|
-
if (!found) {
|
|
130
|
-
notes.push(`skip:${e.name}:route-file-not-found`);
|
|
131
|
-
eventsSkipped++;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
const key = `${found}::${parsed.method}`;
|
|
135
|
-
if (!groups.has(key)) groups.set(key, { relPath: found, events: [] });
|
|
136
|
-
groups.get(key).events.push(e);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
for (const group of groups.values()) {
|
|
140
|
-
const abs = path.join(ctx.repoRoot, group.relPath);
|
|
141
|
-
const before = fs.readFileSync(abs, 'utf8');
|
|
142
|
-
// Sprint D / D4 — pass repoRoot + relPath to the import resolver.
|
|
143
|
-
const fileOpts = { repoRoot: (ctx && ctx.repoRoot) || null, relPath: group.relPath };
|
|
144
|
-
let res;
|
|
145
|
-
try {
|
|
146
|
-
res = astInstrumentFile(before, group.events, fileOpts);
|
|
147
|
-
} catch (err) {
|
|
148
|
-
const msg = (err && err.message) || String(err);
|
|
149
|
-
// eslint-disable-next-line no-console
|
|
150
|
-
console.warn(`[auto-instrument] patch.fallback ${group.relPath}: ${msg}`);
|
|
151
|
-
notes.push(`patch.fallback:${group.relPath}:${msg}`);
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
if (!res.ok) {
|
|
155
|
-
for (const e of group.events) {
|
|
156
|
-
notes.push(`skip:${e.name}:${res.reason}`);
|
|
157
|
-
eventsSkipped++;
|
|
158
|
-
}
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
for (const s of res.skipped || []) {
|
|
162
|
-
notes.push(`skip:${s.event}:${s.reason}`);
|
|
163
|
-
eventsSkipped++;
|
|
164
|
-
}
|
|
165
|
-
if (res.changed) {
|
|
166
|
-
changes.push({
|
|
167
|
-
relPath: group.relPath,
|
|
168
|
-
before,
|
|
169
|
-
after: res.after,
|
|
170
|
-
reason: `auto-instrument-vue`,
|
|
171
|
-
type: 'auto-instrument',
|
|
172
|
-
});
|
|
173
|
-
eventsInstrumented += (res.instrumented || []).length;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const filesModified = changes.filter((c) => c.type === 'auto-instrument').length;
|
|
178
|
-
return { changes, notes, filesModified, eventsInstrumented, eventsSkipped, collision: false };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function ensureSingletonHelper(ctx) {
|
|
182
|
-
return singleton.ensureSingletonHelper(ctx, 'vue');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
module.exports = {
|
|
186
|
-
name: NAME,
|
|
187
|
-
supportedFrameworks: SUPPORTED,
|
|
188
|
-
ensureSingletonHelper,
|
|
189
|
-
instrumentEvents,
|
|
190
|
-
_internals: {
|
|
191
|
-
parseEventRoute,
|
|
192
|
-
routeToCandidates,
|
|
193
|
-
findEventHandler,
|
|
194
|
-
astInstrumentFile,
|
|
195
|
-
},
|
|
196
|
-
};
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
// express.patch.cjs — Phase 16 A1
|
|
2
|
-
//
|
|
3
|
-
// Detects an Express server (`app.js`, `server.js`, `src/index.js` with the
|
|
4
|
-
// idiomatic `const app = express()` signature) and appends:
|
|
5
|
-
// - A proxy route that serves /gurulu-tracker from the Gurulu CDN
|
|
6
|
-
// - A `res.locals.guruluConfig` middleware exposing siteId / tenantId
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
const NAME = 'express';
|
|
12
|
-
const CANDIDATES = [
|
|
13
|
-
'app.js',
|
|
14
|
-
'server.js',
|
|
15
|
-
'src/index.js',
|
|
16
|
-
'src/app.js',
|
|
17
|
-
'src/server.js',
|
|
18
|
-
'index.js',
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
const MARKER = '/* gurulu:install */';
|
|
22
|
-
|
|
23
|
-
function looksLikeExpress(source) {
|
|
24
|
-
return (
|
|
25
|
-
/require\s*\(\s*['"]express['"]\s*\)/.test(source) ||
|
|
26
|
-
/from\s+['"]express['"]/.test(source) ||
|
|
27
|
-
/express\s*\(\s*\)/.test(source)
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function detect(repoRoot) {
|
|
32
|
-
for (const rel of CANDIDATES) {
|
|
33
|
-
const abs = path.join(repoRoot, rel);
|
|
34
|
-
if (!fs.existsSync(abs)) continue;
|
|
35
|
-
const source = fs.readFileSync(abs, 'utf8');
|
|
36
|
-
if (looksLikeExpress(source)) {
|
|
37
|
-
return { framework: NAME, files: [rel] };
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function findAppIdentifier(source) {
|
|
44
|
-
const m = source.match(/(?:const|let|var)\s+(\w+)\s*=\s*express\s*\(\s*\)/);
|
|
45
|
-
return m ? m[1] : 'app';
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function buildMiddleware(appVar, injection) {
|
|
49
|
-
const pkField = injection.publishableKey
|
|
50
|
-
? ` publishableKey: ${JSON.stringify(injection.publishableKey)},\n`
|
|
51
|
-
: '';
|
|
52
|
-
return (
|
|
53
|
-
`\n${MARKER}\n` +
|
|
54
|
-
`${appVar}.use((req, res, next) => {\n` +
|
|
55
|
-
` res.locals.guruluConfig = {\n` +
|
|
56
|
-
` siteId: ${JSON.stringify(injection.siteId)},\n` +
|
|
57
|
-
` tenantId: ${JSON.stringify(injection.tenantId)},\n` +
|
|
58
|
-
pkField +
|
|
59
|
-
` };\n` +
|
|
60
|
-
` next();\n` +
|
|
61
|
-
`});\n` +
|
|
62
|
-
`${appVar}.use('/gurulu-tracker', (req, res) => {\n` +
|
|
63
|
-
` res.set('Content-Type', 'application/javascript');\n` +
|
|
64
|
-
` res.send(\`// Gurulu tracker proxy — site=\${${JSON.stringify(injection.siteId)}} data-gurulu-publishable-key=\${${JSON.stringify(injection.publishableKey || '')}}\\n` +
|
|
65
|
-
`window.gurulu = window.gurulu || { queue: [] };\`);\n` +
|
|
66
|
-
`});\n`
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function inject(source, injection) {
|
|
71
|
-
if (source.includes(MARKER)) return source;
|
|
72
|
-
const appVar = findAppIdentifier(source);
|
|
73
|
-
const mw = buildMiddleware(appVar, injection);
|
|
74
|
-
// Insert before `app.listen(` if present, otherwise append.
|
|
75
|
-
const listenRe = new RegExp(`${appVar}\\s*\\.\\s*listen\\s*\\(`);
|
|
76
|
-
const idx = source.search(listenRe);
|
|
77
|
-
if (idx !== -1) {
|
|
78
|
-
const lineStart = source.lastIndexOf('\n', idx) + 1;
|
|
79
|
-
return source.slice(0, lineStart) + mw + '\n' + source.slice(lineStart);
|
|
80
|
-
}
|
|
81
|
-
return source + mw;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function plan(ctx) {
|
|
85
|
-
const detection = detect(ctx.repoRoot);
|
|
86
|
-
if (!detection) return { changes: [], notes: ['no-express'] };
|
|
87
|
-
const changes = [];
|
|
88
|
-
for (const rel of detection.files) {
|
|
89
|
-
const abs = path.join(ctx.repoRoot, rel);
|
|
90
|
-
const before = fs.readFileSync(abs, 'utf8');
|
|
91
|
-
const after = inject(before, ctx.injection);
|
|
92
|
-
if (after !== before) {
|
|
93
|
-
changes.push({ relPath: rel, before, after, reason: 'inject-middleware' });
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return { changes, notes: [] };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
module.exports = { name: NAME, detect, plan, looksLikeExpress };
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
// fastify.patch.cjs — Phase 18.7 A1
|
|
2
|
-
//
|
|
3
|
-
// Fastify is a server-side framework. If a static HTML shell is shipped
|
|
4
|
-
// under `public/`, `static/`, `dist/`, or `build/`, we patch that.
|
|
5
|
-
// Otherwise we emit a server-only note and rely on --auto-instrument
|
|
6
|
-
// (Phase 18.7 Agent B) to wire server-side track calls.
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
const NAME = 'fastify';
|
|
12
|
-
const HTML_CANDIDATES = [
|
|
13
|
-
'public/index.html',
|
|
14
|
-
'static/index.html',
|
|
15
|
-
'dist/index.html',
|
|
16
|
-
'build/index.html',
|
|
17
|
-
];
|
|
18
|
-
const SERVER_CANDIDATES = [
|
|
19
|
-
'server.js',
|
|
20
|
-
'app.js',
|
|
21
|
-
'src/server.js',
|
|
22
|
-
'src/app.js',
|
|
23
|
-
'src/server.ts',
|
|
24
|
-
'src/app.ts',
|
|
25
|
-
'index.js',
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
const MARKER = 'data-gurulu-install="1"';
|
|
29
|
-
|
|
30
|
-
function looksLikeFastify(repoRoot) {
|
|
31
|
-
const pkgPath = path.join(repoRoot, 'package.json');
|
|
32
|
-
if (fs.existsSync(pkgPath)) {
|
|
33
|
-
try {
|
|
34
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
35
|
-
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
36
|
-
if (deps['fastify']) return true;
|
|
37
|
-
} catch {
|
|
38
|
-
/* ignore */
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
for (const rel of SERVER_CANDIDATES) {
|
|
42
|
-
const abs = path.join(repoRoot, rel);
|
|
43
|
-
if (!fs.existsSync(abs)) continue;
|
|
44
|
-
const source = fs.readFileSync(abs, 'utf8');
|
|
45
|
-
if (/require\s*\(\s*['"]fastify['"]\s*\)/.test(source) || /from\s+['"]fastify['"]/.test(source)) {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function detect(repoRoot) {
|
|
53
|
-
if (!looksLikeFastify(repoRoot)) return null;
|
|
54
|
-
for (const rel of HTML_CANDIDATES) {
|
|
55
|
-
if (fs.existsSync(path.join(repoRoot, rel))) {
|
|
56
|
-
return { framework: NAME, files: [rel] };
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return { framework: NAME, files: [], serverOnly: true };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function buildScriptTag(injection) {
|
|
63
|
-
const pkAttr = injection.publishableKey
|
|
64
|
-
? ` data-gurulu-publishable-key="${injection.publishableKey}"`
|
|
65
|
-
: '';
|
|
66
|
-
return (
|
|
67
|
-
` <script\n` +
|
|
68
|
-
` src="${injection.scriptSrc}"\n` +
|
|
69
|
-
` data-gurulu-site-id="${injection.siteId}"\n` +
|
|
70
|
-
` data-gurulu-tenant-id="${injection.tenantId}"${pkAttr}\n` +
|
|
71
|
-
` data-features="errors,replay,advanced"\n` +
|
|
72
|
-
` ${MARKER}\n` +
|
|
73
|
-
` async\n` +
|
|
74
|
-
` ></script>\n`
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function injectScriptTag(source, injection) {
|
|
79
|
-
if (source.includes(MARKER)) return source;
|
|
80
|
-
const tag = buildScriptTag(injection);
|
|
81
|
-
if (/<\/body>/.test(source)) {
|
|
82
|
-
return source.replace(/<\/body>/, `${tag} </body>`);
|
|
83
|
-
}
|
|
84
|
-
return source + '\n' + tag;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function plan(ctx) {
|
|
88
|
-
const detection = detect(ctx.repoRoot);
|
|
89
|
-
if (!detection) return { changes: [], notes: ['no-fastify'] };
|
|
90
|
-
if (detection.serverOnly) {
|
|
91
|
-
return {
|
|
92
|
-
changes: [],
|
|
93
|
-
notes: ['server-only; use --auto-instrument for server-side tracking'],
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
const changes = [];
|
|
97
|
-
for (const rel of detection.files) {
|
|
98
|
-
const abs = path.join(ctx.repoRoot, rel);
|
|
99
|
-
const before = fs.readFileSync(abs, 'utf8');
|
|
100
|
-
const after = injectScriptTag(before, ctx.injection);
|
|
101
|
-
if (after !== before) {
|
|
102
|
-
changes.push({ relPath: rel, before, after, reason: 'inject-script-tag' });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return { changes, notes: [] };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
module.exports = { name: NAME, detect, plan };
|