{"version":3,"file":"context.cjs","names":["contextStorage"],"sources":["../../src/lib/context.ts"],"sourcesContent":["import type { CLI, AnyCLI, ProviderConfig, GlobalProviderConfig } from './public-api';\nimport { contextStorage, ForgeContextData } from './async-context';\n\n// ─── Type Helpers ─────────────────────────────────────────────────────────────\n\n/**\n * Recursively gathers all providers from a CLI and its ancestors.\n *\n * When a child command re-provides a key that its parent also provides, the\n * child's type shadows the parent's — matching the runtime override semantics\n * in `collectProviders()`. This is why the parent's providers are wrapped in\n * `Omit<..., keyof TProviders>` before being intersected with the child's.\n */\nexport type ProvidersOf<T> = T extends CLI<any, any, any, infer TParent, infer TProviders>\n  ? TProviders &\n      (TParent extends AnyCLI\n        ? Omit<ProvidersOf<TParent>, keyof TProviders>\n        : Record<never, never>)\n  : Record<never, never>;\n\n/**\n * Same chain-walking semantics as {@link ProvidersOf}, but takes\n * `TProviders` and `TParent` directly instead of a `CLI<...>` instance type.\n *\n * This exists so the `CLI` interface can declare\n * `getContext(): CommandContext<TArgs, ProvidersFromChain<TProviders, TParent>, TChildren>`\n * without re-wrapping its own type parameters in a `CLI<...>`. Writing\n * `ProvidersOf<CLI<TArgs, ..., TParent, TProviders>>` inside `CLI` would\n * reference `CLI` while the interface is still being constructed, which\n * has caused TypeScript cycle / constraint-solver headaches in the past.\n * Passing the raw parameters through a non-CLI helper keeps the\n * recursion purely in conditional-type space.\n */\nexport type ProvidersFromChain<TProviders, TParent> =\n  TParent extends CLI<any, any, any, infer TGrandParent, infer TParentProviders>\n    ? TProviders &\n        Omit<ProvidersFromChain<TParentProviders, TGrandParent>, keyof TProviders>\n    : TProviders;\n\n/**\n * Infers the full CommandContext type from a CLI type.\n */\nexport type InferContextOfCommand<T extends AnyCLI> = T extends CLI<\n  infer TArgs,\n  any,\n  infer TChildren,\n  any,\n  any\n>\n  ? CommandContext<TArgs, ProvidersOf<T>, TChildren>\n  : never;\n\n/**\n * The context object available inside a command handler via {@link getCommandContext}.\n */\nexport interface CommandContext<TArgs, TProviders, TChildren = {}> {\n  /** The parsed arguments for the current command. */\n  readonly args: TArgs;\n\n  /**\n   * The chain of command names from root to the currently executing command.\n   * e.g. `['my-app', 'serve']`\n   */\n  readonly commandChain: string[];\n\n  /**\n   * Resolves a provider by its key.\n   *\n   * @param key The provider key (must be a key of TProviders)\n   * @param defaultValue Optional fallback if the provider has no registered factory\n   */\n  inject<K extends keyof TProviders>(key: K): TProviders[K];\n  inject<K extends keyof TProviders>(key: K, defaultValue: TProviders[K]): TProviders[K];\n\n  /**\n   * Returns a CommandContext scoped to a child command that was already executed.\n   * The child command must appear in the current commandChain.\n   */\n  getChildContext<K extends keyof TChildren>(\n    command: K & string\n  ): TChildren[K] extends AnyCLI ? InferContextOfCommand<TChildren[K]> : never;\n}\n\n// ─── Module-level state ───────────────────────────────────────────────────────\n\n/**\n * Permanent cache for global-lifetime providers. Survives across executions.\n *\n * Keyed by the factory function identity rather than the provider name, so\n * two unrelated CLI apps (or two registrations of the same name with\n * different factories) cannot collide on a shared cached value. Each\n * distinct factory function gets its own slot; calling `.provide()` twice\n * with the same factory reference (e.g. across clones) correctly shares.\n */\nconst globalProviderCache = new Map<Function, unknown>();\n\n/**\n * Clears the module-level cache for `lifetime: 'global'` providers.\n *\n * Global provider factories normally run once per Node process and their\n * result is memoized forever. That's the desired behavior in production —\n * it's also what makes them leak between tests. Call this from test setup\n * (or automatically via `TestHarness.clearMockedContexts()`) to get a clean\n * slate between specs that exercise global-lifetime providers.\n */\nexport function resetGlobalProviders(): void {\n  globalProviderCache.clear();\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────────\n\nfunction readStore(): ForgeContextData {\n  const store = contextStorage.getStore();\n  if (!store) {\n    throw new Error(\n      'No CLI context found. getCommandContext() must be called from within a command handler.'\n    );\n  }\n  if (!store.handlerPhase) {\n    throw new Error(\n      'inject() can only be called during the handler phase. ' +\n        'Do not call getCommandContext() during option builders or middleware.'\n    );\n  }\n  return store;\n}\n\nconst NOT_FOUND = Symbol('NOT_FOUND');\n\nfunction resolveProvider(store: ForgeContextData, key: string): unknown | typeof NOT_FOUND {\n  // 1. Check pre-cached/pre-resolved providers (use .has() to handle undefined values)\n  if (store.providers.has(key)) {\n    return store.providers.get(key);\n  }\n\n  // 2. Lazy-resolve from factories\n  const registration = store.providerFactories.get(key);\n  if (!registration) {\n    return NOT_FOUND;\n  }\n\n  // 3. Cycle detection — a factory that inject()s a provider whose own\n  //    factory inject()s back into this key would otherwise blow the stack.\n  if (store.resolving.has(key)) {\n    const cycle = [...store.resolving, key].join(' -> ');\n    throw new Error(\n      `Circular provider dependency detected while resolving \"${key}\": ${cycle}`\n    );\n  }\n\n  const { factory, lifetime } = registration;\n  store.resolving.add(key);\n  try {\n    if (lifetime === 'global') {\n      // Global: permanent module-level cache keyed by factory identity\n      // (not by key name) so unrelated registrations can't collide.\n      const globalFactory = factory as GlobalProviderConfig<unknown>['factory'];\n      if (!globalProviderCache.has(globalFactory)) {\n        globalProviderCache.set(globalFactory, globalFactory());\n      }\n      const value = globalProviderCache.get(globalFactory);\n      // Also cache in the store so subsequent inject() calls skip factory lookup\n      store.providers.set(key, value);\n      return value;\n    } else {\n      // executionScope: scoped to this execution, keyed by args identity\n      const value = (factory as ProviderConfig<unknown>['factory'])(store.args);\n      store.providers.set(key, value);\n      return value;\n    }\n  } finally {\n    store.resolving.delete(key);\n  }\n}\n\nfunction createCommandContext(store: ForgeContextData): CommandContext<any, any, any> {\n  return {\n    get args() {\n      return store.args;\n    },\n\n    get commandChain() {\n      return store.commandChain;\n    },\n\n    inject(key: string, defaultValue?: unknown): unknown {\n      const resolved = resolveProvider(store, key);\n      if (resolved === NOT_FOUND) {\n        if (arguments.length >= 2) {\n          return defaultValue;\n        }\n        throw new Error(\n          `No provider registered for key \"${key}\". ` +\n            `Register it with .provide(\"${key}\", { factory: ... }) on the CLI.`\n        );\n      }\n      return resolved;\n    },\n\n    getChildContext(command: string): any {\n      if (!store.commandChain.includes(command)) {\n        throw new Error(\n          `Command \"${command}\" is not in the current command chain: [${store.commandChain.join(', ')}]. ` +\n            `getChildContext() can only be used with commands that have already executed.`\n        );\n      }\n      return createCommandContext(store);\n    },\n  };\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Returns the {@link CommandContext} for the currently executing command.\n *\n * Must be called from within a command handler (not during builders or middleware).\n *\n * ## Runtime safety\n *\n * When called with a CLI instance, `getCommandContext` validates at runtime\n * that the instance is part of the active command chain — the root app,\n * any ancestor on the chain, or the currently-running subcommand. If you\n * pass a CLI that isn't in the chain (a sibling command, an unrelated app,\n * a descendant that didn't run), it throws with a descriptive error. This\n * catches the common bug where the wrong instance is passed as a type\n * witness and `inject()` silently returns the wrong providers.\n *\n * ## Two ways to reach subcommand-typed access\n *\n * **Option 1** — from the root, walk by name:\n * ```ts\n * const ctx = getCommandContext(app);\n * const buildCtx = ctx.getChildContext('build');\n * console.log(buildCtx.args.target);\n * ```\n *\n * **Option 2** — pass a composed subcommand reference directly, skipping\n * the `getChildContext` hop:\n * ```ts\n * import { build } from './build'; // standalone CLI composed into app\n * const ctx = getCommandContext(build);\n * console.log(ctx.args.target);\n * ```\n *\n * Both forms are runtime-safe. Option 2 is only typed if the subcommand\n * reference carries the full `TProviders` — i.e. the subcommand is declared\n * inside the parent's `.command('name', { builder, handler })` call, where\n * TypeScript can thread parent providers into the builder's `cmd`. A\n * standalone `cli('build', ...)` reference doesn't see inherited providers\n * in its type; use Option 1 for that shape.\n *\n * ## Type witness (no instance)\n *\n * The parameterless overload returns a context typed by an explicit generic\n * — useful when you can't get a live reference to the CLI from where the\n * handler is written. **This form has no runtime safety**: the `T` type\n * parameter is trusted as-is, so a mismatched generic silently returns a\n * context typed for a CLI that isn't running. Prefer passing the CLI\n * instance whenever feasible.\n *\n * @param cli CLI instance used as both a type witness for inference and\n *            a runtime identity check. Required for runtime-safe usage.\n *\n * @example\n * ```ts\n * import { getCommandContext } from 'cli-forge/context';\n * import { app } from './cli';\n *\n * const ctx = getCommandContext(app);\n * const db = ctx.inject('db');\n * ```\n */\nexport function getCommandContext<T extends AnyCLI>(cli: T): InferContextOfCommand<T>;\n/**\n * Type-only overload: trusts `T` without runtime validation. Prefer the\n * instance-based overload whenever you can import the CLI that owns the\n * handler — this form exists as an escape hatch for cases where no live\n * CLI reference is reachable from the handler's module.\n */\nexport function getCommandContext<T extends AnyCLI>(): InferContextOfCommand<T>;\nexport function getCommandContext<T extends AnyCLI>(cli?: T): InferContextOfCommand<T> {\n  const store = readStore();\n  if (cli && typeof (cli as { commandId?: unknown }).commandId === 'string') {\n    const { commandId } = cli as unknown as { commandId: string; name?: string };\n    if (!store.commandIdChain.includes(commandId)) {\n      const name = (cli as unknown as { name?: string }).name ?? commandId;\n      throw new Error(\n        `getCommandContext() was called with a CLI instance (\"${name}\", id=${commandId}) ` +\n          `that is not part of the active command chain. ` +\n          `Active chain ids: [${store.commandIdChain.join(', ')}]. ` +\n          `Pass the root app, an ancestor on the chain, or the currently-running subcommand.`\n      );\n    }\n  }\n  return createCommandContext(store) as InferContextOfCommand<T>;\n}\n"],"mappings":";;;;;;;;;;;;AA8FA,MAAM,sCAAsB,IAAI,KAAwB;;;;;;;;;;AAWxD,SAAgB,uBAA6B;AAC3C,qBAAoB,OAAO;;AAK7B,SAAS,YAA8B;CACrC,MAAM,QAAQA,0BAAAA,eAAe,UAAU;AACvC,KAAI,CAAC,MACH,OAAM,IAAI,MACR,0FACD;AAEH,KAAI,CAAC,MAAM,aACT,OAAM,IAAI,MACR,8HAED;AAEH,QAAO;;AAGT,MAAM,YAAY,OAAO,YAAY;AAErC,SAAS,gBAAgB,OAAyB,KAAyC;AAEzF,KAAI,MAAM,UAAU,IAAI,IAAI,CAC1B,QAAO,MAAM,UAAU,IAAI,IAAI;CAIjC,MAAM,eAAe,MAAM,kBAAkB,IAAI,IAAI;AACrD,KAAI,CAAC,aACH,QAAO;AAKT,KAAI,MAAM,UAAU,IAAI,IAAI,EAAE;EAC5B,MAAM,QAAQ,CAAC,GAAG,MAAM,WAAW,IAAI,CAAC,KAAK,OAAO;AACpD,QAAM,IAAI,MACR,0DAA0D,IAAI,KAAK,QACpE;;CAGH,MAAM,EAAE,SAAS,aAAa;AAC9B,OAAM,UAAU,IAAI,IAAI;AACxB,KAAI;AACF,MAAI,aAAa,UAAU;GAGzB,MAAM,gBAAgB;AACtB,OAAI,CAAC,oBAAoB,IAAI,cAAc,CACzC,qBAAoB,IAAI,eAAe,eAAe,CAAC;GAEzD,MAAM,QAAQ,oBAAoB,IAAI,cAAc;AAEpD,SAAM,UAAU,IAAI,KAAK,MAAM;AAC/B,UAAO;SACF;GAEL,MAAM,QAAS,QAA+C,MAAM,KAAK;AACzE,SAAM,UAAU,IAAI,KAAK,MAAM;AAC/B,UAAO;;WAED;AACR,QAAM,UAAU,OAAO,IAAI;;;AAI/B,SAAS,qBAAqB,OAAwD;AACpF,QAAO;EACL,IAAI,OAAO;AACT,UAAO,MAAM;;EAGf,IAAI,eAAe;AACjB,UAAO,MAAM;;EAGf,OAAO,KAAa,cAAiC;GACnD,MAAM,WAAW,gBAAgB,OAAO,IAAI;AAC5C,OAAI,aAAa,WAAW;AAC1B,QAAI,UAAU,UAAU,EACtB,QAAO;AAET,UAAM,IAAI,MACR,mCAAmC,IAAI,gCACP,IAAI,kCACrC;;AAEH,UAAO;;EAGT,gBAAgB,SAAsB;AACpC,OAAI,CAAC,MAAM,aAAa,SAAS,QAAQ,CACvC,OAAM,IAAI,MACR,YAAY,QAAQ,0CAA0C,MAAM,aAAa,KAAK,KAAK,CAAC,iFAE7F;AAEH,UAAO,qBAAqB,MAAM;;EAErC;;AAyEH,SAAgB,kBAAoC,KAAmC;CACrF,MAAM,QAAQ,WAAW;AACzB,KAAI,OAAO,OAAQ,IAAgC,cAAc,UAAU;EACzE,MAAM,EAAE,cAAc;AACtB,MAAI,CAAC,MAAM,eAAe,SAAS,UAAU,EAAE;GAC7C,MAAM,OAAQ,IAAqC,QAAQ;AAC3D,SAAM,IAAI,MACR,wDAAwD,KAAK,QAAQ,UAAU,qEAEvD,MAAM,eAAe,KAAK,KAAK,CAAC,sFAEzD;;;AAGL,QAAO,qBAAqB,MAAM"}