@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
|
@@ -1,480 +0,0 @@
|
|
|
1
|
-
// scripts/patches/auto-instrument/ast-helper.cjs — Phase 20 W1 A1.
|
|
2
|
-
//
|
|
3
|
-
// Shared Babel AST helpers used by every auto-instrument module. The goal of
|
|
4
|
-
// this file is twofold:
|
|
5
|
-
//
|
|
6
|
-
// 1. Provide a single place where we resolve `@babel/parser`,
|
|
7
|
-
// `@babel/traverse`, `@babel/generator`, `@babel/types` so the patcher
|
|
8
|
-
// modules stay tiny and framework-focused.
|
|
9
|
-
// 2. Encode the shared instrumentation operations (parse, find function
|
|
10
|
-
// node, insert a `gurulu.track(...)` call before the last return, ensure
|
|
11
|
-
// the `gurulu` import, check idempotency marker, serialize back to
|
|
12
|
-
// source) in one place — consistent behaviour across NestJS, Remix,
|
|
13
|
-
// SvelteKit, Astro, Fastify, Vue patchers.
|
|
14
|
-
//
|
|
15
|
-
// None of these helpers mutate source strings directly; they parse an AST,
|
|
16
|
-
// apply transforms, then run `@babel/generator` to serialize the result.
|
|
17
|
-
// Parse errors are NOT swallowed — callers catch and fall back to regex.
|
|
18
|
-
|
|
19
|
-
const fs = require('fs');
|
|
20
|
-
const path = require('path');
|
|
21
|
-
|
|
22
|
-
const parser = require('@babel/parser');
|
|
23
|
-
// `@babel/traverse` exposes its default export under `.default` in CJS.
|
|
24
|
-
const traverseMod = require('@babel/traverse');
|
|
25
|
-
const traverse = traverseMod.default || traverseMod;
|
|
26
|
-
const generatorMod = require('@babel/generator');
|
|
27
|
-
const generate = generatorMod.default || generatorMod;
|
|
28
|
-
const t = require('@babel/types');
|
|
29
|
-
|
|
30
|
-
const IMPORT_LINE = "import { gurulu } from '@/lib/gurulu';";
|
|
31
|
-
const HELPER_REL_PATH = 'src/lib/gurulu';
|
|
32
|
-
const MARKER = '@gurulu-instrumented';
|
|
33
|
-
const MARKER_COMMENT = `// @gurulu-instrumented`;
|
|
34
|
-
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// Sprint D / D4 — tsconfig `@/*` path detection + relative-path fallback.
|
|
37
|
-
//
|
|
38
|
-
// Many auto-instrumented files want to write `import { gurulu } from
|
|
39
|
-
// '@/lib/gurulu'`. That works for the typical Next.js scaffold, but breaks
|
|
40
|
-
// in vanilla TS / Vite / Express setups whose `tsconfig.json` does not
|
|
41
|
-
// configure `compilerOptions.paths['@/*']`. Without the alias the inserted
|
|
42
|
-
// import resolves to a missing module and the patched file fails to
|
|
43
|
-
// compile.
|
|
44
|
-
//
|
|
45
|
-
// `resolveGuruluImportSpecifier(repoRoot, relativeRouteFile)` returns the
|
|
46
|
-
// import specifier the patcher should embed. When the alias is configured
|
|
47
|
-
// it returns `'@/lib/gurulu'`; otherwise it computes the POSIX-style
|
|
48
|
-
// relative path from the route file to `src/lib/gurulu` (e.g.
|
|
49
|
-
// `'../../lib/gurulu'`).
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
function stripJsonComments(text) {
|
|
53
|
-
// Tolerate `// ...` and `/* ... */` comments as found in tsconfig files.
|
|
54
|
-
return text
|
|
55
|
-
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
56
|
-
.replace(/(^|[^:\\])\/\/.*$/gm, '$1');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function readTsconfigPaths(repoRoot) {
|
|
60
|
-
const candidates = ['tsconfig.json', 'tsconfig.base.json', 'jsconfig.json'];
|
|
61
|
-
for (const rel of candidates) {
|
|
62
|
-
const abs = path.join(repoRoot, rel);
|
|
63
|
-
if (!fs.existsSync(abs)) continue;
|
|
64
|
-
try {
|
|
65
|
-
const raw = fs.readFileSync(abs, 'utf8');
|
|
66
|
-
const parsed = JSON.parse(stripJsonComments(raw));
|
|
67
|
-
const co = parsed && parsed.compilerOptions;
|
|
68
|
-
if (co && co.paths && typeof co.paths === 'object') {
|
|
69
|
-
return { paths: co.paths, baseUrl: co.baseUrl || '.' };
|
|
70
|
-
}
|
|
71
|
-
} catch {
|
|
72
|
-
// Ignore malformed tsconfig — fall through to the next candidate.
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Returns true when tsconfig.json (or jsconfig.json) declares
|
|
80
|
-
* `compilerOptions.paths['@/*']`. We accept any value — most projects map it
|
|
81
|
-
* to `['./src/*']` but custom roots like `['./*']` also count as wired.
|
|
82
|
-
*/
|
|
83
|
-
function hasAtAlias(repoRoot) {
|
|
84
|
-
const cfg = readTsconfigPaths(repoRoot);
|
|
85
|
-
if (!cfg) return false;
|
|
86
|
-
return Object.prototype.hasOwnProperty.call(cfg.paths, '@/*');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Compute a POSIX-style relative import path from `fromFileRel` (a repo
|
|
91
|
-
* relative path like `src/app/api/checkout/route.ts`) to the singleton
|
|
92
|
-
* helper at `src/lib/gurulu`. The returned path is suitable for use as an
|
|
93
|
-
* ES module import specifier: it always starts with `./` or `../` and never
|
|
94
|
-
* carries a file extension.
|
|
95
|
-
*/
|
|
96
|
-
function relativeHelperSpecifier(fromFileRel) {
|
|
97
|
-
const fromDir = path.posix.dirname(fromFileRel.split(path.sep).join('/'));
|
|
98
|
-
let rel = path.posix.relative(fromDir, HELPER_REL_PATH);
|
|
99
|
-
if (!rel) rel = '.';
|
|
100
|
-
if (!rel.startsWith('.')) rel = `./${rel}`;
|
|
101
|
-
return rel;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Pick the right import specifier for `import { gurulu } from <here>`
|
|
106
|
-
* given the repo root and the file the import will be written into.
|
|
107
|
-
*
|
|
108
|
-
* - When `@/*` is configured in tsconfig, prefer the alias for clean diffs
|
|
109
|
-
* that match the singleton helper documentation.
|
|
110
|
-
* - Otherwise fall back to a relative import computed from the route file.
|
|
111
|
-
*/
|
|
112
|
-
function resolveGuruluImportSpecifier(repoRoot, fromFileRel) {
|
|
113
|
-
if (repoRoot && hasAtAlias(repoRoot)) return '@/lib/gurulu';
|
|
114
|
-
if (fromFileRel) return relativeHelperSpecifier(fromFileRel);
|
|
115
|
-
return '@/lib/gurulu';
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const DEFAULT_PARSE_PLUGINS = [
|
|
119
|
-
'typescript',
|
|
120
|
-
'jsx',
|
|
121
|
-
'decorators-legacy',
|
|
122
|
-
'classProperties',
|
|
123
|
-
'classPrivateProperties',
|
|
124
|
-
'classPrivateMethods',
|
|
125
|
-
'topLevelAwait',
|
|
126
|
-
'importAssertions',
|
|
127
|
-
'importAttributes',
|
|
128
|
-
'explicitResourceManagement',
|
|
129
|
-
];
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Parse `content` into a Babel AST. Throws on parse error so callers can
|
|
133
|
-
* fall back to their legacy regex path.
|
|
134
|
-
*/
|
|
135
|
-
function parseSource(content) {
|
|
136
|
-
return parser.parse(content, {
|
|
137
|
-
sourceType: 'module',
|
|
138
|
-
allowReturnOutsideFunction: true,
|
|
139
|
-
allowAwaitOutsideFunction: true,
|
|
140
|
-
allowImportExportEverywhere: true,
|
|
141
|
-
errorRecovery: false,
|
|
142
|
-
plugins: DEFAULT_PARSE_PLUGINS,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Serialize an AST back to source. We preserve the original source for
|
|
148
|
-
* fidelity when possible; otherwise Babel re-prints using its default.
|
|
149
|
-
*/
|
|
150
|
-
function generateSource(ast, originalSource) {
|
|
151
|
-
const out = generate(
|
|
152
|
-
ast,
|
|
153
|
-
{
|
|
154
|
-
retainLines: false,
|
|
155
|
-
compact: false,
|
|
156
|
-
comments: true,
|
|
157
|
-
jsescOption: { minimal: true, quotes: 'single' },
|
|
158
|
-
},
|
|
159
|
-
originalSource,
|
|
160
|
-
);
|
|
161
|
-
return out.code;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Return true if `node` (function declaration / expression / arrow) carries
|
|
166
|
-
* a leading `@gurulu-instrumented` block/line comment. We look at
|
|
167
|
-
* `leadingComments` on the node AND on its parent export declaration (set by
|
|
168
|
-
* the caller when relevant).
|
|
169
|
-
*/
|
|
170
|
-
function hasInstrumentedMarker(node) {
|
|
171
|
-
const comments = (node && (node.leadingComments || [])) || [];
|
|
172
|
-
for (const c of comments) {
|
|
173
|
-
if (c && typeof c.value === 'string' && c.value.includes(MARKER)) return true;
|
|
174
|
-
}
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Attach the `@gurulu-instrumented` marker to a function node so future
|
|
180
|
-
* runs are idempotent.
|
|
181
|
-
*/
|
|
182
|
-
function tagInstrumented(node) {
|
|
183
|
-
t.addComment(node, 'leading', ` @gurulu-instrumented`, true);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Sprint D / D1 — typed-helper selector. Lives in its own CJS module so the
|
|
187
|
-
// auto-instrumenter can swap `gurulu.track('$purchase', {...})` for the
|
|
188
|
-
// canonical `gurulu.purchase({...})` whenever the LLM-extracted properties
|
|
189
|
-
// satisfy the helper's required fields. When `selectHelper` returns null we
|
|
190
|
-
// fall through to the legacy generic-track shape — no behaviour change for
|
|
191
|
-
// custom (non-canonical) events.
|
|
192
|
-
const sdkHelperMap = require('./sdk-helper-map.cjs');
|
|
193
|
-
|
|
194
|
-
function safeParseExpression(text) {
|
|
195
|
-
return parser.parseExpression(text, { plugins: DEFAULT_PARSE_PLUGINS });
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function buildTypedHelperCall(eventName, autoProperties) {
|
|
199
|
-
const helper = sdkHelperMap.selectHelper(eventName, autoProperties);
|
|
200
|
-
if (!helper) return null;
|
|
201
|
-
const argNodes = [];
|
|
202
|
-
for (const expr of helper.argExpressions) {
|
|
203
|
-
try {
|
|
204
|
-
argNodes.push(safeParseExpression(expr));
|
|
205
|
-
} catch (_) {
|
|
206
|
-
// Defensive: if any arg expression is malformed, abort the typed
|
|
207
|
-
// upgrade and let the caller emit `gurulu.track(...)` instead.
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return t.expressionStatement(
|
|
212
|
-
t.callExpression(
|
|
213
|
-
t.memberExpression(t.identifier('gurulu'), t.identifier(helper.method)),
|
|
214
|
-
argNodes,
|
|
215
|
-
),
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Build the `gurulu.track('eventName', { ...properties })` statement. Empty
|
|
221
|
-
* object when no auto-properties; otherwise an ObjectExpression with the
|
|
222
|
-
* property AST nodes inlined.
|
|
223
|
-
*
|
|
224
|
-
* Sprint D / D1: when `eventName` is a canonical event ($purchase, $signup,
|
|
225
|
-
* etc.) AND the extracted properties supply every required field, we emit
|
|
226
|
-
* the typed helper instead — `gurulu.purchase({ value, currency })` rather
|
|
227
|
-
* than `gurulu.track('$purchase', { ... })`. The marker comment is identical
|
|
228
|
-
* in either form so idempotency checks still work.
|
|
229
|
-
*/
|
|
230
|
-
function buildTrackStatement(eventName, autoProperties, opts = {}) {
|
|
231
|
-
// Try the typed helper first. Tests and patcher modules pass `opts.preferTyped =
|
|
232
|
-
// false` to opt out (e.g. when verifying the legacy shape). Default is true.
|
|
233
|
-
const preferTyped = opts.preferTyped !== false;
|
|
234
|
-
if (preferTyped) {
|
|
235
|
-
const typed = buildTypedHelperCall(eventName, autoProperties);
|
|
236
|
-
if (typed) {
|
|
237
|
-
t.addComment(typed, 'leading', ` @gurulu-instrumented ${eventName}`, true);
|
|
238
|
-
return typed;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const args = [t.stringLiteral(eventName)];
|
|
243
|
-
if (Array.isArray(autoProperties) && autoProperties.length > 0) {
|
|
244
|
-
const props = [];
|
|
245
|
-
for (const p of autoProperties) {
|
|
246
|
-
if (!p || !p.name) continue;
|
|
247
|
-
// Allow `source` to be a JS expression like `req.body.orderId`.
|
|
248
|
-
let valueNode;
|
|
249
|
-
if (p.source && typeof p.source === 'string') {
|
|
250
|
-
try {
|
|
251
|
-
valueNode = safeParseExpression(p.source);
|
|
252
|
-
} catch (_) {
|
|
253
|
-
valueNode = t.stringLiteral(String(p.source));
|
|
254
|
-
}
|
|
255
|
-
} else {
|
|
256
|
-
valueNode = t.identifier('undefined');
|
|
257
|
-
}
|
|
258
|
-
props.push(
|
|
259
|
-
t.objectProperty(
|
|
260
|
-
t.identifier(toSafeIdentifier(p.name)),
|
|
261
|
-
valueNode,
|
|
262
|
-
false,
|
|
263
|
-
toSafeIdentifier(p.name) === p.name,
|
|
264
|
-
),
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
args.push(t.objectExpression(props));
|
|
268
|
-
} else {
|
|
269
|
-
args.push(t.objectExpression([]));
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const call = t.expressionStatement(
|
|
273
|
-
t.callExpression(
|
|
274
|
-
t.memberExpression(t.identifier('gurulu'), t.identifier('track')),
|
|
275
|
-
args,
|
|
276
|
-
),
|
|
277
|
-
);
|
|
278
|
-
// Leading marker so humans can spot the insertion and so idempotency
|
|
279
|
-
// fallbacks (string-based) still see it.
|
|
280
|
-
t.addComment(
|
|
281
|
-
call,
|
|
282
|
-
'leading',
|
|
283
|
-
` @gurulu-instrumented ${eventName}`,
|
|
284
|
-
true,
|
|
285
|
-
);
|
|
286
|
-
return call;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function toSafeIdentifier(name) {
|
|
290
|
-
return String(name).replace(/[^A-Za-z0-9_$]/g, '_');
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Given a function body (BlockStatement), inject `gurulu.track(...)`
|
|
295
|
-
* statements right before the LAST top-level `return` statement. If the
|
|
296
|
-
* function has no top-level return, append at the end of the body.
|
|
297
|
-
*
|
|
298
|
-
* The function tags the containing function node with the
|
|
299
|
-
* `@gurulu-instrumented` comment (via the `fn` param passed by the caller).
|
|
300
|
-
*
|
|
301
|
-
* Returns `true` if any statements were injected.
|
|
302
|
-
*/
|
|
303
|
-
function injectTrackBeforeLastReturn(fn, body, trackStatements) {
|
|
304
|
-
if (!body || !Array.isArray(body.body)) return false;
|
|
305
|
-
if (trackStatements.length === 0) return false;
|
|
306
|
-
|
|
307
|
-
// Find last top-level return.
|
|
308
|
-
let lastIdx = -1;
|
|
309
|
-
for (let i = body.body.length - 1; i >= 0; i--) {
|
|
310
|
-
if (t.isReturnStatement(body.body[i])) {
|
|
311
|
-
lastIdx = i;
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (lastIdx === -1) {
|
|
317
|
-
// No top-level return — insert BEFORE the last statement of the body
|
|
318
|
-
// so trailing `res.json(...)` / `reply.send(...)` calls still come after
|
|
319
|
-
// our track call (matching operator intuition + Phase 18.7 tests).
|
|
320
|
-
const insertAt = Math.max(0, body.body.length - 1);
|
|
321
|
-
body.body.splice(insertAt, 0, ...trackStatements);
|
|
322
|
-
} else {
|
|
323
|
-
body.body.splice(lastIdx, 0, ...trackStatements);
|
|
324
|
-
}
|
|
325
|
-
tagInstrumented(fn);
|
|
326
|
-
return true;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Ensure `import { gurulu } from <specifier>` is present. No-op when an
|
|
331
|
-
* existing import already binds the `gurulu` named export from a path
|
|
332
|
-
* pointing at our singleton helper (alias OR relative). Sprint D / D4: the
|
|
333
|
-
* specifier is derived from tsconfig — callers may pass an explicit one.
|
|
334
|
-
*/
|
|
335
|
-
function ensureGuruluImport(ast, specifierOpt) {
|
|
336
|
-
const specifier = specifierOpt || '@/lib/gurulu';
|
|
337
|
-
let present = false;
|
|
338
|
-
let lastImportIdx = -1;
|
|
339
|
-
const body = ast.program.body;
|
|
340
|
-
for (let i = 0; i < body.length; i++) {
|
|
341
|
-
const node = body[i];
|
|
342
|
-
if (t.isImportDeclaration(node)) {
|
|
343
|
-
lastImportIdx = i;
|
|
344
|
-
const src = node.source && node.source.value;
|
|
345
|
-
if (
|
|
346
|
-
src === specifier ||
|
|
347
|
-
src === '@/lib/gurulu' ||
|
|
348
|
-
(typeof src === 'string' && /(^|\/)lib\/gurulu$/.test(src))
|
|
349
|
-
) {
|
|
350
|
-
const hasNamed = (node.specifiers || []).some(
|
|
351
|
-
(s) => t.isImportSpecifier(s) && t.isIdentifier(s.imported) && s.imported.name === 'gurulu',
|
|
352
|
-
);
|
|
353
|
-
if (hasNamed) present = true;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
if (present) return;
|
|
358
|
-
const imp = t.importDeclaration(
|
|
359
|
-
[t.importSpecifier(t.identifier('gurulu'), t.identifier('gurulu'))],
|
|
360
|
-
t.stringLiteral(specifier),
|
|
361
|
-
);
|
|
362
|
-
body.splice(lastImportIdx + 1, 0, imp);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Walk the AST and locate the underlying function node for a named export
|
|
367
|
-
* (handles `export async function NAME(){}`, `export const NAME = () => {}`,
|
|
368
|
-
* `export { NAME }` re-exports where NAME binds to a locally-defined function,
|
|
369
|
-
* and `export default` when `defaultName === true`).
|
|
370
|
-
*
|
|
371
|
-
* Returns an array of `{ fn, body }` entries (multiple when the name matches
|
|
372
|
-
* several export shapes in the same file, which is rare but tolerated).
|
|
373
|
-
*/
|
|
374
|
-
function findExportedFunction(ast, exportName, opts = {}) {
|
|
375
|
-
const results = [];
|
|
376
|
-
const wantDefault = !!opts.defaultExport;
|
|
377
|
-
|
|
378
|
-
// Resolve local bindings for later lookup of re-exports + const-init arrows.
|
|
379
|
-
const localBindings = new Map();
|
|
380
|
-
|
|
381
|
-
traverse(ast, {
|
|
382
|
-
FunctionDeclaration(pathNode) {
|
|
383
|
-
if (pathNode.node.id && pathNode.node.id.name) {
|
|
384
|
-
localBindings.set(pathNode.node.id.name, pathNode.node);
|
|
385
|
-
}
|
|
386
|
-
},
|
|
387
|
-
VariableDeclaration(pathNode) {
|
|
388
|
-
for (const decl of pathNode.node.declarations) {
|
|
389
|
-
if (!t.isIdentifier(decl.id)) continue;
|
|
390
|
-
if (!decl.init) continue;
|
|
391
|
-
if (
|
|
392
|
-
t.isArrowFunctionExpression(decl.init) ||
|
|
393
|
-
t.isFunctionExpression(decl.init)
|
|
394
|
-
) {
|
|
395
|
-
localBindings.set(decl.id.name, decl.init);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
},
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
traverse(ast, {
|
|
402
|
-
ExportNamedDeclaration(pathNode) {
|
|
403
|
-
const decl = pathNode.node.declaration;
|
|
404
|
-
if (decl) {
|
|
405
|
-
if (t.isFunctionDeclaration(decl) && decl.id && decl.id.name === exportName) {
|
|
406
|
-
results.push({ fn: decl, body: decl.body });
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
if (t.isVariableDeclaration(decl)) {
|
|
410
|
-
for (const d of decl.declarations) {
|
|
411
|
-
if (!t.isIdentifier(d.id) || d.id.name !== exportName) continue;
|
|
412
|
-
if (t.isArrowFunctionExpression(d.init) || t.isFunctionExpression(d.init)) {
|
|
413
|
-
// Arrow functions need a block body for injection.
|
|
414
|
-
if (t.isBlockStatement(d.init.body)) {
|
|
415
|
-
results.push({ fn: d.init, body: d.init.body });
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
// `export { foo }` re-export referencing local binding.
|
|
422
|
-
for (const spec of pathNode.node.specifiers || []) {
|
|
423
|
-
if (!t.isExportSpecifier(spec)) continue;
|
|
424
|
-
const exported = t.isIdentifier(spec.exported) ? spec.exported.name : null;
|
|
425
|
-
if (exported !== exportName) continue;
|
|
426
|
-
const localName = t.isIdentifier(spec.local) ? spec.local.name : null;
|
|
427
|
-
if (!localName) continue;
|
|
428
|
-
const bound = localBindings.get(localName);
|
|
429
|
-
if (!bound) continue;
|
|
430
|
-
if (t.isBlockStatement(bound.body)) {
|
|
431
|
-
results.push({ fn: bound, body: bound.body });
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
},
|
|
435
|
-
ExportDefaultDeclaration(pathNode) {
|
|
436
|
-
if (!wantDefault) return;
|
|
437
|
-
const decl = pathNode.node.declaration;
|
|
438
|
-
if (t.isFunctionDeclaration(decl) || t.isFunctionExpression(decl)) {
|
|
439
|
-
if (t.isBlockStatement(decl.body)) {
|
|
440
|
-
results.push({ fn: decl, body: decl.body });
|
|
441
|
-
}
|
|
442
|
-
} else if (t.isArrowFunctionExpression(decl)) {
|
|
443
|
-
if (t.isBlockStatement(decl.body)) {
|
|
444
|
-
results.push({ fn: decl, body: decl.body });
|
|
445
|
-
}
|
|
446
|
-
} else if (t.isIdentifier(decl)) {
|
|
447
|
-
const bound = localBindings.get(decl.name);
|
|
448
|
-
if (bound && t.isBlockStatement(bound.body)) {
|
|
449
|
-
results.push({ fn: bound, body: bound.body });
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
},
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
return results;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
module.exports = {
|
|
459
|
-
parser,
|
|
460
|
-
traverse,
|
|
461
|
-
generate,
|
|
462
|
-
t,
|
|
463
|
-
parseSource,
|
|
464
|
-
generateSource,
|
|
465
|
-
hasInstrumentedMarker,
|
|
466
|
-
tagInstrumented,
|
|
467
|
-
buildTrackStatement,
|
|
468
|
-
buildTypedHelperCall,
|
|
469
|
-
injectTrackBeforeLastReturn,
|
|
470
|
-
ensureGuruluImport,
|
|
471
|
-
findExportedFunction,
|
|
472
|
-
hasAtAlias,
|
|
473
|
-
relativeHelperSpecifier,
|
|
474
|
-
resolveGuruluImportSpecifier,
|
|
475
|
-
sdkHelperMap,
|
|
476
|
-
IMPORT_LINE,
|
|
477
|
-
HELPER_REL_PATH,
|
|
478
|
-
MARKER,
|
|
479
|
-
MARKER_COMMENT,
|
|
480
|
-
};
|