@gurulu/cli 0.4.6 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) 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 -853
  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 -18
  45. package/dist/commands/chat.js +0 -117
  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 -349
  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 -65
  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/sites.d.ts +0 -18
  89. package/dist/commands/sites.js +0 -139
  90. package/dist/commands/skad.d.ts +0 -18
  91. package/dist/commands/skad.js +0 -53
  92. package/dist/commands/sourcemap.d.ts +0 -33
  93. package/dist/commands/sourcemap.js +0 -204
  94. package/dist/commands/status.d.ts +0 -7
  95. package/dist/commands/status.js +0 -136
  96. package/dist/commands/upgrade.d.ts +0 -21
  97. package/dist/commands/upgrade.js +0 -183
  98. package/dist/commands/warehouse.d.ts +0 -20
  99. package/dist/commands/warehouse.js +0 -65
  100. package/dist/commands/warehouses.d.ts +0 -17
  101. package/dist/commands/warehouses.js +0 -182
  102. package/dist/commands/watch.d.ts +0 -45
  103. package/dist/commands/watch.js +0 -258
  104. package/dist/commands/whoami.d.ts +0 -9
  105. package/dist/commands/whoami.js +0 -50
  106. package/dist/config.d.ts +0 -75
  107. package/dist/config.js +0 -329
  108. package/dist/frameworks/detect.d.ts +0 -8
  109. package/dist/frameworks/detect.js +0 -444
  110. package/dist/install-intent-proposal.d.ts +0 -99
  111. package/dist/install-intent-proposal.js +0 -202
  112. package/dist/utils/api.d.ts +0 -20
  113. package/dist/utils/api.js +0 -47
  114. package/dist/utils/config.d.ts +0 -13
  115. package/dist/utils/config.js +0 -30
  116. package/dist/utils/confirm.d.ts +0 -17
  117. package/dist/utils/confirm.js +0 -40
  118. package/dist/utils/dry-run.d.ts +0 -20
  119. package/dist/utils/dry-run.js +0 -67
  120. package/dist/utils/from-file.d.ts +0 -9
  121. package/dist/utils/from-file.js +0 -72
  122. package/dist/utils/redact.d.ts +0 -14
  123. package/dist/utils/redact.js +0 -48
  124. package/dist/utils/ui.d.ts +0 -14
  125. package/dist/utils/ui.js +0 -59
  126. package/scripts/.gitkeep +0 -0
  127. package/scripts/README-gurulu-agentic-install.md +0 -114
  128. package/scripts/README-gurulu-scan.md +0 -98
  129. package/scripts/audit-cli-scopes.mjs +0 -204
  130. package/scripts/backfill-tenant-id.mjs +0 -172
  131. package/scripts/backfill-tenant-links.ts +0 -252
  132. package/scripts/backup-clickhouse.sh +0 -27
  133. package/scripts/backup-postgres.sh +0 -19
  134. package/scripts/bootstrap-runtime-schema.mjs +0 -87
  135. package/scripts/bootstrap-stripe.mjs +0 -158
  136. package/scripts/gurulu-agentic-install.lib.cjs +0 -762
  137. package/scripts/gurulu-agentic-install.mjs +0 -623
  138. package/scripts/gurulu-scan.lib.cjs +0 -1509
  139. package/scripts/gurulu-scan.mjs +0 -91
  140. package/scripts/gurulu-verify-install.lib.cjs +0 -334
  141. package/scripts/gurulu-verify-install.mjs +0 -59
  142. package/scripts/init-ssl.sh +0 -26
  143. package/scripts/migrate-flow-graph-enums.sh +0 -86
  144. package/scripts/monitor-disk.sh +0 -24
  145. package/scripts/patches/astro.patch.cjs +0 -74
  146. package/scripts/patches/auto-instrument/ast-helper.cjs +0 -480
  147. package/scripts/patches/auto-instrument/astro.cjs +0 -273
  148. package/scripts/patches/auto-instrument/express.cjs +0 -383
  149. package/scripts/patches/auto-instrument/fastify.cjs +0 -262
  150. package/scripts/patches/auto-instrument/hono.cjs +0 -392
  151. package/scripts/patches/auto-instrument/index.cjs +0 -80
  152. package/scripts/patches/auto-instrument/nestjs.cjs +0 -286
  153. package/scripts/patches/auto-instrument/nextjs-app-router.cjs +0 -345
  154. package/scripts/patches/auto-instrument/nextjs-pages.cjs +0 -361
  155. package/scripts/patches/auto-instrument/remix.cjs +0 -168
  156. package/scripts/patches/auto-instrument/sdk-helper-map.cjs +0 -241
  157. package/scripts/patches/auto-instrument/singleton-helper.cjs +0 -193
  158. package/scripts/patches/auto-instrument/sveltekit.cjs +0 -161
  159. package/scripts/patches/auto-instrument/vite-react.cjs +0 -37
  160. package/scripts/patches/auto-instrument/vue.cjs +0 -196
  161. package/scripts/patches/express.patch.cjs +0 -99
  162. package/scripts/patches/fastify.patch.cjs +0 -108
  163. package/scripts/patches/index.cjs +0 -300
  164. package/scripts/patches/nestjs.patch.cjs +0 -112
  165. package/scripts/patches/nextjs-app-router.patch.cjs +0 -97
  166. package/scripts/patches/nextjs-pages.patch.cjs +0 -97
  167. package/scripts/patches/remix.patch.cjs +0 -75
  168. package/scripts/patches/sveltekit.patch.cjs +0 -72
  169. package/scripts/patches/vite-react.patch.cjs +0 -73
  170. package/scripts/patches/vue.patch.cjs +0 -82
  171. package/scripts/renew-ssl.sh +0 -14
  172. package/scripts/resolve-migration.sh +0 -23
  173. package/scripts/seed-cli-dev-keys.mjs +0 -130
  174. package/scripts/seed-test-data.mjs +0 -391
  175. package/scripts/spike-browserless.ts +0 -65
  176. package/scripts/tenant-pivot-consistency-check.mjs +0 -205
  177. package/scripts/tenant-pivot-phase-3-cleanup.lib.cjs +0 -258
  178. package/scripts/tenant-pivot-phase-3-cleanup.mjs +0 -98
  179. package/scripts/test-identity-resolution.ts +0 -804
  180. 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 };