autotel-cli 0.8.6 → 0.8.8
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 +72 -6
- package/dist/index.js +1523 -381
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
|
-
import * as
|
|
7
|
+
import * as fs4 from "fs";
|
|
8
|
+
import * as path9 from "path";
|
|
8
9
|
import { execSync } from "child_process";
|
|
9
10
|
|
|
10
11
|
// src/lib/project.ts
|
|
@@ -330,11 +331,25 @@ function createCodeFile() {
|
|
|
330
331
|
backendImports: [],
|
|
331
332
|
pluginImports: [],
|
|
332
333
|
subscriberImports: [],
|
|
334
|
+
loggerImports: [],
|
|
335
|
+
loggerSetup: null,
|
|
336
|
+
loggerExpr: null,
|
|
337
|
+
autoInstrumentations: [],
|
|
333
338
|
backendConfig: null,
|
|
334
339
|
subscribersConfig: [],
|
|
335
340
|
pluginInit: []
|
|
336
341
|
};
|
|
337
342
|
}
|
|
343
|
+
function setPinoLogger(file) {
|
|
344
|
+
file.loggerImports.push({ source: "pino", default: "pino" });
|
|
345
|
+
file.loggerSetup = `const logger = pino({ name: 'app', level: process.env.LOG_LEVEL ?? 'info' });`;
|
|
346
|
+
file.loggerExpr = "logger";
|
|
347
|
+
}
|
|
348
|
+
function addAutoInstrumentationLogger(file, name) {
|
|
349
|
+
if (!file.autoInstrumentations.includes(name)) {
|
|
350
|
+
file.autoInstrumentations.push(name);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
338
353
|
function addImport(file, imp, section) {
|
|
339
354
|
switch (section) {
|
|
340
355
|
case "backend":
|
|
@@ -362,7 +377,7 @@ function addPluginInit(file, init) {
|
|
|
362
377
|
function sortImports(imports) {
|
|
363
378
|
const sideEffect = [];
|
|
364
379
|
const external = [];
|
|
365
|
-
const
|
|
380
|
+
const relative6 = [];
|
|
366
381
|
for (const imp of imports) {
|
|
367
382
|
if (imp.sideEffect) {
|
|
368
383
|
if (imp.source === "autotel/register") {
|
|
@@ -371,15 +386,15 @@ function sortImports(imports) {
|
|
|
371
386
|
sideEffect.push(imp);
|
|
372
387
|
}
|
|
373
388
|
} else if (imp.source.startsWith(".") || imp.source.startsWith("/")) {
|
|
374
|
-
|
|
389
|
+
relative6.push(imp);
|
|
375
390
|
} else {
|
|
376
391
|
external.push(imp);
|
|
377
392
|
}
|
|
378
393
|
}
|
|
379
394
|
const sortBySource = (a, b) => a.source.localeCompare(b.source);
|
|
380
395
|
external.sort(sortBySource);
|
|
381
|
-
|
|
382
|
-
return [...sideEffect, ...external, ...
|
|
396
|
+
relative6.sort(sortBySource);
|
|
397
|
+
return [...sideEffect, ...external, ...relative6];
|
|
383
398
|
}
|
|
384
399
|
function renderImport(imp) {
|
|
385
400
|
if (imp.sideEffect) {
|
|
@@ -427,7 +442,23 @@ function renderCodeFile(file) {
|
|
|
427
442
|
lines.push(renderImports(file.subscriberImports));
|
|
428
443
|
lines.push("");
|
|
429
444
|
}
|
|
445
|
+
if (file.loggerImports.length > 0) {
|
|
446
|
+
lines.push(SECTION_MARKER("LOGGER"));
|
|
447
|
+
lines.push(renderImports(file.loggerImports));
|
|
448
|
+
lines.push("");
|
|
449
|
+
}
|
|
450
|
+
if (file.loggerSetup !== null) {
|
|
451
|
+
lines.push(file.loggerSetup);
|
|
452
|
+
lines.push("");
|
|
453
|
+
}
|
|
430
454
|
lines.push("init({");
|
|
455
|
+
if (file.loggerExpr !== null) {
|
|
456
|
+
lines.push(` logger: ${file.loggerExpr},`);
|
|
457
|
+
}
|
|
458
|
+
if (file.autoInstrumentations.length > 0) {
|
|
459
|
+
const list = file.autoInstrumentations.map((n) => `'${n}'`).join(", ");
|
|
460
|
+
lines.push(` autoInstrumentations: [${list}],`);
|
|
461
|
+
}
|
|
431
462
|
if (file.backendConfig) {
|
|
432
463
|
lines.push(" " + SECTION_MARKER("BACKEND_CONFIG"));
|
|
433
464
|
lines.push(` ${file.backendConfig}`);
|
|
@@ -638,60 +669,192 @@ async function checkEnvVarsPresent(packageRoot, varNames, specificFile) {
|
|
|
638
669
|
return results;
|
|
639
670
|
}
|
|
640
671
|
|
|
641
|
-
// src/lib/
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
672
|
+
// src/lib/dep-detector.ts
|
|
673
|
+
import * as fs2 from "fs";
|
|
674
|
+
import * as path6 from "path";
|
|
675
|
+
var DEP_TO_PRESET = {
|
|
676
|
+
// First-party autotel wrappers
|
|
677
|
+
"@sentry/node": "sentry",
|
|
678
|
+
"@sentry/bun": "sentry",
|
|
679
|
+
hono: "hono",
|
|
680
|
+
"@modelcontextprotocol/sdk": "mcp",
|
|
681
|
+
"@tanstack/start": "tanstack",
|
|
682
|
+
"@tanstack/start-server": "tanstack",
|
|
683
|
+
"@tanstack/start-client": "tanstack",
|
|
684
|
+
// Subscribers
|
|
685
|
+
"posthog-node": "posthog",
|
|
686
|
+
"@posthog/node": "posthog",
|
|
687
|
+
mixpanel: "mixpanel",
|
|
688
|
+
"@amplitude/analytics-node": "amplitude",
|
|
689
|
+
"@segment/analytics-node": "segment",
|
|
690
|
+
"@slack/web-api": "slack",
|
|
691
|
+
"@slack/webhook": "slack",
|
|
692
|
+
// Plugins (existing presets)
|
|
693
|
+
mongoose: "mongoose",
|
|
694
|
+
"drizzle-orm": "drizzle",
|
|
695
|
+
// Auto-instrumented (no first-party preset needed)
|
|
696
|
+
express: "auto-instr",
|
|
697
|
+
fastify: "auto-instr",
|
|
698
|
+
"@nestjs/core": "auto-instr",
|
|
699
|
+
next: "auto-instr",
|
|
700
|
+
pg: "auto-instr",
|
|
701
|
+
mysql: "auto-instr",
|
|
702
|
+
mysql2: "auto-instr",
|
|
703
|
+
redis: "auto-instr",
|
|
704
|
+
ioredis: "auto-instr",
|
|
705
|
+
"@aws-sdk/client-s3": "auto-instr",
|
|
706
|
+
graphql: "auto-instr"
|
|
707
|
+
};
|
|
708
|
+
var LOGGER_DEPS = {
|
|
709
|
+
pino: "pino",
|
|
710
|
+
winston: "winston",
|
|
711
|
+
bunyan: "bunyan"
|
|
712
|
+
};
|
|
713
|
+
var ENV_KEY_TO_BACKEND = [
|
|
714
|
+
{ key: "DD_API_KEY", slug: "datadog" },
|
|
715
|
+
{ key: "DATADOG_API_KEY", slug: "datadog" },
|
|
716
|
+
{ key: "HONEYCOMB_API_KEY", slug: "honeycomb" },
|
|
717
|
+
{ key: "HONEYCOMB_WRITE_KEY", slug: "honeycomb" },
|
|
718
|
+
{ key: "GOOGLE_CLOUD_PROJECT", slug: "google-cloud" },
|
|
719
|
+
// OTEL endpoint is generic enough that we pick otlp-http
|
|
720
|
+
{ key: "OTEL_EXPORTER_OTLP_ENDPOINT", slug: "otlp-http" }
|
|
721
|
+
];
|
|
722
|
+
function mergeDeps(target, root) {
|
|
723
|
+
const out = /* @__PURE__ */ new Map();
|
|
724
|
+
if (root) {
|
|
725
|
+
for (const [name, version] of Object.entries({
|
|
726
|
+
...root.dependencies,
|
|
727
|
+
...root.devDependencies
|
|
728
|
+
})) {
|
|
729
|
+
out.set(name, { version, resolution: "workspace-root" });
|
|
653
730
|
}
|
|
654
731
|
}
|
|
732
|
+
for (const [name, version] of Object.entries({
|
|
733
|
+
...target.dependencies,
|
|
734
|
+
...target.devDependencies
|
|
735
|
+
})) {
|
|
736
|
+
out.set(name, { version, resolution: "target" });
|
|
737
|
+
}
|
|
738
|
+
return out;
|
|
655
739
|
}
|
|
656
|
-
function
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
740
|
+
function detectLoggers(deps) {
|
|
741
|
+
const present = [];
|
|
742
|
+
for (const [name, kind] of Object.entries(LOGGER_DEPS)) {
|
|
743
|
+
if (deps.has(name)) present.push(kind);
|
|
744
|
+
}
|
|
745
|
+
if (present.length === 0) return { primary: null, autoInstrument: [] };
|
|
746
|
+
if (present.includes("pino")) {
|
|
747
|
+
return {
|
|
748
|
+
primary: "pino",
|
|
749
|
+
autoInstrument: present.filter((l) => l !== "pino")
|
|
750
|
+
};
|
|
664
751
|
}
|
|
752
|
+
return {
|
|
753
|
+
primary: present[0] ?? null,
|
|
754
|
+
autoInstrument: present.slice(1)
|
|
755
|
+
};
|
|
665
756
|
}
|
|
666
|
-
function
|
|
667
|
-
|
|
757
|
+
function parseEnvKeys(content) {
|
|
758
|
+
const out = /* @__PURE__ */ new Set();
|
|
759
|
+
for (const rawLine of content.split("\n")) {
|
|
760
|
+
const line = rawLine.trim();
|
|
761
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
762
|
+
const eq = line.indexOf("=");
|
|
763
|
+
if (eq === -1) continue;
|
|
764
|
+
const key = line.slice(0, eq).trim();
|
|
765
|
+
if (key.length > 0) out.add(key);
|
|
766
|
+
}
|
|
767
|
+
return out;
|
|
668
768
|
}
|
|
669
|
-
function
|
|
670
|
-
|
|
769
|
+
function collectEnvKeys(opts) {
|
|
770
|
+
const keys = /* @__PURE__ */ new Set();
|
|
771
|
+
const sources = [];
|
|
772
|
+
const tryRead = (name, requiresConsent) => {
|
|
773
|
+
if (requiresConsent && !opts.envConsent) return;
|
|
774
|
+
const p = path6.join(opts.packageRoot, name);
|
|
775
|
+
const content = readFileSafe(p);
|
|
776
|
+
if (content === null) return;
|
|
777
|
+
sources.push(name);
|
|
778
|
+
for (const k of parseEnvKeys(content)) keys.add(k);
|
|
779
|
+
};
|
|
780
|
+
tryRead(".env.example", false);
|
|
781
|
+
tryRead(".env.sample", false);
|
|
782
|
+
tryRead(".env", true);
|
|
783
|
+
tryRead(".env.local", true);
|
|
784
|
+
return { keys, sources };
|
|
671
785
|
}
|
|
672
|
-
function
|
|
673
|
-
|
|
786
|
+
function detectBackend(opts) {
|
|
787
|
+
for (const { key, slug } of ENV_KEY_TO_BACKEND) {
|
|
788
|
+
if (opts.envKeys.has(key)) {
|
|
789
|
+
return { slug, source: "env", detail: key };
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (opts.deps.has("dd-trace")) {
|
|
793
|
+
return { slug: "datadog", source: "deps", detail: "dd-trace" };
|
|
794
|
+
}
|
|
795
|
+
return { slug: "local", source: "default" };
|
|
674
796
|
}
|
|
675
|
-
function
|
|
676
|
-
if (
|
|
677
|
-
return;
|
|
797
|
+
function detectPlatform(packageRoot) {
|
|
798
|
+
if (fileExists(path6.join(packageRoot, "wrangler.toml")) || fileExists(path6.join(packageRoot, "wrangler.jsonc")) || fileExists(path6.join(packageRoot, "wrangler.json"))) {
|
|
799
|
+
return "cloudflare";
|
|
678
800
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
function detectInProject(opts) {
|
|
804
|
+
const { project } = opts;
|
|
805
|
+
let rootPkg = null;
|
|
806
|
+
if (project.workspace.workspaceRoot !== null && project.workspace.workspaceRoot !== project.packageRoot) {
|
|
807
|
+
rootPkg = readJsonSafe(
|
|
808
|
+
path6.join(project.workspace.workspaceRoot, "package.json")
|
|
809
|
+
);
|
|
682
810
|
}
|
|
683
|
-
|
|
684
|
-
|
|
811
|
+
const deps = mergeDeps(project.packageJson, rootPkg);
|
|
812
|
+
const detectedPackages = [];
|
|
813
|
+
const presetSet = /* @__PURE__ */ new Set();
|
|
814
|
+
const autoInstrDeps = [];
|
|
815
|
+
for (const [name, info2] of deps.entries()) {
|
|
816
|
+
const mapping = DEP_TO_PRESET[name];
|
|
817
|
+
if (mapping === void 0 && LOGGER_DEPS[name] === void 0) continue;
|
|
818
|
+
detectedPackages.push({
|
|
819
|
+
name,
|
|
820
|
+
version: info2.version,
|
|
821
|
+
resolution: info2.resolution
|
|
822
|
+
});
|
|
823
|
+
if (mapping === "auto-instr") {
|
|
824
|
+
autoInstrDeps.push(name);
|
|
825
|
+
} else if (mapping !== void 0) {
|
|
826
|
+
presetSet.add(mapping);
|
|
827
|
+
}
|
|
685
828
|
}
|
|
829
|
+
const loggers = detectLoggers(deps);
|
|
830
|
+
const platform = detectPlatform(project.packageRoot);
|
|
831
|
+
if (platform !== null) presetSet.add(platform);
|
|
832
|
+
const { keys: envKeys } = collectEnvKeys({
|
|
833
|
+
packageRoot: project.packageRoot,
|
|
834
|
+
envConsent: opts.envConsent
|
|
835
|
+
});
|
|
836
|
+
const backend = detectBackend({
|
|
837
|
+
envKeys,
|
|
838
|
+
packageRoot: project.packageRoot,
|
|
839
|
+
deps
|
|
840
|
+
});
|
|
841
|
+
return {
|
|
842
|
+
packages: detectedPackages,
|
|
843
|
+
presets: [...presetSet],
|
|
844
|
+
primaryLogger: loggers.primary,
|
|
845
|
+
autoInstrumentLoggers: loggers.autoInstrument,
|
|
846
|
+
autoInstrumentedDeps: autoInstrDeps,
|
|
847
|
+
backend,
|
|
848
|
+
platform
|
|
849
|
+
};
|
|
686
850
|
}
|
|
687
|
-
function
|
|
688
|
-
|
|
689
|
-
addCorePackages(plan);
|
|
690
|
-
addPresetsToPlan(plan, options.presets);
|
|
691
|
-
addAutoInstrumentationPackages(plan, options.autoInstrumentations);
|
|
692
|
-
return plan;
|
|
851
|
+
function envFilesRequireConsent(packageRoot) {
|
|
852
|
+
return fileExists(path6.join(packageRoot, ".env")) || fileExists(path6.join(packageRoot, ".env.local"));
|
|
693
853
|
}
|
|
694
854
|
|
|
855
|
+
// src/lib/plan-builder.ts
|
|
856
|
+
import * as path7 from "path";
|
|
857
|
+
|
|
695
858
|
// src/presets/backends/datadog.ts
|
|
696
859
|
var datadogDirect = {
|
|
697
860
|
name: "Datadog (Direct)",
|
|
@@ -1475,6 +1638,183 @@ var drizzle = {
|
|
|
1475
1638
|
]
|
|
1476
1639
|
};
|
|
1477
1640
|
|
|
1641
|
+
// src/presets/plugins/sentry.ts
|
|
1642
|
+
var sentry = {
|
|
1643
|
+
name: "Sentry",
|
|
1644
|
+
slug: "sentry",
|
|
1645
|
+
type: "plugin",
|
|
1646
|
+
description: "Send traces to Sentry via OTLP and link captured errors to active traces",
|
|
1647
|
+
packages: {
|
|
1648
|
+
required: ["autotel-sentry", "@sentry/node"],
|
|
1649
|
+
optional: [],
|
|
1650
|
+
devOnly: []
|
|
1651
|
+
},
|
|
1652
|
+
env: {
|
|
1653
|
+
required: [
|
|
1654
|
+
{
|
|
1655
|
+
name: "SENTRY_DSN",
|
|
1656
|
+
description: "Sentry Data Source Name (project ingest URL)",
|
|
1657
|
+
example: "https://<key>@o<org>.ingest.sentry.io/<project>",
|
|
1658
|
+
sensitive: true
|
|
1659
|
+
}
|
|
1660
|
+
],
|
|
1661
|
+
optional: []
|
|
1662
|
+
},
|
|
1663
|
+
imports: [
|
|
1664
|
+
{
|
|
1665
|
+
source: "@sentry/node",
|
|
1666
|
+
specifiers: ["*"],
|
|
1667
|
+
default: "* as Sentry"
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
source: "autotel-sentry",
|
|
1671
|
+
specifiers: ["sentryOtlpConfig", "linkSentryErrors"]
|
|
1672
|
+
}
|
|
1673
|
+
],
|
|
1674
|
+
configBlock: {
|
|
1675
|
+
type: "plugin",
|
|
1676
|
+
code: `// Sentry OTLP setup
|
|
1677
|
+
const sentryConfig = sentryOtlpConfig(process.env.SENTRY_DSN!);
|
|
1678
|
+
Sentry.init({ dsn: sentryConfig.dsn, skipOpenTelemetrySetup: true });
|
|
1679
|
+
linkSentryErrors(Sentry);`,
|
|
1680
|
+
section: "PLUGIN_INIT"
|
|
1681
|
+
},
|
|
1682
|
+
nextSteps: [
|
|
1683
|
+
"Set SENTRY_DSN in .env (from Sentry \u2192 Settings \u2192 Projects \u2192 Client Keys)",
|
|
1684
|
+
"Captured Sentry errors will now be linked to active OTel traces",
|
|
1685
|
+
"Pass sentryConfig.endpoint and sentryConfig.headers to init() if you want Sentry as the trace backend"
|
|
1686
|
+
]
|
|
1687
|
+
};
|
|
1688
|
+
|
|
1689
|
+
// src/presets/plugins/hono.ts
|
|
1690
|
+
var hono = {
|
|
1691
|
+
name: "Hono",
|
|
1692
|
+
slug: "hono",
|
|
1693
|
+
type: "plugin",
|
|
1694
|
+
description: "HTTP tracing + metrics middleware for Hono apps",
|
|
1695
|
+
packages: {
|
|
1696
|
+
required: ["autotel-hono"],
|
|
1697
|
+
optional: ["autotel-adapters"],
|
|
1698
|
+
devOnly: []
|
|
1699
|
+
},
|
|
1700
|
+
env: {
|
|
1701
|
+
required: [],
|
|
1702
|
+
optional: []
|
|
1703
|
+
},
|
|
1704
|
+
imports: [
|
|
1705
|
+
{
|
|
1706
|
+
source: "autotel-hono",
|
|
1707
|
+
specifiers: ["otel"]
|
|
1708
|
+
}
|
|
1709
|
+
],
|
|
1710
|
+
configBlock: {
|
|
1711
|
+
type: "plugin",
|
|
1712
|
+
code: `// Register the otel() middleware on your Hono app:
|
|
1713
|
+
// app.use('*', otel({
|
|
1714
|
+
// serviceName: 'my-service',
|
|
1715
|
+
// captureRequestHeaders: ['user-agent'],
|
|
1716
|
+
// captureResponseHeaders: ['content-type'],
|
|
1717
|
+
// }));`,
|
|
1718
|
+
section: "PLUGIN_INIT"
|
|
1719
|
+
},
|
|
1720
|
+
nextSteps: [
|
|
1721
|
+
"Add otel() middleware to your Hono app: app.use('*', otel({ serviceName: '<name>' }))",
|
|
1722
|
+
"Optionally install autotel-adapters and use useLogger() to thread the request logger",
|
|
1723
|
+
"See packages/autotel-hono README for header/metric configuration"
|
|
1724
|
+
]
|
|
1725
|
+
};
|
|
1726
|
+
|
|
1727
|
+
// src/presets/plugins/mcp.ts
|
|
1728
|
+
var mcp = {
|
|
1729
|
+
name: "MCP (Model Context Protocol)",
|
|
1730
|
+
slug: "mcp",
|
|
1731
|
+
type: "plugin",
|
|
1732
|
+
description: "Distributed tracing for MCP servers and clients (W3C trace context via _meta)",
|
|
1733
|
+
packages: {
|
|
1734
|
+
required: ["autotel-mcp-instrumentation"],
|
|
1735
|
+
optional: [],
|
|
1736
|
+
devOnly: []
|
|
1737
|
+
},
|
|
1738
|
+
env: {
|
|
1739
|
+
required: [],
|
|
1740
|
+
optional: []
|
|
1741
|
+
},
|
|
1742
|
+
imports: [
|
|
1743
|
+
{
|
|
1744
|
+
source: "autotel-mcp-instrumentation",
|
|
1745
|
+
specifiers: ["instrumentMcpServer"]
|
|
1746
|
+
}
|
|
1747
|
+
],
|
|
1748
|
+
configBlock: {
|
|
1749
|
+
type: "plugin",
|
|
1750
|
+
code: `// Wrap your MCP server after constructing it:
|
|
1751
|
+
// const instrumented = instrumentMcpServer(server, {
|
|
1752
|
+
// networkTransport: 'pipe',
|
|
1753
|
+
// captureToolArgs: true,
|
|
1754
|
+
// captureToolResults: false,
|
|
1755
|
+
// captureErrors: true,
|
|
1756
|
+
// });
|
|
1757
|
+
//
|
|
1758
|
+
// For clients, import { instrumentMcpClient } and wrap the client.`,
|
|
1759
|
+
section: "PLUGIN_INIT"
|
|
1760
|
+
},
|
|
1761
|
+
nextSteps: [
|
|
1762
|
+
"Wrap your MCP server with instrumentMcpServer(server, { ... })",
|
|
1763
|
+
"For clients, use instrumentMcpClient from autotel-mcp-instrumentation",
|
|
1764
|
+
"Traces follow OTel MCP semantic conventions and propagate via the _meta field"
|
|
1765
|
+
]
|
|
1766
|
+
};
|
|
1767
|
+
|
|
1768
|
+
// src/presets/plugins/tanstack.ts
|
|
1769
|
+
var tanstack = {
|
|
1770
|
+
name: "TanStack Start",
|
|
1771
|
+
slug: "tanstack",
|
|
1772
|
+
type: "plugin",
|
|
1773
|
+
description: "Tracing middleware for TanStack Start (SSR requests + server functions)",
|
|
1774
|
+
packages: {
|
|
1775
|
+
required: ["autotel-tanstack"],
|
|
1776
|
+
optional: [],
|
|
1777
|
+
devOnly: []
|
|
1778
|
+
},
|
|
1779
|
+
env: {
|
|
1780
|
+
required: [],
|
|
1781
|
+
optional: []
|
|
1782
|
+
},
|
|
1783
|
+
imports: [
|
|
1784
|
+
{
|
|
1785
|
+
source: "autotel-tanstack/middleware",
|
|
1786
|
+
specifiers: ["createTracingServerHandler"]
|
|
1787
|
+
}
|
|
1788
|
+
],
|
|
1789
|
+
configBlock: {
|
|
1790
|
+
type: "plugin",
|
|
1791
|
+
code: `// In your start.ts, register tracing middleware on createStart():
|
|
1792
|
+
// import { createMiddleware, createStart } from '@tanstack/react-start';
|
|
1793
|
+
//
|
|
1794
|
+
// const requestTracing = createMiddleware().server(
|
|
1795
|
+
// createTracingServerHandler({
|
|
1796
|
+
// captureHeaders: ['x-request-id', 'user-agent'],
|
|
1797
|
+
// excludePaths: ['/health', '/metrics'],
|
|
1798
|
+
// }),
|
|
1799
|
+
// );
|
|
1800
|
+
//
|
|
1801
|
+
// const functionTracing = createMiddleware({ type: 'function' }).server(
|
|
1802
|
+
// createTracingServerHandler({ type: 'function', captureArgs: true }),
|
|
1803
|
+
// );
|
|
1804
|
+
//
|
|
1805
|
+
// export const startInstance = createStart(() => ({
|
|
1806
|
+
// requestMiddleware: [requestTracing],
|
|
1807
|
+
// functionMiddleware: [functionTracing],
|
|
1808
|
+
// }));`,
|
|
1809
|
+
section: "PLUGIN_INIT"
|
|
1810
|
+
},
|
|
1811
|
+
nextSteps: [
|
|
1812
|
+
"Add tracing middleware to your createStart() call in src/start.ts",
|
|
1813
|
+
'Alternative: import "autotel-tanstack/auto" for zero-config init via OTEL_* env vars',
|
|
1814
|
+
"See packages/autotel-tanstack README for loader/server-function tracing helpers"
|
|
1815
|
+
]
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1478
1818
|
// src/presets/platforms/aws.ts
|
|
1479
1819
|
var awsLambda = {
|
|
1480
1820
|
name: "AWS Lambda",
|
|
@@ -1601,7 +1941,11 @@ var subscribers = /* @__PURE__ */ new Map([
|
|
|
1601
1941
|
]);
|
|
1602
1942
|
var plugins = /* @__PURE__ */ new Map([
|
|
1603
1943
|
["mongoose", mongoose],
|
|
1604
|
-
["drizzle", drizzle]
|
|
1944
|
+
["drizzle", drizzle],
|
|
1945
|
+
["sentry", sentry],
|
|
1946
|
+
["hono", hono],
|
|
1947
|
+
["mcp", mcp],
|
|
1948
|
+
["tanstack", tanstack]
|
|
1605
1949
|
]);
|
|
1606
1950
|
var platforms = /* @__PURE__ */ new Map([
|
|
1607
1951
|
["aws-lambda", awsLambda],
|
|
@@ -1693,120 +2037,399 @@ function listPresetSlugs(type) {
|
|
|
1693
2037
|
return [...getPresetsByType(type).keys()];
|
|
1694
2038
|
}
|
|
1695
2039
|
|
|
1696
|
-
// src/
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
2040
|
+
// src/lib/plan-builder.ts
|
|
2041
|
+
function buildPlanFromDetection(opts) {
|
|
2042
|
+
const { project, detection } = opts;
|
|
2043
|
+
const presets = [];
|
|
2044
|
+
const presetSlugs = [];
|
|
2045
|
+
const backendPreset = findPreset(detection.backend.slug);
|
|
2046
|
+
if (backendPreset !== null) {
|
|
2047
|
+
presets.push(backendPreset);
|
|
2048
|
+
presetSlugs.push(detection.backend.slug);
|
|
2049
|
+
}
|
|
2050
|
+
for (const slug of detection.presets) {
|
|
2051
|
+
if (slug === detection.backend.slug) continue;
|
|
2052
|
+
const p = findPreset(slug);
|
|
2053
|
+
if (p !== null) {
|
|
2054
|
+
presets.push(p);
|
|
2055
|
+
presetSlugs.push(slug);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
const prod = /* @__PURE__ */ new Set(["autotel"]);
|
|
2059
|
+
const dev = /* @__PURE__ */ new Set();
|
|
2060
|
+
for (const p of presets) {
|
|
2061
|
+
for (const pkg of p.packages.required) prod.add(pkg);
|
|
2062
|
+
for (const pkg of p.packages.optional) prod.add(pkg);
|
|
2063
|
+
for (const pkg of p.packages.devOnly) dev.add(pkg);
|
|
2064
|
+
}
|
|
2065
|
+
if (detection.primaryLogger === "pino") prod.add("pino");
|
|
2066
|
+
if (detection.autoInstrumentedDeps.length > 0 || detection.autoInstrumentLoggers.length > 0 || detection.primaryLogger === "winston" || detection.primaryLogger === "bunyan") {
|
|
2067
|
+
prod.add("@opentelemetry/auto-instrumentations-node");
|
|
2068
|
+
}
|
|
2069
|
+
const envVars = [];
|
|
2070
|
+
const seenEnv = /* @__PURE__ */ new Set();
|
|
2071
|
+
for (const p of presets) {
|
|
2072
|
+
for (const ev of [...p.env.required, ...p.env.optional]) {
|
|
2073
|
+
if (seenEnv.has(ev.name)) continue;
|
|
2074
|
+
seenEnv.add(ev.name);
|
|
2075
|
+
envVars.push({
|
|
2076
|
+
name: ev.name,
|
|
2077
|
+
sensitive: ev.sensitive,
|
|
2078
|
+
action: "add-to-.env.example"
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
const instrumentationPath = getInstrumentationPath(
|
|
2083
|
+
project.packageRoot,
|
|
2084
|
+
project.hasTypeScript
|
|
2085
|
+
);
|
|
2086
|
+
const filesToWrite = [
|
|
2087
|
+
{
|
|
2088
|
+
path: path7.relative(project.cwd, instrumentationPath),
|
|
2089
|
+
action: fileExists(instrumentationPath) ? "merge" : "create"
|
|
2090
|
+
}
|
|
1717
2091
|
];
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
2092
|
+
const envExamplePath = path7.join(project.packageRoot, ".env.example");
|
|
2093
|
+
if (envVars.length > 0 && !fileExists(envExamplePath)) {
|
|
2094
|
+
filesToWrite.push({
|
|
2095
|
+
path: path7.relative(project.cwd, envExamplePath),
|
|
2096
|
+
action: "create"
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
const nextSteps = presets.flatMap((p) => p.nextSteps);
|
|
2100
|
+
const plan = {
|
|
2101
|
+
v: 1,
|
|
2102
|
+
presets: presetSlugs,
|
|
2103
|
+
packagesToInstall: { prod: [...prod], dev: [...dev] },
|
|
2104
|
+
filesToWrite,
|
|
2105
|
+
envVars,
|
|
2106
|
+
nextSteps,
|
|
2107
|
+
detected: {
|
|
2108
|
+
packages: detection.packages,
|
|
2109
|
+
primaryLogger: detection.primaryLogger,
|
|
2110
|
+
autoInstrumentLoggers: detection.autoInstrumentLoggers,
|
|
2111
|
+
autoInstrumentedDeps: detection.autoInstrumentedDeps,
|
|
2112
|
+
backend: detection.backend,
|
|
2113
|
+
platform: detection.platform
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
return { plan, presets, instrumentationPath };
|
|
1723
2117
|
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
{ value: "winston", name: "Winston" }
|
|
1731
|
-
],
|
|
1732
|
-
default: null
|
|
1733
|
-
});
|
|
2118
|
+
function findPreset(slug) {
|
|
2119
|
+
for (const type of ["backend", "subscriber", "plugin", "platform"]) {
|
|
2120
|
+
const p = getPreset(type, slug);
|
|
2121
|
+
if (p !== void 0) return p;
|
|
2122
|
+
}
|
|
2123
|
+
return null;
|
|
1734
2124
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
2125
|
+
|
|
2126
|
+
// src/lib/errors.ts
|
|
2127
|
+
var AutotelErrorCodes = {
|
|
2128
|
+
// validation
|
|
2129
|
+
E_NO_PACKAGE_JSON: "AUTOTEL_E_NO_PACKAGE_JSON",
|
|
2130
|
+
E_UNKNOWN_PRESET: "AUTOTEL_E_UNKNOWN_PRESET",
|
|
2131
|
+
E_INVALID_PLAN: "AUTOTEL_E_INVALID_PLAN",
|
|
2132
|
+
E_INVALID_INPUT: "AUTOTEL_E_INVALID_INPUT",
|
|
2133
|
+
E_INVALID_FLAG: "AUTOTEL_E_INVALID_FLAG",
|
|
2134
|
+
// environment
|
|
2135
|
+
E_NO_WORKSPACE_PACKAGES: "AUTOTEL_E_NO_WORKSPACE_PACKAGES",
|
|
2136
|
+
E_ENV_CONSENT_REQUIRED: "AUTOTEL_E_ENV_CONSENT_REQUIRED",
|
|
2137
|
+
// conflict
|
|
2138
|
+
E_EXISTING_CONFIG: "AUTOTEL_E_EXISTING_CONFIG",
|
|
2139
|
+
E_AMBIGUOUS_LOGGER: "AUTOTEL_E_AMBIGUOUS_LOGGER",
|
|
2140
|
+
// install
|
|
2141
|
+
E_INSTALL_FAILED: "AUTOTEL_E_INSTALL_FAILED",
|
|
2142
|
+
// io
|
|
2143
|
+
E_WRITE_FAILED: "AUTOTEL_E_WRITE_FAILED",
|
|
2144
|
+
E_READ_FAILED: "AUTOTEL_E_READ_FAILED",
|
|
2145
|
+
// runtime
|
|
2146
|
+
E_UNKNOWN: "AUTOTEL_E_UNKNOWN"
|
|
2147
|
+
};
|
|
2148
|
+
var AutotelError = class extends Error {
|
|
2149
|
+
type;
|
|
2150
|
+
code;
|
|
2151
|
+
retryable;
|
|
2152
|
+
fix;
|
|
2153
|
+
expected;
|
|
2154
|
+
suggestions;
|
|
2155
|
+
constructor(opts) {
|
|
2156
|
+
super(opts.message);
|
|
2157
|
+
this.name = "AutotelError";
|
|
2158
|
+
this.type = opts.type;
|
|
2159
|
+
this.code = opts.code;
|
|
2160
|
+
this.retryable = opts.retryable ?? false;
|
|
2161
|
+
this.fix = opts.fix;
|
|
2162
|
+
this.expected = opts.expected;
|
|
2163
|
+
this.suggestions = opts.suggestions;
|
|
2164
|
+
}
|
|
2165
|
+
toEnvelope(command) {
|
|
2166
|
+
return {
|
|
2167
|
+
ok: false,
|
|
2168
|
+
...command !== void 0 ? { command } : {},
|
|
2169
|
+
error: {
|
|
2170
|
+
type: this.type,
|
|
2171
|
+
code: this.code,
|
|
2172
|
+
message: this.message,
|
|
2173
|
+
retryable: this.retryable,
|
|
2174
|
+
...this.fix !== void 0 ? { fix: this.fix } : {},
|
|
2175
|
+
...this.expected !== void 0 ? { expected: this.expected } : {},
|
|
2176
|
+
...this.suggestions !== void 0 ? { suggestions: this.suggestions } : {}
|
|
2177
|
+
}
|
|
2178
|
+
};
|
|
1742
2179
|
}
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
2180
|
+
};
|
|
2181
|
+
function exitCodeForError(err) {
|
|
2182
|
+
if (err.type === "validation" || err.type === "conflict") {
|
|
2183
|
+
return 2;
|
|
2184
|
+
}
|
|
2185
|
+
return 1;
|
|
2186
|
+
}
|
|
2187
|
+
function toAutotelError(value) {
|
|
2188
|
+
if (value instanceof AutotelError) return value;
|
|
2189
|
+
const message = value instanceof Error ? value.message : String(value);
|
|
2190
|
+
return new AutotelError({
|
|
2191
|
+
type: "runtime",
|
|
2192
|
+
code: AutotelErrorCodes.E_UNKNOWN,
|
|
2193
|
+
message
|
|
1746
2194
|
});
|
|
1747
2195
|
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
2196
|
+
|
|
2197
|
+
// src/lib/plan.ts
|
|
2198
|
+
function parsePlan(input2) {
|
|
2199
|
+
if (typeof input2 !== "object" || input2 === null || Array.isArray(input2)) {
|
|
2200
|
+
throw new AutotelError({
|
|
2201
|
+
type: "validation",
|
|
2202
|
+
code: AutotelErrorCodes.E_INVALID_PLAN,
|
|
2203
|
+
message: "Plan must be a JSON object",
|
|
2204
|
+
expected: { v: 1, presets: "string[]" }
|
|
2205
|
+
});
|
|
1755
2206
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
2207
|
+
const obj = input2;
|
|
2208
|
+
if (obj["v"] !== 1) {
|
|
2209
|
+
throw new AutotelError({
|
|
2210
|
+
type: "validation",
|
|
2211
|
+
code: AutotelErrorCodes.E_INVALID_PLAN,
|
|
2212
|
+
message: `Unsupported plan version: ${String(obj["v"])}`,
|
|
2213
|
+
fix: "Regenerate the plan with `autotel init --json --dry-run`",
|
|
2214
|
+
expected: { v: 1 }
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
if (!Array.isArray(obj["presets"])) {
|
|
2218
|
+
throw new AutotelError({
|
|
2219
|
+
type: "validation",
|
|
2220
|
+
code: AutotelErrorCodes.E_INVALID_PLAN,
|
|
2221
|
+
message: "plan.presets must be an array of preset slugs"
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
const pkgs = obj["packagesToInstall"];
|
|
2225
|
+
if (typeof pkgs !== "object" || pkgs === null || !Array.isArray(pkgs["prod"]) || !Array.isArray(pkgs["dev"])) {
|
|
2226
|
+
throw new AutotelError({
|
|
2227
|
+
type: "validation",
|
|
2228
|
+
code: AutotelErrorCodes.E_INVALID_PLAN,
|
|
2229
|
+
message: "plan.packagesToInstall must be { prod: string[], dev: string[] }"
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
return obj;
|
|
1760
2233
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
2234
|
+
|
|
2235
|
+
// src/lib/instrumentation-parser.ts
|
|
2236
|
+
function parseInstrumentation(content) {
|
|
2237
|
+
const cliOwned = hasCliOwnershipHeader(content);
|
|
2238
|
+
const importedSources = collectImportSources(content);
|
|
2239
|
+
const detectedLogger = pickLogger(importedSources);
|
|
2240
|
+
const autoInstrumentations = parseAutoInstrumentations(content);
|
|
2241
|
+
return { cliOwned, importedSources, detectedLogger, autoInstrumentations };
|
|
2242
|
+
}
|
|
2243
|
+
function collectImportSources(content) {
|
|
2244
|
+
const out = /* @__PURE__ */ new Set();
|
|
2245
|
+
const re = /import\s+(?:[^'"]*\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
2246
|
+
let match;
|
|
2247
|
+
while ((match = re.exec(content)) !== null) {
|
|
2248
|
+
if (match[1] !== void 0) out.add(match[1]);
|
|
2249
|
+
}
|
|
2250
|
+
return out;
|
|
2251
|
+
}
|
|
2252
|
+
function pickLogger(imports) {
|
|
2253
|
+
if (imports.has("pino")) return "pino";
|
|
2254
|
+
if (imports.has("winston")) return "winston";
|
|
2255
|
+
if (imports.has("bunyan")) return "bunyan";
|
|
2256
|
+
return null;
|
|
2257
|
+
}
|
|
2258
|
+
function parseAutoInstrumentations(content) {
|
|
2259
|
+
const re = /autoInstrumentations\s*:\s*\[([^\]]*)\]/;
|
|
2260
|
+
const match = re.exec(content);
|
|
2261
|
+
if (!match || match[1] === void 0) return [];
|
|
2262
|
+
return [...match[1].matchAll(/['"]([^'"]+)['"]/g)].map((m) => m[1]).filter((s) => s !== void 0);
|
|
2263
|
+
}
|
|
2264
|
+
function diffImportSources(existing, planImportSources) {
|
|
2265
|
+
return planImportSources.filter((s) => !existing.importedSources.has(s));
|
|
1771
2266
|
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
2267
|
+
function diffAutoInstrumentations(existing, planEntries) {
|
|
2268
|
+
return planEntries.filter((e) => !existing.autoInstrumentations.includes(e));
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// src/ui/preview.ts
|
|
2272
|
+
import chalk from "chalk";
|
|
2273
|
+
import { checkbox, select } from "@inquirer/prompts";
|
|
2274
|
+
function renderPlanPreview(plan) {
|
|
2275
|
+
const lines = [
|
|
2276
|
+
chalk.bold("autotel init \u2014 proposed plan"),
|
|
2277
|
+
""
|
|
1783
2278
|
];
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
2279
|
+
if (plan.detected) {
|
|
2280
|
+
const det = plan.detected;
|
|
2281
|
+
const pkgList = det.packages.map((p) => `${p.name}@${p.version}`).join(", ");
|
|
2282
|
+
lines.push(
|
|
2283
|
+
`${chalk.dim("Detected packages:")} ${pkgList || chalk.dim("(none)")}`
|
|
2284
|
+
);
|
|
2285
|
+
if (det.primaryLogger !== null) {
|
|
2286
|
+
lines.push(
|
|
2287
|
+
`${chalk.dim("Logger:")} ${chalk.bold(det.primaryLogger)} ${chalk.dim("(first-class)")}` + (det.autoInstrumentLoggers.length > 0 ? `, ${chalk.dim("+ auto-instrumented:")} ${det.autoInstrumentLoggers.join(", ")}` : "")
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
if (det.autoInstrumentedDeps.length > 0) {
|
|
2291
|
+
lines.push(
|
|
2292
|
+
`${chalk.dim("Covered by auto-instrumentations-node:")} ${det.autoInstrumentedDeps.join(", ")}`
|
|
2293
|
+
);
|
|
2294
|
+
}
|
|
2295
|
+
lines.push(
|
|
2296
|
+
`${chalk.dim("Backend:")} ${chalk.bold(det.backend.slug)} ${chalk.dim(`(${det.backend.source}${det.backend.detail ? `: ${det.backend.detail}` : ""})`)}`
|
|
2297
|
+
);
|
|
2298
|
+
if (det.platform !== null) {
|
|
2299
|
+
lines.push(`${chalk.dim("Platform:")} ${chalk.bold(det.platform)}`);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
lines.push("");
|
|
2303
|
+
lines.push(chalk.bold("Will wire:"));
|
|
2304
|
+
for (const slug of plan.presets) {
|
|
2305
|
+
lines.push(` ${chalk.green("+")} ${slug}`);
|
|
2306
|
+
}
|
|
2307
|
+
lines.push("");
|
|
2308
|
+
if (plan.packagesToInstall.prod.length > 0) {
|
|
2309
|
+
lines.push(
|
|
2310
|
+
`${chalk.bold("Install:")} ${plan.packagesToInstall.prod.join(", ")}`
|
|
2311
|
+
);
|
|
2312
|
+
}
|
|
2313
|
+
if (plan.packagesToInstall.dev.length > 0) {
|
|
2314
|
+
lines.push(
|
|
2315
|
+
`${chalk.bold("Install (dev):")} ${plan.packagesToInstall.dev.join(", ")}`
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
if (plan.envVars.length > 0) {
|
|
2319
|
+
lines.push("");
|
|
2320
|
+
lines.push(chalk.bold("Env vars:"));
|
|
2321
|
+
for (const ev of plan.envVars) {
|
|
2322
|
+
const marker = ev.sensitive ? chalk.yellow("[sensitive]") : "";
|
|
2323
|
+
lines.push(` ${ev.name} ${marker}`);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
if (plan.filesToWrite.length > 0) {
|
|
2327
|
+
lines.push("");
|
|
2328
|
+
lines.push(chalk.bold("Files:"));
|
|
2329
|
+
for (const f of plan.filesToWrite) {
|
|
2330
|
+
lines.push(` ${chalk.dim(f.action)} ${f.path}`);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
return lines.join("\n");
|
|
1789
2334
|
}
|
|
1790
|
-
async function
|
|
1791
|
-
|
|
1792
|
-
|
|
2335
|
+
async function confirmOrEditPlan(plan) {
|
|
2336
|
+
console.log(renderPlanPreview(plan));
|
|
2337
|
+
console.log("");
|
|
2338
|
+
const choice = await select({
|
|
2339
|
+
message: "Proceed?",
|
|
1793
2340
|
choices: [
|
|
1794
|
-
{ value: "
|
|
1795
|
-
{ value: "
|
|
1796
|
-
{ value: "abort", name: "Abort" }
|
|
2341
|
+
{ value: "apply", name: "Apply the plan above" },
|
|
2342
|
+
{ value: "edit", name: "Edit \u2014 deselect items I do not want" },
|
|
2343
|
+
{ value: "abort", name: "Abort, write nothing" }
|
|
1797
2344
|
],
|
|
1798
|
-
default: "
|
|
2345
|
+
default: "apply"
|
|
2346
|
+
});
|
|
2347
|
+
if (choice === "abort") return null;
|
|
2348
|
+
if (choice === "apply") return plan;
|
|
2349
|
+
const kept = await checkbox({
|
|
2350
|
+
message: "Keep which presets?",
|
|
2351
|
+
choices: plan.presets.map((slug) => ({
|
|
2352
|
+
value: slug,
|
|
2353
|
+
name: slug,
|
|
2354
|
+
checked: true
|
|
2355
|
+
}))
|
|
2356
|
+
});
|
|
2357
|
+
return { ...plan, presets: kept };
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// src/lib/json-output.ts
|
|
2361
|
+
import * as fs3 from "fs";
|
|
2362
|
+
import * as path8 from "path";
|
|
2363
|
+
var outputFilePath = null;
|
|
2364
|
+
var outputFileWritten = false;
|
|
2365
|
+
var outputRoot = null;
|
|
2366
|
+
var redactSecrets = false;
|
|
2367
|
+
function configureJsonOutput(opts) {
|
|
2368
|
+
outputFilePath = opts.outputFile ?? null;
|
|
2369
|
+
outputFileWritten = false;
|
|
2370
|
+
outputRoot = opts.outputRoot ?? null;
|
|
2371
|
+
redactSecrets = opts.noSecrets === true || process.env["AUTOTEL_NO_SECRETS"] === "1" || process.env["AGENT_SANDBOX"] === "1";
|
|
2372
|
+
}
|
|
2373
|
+
var SECRET_KEY_PATTERN = /SECRET|TOKEN|PASSWORD|API[_-]?KEY|DSN/i;
|
|
2374
|
+
var SECRET_VALUE_PATTERN = /^[A-Za-z0-9_\-+/=]{40,}$/;
|
|
2375
|
+
var REDACTED = "[REDACTED]";
|
|
2376
|
+
function redact(value) {
|
|
2377
|
+
if (value === null || value === void 0) return value;
|
|
2378
|
+
if (Array.isArray(value)) {
|
|
2379
|
+
return value.map((v) => redact(v));
|
|
2380
|
+
}
|
|
2381
|
+
if (typeof value === "object") {
|
|
2382
|
+
const out = {};
|
|
2383
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2384
|
+
if (typeof v === "string" && SECRET_KEY_PATTERN.test(k)) {
|
|
2385
|
+
out[k] = REDACTED;
|
|
2386
|
+
} else {
|
|
2387
|
+
out[k] = redact(v);
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
return out;
|
|
2391
|
+
}
|
|
2392
|
+
if (typeof value === "string" && SECRET_VALUE_PATTERN.test(value)) {
|
|
2393
|
+
return REDACTED;
|
|
2394
|
+
}
|
|
2395
|
+
return value;
|
|
2396
|
+
}
|
|
2397
|
+
function printJson(data) {
|
|
2398
|
+
const payload = redactSecrets ? redact(data) : data;
|
|
2399
|
+
const serialised = JSON.stringify(payload, null, 2);
|
|
2400
|
+
process.stdout.write(serialised + "\n");
|
|
2401
|
+
if (outputFilePath !== null && !outputFileWritten) {
|
|
2402
|
+
writeArtifactFile(outputFilePath, serialised);
|
|
2403
|
+
outputFileWritten = true;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
function writeArtifactFile(filePath, content) {
|
|
2407
|
+
const resolved = path8.resolve(filePath);
|
|
2408
|
+
if (outputRoot !== null) {
|
|
2409
|
+
atomicWrite(resolved, content, { root: outputRoot });
|
|
2410
|
+
} else {
|
|
2411
|
+
fs3.mkdirSync(path8.dirname(resolved), { recursive: true });
|
|
2412
|
+
fs3.writeFileSync(resolved, content, "utf8");
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
// src/ui/prompts.ts
|
|
2417
|
+
import { select as select2, checkbox as checkbox2, confirm, input } from "@inquirer/prompts";
|
|
2418
|
+
async function promptConfirm(message, defaultValue = true) {
|
|
2419
|
+
return await confirm({
|
|
2420
|
+
message,
|
|
2421
|
+
default: defaultValue
|
|
1799
2422
|
});
|
|
1800
2423
|
}
|
|
1801
2424
|
|
|
1802
2425
|
// src/ui/output.ts
|
|
1803
|
-
import
|
|
2426
|
+
import chalk2 from "chalk";
|
|
1804
2427
|
var STATUS = {
|
|
1805
|
-
ok:
|
|
1806
|
-
warn:
|
|
1807
|
-
error:
|
|
1808
|
-
info:
|
|
1809
|
-
skip:
|
|
2428
|
+
ok: chalk2.green("[OK]"),
|
|
2429
|
+
warn: chalk2.yellow("[WARN]"),
|
|
2430
|
+
error: chalk2.red("[ERROR]"),
|
|
2431
|
+
info: chalk2.blue("[INFO]"),
|
|
2432
|
+
skip: chalk2.gray("[SKIP]")
|
|
1810
2433
|
};
|
|
1811
2434
|
function formatCheck(check) {
|
|
1812
2435
|
const lines = [];
|
|
@@ -1814,68 +2437,66 @@ function formatCheck(check) {
|
|
|
1814
2437
|
lines.push(` ${statusToken} ${check.message}`);
|
|
1815
2438
|
if (check.details && check.details.length > 0) {
|
|
1816
2439
|
for (const detail of check.details) {
|
|
1817
|
-
lines.push(` ${
|
|
2440
|
+
lines.push(` ${chalk2.dim(detail)}`);
|
|
1818
2441
|
}
|
|
1819
2442
|
}
|
|
1820
2443
|
if (check.fix) {
|
|
1821
|
-
lines.push(` ${
|
|
2444
|
+
lines.push(` ${chalk2.cyan("Fix:")} ${check.fix.cmd}`);
|
|
1822
2445
|
}
|
|
1823
2446
|
return lines;
|
|
1824
2447
|
}
|
|
1825
2448
|
function formatSummary(summary) {
|
|
1826
2449
|
const parts = [];
|
|
1827
2450
|
if (summary.ok > 0) {
|
|
1828
|
-
parts.push(
|
|
2451
|
+
parts.push(chalk2.green(`${summary.ok} passed`));
|
|
1829
2452
|
}
|
|
1830
2453
|
if (summary.warnings > 0) {
|
|
1831
|
-
parts.push(
|
|
2454
|
+
parts.push(chalk2.yellow(`${summary.warnings} warning${summary.warnings > 1 ? "s" : ""}`));
|
|
1832
2455
|
}
|
|
1833
2456
|
if (summary.errors > 0) {
|
|
1834
|
-
parts.push(
|
|
2457
|
+
parts.push(chalk2.red(`${summary.errors} error${summary.errors > 1 ? "s" : ""}`));
|
|
1835
2458
|
}
|
|
1836
2459
|
if (summary.skipped > 0) {
|
|
1837
|
-
parts.push(
|
|
2460
|
+
parts.push(chalk2.gray(`${summary.skipped} skipped`));
|
|
1838
2461
|
}
|
|
1839
2462
|
return `Summary: ${parts.join(", ")}`;
|
|
1840
2463
|
}
|
|
1841
2464
|
function formatFooter(options) {
|
|
1842
2465
|
const lines = [""];
|
|
1843
2466
|
if (options.detected) {
|
|
1844
|
-
lines.push(
|
|
2467
|
+
lines.push(chalk2.dim(`Detected: ${options.detected}`));
|
|
1845
2468
|
}
|
|
1846
2469
|
if (options.wrote && options.wrote.length > 0) {
|
|
1847
|
-
lines.push(
|
|
2470
|
+
lines.push(chalk2.dim(`Wrote: ${options.wrote.join(", ")}`));
|
|
1848
2471
|
}
|
|
1849
2472
|
if (options.next) {
|
|
1850
|
-
lines.push(
|
|
2473
|
+
lines.push(chalk2.cyan(`Next: ${options.next}`));
|
|
1851
2474
|
}
|
|
1852
2475
|
return lines.join("\n");
|
|
1853
2476
|
}
|
|
2477
|
+
function formatPackageManagerInfo(pm, lockfilePath) {
|
|
2478
|
+
if (lockfilePath) {
|
|
2479
|
+
return `${pm} (via ${chalk2.dim(lockfilePath)})`;
|
|
2480
|
+
}
|
|
2481
|
+
return `${pm} (default, no lockfile found)`;
|
|
2482
|
+
}
|
|
1854
2483
|
function heading(text) {
|
|
1855
|
-
console.log(
|
|
2484
|
+
console.log(chalk2.bold(text));
|
|
1856
2485
|
}
|
|
1857
2486
|
function info(text) {
|
|
1858
|
-
console.log(
|
|
2487
|
+
console.log(chalk2.blue(text));
|
|
1859
2488
|
}
|
|
1860
2489
|
function success(text) {
|
|
1861
|
-
console.log(
|
|
2490
|
+
console.log(chalk2.green(text));
|
|
1862
2491
|
}
|
|
1863
2492
|
function warn(text) {
|
|
1864
|
-
console.log(
|
|
2493
|
+
console.log(chalk2.yellow(text));
|
|
1865
2494
|
}
|
|
1866
2495
|
function error(text) {
|
|
1867
|
-
console.log(
|
|
2496
|
+
console.log(chalk2.red(text));
|
|
1868
2497
|
}
|
|
1869
2498
|
function dim(text) {
|
|
1870
|
-
console.log(
|
|
1871
|
-
}
|
|
1872
|
-
function isVerbose() {
|
|
1873
|
-
return process.env["AUTOTEL_VERBOSE"] === "true";
|
|
1874
|
-
}
|
|
1875
|
-
function verbose(text) {
|
|
1876
|
-
if (isVerbose()) {
|
|
1877
|
-
console.log(chalk.gray(`[verbose] ${text}`));
|
|
1878
|
-
}
|
|
2499
|
+
console.log(chalk2.dim(text));
|
|
1879
2500
|
}
|
|
1880
2501
|
|
|
1881
2502
|
// src/ui/spinner.ts
|
|
@@ -1906,104 +2527,200 @@ function isCI() {
|
|
|
1906
2527
|
|
|
1907
2528
|
// src/commands/init.ts
|
|
1908
2529
|
async function runInit(options) {
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
process.env["AUTOTEL_QUIET"] = "true";
|
|
2530
|
+
if (options.json) {
|
|
2531
|
+
configureJsonOutput({
|
|
2532
|
+
outputFile: options.outputFile,
|
|
2533
|
+
noSecrets: options.noSecrets
|
|
2534
|
+
});
|
|
1915
2535
|
}
|
|
1916
|
-
|
|
2536
|
+
if (options.verbose) process.env["AUTOTEL_VERBOSE"] = "true";
|
|
2537
|
+
if (options.quiet) process.env["AUTOTEL_QUIET"] = "true";
|
|
1917
2538
|
const project = discoverProject(options.cwd);
|
|
1918
|
-
if (
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2539
|
+
if (project === null) {
|
|
2540
|
+
throw new AutotelError({
|
|
2541
|
+
type: "environment",
|
|
2542
|
+
code: AutotelErrorCodes.E_NO_PACKAGE_JSON,
|
|
2543
|
+
message: `No package.json found at or above ${options.cwd}`,
|
|
2544
|
+
fix: "cd into a directory with a package.json, or pass --cwd <path>",
|
|
2545
|
+
expected: { file: "package.json" }
|
|
2546
|
+
});
|
|
1922
2547
|
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
2548
|
+
if (options.json && options.outputFile !== void 0) {
|
|
2549
|
+
configureJsonOutput({
|
|
2550
|
+
outputFile: options.outputFile,
|
|
2551
|
+
outputRoot: project.packageRoot,
|
|
2552
|
+
noSecrets: options.noSecrets
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
let plan = null;
|
|
2556
|
+
if (options.plan !== void 0) {
|
|
2557
|
+
plan = readPlanFromFile(options.plan);
|
|
2558
|
+
} else if (options.input !== void 0) {
|
|
2559
|
+
plan = await readPlanFromInput(options.input);
|
|
2560
|
+
} else if (options.preset !== void 0) {
|
|
2561
|
+
plan = planFromQuickPreset(options.preset, project);
|
|
2562
|
+
} else if (!options.noDetect) {
|
|
2563
|
+
plan = await planFromDetection(project, options);
|
|
2564
|
+
}
|
|
2565
|
+
if (plan === null) {
|
|
2566
|
+
throw new AutotelError({
|
|
2567
|
+
type: "validation",
|
|
2568
|
+
code: AutotelErrorCodes.E_INVALID_FLAG,
|
|
2569
|
+
message: "No plan source available (--no-detect disables detection)",
|
|
2570
|
+
fix: "Drop --no-detect or pass --plan / --input / --preset"
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
const interactive = !options.yes && !options.noInteractive && !options.json && !isCI();
|
|
2574
|
+
if (interactive && options.plan === void 0 && options.input === void 0 && options.preset === void 0) {
|
|
2575
|
+
const confirmed = await confirmOrEditPlan(plan);
|
|
2576
|
+
if (confirmed === null) {
|
|
1935
2577
|
info("Aborted");
|
|
1936
|
-
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
const quickPreset = getQuickPreset(options.preset);
|
|
1944
|
-
if (quickPreset) {
|
|
1945
|
-
info(`Using quick preset: ${quickPreset.name}`);
|
|
1946
|
-
const backendPreset = getPreset("backend", quickPreset.backend);
|
|
1947
|
-
if (backendPreset) {
|
|
1948
|
-
selectedPresets.push(backendPreset);
|
|
1949
|
-
}
|
|
1950
|
-
if (quickPreset.subscribers) {
|
|
1951
|
-
for (const sub of quickPreset.subscribers) {
|
|
1952
|
-
const subPreset = getPreset("subscriber", sub);
|
|
1953
|
-
if (subPreset) selectedPresets.push(subPreset);
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
if (quickPreset.plugins) {
|
|
1957
|
-
for (const plug of quickPreset.plugins) {
|
|
1958
|
-
const plugPreset = getPreset("plugin", plug);
|
|
1959
|
-
if (plugPreset) selectedPresets.push(plugPreset);
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
autoInstrumentations = quickPreset.autoInstrumentations;
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
plan = confirmed;
|
|
2581
|
+
}
|
|
2582
|
+
if (options.detectOnly) {
|
|
2583
|
+
if (options.json) {
|
|
2584
|
+
printJson({ ok: true, command: "autotel init", plan });
|
|
1963
2585
|
} else {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
process.exit(1);
|
|
2586
|
+
info("Detection-only mode \u2014 no files written");
|
|
2587
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
1967
2588
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
if (options.json && options.dryRun) {
|
|
2592
|
+
printJson({ ok: true, command: "autotel init", plan, dryRun: true });
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
if (options.json && !options.dryRun) {
|
|
2596
|
+
const applied2 = applyPlan({ plan, project, options });
|
|
2597
|
+
printJson({ ok: true, command: "autotel init", plan, applied: applied2 });
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
if (options.dryRun) {
|
|
2601
|
+
heading("\nDry run \u2014 no files will be written\n");
|
|
2602
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
const applied = applyPlan({ plan, project, options });
|
|
2606
|
+
printApplySummary({ plan, applied, project, options });
|
|
2607
|
+
}
|
|
2608
|
+
function readPlanFromFile(filePath) {
|
|
2609
|
+
const content = readFileSafe(filePath);
|
|
2610
|
+
if (content === null) {
|
|
2611
|
+
throw new AutotelError({
|
|
2612
|
+
type: "io",
|
|
2613
|
+
code: AutotelErrorCodes.E_READ_FAILED,
|
|
2614
|
+
message: `Could not read plan file: ${filePath}`
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
try {
|
|
2618
|
+
return parsePlan(JSON.parse(content));
|
|
2619
|
+
} catch (error2) {
|
|
2620
|
+
if (error2 instanceof AutotelError) throw error2;
|
|
2621
|
+
throw new AutotelError({
|
|
2622
|
+
type: "validation",
|
|
2623
|
+
code: AutotelErrorCodes.E_INVALID_PLAN,
|
|
2624
|
+
message: `Plan file is not valid JSON: ${error2.message}`
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
async function readPlanFromInput(input2) {
|
|
2629
|
+
if (input2 === "-") {
|
|
2630
|
+
const chunks = [];
|
|
2631
|
+
for await (const chunk of process.stdin) {
|
|
2632
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1973
2633
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
for (const slug of pluginSlugs) {
|
|
1985
|
-
const preset = getPreset("plugin", slug);
|
|
1986
|
-
if (preset) selectedPresets.push(preset);
|
|
1987
|
-
}
|
|
1988
|
-
const subscriberSlugs = await promptSubscribers(subscribers);
|
|
1989
|
-
for (const slug of subscriberSlugs) {
|
|
1990
|
-
const preset = getPreset("subscriber", slug);
|
|
1991
|
-
if (preset) selectedPresets.push(preset);
|
|
1992
|
-
}
|
|
1993
|
-
const autoChoice = await promptAutoInstrumentation();
|
|
1994
|
-
if (autoChoice === "none") {
|
|
1995
|
-
autoInstrumentations = "none";
|
|
1996
|
-
} else if (autoChoice === "specific") {
|
|
1997
|
-
autoInstrumentations = "all";
|
|
1998
|
-
}
|
|
1999
|
-
if (runtime === "node") {
|
|
2000
|
-
startupStyle = await promptStartupStyle(project.hasTypeScript);
|
|
2634
|
+
const content = Buffer.concat(chunks).toString("utf8");
|
|
2635
|
+
try {
|
|
2636
|
+
return parsePlan(JSON.parse(content));
|
|
2637
|
+
} catch (error2) {
|
|
2638
|
+
if (error2 instanceof AutotelError) throw error2;
|
|
2639
|
+
throw new AutotelError({
|
|
2640
|
+
type: "validation",
|
|
2641
|
+
code: AutotelErrorCodes.E_INVALID_INPUT,
|
|
2642
|
+
message: `stdin did not contain valid JSON: ${error2.message}`
|
|
2643
|
+
});
|
|
2001
2644
|
}
|
|
2002
2645
|
}
|
|
2646
|
+
return readPlanFromFile(input2);
|
|
2647
|
+
}
|
|
2648
|
+
function planFromQuickPreset(slug, project) {
|
|
2649
|
+
if (project === null) {
|
|
2650
|
+
throw new AutotelError({
|
|
2651
|
+
type: "environment",
|
|
2652
|
+
code: AutotelErrorCodes.E_NO_PACKAGE_JSON,
|
|
2653
|
+
message: "project required"
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
const quick = getQuickPreset(slug);
|
|
2657
|
+
if (quick === void 0) {
|
|
2658
|
+
throw new AutotelError({
|
|
2659
|
+
type: "validation",
|
|
2660
|
+
code: AutotelErrorCodes.E_UNKNOWN_PRESET,
|
|
2661
|
+
message: `Unknown preset: ${slug}`,
|
|
2662
|
+
fix: "Run `autotel commands --json` to see available presets"
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
const presets = [quick.backend];
|
|
2666
|
+
if (quick.subscribers) presets.push(...quick.subscribers);
|
|
2667
|
+
if (quick.plugins) presets.push(...quick.plugins);
|
|
2668
|
+
const { plan } = buildPlanFromDetection({
|
|
2669
|
+
project,
|
|
2670
|
+
detection: {
|
|
2671
|
+
packages: [],
|
|
2672
|
+
presets,
|
|
2673
|
+
primaryLogger: quick.logging === "pino" ? "pino" : null,
|
|
2674
|
+
autoInstrumentLoggers: [],
|
|
2675
|
+
autoInstrumentedDeps: [],
|
|
2676
|
+
backend: { slug: quick.backend, source: "default" },
|
|
2677
|
+
platform: null
|
|
2678
|
+
}
|
|
2679
|
+
});
|
|
2680
|
+
return plan;
|
|
2681
|
+
}
|
|
2682
|
+
async function planFromDetection(project, options) {
|
|
2683
|
+
if (project === null) {
|
|
2684
|
+
throw new AutotelError({
|
|
2685
|
+
type: "environment",
|
|
2686
|
+
code: AutotelErrorCodes.E_NO_PACKAGE_JSON,
|
|
2687
|
+
message: "project required"
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
let envConsent = options.scanEnv;
|
|
2691
|
+
if (!envConsent && envFilesRequireConsent(project.packageRoot) && !options.yes && !options.noInteractive && !options.json && !isCI()) {
|
|
2692
|
+
envConsent = await promptConfirm(
|
|
2693
|
+
`Found a .env file. Read its keys to help detect the backend? (values are never read)`,
|
|
2694
|
+
false
|
|
2695
|
+
);
|
|
2696
|
+
}
|
|
2697
|
+
const detection = detectInProject({ project, envConsent });
|
|
2698
|
+
const { plan } = buildPlanFromDetection({ project, detection });
|
|
2699
|
+
return plan;
|
|
2700
|
+
}
|
|
2701
|
+
function applyPlan(args) {
|
|
2702
|
+
const { plan, project, options } = args;
|
|
2703
|
+
const result = {
|
|
2704
|
+
wroteFiles: [],
|
|
2705
|
+
ranInstalls: [],
|
|
2706
|
+
printedInstalls: [],
|
|
2707
|
+
installErrors: []
|
|
2708
|
+
};
|
|
2709
|
+
const presets = [];
|
|
2710
|
+
for (const slug of plan.presets) {
|
|
2711
|
+
const p = resolvePreset(slug);
|
|
2712
|
+
if (p !== null) presets.push(p);
|
|
2713
|
+
}
|
|
2003
2714
|
const codeFile = createCodeFile();
|
|
2004
2715
|
addImport(codeFile, { source: "autotel/register", sideEffect: true });
|
|
2005
2716
|
addImport(codeFile, { source: "autotel", specifiers: ["init"] });
|
|
2006
|
-
|
|
2717
|
+
if (plan.detected?.primaryLogger === "pino") {
|
|
2718
|
+
setPinoLogger(codeFile);
|
|
2719
|
+
}
|
|
2720
|
+
for (const l of plan.detected?.autoInstrumentLoggers ?? []) {
|
|
2721
|
+
addAutoInstrumentationLogger(codeFile, l);
|
|
2722
|
+
}
|
|
2723
|
+
for (const preset of presets) {
|
|
2007
2724
|
for (const imp of preset.imports) {
|
|
2008
2725
|
const section = preset.type === "backend" || preset.type === "platform" ? "backend" : preset.type === "plugin" ? "plugin" : preset.type === "subscriber" ? "subscriber" : void 0;
|
|
2009
2726
|
addImport(codeFile, imp, section);
|
|
@@ -2016,130 +2733,143 @@ async function runInit(options) {
|
|
|
2016
2733
|
addPluginInit(codeFile, preset.configBlock.code);
|
|
2017
2734
|
}
|
|
2018
2735
|
}
|
|
2019
|
-
if (
|
|
2736
|
+
if (codeFile.backendConfig === null) {
|
|
2020
2737
|
setBackendConfig(codeFile, "// Local/console mode - no backend configured");
|
|
2021
2738
|
}
|
|
2022
|
-
const
|
|
2023
|
-
const
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
info(`Would write: ${path6.relative(project.cwd, instrumentationPath)}`);
|
|
2037
|
-
console.log("---");
|
|
2038
|
-
console.log(instrumentationContent);
|
|
2039
|
-
console.log("---\n");
|
|
2040
|
-
if (envExampleContent) {
|
|
2041
|
-
info(`Would write: ${path6.relative(project.cwd, envExamplePath)}`);
|
|
2042
|
-
console.log("---");
|
|
2043
|
-
console.log(envExampleContent);
|
|
2044
|
-
console.log("---\n");
|
|
2045
|
-
}
|
|
2046
|
-
const prodPkgs2 = getProdPackages(depPlan);
|
|
2047
|
-
const devPkgs2 = getDevPackages(depPlan);
|
|
2048
|
-
if (prodPkgs2.length > 0) {
|
|
2049
|
-
const cmd = getInstallCommand(project.packageManager, prodPkgs2);
|
|
2050
|
-
info(`Would run: ${cmd}`);
|
|
2739
|
+
const newContent = renderCodeFile(codeFile);
|
|
2740
|
+
const instrumentationPath = resolveInstrumentationPath(project);
|
|
2741
|
+
const envExamplePath = path9.join(project.packageRoot, ".env.example");
|
|
2742
|
+
const existing = readFileSafe(instrumentationPath);
|
|
2743
|
+
if (existing !== null) {
|
|
2744
|
+
const parsed = parseInstrumentation(existing);
|
|
2745
|
+
if (!parsed.cliOwned && !options.force) {
|
|
2746
|
+
throw new AutotelError({
|
|
2747
|
+
type: "conflict",
|
|
2748
|
+
code: AutotelErrorCodes.E_EXISTING_CONFIG,
|
|
2749
|
+
message: `Hand-edited instrumentation file at ${instrumentationPath} (no CLI markers found)`,
|
|
2750
|
+
fix: "Pass --force to overwrite (creates a .bak backup) or remove the file",
|
|
2751
|
+
expected: { path: instrumentationPath }
|
|
2752
|
+
});
|
|
2051
2753
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2754
|
+
const addedImports = diffImportSources(
|
|
2755
|
+
parsed,
|
|
2756
|
+
[
|
|
2757
|
+
...codeFile.imports,
|
|
2758
|
+
...codeFile.backendImports,
|
|
2759
|
+
...codeFile.pluginImports,
|
|
2760
|
+
...codeFile.subscriberImports,
|
|
2761
|
+
...codeFile.loggerImports
|
|
2762
|
+
].map((i) => i.source)
|
|
2763
|
+
);
|
|
2764
|
+
const addedAuto = diffAutoInstrumentations(
|
|
2765
|
+
parsed,
|
|
2766
|
+
codeFile.autoInstrumentations
|
|
2767
|
+
);
|
|
2768
|
+
const contentChanged = existing !== newContent;
|
|
2769
|
+
if (addedImports.length === 0 && addedAuto.length === 0 && parsed.cliOwned && !contentChanged) {
|
|
2770
|
+
result.wroteFiles.push(
|
|
2771
|
+
`${path9.relative(project.cwd, instrumentationPath)} (no changes)`
|
|
2772
|
+
);
|
|
2773
|
+
} else {
|
|
2774
|
+
atomicWrite(instrumentationPath, newContent, {
|
|
2775
|
+
root: project.packageRoot,
|
|
2776
|
+
backup: true
|
|
2777
|
+
});
|
|
2778
|
+
result.wroteFiles.push(path9.relative(project.cwd, instrumentationPath));
|
|
2055
2779
|
}
|
|
2056
|
-
|
|
2780
|
+
} else {
|
|
2781
|
+
atomicWrite(instrumentationPath, newContent, {
|
|
2782
|
+
root: project.packageRoot
|
|
2783
|
+
});
|
|
2784
|
+
result.wroteFiles.push(path9.relative(project.cwd, instrumentationPath));
|
|
2057
2785
|
}
|
|
2058
|
-
|
|
2059
|
-
const
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
if (
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2786
|
+
const envVars = [];
|
|
2787
|
+
for (const p of presets) {
|
|
2788
|
+
envVars.push(...p.env.required, ...p.env.optional);
|
|
2789
|
+
}
|
|
2790
|
+
const envExampleContent = generateEnvExample(envVars);
|
|
2791
|
+
if (envExampleContent.length > 0 && !fileExists(envExamplePath)) {
|
|
2792
|
+
atomicWrite(envExamplePath, envExampleContent, {
|
|
2793
|
+
root: project.packageRoot
|
|
2794
|
+
});
|
|
2795
|
+
result.wroteFiles.push(path9.relative(project.cwd, envExamplePath));
|
|
2796
|
+
}
|
|
2797
|
+
const prod = plan.packagesToInstall.prod;
|
|
2798
|
+
const dev = plan.packagesToInstall.dev;
|
|
2799
|
+
if (prod.length > 0 || dev.length > 0) {
|
|
2800
|
+
if (options.noInstall || options.printInstallCmd) {
|
|
2801
|
+
if (prod.length > 0) {
|
|
2802
|
+
result.printedInstalls.push(
|
|
2803
|
+
getInstallCommand(project.packageManager, prod)
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2806
|
+
if (dev.length > 0) {
|
|
2807
|
+
result.printedInstalls.push(
|
|
2808
|
+
getInstallCommand(project.packageManager, dev, { dev: true })
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
} else {
|
|
2812
|
+
if (prod.length > 0) {
|
|
2813
|
+
const cmd = getInstallCommand(project.packageManager, prod);
|
|
2081
2814
|
try {
|
|
2082
2815
|
execSync(cmd, { cwd: project.packageRoot, stdio: "pipe" });
|
|
2083
|
-
|
|
2816
|
+
result.ranInstalls.push(cmd);
|
|
2084
2817
|
} catch {
|
|
2085
|
-
|
|
2086
|
-
error(`Run manually: ${cmd}`);
|
|
2818
|
+
result.installErrors.push(cmd);
|
|
2087
2819
|
}
|
|
2088
2820
|
}
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
info(`Install command (dev): ${cmd}`);
|
|
2094
|
-
} else {
|
|
2095
|
-
spinner.start("Installing dev dependencies...");
|
|
2821
|
+
if (dev.length > 0) {
|
|
2822
|
+
const cmd = getInstallCommand(project.packageManager, dev, {
|
|
2823
|
+
dev: true
|
|
2824
|
+
});
|
|
2096
2825
|
try {
|
|
2097
2826
|
execSync(cmd, { cwd: project.packageRoot, stdio: "pipe" });
|
|
2098
|
-
|
|
2827
|
+
result.ranInstalls.push(cmd);
|
|
2099
2828
|
} catch {
|
|
2100
|
-
|
|
2101
|
-
error(`Run manually: ${cmd}`);
|
|
2829
|
+
result.installErrors.push(cmd);
|
|
2102
2830
|
}
|
|
2103
2831
|
}
|
|
2104
2832
|
}
|
|
2105
|
-
} else if (options.noInstall && (prodPkgs.length > 0 || devPkgs.length > 0)) {
|
|
2106
|
-
info("Skipping installation (--no-install)");
|
|
2107
|
-
if (prodPkgs.length > 0) {
|
|
2108
|
-
const cmd = getInstallCommand(project.packageManager, prodPkgs);
|
|
2109
|
-
dim(`Run: ${cmd}`);
|
|
2110
|
-
}
|
|
2111
|
-
if (devPkgs.length > 0) {
|
|
2112
|
-
const cmd = getInstallCommand(project.packageManager, devPkgs, { dev: true });
|
|
2113
|
-
dim(`Run: ${cmd}`);
|
|
2114
|
-
}
|
|
2115
2833
|
}
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2834
|
+
return result;
|
|
2835
|
+
}
|
|
2836
|
+
function resolveInstrumentationPath(project) {
|
|
2837
|
+
const srcDir = path9.join(project.packageRoot, "src");
|
|
2838
|
+
const hasSrcDir = fs4.existsSync(srcDir) || fileExists(path9.join(project.packageRoot, "src", "index.ts")) || fileExists(path9.join(project.packageRoot, "src", "index.js"));
|
|
2839
|
+
const dir = hasSrcDir ? srcDir : project.packageRoot;
|
|
2840
|
+
const ext = project.hasTypeScript ? "mts" : "mjs";
|
|
2841
|
+
return path9.join(dir, `instrumentation.${ext}`);
|
|
2842
|
+
}
|
|
2843
|
+
function resolvePreset(slug) {
|
|
2844
|
+
for (const type of ["backend", "subscriber", "plugin", "platform"]) {
|
|
2845
|
+
const p = getPreset(type, slug);
|
|
2846
|
+
if (p !== void 0) return p;
|
|
2125
2847
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2848
|
+
return null;
|
|
2849
|
+
}
|
|
2850
|
+
function printApplySummary(args) {
|
|
2851
|
+
const { applied, plan, project } = args;
|
|
2852
|
+
if (applied.wroteFiles.length > 0) {
|
|
2853
|
+
success(`Wrote: ${applied.wroteFiles.join(", ")}`);
|
|
2130
2854
|
}
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2855
|
+
for (const cmd of applied.ranInstalls) {
|
|
2856
|
+
info(`Installed: ${cmd}`);
|
|
2857
|
+
}
|
|
2858
|
+
for (const cmd of applied.printedInstalls) {
|
|
2859
|
+
dim(`Run: ${cmd}`);
|
|
2860
|
+
}
|
|
2861
|
+
for (const cmd of applied.installErrors) {
|
|
2862
|
+
error(`Install failed \u2014 run manually: ${cmd}`);
|
|
2863
|
+
}
|
|
2864
|
+
if (plan.nextSteps.length > 0) {
|
|
2138
2865
|
console.log("\nNext steps:");
|
|
2139
|
-
for (const step of
|
|
2866
|
+
for (const step of plan.nextSteps) {
|
|
2140
2867
|
console.log(` - ${step}`);
|
|
2141
2868
|
}
|
|
2142
2869
|
}
|
|
2870
|
+
const pmInfo = project.workspace.isMonorepo ? `${project.packageManager} workspace, package root ${project.packageRoot}` : project.packageManager;
|
|
2871
|
+
console.log(`
|
|
2872
|
+
${formatPackageManagerInfo ? formatPackageManagerInfo(project.packageManager, project.lockfilePath) : pmInfo}`);
|
|
2143
2873
|
}
|
|
2144
2874
|
|
|
2145
2875
|
// src/lib/dependency-auditor.ts
|
|
@@ -2181,7 +2911,7 @@ function getAutotelInfo(packageJson) {
|
|
|
2181
2911
|
}
|
|
2182
2912
|
|
|
2183
2913
|
// src/lib/esm-checker.ts
|
|
2184
|
-
import * as
|
|
2914
|
+
import * as path10 from "path";
|
|
2185
2915
|
function checkRegisterImportOrder(content) {
|
|
2186
2916
|
const lines = content.split("\n");
|
|
2187
2917
|
let registerLine = null;
|
|
@@ -2238,7 +2968,7 @@ function checkEsmHook(project) {
|
|
|
2238
2968
|
if (result.found && !result.isFirst) {
|
|
2239
2969
|
return {
|
|
2240
2970
|
status: "warn",
|
|
2241
|
-
message: `autotel/register import found but not first in ${
|
|
2971
|
+
message: `autotel/register import found but not first in ${path10.basename(entrypoint)}:${result.lineNumber}`,
|
|
2242
2972
|
details: [
|
|
2243
2973
|
"autotel/register must be the first import for instrumentation to work",
|
|
2244
2974
|
"Move it to the top of the file, before any other imports"
|
|
@@ -2248,7 +2978,7 @@ function checkEsmHook(project) {
|
|
|
2248
2978
|
if (result.found && result.isFirst) {
|
|
2249
2979
|
return {
|
|
2250
2980
|
status: "ok",
|
|
2251
|
-
message: `autotel/register correctly imported first in ${
|
|
2981
|
+
message: `autotel/register correctly imported first in ${path10.basename(entrypoint)}`
|
|
2252
2982
|
};
|
|
2253
2983
|
}
|
|
2254
2984
|
}
|
|
@@ -2263,8 +2993,8 @@ function checkEsmHook(project) {
|
|
|
2263
2993
|
}
|
|
2264
2994
|
|
|
2265
2995
|
// src/lib/logger-checker.ts
|
|
2266
|
-
import * as
|
|
2267
|
-
import * as
|
|
2996
|
+
import * as path11 from "path";
|
|
2997
|
+
import * as fs5 from "fs";
|
|
2268
2998
|
var LOGGER_INSTRUMENTATION = {
|
|
2269
2999
|
winston: "@opentelemetry/instrumentation-winston",
|
|
2270
3000
|
bunyan: "@opentelemetry/instrumentation-bunyan",
|
|
@@ -2294,17 +3024,17 @@ function extractAutoInstrumentations(content) {
|
|
|
2294
3024
|
}
|
|
2295
3025
|
function findSourceFiles(packageRoot) {
|
|
2296
3026
|
const sourceFiles = [];
|
|
2297
|
-
const srcDir =
|
|
3027
|
+
const srcDir = path11.join(packageRoot, "src");
|
|
2298
3028
|
const dirsToCheck = [packageRoot, srcDir].filter(
|
|
2299
|
-
(dir) =>
|
|
3029
|
+
(dir) => fs5.existsSync(dir) && fs5.statSync(dir).isDirectory()
|
|
2300
3030
|
);
|
|
2301
3031
|
for (const dir of dirsToCheck) {
|
|
2302
|
-
const files =
|
|
3032
|
+
const files = fs5.readdirSync(dir, { recursive: true });
|
|
2303
3033
|
for (const file of files) {
|
|
2304
3034
|
if (typeof file !== "string") continue;
|
|
2305
|
-
const filePath =
|
|
3035
|
+
const filePath = path11.join(dir, file);
|
|
2306
3036
|
try {
|
|
2307
|
-
if (
|
|
3037
|
+
if (fs5.statSync(filePath).isFile() && /\.(ts|js|mts|mjs|tsx|jsx)$/.test(file)) {
|
|
2308
3038
|
sourceFiles.push(filePath);
|
|
2309
3039
|
}
|
|
2310
3040
|
} catch {
|
|
@@ -3028,11 +3758,11 @@ ${presetType} presets:
|
|
|
3028
3758
|
}
|
|
3029
3759
|
|
|
3030
3760
|
// src/commands/codemod-trace.ts
|
|
3031
|
-
import * as
|
|
3761
|
+
import * as path13 from "path";
|
|
3032
3762
|
import { glob as glob2 } from "glob";
|
|
3033
3763
|
|
|
3034
3764
|
// src/lib/codemod-trace.ts
|
|
3035
|
-
import * as
|
|
3765
|
+
import * as path12 from "path";
|
|
3036
3766
|
import { Project, SyntaxKind } from "ts-morph";
|
|
3037
3767
|
var TRACE_IMPORT_MODULE = "autotel";
|
|
3038
3768
|
function hasTraceImport(sourceFile) {
|
|
@@ -3055,8 +3785,8 @@ function addTraceImport(sourceFile) {
|
|
|
3055
3785
|
});
|
|
3056
3786
|
}
|
|
3057
3787
|
function expandNamePattern(pattern, name, filePath, cwd) {
|
|
3058
|
-
const file =
|
|
3059
|
-
const relPath =
|
|
3788
|
+
const file = path12.basename(filePath, path12.extname(filePath));
|
|
3789
|
+
const relPath = path12.relative(cwd, filePath).replaceAll("\\", "/");
|
|
3060
3790
|
return pattern.replaceAll("{name}", name).replaceAll("{file}", file).replaceAll("{path}", relPath);
|
|
3061
3791
|
}
|
|
3062
3792
|
function getSpanName(name, filePath, options) {
|
|
@@ -3292,28 +4022,28 @@ var GLOB_META = /[*?[\]]/;
|
|
|
3292
4022
|
async function resolveCodemodFiles(pathArg, cwd) {
|
|
3293
4023
|
const isGlob = GLOB_META.test(pathArg);
|
|
3294
4024
|
if (!isGlob) {
|
|
3295
|
-
const absolute =
|
|
4025
|
+
const absolute = path13.isAbsolute(pathArg) ? pathArg : path13.resolve(cwd, pathArg);
|
|
3296
4026
|
if (fileExists(absolute)) {
|
|
3297
|
-
const ext =
|
|
4027
|
+
const ext = path13.extname(absolute);
|
|
3298
4028
|
if (CODEMOD_EXTENSIONS.has(ext) && !absolute.endsWith(".d.ts")) {
|
|
3299
4029
|
return [absolute];
|
|
3300
4030
|
}
|
|
3301
4031
|
return [];
|
|
3302
4032
|
}
|
|
3303
4033
|
}
|
|
3304
|
-
const pattern =
|
|
4034
|
+
const pattern = path13.isAbsolute(pathArg) ? pathArg : path13.join(cwd, pathArg);
|
|
3305
4035
|
const matches = await glob2(pattern, {
|
|
3306
4036
|
cwd,
|
|
3307
4037
|
absolute: true,
|
|
3308
4038
|
ignore: ["**/node_modules/**", "**/*.d.ts"]
|
|
3309
4039
|
});
|
|
3310
4040
|
return matches.filter((f) => {
|
|
3311
|
-
const ext =
|
|
4041
|
+
const ext = path13.extname(f);
|
|
3312
4042
|
return CODEMOD_EXTENSIONS.has(ext) && !f.endsWith(".d.ts");
|
|
3313
4043
|
});
|
|
3314
4044
|
}
|
|
3315
4045
|
async function runCodemodTrace(options) {
|
|
3316
|
-
const { path: pathArg, cwd, dryRun, namePattern, skip, printFiles, verbose
|
|
4046
|
+
const { path: pathArg, cwd, dryRun, namePattern, skip, printFiles, verbose, quiet } = options;
|
|
3317
4047
|
const files = await resolveCodemodFiles(pathArg, cwd);
|
|
3318
4048
|
if (files.length === 0) {
|
|
3319
4049
|
if (!quiet) {
|
|
@@ -3329,7 +4059,7 @@ async function runCodemodTrace(options) {
|
|
|
3329
4059
|
for (const filePath of files) {
|
|
3330
4060
|
const content = readFileSafe(filePath);
|
|
3331
4061
|
if (content === null) {
|
|
3332
|
-
if (
|
|
4062
|
+
if (verbose) dim(`Skip ${filePath} (unreadable)`);
|
|
3333
4063
|
continue;
|
|
3334
4064
|
}
|
|
3335
4065
|
let result;
|
|
@@ -3339,17 +4069,17 @@ async function runCodemodTrace(options) {
|
|
|
3339
4069
|
if (!quiet) {
|
|
3340
4070
|
error(`Failed to transform ${filePath}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
3341
4071
|
}
|
|
3342
|
-
if (
|
|
4072
|
+
if (verbose && error2 instanceof Error && error2.stack) dim(error2.stack);
|
|
3343
4073
|
process.exitCode = 1;
|
|
3344
4074
|
continue;
|
|
3345
4075
|
}
|
|
3346
|
-
const relativePath =
|
|
4076
|
+
const relativePath = path13.relative(cwd, filePath);
|
|
3347
4077
|
if (result.changed) {
|
|
3348
4078
|
totalWrapped += result.wrappedCount;
|
|
3349
4079
|
totalChanged += 1;
|
|
3350
4080
|
if (!dryRun) {
|
|
3351
|
-
const
|
|
3352
|
-
|
|
4081
|
+
const fs7 = await import("fs");
|
|
4082
|
+
fs7.writeFileSync(filePath, result.modified, "utf8");
|
|
3353
4083
|
}
|
|
3354
4084
|
}
|
|
3355
4085
|
const showSummary = printFiles || dryRun || result.changed;
|
|
@@ -3368,6 +4098,369 @@ async function runCodemodTrace(options) {
|
|
|
3368
4098
|
}
|
|
3369
4099
|
}
|
|
3370
4100
|
|
|
4101
|
+
// src/commands/schema.ts
|
|
4102
|
+
import * as fs6 from "fs";
|
|
4103
|
+
import * as path14 from "path";
|
|
4104
|
+
import { fileURLToPath } from "url";
|
|
4105
|
+
|
|
4106
|
+
// src/lib/manifest.ts
|
|
4107
|
+
var GLOBAL_FLAGS = [
|
|
4108
|
+
{
|
|
4109
|
+
name: "--cwd",
|
|
4110
|
+
takesValue: true,
|
|
4111
|
+
description: "Target directory (default: current working directory)"
|
|
4112
|
+
},
|
|
4113
|
+
{ name: "--verbose", description: "Show detailed output" },
|
|
4114
|
+
{ name: "--quiet", description: "Only show warnings and errors" }
|
|
4115
|
+
];
|
|
4116
|
+
var AGENT_FLAGS = [
|
|
4117
|
+
{
|
|
4118
|
+
name: "--json",
|
|
4119
|
+
description: "Emit machine-readable JSON instead of human output"
|
|
4120
|
+
},
|
|
4121
|
+
{
|
|
4122
|
+
name: "--output-file",
|
|
4123
|
+
takesValue: true,
|
|
4124
|
+
description: "Persist the first JSON payload to this path"
|
|
4125
|
+
},
|
|
4126
|
+
{
|
|
4127
|
+
name: "--no-secrets-in-output",
|
|
4128
|
+
description: "Redact secret-shaped values (also via AUTOTEL_NO_SECRETS=1 / AGENT_SANDBOX=1)"
|
|
4129
|
+
},
|
|
4130
|
+
{
|
|
4131
|
+
name: "--no-interactive",
|
|
4132
|
+
description: "Never prompt; fail fast if input is required"
|
|
4133
|
+
}
|
|
4134
|
+
];
|
|
4135
|
+
var COMMANDS = [
|
|
4136
|
+
{
|
|
4137
|
+
name: "init",
|
|
4138
|
+
description: "Initialise autotel in your project",
|
|
4139
|
+
flags: [
|
|
4140
|
+
...GLOBAL_FLAGS,
|
|
4141
|
+
...AGENT_FLAGS,
|
|
4142
|
+
{ name: "--dry-run", description: "Print what would be done; write nothing" },
|
|
4143
|
+
{ name: "--no-install", description: "Generate files only, skip package installation" },
|
|
4144
|
+
{ name: "--print-install-cmd", description: "Output install command without running it" },
|
|
4145
|
+
{ name: "--yes", alias: "-y", description: "Auto-apply detected items; no prompts" },
|
|
4146
|
+
{ name: "--preset", takesValue: true, description: "Use a named quick preset" },
|
|
4147
|
+
{ name: "--force", description: "Overwrite existing config (creates backup)" },
|
|
4148
|
+
{ name: "--workspace-root", description: "Install at workspace root, not package root" },
|
|
4149
|
+
{ name: "--no-detect", description: "Skip auto-detection of installed deps" },
|
|
4150
|
+
{ name: "--detect-only", description: "Run detection and print the proposal; write nothing" },
|
|
4151
|
+
{ name: "--plan", takesValue: true, description: "Read a pre-built InitPlan JSON from this path and apply it" },
|
|
4152
|
+
{ name: "--input", takesValue: true, description: "Read InitPlan JSON from stdin (-) or a file" },
|
|
4153
|
+
{ name: "--scan-env", description: "Consent to reading uncommitted .env files for backend detection" }
|
|
4154
|
+
],
|
|
4155
|
+
mutating: true,
|
|
4156
|
+
network: true,
|
|
4157
|
+
writesFiles: true,
|
|
4158
|
+
supportsDryRun: true,
|
|
4159
|
+
requiresPackageJson: true,
|
|
4160
|
+
mayReadEnv: true,
|
|
4161
|
+
supportsJson: true,
|
|
4162
|
+
examples: [
|
|
4163
|
+
{ description: "Interactive setup with detection", command: "autotel init" },
|
|
4164
|
+
{ description: "Non-interactive, apply all detected items", command: "autotel init --yes" },
|
|
4165
|
+
{ description: "Preview as JSON without writing", command: "autotel init --json --dry-run" },
|
|
4166
|
+
{ description: "Detection only", command: "autotel init --detect-only --json" }
|
|
4167
|
+
]
|
|
4168
|
+
},
|
|
4169
|
+
{
|
|
4170
|
+
name: "doctor",
|
|
4171
|
+
description: "Run diagnostics on your autotel setup",
|
|
4172
|
+
flags: [
|
|
4173
|
+
...GLOBAL_FLAGS,
|
|
4174
|
+
...AGENT_FLAGS,
|
|
4175
|
+
{ name: "--fix", description: "Auto-fix resolvable issues" },
|
|
4176
|
+
{ name: "--list-checks", description: "List all available checks" },
|
|
4177
|
+
{ name: "--env-file", takesValue: true, description: "Path to env file to check" }
|
|
4178
|
+
],
|
|
4179
|
+
mutating: false,
|
|
4180
|
+
network: false,
|
|
4181
|
+
writesFiles: false,
|
|
4182
|
+
supportsDryRun: false,
|
|
4183
|
+
requiresPackageJson: true,
|
|
4184
|
+
mayReadEnv: true,
|
|
4185
|
+
supportsJson: true
|
|
4186
|
+
},
|
|
4187
|
+
{
|
|
4188
|
+
name: "add",
|
|
4189
|
+
description: "Add a backend, subscriber, plugin, or platform incrementally",
|
|
4190
|
+
args: [
|
|
4191
|
+
{ name: "type", required: false, description: "Preset type" },
|
|
4192
|
+
{ name: "name", required: false, description: "Preset name" }
|
|
4193
|
+
],
|
|
4194
|
+
flags: [
|
|
4195
|
+
...GLOBAL_FLAGS,
|
|
4196
|
+
...AGENT_FLAGS,
|
|
4197
|
+
{ name: "--list", description: "List available presets for the given type" },
|
|
4198
|
+
{ name: "--dry-run", description: "Print what would be done" },
|
|
4199
|
+
{ name: "--no-install", description: "Generate files only" },
|
|
4200
|
+
{ name: "--print-install-cmd", description: "Output install command without running" },
|
|
4201
|
+
{ name: "--yes", alias: "-y", description: "Accept defaults" },
|
|
4202
|
+
{ name: "--force", description: "Overwrite non-CLI-owned config" },
|
|
4203
|
+
{ name: "--workspace-root", description: "Install at workspace root" }
|
|
4204
|
+
],
|
|
4205
|
+
mutating: true,
|
|
4206
|
+
network: true,
|
|
4207
|
+
writesFiles: true,
|
|
4208
|
+
supportsDryRun: true,
|
|
4209
|
+
requiresPackageJson: true,
|
|
4210
|
+
mayReadEnv: false,
|
|
4211
|
+
supportsJson: true
|
|
4212
|
+
},
|
|
4213
|
+
{
|
|
4214
|
+
name: "codemod trace",
|
|
4215
|
+
description: "Wrap functions in trace() with span name from function/variable/method name",
|
|
4216
|
+
args: [{ name: "path", required: true, description: "File path or glob" }],
|
|
4217
|
+
flags: [
|
|
4218
|
+
...GLOBAL_FLAGS,
|
|
4219
|
+
{ name: "--dry-run", description: "Print changes without writing files" },
|
|
4220
|
+
{ name: "--name-pattern", takesValue: true, description: "Span name template" },
|
|
4221
|
+
{ name: "--skip", takesValue: true, description: "Skip functions whose name matches (repeatable)" },
|
|
4222
|
+
{ name: "--print-files", description: "Print per-file summary" }
|
|
4223
|
+
],
|
|
4224
|
+
mutating: true,
|
|
4225
|
+
network: false,
|
|
4226
|
+
writesFiles: true,
|
|
4227
|
+
supportsDryRun: true,
|
|
4228
|
+
requiresPackageJson: false,
|
|
4229
|
+
mayReadEnv: false,
|
|
4230
|
+
supportsJson: false
|
|
4231
|
+
},
|
|
4232
|
+
{
|
|
4233
|
+
name: "schema",
|
|
4234
|
+
description: "Print the CLI manifest as JSON (agent discovery)",
|
|
4235
|
+
flags: [
|
|
4236
|
+
{ name: "--json", description: "Always JSON (this command is JSON-only)" }
|
|
4237
|
+
],
|
|
4238
|
+
mutating: false,
|
|
4239
|
+
network: false,
|
|
4240
|
+
writesFiles: false,
|
|
4241
|
+
supportsDryRun: false,
|
|
4242
|
+
requiresPackageJson: false,
|
|
4243
|
+
mayReadEnv: false,
|
|
4244
|
+
supportsJson: true
|
|
4245
|
+
},
|
|
4246
|
+
{
|
|
4247
|
+
name: "schema errors",
|
|
4248
|
+
description: "Print error envelope shape + AUTOTEL_E_* enum",
|
|
4249
|
+
flags: [{ name: "--json", description: "Always JSON" }],
|
|
4250
|
+
mutating: false,
|
|
4251
|
+
network: false,
|
|
4252
|
+
writesFiles: false,
|
|
4253
|
+
supportsDryRun: false,
|
|
4254
|
+
requiresPackageJson: false,
|
|
4255
|
+
mayReadEnv: false,
|
|
4256
|
+
supportsJson: true
|
|
4257
|
+
},
|
|
4258
|
+
{
|
|
4259
|
+
name: "schema outputs",
|
|
4260
|
+
description: "Print JSON output shapes per command",
|
|
4261
|
+
flags: [{ name: "--json", description: "Always JSON" }],
|
|
4262
|
+
mutating: false,
|
|
4263
|
+
network: false,
|
|
4264
|
+
writesFiles: false,
|
|
4265
|
+
supportsDryRun: false,
|
|
4266
|
+
requiresPackageJson: false,
|
|
4267
|
+
mayReadEnv: false,
|
|
4268
|
+
supportsJson: true
|
|
4269
|
+
},
|
|
4270
|
+
{
|
|
4271
|
+
name: "commands",
|
|
4272
|
+
description: "Print compact tool-style command listing",
|
|
4273
|
+
flags: [{ name: "--json", description: "Always JSON" }],
|
|
4274
|
+
mutating: false,
|
|
4275
|
+
network: false,
|
|
4276
|
+
writesFiles: false,
|
|
4277
|
+
supportsDryRun: false,
|
|
4278
|
+
requiresPackageJson: false,
|
|
4279
|
+
mayReadEnv: false,
|
|
4280
|
+
supportsJson: true
|
|
4281
|
+
},
|
|
4282
|
+
{
|
|
4283
|
+
name: "examples",
|
|
4284
|
+
description: "Print copy-pasteable examples for a command",
|
|
4285
|
+
args: [
|
|
4286
|
+
{ name: "command", required: false, description: "Command name (omit for all)" }
|
|
4287
|
+
],
|
|
4288
|
+
flags: [{ name: "--json", description: "Emit JSON" }],
|
|
4289
|
+
mutating: false,
|
|
4290
|
+
network: false,
|
|
4291
|
+
writesFiles: false,
|
|
4292
|
+
supportsDryRun: false,
|
|
4293
|
+
requiresPackageJson: false,
|
|
4294
|
+
mayReadEnv: false,
|
|
4295
|
+
supportsJson: true
|
|
4296
|
+
},
|
|
4297
|
+
{
|
|
4298
|
+
name: "version",
|
|
4299
|
+
description: "Print version info",
|
|
4300
|
+
flags: [{ name: "--json", description: "Emit JSON" }],
|
|
4301
|
+
mutating: false,
|
|
4302
|
+
network: false,
|
|
4303
|
+
writesFiles: false,
|
|
4304
|
+
supportsDryRun: false,
|
|
4305
|
+
requiresPackageJson: false,
|
|
4306
|
+
mayReadEnv: false,
|
|
4307
|
+
supportsJson: true
|
|
4308
|
+
}
|
|
4309
|
+
];
|
|
4310
|
+
function getCommand(name) {
|
|
4311
|
+
return COMMANDS.find((c) => c.name === name);
|
|
4312
|
+
}
|
|
4313
|
+
var ERROR_CATALOGUE = [
|
|
4314
|
+
{ code: AutotelErrorCodes.E_NO_PACKAGE_JSON, type: "environment", description: "No package.json found in cwd or ancestors" },
|
|
4315
|
+
{ code: AutotelErrorCodes.E_UNKNOWN_PRESET, type: "validation", description: "Preset slug not in registry" },
|
|
4316
|
+
{ code: AutotelErrorCodes.E_INVALID_PLAN, type: "validation", description: "Plan file failed schema validation" },
|
|
4317
|
+
{ code: AutotelErrorCodes.E_INVALID_INPUT, type: "validation", description: "Stdin or --input payload invalid" },
|
|
4318
|
+
{ code: AutotelErrorCodes.E_INVALID_FLAG, type: "validation", description: "Conflicting or invalid flag combination" },
|
|
4319
|
+
{ code: AutotelErrorCodes.E_NO_WORKSPACE_PACKAGES, type: "environment", description: "Workspace root has no detectable packages" },
|
|
4320
|
+
{ code: AutotelErrorCodes.E_ENV_CONSENT_REQUIRED, type: "environment", description: ".env file present; consent required (pass --scan-env or run interactively)" },
|
|
4321
|
+
{ code: AutotelErrorCodes.E_EXISTING_CONFIG, type: "conflict", description: "Existing instrumentation config; use --force or run with merge" },
|
|
4322
|
+
{ code: AutotelErrorCodes.E_AMBIGUOUS_LOGGER, type: "conflict", description: "Multiple loggers detected and selection could not be inferred" },
|
|
4323
|
+
{ code: AutotelErrorCodes.E_INSTALL_FAILED, type: "install", description: "Package manager install command failed" },
|
|
4324
|
+
{ code: AutotelErrorCodes.E_WRITE_FAILED, type: "io", description: "Failed to write a file" },
|
|
4325
|
+
{ code: AutotelErrorCodes.E_READ_FAILED, type: "io", description: "Failed to read a file" },
|
|
4326
|
+
{ code: AutotelErrorCodes.E_UNKNOWN, type: "runtime", description: "Unexpected error" }
|
|
4327
|
+
];
|
|
4328
|
+
|
|
4329
|
+
// src/commands/schema.ts
|
|
4330
|
+
function configure(opts) {
|
|
4331
|
+
configureJsonOutput({
|
|
4332
|
+
outputFile: opts.outputFile,
|
|
4333
|
+
noSecrets: opts.noSecrets
|
|
4334
|
+
});
|
|
4335
|
+
}
|
|
4336
|
+
function readSelfVersion() {
|
|
4337
|
+
try {
|
|
4338
|
+
const here = path14.dirname(fileURLToPath(import.meta.url));
|
|
4339
|
+
let dir = here;
|
|
4340
|
+
for (let i = 0; i < 5; i++) {
|
|
4341
|
+
const candidate = path14.join(dir, "package.json");
|
|
4342
|
+
if (fs6.existsSync(candidate)) {
|
|
4343
|
+
const pkg = JSON.parse(fs6.readFileSync(candidate, "utf8"));
|
|
4344
|
+
if (pkg.name === "autotel-cli") return String(pkg.version ?? "0.0.0");
|
|
4345
|
+
}
|
|
4346
|
+
dir = path14.dirname(dir);
|
|
4347
|
+
}
|
|
4348
|
+
} catch {
|
|
4349
|
+
}
|
|
4350
|
+
return "0.0.0";
|
|
4351
|
+
}
|
|
4352
|
+
function runSchema(opts) {
|
|
4353
|
+
configure(opts);
|
|
4354
|
+
printJson({
|
|
4355
|
+
ok: true,
|
|
4356
|
+
command: "autotel schema",
|
|
4357
|
+
version: readSelfVersion(),
|
|
4358
|
+
commands: COMMANDS
|
|
4359
|
+
});
|
|
4360
|
+
}
|
|
4361
|
+
function runSchemaErrors(opts) {
|
|
4362
|
+
configure(opts);
|
|
4363
|
+
printJson({
|
|
4364
|
+
ok: true,
|
|
4365
|
+
command: "autotel schema errors",
|
|
4366
|
+
envelope: {
|
|
4367
|
+
ok: false,
|
|
4368
|
+
command: "<command>",
|
|
4369
|
+
error: {
|
|
4370
|
+
type: "<one of: validation, environment, auth, conflict, install, io, runtime>",
|
|
4371
|
+
code: "<AUTOTEL_E_*>",
|
|
4372
|
+
message: "human-readable message",
|
|
4373
|
+
retryable: false,
|
|
4374
|
+
fix: "optional remediation hint",
|
|
4375
|
+
expected: { "<key>": "<value>" },
|
|
4376
|
+
suggestions: ["optional follow-up commands"]
|
|
4377
|
+
}
|
|
4378
|
+
},
|
|
4379
|
+
codes: ERROR_CATALOGUE,
|
|
4380
|
+
exitCodes: {
|
|
4381
|
+
"0": "success",
|
|
4382
|
+
"1": "runtime / unexpected failure",
|
|
4383
|
+
"2": "validation / conflict / refusal"
|
|
4384
|
+
}
|
|
4385
|
+
});
|
|
4386
|
+
}
|
|
4387
|
+
function runSchemaOutputs(opts) {
|
|
4388
|
+
configure(opts);
|
|
4389
|
+
printJson({
|
|
4390
|
+
ok: true,
|
|
4391
|
+
command: "autotel schema outputs",
|
|
4392
|
+
outputs: {
|
|
4393
|
+
"autotel init --json": {
|
|
4394
|
+
ok: "boolean",
|
|
4395
|
+
command: "string",
|
|
4396
|
+
detected: {
|
|
4397
|
+
packages: 'array of { name, version, resolution: "target" | "workspace-root" }',
|
|
4398
|
+
logger: '"pino" | "winston" | "bunyan" | null',
|
|
4399
|
+
backend: '{ source: "env" | "wrangler" | "deps" | "prompt" | "default", value: string }',
|
|
4400
|
+
platform: '"cloudflare" | "aws-lambda" | "edge" | null'
|
|
4401
|
+
},
|
|
4402
|
+
plan: {
|
|
4403
|
+
presets: "string[] (slugs)",
|
|
4404
|
+
packagesToInstall: { prod: "string[]", dev: "string[]" },
|
|
4405
|
+
filesToWrite: 'array of { path, action: "create" | "merge" | "skip" }',
|
|
4406
|
+
envVars: "array of { name, sensitive, action }"
|
|
4407
|
+
},
|
|
4408
|
+
nextSteps: "string[]"
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
});
|
|
4412
|
+
}
|
|
4413
|
+
function runCommandsListing(opts) {
|
|
4414
|
+
configure(opts);
|
|
4415
|
+
const compact = COMMANDS.map((c) => ({
|
|
4416
|
+
name: c.name,
|
|
4417
|
+
description: c.description,
|
|
4418
|
+
mutating: c.mutating,
|
|
4419
|
+
network: c.network,
|
|
4420
|
+
writesFiles: c.writesFiles,
|
|
4421
|
+
supportsDryRun: c.supportsDryRun,
|
|
4422
|
+
supportsJson: c.supportsJson
|
|
4423
|
+
}));
|
|
4424
|
+
printJson({ ok: true, command: "autotel commands", commands: compact });
|
|
4425
|
+
}
|
|
4426
|
+
function runExamples(name, opts) {
|
|
4427
|
+
configure(opts);
|
|
4428
|
+
if (name === void 0) {
|
|
4429
|
+
const all = COMMANDS.filter((c) => c.examples && c.examples.length > 0).map(
|
|
4430
|
+
(c) => ({ command: c.name, examples: c.examples })
|
|
4431
|
+
);
|
|
4432
|
+
printJson({ ok: true, command: "autotel examples", examples: all });
|
|
4433
|
+
return;
|
|
4434
|
+
}
|
|
4435
|
+
const cmd = getCommand(name);
|
|
4436
|
+
if (!cmd) {
|
|
4437
|
+
throw new AutotelError({
|
|
4438
|
+
type: "validation",
|
|
4439
|
+
code: AutotelErrorCodes.E_INVALID_INPUT,
|
|
4440
|
+
message: `Unknown command: ${name}`,
|
|
4441
|
+
expected: { command: COMMANDS.map((c) => c.name) },
|
|
4442
|
+
fix: "Run `autotel commands --json` to see available commands"
|
|
4443
|
+
});
|
|
4444
|
+
}
|
|
4445
|
+
printJson({
|
|
4446
|
+
ok: true,
|
|
4447
|
+
command: "autotel examples",
|
|
4448
|
+
target: cmd.name,
|
|
4449
|
+
examples: cmd.examples ?? []
|
|
4450
|
+
});
|
|
4451
|
+
}
|
|
4452
|
+
function runVersion(opts) {
|
|
4453
|
+
configure(opts);
|
|
4454
|
+
printJson({
|
|
4455
|
+
ok: true,
|
|
4456
|
+
command: "autotel version",
|
|
4457
|
+
version: readSelfVersion(),
|
|
4458
|
+
node: process.version,
|
|
4459
|
+
platform: process.platform,
|
|
4460
|
+
arch: process.arch
|
|
4461
|
+
});
|
|
4462
|
+
}
|
|
4463
|
+
|
|
3371
4464
|
// src/cli.ts
|
|
3372
4465
|
function createProgram() {
|
|
3373
4466
|
const program = new Command();
|
|
@@ -3375,18 +4468,27 @@ function createProgram() {
|
|
|
3375
4468
|
const addGlobalOptions = (cmd) => {
|
|
3376
4469
|
return cmd.option("--cwd <path>", "Target directory", process.cwd()).option("--verbose", "Show detailed output").option("--quiet", "Only show warnings and errors");
|
|
3377
4470
|
};
|
|
3378
|
-
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) => {
|
|
4471
|
+
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").option("--no-detect", "Skip auto-detection of installed deps").option("--detect-only", "Run detection, print the plan, write nothing").option("--plan <path>", "Apply a pre-built InitPlan JSON file").option("--input <path>", "Read InitPlan JSON from stdin (-) or a file").option("--scan-env", "Consent to reading .env / .env.local for backend detection").option("--json", "Emit machine-readable JSON").option("--output-file <path>", "Persist JSON output to this file").option("--no-secrets-in-output", "Redact secret-shaped values").option("--no-interactive", "Never prompt; fail if input would be required").action(async (opts) => {
|
|
3379
4472
|
const options = {
|
|
3380
4473
|
cwd: opts.cwd ?? process.cwd(),
|
|
3381
4474
|
dryRun: opts.dryRun ?? false,
|
|
3382
|
-
noInstall: opts.
|
|
4475
|
+
noInstall: opts.install === false,
|
|
3383
4476
|
printInstallCmd: opts.printInstallCmd ?? false,
|
|
3384
4477
|
verbose: opts.verbose ?? false,
|
|
3385
4478
|
quiet: opts.quiet ?? false,
|
|
3386
4479
|
workspaceRoot: opts.workspaceRoot ?? false,
|
|
3387
4480
|
yes: opts.yes ?? false,
|
|
3388
4481
|
preset: opts.preset,
|
|
3389
|
-
force: opts.force ?? false
|
|
4482
|
+
force: opts.force ?? false,
|
|
4483
|
+
noDetect: opts.detect === false,
|
|
4484
|
+
detectOnly: opts.detectOnly ?? false,
|
|
4485
|
+
plan: opts.plan,
|
|
4486
|
+
input: opts.input,
|
|
4487
|
+
scanEnv: opts.scanEnv ?? false,
|
|
4488
|
+
json: opts.json ?? false,
|
|
4489
|
+
outputFile: opts.outputFile,
|
|
4490
|
+
noSecrets: opts.secretsInOutput === false,
|
|
4491
|
+
noInteractive: opts.interactive === false
|
|
3390
4492
|
};
|
|
3391
4493
|
if (options.dryRun) {
|
|
3392
4494
|
options.noInstall = true;
|
|
@@ -3457,6 +4559,30 @@ function createProgram() {
|
|
|
3457
4559
|
codemodCmd.addCommand(traceCmd);
|
|
3458
4560
|
addGlobalOptions(codemodCmd);
|
|
3459
4561
|
program.addCommand(codemodCmd);
|
|
4562
|
+
const schemaCmd = new Command("schema").description("Print the CLI manifest as JSON (agent discovery)").option("--output-file <path>", "Persist JSON to a file").option("--no-secrets-in-output", "Redact secret-shaped values").action((opts) => {
|
|
4563
|
+
runSchema({ outputFile: opts.outputFile, noSecrets: opts.secretsInOutput === false });
|
|
4564
|
+
});
|
|
4565
|
+
const schemaErrorsCmd = new Command("errors").description("Print error envelope shape + AUTOTEL_E_* codes").option("--output-file <path>", "Persist JSON to a file").action((opts) => {
|
|
4566
|
+
runSchemaErrors({ outputFile: opts.outputFile });
|
|
4567
|
+
});
|
|
4568
|
+
const schemaOutputsCmd = new Command("outputs").description("Print JSON output shapes per command").option("--output-file <path>", "Persist JSON to a file").action((opts) => {
|
|
4569
|
+
runSchemaOutputs({ outputFile: opts.outputFile });
|
|
4570
|
+
});
|
|
4571
|
+
schemaCmd.addCommand(schemaErrorsCmd);
|
|
4572
|
+
schemaCmd.addCommand(schemaOutputsCmd);
|
|
4573
|
+
program.addCommand(schemaCmd);
|
|
4574
|
+
const commandsCmd = new Command("commands").description("Print compact tool-style listing of commands").option("--output-file <path>", "Persist JSON to a file").action((opts) => {
|
|
4575
|
+
runCommandsListing({ outputFile: opts.outputFile });
|
|
4576
|
+
});
|
|
4577
|
+
program.addCommand(commandsCmd);
|
|
4578
|
+
const examplesCmd = new Command("examples").description("Print copy-pasteable examples for a command").argument("[command]", "Command name (omit for all)").option("--output-file <path>", "Persist JSON to a file").action((name, opts) => {
|
|
4579
|
+
runExamples(name, { outputFile: opts.outputFile });
|
|
4580
|
+
});
|
|
4581
|
+
program.addCommand(examplesCmd);
|
|
4582
|
+
const versionCmd = new Command("version").description("Print version info as JSON").option("--output-file <path>", "Persist JSON to a file").action((opts) => {
|
|
4583
|
+
runVersion({ outputFile: opts.outputFile });
|
|
4584
|
+
});
|
|
4585
|
+
program.addCommand(versionCmd);
|
|
3460
4586
|
return program;
|
|
3461
4587
|
}
|
|
3462
4588
|
async function run() {
|
|
@@ -3465,8 +4591,24 @@ async function run() {
|
|
|
3465
4591
|
}
|
|
3466
4592
|
|
|
3467
4593
|
// src/index.ts
|
|
4594
|
+
function jsonModeRequested() {
|
|
4595
|
+
return process.argv.includes("--json");
|
|
4596
|
+
}
|
|
3468
4597
|
run().catch((error2) => {
|
|
3469
|
-
|
|
3470
|
-
|
|
4598
|
+
const err = toAutotelError(error2);
|
|
4599
|
+
const isJson = jsonModeRequested() || // schema/commands/examples/version are JSON-only
|
|
4600
|
+
/^(schema|commands|examples|version)\b/.test(
|
|
4601
|
+
process.argv.slice(2).join(" ")
|
|
4602
|
+
);
|
|
4603
|
+
if (isJson) {
|
|
4604
|
+
printJson(err.toEnvelope());
|
|
4605
|
+
} else {
|
|
4606
|
+
process.stderr.write(
|
|
4607
|
+
`Error [${err.code}]: ${err.message}
|
|
4608
|
+
` + (err.fix !== void 0 ? `Fix: ${err.fix}
|
|
4609
|
+
` : "")
|
|
4610
|
+
);
|
|
4611
|
+
}
|
|
4612
|
+
process.exit(exitCodeForError(err));
|
|
3471
4613
|
});
|
|
3472
4614
|
//# sourceMappingURL=index.js.map
|