@gurulu/cli 0.4.7 → 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.
Files changed (182) 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 +25410 -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 +25 -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 +24985 -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/package.json +40 -20
  28. package/bin/gurulu.js +0 -2
  29. package/dist/api-client.d.ts +0 -33
  30. package/dist/api-client.js +0 -175
  31. package/dist/commands/add-server.d.ts +0 -9
  32. package/dist/commands/add-server.js +0 -162
  33. package/dist/commands/alerts.d.ts +0 -27
  34. package/dist/commands/alerts.js +0 -309
  35. package/dist/commands/api-keys.d.ts +0 -20
  36. package/dist/commands/api-keys.js +0 -130
  37. package/dist/commands/attribution.d.ts +0 -22
  38. package/dist/commands/attribution.js +0 -111
  39. package/dist/commands/audiences.d.ts +0 -23
  40. package/dist/commands/audiences.js +0 -243
  41. package/dist/commands/audit.d.ts +0 -20
  42. package/dist/commands/audit.js +0 -130
  43. package/dist/commands/auth.js +0 -249
  44. package/dist/commands/chat.d.ts +0 -19
  45. package/dist/commands/chat.js +0 -118
  46. package/dist/commands/config.d.ts +0 -10
  47. package/dist/commands/config.js +0 -92
  48. package/dist/commands/consent.d.ts +0 -27
  49. package/dist/commands/consent.js +0 -233
  50. package/dist/commands/conversion-paths.d.ts +0 -19
  51. package/dist/commands/conversion-paths.js +0 -55
  52. package/dist/commands/db.d.ts +0 -25
  53. package/dist/commands/db.js +0 -330
  54. package/dist/commands/destinations.d.ts +0 -20
  55. package/dist/commands/destinations.js +0 -191
  56. package/dist/commands/doctor.js +0 -360
  57. package/dist/commands/errors.d.ts +0 -27
  58. package/dist/commands/errors.js +0 -121
  59. package/dist/commands/events.d.ts +0 -33
  60. package/dist/commands/events.js +0 -371
  61. package/dist/commands/experiments.d.ts +0 -22
  62. package/dist/commands/experiments.js +0 -264
  63. package/dist/commands/funnels.d.ts +0 -17
  64. package/dist/commands/funnels.js +0 -203
  65. package/dist/commands/goals.d.ts +0 -18
  66. package/dist/commands/goals.js +0 -214
  67. package/dist/commands/heatmap.d.ts +0 -27
  68. package/dist/commands/heatmap.js +0 -112
  69. package/dist/commands/identity.d.ts +0 -29
  70. package/dist/commands/identity.js +0 -328
  71. package/dist/commands/init.js +0 -215
  72. package/dist/commands/insights.d.ts +0 -10
  73. package/dist/commands/insights.js +0 -77
  74. package/dist/commands/install.d.ts +0 -259
  75. package/dist/commands/install.js +0 -1590
  76. package/dist/commands/login.d.ts +0 -20
  77. package/dist/commands/login.js +0 -170
  78. package/dist/commands/logout.d.ts +0 -10
  79. package/dist/commands/logout.js +0 -41
  80. package/dist/commands/playground.d.ts +0 -11
  81. package/dist/commands/playground.js +0 -47
  82. package/dist/commands/releases.d.ts +0 -17
  83. package/dist/commands/releases.js +0 -54
  84. package/dist/commands/replay.d.ts +0 -18
  85. package/dist/commands/replay.js +0 -64
  86. package/dist/commands/secrets.d.ts +0 -19
  87. package/dist/commands/secrets.js +0 -145
  88. package/dist/commands/setup.d.ts +0 -21
  89. package/dist/commands/setup.js +0 -67
  90. package/dist/commands/sites.d.ts +0 -18
  91. package/dist/commands/sites.js +0 -139
  92. package/dist/commands/skad.d.ts +0 -18
  93. package/dist/commands/skad.js +0 -53
  94. package/dist/commands/sourcemap.d.ts +0 -33
  95. package/dist/commands/sourcemap.js +0 -204
  96. package/dist/commands/status.d.ts +0 -7
  97. package/dist/commands/status.js +0 -136
  98. package/dist/commands/upgrade.d.ts +0 -21
  99. package/dist/commands/upgrade.js +0 -183
  100. package/dist/commands/warehouse.d.ts +0 -20
  101. package/dist/commands/warehouse.js +0 -65
  102. package/dist/commands/warehouses.d.ts +0 -17
  103. package/dist/commands/warehouses.js +0 -182
  104. package/dist/commands/watch.d.ts +0 -45
  105. package/dist/commands/watch.js +0 -258
  106. package/dist/commands/whoami.d.ts +0 -9
  107. package/dist/commands/whoami.js +0 -50
  108. package/dist/config.d.ts +0 -75
  109. package/dist/config.js +0 -329
  110. package/dist/frameworks/detect.d.ts +0 -8
  111. package/dist/frameworks/detect.js +0 -458
  112. package/dist/install-intent-proposal.d.ts +0 -99
  113. package/dist/install-intent-proposal.js +0 -202
  114. package/dist/utils/api.d.ts +0 -20
  115. package/dist/utils/api.js +0 -47
  116. package/dist/utils/config.d.ts +0 -13
  117. package/dist/utils/config.js +0 -30
  118. package/dist/utils/confirm.d.ts +0 -17
  119. package/dist/utils/confirm.js +0 -40
  120. package/dist/utils/dry-run.d.ts +0 -20
  121. package/dist/utils/dry-run.js +0 -67
  122. package/dist/utils/from-file.d.ts +0 -9
  123. package/dist/utils/from-file.js +0 -72
  124. package/dist/utils/redact.d.ts +0 -14
  125. package/dist/utils/redact.js +0 -48
  126. package/dist/utils/ui.d.ts +0 -14
  127. package/dist/utils/ui.js +0 -59
  128. package/scripts/.gitkeep +0 -0
  129. package/scripts/README-gurulu-agentic-install.md +0 -114
  130. package/scripts/README-gurulu-scan.md +0 -98
  131. package/scripts/audit-cli-scopes.mjs +0 -204
  132. package/scripts/backfill-tenant-id.mjs +0 -172
  133. package/scripts/backfill-tenant-links.ts +0 -252
  134. package/scripts/backup-clickhouse.sh +0 -27
  135. package/scripts/backup-postgres.sh +0 -19
  136. package/scripts/bootstrap-runtime-schema.mjs +0 -87
  137. package/scripts/bootstrap-stripe.mjs +0 -158
  138. package/scripts/gurulu-agentic-install.lib.cjs +0 -762
  139. package/scripts/gurulu-agentic-install.mjs +0 -623
  140. package/scripts/gurulu-scan.lib.cjs +0 -1509
  141. package/scripts/gurulu-scan.mjs +0 -91
  142. package/scripts/gurulu-verify-install.lib.cjs +0 -334
  143. package/scripts/gurulu-verify-install.mjs +0 -59
  144. package/scripts/init-ssl.sh +0 -26
  145. package/scripts/migrate-flow-graph-enums.sh +0 -86
  146. package/scripts/monitor-disk.sh +0 -24
  147. package/scripts/patches/astro.patch.cjs +0 -74
  148. package/scripts/patches/auto-instrument/ast-helper.cjs +0 -480
  149. package/scripts/patches/auto-instrument/astro.cjs +0 -273
  150. package/scripts/patches/auto-instrument/express.cjs +0 -383
  151. package/scripts/patches/auto-instrument/fastify.cjs +0 -262
  152. package/scripts/patches/auto-instrument/hono.cjs +0 -392
  153. package/scripts/patches/auto-instrument/index.cjs +0 -80
  154. package/scripts/patches/auto-instrument/nestjs.cjs +0 -286
  155. package/scripts/patches/auto-instrument/nextjs-app-router.cjs +0 -345
  156. package/scripts/patches/auto-instrument/nextjs-pages.cjs +0 -361
  157. package/scripts/patches/auto-instrument/remix.cjs +0 -168
  158. package/scripts/patches/auto-instrument/sdk-helper-map.cjs +0 -241
  159. package/scripts/patches/auto-instrument/singleton-helper.cjs +0 -193
  160. package/scripts/patches/auto-instrument/sveltekit.cjs +0 -161
  161. package/scripts/patches/auto-instrument/vite-react.cjs +0 -37
  162. package/scripts/patches/auto-instrument/vue.cjs +0 -196
  163. package/scripts/patches/express.patch.cjs +0 -99
  164. package/scripts/patches/fastify.patch.cjs +0 -108
  165. package/scripts/patches/index.cjs +0 -300
  166. package/scripts/patches/nestjs.patch.cjs +0 -112
  167. package/scripts/patches/nextjs-app-router.patch.cjs +0 -97
  168. package/scripts/patches/nextjs-pages.patch.cjs +0 -97
  169. package/scripts/patches/remix.patch.cjs +0 -75
  170. package/scripts/patches/sveltekit.patch.cjs +0 -72
  171. package/scripts/patches/vite-react.patch.cjs +0 -73
  172. package/scripts/patches/vue.patch.cjs +0 -82
  173. package/scripts/renew-ssl.sh +0 -14
  174. package/scripts/resolve-migration.sh +0 -23
  175. package/scripts/seed-cli-dev-keys.mjs +0 -130
  176. package/scripts/seed-test-data.mjs +0 -391
  177. package/scripts/spike-browserless.ts +0 -65
  178. package/scripts/tenant-pivot-consistency-check.mjs +0 -205
  179. package/scripts/tenant-pivot-phase-3-cleanup.lib.cjs +0 -258
  180. package/scripts/tenant-pivot-phase-3-cleanup.mjs +0 -98
  181. package/scripts/test-identity-resolution.ts +0 -804
  182. package/scripts/validate-gurulu-schemas.mjs +0 -79
@@ -1,300 +0,0 @@
1
- // scripts/patches/index.cjs — Phase 16 A1 patch engine registry.
2
- //
3
- // Each patcher exports { name, detect, plan, apply, rollback }. The CLI
4
- // resolves them in priority order via `resolvePatcher()`. Backups and patch
5
- // logs are produced by shared helpers in this file.
6
- //
7
- // Pure Node, no deps. Regex-based patching (ts-morph not available).
8
-
9
- const fs = require('fs');
10
- const path = require('path');
11
- const crypto = require('crypto');
12
-
13
- // ---------------------------------------------------------------------------
14
- // BEGIN: Agent A (Phase 18.7) — Framework patcher registration
15
- // Script-tag injection patchers. Order matters for auto-detection: more
16
- // specific frameworks first (Next.js before generic Vite+React, Remix
17
- // before Vue, NestJS/Fastify server-only last so SPA frontends in the
18
- // same repo win).
19
- // ---------------------------------------------------------------------------
20
- const nextAppRouter = require('./nextjs-app-router.patch.cjs');
21
- const nextPages = require('./nextjs-pages.patch.cjs');
22
- const remix = require('./remix.patch.cjs');
23
- const sveltekit = require('./sveltekit.patch.cjs');
24
- const astro = require('./astro.patch.cjs');
25
- const viteReact = require('./vite-react.patch.cjs');
26
- const vue = require('./vue.patch.cjs');
27
- const nestjs = require('./nestjs.patch.cjs');
28
- const fastify = require('./fastify.patch.cjs');
29
- const express = require('./express.patch.cjs');
30
-
31
- const PATCHERS = [
32
- nextAppRouter,
33
- nextPages,
34
- remix,
35
- sveltekit,
36
- astro,
37
- viteReact,
38
- vue,
39
- nestjs,
40
- fastify,
41
- express,
42
- ];
43
-
44
- const PATCHER_BY_NAME = {
45
- // Sprint D / D3 — `nextjs` (bare) is the alias most agents emit. Resolve it
46
- // to the App Router patcher (the modern default). The Pages Router can
47
- // still be selected explicitly via `nextjs-pages`. Auto-detection (in the
48
- // PATCHERS array above) handles the case where a project actually uses
49
- // Pages Router and the agent passed `auto`.
50
- nextjs: nextAppRouter,
51
- 'nextjs-app': nextAppRouter,
52
- 'nextjs-app-router': nextAppRouter,
53
- 'nextjs-pages': nextPages,
54
- remix,
55
- sveltekit,
56
- astro,
57
- 'vite-react': viteReact,
58
- vue,
59
- vue3: vue,
60
- nestjs,
61
- fastify,
62
- express,
63
- };
64
- // ---------------------------------------------------------------------------
65
- // END: Agent A (Phase 18.7) — Framework patcher registration
66
- // NOTE for Agent B: Auto-instrument dispatcher goes in its own section below
67
- // (or in scripts/patches/auto-instrument/index.cjs). Do not inline it in the
68
- // PATCHERS array above — those are script-tag patchers only.
69
- // ---------------------------------------------------------------------------
70
-
71
- // ---------------------------------------------------------------------------
72
- // BEGIN: Agent B (Phase 18.7) — Auto-instrumentation dispatcher
73
- // Route-handler instrumentation modules live in `./auto-instrument/*.cjs`.
74
- // They share a singleton-helper utility that keeps `src/lib/gurulu.ts`,
75
- // `package.json`, and `.env.local` / `.env` in sync. The dispatcher below
76
- // is a thin wrapper that `scripts/gurulu-agentic-install.mjs` calls when
77
- // `--auto-instrument` is set. Changes produced by auto-instrument modules
78
- // are stamped `type: 'auto-instrument'` so `applyAutoInstrumentPlan()` can
79
- // append them to the existing `.gurulu/patch-log.json` (rather than
80
- // overwriting the script-tag entries) — `rollback()` then restores both
81
- // patch generations in a single pass.
82
- // ---------------------------------------------------------------------------
83
- const autoInstrumentDispatcher = require('./auto-instrument/index.cjs');
84
-
85
- function autoInstrumentDispatch(framework, ctx, events) {
86
- return autoInstrumentDispatcher.dispatch(framework, ctx, events);
87
- }
88
- // ---------------------------------------------------------------------------
89
- // END: Agent B (Phase 18.7) — Auto-instrumentation dispatcher
90
- // ---------------------------------------------------------------------------
91
-
92
- // Tracker tag context — a small object the patchers stamp into the code they inject.
93
- function buildInjection(opts) {
94
- const siteId = opts.siteId || '';
95
- const tenantId = opts.tenantId || '';
96
- const publishableKey = opts.publishableKey || '';
97
- const src = opts.scriptSrc || 'https://gurulu.io/t.js';
98
- return { siteId, tenantId, publishableKey, scriptSrc: src };
99
- }
100
-
101
- function resolvePatcher(repoRoot, frameworkHint) {
102
- if (frameworkHint && frameworkHint !== 'auto') {
103
- const p = PATCHER_BY_NAME[frameworkHint];
104
- if (!p) return { patcher: null, detection: null, error: `Unknown framework: ${frameworkHint}` };
105
- const detection = p.detect(repoRoot);
106
- return { patcher: p, detection: detection || { framework: p.name, files: [] } };
107
- }
108
- for (const p of PATCHERS) {
109
- const detection = p.detect(repoRoot);
110
- if (detection) return { patcher: p, detection };
111
- }
112
- return { patcher: null, detection: null };
113
- }
114
-
115
- // Backup one file into .gurulu-backup/<timestamp>/<relPath>.
116
- function backupFile(repoRoot, relPath, timestamp) {
117
- const abs = path.join(repoRoot, relPath);
118
- const original = fs.readFileSync(abs, 'utf8');
119
- const backupDir = path.join(repoRoot, '.gurulu-backup', timestamp);
120
- const backupPath = path.join(backupDir, relPath);
121
- fs.mkdirSync(path.dirname(backupPath), { recursive: true });
122
- fs.writeFileSync(backupPath, original, 'utf8');
123
- return { backupPath: path.relative(repoRoot, backupPath), original };
124
- }
125
-
126
- function hashContent(text) {
127
- return crypto.createHash('sha256').update(text).digest('hex').slice(0, 16);
128
- }
129
-
130
- function ensureGitignore(repoRoot) {
131
- const gi = path.join(repoRoot, '.gitignore');
132
- let contents = '';
133
- if (fs.existsSync(gi)) contents = fs.readFileSync(gi, 'utf8');
134
- if (!/\.gurulu-backup/.test(contents)) {
135
- const append = (contents.endsWith('\n') || !contents ? '' : '\n') + '.gurulu-backup/\n';
136
- fs.writeFileSync(gi, contents + append, 'utf8');
137
- return true;
138
- }
139
- return false;
140
- }
141
-
142
- // Utility: produce a plain unified-diff-ish output for dry-run (tiny, no deps).
143
- function unifiedDiff(relPath, before, after) {
144
- if (before === after) return `--- a/${relPath}\n+++ b/${relPath}\n(no changes)\n`;
145
- const beforeLines = before.split('\n');
146
- const afterLines = after.split('\n');
147
- const lines = [`--- a/${relPath}`, `+++ b/${relPath}`];
148
- // Naive: show all "-" then all "+" — sufficient for alpha dry-run display.
149
- for (const l of beforeLines) lines.push(`-${l}`);
150
- for (const l of afterLines) lines.push(`+${l}`);
151
- return lines.join('\n') + '\n';
152
- }
153
-
154
- // Plan → { changes: [{ relPath, before, after, reason }], notes: [] }
155
- // Apply consumes a plan, writes files, creates backups, writes patch-log.
156
- async function applyPlan(repoRoot, patcher, plan) {
157
- if (!plan || !plan.changes || plan.changes.length === 0) {
158
- return { applied: false, reason: 'no-changes', files: [] };
159
- }
160
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
161
- const fileRecords = [];
162
- for (const change of plan.changes) {
163
- const abs = path.join(repoRoot, change.relPath);
164
- // Ensure parent dir exists (for new files added by patcher if any).
165
- fs.mkdirSync(path.dirname(abs), { recursive: true });
166
- let backupRel = null;
167
- if (fs.existsSync(abs)) {
168
- const { backupPath } = backupFile(repoRoot, change.relPath, timestamp);
169
- backupRel = backupPath;
170
- }
171
- fs.writeFileSync(abs, change.after, 'utf8');
172
- fileRecords.push({
173
- path: change.relPath,
174
- hash: hashContent(change.after),
175
- backupPath: backupRel,
176
- type: change.type || 'script-tag',
177
- });
178
- }
179
- ensureGitignore(repoRoot);
180
- const logDir = path.join(repoRoot, '.gurulu');
181
- fs.mkdirSync(logDir, { recursive: true });
182
- const logFile = path.join(logDir, 'patch-log.json');
183
- const log = {
184
- appliedAt: new Date().toISOString(),
185
- framework: patcher.name,
186
- timestamp,
187
- files: fileRecords,
188
- rollbackAvailable: true,
189
- };
190
- fs.writeFileSync(logFile, JSON.stringify(log, null, 2) + '\n', 'utf8');
191
- return { applied: true, log, files: fileRecords };
192
- }
193
-
194
- // ---------------------------------------------------------------------------
195
- // Phase 18.7 B6 — applyAutoInstrumentPlan
196
- // Applies the auto-instrument plan (helper + route handler changes) using
197
- // the same backup mechanism as applyPlan, but APPENDS to an existing
198
- // `.gurulu/patch-log.json` rather than overwriting it. This lets the
199
- // script-tag patch and the auto-instrument patch coexist under a single
200
- // rollback pass.
201
- // ---------------------------------------------------------------------------
202
- async function applyAutoInstrumentPlan(repoRoot, plan) {
203
- if (!plan || !plan.changes || plan.changes.length === 0) {
204
- return { applied: false, reason: 'no-changes', files: [] };
205
- }
206
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
207
- const fileRecords = [];
208
- for (const change of plan.changes) {
209
- const abs = path.join(repoRoot, change.relPath);
210
- fs.mkdirSync(path.dirname(abs), { recursive: true });
211
- let backupRel = null;
212
- if (fs.existsSync(abs)) {
213
- const { backupPath } = backupFile(repoRoot, change.relPath, timestamp);
214
- backupRel = backupPath;
215
- }
216
- fs.writeFileSync(abs, change.after, 'utf8');
217
- fileRecords.push({
218
- path: change.relPath,
219
- hash: hashContent(change.after),
220
- backupPath: backupRel,
221
- type: change.type || 'auto-instrument',
222
- });
223
- }
224
- ensureGitignore(repoRoot);
225
- const logDir = path.join(repoRoot, '.gurulu');
226
- fs.mkdirSync(logDir, { recursive: true });
227
- const logFile = path.join(logDir, 'patch-log.json');
228
- let log;
229
- if (fs.existsSync(logFile)) {
230
- try {
231
- log = JSON.parse(fs.readFileSync(logFile, 'utf8'));
232
- } catch {
233
- log = null;
234
- }
235
- }
236
- if (!log) {
237
- log = {
238
- appliedAt: new Date().toISOString(),
239
- framework: plan.framework || 'auto-instrument',
240
- timestamp,
241
- files: [],
242
- rollbackAvailable: true,
243
- };
244
- }
245
- log.files = [...(log.files || []), ...fileRecords];
246
- log.autoInstrumentAppliedAt = new Date().toISOString();
247
- log.autoInstrumentTimestamp = timestamp;
248
- log.rollbackAvailable = true;
249
- fs.writeFileSync(logFile, JSON.stringify(log, null, 2) + '\n', 'utf8');
250
- return { applied: true, log, files: fileRecords };
251
- }
252
-
253
- function rollback(repoRoot) {
254
- const logFile = path.join(repoRoot, '.gurulu', 'patch-log.json');
255
- if (!fs.existsSync(logFile)) {
256
- return { rolledBack: false, reason: 'no-patch-log' };
257
- }
258
- const log = JSON.parse(fs.readFileSync(logFile, 'utf8'));
259
- if (!log.rollbackAvailable) return { rolledBack: false, reason: 'rollback-unavailable' };
260
- const restored = [];
261
- for (const f of log.files) {
262
- if (!f.backupPath) {
263
- // File was newly created by patcher — delete it.
264
- const abs = path.join(repoRoot, f.path);
265
- if (fs.existsSync(abs)) fs.unlinkSync(abs);
266
- restored.push({ path: f.path, action: 'deleted' });
267
- continue;
268
- }
269
- const backupAbs = path.join(repoRoot, f.backupPath);
270
- if (!fs.existsSync(backupAbs)) {
271
- restored.push({ path: f.path, action: 'missing-backup' });
272
- continue;
273
- }
274
- const original = fs.readFileSync(backupAbs, 'utf8');
275
- fs.writeFileSync(path.join(repoRoot, f.path), original, 'utf8');
276
- restored.push({ path: f.path, action: 'restored' });
277
- }
278
- // Mark log as consumed.
279
- log.rollbackAvailable = false;
280
- log.rolledBackAt = new Date().toISOString();
281
- fs.writeFileSync(logFile, JSON.stringify(log, null, 2) + '\n', 'utf8');
282
- return { rolledBack: true, files: restored };
283
- }
284
-
285
- module.exports = {
286
- PATCHERS,
287
- PATCHER_BY_NAME,
288
- resolvePatcher,
289
- buildInjection,
290
- backupFile,
291
- hashContent,
292
- ensureGitignore,
293
- unifiedDiff,
294
- applyPlan,
295
- rollback,
296
- // Phase 18.7 B — auto-instrumentation exports.
297
- autoInstrumentDispatch,
298
- autoInstrument: autoInstrumentDispatcher,
299
- applyAutoInstrumentPlan,
300
- };
@@ -1,112 +0,0 @@
1
- // nestjs.patch.cjs — Phase 18.7 A1
2
- //
3
- // NestJS is a server-side framework. Most real-world NestJS apps serve a
4
- // separate frontend (React/Vue/Angular) or bundle a static client under
5
- // `client/dist/` / `public/`. When we find a static `index.html` we patch
6
- // it with a plain <script> tag. Otherwise we emit a detection note telling
7
- // the operator to use --auto-instrument for server-side tracking (Phase
8
- // 18.7 Agent B territory).
9
-
10
- const fs = require('fs');
11
- const path = require('path');
12
-
13
- const NAME = 'nestjs';
14
- const HTML_CANDIDATES = [
15
- 'public/index.html',
16
- 'client/dist/index.html',
17
- 'client/build/index.html',
18
- 'dist/client/index.html',
19
- ];
20
- const SERVER_CANDIDATES = [
21
- 'src/main.ts',
22
- 'src/main.js',
23
- ];
24
-
25
- const MARKER = 'data-gurulu-install="1"';
26
-
27
- function looksLikeNest(repoRoot) {
28
- // Detect either via package.json `@nestjs/core` dep or the idiomatic
29
- // `NestFactory.create(...)` bootstrap call in main.ts.
30
- const pkgPath = path.join(repoRoot, 'package.json');
31
- if (fs.existsSync(pkgPath)) {
32
- try {
33
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
34
- const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
35
- if (deps['@nestjs/core']) return true;
36
- } catch {
37
- /* ignore malformed package.json */
38
- }
39
- }
40
- for (const rel of SERVER_CANDIDATES) {
41
- const abs = path.join(repoRoot, rel);
42
- if (fs.existsSync(abs)) {
43
- const source = fs.readFileSync(abs, 'utf8');
44
- if (/NestFactory\.create\s*\(/.test(source) || /@nestjs\/core/.test(source)) {
45
- return true;
46
- }
47
- }
48
- }
49
- return false;
50
- }
51
-
52
- function detect(repoRoot) {
53
- if (!looksLikeNest(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
- // Server-only — no frontend to patch. Return detection with empty file list
60
- // so the caller can surface the note.
61
- return { framework: NAME, files: [], serverOnly: true };
62
- }
63
-
64
- function buildScriptTag(injection) {
65
- const pkAttr = injection.publishableKey
66
- ? ` data-gurulu-publishable-key="${injection.publishableKey}"`
67
- : '';
68
- return (
69
- ` <script\n` +
70
- ` src="${injection.scriptSrc}"\n` +
71
- ` data-gurulu-site-id="${injection.siteId}"\n` +
72
- ` data-gurulu-tenant-id="${injection.tenantId}"${pkAttr}\n` +
73
- ` data-features="errors,replay,advanced"\n` +
74
- ` ${MARKER}\n` +
75
- ` async\n` +
76
- ` ></script>\n`
77
- );
78
- }
79
-
80
- function injectScriptTag(source, injection) {
81
- if (source.includes(MARKER)) return source;
82
- const tag = buildScriptTag(injection);
83
- if (/<\/body>/.test(source)) {
84
- return source.replace(/<\/body>/, `${tag} </body>`);
85
- }
86
- return source + '\n' + tag;
87
- }
88
-
89
- function plan(ctx) {
90
- const detection = detect(ctx.repoRoot);
91
- if (!detection) return { changes: [], notes: ['no-nestjs'] };
92
- if (detection.serverOnly) {
93
- return {
94
- changes: [],
95
- notes: [
96
- 'server-only; script injection skipped, use --auto-instrument for server-side tracking',
97
- ],
98
- };
99
- }
100
- const changes = [];
101
- for (const rel of detection.files) {
102
- const abs = path.join(ctx.repoRoot, rel);
103
- const before = fs.readFileSync(abs, 'utf8');
104
- const after = injectScriptTag(before, ctx.injection);
105
- if (after !== before) {
106
- changes.push({ relPath: rel, before, after, reason: 'inject-script-tag' });
107
- }
108
- }
109
- return { changes, notes: [] };
110
- }
111
-
112
- module.exports = { name: NAME, detect, plan };
@@ -1,97 +0,0 @@
1
- // nextjs-app-router.patch.cjs — Phase 16 A1
2
- //
3
- // Detects a Next.js App Router layout (`src/app/layout.tsx` or `app/layout.tsx`)
4
- // and injects a <Script> tag loaded via next/script that points to the Gurulu
5
- // Web SDK tracker (`https://gurulu.io/t.js` by default).
6
- //
7
- // Regex-based: fast alpha, good enough for most idiomatic RootLayouts.
8
-
9
- const fs = require('fs');
10
- const path = require('path');
11
-
12
- const NAME = 'nextjs-app-router';
13
- const CANDIDATES = [
14
- 'src/app/layout.tsx',
15
- 'src/app/layout.jsx',
16
- 'app/layout.tsx',
17
- 'app/layout.jsx',
18
- ];
19
-
20
- const MARKER = 'data-gurulu-install="1"';
21
-
22
- function detect(repoRoot) {
23
- for (const rel of CANDIDATES) {
24
- if (fs.existsSync(path.join(repoRoot, rel))) {
25
- return { framework: NAME, files: [rel] };
26
- }
27
- }
28
- return null;
29
- }
30
-
31
- function buildScriptTag(injection) {
32
- const pkAttr = injection.publishableKey
33
- ? ` data-gurulu-publishable-key=${JSON.stringify(injection.publishableKey)}\n`
34
- : '';
35
- return (
36
- ` <Script\n` +
37
- ` id="gurulu-tracker"\n` +
38
- ` src=${JSON.stringify(injection.scriptSrc)}\n` +
39
- ` strategy="afterInteractive"\n` +
40
- ` data-gurulu-site-id=${JSON.stringify(injection.siteId)}\n` +
41
- ` data-gurulu-tenant-id=${JSON.stringify(injection.tenantId)}\n` +
42
- pkAttr +
43
- ` data-features="errors,replay,advanced"\n` +
44
- ` ${MARKER}\n` +
45
- ` />\n`
46
- );
47
- }
48
-
49
- function ensureScriptImport(source) {
50
- if (/from\s+['"]next\/script['"]/.test(source)) return source;
51
- // Insert after the last import statement (or at top).
52
- const importRegex = /^(?:import[\s\S]*?;\s*\n)+/m;
53
- const m = source.match(importRegex);
54
- const inject = `import Script from 'next/script';\n`;
55
- if (m) {
56
- const end = m.index + m[0].length;
57
- return source.slice(0, end) + inject + source.slice(end);
58
- }
59
- return inject + source;
60
- }
61
-
62
- function injectScriptTag(source, injection) {
63
- if (source.includes(MARKER)) return source; // idempotent
64
- // Also detect existing tracker tags (manual or from other install methods)
65
- if (source.includes('data-site-id=') || source.includes('data-gurulu-site-id=') || source.includes('/t.js')) return source;
66
- const tag = buildScriptTag(injection);
67
- // Prefer to insert just before </body>. Fall back to before the closing
68
- // top-level fragment/element.
69
- if (/<\/body>/.test(source)) {
70
- return source.replace(/<\/body>/, `${tag} </body>`);
71
- }
72
- // Insert before the final closing tag of children area — best-effort.
73
- return source.replace(/(\n\s*<\/(?:html|main|div)>\s*\)\s*;?\s*\}\s*$)/, (m) => `\n${tag}${m}`);
74
- }
75
-
76
- function plan(ctx) {
77
- const detection = detect(ctx.repoRoot);
78
- if (!detection) return { changes: [], notes: ['no-layout-found'] };
79
- const changes = [];
80
- for (const rel of detection.files) {
81
- const abs = path.join(ctx.repoRoot, rel);
82
- const before = fs.readFileSync(abs, 'utf8');
83
- let after = ensureScriptImport(before);
84
- after = injectScriptTag(after, ctx.injection);
85
- if (after !== before) {
86
- changes.push({ relPath: rel, before, after, reason: 'inject-next-script' });
87
- } else {
88
- changes.push({ relPath: rel, before, after, reason: 'already-installed', skip: true });
89
- }
90
- }
91
- return {
92
- changes: changes.filter((c) => !c.skip),
93
- notes: changes.filter((c) => c.skip).map((c) => `skip:${c.relPath}`),
94
- };
95
- }
96
-
97
- module.exports = { name: NAME, detect, plan };
@@ -1,97 +0,0 @@
1
- // nextjs-pages.patch.cjs — Phase 16 A1
2
- //
3
- // Detects a legacy Next.js Pages Router `_app` file and injects a useEffect
4
- // that appends the Gurulu tracker <script> to document.head.
5
-
6
- const fs = require('fs');
7
- const path = require('path');
8
-
9
- const NAME = 'nextjs-pages';
10
- const CANDIDATES = [
11
- 'pages/_app.tsx',
12
- 'pages/_app.jsx',
13
- 'src/pages/_app.tsx',
14
- 'src/pages/_app.jsx',
15
- ];
16
-
17
- const MARKER = '/* gurulu:install */';
18
-
19
- function detect(repoRoot) {
20
- for (const rel of CANDIDATES) {
21
- if (fs.existsSync(path.join(repoRoot, rel))) {
22
- return { framework: NAME, files: [rel] };
23
- }
24
- }
25
- return null;
26
- }
27
-
28
- function ensureUseEffectImport(source) {
29
- if (/from\s+['"]react['"]/.test(source)) {
30
- if (/useEffect/.test(source)) return source;
31
- return source.replace(/import\s+([^;]*?)from\s+(['"])react\2/, (m, names, q) => {
32
- if (/\{[^}]*\}/.test(names)) {
33
- return m.replace(/\{([^}]*)\}/, (mm, inner) => `{${inner.trim()}, useEffect }`);
34
- }
35
- return `${m.trim()}\nimport { useEffect } from 'react'`;
36
- });
37
- }
38
- return `import { useEffect } from 'react';\n` + source;
39
- }
40
-
41
- function buildHook(injection) {
42
- const pkLine = injection.publishableKey
43
- ? ` s.setAttribute('data-gurulu-publishable-key', ${JSON.stringify(injection.publishableKey)});\n`
44
- : '';
45
- return (
46
- ` ${MARKER}\n` +
47
- ` useEffect(() => {\n` +
48
- ` if (typeof window === 'undefined') return;\n` +
49
- ` if (document.querySelector('script[data-gurulu-install]')) return;\n` +
50
- ` const s = document.createElement('script');\n` +
51
- ` s.src = ${JSON.stringify(injection.scriptSrc)};\n` +
52
- ` s.async = true;\n` +
53
- ` s.setAttribute('data-gurulu-install', '1');\n` +
54
- ` s.setAttribute('data-gurulu-site-id', ${JSON.stringify(injection.siteId)});\n` +
55
- ` s.setAttribute('data-gurulu-tenant-id', ${JSON.stringify(injection.tenantId)});\n` +
56
- pkLine +
57
- ` s.setAttribute('data-features', 'errors,replay,advanced');\n` +
58
- ` document.head.appendChild(s);\n` +
59
- ` }, []);\n`
60
- );
61
- }
62
-
63
- function injectHook(source, injection) {
64
- if (source.includes(MARKER)) return source;
65
- const hook = buildHook(injection);
66
- // Match: function MyApp({ Component, pageProps }) { ... — insert hook after `{`.
67
- const fnMatch = source.match(/function\s+\w+\s*\([^)]*\)\s*\{/);
68
- if (fnMatch) {
69
- const end = fnMatch.index + fnMatch[0].length;
70
- return source.slice(0, end) + '\n' + hook + source.slice(end);
71
- }
72
- // Arrow form: `const MyApp = ({ Component, pageProps }) => {`
73
- const arrowMatch = source.match(/const\s+\w+\s*=\s*\([^)]*\)\s*=>\s*\{/);
74
- if (arrowMatch) {
75
- const end = arrowMatch.index + arrowMatch[0].length;
76
- return source.slice(0, end) + '\n' + hook + source.slice(end);
77
- }
78
- return source;
79
- }
80
-
81
- function plan(ctx) {
82
- const detection = detect(ctx.repoRoot);
83
- if (!detection) return { changes: [], notes: ['no-_app-found'] };
84
- const changes = [];
85
- for (const rel of detection.files) {
86
- const abs = path.join(ctx.repoRoot, rel);
87
- const before = fs.readFileSync(abs, 'utf8');
88
- let after = injectHook(before, ctx.injection);
89
- if (after !== before) after = ensureUseEffectImport(after);
90
- if (after !== before) {
91
- changes.push({ relPath: rel, before, after, reason: 'inject-useeffect' });
92
- }
93
- }
94
- return { changes, notes: [] };
95
- }
96
-
97
- module.exports = { name: NAME, detect, plan };
@@ -1,75 +0,0 @@
1
- // remix.patch.cjs — Phase 18.7 A1
2
- //
3
- // Detects a Remix app by the presence of `app/root.tsx` (or `.jsx`) and
4
- // injects a plain <script> tag before the `<Scripts />` component inside
5
- // the root layout's <body>. Remix's <Scripts /> handles hydration; we keep
6
- // our tracker as a vanilla async <script> so it's independent of the React
7
- // runtime.
8
-
9
- const fs = require('fs');
10
- const path = require('path');
11
-
12
- const NAME = 'remix';
13
- const CANDIDATES = [
14
- 'app/root.tsx',
15
- 'app/root.jsx',
16
- ];
17
-
18
- const MARKER = 'data-gurulu-install="1"';
19
-
20
- function detect(repoRoot) {
21
- for (const rel of CANDIDATES) {
22
- if (fs.existsSync(path.join(repoRoot, rel))) {
23
- return { framework: NAME, files: [rel] };
24
- }
25
- }
26
- return null;
27
- }
28
-
29
- function buildScriptTag(injection) {
30
- const pkAttr = injection.publishableKey
31
- ? ` data-gurulu-publishable-key=${JSON.stringify(injection.publishableKey)}\n`
32
- : '';
33
- return (
34
- ` <script\n` +
35
- ` src=${JSON.stringify(injection.scriptSrc)}\n` +
36
- ` data-gurulu-site-id=${JSON.stringify(injection.siteId)}\n` +
37
- ` data-gurulu-tenant-id=${JSON.stringify(injection.tenantId)}\n` +
38
- pkAttr +
39
- ` data-features="errors,replay,advanced"\n` +
40
- ` ${MARKER}\n` +
41
- ` async\n` +
42
- ` />\n`
43
- );
44
- }
45
-
46
- function injectScriptTag(source, injection) {
47
- if (source.includes(MARKER)) return source;
48
- const tag = buildScriptTag(injection);
49
- // Prefer to insert just before <Scripts /> (self-closing or open tag).
50
- if (/<Scripts\s*\/?>/.test(source)) {
51
- return source.replace(/(<Scripts\s*\/?>)/, `${tag} $1`);
52
- }
53
- // Fallback: before </body>.
54
- if (/<\/body>/.test(source)) {
55
- return source.replace(/<\/body>/, `${tag} </body>`);
56
- }
57
- return source;
58
- }
59
-
60
- function plan(ctx) {
61
- const detection = detect(ctx.repoRoot);
62
- if (!detection) return { changes: [], notes: ['no-remix-root'] };
63
- const changes = [];
64
- for (const rel of detection.files) {
65
- const abs = path.join(ctx.repoRoot, rel);
66
- const before = fs.readFileSync(abs, 'utf8');
67
- const after = injectScriptTag(before, ctx.injection);
68
- if (after !== before) {
69
- changes.push({ relPath: rel, before, after, reason: 'inject-script-tag' });
70
- }
71
- }
72
- return { changes, notes: [] };
73
- }
74
-
75
- module.exports = { name: NAME, detect, plan };