@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,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 };