{"version":3,"file":"tracing.mjs","names":[],"sources":["../../src/extension/tracing.ts"],"sourcesContent":["import { defineInterceptor } from '#src/core/interceptors.ts';\nimport { thenMaybe } from '#src/core/results.ts';\nimport type { AnyPadroneBuilder, CommandTypesBase } from '#src/types/index.ts';\nimport type { WithInterceptor } from '#src/util/type-utils.ts';\n\n// ---------------------------------------------------------------------------\n// Types — minimal OTEL-compatible interfaces so we don't hard-depend on\n// `@opentelemetry/api`. Users pass their real Tracer / Span instances.\n// ---------------------------------------------------------------------------\n\n/** Minimal subset of OTEL `SpanStatusCode`. */\ntype SpanStatusCode = 0 | 1 | 2;\n\n/** Minimal subset of OTEL `SpanStatus`. */\ntype SpanStatus = { code: SpanStatusCode; message?: string };\n\n/** Minimal subset of OTEL `Span`. */\nexport interface OtelSpan {\n  setAttribute(key: string, value: string | number | boolean): this;\n  addEvent(name: string, attributes?: Record<string, string | number | boolean>): this;\n  setStatus(status: SpanStatus): this;\n  recordException(error: unknown): this;\n  end(): void;\n  spanContext(): { traceId: string; spanId: string };\n}\n\n/** Minimal subset of OTEL `Tracer`. */\nexport interface OtelTracer {\n  startSpan(name: string): OtelSpan;\n}\n\n/** Minimal subset of OTEL `TracerProvider`. */\nexport interface OtelTracerProvider {\n  getTracer(name: string, version?: string): OtelTracer;\n}\n\n/** Tracing handle injected into the command context. */\nexport type PadroneTracer = {\n  /** The underlying OTEL tracer. */\n  tracer: OtelTracer;\n  /** Root span covering the full command execution. */\n  rootSpan: OtelSpan;\n  /** Run `fn` inside a child span that is automatically ended when `fn` returns (or rejects). */\n  span: <T>(name: string, fn: (span: OtelSpan) => T) => T;\n};\n\n/** Configuration for the tracing extension. */\nexport type PadroneTracingConfig = {\n  /** OTEL `TracerProvider`. Required — there is no global fallback. */\n  provider: OtelTracerProvider;\n  /** Service / tracer name. Defaults to the CLI program name. */\n  serviceName?: string;\n};\n\n/** Builder/program type after applying `padroneTracing()`. Adds `{ tracing: PadroneTracer }` to the command context. */\nexport type WithTracing<T> = WithInterceptor<T, { tracing: PadroneTracer }>;\n\n// ---------------------------------------------------------------------------\n// Interceptor\n// ---------------------------------------------------------------------------\n\nconst OTEL_ERROR: SpanStatusCode = 2;\n\ntype ResolvedTracingConfig = { provider: OtelTracerProvider; serviceName: string | undefined };\n\nfunction tracingInterceptor(config: ResolvedTracingConfig) {\n  return defineInterceptor({ id: 'padrone:tracing', name: 'padrone:tracing', order: -1 }, () => {\n    let rootSpan: OtelSpan;\n    let tracer: OtelTracer;\n\n    return {\n      start(ctx, next) {\n        tracer = config.provider.getTracer(config.serviceName ?? ctx.command.name);\n        return next();\n      },\n\n      execute(ctx, next) {\n        rootSpan = tracer.startSpan(`cli ${ctx.command.name}`);\n\n        const padroneTracer: PadroneTracer = {\n          tracer,\n          rootSpan,\n          span(name, fn) {\n            const child = tracer.startSpan(name);\n            try {\n              const result = fn(child);\n              if (result != null && typeof (result as any).then === 'function') {\n                return (result as any).then(\n                  (v: any) => {\n                    child.end();\n                    return v;\n                  },\n                  (err: unknown) => {\n                    child.recordException(err);\n                    child.setStatus({ code: OTEL_ERROR });\n                    child.end();\n                    throw err;\n                  },\n                );\n              }\n              child.end();\n              return result;\n            } catch (err) {\n              child.recordException(err);\n              child.setStatus({ code: OTEL_ERROR });\n              child.end();\n              throw err;\n            }\n          },\n        };\n\n        return next({ context: { tracing: padroneTracer } });\n      },\n\n      error(ctx, next) {\n        rootSpan?.recordException(ctx.error);\n        rootSpan?.setStatus({ code: OTEL_ERROR });\n        return next();\n      },\n\n      shutdown(_ctx, next) {\n        return thenMaybe(next(), (res) => {\n          rootSpan?.end();\n          return res;\n        });\n      },\n    };\n  });\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\n/**\n * Extension that adds OpenTelemetry tracing to command execution.\n *\n * Creates a root span for each command invocation and provides a `PadroneTracer`\n * on the command context for creating child spans in action handlers.\n *\n * When used with `padroneLogger()`, the logger automatically emits span events\n * for each log call — no extra configuration needed. The logger detects the\n * tracing context and bridges log output to span events.\n *\n * Uses minimal OTEL-compatible interfaces — pass any `TracerProvider` that\n * implements `getTracer()`. Works with `@opentelemetry/api` or compatible\n * libraries.\n *\n * Provides `{ tracing: PadroneTracer }` on the command context.\n * Access it in action handlers as `ctx.context.tracing`.\n *\n * Usage:\n * ```ts\n * import { trace } from '@opentelemetry/api';\n * import { createPadrone, padroneLogger } from 'padrone';\n * import { padroneTracing } from 'padrone/tracing';\n *\n * createPadrone('my-cli')\n *   .extend(padroneTracing({ provider: trace.getTracerProvider() }))\n *   .extend(padroneLogger())\n *   .command('deploy', (c) =>\n *     c.action((_args, ctx) => {\n *       ctx.context.logger.info('deploying');  // also emits a span event\n *       ctx.context.tracing.span('build', (span) => {\n *         span.setAttribute('target', 'production');\n *       });\n *     })\n *   )\n * ```\n */\nexport function padroneTracing<T extends CommandTypesBase>(config: PadroneTracingConfig): (builder: T) => WithTracing<T> {\n  const resolved: ResolvedTracingConfig = {\n    provider: config.provider,\n    serviceName: config.serviceName,\n  };\n  return ((builder: AnyPadroneBuilder) => builder.intercept(tracingInterceptor(resolved))) as any;\n}\n"],"mappings":";;;AA6DA,MAAM,aAA6B;AAInC,SAAS,mBAAmB,QAA+B;CACzD,OAAO,kBAAkB;EAAE,IAAI;EAAmB,MAAM;EAAmB,OAAO;CAAG,SAAS;EAC5F,IAAI;EACJ,IAAI;EAEJ,OAAO;GACL,MAAM,KAAK,MAAM;IACf,SAAS,OAAO,SAAS,UAAU,OAAO,eAAe,IAAI,QAAQ,IAAI;IACzE,OAAO,KAAK;GACd;GAEA,QAAQ,KAAK,MAAM;IACjB,WAAW,OAAO,UAAU,OAAO,IAAI,QAAQ,MAAM;IAkCrD,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS;KA/BhC;KACA;KACA,KAAK,MAAM,IAAI;MACb,MAAM,QAAQ,OAAO,UAAU,IAAI;MACnC,IAAI;OACF,MAAM,SAAS,GAAG,KAAK;OACvB,IAAI,UAAU,QAAQ,OAAQ,OAAe,SAAS,YACpD,OAAQ,OAAe,MACpB,MAAW;QACV,MAAM,IAAI;QACV,OAAO;OACT,IACC,QAAiB;QAChB,MAAM,gBAAgB,GAAG;QACzB,MAAM,UAAU,EAAE,MAAM,WAAW,CAAC;QACpC,MAAM,IAAI;QACV,MAAM;OACR,CACF;OAEF,MAAM,IAAI;OACV,OAAO;MACT,SAAS,KAAK;OACZ,MAAM,gBAAgB,GAAG;OACzB,MAAM,UAAU,EAAE,MAAM,WAAW,CAAC;OACpC,MAAM,IAAI;OACV,MAAM;MACR;KACF;IAG4C,EAAE,EAAE,CAAC;GACrD;GAEA,MAAM,KAAK,MAAM;IACf,UAAU,gBAAgB,IAAI,KAAK;IACnC,UAAU,UAAU,EAAE,MAAM,WAAW,CAAC;IACxC,OAAO,KAAK;GACd;GAEA,SAAS,MAAM,MAAM;IACnB,OAAO,UAAU,KAAK,IAAI,QAAQ;KAChC,UAAU,IAAI;KACd,OAAO;IACT,CAAC;GACH;EACF;CACF,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,SAAgB,eAA2C,QAA8D;CACvH,MAAM,WAAkC;EACtC,UAAU,OAAO;EACjB,aAAa,OAAO;CACtB;CACA,SAAS,YAA+B,QAAQ,UAAU,mBAAmB,QAAQ,CAAC;AACxF"}