@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.
Files changed (190) hide show
  1. package/LICENSE +92 -0
  2. package/README.md +35 -106
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +25751 -0
  6. package/dist/commands/auth.d.ts +23 -20
  7. package/dist/commands/auth.d.ts.map +1 -0
  8. package/dist/commands/doctor.d.ts +20 -6
  9. package/dist/commands/doctor.d.ts.map +1 -0
  10. package/dist/commands/init.d.ts +33 -11
  11. package/dist/commands/init.d.ts.map +1 -0
  12. package/dist/commands/pull.d.ts +13 -0
  13. package/dist/commands/pull.d.ts.map +1 -0
  14. package/dist/commands/push.d.ts +40 -0
  15. package/dist/commands/push.d.ts.map +1 -0
  16. package/dist/commands/validate.d.ts +36 -0
  17. package/dist/commands/validate.d.ts.map +1 -0
  18. package/dist/index.d.ts +4 -1
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +25326 -876
  21. package/dist/lib/api.d.ts +139 -0
  22. package/dist/lib/api.d.ts.map +1 -0
  23. package/dist/lib/codegen.d.ts +4 -0
  24. package/dist/lib/codegen.d.ts.map +1 -0
  25. package/dist/lib/config.d.ts +43 -0
  26. package/dist/lib/config.d.ts.map +1 -0
  27. package/dist/lib/detect.d.ts +27 -0
  28. package/dist/lib/detect.d.ts.map +1 -0
  29. package/dist/lib/detect.js +106 -0
  30. package/dist/lib/exec-install.d.ts +21 -0
  31. package/dist/lib/exec-install.d.ts.map +1 -0
  32. package/dist/lib/install-plan.d.ts +25 -0
  33. package/dist/lib/install-plan.d.ts.map +1 -0
  34. package/dist/lib/install-plan.js +161 -0
  35. package/package.json +51 -20
  36. package/bin/gurulu.js +0 -2
  37. package/dist/api-client.d.ts +0 -33
  38. package/dist/api-client.js +0 -175
  39. package/dist/commands/add-server.d.ts +0 -9
  40. package/dist/commands/add-server.js +0 -162
  41. package/dist/commands/alerts.d.ts +0 -27
  42. package/dist/commands/alerts.js +0 -309
  43. package/dist/commands/api-keys.d.ts +0 -20
  44. package/dist/commands/api-keys.js +0 -130
  45. package/dist/commands/attribution.d.ts +0 -22
  46. package/dist/commands/attribution.js +0 -111
  47. package/dist/commands/audiences.d.ts +0 -23
  48. package/dist/commands/audiences.js +0 -243
  49. package/dist/commands/audit.d.ts +0 -20
  50. package/dist/commands/audit.js +0 -130
  51. package/dist/commands/auth.js +0 -249
  52. package/dist/commands/chat.d.ts +0 -19
  53. package/dist/commands/chat.js +0 -118
  54. package/dist/commands/config.d.ts +0 -10
  55. package/dist/commands/config.js +0 -92
  56. package/dist/commands/consent.d.ts +0 -27
  57. package/dist/commands/consent.js +0 -233
  58. package/dist/commands/conversion-paths.d.ts +0 -19
  59. package/dist/commands/conversion-paths.js +0 -55
  60. package/dist/commands/db.d.ts +0 -25
  61. package/dist/commands/db.js +0 -330
  62. package/dist/commands/destinations.d.ts +0 -20
  63. package/dist/commands/destinations.js +0 -191
  64. package/dist/commands/doctor.js +0 -360
  65. package/dist/commands/errors.d.ts +0 -27
  66. package/dist/commands/errors.js +0 -121
  67. package/dist/commands/events.d.ts +0 -33
  68. package/dist/commands/events.js +0 -371
  69. package/dist/commands/experiments.d.ts +0 -22
  70. package/dist/commands/experiments.js +0 -264
  71. package/dist/commands/funnels.d.ts +0 -17
  72. package/dist/commands/funnels.js +0 -203
  73. package/dist/commands/goals.d.ts +0 -18
  74. package/dist/commands/goals.js +0 -214
  75. package/dist/commands/heatmap.d.ts +0 -27
  76. package/dist/commands/heatmap.js +0 -112
  77. package/dist/commands/identity.d.ts +0 -29
  78. package/dist/commands/identity.js +0 -328
  79. package/dist/commands/init.js +0 -215
  80. package/dist/commands/insights.d.ts +0 -10
  81. package/dist/commands/insights.js +0 -77
  82. package/dist/commands/install.d.ts +0 -259
  83. package/dist/commands/install.js +0 -1590
  84. package/dist/commands/login.d.ts +0 -20
  85. package/dist/commands/login.js +0 -170
  86. package/dist/commands/logout.d.ts +0 -10
  87. package/dist/commands/logout.js +0 -41
  88. package/dist/commands/playground.d.ts +0 -11
  89. package/dist/commands/playground.js +0 -47
  90. package/dist/commands/releases.d.ts +0 -17
  91. package/dist/commands/releases.js +0 -54
  92. package/dist/commands/replay.d.ts +0 -18
  93. package/dist/commands/replay.js +0 -64
  94. package/dist/commands/secrets.d.ts +0 -19
  95. package/dist/commands/secrets.js +0 -145
  96. package/dist/commands/setup.d.ts +0 -21
  97. package/dist/commands/setup.js +0 -67
  98. package/dist/commands/sites.d.ts +0 -18
  99. package/dist/commands/sites.js +0 -139
  100. package/dist/commands/skad.d.ts +0 -18
  101. package/dist/commands/skad.js +0 -53
  102. package/dist/commands/sourcemap.d.ts +0 -33
  103. package/dist/commands/sourcemap.js +0 -204
  104. package/dist/commands/status.d.ts +0 -7
  105. package/dist/commands/status.js +0 -136
  106. package/dist/commands/upgrade.d.ts +0 -21
  107. package/dist/commands/upgrade.js +0 -183
  108. package/dist/commands/warehouse.d.ts +0 -20
  109. package/dist/commands/warehouse.js +0 -65
  110. package/dist/commands/warehouses.d.ts +0 -17
  111. package/dist/commands/warehouses.js +0 -182
  112. package/dist/commands/watch.d.ts +0 -45
  113. package/dist/commands/watch.js +0 -258
  114. package/dist/commands/whoami.d.ts +0 -9
  115. package/dist/commands/whoami.js +0 -50
  116. package/dist/config.d.ts +0 -75
  117. package/dist/config.js +0 -329
  118. package/dist/frameworks/detect.d.ts +0 -8
  119. package/dist/frameworks/detect.js +0 -458
  120. package/dist/install-intent-proposal.d.ts +0 -99
  121. package/dist/install-intent-proposal.js +0 -202
  122. package/dist/utils/api.d.ts +0 -20
  123. package/dist/utils/api.js +0 -47
  124. package/dist/utils/config.d.ts +0 -13
  125. package/dist/utils/config.js +0 -30
  126. package/dist/utils/confirm.d.ts +0 -17
  127. package/dist/utils/confirm.js +0 -40
  128. package/dist/utils/dry-run.d.ts +0 -20
  129. package/dist/utils/dry-run.js +0 -67
  130. package/dist/utils/from-file.d.ts +0 -9
  131. package/dist/utils/from-file.js +0 -72
  132. package/dist/utils/redact.d.ts +0 -14
  133. package/dist/utils/redact.js +0 -48
  134. package/dist/utils/ui.d.ts +0 -14
  135. package/dist/utils/ui.js +0 -59
  136. package/scripts/.gitkeep +0 -0
  137. package/scripts/README-gurulu-agentic-install.md +0 -114
  138. package/scripts/README-gurulu-scan.md +0 -98
  139. package/scripts/audit-cli-scopes.mjs +0 -204
  140. package/scripts/backfill-tenant-id.mjs +0 -172
  141. package/scripts/backfill-tenant-links.ts +0 -252
  142. package/scripts/backup-clickhouse.sh +0 -27
  143. package/scripts/backup-postgres.sh +0 -19
  144. package/scripts/bootstrap-runtime-schema.mjs +0 -87
  145. package/scripts/bootstrap-stripe.mjs +0 -158
  146. package/scripts/gurulu-agentic-install.lib.cjs +0 -762
  147. package/scripts/gurulu-agentic-install.mjs +0 -623
  148. package/scripts/gurulu-scan.lib.cjs +0 -1509
  149. package/scripts/gurulu-scan.mjs +0 -91
  150. package/scripts/gurulu-verify-install.lib.cjs +0 -334
  151. package/scripts/gurulu-verify-install.mjs +0 -59
  152. package/scripts/init-ssl.sh +0 -26
  153. package/scripts/migrate-flow-graph-enums.sh +0 -86
  154. package/scripts/monitor-disk.sh +0 -24
  155. package/scripts/patches/astro.patch.cjs +0 -74
  156. package/scripts/patches/auto-instrument/ast-helper.cjs +0 -480
  157. package/scripts/patches/auto-instrument/astro.cjs +0 -273
  158. package/scripts/patches/auto-instrument/express.cjs +0 -383
  159. package/scripts/patches/auto-instrument/fastify.cjs +0 -262
  160. package/scripts/patches/auto-instrument/hono.cjs +0 -392
  161. package/scripts/patches/auto-instrument/index.cjs +0 -80
  162. package/scripts/patches/auto-instrument/nestjs.cjs +0 -286
  163. package/scripts/patches/auto-instrument/nextjs-app-router.cjs +0 -345
  164. package/scripts/patches/auto-instrument/nextjs-pages.cjs +0 -361
  165. package/scripts/patches/auto-instrument/remix.cjs +0 -168
  166. package/scripts/patches/auto-instrument/sdk-helper-map.cjs +0 -241
  167. package/scripts/patches/auto-instrument/singleton-helper.cjs +0 -193
  168. package/scripts/patches/auto-instrument/sveltekit.cjs +0 -161
  169. package/scripts/patches/auto-instrument/vite-react.cjs +0 -37
  170. package/scripts/patches/auto-instrument/vue.cjs +0 -196
  171. package/scripts/patches/express.patch.cjs +0 -99
  172. package/scripts/patches/fastify.patch.cjs +0 -108
  173. package/scripts/patches/index.cjs +0 -300
  174. package/scripts/patches/nestjs.patch.cjs +0 -112
  175. package/scripts/patches/nextjs-app-router.patch.cjs +0 -97
  176. package/scripts/patches/nextjs-pages.patch.cjs +0 -97
  177. package/scripts/patches/remix.patch.cjs +0 -75
  178. package/scripts/patches/sveltekit.patch.cjs +0 -72
  179. package/scripts/patches/vite-react.patch.cjs +0 -73
  180. package/scripts/patches/vue.patch.cjs +0 -82
  181. package/scripts/renew-ssl.sh +0 -14
  182. package/scripts/resolve-migration.sh +0 -23
  183. package/scripts/seed-cli-dev-keys.mjs +0 -130
  184. package/scripts/seed-test-data.mjs +0 -391
  185. package/scripts/spike-browserless.ts +0 -65
  186. package/scripts/tenant-pivot-consistency-check.mjs +0 -205
  187. package/scripts/tenant-pivot-phase-3-cleanup.lib.cjs +0 -258
  188. package/scripts/tenant-pivot-phase-3-cleanup.mjs +0 -98
  189. package/scripts/test-identity-resolution.ts +0 -804
  190. 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
- };