autotel-cli 0.1.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.
- package/README.md +220 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2889 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2889 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import * as path6 from "path";
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
|
|
10
|
+
// src/lib/project.ts
|
|
11
|
+
import * as path3 from "path";
|
|
12
|
+
|
|
13
|
+
// src/lib/fs.ts
|
|
14
|
+
import * as fs from "fs";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
import { randomBytes } from "crypto";
|
|
17
|
+
function isPathWithinRoot(targetPath, rootPath) {
|
|
18
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
19
|
+
const resolvedRoot = path.resolve(rootPath);
|
|
20
|
+
return resolvedTarget.startsWith(resolvedRoot + path.sep) || resolvedTarget === resolvedRoot;
|
|
21
|
+
}
|
|
22
|
+
function ensureDir(filePath) {
|
|
23
|
+
const dir = path.dirname(filePath);
|
|
24
|
+
if (!fs.existsSync(dir)) {
|
|
25
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function readFileSafe(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
return fs.readFileSync(filePath, "utf8");
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function fileExists(filePath) {
|
|
36
|
+
try {
|
|
37
|
+
return fs.statSync(filePath).isFile();
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function dirExists(dirPath) {
|
|
43
|
+
try {
|
|
44
|
+
return fs.statSync(dirPath).isDirectory();
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function createBackup(filePath) {
|
|
50
|
+
if (!fileExists(filePath)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const backupPath = `${filePath}.bak`;
|
|
54
|
+
fs.copyFileSync(filePath, backupPath);
|
|
55
|
+
return backupPath;
|
|
56
|
+
}
|
|
57
|
+
function atomicWrite(filePath, content, options) {
|
|
58
|
+
const resolvedPath = path.resolve(filePath);
|
|
59
|
+
if (!isPathWithinRoot(resolvedPath, options.root)) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Path traversal detected: ${filePath} resolves outside root ${options.root}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
let backupPath = null;
|
|
65
|
+
if (options.backup && fileExists(resolvedPath)) {
|
|
66
|
+
backupPath = createBackup(resolvedPath);
|
|
67
|
+
}
|
|
68
|
+
ensureDir(resolvedPath);
|
|
69
|
+
const tempPath = `${resolvedPath}.${randomBytes(4).toString("hex")}.tmp`;
|
|
70
|
+
try {
|
|
71
|
+
fs.writeFileSync(tempPath, content, "utf8");
|
|
72
|
+
fs.renameSync(tempPath, resolvedPath);
|
|
73
|
+
} catch (error2) {
|
|
74
|
+
try {
|
|
75
|
+
fs.unlinkSync(tempPath);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
throw error2;
|
|
79
|
+
}
|
|
80
|
+
return { backupPath };
|
|
81
|
+
}
|
|
82
|
+
function readJsonSafe(filePath) {
|
|
83
|
+
const content = readFileSafe(filePath);
|
|
84
|
+
if (content === null) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(content);
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function findUpward(startDir, filename, stopAtRoot = true) {
|
|
94
|
+
let currentDir = path.resolve(startDir);
|
|
95
|
+
const root = path.parse(currentDir).root;
|
|
96
|
+
while (currentDir !== root) {
|
|
97
|
+
const filePath = path.join(currentDir, filename);
|
|
98
|
+
if (fileExists(filePath)) {
|
|
99
|
+
return filePath;
|
|
100
|
+
}
|
|
101
|
+
const parentDir = path.dirname(currentDir);
|
|
102
|
+
if (parentDir === currentDir) {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
currentDir = parentDir;
|
|
106
|
+
if (stopAtRoot && currentDir === root) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const rootFilePath = path.join(root, filename);
|
|
111
|
+
if (fileExists(rootFilePath)) {
|
|
112
|
+
return rootFilePath;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
function findAllUpward(startDir, filenames) {
|
|
117
|
+
const found = /* @__PURE__ */ new Map();
|
|
118
|
+
let currentDir = path.resolve(startDir);
|
|
119
|
+
const root = path.parse(currentDir).root;
|
|
120
|
+
while (currentDir !== root) {
|
|
121
|
+
for (const filename of filenames) {
|
|
122
|
+
if (!found.has(filename)) {
|
|
123
|
+
const filePath = path.join(currentDir, filename);
|
|
124
|
+
if (fileExists(filePath)) {
|
|
125
|
+
found.set(filename, filePath);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const parentDir = path.dirname(currentDir);
|
|
130
|
+
if (parentDir === currentDir) {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
currentDir = parentDir;
|
|
134
|
+
}
|
|
135
|
+
return found;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/lib/package-manager.ts
|
|
139
|
+
import * as path2 from "path";
|
|
140
|
+
var LOCKFILE_MAP = {
|
|
141
|
+
"pnpm-lock.yaml": "pnpm",
|
|
142
|
+
"bun.lockb": "bun",
|
|
143
|
+
"yarn.lock": "yarn",
|
|
144
|
+
"package-lock.json": "npm"
|
|
145
|
+
};
|
|
146
|
+
var LOCKFILES = ["pnpm-lock.yaml", "bun.lockb", "yarn.lock", "package-lock.json"];
|
|
147
|
+
var PM_PRIORITY = ["pnpm", "bun", "yarn", "npm"];
|
|
148
|
+
function detectPackageManager(startDir) {
|
|
149
|
+
const foundLockfiles = findAllUpward(startDir, LOCKFILES);
|
|
150
|
+
if (foundLockfiles.size === 0) {
|
|
151
|
+
return { packageManager: "npm", lockfilePath: null };
|
|
152
|
+
}
|
|
153
|
+
let closestLockfile = null;
|
|
154
|
+
let closestDepth = -1;
|
|
155
|
+
let closestPM = "npm";
|
|
156
|
+
for (const [lockfileName, lockfilePath] of foundLockfiles) {
|
|
157
|
+
const depth = lockfilePath.split(path2.sep).length;
|
|
158
|
+
const pm = LOCKFILE_MAP[lockfileName];
|
|
159
|
+
if (pm === void 0) continue;
|
|
160
|
+
if (depth > closestDepth || depth === closestDepth && PM_PRIORITY.indexOf(pm) < PM_PRIORITY.indexOf(closestPM)) {
|
|
161
|
+
closestDepth = depth;
|
|
162
|
+
closestLockfile = lockfilePath;
|
|
163
|
+
closestPM = pm;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
packageManager: closestPM,
|
|
168
|
+
lockfilePath: closestLockfile
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function getInstallCommand(pm, packages, options = {}) {
|
|
172
|
+
const pkgList = packages.join(" ");
|
|
173
|
+
const { dev = false, workspaceRoot = false } = options;
|
|
174
|
+
switch (pm) {
|
|
175
|
+
case "pnpm": {
|
|
176
|
+
const devFlag = dev ? " -D" : "";
|
|
177
|
+
const wsFlag = workspaceRoot ? " -w" : "";
|
|
178
|
+
return `pnpm add${devFlag}${wsFlag} ${pkgList}`;
|
|
179
|
+
}
|
|
180
|
+
case "bun": {
|
|
181
|
+
const devFlag = dev ? " -d" : "";
|
|
182
|
+
return `bun add${devFlag} ${pkgList}`;
|
|
183
|
+
}
|
|
184
|
+
case "yarn": {
|
|
185
|
+
const devFlag = dev ? " -D" : "";
|
|
186
|
+
const wsFlag = workspaceRoot ? " -W" : "";
|
|
187
|
+
return `yarn add${devFlag}${wsFlag} ${pkgList}`;
|
|
188
|
+
}
|
|
189
|
+
case "npm":
|
|
190
|
+
default: {
|
|
191
|
+
const devFlag = dev ? " --save-dev" : "";
|
|
192
|
+
return `npm install${devFlag} ${pkgList}`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function detectWorkspaceRoot(startDir) {
|
|
197
|
+
let currentDir = path2.resolve(startDir);
|
|
198
|
+
const root = path2.parse(currentDir).root;
|
|
199
|
+
while (currentDir !== root) {
|
|
200
|
+
if (fileExists(path2.join(currentDir, "pnpm-workspace.yaml"))) {
|
|
201
|
+
return { workspaceRoot: currentDir, workspaceType: "pnpm" };
|
|
202
|
+
}
|
|
203
|
+
if (fileExists(path2.join(currentDir, "lerna.json"))) {
|
|
204
|
+
return { workspaceRoot: currentDir, workspaceType: "lerna" };
|
|
205
|
+
}
|
|
206
|
+
const pkgJsonPath = path2.join(currentDir, "package.json");
|
|
207
|
+
const pkgJsonContent = readFileSafe(pkgJsonPath);
|
|
208
|
+
if (pkgJsonContent) {
|
|
209
|
+
try {
|
|
210
|
+
const pkgJson = JSON.parse(pkgJsonContent);
|
|
211
|
+
if (pkgJson.workspaces) {
|
|
212
|
+
const hasYarnLock = fileExists(path2.join(currentDir, "yarn.lock"));
|
|
213
|
+
return {
|
|
214
|
+
workspaceRoot: currentDir,
|
|
215
|
+
workspaceType: hasYarnLock ? "yarn" : "npm"
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const parentDir = path2.dirname(currentDir);
|
|
222
|
+
if (parentDir === currentDir) {
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
currentDir = parentDir;
|
|
226
|
+
}
|
|
227
|
+
return { workspaceRoot: null, workspaceType: null };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/lib/project.ts
|
|
231
|
+
function findPackageJson(startDir) {
|
|
232
|
+
const packageJsonPath = findUpward(startDir, "package.json");
|
|
233
|
+
if (!packageJsonPath) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
packageJsonPath,
|
|
238
|
+
packageRoot: path3.dirname(packageJsonPath)
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function detectTypeScript(packageRoot) {
|
|
242
|
+
if (fileExists(path3.join(packageRoot, "tsconfig.json"))) {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
const pkgJson = readJsonSafe(path3.join(packageRoot, "package.json"));
|
|
246
|
+
if (pkgJson) {
|
|
247
|
+
const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
|
|
248
|
+
if ("typescript" in deps) {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
function detectEsm(packageJson) {
|
|
255
|
+
return packageJson.type === "module";
|
|
256
|
+
}
|
|
257
|
+
function buildWorkspaceInfo(packageRoot, cwd) {
|
|
258
|
+
const { workspaceRoot, workspaceType } = detectWorkspaceRoot(cwd);
|
|
259
|
+
return {
|
|
260
|
+
isMonorepo: workspaceRoot !== null && workspaceRoot !== packageRoot,
|
|
261
|
+
workspaceRoot,
|
|
262
|
+
packageRoot,
|
|
263
|
+
workspaceType
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function discoverProject(cwd) {
|
|
267
|
+
const resolvedCwd = path3.resolve(cwd);
|
|
268
|
+
const pkgResult = findPackageJson(resolvedCwd);
|
|
269
|
+
if (!pkgResult) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
const { packageJsonPath, packageRoot } = pkgResult;
|
|
273
|
+
const packageJson = readJsonSafe(packageJsonPath);
|
|
274
|
+
if (!packageJson) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
const { packageManager, lockfilePath } = detectPackageManager(packageRoot);
|
|
278
|
+
const workspace = buildWorkspaceInfo(packageRoot, resolvedCwd);
|
|
279
|
+
const hasTypeScript = detectTypeScript(packageRoot);
|
|
280
|
+
const isEsm = detectEsm(packageJson);
|
|
281
|
+
return {
|
|
282
|
+
cwd: resolvedCwd,
|
|
283
|
+
packageRoot,
|
|
284
|
+
packageJson,
|
|
285
|
+
packageJsonPath,
|
|
286
|
+
packageManager,
|
|
287
|
+
lockfilePath,
|
|
288
|
+
workspace,
|
|
289
|
+
hasTypeScript,
|
|
290
|
+
isEsm
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function getEntrypointCandidates(packageRoot) {
|
|
294
|
+
const candidates = [
|
|
295
|
+
"src/index.ts",
|
|
296
|
+
"src/main.ts",
|
|
297
|
+
"src/index.mts",
|
|
298
|
+
"src/main.mts",
|
|
299
|
+
"server.ts",
|
|
300
|
+
"app.ts",
|
|
301
|
+
"index.ts",
|
|
302
|
+
"src/index.js",
|
|
303
|
+
"src/main.js",
|
|
304
|
+
"server.js",
|
|
305
|
+
"app.js",
|
|
306
|
+
"index.js"
|
|
307
|
+
];
|
|
308
|
+
return candidates.map((c) => path3.join(packageRoot, c)).filter((p) => fileExists(p));
|
|
309
|
+
}
|
|
310
|
+
function getInstrumentationPath(packageRoot, hasTypeScript) {
|
|
311
|
+
const srcDir = path3.join(packageRoot, "src");
|
|
312
|
+
const hasSrcDir = dirExists(srcDir) || fileExists(path3.join(packageRoot, "src", "index.ts")) || fileExists(path3.join(packageRoot, "src", "index.js"));
|
|
313
|
+
const dir = hasSrcDir ? path3.join(packageRoot, "src") : packageRoot;
|
|
314
|
+
const ext = hasTypeScript ? "mts" : "mjs";
|
|
315
|
+
return path3.join(dir, `instrumentation.${ext}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/lib/config-detector.ts
|
|
319
|
+
import * as path4 from "path";
|
|
320
|
+
|
|
321
|
+
// src/lib/code-builder.ts
|
|
322
|
+
var CLI_HEADER = `/**
|
|
323
|
+
* autotel instrumentation - managed by autotel-cli
|
|
324
|
+
* Run \`autotel add <feature>\` to update this file
|
|
325
|
+
*/`;
|
|
326
|
+
var SECTION_MARKER = (name) => `// --- AUTOTEL:${name} ---`;
|
|
327
|
+
function createCodeFile() {
|
|
328
|
+
return {
|
|
329
|
+
imports: [],
|
|
330
|
+
backendImports: [],
|
|
331
|
+
pluginImports: [],
|
|
332
|
+
subscriberImports: [],
|
|
333
|
+
backendConfig: null,
|
|
334
|
+
subscribersConfig: [],
|
|
335
|
+
pluginInit: []
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function addImport(file, imp, section) {
|
|
339
|
+
switch (section) {
|
|
340
|
+
case "backend":
|
|
341
|
+
file.backendImports.push(imp);
|
|
342
|
+
break;
|
|
343
|
+
case "plugin":
|
|
344
|
+
file.pluginImports.push(imp);
|
|
345
|
+
break;
|
|
346
|
+
case "subscriber":
|
|
347
|
+
file.subscriberImports.push(imp);
|
|
348
|
+
break;
|
|
349
|
+
default:
|
|
350
|
+
file.imports.push(imp);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function setBackendConfig(file, config) {
|
|
354
|
+
file.backendConfig = config;
|
|
355
|
+
}
|
|
356
|
+
function addSubscriberConfig(file, config) {
|
|
357
|
+
file.subscribersConfig.push(config);
|
|
358
|
+
}
|
|
359
|
+
function addPluginInit(file, init) {
|
|
360
|
+
file.pluginInit.push(init);
|
|
361
|
+
}
|
|
362
|
+
function sortImports(imports) {
|
|
363
|
+
const sideEffect = [];
|
|
364
|
+
const external = [];
|
|
365
|
+
const relative3 = [];
|
|
366
|
+
for (const imp of imports) {
|
|
367
|
+
if (imp.sideEffect) {
|
|
368
|
+
if (imp.source === "autotel/register") {
|
|
369
|
+
sideEffect.unshift(imp);
|
|
370
|
+
} else {
|
|
371
|
+
sideEffect.push(imp);
|
|
372
|
+
}
|
|
373
|
+
} else if (imp.source.startsWith(".") || imp.source.startsWith("/")) {
|
|
374
|
+
relative3.push(imp);
|
|
375
|
+
} else {
|
|
376
|
+
external.push(imp);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const sortBySource = (a, b) => a.source.localeCompare(b.source);
|
|
380
|
+
external.sort(sortBySource);
|
|
381
|
+
relative3.sort(sortBySource);
|
|
382
|
+
return [...sideEffect, ...external, ...relative3];
|
|
383
|
+
}
|
|
384
|
+
function renderImport(imp) {
|
|
385
|
+
if (imp.sideEffect) {
|
|
386
|
+
return `import '${imp.source}';`;
|
|
387
|
+
}
|
|
388
|
+
const parts = [];
|
|
389
|
+
if (imp.default) {
|
|
390
|
+
parts.push(imp.default);
|
|
391
|
+
}
|
|
392
|
+
if (imp.specifiers && imp.specifiers.length > 0) {
|
|
393
|
+
const specifiers = imp.specifiers.join(", ");
|
|
394
|
+
parts.push(`{ ${specifiers} }`);
|
|
395
|
+
}
|
|
396
|
+
if (parts.length === 0) {
|
|
397
|
+
return `import '${imp.source}';`;
|
|
398
|
+
}
|
|
399
|
+
return `import ${parts.join(", ")} from '${imp.source}';`;
|
|
400
|
+
}
|
|
401
|
+
function renderImports(imports) {
|
|
402
|
+
if (imports.length === 0) {
|
|
403
|
+
return "";
|
|
404
|
+
}
|
|
405
|
+
const sorted = sortImports(imports);
|
|
406
|
+
return sorted.map(renderImport).join("\n");
|
|
407
|
+
}
|
|
408
|
+
function renderCodeFile(file) {
|
|
409
|
+
const lines = [];
|
|
410
|
+
lines.push(CLI_HEADER);
|
|
411
|
+
lines.push("");
|
|
412
|
+
const mainImports = sortImports(file.imports);
|
|
413
|
+
if (mainImports.length > 0) {
|
|
414
|
+
lines.push(renderImports(mainImports));
|
|
415
|
+
lines.push("");
|
|
416
|
+
}
|
|
417
|
+
if (file.backendImports.length > 0) {
|
|
418
|
+
lines.push(SECTION_MARKER("BACKEND"));
|
|
419
|
+
lines.push(renderImports(file.backendImports));
|
|
420
|
+
lines.push("");
|
|
421
|
+
}
|
|
422
|
+
if (file.pluginImports.length > 0) {
|
|
423
|
+
lines.push(SECTION_MARKER("PLUGINS"));
|
|
424
|
+
lines.push(renderImports(file.pluginImports));
|
|
425
|
+
lines.push("");
|
|
426
|
+
}
|
|
427
|
+
if (file.subscriberImports.length > 0) {
|
|
428
|
+
lines.push(SECTION_MARKER("SUBSCRIBERS"));
|
|
429
|
+
lines.push(renderImports(file.subscriberImports));
|
|
430
|
+
lines.push("");
|
|
431
|
+
}
|
|
432
|
+
lines.push("init({");
|
|
433
|
+
if (file.backendConfig) {
|
|
434
|
+
lines.push(" " + SECTION_MARKER("BACKEND_CONFIG"));
|
|
435
|
+
lines.push(` ${file.backendConfig}`);
|
|
436
|
+
}
|
|
437
|
+
if (file.subscribersConfig.length > 0) {
|
|
438
|
+
lines.push("");
|
|
439
|
+
lines.push(" " + SECTION_MARKER("SUBSCRIBERS_CONFIG"));
|
|
440
|
+
lines.push(" subscribers: [");
|
|
441
|
+
for (const sub of file.subscribersConfig) {
|
|
442
|
+
lines.push(` ${sub}`);
|
|
443
|
+
}
|
|
444
|
+
lines.push(" ],");
|
|
445
|
+
}
|
|
446
|
+
lines.push("});");
|
|
447
|
+
if (file.pluginInit.length > 0) {
|
|
448
|
+
lines.push("");
|
|
449
|
+
lines.push(SECTION_MARKER("PLUGIN_INIT"));
|
|
450
|
+
for (const init of file.pluginInit) {
|
|
451
|
+
lines.push(init);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
lines.push("");
|
|
455
|
+
return lines.join("\n");
|
|
456
|
+
}
|
|
457
|
+
function hasCliOwnershipHeader(content) {
|
|
458
|
+
return content.includes("autotel instrumentation - managed by autotel-cli");
|
|
459
|
+
}
|
|
460
|
+
function getSectionMarkers(content) {
|
|
461
|
+
const markers = [];
|
|
462
|
+
const regex = /\/\/ --- AUTOTEL:(\w+) ---/g;
|
|
463
|
+
let match;
|
|
464
|
+
while ((match = regex.exec(content)) !== null) {
|
|
465
|
+
if (match[1]) {
|
|
466
|
+
markers.push(match[1]);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return markers;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/lib/config-detector.ts
|
|
473
|
+
var INSTRUMENTATION_LOCATIONS = [
|
|
474
|
+
"src/instrumentation.mts",
|
|
475
|
+
"src/instrumentation.ts",
|
|
476
|
+
"src/instrumentation.mjs",
|
|
477
|
+
"src/instrumentation.js",
|
|
478
|
+
"instrumentation.mts",
|
|
479
|
+
"instrumentation.ts",
|
|
480
|
+
"instrumentation.mjs",
|
|
481
|
+
"instrumentation.js"
|
|
482
|
+
];
|
|
483
|
+
function findInstrumentationFile(packageRoot) {
|
|
484
|
+
for (const location of INSTRUMENTATION_LOCATIONS) {
|
|
485
|
+
const filePath = path4.join(packageRoot, location);
|
|
486
|
+
if (fileExists(filePath)) {
|
|
487
|
+
const content = readFileSafe(filePath);
|
|
488
|
+
if (content === null) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
const isCliOwned = hasCliOwnershipHeader(content);
|
|
492
|
+
const markerStrings = getSectionMarkers(content);
|
|
493
|
+
const sections = markerStrings.filter(
|
|
494
|
+
(m) => ["BACKEND", "PLUGINS", "SUBSCRIBERS", "BACKEND_CONFIG", "SUBSCRIBERS_CONFIG", "PLUGIN_INIT"].includes(m)
|
|
495
|
+
);
|
|
496
|
+
return {
|
|
497
|
+
path: filePath,
|
|
498
|
+
isCliOwned,
|
|
499
|
+
sections
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
function findAutotelYaml(packageRoot) {
|
|
506
|
+
const candidates = ["autotel.yaml", "autotel.yml", ".autotelrc.yaml", ".autotelrc.yml"];
|
|
507
|
+
for (const candidate of candidates) {
|
|
508
|
+
const filePath = path4.join(packageRoot, candidate);
|
|
509
|
+
if (fileExists(filePath)) {
|
|
510
|
+
return filePath;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
function detectConfig(packageRoot) {
|
|
516
|
+
const instrumentationFile = findInstrumentationFile(packageRoot);
|
|
517
|
+
if (instrumentationFile?.isCliOwned) {
|
|
518
|
+
return {
|
|
519
|
+
found: true,
|
|
520
|
+
type: "cli-owned",
|
|
521
|
+
path: instrumentationFile.path,
|
|
522
|
+
instrumentationFile
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
if (instrumentationFile && !instrumentationFile.isCliOwned) {
|
|
526
|
+
return {
|
|
527
|
+
found: true,
|
|
528
|
+
type: "user-created",
|
|
529
|
+
path: instrumentationFile.path,
|
|
530
|
+
instrumentationFile
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
const yamlPath = findAutotelYaml(packageRoot);
|
|
534
|
+
if (yamlPath) {
|
|
535
|
+
return {
|
|
536
|
+
found: true,
|
|
537
|
+
type: "autotel-yaml",
|
|
538
|
+
path: yamlPath,
|
|
539
|
+
instrumentationFile: null
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
found: false,
|
|
544
|
+
type: "none",
|
|
545
|
+
path: null,
|
|
546
|
+
instrumentationFile: null
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function isFeatureConfigured(instrumentationFile, feature) {
|
|
550
|
+
switch (feature) {
|
|
551
|
+
case "backend":
|
|
552
|
+
case "platform":
|
|
553
|
+
return instrumentationFile.sections.includes("BACKEND") || instrumentationFile.sections.includes("BACKEND_CONFIG");
|
|
554
|
+
case "subscriber":
|
|
555
|
+
return instrumentationFile.sections.includes("SUBSCRIBERS") || instrumentationFile.sections.includes("SUBSCRIBERS_CONFIG");
|
|
556
|
+
case "plugin":
|
|
557
|
+
return instrumentationFile.sections.includes("PLUGINS") || instrumentationFile.sections.includes("PLUGIN_INIT");
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// src/lib/env-generator.ts
|
|
562
|
+
import * as path5 from "path";
|
|
563
|
+
import { glob } from "glob";
|
|
564
|
+
function generateEnvExample(envVars) {
|
|
565
|
+
if (envVars.length === 0) {
|
|
566
|
+
return "";
|
|
567
|
+
}
|
|
568
|
+
const lines = [
|
|
569
|
+
"# Autotel configuration",
|
|
570
|
+
"# Copy to .env and fill in values",
|
|
571
|
+
""
|
|
572
|
+
];
|
|
573
|
+
for (const envVar of envVars) {
|
|
574
|
+
lines.push(`# ${envVar.description}`);
|
|
575
|
+
if (envVar.sensitive) {
|
|
576
|
+
lines.push("# \u26A0\uFE0F SENSITIVE: Do not commit this value");
|
|
577
|
+
}
|
|
578
|
+
const value = envVar.example ?? "";
|
|
579
|
+
lines.push(`${envVar.name}=${value}`);
|
|
580
|
+
lines.push("");
|
|
581
|
+
}
|
|
582
|
+
return lines.join("\n");
|
|
583
|
+
}
|
|
584
|
+
function parseEnvFile(content) {
|
|
585
|
+
const vars = /* @__PURE__ */ new Map();
|
|
586
|
+
const lines = content.split("\n");
|
|
587
|
+
for (const line of lines) {
|
|
588
|
+
const trimmed = line.trim();
|
|
589
|
+
if (trimmed.startsWith("#") || trimmed === "") {
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
const eqIndex = trimmed.indexOf("=");
|
|
593
|
+
if (eqIndex > 0) {
|
|
594
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
595
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
596
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
597
|
+
value = value.slice(1, -1);
|
|
598
|
+
}
|
|
599
|
+
vars.set(key, value);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return vars;
|
|
603
|
+
}
|
|
604
|
+
async function findEnvFiles(packageRoot) {
|
|
605
|
+
const patterns = [".env", ".env.local", ".env.*"];
|
|
606
|
+
const files = [];
|
|
607
|
+
for (const pattern of patterns) {
|
|
608
|
+
try {
|
|
609
|
+
const matches = await glob(pattern, {
|
|
610
|
+
cwd: packageRoot,
|
|
611
|
+
absolute: true,
|
|
612
|
+
dot: true
|
|
613
|
+
});
|
|
614
|
+
files.push(...matches);
|
|
615
|
+
} catch {
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return [...new Set(files)].filter((f) => fileExists(f));
|
|
619
|
+
}
|
|
620
|
+
async function checkEnvVarPresent(packageRoot, varName, specificFile) {
|
|
621
|
+
const files = specificFile ? [path5.resolve(packageRoot, specificFile)] : await findEnvFiles(packageRoot);
|
|
622
|
+
for (const file of files) {
|
|
623
|
+
const content = readFileSafe(file);
|
|
624
|
+
if (content === null) {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
const vars = parseEnvFile(content);
|
|
628
|
+
if (vars.has(varName)) {
|
|
629
|
+
return { found: true, file };
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return { found: false, file: null };
|
|
633
|
+
}
|
|
634
|
+
async function checkEnvVarsPresent(packageRoot, varNames, specificFile) {
|
|
635
|
+
const results = /* @__PURE__ */ new Map();
|
|
636
|
+
for (const varName of varNames) {
|
|
637
|
+
const result = await checkEnvVarPresent(packageRoot, varName, specificFile);
|
|
638
|
+
results.set(varName, result);
|
|
639
|
+
}
|
|
640
|
+
return results;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// src/lib/dependency-planner.ts
|
|
644
|
+
function createDependencyPlan() {
|
|
645
|
+
return {
|
|
646
|
+
required: [],
|
|
647
|
+
optional: [],
|
|
648
|
+
devOnly: []
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function mergePackages(target, source) {
|
|
652
|
+
for (const pkg of source) {
|
|
653
|
+
if (!target.includes(pkg)) {
|
|
654
|
+
target.push(pkg);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
function addPresetToPlan(plan, preset) {
|
|
659
|
+
mergePackages(plan.required, preset.packages.required);
|
|
660
|
+
mergePackages(plan.optional, preset.packages.optional);
|
|
661
|
+
mergePackages(plan.devOnly, preset.packages.devOnly);
|
|
662
|
+
}
|
|
663
|
+
function addPresetsToPlan(plan, presets) {
|
|
664
|
+
for (const preset of presets) {
|
|
665
|
+
addPresetToPlan(plan, preset);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function getProdPackages(plan) {
|
|
669
|
+
return [...plan.required, ...plan.optional];
|
|
670
|
+
}
|
|
671
|
+
function getDevPackages(plan) {
|
|
672
|
+
return [...plan.devOnly];
|
|
673
|
+
}
|
|
674
|
+
function addCorePackages(plan) {
|
|
675
|
+
mergePackages(plan.required, ["autotel"]);
|
|
676
|
+
}
|
|
677
|
+
function addAutoInstrumentationPackages(plan, selection) {
|
|
678
|
+
if (selection === "none") {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (selection === "all") {
|
|
682
|
+
mergePackages(plan.required, ["@opentelemetry/auto-instrumentations-node"]);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
for (const name of selection) {
|
|
686
|
+
mergePackages(plan.required, [`@opentelemetry/instrumentation-${name}`]);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function buildDependencyPlan(options) {
|
|
690
|
+
const plan = createDependencyPlan();
|
|
691
|
+
addCorePackages(plan);
|
|
692
|
+
addPresetsToPlan(plan, options.presets);
|
|
693
|
+
addAutoInstrumentationPackages(plan, options.autoInstrumentations);
|
|
694
|
+
return plan;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// src/presets/backends/datadog.ts
|
|
698
|
+
var datadogDirect = {
|
|
699
|
+
name: "Datadog (Direct)",
|
|
700
|
+
slug: "datadog",
|
|
701
|
+
type: "backend",
|
|
702
|
+
description: "Send traces directly to Datadog via OTLP HTTP",
|
|
703
|
+
protocol: "http",
|
|
704
|
+
exporter: "otlp-http",
|
|
705
|
+
packages: {
|
|
706
|
+
required: [
|
|
707
|
+
"autotel-backends",
|
|
708
|
+
"@opentelemetry/exporter-trace-otlp-http"
|
|
709
|
+
],
|
|
710
|
+
optional: [
|
|
711
|
+
"@opentelemetry/sdk-logs",
|
|
712
|
+
"@opentelemetry/exporter-logs-otlp-http"
|
|
713
|
+
],
|
|
714
|
+
devOnly: []
|
|
715
|
+
},
|
|
716
|
+
env: {
|
|
717
|
+
required: [
|
|
718
|
+
{
|
|
719
|
+
name: "DATADOG_API_KEY",
|
|
720
|
+
description: "Datadog API key for authentication",
|
|
721
|
+
example: "your-api-key",
|
|
722
|
+
sensitive: true
|
|
723
|
+
}
|
|
724
|
+
],
|
|
725
|
+
optional: [
|
|
726
|
+
{
|
|
727
|
+
name: "DATADOG_SITE",
|
|
728
|
+
description: "Datadog region (e.g., datadoghq.eu for EU)",
|
|
729
|
+
example: "datadoghq.com",
|
|
730
|
+
sensitive: false
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
name: "DD_ENV",
|
|
734
|
+
description: "Environment tag (e.g., production, staging)",
|
|
735
|
+
example: "production",
|
|
736
|
+
sensitive: false
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
name: "DD_SERVICE",
|
|
740
|
+
description: "Service name override",
|
|
741
|
+
example: "my-service",
|
|
742
|
+
sensitive: false
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
name: "DD_VERSION",
|
|
746
|
+
description: "Version tag",
|
|
747
|
+
example: "1.0.0",
|
|
748
|
+
sensitive: false
|
|
749
|
+
}
|
|
750
|
+
]
|
|
751
|
+
},
|
|
752
|
+
imports: [
|
|
753
|
+
{
|
|
754
|
+
source: "autotel-backends/datadog",
|
|
755
|
+
specifiers: ["createDatadogConfig"]
|
|
756
|
+
}
|
|
757
|
+
],
|
|
758
|
+
configBlock: {
|
|
759
|
+
type: "backend",
|
|
760
|
+
code: `...createDatadogConfig({
|
|
761
|
+
apiKey: process.env.DATADOG_API_KEY,
|
|
762
|
+
site: process.env.DATADOG_SITE,
|
|
763
|
+
}),`,
|
|
764
|
+
section: "BACKEND_CONFIG"
|
|
765
|
+
},
|
|
766
|
+
nextSteps: [
|
|
767
|
+
"Set DATADOG_API_KEY environment variable",
|
|
768
|
+
"Optionally set DD_ENV, DD_SERVICE, DD_VERSION for unified service tagging"
|
|
769
|
+
]
|
|
770
|
+
};
|
|
771
|
+
var datadogAgent = {
|
|
772
|
+
name: "Datadog (Agent)",
|
|
773
|
+
slug: "datadog-agent",
|
|
774
|
+
type: "backend",
|
|
775
|
+
description: "Send traces to Datadog via local agent",
|
|
776
|
+
protocol: "http",
|
|
777
|
+
exporter: "otlp-http",
|
|
778
|
+
packages: {
|
|
779
|
+
required: [
|
|
780
|
+
"autotel-backends",
|
|
781
|
+
"@opentelemetry/exporter-trace-otlp-http"
|
|
782
|
+
],
|
|
783
|
+
optional: [
|
|
784
|
+
"@opentelemetry/sdk-logs",
|
|
785
|
+
"@opentelemetry/exporter-logs-otlp-http"
|
|
786
|
+
],
|
|
787
|
+
devOnly: []
|
|
788
|
+
},
|
|
789
|
+
env: {
|
|
790
|
+
required: [],
|
|
791
|
+
optional: [
|
|
792
|
+
{
|
|
793
|
+
name: "DD_AGENT_HOST",
|
|
794
|
+
description: "Datadog Agent hostname",
|
|
795
|
+
example: "localhost",
|
|
796
|
+
sensitive: false
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
name: "DD_OTLP_PORT",
|
|
800
|
+
description: "OTLP receiver port on Agent",
|
|
801
|
+
example: "4318",
|
|
802
|
+
sensitive: false
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
name: "DD_ENV",
|
|
806
|
+
description: "Environment tag (e.g., production, staging)",
|
|
807
|
+
example: "production",
|
|
808
|
+
sensitive: false
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
name: "DD_SERVICE",
|
|
812
|
+
description: "Service name override",
|
|
813
|
+
example: "my-service",
|
|
814
|
+
sensitive: false
|
|
815
|
+
}
|
|
816
|
+
]
|
|
817
|
+
},
|
|
818
|
+
imports: [
|
|
819
|
+
{
|
|
820
|
+
source: "autotel-backends/datadog",
|
|
821
|
+
specifiers: ["createDatadogAgentConfig"]
|
|
822
|
+
}
|
|
823
|
+
],
|
|
824
|
+
configBlock: {
|
|
825
|
+
type: "backend",
|
|
826
|
+
code: `...createDatadogAgentConfig({
|
|
827
|
+
agentHost: process.env.DD_AGENT_HOST ?? 'localhost',
|
|
828
|
+
otlpPort: process.env.DD_OTLP_PORT ?? '4318',
|
|
829
|
+
}),`,
|
|
830
|
+
section: "BACKEND_CONFIG"
|
|
831
|
+
},
|
|
832
|
+
nextSteps: [
|
|
833
|
+
"Ensure Datadog Agent is running with OTLP receiver enabled",
|
|
834
|
+
"Agent handles authentication - no API key needed in app"
|
|
835
|
+
]
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
// src/presets/backends/honeycomb.ts
|
|
839
|
+
var honeycomb = {
|
|
840
|
+
name: "Honeycomb",
|
|
841
|
+
slug: "honeycomb",
|
|
842
|
+
type: "backend",
|
|
843
|
+
description: "Send traces to Honeycomb via OTLP gRPC",
|
|
844
|
+
protocol: "grpc",
|
|
845
|
+
exporter: "otlp-grpc",
|
|
846
|
+
packages: {
|
|
847
|
+
required: [
|
|
848
|
+
"autotel-backends",
|
|
849
|
+
"@opentelemetry/exporter-trace-otlp-grpc"
|
|
850
|
+
],
|
|
851
|
+
optional: [
|
|
852
|
+
"@opentelemetry/sdk-logs",
|
|
853
|
+
"@opentelemetry/exporter-logs-otlp-grpc"
|
|
854
|
+
],
|
|
855
|
+
devOnly: []
|
|
856
|
+
},
|
|
857
|
+
env: {
|
|
858
|
+
required: [
|
|
859
|
+
{
|
|
860
|
+
name: "HONEYCOMB_API_KEY",
|
|
861
|
+
description: "Honeycomb API key (Ingest Key)",
|
|
862
|
+
example: "your-api-key",
|
|
863
|
+
sensitive: true
|
|
864
|
+
}
|
|
865
|
+
],
|
|
866
|
+
optional: [
|
|
867
|
+
{
|
|
868
|
+
name: "HONEYCOMB_DATASET",
|
|
869
|
+
description: "Dataset name (Classic accounts only)",
|
|
870
|
+
example: "my-dataset",
|
|
871
|
+
sensitive: false
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
name: "OTEL_SERVICE_NAME",
|
|
875
|
+
description: "Service name for traces",
|
|
876
|
+
example: "my-service",
|
|
877
|
+
sensitive: false
|
|
878
|
+
}
|
|
879
|
+
]
|
|
880
|
+
},
|
|
881
|
+
imports: [
|
|
882
|
+
{
|
|
883
|
+
source: "autotel-backends/honeycomb",
|
|
884
|
+
specifiers: ["createHoneycombConfig"]
|
|
885
|
+
}
|
|
886
|
+
],
|
|
887
|
+
configBlock: {
|
|
888
|
+
type: "backend",
|
|
889
|
+
code: `...createHoneycombConfig({
|
|
890
|
+
apiKey: process.env.HONEYCOMB_API_KEY,
|
|
891
|
+
dataset: process.env.HONEYCOMB_DATASET,
|
|
892
|
+
}),`,
|
|
893
|
+
section: "BACKEND_CONFIG"
|
|
894
|
+
},
|
|
895
|
+
nextSteps: [
|
|
896
|
+
"Set HONEYCOMB_API_KEY environment variable",
|
|
897
|
+
"Get your API key from Honeycomb Team Settings > API Keys"
|
|
898
|
+
]
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
// src/presets/backends/otlp.ts
|
|
902
|
+
var otlpHttp = {
|
|
903
|
+
name: "OTLP (HTTP)",
|
|
904
|
+
slug: "otlp-http",
|
|
905
|
+
type: "backend",
|
|
906
|
+
description: "Send traces to any OTLP-compatible endpoint via HTTP",
|
|
907
|
+
protocol: "http",
|
|
908
|
+
exporter: "otlp-http",
|
|
909
|
+
packages: {
|
|
910
|
+
required: [
|
|
911
|
+
"autotel-backends",
|
|
912
|
+
"@opentelemetry/exporter-trace-otlp-http"
|
|
913
|
+
],
|
|
914
|
+
optional: [
|
|
915
|
+
"@opentelemetry/sdk-logs",
|
|
916
|
+
"@opentelemetry/exporter-logs-otlp-http"
|
|
917
|
+
],
|
|
918
|
+
devOnly: []
|
|
919
|
+
},
|
|
920
|
+
env: {
|
|
921
|
+
required: [
|
|
922
|
+
{
|
|
923
|
+
name: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
924
|
+
description: "OTLP endpoint URL",
|
|
925
|
+
example: "http://localhost:4318",
|
|
926
|
+
sensitive: false
|
|
927
|
+
}
|
|
928
|
+
],
|
|
929
|
+
optional: [
|
|
930
|
+
{
|
|
931
|
+
name: "OTEL_EXPORTER_OTLP_HEADERS",
|
|
932
|
+
description: "Headers for authentication (key=value pairs)",
|
|
933
|
+
example: "Authorization=Bearer token",
|
|
934
|
+
sensitive: true
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
name: "OTEL_SERVICE_NAME",
|
|
938
|
+
description: "Service name for traces",
|
|
939
|
+
example: "my-service",
|
|
940
|
+
sensitive: false
|
|
941
|
+
}
|
|
942
|
+
]
|
|
943
|
+
},
|
|
944
|
+
imports: [
|
|
945
|
+
{
|
|
946
|
+
source: "autotel-backends/otlp",
|
|
947
|
+
specifiers: ["createOtlpHttpConfig"]
|
|
948
|
+
}
|
|
949
|
+
],
|
|
950
|
+
configBlock: {
|
|
951
|
+
type: "backend",
|
|
952
|
+
code: `...createOtlpHttpConfig({
|
|
953
|
+
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
954
|
+
}),`,
|
|
955
|
+
section: "BACKEND_CONFIG"
|
|
956
|
+
},
|
|
957
|
+
nextSteps: [
|
|
958
|
+
"Set OTEL_EXPORTER_OTLP_ENDPOINT to your collector or backend URL",
|
|
959
|
+
"Add authentication headers if required by your endpoint"
|
|
960
|
+
]
|
|
961
|
+
};
|
|
962
|
+
var otlpGrpc = {
|
|
963
|
+
name: "OTLP (gRPC)",
|
|
964
|
+
slug: "otlp-grpc",
|
|
965
|
+
type: "backend",
|
|
966
|
+
description: "Send traces to any OTLP-compatible endpoint via gRPC",
|
|
967
|
+
protocol: "grpc",
|
|
968
|
+
exporter: "otlp-grpc",
|
|
969
|
+
packages: {
|
|
970
|
+
required: [
|
|
971
|
+
"autotel-backends",
|
|
972
|
+
"@opentelemetry/exporter-trace-otlp-grpc"
|
|
973
|
+
],
|
|
974
|
+
optional: [
|
|
975
|
+
"@opentelemetry/sdk-logs",
|
|
976
|
+
"@opentelemetry/exporter-logs-otlp-grpc"
|
|
977
|
+
],
|
|
978
|
+
devOnly: []
|
|
979
|
+
},
|
|
980
|
+
env: {
|
|
981
|
+
required: [
|
|
982
|
+
{
|
|
983
|
+
name: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
984
|
+
description: "OTLP gRPC endpoint URL",
|
|
985
|
+
example: "http://localhost:4317",
|
|
986
|
+
sensitive: false
|
|
987
|
+
}
|
|
988
|
+
],
|
|
989
|
+
optional: [
|
|
990
|
+
{
|
|
991
|
+
name: "OTEL_EXPORTER_OTLP_HEADERS",
|
|
992
|
+
description: "Headers for authentication (key=value pairs)",
|
|
993
|
+
example: "Authorization=Bearer token",
|
|
994
|
+
sensitive: true
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
name: "OTEL_SERVICE_NAME",
|
|
998
|
+
description: "Service name for traces",
|
|
999
|
+
example: "my-service",
|
|
1000
|
+
sensitive: false
|
|
1001
|
+
}
|
|
1002
|
+
]
|
|
1003
|
+
},
|
|
1004
|
+
imports: [
|
|
1005
|
+
{
|
|
1006
|
+
source: "autotel-backends/otlp",
|
|
1007
|
+
specifiers: ["createOtlpGrpcConfig"]
|
|
1008
|
+
}
|
|
1009
|
+
],
|
|
1010
|
+
configBlock: {
|
|
1011
|
+
type: "backend",
|
|
1012
|
+
code: `...createOtlpGrpcConfig({
|
|
1013
|
+
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
1014
|
+
}),`,
|
|
1015
|
+
section: "BACKEND_CONFIG"
|
|
1016
|
+
},
|
|
1017
|
+
nextSteps: [
|
|
1018
|
+
"Set OTEL_EXPORTER_OTLP_ENDPOINT to your collector or backend URL",
|
|
1019
|
+
"gRPC typically uses port 4317 (HTTP uses 4318)"
|
|
1020
|
+
]
|
|
1021
|
+
};
|
|
1022
|
+
var local = {
|
|
1023
|
+
name: "Local/Console",
|
|
1024
|
+
slug: "local",
|
|
1025
|
+
type: "backend",
|
|
1026
|
+
description: "Print traces to console (development only)",
|
|
1027
|
+
protocol: "http",
|
|
1028
|
+
exporter: "otlp-http",
|
|
1029
|
+
packages: {
|
|
1030
|
+
required: [],
|
|
1031
|
+
optional: [],
|
|
1032
|
+
devOnly: []
|
|
1033
|
+
},
|
|
1034
|
+
env: {
|
|
1035
|
+
required: [],
|
|
1036
|
+
optional: []
|
|
1037
|
+
},
|
|
1038
|
+
imports: [],
|
|
1039
|
+
configBlock: {
|
|
1040
|
+
type: "backend",
|
|
1041
|
+
code: "// Local/console mode - no backend configured",
|
|
1042
|
+
section: "BACKEND_CONFIG"
|
|
1043
|
+
},
|
|
1044
|
+
nextSteps: [
|
|
1045
|
+
"Traces will be logged to console",
|
|
1046
|
+
"Run `autotel add backend <provider>` to add a real backend"
|
|
1047
|
+
]
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
// src/presets/subscribers/posthog.ts
|
|
1051
|
+
var posthog = {
|
|
1052
|
+
name: "PostHog",
|
|
1053
|
+
slug: "posthog",
|
|
1054
|
+
type: "subscriber",
|
|
1055
|
+
description: "Send events to PostHog for product analytics",
|
|
1056
|
+
packages: {
|
|
1057
|
+
required: [
|
|
1058
|
+
"autotel-subscribers",
|
|
1059
|
+
"posthog-node"
|
|
1060
|
+
],
|
|
1061
|
+
optional: [],
|
|
1062
|
+
devOnly: []
|
|
1063
|
+
},
|
|
1064
|
+
env: {
|
|
1065
|
+
required: [
|
|
1066
|
+
{
|
|
1067
|
+
name: "POSTHOG_API_KEY",
|
|
1068
|
+
description: "PostHog Project API Key",
|
|
1069
|
+
example: "phc_...",
|
|
1070
|
+
sensitive: true
|
|
1071
|
+
}
|
|
1072
|
+
],
|
|
1073
|
+
optional: [
|
|
1074
|
+
{
|
|
1075
|
+
name: "POSTHOG_HOST",
|
|
1076
|
+
description: "PostHog host URL (for self-hosted)",
|
|
1077
|
+
example: "https://app.posthog.com",
|
|
1078
|
+
sensitive: false
|
|
1079
|
+
}
|
|
1080
|
+
]
|
|
1081
|
+
},
|
|
1082
|
+
imports: [
|
|
1083
|
+
{
|
|
1084
|
+
source: "autotel-subscribers/posthog",
|
|
1085
|
+
specifiers: ["PostHogSubscriber"]
|
|
1086
|
+
}
|
|
1087
|
+
],
|
|
1088
|
+
configBlock: {
|
|
1089
|
+
type: "subscriber",
|
|
1090
|
+
code: `new PostHogSubscriber({
|
|
1091
|
+
apiKey: process.env.POSTHOG_API_KEY,
|
|
1092
|
+
host: process.env.POSTHOG_HOST,
|
|
1093
|
+
}),`,
|
|
1094
|
+
section: "SUBSCRIBERS_CONFIG"
|
|
1095
|
+
},
|
|
1096
|
+
nextSteps: [
|
|
1097
|
+
"Set POSTHOG_API_KEY from PostHog Project Settings",
|
|
1098
|
+
"Events will be sent to PostHog as custom events"
|
|
1099
|
+
]
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
// src/presets/subscribers/mixpanel.ts
|
|
1103
|
+
var mixpanel = {
|
|
1104
|
+
name: "Mixpanel",
|
|
1105
|
+
slug: "mixpanel",
|
|
1106
|
+
type: "subscriber",
|
|
1107
|
+
description: "Send events to Mixpanel for product analytics",
|
|
1108
|
+
packages: {
|
|
1109
|
+
required: [
|
|
1110
|
+
"autotel-subscribers",
|
|
1111
|
+
"mixpanel"
|
|
1112
|
+
],
|
|
1113
|
+
optional: [],
|
|
1114
|
+
devOnly: [
|
|
1115
|
+
"@types/mixpanel"
|
|
1116
|
+
]
|
|
1117
|
+
},
|
|
1118
|
+
env: {
|
|
1119
|
+
required: [
|
|
1120
|
+
{
|
|
1121
|
+
name: "MIXPANEL_TOKEN",
|
|
1122
|
+
description: "Mixpanel Project Token",
|
|
1123
|
+
example: "your-project-token",
|
|
1124
|
+
sensitive: true
|
|
1125
|
+
}
|
|
1126
|
+
],
|
|
1127
|
+
optional: [
|
|
1128
|
+
{
|
|
1129
|
+
name: "MIXPANEL_API_HOST",
|
|
1130
|
+
description: "Mixpanel API host (for EU data residency)",
|
|
1131
|
+
example: "api-eu.mixpanel.com",
|
|
1132
|
+
sensitive: false
|
|
1133
|
+
}
|
|
1134
|
+
]
|
|
1135
|
+
},
|
|
1136
|
+
imports: [
|
|
1137
|
+
{
|
|
1138
|
+
source: "autotel-subscribers/mixpanel",
|
|
1139
|
+
specifiers: ["MixpanelSubscriber"]
|
|
1140
|
+
}
|
|
1141
|
+
],
|
|
1142
|
+
configBlock: {
|
|
1143
|
+
type: "subscriber",
|
|
1144
|
+
code: `new MixpanelSubscriber({
|
|
1145
|
+
token: process.env.MIXPANEL_TOKEN,
|
|
1146
|
+
}),`,
|
|
1147
|
+
section: "SUBSCRIBERS_CONFIG"
|
|
1148
|
+
},
|
|
1149
|
+
nextSteps: [
|
|
1150
|
+
"Set MIXPANEL_TOKEN from Mixpanel Project Settings",
|
|
1151
|
+
"Events will be tracked as Mixpanel events"
|
|
1152
|
+
]
|
|
1153
|
+
};
|
|
1154
|
+
var amplitude = {
|
|
1155
|
+
name: "Amplitude",
|
|
1156
|
+
slug: "amplitude",
|
|
1157
|
+
type: "subscriber",
|
|
1158
|
+
description: "Send events to Amplitude for product analytics",
|
|
1159
|
+
packages: {
|
|
1160
|
+
required: [
|
|
1161
|
+
"autotel-subscribers",
|
|
1162
|
+
"@amplitude/analytics-node"
|
|
1163
|
+
],
|
|
1164
|
+
optional: [],
|
|
1165
|
+
devOnly: []
|
|
1166
|
+
},
|
|
1167
|
+
env: {
|
|
1168
|
+
required: [
|
|
1169
|
+
{
|
|
1170
|
+
name: "AMPLITUDE_API_KEY",
|
|
1171
|
+
description: "Amplitude API Key",
|
|
1172
|
+
example: "your-api-key",
|
|
1173
|
+
sensitive: true
|
|
1174
|
+
}
|
|
1175
|
+
],
|
|
1176
|
+
optional: [
|
|
1177
|
+
{
|
|
1178
|
+
name: "AMPLITUDE_SERVER_URL",
|
|
1179
|
+
description: "Amplitude server URL (for EU data center)",
|
|
1180
|
+
example: "https://api.eu.amplitude.com/2/httpapi",
|
|
1181
|
+
sensitive: false
|
|
1182
|
+
}
|
|
1183
|
+
]
|
|
1184
|
+
},
|
|
1185
|
+
imports: [
|
|
1186
|
+
{
|
|
1187
|
+
source: "autotel-subscribers/amplitude",
|
|
1188
|
+
specifiers: ["AmplitudeSubscriber"]
|
|
1189
|
+
}
|
|
1190
|
+
],
|
|
1191
|
+
configBlock: {
|
|
1192
|
+
type: "subscriber",
|
|
1193
|
+
code: `new AmplitudeSubscriber({
|
|
1194
|
+
apiKey: process.env.AMPLITUDE_API_KEY,
|
|
1195
|
+
}),`,
|
|
1196
|
+
section: "SUBSCRIBERS_CONFIG"
|
|
1197
|
+
},
|
|
1198
|
+
nextSteps: [
|
|
1199
|
+
"Set AMPLITUDE_API_KEY from Amplitude Project Settings",
|
|
1200
|
+
"Events will be sent to Amplitude"
|
|
1201
|
+
]
|
|
1202
|
+
};
|
|
1203
|
+
var segment = {
|
|
1204
|
+
name: "Segment",
|
|
1205
|
+
slug: "segment",
|
|
1206
|
+
type: "subscriber",
|
|
1207
|
+
description: "Send events to Segment for routing to destinations",
|
|
1208
|
+
packages: {
|
|
1209
|
+
required: [
|
|
1210
|
+
"autotel-subscribers",
|
|
1211
|
+
"@segment/analytics-node"
|
|
1212
|
+
],
|
|
1213
|
+
optional: [],
|
|
1214
|
+
devOnly: []
|
|
1215
|
+
},
|
|
1216
|
+
env: {
|
|
1217
|
+
required: [
|
|
1218
|
+
{
|
|
1219
|
+
name: "SEGMENT_WRITE_KEY",
|
|
1220
|
+
description: "Segment Source Write Key",
|
|
1221
|
+
example: "your-write-key",
|
|
1222
|
+
sensitive: true
|
|
1223
|
+
}
|
|
1224
|
+
],
|
|
1225
|
+
optional: []
|
|
1226
|
+
},
|
|
1227
|
+
imports: [
|
|
1228
|
+
{
|
|
1229
|
+
source: "autotel-subscribers/segment",
|
|
1230
|
+
specifiers: ["SegmentSubscriber"]
|
|
1231
|
+
}
|
|
1232
|
+
],
|
|
1233
|
+
configBlock: {
|
|
1234
|
+
type: "subscriber",
|
|
1235
|
+
code: `new SegmentSubscriber({
|
|
1236
|
+
writeKey: process.env.SEGMENT_WRITE_KEY,
|
|
1237
|
+
}),`,
|
|
1238
|
+
section: "SUBSCRIBERS_CONFIG"
|
|
1239
|
+
},
|
|
1240
|
+
nextSteps: [
|
|
1241
|
+
"Set SEGMENT_WRITE_KEY from your Segment Source settings",
|
|
1242
|
+
"Configure destinations in Segment to route events"
|
|
1243
|
+
]
|
|
1244
|
+
};
|
|
1245
|
+
var slack = {
|
|
1246
|
+
name: "Slack",
|
|
1247
|
+
slug: "slack",
|
|
1248
|
+
type: "subscriber",
|
|
1249
|
+
description: "Send event notifications to Slack",
|
|
1250
|
+
packages: {
|
|
1251
|
+
required: [
|
|
1252
|
+
"autotel-subscribers",
|
|
1253
|
+
"@slack/web-api"
|
|
1254
|
+
],
|
|
1255
|
+
optional: [],
|
|
1256
|
+
devOnly: []
|
|
1257
|
+
},
|
|
1258
|
+
env: {
|
|
1259
|
+
required: [
|
|
1260
|
+
{
|
|
1261
|
+
name: "SLACK_WEBHOOK_URL",
|
|
1262
|
+
description: "Slack Incoming Webhook URL",
|
|
1263
|
+
example: "https://hooks.slack.com/services/...",
|
|
1264
|
+
sensitive: true
|
|
1265
|
+
}
|
|
1266
|
+
],
|
|
1267
|
+
optional: [
|
|
1268
|
+
{
|
|
1269
|
+
name: "SLACK_CHANNEL",
|
|
1270
|
+
description: "Default channel for notifications",
|
|
1271
|
+
example: "#alerts",
|
|
1272
|
+
sensitive: false
|
|
1273
|
+
}
|
|
1274
|
+
]
|
|
1275
|
+
},
|
|
1276
|
+
imports: [
|
|
1277
|
+
{
|
|
1278
|
+
source: "autotel-subscribers/slack",
|
|
1279
|
+
specifiers: ["SlackSubscriber"]
|
|
1280
|
+
}
|
|
1281
|
+
],
|
|
1282
|
+
configBlock: {
|
|
1283
|
+
type: "subscriber",
|
|
1284
|
+
code: `new SlackSubscriber({
|
|
1285
|
+
webhookUrl: process.env.SLACK_WEBHOOK_URL,
|
|
1286
|
+
channel: process.env.SLACK_CHANNEL,
|
|
1287
|
+
}),`,
|
|
1288
|
+
section: "SUBSCRIBERS_CONFIG"
|
|
1289
|
+
},
|
|
1290
|
+
nextSteps: [
|
|
1291
|
+
"Create an Incoming Webhook in your Slack workspace",
|
|
1292
|
+
"Set SLACK_WEBHOOK_URL with the webhook URL"
|
|
1293
|
+
]
|
|
1294
|
+
};
|
|
1295
|
+
var webhook = {
|
|
1296
|
+
name: "Webhook",
|
|
1297
|
+
slug: "webhook",
|
|
1298
|
+
type: "subscriber",
|
|
1299
|
+
description: "Send events to a custom webhook endpoint",
|
|
1300
|
+
packages: {
|
|
1301
|
+
required: [
|
|
1302
|
+
"autotel-subscribers"
|
|
1303
|
+
],
|
|
1304
|
+
optional: [],
|
|
1305
|
+
devOnly: []
|
|
1306
|
+
},
|
|
1307
|
+
env: {
|
|
1308
|
+
required: [
|
|
1309
|
+
{
|
|
1310
|
+
name: "WEBHOOK_URL",
|
|
1311
|
+
description: "Webhook endpoint URL",
|
|
1312
|
+
example: "https://api.example.com/events",
|
|
1313
|
+
sensitive: false
|
|
1314
|
+
}
|
|
1315
|
+
],
|
|
1316
|
+
optional: [
|
|
1317
|
+
{
|
|
1318
|
+
name: "WEBHOOK_SECRET",
|
|
1319
|
+
description: "Shared secret for webhook signatures",
|
|
1320
|
+
example: "your-secret",
|
|
1321
|
+
sensitive: true
|
|
1322
|
+
}
|
|
1323
|
+
]
|
|
1324
|
+
},
|
|
1325
|
+
imports: [
|
|
1326
|
+
{
|
|
1327
|
+
source: "autotel-subscribers/webhook",
|
|
1328
|
+
specifiers: ["WebhookSubscriber"]
|
|
1329
|
+
}
|
|
1330
|
+
],
|
|
1331
|
+
configBlock: {
|
|
1332
|
+
type: "subscriber",
|
|
1333
|
+
code: `new WebhookSubscriber({
|
|
1334
|
+
url: process.env.WEBHOOK_URL,
|
|
1335
|
+
secret: process.env.WEBHOOK_SECRET,
|
|
1336
|
+
}),`,
|
|
1337
|
+
section: "SUBSCRIBERS_CONFIG"
|
|
1338
|
+
},
|
|
1339
|
+
nextSteps: [
|
|
1340
|
+
"Set WEBHOOK_URL to your endpoint",
|
|
1341
|
+
"Implement the webhook receiver to handle incoming events"
|
|
1342
|
+
]
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1345
|
+
// src/presets/plugins/mongoose.ts
|
|
1346
|
+
var mongoose = {
|
|
1347
|
+
name: "Mongoose",
|
|
1348
|
+
slug: "mongoose",
|
|
1349
|
+
type: "plugin",
|
|
1350
|
+
description: "Instrument Mongoose ODM for MongoDB tracing",
|
|
1351
|
+
packages: {
|
|
1352
|
+
required: [
|
|
1353
|
+
"autotel-plugins"
|
|
1354
|
+
],
|
|
1355
|
+
optional: [],
|
|
1356
|
+
devOnly: []
|
|
1357
|
+
},
|
|
1358
|
+
env: {
|
|
1359
|
+
required: [],
|
|
1360
|
+
optional: []
|
|
1361
|
+
},
|
|
1362
|
+
imports: [
|
|
1363
|
+
{
|
|
1364
|
+
source: "autotel-plugins/mongoose",
|
|
1365
|
+
specifiers: ["instrumentMongoose"]
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
source: "mongoose",
|
|
1369
|
+
default: "mongoose"
|
|
1370
|
+
}
|
|
1371
|
+
],
|
|
1372
|
+
configBlock: {
|
|
1373
|
+
type: "plugin",
|
|
1374
|
+
code: "instrumentMongoose(mongoose);",
|
|
1375
|
+
section: "PLUGIN_INIT"
|
|
1376
|
+
},
|
|
1377
|
+
nextSteps: [
|
|
1378
|
+
"Mongoose operations will now be traced",
|
|
1379
|
+
"Spans will include query, collection, and timing information"
|
|
1380
|
+
]
|
|
1381
|
+
};
|
|
1382
|
+
var drizzle = {
|
|
1383
|
+
name: "Drizzle",
|
|
1384
|
+
slug: "drizzle",
|
|
1385
|
+
type: "plugin",
|
|
1386
|
+
description: "Instrument Drizzle ORM for database tracing",
|
|
1387
|
+
packages: {
|
|
1388
|
+
required: [
|
|
1389
|
+
"autotel-plugins"
|
|
1390
|
+
],
|
|
1391
|
+
optional: [],
|
|
1392
|
+
devOnly: []
|
|
1393
|
+
},
|
|
1394
|
+
env: {
|
|
1395
|
+
required: [],
|
|
1396
|
+
optional: []
|
|
1397
|
+
},
|
|
1398
|
+
imports: [
|
|
1399
|
+
{
|
|
1400
|
+
source: "autotel-plugins/drizzle",
|
|
1401
|
+
specifiers: ["instrumentDrizzle"]
|
|
1402
|
+
}
|
|
1403
|
+
],
|
|
1404
|
+
configBlock: {
|
|
1405
|
+
type: "plugin",
|
|
1406
|
+
code: "// Call instrumentDrizzle(db) with your Drizzle instance",
|
|
1407
|
+
section: "PLUGIN_INIT"
|
|
1408
|
+
},
|
|
1409
|
+
nextSteps: [
|
|
1410
|
+
"Import instrumentDrizzle and call it with your Drizzle instance",
|
|
1411
|
+
"Example: const db = instrumentDrizzle(drizzle(pool))"
|
|
1412
|
+
]
|
|
1413
|
+
};
|
|
1414
|
+
|
|
1415
|
+
// src/presets/platforms/aws.ts
|
|
1416
|
+
var awsLambda = {
|
|
1417
|
+
name: "AWS Lambda",
|
|
1418
|
+
slug: "aws-lambda",
|
|
1419
|
+
type: "platform",
|
|
1420
|
+
description: "AWS Lambda support with cold start handling",
|
|
1421
|
+
packages: {
|
|
1422
|
+
required: [
|
|
1423
|
+
"autotel-platforms",
|
|
1424
|
+
"@opentelemetry/instrumentation-aws-lambda"
|
|
1425
|
+
],
|
|
1426
|
+
optional: [],
|
|
1427
|
+
devOnly: []
|
|
1428
|
+
},
|
|
1429
|
+
env: {
|
|
1430
|
+
required: [],
|
|
1431
|
+
optional: [
|
|
1432
|
+
{
|
|
1433
|
+
name: "AWS_LAMBDA_FUNCTION_NAME",
|
|
1434
|
+
description: "Lambda function name (auto-set by AWS)",
|
|
1435
|
+
example: "my-function",
|
|
1436
|
+
sensitive: false
|
|
1437
|
+
}
|
|
1438
|
+
]
|
|
1439
|
+
},
|
|
1440
|
+
imports: [
|
|
1441
|
+
{
|
|
1442
|
+
source: "autotel-platforms/aws",
|
|
1443
|
+
specifiers: ["createLambdaConfig"]
|
|
1444
|
+
}
|
|
1445
|
+
],
|
|
1446
|
+
configBlock: {
|
|
1447
|
+
type: "platform",
|
|
1448
|
+
code: "...createLambdaConfig(),",
|
|
1449
|
+
section: "BACKEND_CONFIG"
|
|
1450
|
+
},
|
|
1451
|
+
nextSteps: [
|
|
1452
|
+
"Add the Lambda layer or bundle instrumentation with your function",
|
|
1453
|
+
"Set OTEL_* environment variables in Lambda configuration"
|
|
1454
|
+
]
|
|
1455
|
+
};
|
|
1456
|
+
var cloudflare = {
|
|
1457
|
+
name: "Cloudflare Workers",
|
|
1458
|
+
slug: "cloudflare",
|
|
1459
|
+
type: "platform",
|
|
1460
|
+
description: "Cloudflare Workers support",
|
|
1461
|
+
packages: {
|
|
1462
|
+
required: [
|
|
1463
|
+
"autotel-platforms"
|
|
1464
|
+
],
|
|
1465
|
+
optional: [],
|
|
1466
|
+
devOnly: []
|
|
1467
|
+
},
|
|
1468
|
+
env: {
|
|
1469
|
+
required: [],
|
|
1470
|
+
optional: []
|
|
1471
|
+
},
|
|
1472
|
+
imports: [
|
|
1473
|
+
{
|
|
1474
|
+
source: "autotel-platforms/cloudflare",
|
|
1475
|
+
specifiers: ["createCloudflareConfig"]
|
|
1476
|
+
}
|
|
1477
|
+
],
|
|
1478
|
+
configBlock: {
|
|
1479
|
+
type: "platform",
|
|
1480
|
+
code: "...createCloudflareConfig(),",
|
|
1481
|
+
section: "BACKEND_CONFIG"
|
|
1482
|
+
},
|
|
1483
|
+
nextSteps: [
|
|
1484
|
+
"Workers have limited API support - some features may not be available",
|
|
1485
|
+
"Use waitUntil() for async span flushing"
|
|
1486
|
+
]
|
|
1487
|
+
};
|
|
1488
|
+
var edge = {
|
|
1489
|
+
name: "Edge Runtime",
|
|
1490
|
+
slug: "edge",
|
|
1491
|
+
type: "platform",
|
|
1492
|
+
description: "Vercel Edge, Deno Deploy, and other edge runtimes",
|
|
1493
|
+
packages: {
|
|
1494
|
+
required: [
|
|
1495
|
+
"autotel-platforms"
|
|
1496
|
+
],
|
|
1497
|
+
optional: [],
|
|
1498
|
+
devOnly: []
|
|
1499
|
+
},
|
|
1500
|
+
env: {
|
|
1501
|
+
required: [],
|
|
1502
|
+
optional: []
|
|
1503
|
+
},
|
|
1504
|
+
imports: [
|
|
1505
|
+
{
|
|
1506
|
+
source: "autotel-platforms/edge",
|
|
1507
|
+
specifiers: ["createEdgeConfig"]
|
|
1508
|
+
}
|
|
1509
|
+
],
|
|
1510
|
+
configBlock: {
|
|
1511
|
+
type: "platform",
|
|
1512
|
+
code: "...createEdgeConfig(),",
|
|
1513
|
+
section: "BACKEND_CONFIG"
|
|
1514
|
+
},
|
|
1515
|
+
nextSteps: [
|
|
1516
|
+
"Edge runtimes have limited API support",
|
|
1517
|
+
"Auto-instrumentation may not work - use manual instrumentation"
|
|
1518
|
+
]
|
|
1519
|
+
};
|
|
1520
|
+
|
|
1521
|
+
// src/presets/index.ts
|
|
1522
|
+
var backends = /* @__PURE__ */ new Map([
|
|
1523
|
+
["datadog", datadogDirect],
|
|
1524
|
+
["datadog-agent", datadogAgent],
|
|
1525
|
+
["honeycomb", honeycomb],
|
|
1526
|
+
["otlp-http", otlpHttp],
|
|
1527
|
+
["otlp-grpc", otlpGrpc],
|
|
1528
|
+
["local", local]
|
|
1529
|
+
]);
|
|
1530
|
+
var subscribers = /* @__PURE__ */ new Map([
|
|
1531
|
+
["posthog", posthog],
|
|
1532
|
+
["mixpanel", mixpanel],
|
|
1533
|
+
["amplitude", amplitude],
|
|
1534
|
+
["segment", segment],
|
|
1535
|
+
["slack", slack],
|
|
1536
|
+
["webhook", webhook]
|
|
1537
|
+
]);
|
|
1538
|
+
var plugins = /* @__PURE__ */ new Map([
|
|
1539
|
+
["mongoose", mongoose],
|
|
1540
|
+
["drizzle", drizzle]
|
|
1541
|
+
]);
|
|
1542
|
+
var platforms = /* @__PURE__ */ new Map([
|
|
1543
|
+
["aws-lambda", awsLambda],
|
|
1544
|
+
["cloudflare", cloudflare],
|
|
1545
|
+
["edge", edge]
|
|
1546
|
+
]);
|
|
1547
|
+
var quickPresets = /* @__PURE__ */ new Map([
|
|
1548
|
+
[
|
|
1549
|
+
"node-datadog-pino",
|
|
1550
|
+
{
|
|
1551
|
+
name: "Node.js + Datadog + Pino",
|
|
1552
|
+
slug: "node-datadog-pino",
|
|
1553
|
+
description: "Standard Node.js setup with Datadog and Pino logging",
|
|
1554
|
+
backend: "datadog",
|
|
1555
|
+
logging: "pino",
|
|
1556
|
+
autoInstrumentations: "all"
|
|
1557
|
+
}
|
|
1558
|
+
],
|
|
1559
|
+
[
|
|
1560
|
+
"node-datadog-agent",
|
|
1561
|
+
{
|
|
1562
|
+
name: "Node.js + Datadog Agent",
|
|
1563
|
+
slug: "node-datadog-agent",
|
|
1564
|
+
description: "Node.js with Datadog Agent for local development",
|
|
1565
|
+
backend: "datadog-agent",
|
|
1566
|
+
logging: "pino",
|
|
1567
|
+
autoInstrumentations: "all"
|
|
1568
|
+
}
|
|
1569
|
+
],
|
|
1570
|
+
[
|
|
1571
|
+
"node-honeycomb",
|
|
1572
|
+
{
|
|
1573
|
+
name: "Node.js + Honeycomb",
|
|
1574
|
+
slug: "node-honeycomb",
|
|
1575
|
+
description: "Standard Node.js setup with Honeycomb",
|
|
1576
|
+
backend: "honeycomb",
|
|
1577
|
+
autoInstrumentations: "all"
|
|
1578
|
+
}
|
|
1579
|
+
],
|
|
1580
|
+
[
|
|
1581
|
+
"node-otlp",
|
|
1582
|
+
{
|
|
1583
|
+
name: "Node.js + Generic OTLP",
|
|
1584
|
+
slug: "node-otlp",
|
|
1585
|
+
description: "Node.js with generic OTLP endpoint",
|
|
1586
|
+
backend: "otlp-http",
|
|
1587
|
+
autoInstrumentations: "all"
|
|
1588
|
+
}
|
|
1589
|
+
]
|
|
1590
|
+
]);
|
|
1591
|
+
function getPreset(type, slug) {
|
|
1592
|
+
switch (type) {
|
|
1593
|
+
case "backend":
|
|
1594
|
+
return backends.get(slug);
|
|
1595
|
+
case "subscriber":
|
|
1596
|
+
return subscribers.get(slug);
|
|
1597
|
+
case "plugin":
|
|
1598
|
+
return plugins.get(slug);
|
|
1599
|
+
case "platform":
|
|
1600
|
+
return platforms.get(slug);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
function getPresetsByType(type) {
|
|
1604
|
+
switch (type) {
|
|
1605
|
+
case "backend":
|
|
1606
|
+
return backends;
|
|
1607
|
+
case "subscriber":
|
|
1608
|
+
return subscribers;
|
|
1609
|
+
case "plugin":
|
|
1610
|
+
return plugins;
|
|
1611
|
+
case "platform":
|
|
1612
|
+
return platforms;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
function getQuickPreset(slug) {
|
|
1616
|
+
return quickPresets.get(slug);
|
|
1617
|
+
}
|
|
1618
|
+
function listPresetSlugs(type) {
|
|
1619
|
+
return [...getPresetsByType(type).keys()];
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// src/ui/prompts.ts
|
|
1623
|
+
import { select, checkbox, confirm, input } from "@inquirer/prompts";
|
|
1624
|
+
async function promptRuntime() {
|
|
1625
|
+
return await select({
|
|
1626
|
+
message: "What runtime are you using?",
|
|
1627
|
+
choices: [
|
|
1628
|
+
{ value: "node", name: "Node.js" },
|
|
1629
|
+
{ value: "lambda", name: "AWS Lambda" },
|
|
1630
|
+
{ value: "cloudflare", name: "Cloudflare Workers" },
|
|
1631
|
+
{ value: "edge", name: "Edge Runtime (Vercel Edge, etc.)" }
|
|
1632
|
+
],
|
|
1633
|
+
default: "node"
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
async function promptBackend(backends2) {
|
|
1637
|
+
const choices = [
|
|
1638
|
+
{ value: "local", name: "Local/Console (development only)" },
|
|
1639
|
+
...[...backends2.entries()].map(([slug, preset]) => ({
|
|
1640
|
+
value: slug,
|
|
1641
|
+
name: `${preset.name} - ${preset.description}`
|
|
1642
|
+
}))
|
|
1643
|
+
];
|
|
1644
|
+
return await select({
|
|
1645
|
+
message: "Where do you want to send telemetry?",
|
|
1646
|
+
choices,
|
|
1647
|
+
default: "local"
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
async function promptLogging() {
|
|
1651
|
+
return await select({
|
|
1652
|
+
message: "Which logging framework do you use?",
|
|
1653
|
+
choices: [
|
|
1654
|
+
{ value: null, name: "None / Not sure" },
|
|
1655
|
+
{ value: "pino", name: "Pino" },
|
|
1656
|
+
{ value: "winston", name: "Winston" }
|
|
1657
|
+
],
|
|
1658
|
+
default: null
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
async function promptDatabases(plugins2) {
|
|
1662
|
+
const choices = [...plugins2.entries()].filter(([, preset]) => preset.type === "plugin").map(([slug, preset]) => ({
|
|
1663
|
+
value: slug,
|
|
1664
|
+
name: `${preset.name} - ${preset.description}`
|
|
1665
|
+
}));
|
|
1666
|
+
if (choices.length === 0) {
|
|
1667
|
+
return [];
|
|
1668
|
+
}
|
|
1669
|
+
return await checkbox({
|
|
1670
|
+
message: "Which databases/ORMs do you use? (space to select, enter to continue)",
|
|
1671
|
+
choices
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
async function promptSubscribers(subscribers2) {
|
|
1675
|
+
const choices = [...subscribers2.entries()].map(([slug, preset]) => ({
|
|
1676
|
+
value: slug,
|
|
1677
|
+
name: `${preset.name} - ${preset.description}`
|
|
1678
|
+
}));
|
|
1679
|
+
if (choices.length === 0) {
|
|
1680
|
+
return [];
|
|
1681
|
+
}
|
|
1682
|
+
return await checkbox({
|
|
1683
|
+
message: "Which event destinations? (space to select, enter to continue)",
|
|
1684
|
+
choices
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
async function promptAutoInstrumentation() {
|
|
1688
|
+
return await select({
|
|
1689
|
+
message: "Auto-instrument common libraries?",
|
|
1690
|
+
choices: [
|
|
1691
|
+
{ value: "all", name: "All (recommended) - http, express, pg, redis, etc." },
|
|
1692
|
+
{ value: "specific", name: "Let me choose specific ones" },
|
|
1693
|
+
{ value: "none", name: "None - I'll handle it manually" }
|
|
1694
|
+
],
|
|
1695
|
+
default: "all"
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
async function promptStartupStyle(hasTypeScript) {
|
|
1699
|
+
const choices = hasTypeScript ? [
|
|
1700
|
+
{ value: "node-esm", name: "Node ESM (node --import) - Recommended" },
|
|
1701
|
+
{ value: "tsx", name: "tsx (tsx --import) - For development" },
|
|
1702
|
+
{ value: "ts-node", name: "ts-node" },
|
|
1703
|
+
{ value: "nextjs", name: "Next.js" },
|
|
1704
|
+
{ value: "other", name: "Other / Manual" }
|
|
1705
|
+
] : [
|
|
1706
|
+
{ value: "node-esm", name: "Node ESM (node --import) - Recommended" },
|
|
1707
|
+
{ value: "nextjs", name: "Next.js" },
|
|
1708
|
+
{ value: "other", name: "Other / Manual" }
|
|
1709
|
+
];
|
|
1710
|
+
return await select({
|
|
1711
|
+
message: "How do you start your app?",
|
|
1712
|
+
choices,
|
|
1713
|
+
default: "node-esm"
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
async function promptExistingConfigAction() {
|
|
1717
|
+
return await select({
|
|
1718
|
+
message: "Existing instrumentation detected. What would you like to do?",
|
|
1719
|
+
choices: [
|
|
1720
|
+
{ value: "update", name: "Update existing file (recommended)" },
|
|
1721
|
+
{ value: "new", name: "Create new file (src/autotel-config.mts)" },
|
|
1722
|
+
{ value: "abort", name: "Abort" }
|
|
1723
|
+
],
|
|
1724
|
+
default: "update"
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
// src/ui/output.ts
|
|
1729
|
+
import chalk from "chalk";
|
|
1730
|
+
var STATUS = {
|
|
1731
|
+
ok: chalk.green("[OK]"),
|
|
1732
|
+
warn: chalk.yellow("[WARN]"),
|
|
1733
|
+
error: chalk.red("[ERROR]"),
|
|
1734
|
+
info: chalk.blue("[INFO]"),
|
|
1735
|
+
skip: chalk.gray("[SKIP]")
|
|
1736
|
+
};
|
|
1737
|
+
function formatCheck(check) {
|
|
1738
|
+
const lines = [];
|
|
1739
|
+
const statusToken = STATUS[check.status];
|
|
1740
|
+
lines.push(` ${statusToken} ${check.message}`);
|
|
1741
|
+
if (check.details && check.details.length > 0) {
|
|
1742
|
+
for (const detail of check.details) {
|
|
1743
|
+
lines.push(` ${chalk.dim(detail)}`);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
if (check.fix) {
|
|
1747
|
+
lines.push(` ${chalk.cyan("Fix:")} ${check.fix.cmd}`);
|
|
1748
|
+
}
|
|
1749
|
+
return lines;
|
|
1750
|
+
}
|
|
1751
|
+
function formatSummary(summary) {
|
|
1752
|
+
const parts = [];
|
|
1753
|
+
if (summary.ok > 0) {
|
|
1754
|
+
parts.push(chalk.green(`${summary.ok} passed`));
|
|
1755
|
+
}
|
|
1756
|
+
if (summary.warnings > 0) {
|
|
1757
|
+
parts.push(chalk.yellow(`${summary.warnings} warning${summary.warnings > 1 ? "s" : ""}`));
|
|
1758
|
+
}
|
|
1759
|
+
if (summary.errors > 0) {
|
|
1760
|
+
parts.push(chalk.red(`${summary.errors} error${summary.errors > 1 ? "s" : ""}`));
|
|
1761
|
+
}
|
|
1762
|
+
if (summary.skipped > 0) {
|
|
1763
|
+
parts.push(chalk.gray(`${summary.skipped} skipped`));
|
|
1764
|
+
}
|
|
1765
|
+
return `Summary: ${parts.join(", ")}`;
|
|
1766
|
+
}
|
|
1767
|
+
function formatFooter(options) {
|
|
1768
|
+
const lines = [""];
|
|
1769
|
+
if (options.detected) {
|
|
1770
|
+
lines.push(chalk.dim(`Detected: ${options.detected}`));
|
|
1771
|
+
}
|
|
1772
|
+
if (options.wrote && options.wrote.length > 0) {
|
|
1773
|
+
lines.push(chalk.dim(`Wrote: ${options.wrote.join(", ")}`));
|
|
1774
|
+
}
|
|
1775
|
+
if (options.next) {
|
|
1776
|
+
lines.push(chalk.cyan(`Next: ${options.next}`));
|
|
1777
|
+
}
|
|
1778
|
+
return lines.join("\n");
|
|
1779
|
+
}
|
|
1780
|
+
function heading(text) {
|
|
1781
|
+
console.log(chalk.bold(text));
|
|
1782
|
+
}
|
|
1783
|
+
function info(text) {
|
|
1784
|
+
console.log(chalk.blue(text));
|
|
1785
|
+
}
|
|
1786
|
+
function success(text) {
|
|
1787
|
+
console.log(chalk.green(text));
|
|
1788
|
+
}
|
|
1789
|
+
function warn(text) {
|
|
1790
|
+
console.log(chalk.yellow(text));
|
|
1791
|
+
}
|
|
1792
|
+
function error(text) {
|
|
1793
|
+
console.log(chalk.red(text));
|
|
1794
|
+
}
|
|
1795
|
+
function dim(text) {
|
|
1796
|
+
console.log(chalk.dim(text));
|
|
1797
|
+
}
|
|
1798
|
+
function isVerbose() {
|
|
1799
|
+
return process.env["AUTOTEL_VERBOSE"] === "true";
|
|
1800
|
+
}
|
|
1801
|
+
function verbose(text) {
|
|
1802
|
+
if (isVerbose()) {
|
|
1803
|
+
console.log(chalk.gray(`[verbose] ${text}`));
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// src/ui/spinner.ts
|
|
1808
|
+
import ora from "ora";
|
|
1809
|
+
function createSpinner(text) {
|
|
1810
|
+
const spinner = ora({
|
|
1811
|
+
text,
|
|
1812
|
+
spinner: "dots"
|
|
1813
|
+
});
|
|
1814
|
+
return {
|
|
1815
|
+
start: (newText) => {
|
|
1816
|
+
if (newText) spinner.text = newText;
|
|
1817
|
+
spinner.start();
|
|
1818
|
+
},
|
|
1819
|
+
stop: () => spinner.stop(),
|
|
1820
|
+
succeed: (newText) => spinner.succeed(newText),
|
|
1821
|
+
fail: (newText) => spinner.fail(newText),
|
|
1822
|
+
warn: (newText) => spinner.warn(newText),
|
|
1823
|
+
info: (newText) => spinner.info(newText),
|
|
1824
|
+
text: (newText) => {
|
|
1825
|
+
spinner.text = newText;
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
function isCI() {
|
|
1830
|
+
return !!(process.env["CI"] || process.env["CONTINUOUS_INTEGRATION"] || process.env["BUILD_NUMBER"] || process.env["GITHUB_ACTIONS"] || process.env["GITLAB_CI"] || process.env["CIRCLECI"] || process.env["TRAVIS"]);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// src/commands/init.ts
|
|
1834
|
+
async function runInit(options) {
|
|
1835
|
+
const spinner = createSpinner();
|
|
1836
|
+
if (options.verbose) {
|
|
1837
|
+
process.env["AUTOTEL_VERBOSE"] = "true";
|
|
1838
|
+
}
|
|
1839
|
+
if (options.quiet) {
|
|
1840
|
+
process.env["AUTOTEL_QUIET"] = "true";
|
|
1841
|
+
}
|
|
1842
|
+
spinner.start("Discovering project...");
|
|
1843
|
+
const project = discoverProject(options.cwd);
|
|
1844
|
+
if (!project) {
|
|
1845
|
+
spinner.fail("No package.json found");
|
|
1846
|
+
error("Run this command in a directory with a package.json, or use --cwd");
|
|
1847
|
+
process.exit(1);
|
|
1848
|
+
}
|
|
1849
|
+
spinner.succeed(`Found ${project.packageJson.name ?? "project"}`);
|
|
1850
|
+
verbose(`Package root: ${project.packageRoot}`);
|
|
1851
|
+
verbose(`Package manager: ${project.packageManager}`);
|
|
1852
|
+
const existingConfig = detectConfig(project.packageRoot);
|
|
1853
|
+
if (existingConfig.found && !options.force) {
|
|
1854
|
+
info(`Existing instrumentation detected at ${existingConfig.path}`);
|
|
1855
|
+
if (options.yes || isCI()) {
|
|
1856
|
+
warn("Use --force to overwrite existing config");
|
|
1857
|
+
process.exit(0);
|
|
1858
|
+
}
|
|
1859
|
+
const action = await promptExistingConfigAction();
|
|
1860
|
+
if (action === "abort") {
|
|
1861
|
+
info("Aborted");
|
|
1862
|
+
process.exit(0);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
const selectedPresets = [];
|
|
1866
|
+
let autoInstrumentations = "all";
|
|
1867
|
+
let startupStyle = "node-esm";
|
|
1868
|
+
if (options.preset) {
|
|
1869
|
+
const quickPreset = getQuickPreset(options.preset);
|
|
1870
|
+
if (quickPreset) {
|
|
1871
|
+
info(`Using quick preset: ${quickPreset.name}`);
|
|
1872
|
+
const backendPreset = getPreset("backend", quickPreset.backend);
|
|
1873
|
+
if (backendPreset) {
|
|
1874
|
+
selectedPresets.push(backendPreset);
|
|
1875
|
+
}
|
|
1876
|
+
if (quickPreset.subscribers) {
|
|
1877
|
+
for (const sub of quickPreset.subscribers) {
|
|
1878
|
+
const subPreset = getPreset("subscriber", sub);
|
|
1879
|
+
if (subPreset) selectedPresets.push(subPreset);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
if (quickPreset.plugins) {
|
|
1883
|
+
for (const plug of quickPreset.plugins) {
|
|
1884
|
+
const plugPreset = getPreset("plugin", plug);
|
|
1885
|
+
if (plugPreset) selectedPresets.push(plugPreset);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
autoInstrumentations = quickPreset.autoInstrumentations;
|
|
1889
|
+
} else {
|
|
1890
|
+
error(`Unknown preset: ${options.preset}`);
|
|
1891
|
+
info("Available presets: node-datadog-pino, node-datadog-agent, node-honeycomb, node-otlp");
|
|
1892
|
+
process.exit(1);
|
|
1893
|
+
}
|
|
1894
|
+
} else if (options.yes || isCI()) {
|
|
1895
|
+
info("Using defaults (local backend, all auto-instrumentations)");
|
|
1896
|
+
const localPreset = getPreset("backend", "local");
|
|
1897
|
+
if (localPreset) {
|
|
1898
|
+
selectedPresets.push(localPreset);
|
|
1899
|
+
}
|
|
1900
|
+
} else {
|
|
1901
|
+
const runtime = await promptRuntime();
|
|
1902
|
+
verbose(`Runtime: ${runtime}`);
|
|
1903
|
+
const backendSlug = await promptBackend(backends);
|
|
1904
|
+
const backendPreset = getPreset("backend", backendSlug);
|
|
1905
|
+
if (backendPreset) {
|
|
1906
|
+
selectedPresets.push(backendPreset);
|
|
1907
|
+
}
|
|
1908
|
+
await promptLogging();
|
|
1909
|
+
const pluginSlugs = await promptDatabases(plugins);
|
|
1910
|
+
for (const slug of pluginSlugs) {
|
|
1911
|
+
const preset = getPreset("plugin", slug);
|
|
1912
|
+
if (preset) selectedPresets.push(preset);
|
|
1913
|
+
}
|
|
1914
|
+
const subscriberSlugs = await promptSubscribers(subscribers);
|
|
1915
|
+
for (const slug of subscriberSlugs) {
|
|
1916
|
+
const preset = getPreset("subscriber", slug);
|
|
1917
|
+
if (preset) selectedPresets.push(preset);
|
|
1918
|
+
}
|
|
1919
|
+
const autoChoice = await promptAutoInstrumentation();
|
|
1920
|
+
if (autoChoice === "none") {
|
|
1921
|
+
autoInstrumentations = "none";
|
|
1922
|
+
} else if (autoChoice === "specific") {
|
|
1923
|
+
autoInstrumentations = "all";
|
|
1924
|
+
}
|
|
1925
|
+
if (runtime === "node") {
|
|
1926
|
+
startupStyle = await promptStartupStyle(project.hasTypeScript);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
const codeFile = createCodeFile();
|
|
1930
|
+
addImport(codeFile, { source: "autotel/register", sideEffect: true });
|
|
1931
|
+
addImport(codeFile, { source: "autotel", specifiers: ["init"] });
|
|
1932
|
+
for (const preset of selectedPresets) {
|
|
1933
|
+
for (const imp of preset.imports) {
|
|
1934
|
+
const section = preset.type === "backend" || preset.type === "platform" ? "backend" : preset.type === "plugin" ? "plugin" : preset.type === "subscriber" ? "subscriber" : void 0;
|
|
1935
|
+
addImport(codeFile, imp, section);
|
|
1936
|
+
}
|
|
1937
|
+
if (preset.configBlock.section === "BACKEND_CONFIG") {
|
|
1938
|
+
setBackendConfig(codeFile, preset.configBlock.code);
|
|
1939
|
+
} else if (preset.configBlock.section === "SUBSCRIBERS_CONFIG") {
|
|
1940
|
+
addSubscriberConfig(codeFile, preset.configBlock.code);
|
|
1941
|
+
} else if (preset.configBlock.section === "PLUGIN_INIT") {
|
|
1942
|
+
addPluginInit(codeFile, preset.configBlock.code);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
if (!codeFile.backendConfig) {
|
|
1946
|
+
setBackendConfig(codeFile, "// Local/console mode - no backend configured");
|
|
1947
|
+
}
|
|
1948
|
+
const instrumentationContent = renderCodeFile(codeFile);
|
|
1949
|
+
const depPlan = buildDependencyPlan({
|
|
1950
|
+
presets: selectedPresets,
|
|
1951
|
+
autoInstrumentations
|
|
1952
|
+
});
|
|
1953
|
+
const envVars = [];
|
|
1954
|
+
for (const preset of selectedPresets) {
|
|
1955
|
+
envVars.push(...preset.env.required, ...preset.env.optional);
|
|
1956
|
+
}
|
|
1957
|
+
const envExampleContent = generateEnvExample(envVars);
|
|
1958
|
+
const instrumentationPath = getInstrumentationPath(project.packageRoot, project.hasTypeScript);
|
|
1959
|
+
const envExamplePath = path6.join(project.packageRoot, ".env.example");
|
|
1960
|
+
if (options.dryRun) {
|
|
1961
|
+
heading("\nDry run - no files will be written\n");
|
|
1962
|
+
info(`Would write: ${path6.relative(project.cwd, instrumentationPath)}`);
|
|
1963
|
+
console.log("---");
|
|
1964
|
+
console.log(instrumentationContent);
|
|
1965
|
+
console.log("---\n");
|
|
1966
|
+
if (envExampleContent) {
|
|
1967
|
+
info(`Would write: ${path6.relative(project.cwd, envExamplePath)}`);
|
|
1968
|
+
console.log("---");
|
|
1969
|
+
console.log(envExampleContent);
|
|
1970
|
+
console.log("---\n");
|
|
1971
|
+
}
|
|
1972
|
+
const prodPkgs2 = getProdPackages(depPlan);
|
|
1973
|
+
const devPkgs2 = getDevPackages(depPlan);
|
|
1974
|
+
if (prodPkgs2.length > 0) {
|
|
1975
|
+
const cmd = getInstallCommand(project.packageManager, prodPkgs2);
|
|
1976
|
+
info(`Would run: ${cmd}`);
|
|
1977
|
+
}
|
|
1978
|
+
if (devPkgs2.length > 0) {
|
|
1979
|
+
const cmd = getInstallCommand(project.packageManager, devPkgs2, { dev: true });
|
|
1980
|
+
info(`Would run: ${cmd}`);
|
|
1981
|
+
}
|
|
1982
|
+
process.exit(0);
|
|
1983
|
+
}
|
|
1984
|
+
spinner.start("Writing instrumentation file...");
|
|
1985
|
+
const { backupPath: instrBackup } = atomicWrite(instrumentationPath, instrumentationContent, {
|
|
1986
|
+
root: project.packageRoot,
|
|
1987
|
+
backup: options.force
|
|
1988
|
+
});
|
|
1989
|
+
if (instrBackup) {
|
|
1990
|
+
verbose(`Backup created: ${instrBackup}`);
|
|
1991
|
+
}
|
|
1992
|
+
spinner.succeed(`Wrote ${path6.relative(project.cwd, instrumentationPath)}`);
|
|
1993
|
+
if (envExampleContent && !fileExists(envExamplePath)) {
|
|
1994
|
+
spinner.start("Writing .env.example...");
|
|
1995
|
+
atomicWrite(envExamplePath, envExampleContent, { root: project.packageRoot });
|
|
1996
|
+
spinner.succeed(`Wrote ${path6.relative(project.cwd, envExamplePath)}`);
|
|
1997
|
+
}
|
|
1998
|
+
const prodPkgs = getProdPackages(depPlan);
|
|
1999
|
+
const devPkgs = getDevPackages(depPlan);
|
|
2000
|
+
if (!options.noInstall && (prodPkgs.length > 0 || devPkgs.length > 0)) {
|
|
2001
|
+
if (prodPkgs.length > 0) {
|
|
2002
|
+
const cmd = getInstallCommand(project.packageManager, prodPkgs);
|
|
2003
|
+
if (options.printInstallCmd) {
|
|
2004
|
+
info(`Install command: ${cmd}`);
|
|
2005
|
+
} else {
|
|
2006
|
+
spinner.start("Installing dependencies...");
|
|
2007
|
+
try {
|
|
2008
|
+
execSync(cmd, { cwd: project.packageRoot, stdio: "pipe" });
|
|
2009
|
+
spinner.succeed("Dependencies installed");
|
|
2010
|
+
} catch {
|
|
2011
|
+
spinner.fail("Failed to install dependencies");
|
|
2012
|
+
error(`Run manually: ${cmd}`);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
if (devPkgs.length > 0) {
|
|
2017
|
+
const cmd = getInstallCommand(project.packageManager, devPkgs, { dev: true });
|
|
2018
|
+
if (options.printInstallCmd) {
|
|
2019
|
+
info(`Install command (dev): ${cmd}`);
|
|
2020
|
+
} else {
|
|
2021
|
+
spinner.start("Installing dev dependencies...");
|
|
2022
|
+
try {
|
|
2023
|
+
execSync(cmd, { cwd: project.packageRoot, stdio: "pipe" });
|
|
2024
|
+
spinner.succeed("Dev dependencies installed");
|
|
2025
|
+
} catch {
|
|
2026
|
+
spinner.fail("Failed to install dev dependencies");
|
|
2027
|
+
error(`Run manually: ${cmd}`);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
} else if (options.noInstall && (prodPkgs.length > 0 || devPkgs.length > 0)) {
|
|
2032
|
+
info("Skipping installation (--no-install)");
|
|
2033
|
+
if (prodPkgs.length > 0) {
|
|
2034
|
+
const cmd = getInstallCommand(project.packageManager, prodPkgs);
|
|
2035
|
+
dim(`Run: ${cmd}`);
|
|
2036
|
+
}
|
|
2037
|
+
if (devPkgs.length > 0) {
|
|
2038
|
+
const cmd = getInstallCommand(project.packageManager, devPkgs, { dev: true });
|
|
2039
|
+
dim(`Run: ${cmd}`);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
const relInstrPath = path6.relative(project.packageRoot, instrumentationPath);
|
|
2043
|
+
let nextStepCmd;
|
|
2044
|
+
switch (startupStyle) {
|
|
2045
|
+
case "tsx":
|
|
2046
|
+
nextStepCmd = `tsx --import ./${relInstrPath} src/index.ts`;
|
|
2047
|
+
break;
|
|
2048
|
+
case "node-esm":
|
|
2049
|
+
default:
|
|
2050
|
+
nextStepCmd = `node --import ./${relInstrPath} dist/index.js`;
|
|
2051
|
+
}
|
|
2052
|
+
const pmInfo = project.workspace.isMonorepo ? `${project.packageManager} workspace, package root ${project.packageRoot}` : `${project.packageManager}`;
|
|
2053
|
+
const writtenFiles = [path6.relative(project.cwd, instrumentationPath)];
|
|
2054
|
+
if (envExampleContent && !fileExists(envExamplePath)) {
|
|
2055
|
+
writtenFiles.push(".env.example");
|
|
2056
|
+
}
|
|
2057
|
+
console.log(formatFooter({
|
|
2058
|
+
detected: pmInfo,
|
|
2059
|
+
wrote: writtenFiles,
|
|
2060
|
+
next: nextStepCmd
|
|
2061
|
+
}));
|
|
2062
|
+
const allNextSteps = selectedPresets.flatMap((p) => p.nextSteps);
|
|
2063
|
+
if (allNextSteps.length > 0) {
|
|
2064
|
+
console.log("\nNext steps:");
|
|
2065
|
+
for (const step of allNextSteps) {
|
|
2066
|
+
console.log(` - ${step}`);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
// src/lib/dependency-auditor.ts
|
|
2072
|
+
import * as semver from "semver";
|
|
2073
|
+
function isPackageInstalled(packageJson, packageName) {
|
|
2074
|
+
const deps = packageJson.dependencies ?? {};
|
|
2075
|
+
const devDeps = packageJson.devDependencies ?? {};
|
|
2076
|
+
if (deps[packageName]) {
|
|
2077
|
+
return { installed: true, version: deps[packageName] ?? null, isDev: false };
|
|
2078
|
+
}
|
|
2079
|
+
if (devDeps[packageName]) {
|
|
2080
|
+
return { installed: true, version: devDeps[packageName] ?? null, isDev: true };
|
|
2081
|
+
}
|
|
2082
|
+
return { installed: false, version: null, isDev: false };
|
|
2083
|
+
}
|
|
2084
|
+
function checkAutotelVersions(packageJson) {
|
|
2085
|
+
const autotelPackages = ["autotel", "autotel-backends", "autotel-plugins", "autotel-subscribers"];
|
|
2086
|
+
const installed = [];
|
|
2087
|
+
for (const pkg of autotelPackages) {
|
|
2088
|
+
const { installed: isInstalled, version } = isPackageInstalled(packageJson, pkg);
|
|
2089
|
+
if (isInstalled && version) {
|
|
2090
|
+
const cleanVersion = version.replace(/^[\^~]/, "");
|
|
2091
|
+
const parsed = semver.parse(cleanVersion);
|
|
2092
|
+
if (parsed) {
|
|
2093
|
+
installed.push({ name: pkg, version: cleanVersion, major: parsed.major });
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
if (installed.length <= 1) {
|
|
2098
|
+
return { compatible: true, packages: installed };
|
|
2099
|
+
}
|
|
2100
|
+
const majors = new Set(installed.map((p) => p.major));
|
|
2101
|
+
const compatible = majors.size === 1;
|
|
2102
|
+
return { compatible, packages: installed };
|
|
2103
|
+
}
|
|
2104
|
+
function getAutotelInfo(packageJson) {
|
|
2105
|
+
const { installed, version } = isPackageInstalled(packageJson, "autotel");
|
|
2106
|
+
return { installed, version };
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// src/lib/esm-checker.ts
|
|
2110
|
+
import * as path7 from "path";
|
|
2111
|
+
function checkRegisterImportOrder(content) {
|
|
2112
|
+
const lines = content.split("\n");
|
|
2113
|
+
let registerLine = null;
|
|
2114
|
+
let firstImportLine = null;
|
|
2115
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2116
|
+
const line = lines[i]?.trim() ?? "";
|
|
2117
|
+
if (line === "" || line.startsWith("//") || line.startsWith("/*") || line.startsWith("*")) {
|
|
2118
|
+
continue;
|
|
2119
|
+
}
|
|
2120
|
+
if (line.startsWith("import ") || line.startsWith("import'") || line.startsWith('import"')) {
|
|
2121
|
+
if (firstImportLine === null) {
|
|
2122
|
+
firstImportLine = i + 1;
|
|
2123
|
+
}
|
|
2124
|
+
if (line.includes("autotel/register")) {
|
|
2125
|
+
registerLine = i + 1;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
if (registerLine === null) {
|
|
2130
|
+
return { found: false, isFirst: false, lineNumber: null };
|
|
2131
|
+
}
|
|
2132
|
+
return {
|
|
2133
|
+
found: true,
|
|
2134
|
+
isFirst: registerLine === firstImportLine,
|
|
2135
|
+
lineNumber: registerLine
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
function checkEsmHook(project) {
|
|
2139
|
+
if (!project.isEsm) {
|
|
2140
|
+
return {
|
|
2141
|
+
status: "info",
|
|
2142
|
+
message: "Project uses CommonJS; ESM hook check skipped",
|
|
2143
|
+
details: [
|
|
2144
|
+
"autotel works best with ESM projects",
|
|
2145
|
+
'Consider adding "type": "module" to package.json'
|
|
2146
|
+
]
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
const entrypoints = getEntrypointCandidates(project.packageRoot);
|
|
2150
|
+
if (entrypoints.length === 0) {
|
|
2151
|
+
return {
|
|
2152
|
+
status: "info",
|
|
2153
|
+
message: "Could not find entrypoint files to verify",
|
|
2154
|
+
details: [
|
|
2155
|
+
"Ensure autotel/register is imported first in your entrypoint",
|
|
2156
|
+
"Or use --import flag: node --import ./src/instrumentation.mts dist/index.js"
|
|
2157
|
+
]
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
for (const entrypoint of entrypoints) {
|
|
2161
|
+
const content = readFileSafe(entrypoint);
|
|
2162
|
+
if (content === null) continue;
|
|
2163
|
+
const result = checkRegisterImportOrder(content);
|
|
2164
|
+
if (result.found && !result.isFirst) {
|
|
2165
|
+
return {
|
|
2166
|
+
status: "warn",
|
|
2167
|
+
message: `autotel/register import found but not first in ${path7.basename(entrypoint)}:${result.lineNumber}`,
|
|
2168
|
+
details: [
|
|
2169
|
+
"autotel/register must be the first import for instrumentation to work",
|
|
2170
|
+
"Move it to the top of the file, before any other imports"
|
|
2171
|
+
]
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
2174
|
+
if (result.found && result.isFirst) {
|
|
2175
|
+
return {
|
|
2176
|
+
status: "ok",
|
|
2177
|
+
message: `autotel/register correctly imported first in ${path7.basename(entrypoint)}`
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
return {
|
|
2182
|
+
status: "info",
|
|
2183
|
+
message: "Could not verify autotel/register import order",
|
|
2184
|
+
details: [
|
|
2185
|
+
"Ensure autotel/register is first import in entrypoint",
|
|
2186
|
+
"Or use the recommended approach: node --import ./src/instrumentation.mts"
|
|
2187
|
+
]
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
// src/commands/doctor.ts
|
|
2192
|
+
var CHECK_DEFINITIONS = [
|
|
2193
|
+
{ id: "autotel-installed", title: "Autotel installed", description: "Check if autotel is installed" },
|
|
2194
|
+
{ id: "peer-deps", title: "Peer dependencies", description: "Check if required peer dependencies are installed" },
|
|
2195
|
+
{ id: "esm-hook", title: "ESM hook setup", description: "Check if autotel/register is imported correctly" },
|
|
2196
|
+
{ id: "env-vars", title: "Environment variables", description: "Check if required env vars are present" },
|
|
2197
|
+
{ id: "version-compat", title: "Version compatibility", description: "Check autotel package versions match" },
|
|
2198
|
+
{ id: "config-found", title: "Configuration found", description: "Check if instrumentation config exists" }
|
|
2199
|
+
];
|
|
2200
|
+
async function inferBackend(packageRoot, deps) {
|
|
2201
|
+
if (deps["autotel-backends"]) {
|
|
2202
|
+
if (deps["@opentelemetry/exporter-trace-otlp-grpc"]) {
|
|
2203
|
+
return getPreset("backend", "honeycomb") ?? null;
|
|
2204
|
+
}
|
|
2205
|
+
if (deps["@opentelemetry/exporter-trace-otlp-http"]) {
|
|
2206
|
+
const ddVars = await checkEnvVarsPresent(packageRoot, ["DATADOG_API_KEY", "DD_API_KEY"]);
|
|
2207
|
+
for (const [, result] of ddVars) {
|
|
2208
|
+
if (result.found) {
|
|
2209
|
+
return getPreset("backend", "datadog") ?? null;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
return getPreset("backend", "otlp-http") ?? null;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
return null;
|
|
2216
|
+
}
|
|
2217
|
+
async function runChecks(options, projectRoot) {
|
|
2218
|
+
const project = discoverProject(projectRoot);
|
|
2219
|
+
if (!project) {
|
|
2220
|
+
return [{
|
|
2221
|
+
id: "project",
|
|
2222
|
+
title: "Project discovery",
|
|
2223
|
+
level: "error",
|
|
2224
|
+
status: "error",
|
|
2225
|
+
message: "No package.json found"
|
|
2226
|
+
}];
|
|
2227
|
+
}
|
|
2228
|
+
const checks = [];
|
|
2229
|
+
const deps = { ...project.packageJson.dependencies, ...project.packageJson.devDependencies };
|
|
2230
|
+
const autotelInfo = getAutotelInfo(project.packageJson);
|
|
2231
|
+
if (autotelInfo.installed) {
|
|
2232
|
+
checks.push({
|
|
2233
|
+
id: "autotel-installed",
|
|
2234
|
+
title: "Autotel installed",
|
|
2235
|
+
level: "error",
|
|
2236
|
+
status: "ok",
|
|
2237
|
+
message: `autotel@${autotelInfo.version} is installed`
|
|
2238
|
+
});
|
|
2239
|
+
} else {
|
|
2240
|
+
checks.push({
|
|
2241
|
+
id: "autotel-installed",
|
|
2242
|
+
title: "Autotel installed",
|
|
2243
|
+
level: "error",
|
|
2244
|
+
status: "error",
|
|
2245
|
+
message: "autotel is not installed",
|
|
2246
|
+
fix: {
|
|
2247
|
+
cmd: getInstallCommand(project.packageManager, ["autotel"]),
|
|
2248
|
+
description: "Install autotel package"
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
const config = detectConfig(project.packageRoot);
|
|
2253
|
+
if (config.found) {
|
|
2254
|
+
if (config.type === "cli-owned") {
|
|
2255
|
+
checks.push({
|
|
2256
|
+
id: "config-found",
|
|
2257
|
+
title: "Configuration found",
|
|
2258
|
+
level: "info",
|
|
2259
|
+
status: "ok",
|
|
2260
|
+
message: `CLI-owned instrumentation at ${config.path}`
|
|
2261
|
+
});
|
|
2262
|
+
} else if (config.type === "user-created") {
|
|
2263
|
+
checks.push({
|
|
2264
|
+
id: "config-found",
|
|
2265
|
+
title: "Configuration found",
|
|
2266
|
+
level: "info",
|
|
2267
|
+
status: "ok",
|
|
2268
|
+
message: `User-created instrumentation at ${config.path}`,
|
|
2269
|
+
details: ["Add CLI header to enable auto-updates"]
|
|
2270
|
+
});
|
|
2271
|
+
} else {
|
|
2272
|
+
checks.push({
|
|
2273
|
+
id: "config-found",
|
|
2274
|
+
title: "Configuration found",
|
|
2275
|
+
level: "info",
|
|
2276
|
+
status: "ok",
|
|
2277
|
+
message: `Config found at ${config.path}`
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
} else {
|
|
2281
|
+
checks.push({
|
|
2282
|
+
id: "config-found",
|
|
2283
|
+
title: "Configuration found",
|
|
2284
|
+
level: "info",
|
|
2285
|
+
status: "warn",
|
|
2286
|
+
message: "No instrumentation config found",
|
|
2287
|
+
details: [
|
|
2288
|
+
"Run `autotel init` to create a CLI-owned config",
|
|
2289
|
+
"Or add `import 'autotel/register'` and call `init()` manually"
|
|
2290
|
+
]
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
const inferredBackend = await inferBackend(project.packageRoot, deps);
|
|
2294
|
+
const canInferPreset = inferredBackend !== null || config.type === "cli-owned";
|
|
2295
|
+
if (canInferPreset && inferredBackend) {
|
|
2296
|
+
const missingDeps = [];
|
|
2297
|
+
for (const pkg of inferredBackend.packages.required) {
|
|
2298
|
+
if (!deps[pkg]) {
|
|
2299
|
+
missingDeps.push(pkg);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
if (missingDeps.length > 0) {
|
|
2303
|
+
checks.push({
|
|
2304
|
+
id: "peer-deps",
|
|
2305
|
+
title: "Peer dependencies",
|
|
2306
|
+
level: "warning",
|
|
2307
|
+
status: "warn",
|
|
2308
|
+
message: "Missing peer dependencies required by selected backend",
|
|
2309
|
+
details: missingDeps.map((d) => `Missing: ${d}`),
|
|
2310
|
+
fix: {
|
|
2311
|
+
cmd: getInstallCommand(project.packageManager, missingDeps),
|
|
2312
|
+
description: "Install missing peer dependencies"
|
|
2313
|
+
}
|
|
2314
|
+
});
|
|
2315
|
+
} else {
|
|
2316
|
+
checks.push({
|
|
2317
|
+
id: "peer-deps",
|
|
2318
|
+
title: "Peer dependencies",
|
|
2319
|
+
level: "warning",
|
|
2320
|
+
status: "ok",
|
|
2321
|
+
message: "All peer dependencies satisfied"
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
} else {
|
|
2325
|
+
checks.push({
|
|
2326
|
+
id: "peer-deps",
|
|
2327
|
+
title: "Peer dependencies",
|
|
2328
|
+
level: "warning",
|
|
2329
|
+
status: "skip",
|
|
2330
|
+
message: "Could not infer backend; skipping peer dep check",
|
|
2331
|
+
details: [
|
|
2332
|
+
"Run `autotel init` to create a CLI-owned config",
|
|
2333
|
+
"Or add header marker to existing instrumentation"
|
|
2334
|
+
]
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
if (canInferPreset && inferredBackend && inferredBackend.env.required.length > 0) {
|
|
2338
|
+
const requiredVarNames = inferredBackend.env.required.map((v) => v.name);
|
|
2339
|
+
const envResults = await checkEnvVarsPresent(
|
|
2340
|
+
project.packageRoot,
|
|
2341
|
+
requiredVarNames,
|
|
2342
|
+
options.envFile
|
|
2343
|
+
);
|
|
2344
|
+
const missingVars = [];
|
|
2345
|
+
for (const [varName, result] of envResults) {
|
|
2346
|
+
if (!result.found) {
|
|
2347
|
+
missingVars.push(varName);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
if (missingVars.length > 0) {
|
|
2351
|
+
checks.push({
|
|
2352
|
+
id: "env-vars",
|
|
2353
|
+
title: "Environment variables",
|
|
2354
|
+
level: "warning",
|
|
2355
|
+
status: "warn",
|
|
2356
|
+
message: "Missing required environment variables",
|
|
2357
|
+
details: [
|
|
2358
|
+
...missingVars.map((v) => `Missing: ${v}`),
|
|
2359
|
+
"Set in environment or deployment secrets"
|
|
2360
|
+
]
|
|
2361
|
+
});
|
|
2362
|
+
} else {
|
|
2363
|
+
checks.push({
|
|
2364
|
+
id: "env-vars",
|
|
2365
|
+
title: "Environment variables",
|
|
2366
|
+
level: "warning",
|
|
2367
|
+
status: "ok",
|
|
2368
|
+
message: "Required environment variables found"
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
2371
|
+
} else if (!canInferPreset) {
|
|
2372
|
+
checks.push({
|
|
2373
|
+
id: "env-vars",
|
|
2374
|
+
title: "Environment variables",
|
|
2375
|
+
level: "warning",
|
|
2376
|
+
status: "skip",
|
|
2377
|
+
message: "Could not infer backend; skipping env check"
|
|
2378
|
+
});
|
|
2379
|
+
} else {
|
|
2380
|
+
checks.push({
|
|
2381
|
+
id: "env-vars",
|
|
2382
|
+
title: "Environment variables",
|
|
2383
|
+
level: "warning",
|
|
2384
|
+
status: "ok",
|
|
2385
|
+
message: "No required environment variables for this backend"
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
const versionCheck = checkAutotelVersions(project.packageJson);
|
|
2389
|
+
if (versionCheck.packages.length > 1) {
|
|
2390
|
+
if (versionCheck.compatible) {
|
|
2391
|
+
checks.push({
|
|
2392
|
+
id: "version-compat",
|
|
2393
|
+
title: "Version compatibility",
|
|
2394
|
+
level: "warning",
|
|
2395
|
+
status: "ok",
|
|
2396
|
+
message: "All autotel packages are compatible"
|
|
2397
|
+
});
|
|
2398
|
+
} else {
|
|
2399
|
+
checks.push({
|
|
2400
|
+
id: "version-compat",
|
|
2401
|
+
title: "Version compatibility",
|
|
2402
|
+
level: "warning",
|
|
2403
|
+
status: "warn",
|
|
2404
|
+
message: "Autotel packages have mismatched major versions",
|
|
2405
|
+
details: versionCheck.packages.map((p) => `${p.name}@${p.version}`)
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
} else {
|
|
2409
|
+
checks.push({
|
|
2410
|
+
id: "version-compat",
|
|
2411
|
+
title: "Version compatibility",
|
|
2412
|
+
level: "warning",
|
|
2413
|
+
status: "skip",
|
|
2414
|
+
message: "Only one autotel package installed; skipping version check"
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
const esmCheck = checkEsmHook(project);
|
|
2418
|
+
checks.push({
|
|
2419
|
+
id: "esm-hook",
|
|
2420
|
+
title: "ESM hook setup",
|
|
2421
|
+
level: esmCheck.status === "warn" ? "warning" : "info",
|
|
2422
|
+
status: esmCheck.status === "ok" ? "ok" : esmCheck.status === "warn" ? "warn" : esmCheck.status === "error" ? "error" : "skip",
|
|
2423
|
+
message: esmCheck.message,
|
|
2424
|
+
details: esmCheck.details
|
|
2425
|
+
});
|
|
2426
|
+
return checks;
|
|
2427
|
+
}
|
|
2428
|
+
function calculateSummary(checks) {
|
|
2429
|
+
return {
|
|
2430
|
+
ok: checks.filter((c) => c.status === "ok").length,
|
|
2431
|
+
warnings: checks.filter((c) => c.status === "warn").length,
|
|
2432
|
+
errors: checks.filter((c) => c.status === "error").length,
|
|
2433
|
+
skipped: checks.filter((c) => c.status === "skip").length
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2436
|
+
function getExitCode(checks) {
|
|
2437
|
+
const hasErrors = checks.some((c) => c.status === "error");
|
|
2438
|
+
const hasWarnings = checks.some((c) => c.status === "warn");
|
|
2439
|
+
if (hasErrors) return 2;
|
|
2440
|
+
if (hasWarnings) return 1;
|
|
2441
|
+
return 0;
|
|
2442
|
+
}
|
|
2443
|
+
async function runDoctor(options) {
|
|
2444
|
+
if (options.verbose) {
|
|
2445
|
+
process.env["AUTOTEL_VERBOSE"] = "true";
|
|
2446
|
+
}
|
|
2447
|
+
if (options.quiet) {
|
|
2448
|
+
process.env["AUTOTEL_QUIET"] = "true";
|
|
2449
|
+
}
|
|
2450
|
+
if (options.listChecks) {
|
|
2451
|
+
if (options.json) {
|
|
2452
|
+
console.log(JSON.stringify(CHECK_DEFINITIONS, null, 2));
|
|
2453
|
+
} else {
|
|
2454
|
+
heading("Available checks:\n");
|
|
2455
|
+
for (const check of CHECK_DEFINITIONS) {
|
|
2456
|
+
console.log(` ${check.id}`);
|
|
2457
|
+
console.log(` ${check.description}
|
|
2458
|
+
`);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
const spinner = createSpinner();
|
|
2464
|
+
spinner.start("Scanning project...");
|
|
2465
|
+
const project = discoverProject(options.cwd);
|
|
2466
|
+
if (!project) {
|
|
2467
|
+
spinner.fail("No package.json found");
|
|
2468
|
+
error("Run this command in a directory with a package.json, or use --cwd");
|
|
2469
|
+
process.exit(2);
|
|
2470
|
+
}
|
|
2471
|
+
spinner.text("Running checks...");
|
|
2472
|
+
const checks = await runChecks(options, options.cwd);
|
|
2473
|
+
const summary = calculateSummary(checks);
|
|
2474
|
+
spinner.stop();
|
|
2475
|
+
if (options.json) {
|
|
2476
|
+
const result = {
|
|
2477
|
+
project: project.packageRoot,
|
|
2478
|
+
checks,
|
|
2479
|
+
summary
|
|
2480
|
+
};
|
|
2481
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2482
|
+
process.exit(getExitCode(checks));
|
|
2483
|
+
}
|
|
2484
|
+
heading(`autotel doctor
|
|
2485
|
+
`);
|
|
2486
|
+
dim(`Scanning ${project.packageRoot}...
|
|
2487
|
+
`);
|
|
2488
|
+
for (const check of checks) {
|
|
2489
|
+
const lines = formatCheck(check);
|
|
2490
|
+
for (const line of lines) {
|
|
2491
|
+
console.log(line);
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
console.log("");
|
|
2495
|
+
console.log(formatSummary(summary));
|
|
2496
|
+
if (options.fix) {
|
|
2497
|
+
const fixableChecks = checks.filter((c) => c.fix && c.status !== "ok");
|
|
2498
|
+
if (fixableChecks.length === 0) {
|
|
2499
|
+
info("\nNo fixes needed");
|
|
2500
|
+
} else {
|
|
2501
|
+
info(`
|
|
2502
|
+
Applying ${fixableChecks.length} fix(es)...`);
|
|
2503
|
+
for (const check of fixableChecks) {
|
|
2504
|
+
if (!check.fix) continue;
|
|
2505
|
+
if (check.id === "autotel-installed" || check.id === "peer-deps") {
|
|
2506
|
+
info(`Running: ${check.fix.cmd}`);
|
|
2507
|
+
try {
|
|
2508
|
+
const { execSync: execSync3 } = await import("child_process");
|
|
2509
|
+
execSync3(check.fix.cmd, { cwd: project.packageRoot, stdio: "inherit" });
|
|
2510
|
+
success(`Fixed: ${check.title}`);
|
|
2511
|
+
} catch {
|
|
2512
|
+
error(`Failed to fix: ${check.title}`);
|
|
2513
|
+
}
|
|
2514
|
+
} else {
|
|
2515
|
+
dim(`Skipping auto-fix for ${check.id} (not safe to auto-fix)`);
|
|
2516
|
+
dim(`Manual fix: ${check.fix.cmd}`);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
process.exit(getExitCode(checks));
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// src/commands/add.ts
|
|
2525
|
+
import { execSync as execSync2 } from "child_process";
|
|
2526
|
+
function formatPresetList(presets, json) {
|
|
2527
|
+
if (json) {
|
|
2528
|
+
const list = [...presets.entries()].map(([slug, preset]) => ({
|
|
2529
|
+
slug,
|
|
2530
|
+
name: preset.name,
|
|
2531
|
+
description: preset.description,
|
|
2532
|
+
requiredEnv: preset.env.required.map((e) => e.name)
|
|
2533
|
+
}));
|
|
2534
|
+
console.log(JSON.stringify(list, null, 2));
|
|
2535
|
+
return;
|
|
2536
|
+
}
|
|
2537
|
+
for (const [slug, preset] of presets) {
|
|
2538
|
+
console.log(` ${slug}`);
|
|
2539
|
+
console.log(` ${preset.description}`);
|
|
2540
|
+
if (preset.env.required.length > 0) {
|
|
2541
|
+
const envNames = preset.env.required.map((e) => e.name).join(", ");
|
|
2542
|
+
dim(` Required env: ${envNames}`);
|
|
2543
|
+
}
|
|
2544
|
+
console.log("");
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
function showPresetHelp(preset) {
|
|
2548
|
+
heading(`
|
|
2549
|
+
${preset.name}
|
|
2550
|
+
`);
|
|
2551
|
+
console.log(preset.description);
|
|
2552
|
+
console.log("");
|
|
2553
|
+
heading("Packages:");
|
|
2554
|
+
for (const pkg of preset.packages.required) {
|
|
2555
|
+
console.log(` ${pkg}`);
|
|
2556
|
+
}
|
|
2557
|
+
if (preset.packages.optional.length > 0) {
|
|
2558
|
+
console.log(" Optional:");
|
|
2559
|
+
for (const pkg of preset.packages.optional) {
|
|
2560
|
+
console.log(` ${pkg}`);
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
console.log("");
|
|
2564
|
+
if (preset.env.required.length > 0) {
|
|
2565
|
+
heading("Required Environment Variables:");
|
|
2566
|
+
for (const envVar of preset.env.required) {
|
|
2567
|
+
console.log(` ${envVar.name}`);
|
|
2568
|
+
console.log(` ${envVar.description}`);
|
|
2569
|
+
if (envVar.example) {
|
|
2570
|
+
dim(` Example: ${envVar.example}`);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
console.log("");
|
|
2574
|
+
}
|
|
2575
|
+
if (preset.env.optional.length > 0) {
|
|
2576
|
+
heading("Optional Environment Variables:");
|
|
2577
|
+
for (const envVar of preset.env.optional) {
|
|
2578
|
+
console.log(` ${envVar.name}`);
|
|
2579
|
+
console.log(` ${envVar.description}`);
|
|
2580
|
+
}
|
|
2581
|
+
console.log("");
|
|
2582
|
+
}
|
|
2583
|
+
heading("Next Steps:");
|
|
2584
|
+
for (const step of preset.nextSteps) {
|
|
2585
|
+
console.log(` - ${step}`);
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
function parsePresetType(type) {
|
|
2589
|
+
const validTypes = ["backend", "subscriber", "plugin", "platform"];
|
|
2590
|
+
if (validTypes.includes(type)) {
|
|
2591
|
+
return type;
|
|
2592
|
+
}
|
|
2593
|
+
return null;
|
|
2594
|
+
}
|
|
2595
|
+
function addPresetToFile(content, preset) {
|
|
2596
|
+
let result = content;
|
|
2597
|
+
for (const imp of preset.imports) {
|
|
2598
|
+
const importLine = imp.sideEffect ? `import '${imp.source}';` : imp.default ? `import ${imp.default} from '${imp.source}';` : `import { ${imp.specifiers?.join(", ")} } from '${imp.source}';`;
|
|
2599
|
+
const importPattern = imp.sideEffect ? `import '${imp.source}'` : imp.default ? `from '${imp.source}'` : `from '${imp.source}'`;
|
|
2600
|
+
if (!result.includes(importPattern)) {
|
|
2601
|
+
const sectionMarker = preset.type === "backend" || preset.type === "platform" ? "// --- AUTOTEL:BACKEND ---" : preset.type === "plugin" ? "// --- AUTOTEL:PLUGINS ---" : preset.type === "subscriber" ? "// --- AUTOTEL:SUBSCRIBERS ---" : null;
|
|
2602
|
+
if (sectionMarker && result.includes(sectionMarker)) {
|
|
2603
|
+
result = result.replace(sectionMarker, `${sectionMarker}
|
|
2604
|
+
${importLine}`);
|
|
2605
|
+
} else {
|
|
2606
|
+
const initIndex = result.indexOf("init({");
|
|
2607
|
+
if (initIndex > 0) {
|
|
2608
|
+
result = result.slice(0, initIndex) + `${importLine}
|
|
2609
|
+
|
|
2610
|
+
` + result.slice(initIndex);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
const configCode = preset.configBlock.code;
|
|
2616
|
+
const configSection = preset.configBlock.section;
|
|
2617
|
+
if (configSection === "BACKEND_CONFIG") {
|
|
2618
|
+
const backendMarker = "// --- AUTOTEL:BACKEND_CONFIG ---";
|
|
2619
|
+
if (result.includes(backendMarker)) {
|
|
2620
|
+
const markerIndex = result.indexOf(backendMarker);
|
|
2621
|
+
const afterMarker = result.slice(markerIndex + backendMarker.length);
|
|
2622
|
+
const nextMarkerMatch = afterMarker.match(/\n\s*(\/\/ --- AUTOTEL:|subscribers:|}\);)/);
|
|
2623
|
+
const endIndex = nextMarkerMatch ? markerIndex + backendMarker.length + (nextMarkerMatch.index ?? 0) : markerIndex + backendMarker.length;
|
|
2624
|
+
result = result.slice(0, markerIndex) + backendMarker + "\n " + configCode + "\n" + result.slice(endIndex);
|
|
2625
|
+
} else {
|
|
2626
|
+
const initMatch = result.match(/init\(\{/);
|
|
2627
|
+
if (initMatch && initMatch.index !== void 0) {
|
|
2628
|
+
const insertPoint = initMatch.index + "init({".length;
|
|
2629
|
+
result = result.slice(0, insertPoint) + "\n " + backendMarker + "\n " + configCode + result.slice(insertPoint);
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
} else if (configSection === "SUBSCRIBERS_CONFIG") {
|
|
2633
|
+
const subscribersMatch = result.match(/subscribers:\s*\[([^\]]*)\]/s);
|
|
2634
|
+
if (subscribersMatch) {
|
|
2635
|
+
const existingSubscribers = subscribersMatch[1]?.trim();
|
|
2636
|
+
const newSubscribers = existingSubscribers ? `${existingSubscribers}
|
|
2637
|
+
${configCode}` : `
|
|
2638
|
+
${configCode}
|
|
2639
|
+
`;
|
|
2640
|
+
result = result.replace(subscribersMatch[0], `subscribers: [${newSubscribers}]`);
|
|
2641
|
+
} else {
|
|
2642
|
+
const subscribersMarker = "// --- AUTOTEL:SUBSCRIBERS_CONFIG ---";
|
|
2643
|
+
const initStart = result.indexOf("init({");
|
|
2644
|
+
if (initStart !== -1) {
|
|
2645
|
+
const afterInit = result.slice(initStart);
|
|
2646
|
+
const closingMatch = afterInit.match(/}\);/);
|
|
2647
|
+
if (closingMatch && closingMatch.index !== void 0) {
|
|
2648
|
+
const insertPoint = initStart + closingMatch.index;
|
|
2649
|
+
result = result.slice(0, insertPoint) + "\n " + subscribersMarker + "\n subscribers: [\n " + configCode + "\n ],\n" + result.slice(insertPoint);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
} else if (configSection === "PLUGIN_INIT") {
|
|
2654
|
+
const pluginMarker = "// --- AUTOTEL:PLUGIN_INIT ---";
|
|
2655
|
+
if (result.includes(pluginMarker)) {
|
|
2656
|
+
result = result.replace(pluginMarker, `${pluginMarker}
|
|
2657
|
+
${configCode}`);
|
|
2658
|
+
} else {
|
|
2659
|
+
result = result.trimEnd() + `
|
|
2660
|
+
|
|
2661
|
+
${pluginMarker}
|
|
2662
|
+
${configCode}
|
|
2663
|
+
`;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
return result;
|
|
2667
|
+
}
|
|
2668
|
+
async function runAdd(type, name, options) {
|
|
2669
|
+
if (options.verbose) {
|
|
2670
|
+
process.env["AUTOTEL_VERBOSE"] = "true";
|
|
2671
|
+
}
|
|
2672
|
+
if (options.quiet) {
|
|
2673
|
+
process.env["AUTOTEL_QUIET"] = "true";
|
|
2674
|
+
}
|
|
2675
|
+
if (options.list && !type) {
|
|
2676
|
+
heading("\nAvailable presets:\n");
|
|
2677
|
+
heading("Backends:");
|
|
2678
|
+
formatPresetList(getPresetsByType("backend"), options.json);
|
|
2679
|
+
heading("Subscribers:");
|
|
2680
|
+
formatPresetList(getPresetsByType("subscriber"), options.json);
|
|
2681
|
+
heading("Plugins:");
|
|
2682
|
+
formatPresetList(getPresetsByType("plugin"), options.json);
|
|
2683
|
+
heading("Platforms:");
|
|
2684
|
+
formatPresetList(getPresetsByType("platform"), options.json);
|
|
2685
|
+
return;
|
|
2686
|
+
}
|
|
2687
|
+
if (!type) {
|
|
2688
|
+
error("Usage: autotel add <type> <name>");
|
|
2689
|
+
info("Types: backend, subscriber, plugin, platform");
|
|
2690
|
+
info("Run `autotel add --list` to see all presets");
|
|
2691
|
+
process.exit(1);
|
|
2692
|
+
}
|
|
2693
|
+
const presetType = parsePresetType(type);
|
|
2694
|
+
if (!presetType) {
|
|
2695
|
+
error(`Invalid type: ${type}`);
|
|
2696
|
+
info("Valid types: backend, subscriber, plugin, platform");
|
|
2697
|
+
process.exit(1);
|
|
2698
|
+
}
|
|
2699
|
+
if (options.list) {
|
|
2700
|
+
heading(`
|
|
2701
|
+
${presetType} presets:
|
|
2702
|
+
`);
|
|
2703
|
+
formatPresetList(getPresetsByType(presetType), options.json);
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
if (!name) {
|
|
2707
|
+
error(`Usage: autotel add ${type} <name>`);
|
|
2708
|
+
info(`Available ${type}s: ${listPresetSlugs(presetType).join(", ")}`);
|
|
2709
|
+
process.exit(1);
|
|
2710
|
+
}
|
|
2711
|
+
const preset = getPreset(presetType, name);
|
|
2712
|
+
if (!preset) {
|
|
2713
|
+
error(`Unknown ${type}: ${name}`);
|
|
2714
|
+
info(`Available ${type}s: ${listPresetSlugs(presetType).join(", ")}`);
|
|
2715
|
+
process.exit(1);
|
|
2716
|
+
}
|
|
2717
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
2718
|
+
showPresetHelp(preset);
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
const spinner = createSpinner();
|
|
2722
|
+
spinner.start("Discovering project...");
|
|
2723
|
+
const project = discoverProject(options.cwd);
|
|
2724
|
+
if (!project) {
|
|
2725
|
+
spinner.fail("No package.json found");
|
|
2726
|
+
error("Run this command in a directory with a package.json, or use --cwd");
|
|
2727
|
+
process.exit(1);
|
|
2728
|
+
}
|
|
2729
|
+
spinner.succeed(`Found ${project.packageJson.name ?? "project"}`);
|
|
2730
|
+
const deps = { ...project.packageJson.dependencies, ...project.packageJson.devDependencies };
|
|
2731
|
+
const allInstalled = preset.packages.required.every((pkg) => deps[pkg]);
|
|
2732
|
+
const config = detectConfig(project.packageRoot);
|
|
2733
|
+
let alreadyConfigured = false;
|
|
2734
|
+
if (config.instrumentationFile) {
|
|
2735
|
+
alreadyConfigured = isFeatureConfigured(config.instrumentationFile, presetType);
|
|
2736
|
+
}
|
|
2737
|
+
if (allInstalled && alreadyConfigured) {
|
|
2738
|
+
success(`[OK] ${preset.name} is already installed and configured`);
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
if (allInstalled) {
|
|
2742
|
+
info(`Packages already installed`);
|
|
2743
|
+
}
|
|
2744
|
+
if (alreadyConfigured) {
|
|
2745
|
+
info(`Already configured in instrumentation file`);
|
|
2746
|
+
}
|
|
2747
|
+
if (options.dryRun) {
|
|
2748
|
+
heading("\nDry run - no changes will be made\n");
|
|
2749
|
+
if (!allInstalled) {
|
|
2750
|
+
const cmd = getInstallCommand(project.packageManager, preset.packages.required);
|
|
2751
|
+
info(`Would run: ${cmd}`);
|
|
2752
|
+
}
|
|
2753
|
+
if (!alreadyConfigured) {
|
|
2754
|
+
info(`Would update instrumentation file with ${preset.name} config`);
|
|
2755
|
+
}
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
2758
|
+
if (!allInstalled && !options.noInstall) {
|
|
2759
|
+
const missingPkgs = preset.packages.required.filter((pkg) => !deps[pkg]);
|
|
2760
|
+
const cmd = getInstallCommand(project.packageManager, missingPkgs);
|
|
2761
|
+
if (options.printInstallCmd) {
|
|
2762
|
+
info(`Install command: ${cmd}`);
|
|
2763
|
+
} else {
|
|
2764
|
+
spinner.start("Installing packages...");
|
|
2765
|
+
try {
|
|
2766
|
+
execSync2(cmd, { cwd: project.packageRoot, stdio: "pipe" });
|
|
2767
|
+
spinner.succeed("Packages installed");
|
|
2768
|
+
} catch {
|
|
2769
|
+
spinner.fail("Failed to install packages");
|
|
2770
|
+
error(`Run manually: ${cmd}`);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
if (!alreadyConfigured) {
|
|
2775
|
+
if (!config.found || config.type === "none") {
|
|
2776
|
+
warn("No instrumentation file found");
|
|
2777
|
+
info("Run `autotel init` first to create one");
|
|
2778
|
+
process.exit(1);
|
|
2779
|
+
}
|
|
2780
|
+
if (config.type === "user-created" && !options.force) {
|
|
2781
|
+
warn("Instrumentation file exists but is not CLI-owned");
|
|
2782
|
+
info("Use --force to modify, or add CLI header to the file");
|
|
2783
|
+
process.exit(1);
|
|
2784
|
+
}
|
|
2785
|
+
const instrPath = config.path;
|
|
2786
|
+
const content = readFileSafe(instrPath);
|
|
2787
|
+
if (content) {
|
|
2788
|
+
spinner.start("Updating instrumentation file...");
|
|
2789
|
+
const updatedContent = addPresetToFile(content, preset);
|
|
2790
|
+
atomicWrite(instrPath, updatedContent, {
|
|
2791
|
+
root: project.packageRoot,
|
|
2792
|
+
backup: options.force
|
|
2793
|
+
});
|
|
2794
|
+
spinner.succeed("Instrumentation file updated");
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
console.log(formatFooter({
|
|
2798
|
+
detected: `${project.packageManager}`,
|
|
2799
|
+
next: preset.nextSteps[0]
|
|
2800
|
+
}));
|
|
2801
|
+
if (preset.nextSteps.length > 1) {
|
|
2802
|
+
console.log("\nAdditional steps:");
|
|
2803
|
+
for (const step of preset.nextSteps.slice(1)) {
|
|
2804
|
+
console.log(` - ${step}`);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
// src/cli.ts
|
|
2810
|
+
function createProgram() {
|
|
2811
|
+
const program = new Command();
|
|
2812
|
+
program.name("autotel").description("CLI for autotel - setup wizard, diagnostics, and incremental features").version("0.1.0");
|
|
2813
|
+
const addGlobalOptions = (cmd) => {
|
|
2814
|
+
return cmd.option("--cwd <path>", "Target directory", process.cwd()).option("--verbose", "Show detailed output").option("--quiet", "Only show warnings and errors");
|
|
2815
|
+
};
|
|
2816
|
+
const initCmd = new Command("init").description("Initialize autotel in your project").option("--dry-run", "Skip installation and print what would be done").option("--no-install", "Generate files only, skip package installation").option("--print-install-cmd", "Output the install command without running it").option("-y, --yes", "Accept defaults, non-interactive").option("--preset <name>", "Use a quick preset (e.g., node-datadog-pino)").option("--force", "Overwrite existing config (creates backup first)").option("--workspace-root", "Install at workspace root instead of package root").action(async (opts) => {
|
|
2817
|
+
const options = {
|
|
2818
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
2819
|
+
dryRun: opts.dryRun ?? false,
|
|
2820
|
+
noInstall: opts.noInstall ?? false,
|
|
2821
|
+
printInstallCmd: opts.printInstallCmd ?? false,
|
|
2822
|
+
verbose: opts.verbose ?? false,
|
|
2823
|
+
quiet: opts.quiet ?? false,
|
|
2824
|
+
workspaceRoot: opts.workspaceRoot ?? false,
|
|
2825
|
+
yes: opts.yes ?? false,
|
|
2826
|
+
preset: opts.preset,
|
|
2827
|
+
force: opts.force ?? false
|
|
2828
|
+
};
|
|
2829
|
+
if (options.dryRun) {
|
|
2830
|
+
options.noInstall = true;
|
|
2831
|
+
options.printInstallCmd = true;
|
|
2832
|
+
}
|
|
2833
|
+
await runInit(options);
|
|
2834
|
+
});
|
|
2835
|
+
addGlobalOptions(initCmd);
|
|
2836
|
+
program.addCommand(initCmd);
|
|
2837
|
+
const doctorCmd = new Command("doctor").description("Run diagnostics on your autotel setup").option("--json", "Output machine-readable JSON").option("--fix", "Auto-fix resolvable issues").option("--list-checks", "List all available checks").option("--env-file <path>", "Specify env file to check").action(async (opts) => {
|
|
2838
|
+
const options = {
|
|
2839
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
2840
|
+
dryRun: false,
|
|
2841
|
+
noInstall: false,
|
|
2842
|
+
printInstallCmd: false,
|
|
2843
|
+
verbose: opts.verbose ?? false,
|
|
2844
|
+
quiet: opts.quiet ?? false,
|
|
2845
|
+
workspaceRoot: false,
|
|
2846
|
+
json: opts.json ?? false,
|
|
2847
|
+
fix: opts.fix ?? false,
|
|
2848
|
+
listChecks: opts.listChecks ?? false,
|
|
2849
|
+
envFile: opts.envFile
|
|
2850
|
+
};
|
|
2851
|
+
await runDoctor(options);
|
|
2852
|
+
});
|
|
2853
|
+
addGlobalOptions(doctorCmd);
|
|
2854
|
+
program.addCommand(doctorCmd);
|
|
2855
|
+
const addCmd = new Command("add").description("Add a backend, subscriber, plugin, or platform").argument("[type]", "Preset type (backend, subscriber, plugin, platform)").argument("[name]", "Preset name (e.g., datadog, posthog, mongoose)").option("--list", "List available presets for the given type").option("--dry-run", "Skip installation and print what would be done").option("--no-install", "Generate files only, skip package installation").option("--print-install-cmd", "Output the install command without running it").option("-y, --yes", "Accept defaults, non-interactive").option("--force", "Overwrite non-CLI-owned config (creates backup first)").option("--json", "Output machine-readable JSON (for --list)").option("--workspace-root", "Install at workspace root instead of package root").action(async (type, name, opts) => {
|
|
2856
|
+
const options = {
|
|
2857
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
2858
|
+
dryRun: opts.dryRun ?? false,
|
|
2859
|
+
noInstall: opts.noInstall ?? false,
|
|
2860
|
+
printInstallCmd: opts.printInstallCmd ?? false,
|
|
2861
|
+
verbose: opts.verbose ?? false,
|
|
2862
|
+
quiet: opts.quiet ?? false,
|
|
2863
|
+
workspaceRoot: opts.workspaceRoot ?? false,
|
|
2864
|
+
list: opts.list ?? false,
|
|
2865
|
+
yes: opts.yes ?? false,
|
|
2866
|
+
force: opts.force ?? false,
|
|
2867
|
+
json: opts.json ?? false
|
|
2868
|
+
};
|
|
2869
|
+
if (options.dryRun) {
|
|
2870
|
+
options.noInstall = true;
|
|
2871
|
+
options.printInstallCmd = true;
|
|
2872
|
+
}
|
|
2873
|
+
await runAdd(type, name, options);
|
|
2874
|
+
});
|
|
2875
|
+
addGlobalOptions(addCmd);
|
|
2876
|
+
program.addCommand(addCmd);
|
|
2877
|
+
return program;
|
|
2878
|
+
}
|
|
2879
|
+
async function run() {
|
|
2880
|
+
const program = createProgram();
|
|
2881
|
+
await program.parseAsync(process.argv);
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
// src/index.ts
|
|
2885
|
+
run().catch((error2) => {
|
|
2886
|
+
console.error("Error:", error2 instanceof Error ? error2.message : error2);
|
|
2887
|
+
process.exit(1);
|
|
2888
|
+
});
|
|
2889
|
+
//# sourceMappingURL=index.js.map
|