@sanity/cli-core 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { styleText } from 'node:util';
1
2
  import { Command } from '@oclif/core';
2
3
  import { getCliConfig } from './config/cli/getCliConfig.js';
3
4
  import { findProjectRoot } from './config/findProjectRoot.js';
@@ -5,7 +6,7 @@ import { subdebug } from './debug.js';
5
6
  import { NonInteractiveError } from './errors/NonInteractiveError.js';
6
7
  import { ProjectRootNotFoundError } from './errors/ProjectRootNotFoundError.js';
7
8
  import { getGlobalCliClient, getProjectCliClient } from './services/apiClient.js';
8
- import { getCliTelemetry } from './util/getCliTelemetry.js';
9
+ import { getCliTelemetry, reportCliTraceError } from './util/getCliTelemetry.js';
9
10
  import { isInteractive } from './util/isInteractive.js';
10
11
  const debug = subdebug('sanityCommand');
11
12
  export class SanityCommand extends Command {
@@ -47,6 +48,22 @@ export class SanityCommand extends Command {
47
48
  * @returns The telemetry store.
48
49
  */ telemetry;
49
50
  /**
51
+ * Report real command errors to the CLI command trace.
52
+ * User aborts (SIGINT, ExitPromptError) are not reported — the trace is left
53
+ * incomplete, which accurately represents that the command was interrupted.
54
+ */ async catch(err) {
55
+ // ExitPromptError is thrown by `@inquirer/prompts` when the user cancels a prompt
56
+ // The `message === 'SIGINT'` check matches oclif's own convention (see handle.js in @oclif/core)
57
+ if (err.name === 'ExitPromptError' || err.message === 'SIGINT') {
58
+ // 130 is the standard exit code for script termination by Ctrl+C
59
+ this.logToStderr(styleText('yellow', '\u{203A}') + ' Aborted by user');
60
+ return this.exit(130);
61
+ }
62
+ // In other cases, we _do_ want to report the error
63
+ reportCliTraceError(err);
64
+ return super.catch(err);
65
+ }
66
+ /**
50
67
  * Get the CLI config.
51
68
  *
52
69
  * @returns The CLI config.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/SanityCommand.ts"],"sourcesContent":["import {Command, Interfaces} from '@oclif/core'\n\nimport {getCliConfig} from './config/cli/getCliConfig.js'\nimport {type CliConfig} from './config/cli/types/cliConfig.js'\nimport {findProjectRoot} from './config/findProjectRoot.js'\nimport {type ProjectRootResult} from './config/util/recursivelyResolveProjectRoot.js'\nimport {subdebug} from './debug.js'\nimport {NonInteractiveError} from './errors/NonInteractiveError.js'\nimport {ProjectRootNotFoundError} from './errors/ProjectRootNotFoundError.js'\nimport {\n getGlobalCliClient,\n getProjectCliClient,\n type GlobalCliClientOptions,\n type ProjectCliClientOptions,\n} from './services/apiClient.js'\nimport {type CLITelemetryStore} from './telemetry/types.js'\nimport {type Output} from './types.js'\nimport {getCliTelemetry} from './util/getCliTelemetry.js'\nimport {isInteractive} from './util/isInteractive.js'\n\ntype Flags<T extends typeof Command> = Interfaces.InferredFlags<\n (typeof SanityCommand)['baseFlags'] & T['flags']\n>\n\ntype Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>\n\nconst debug = subdebug('sanityCommand')\n\nexport abstract class SanityCommand<T extends typeof Command> extends Command {\n protected args!: Args<T>\n protected flags!: Flags<T>\n\n /**\n * Get the global API client.\n *\n * @param args - The global API client options.\n * @returns The global API client.\n *\n * @deprecated use `getGlobalCliClient` function directly instead.\n */\n protected getGlobalApiClient = (args: GlobalCliClientOptions) => getGlobalCliClient(args)\n\n /**\n * Get the project API client.\n *\n * @param args - The project API client options.\n * @returns The project API client.\n *\n * @deprecated use `getProjectCliClient` function directly instead.\n */\n protected getProjectApiClient = (args: ProjectCliClientOptions) => getProjectCliClient(args)\n\n /**\n * Helper for outputting to the console.\n *\n * @example\n * ```ts\n * this.output.log('Hello')\n * this.output.warn('Warning')\n * this.output.error('Error')\n * ```\n */\n protected output: Output = {\n error: this.error.bind(this),\n log: this.log.bind(this),\n warn: this.warn.bind(this),\n }\n\n /**\n * The telemetry store.\n *\n * @returns The telemetry store.\n */\n protected telemetry!: CLITelemetryStore\n\n /**\n * Get the CLI config.\n *\n * @returns The CLI config.\n */\n protected async getCliConfig(): Promise<CliConfig> {\n const root = await this.getProjectRoot()\n\n debug(`Using project root`, root)\n return getCliConfig(root.directory)\n }\n\n /**\n * Get the project ID from passed flags or (if not provided) the CLI config.\n *\n * Optionally accepts a `fallback` function that is called when no project ID\n * can be determined from flags or config. This allows commands to provide\n * interactive project selection while keeping the prompt logic in the CLI package.\n *\n * If the fallback throws a `NonInteractiveError` (e.g. because the terminal is\n * not interactive), it falls through to the standard error with suggestions.\n *\n * Optionally accepts a `deprecatedFlagName` for commands that have a deprecated\n * flag (e.g. `--project`) that should be checked after `--project-id` but before\n * the CLI config.\n *\n * @returns The project ID.\n */\n protected async getProjectId(options?: {\n deprecatedFlagName?: string\n fallback?: () => Promise<string>\n }): Promise<string> {\n const hasProjectFlag = this.ctor.flags != null && 'project-id' in this.ctor.flags\n\n // Check --project-id flag first\n if (hasProjectFlag) {\n const flagProjectId =\n 'project-id' in this.flags && typeof this.flags['project-id'] === 'string'\n ? this.flags['project-id']\n : undefined\n\n if (flagProjectId) return flagProjectId\n }\n\n // Check deprecated flag (e.g. --project) before CLI config\n if (options?.deprecatedFlagName) {\n const deprecatedValue =\n options.deprecatedFlagName in this.flags &&\n typeof this.flags[options.deprecatedFlagName] === 'string'\n ? (this.flags[options.deprecatedFlagName] as string)\n : undefined\n\n if (deprecatedValue) return deprecatedValue\n }\n\n // Fall back to CLI config\n try {\n const config = await this.getCliConfig()\n const configProjectId = config.api?.projectId\n if (configProjectId) return configProjectId\n } catch (err) {\n if (!(err instanceof ProjectRootNotFoundError)) throw err\n // No project root — fall through to fallback/error\n }\n\n // Offer interactive selection if a fallback was provided\n if (options?.fallback) {\n try {\n return await options.fallback()\n } catch (err) {\n if (!(err instanceof NonInteractiveError)) throw err\n // Non-interactive: throw with actionable suggestions\n throw new ProjectRootNotFoundError('Unable to determine project ID', {\n cause: err,\n suggestions: [\n ...(hasProjectFlag ? ['Providing a project ID: --project-id <project-id>'] : []),\n 'Running this command from within a Sanity project directory',\n 'Running in an interactive terminal to get a project selection prompt',\n ],\n })\n }\n }\n\n throw new ProjectRootNotFoundError('Unable to determine project ID', {\n suggestions: [\n ...(hasProjectFlag ? ['Providing a project ID: --project-id <project-id>'] : []),\n 'Running this command from within a Sanity project directory',\n ],\n })\n }\n\n /**\n * Get the project's root directory by resolving the config\n *\n * @returns The project root result.\n */\n protected getProjectRoot(): Promise<ProjectRootResult> {\n return findProjectRoot(process.cwd())\n }\n\n public async init(): Promise<void> {\n const {args, flags} = await this.parse({\n args: this.ctor.args,\n baseFlags: (super.ctor as typeof SanityCommand).baseFlags,\n enableJsonFlag: this.ctor.enableJsonFlag,\n flags: this.ctor.flags,\n strict: this.ctor.strict,\n })\n\n this.args = args as Args<T>\n this.flags = flags as Flags<T>\n this.telemetry = getCliTelemetry()\n\n await super.init()\n }\n\n /**\n * Check if the command is running in unattended mode.\n *\n * This means the command should not ask for user input, instead using defaults where\n * possible, and if that does not make sense (eg there's missing information), then we\n * should error out (remember to exit with a non-zero code).\n *\n * Most commands should take an explicit `--yes` flag to enable unattended mode, but\n * some commands may also be run in unattended mode if `process.stdin` is not a TTY\n * (eg when running in a CI environment).\n */\n protected isUnattended(): boolean {\n return this.flags.yes || !this.resolveIsInteractive()\n }\n\n /**\n * Resolver for checking if the terminal is interactive. Override in tests to provide mock values.\n *\n * @returns Whether the terminal is interactive.\n */\n protected resolveIsInteractive(): boolean {\n return isInteractive()\n }\n\n /**\n * Get the CLI config, returning an empty config if no project root is found.\n *\n * Use this instead of `getCliConfig()` in commands that can operate without a\n * project directory (e.g. when `--project-id` and `--dataset` flags are provided).\n *\n * @returns The CLI config, or an empty config object if no project root is found.\n */\n protected async tryGetCliConfig(): Promise<CliConfig> {\n try {\n return await this.getCliConfig()\n } catch (err) {\n if (!(err instanceof ProjectRootNotFoundError)) throw err\n return {}\n }\n }\n}\n"],"names":["Command","getCliConfig","findProjectRoot","subdebug","NonInteractiveError","ProjectRootNotFoundError","getGlobalCliClient","getProjectCliClient","getCliTelemetry","isInteractive","debug","SanityCommand","args","flags","getGlobalApiClient","getProjectApiClient","output","error","bind","log","warn","telemetry","root","getProjectRoot","directory","getProjectId","options","hasProjectFlag","ctor","flagProjectId","undefined","deprecatedFlagName","deprecatedValue","config","configProjectId","api","projectId","err","fallback","cause","suggestions","process","cwd","init","parse","baseFlags","enableJsonFlag","strict","isUnattended","yes","resolveIsInteractive","tryGetCliConfig"],"mappings":"AAAA,SAAQA,OAAO,QAAmB,cAAa;AAE/C,SAAQC,YAAY,QAAO,+BAA8B;AAEzD,SAAQC,eAAe,QAAO,8BAA6B;AAE3D,SAAQC,QAAQ,QAAO,aAAY;AACnC,SAAQC,mBAAmB,QAAO,kCAAiC;AACnE,SAAQC,wBAAwB,QAAO,uCAAsC;AAC7E,SACEC,kBAAkB,EAClBC,mBAAmB,QAGd,0BAAyB;AAGhC,SAAQC,eAAe,QAAO,4BAA2B;AACzD,SAAQC,aAAa,QAAO,0BAAyB;AAQrD,MAAMC,QAAQP,SAAS;AAEvB,OAAO,MAAeQ,sBAAgDX;IAC1DY,KAAc;IACdC,MAAgB;IAE1B;;;;;;;GAOC,GACD,AAAUC,qBAAqB,CAACF,OAAiCN,mBAAmBM,MAAK;IAEzF;;;;;;;GAOC,GACD,AAAUG,sBAAsB,CAACH,OAAkCL,oBAAoBK,MAAK;IAE5F;;;;;;;;;GASC,GACD,AAAUI,SAAiB;QACzBC,OAAO,IAAI,CAACA,KAAK,CAACC,IAAI,CAAC,IAAI;QAC3BC,KAAK,IAAI,CAACA,GAAG,CAACD,IAAI,CAAC,IAAI;QACvBE,MAAM,IAAI,CAACA,IAAI,CAACF,IAAI,CAAC,IAAI;IAC3B,EAAC;IAED;;;;GAIC,GACD,AAAUG,UAA6B;IAEvC;;;;GAIC,GACD,MAAgBpB,eAAmC;QACjD,MAAMqB,OAAO,MAAM,IAAI,CAACC,cAAc;QAEtCb,MAAM,CAAC,kBAAkB,CAAC,EAAEY;QAC5B,OAAOrB,aAAaqB,KAAKE,SAAS;IACpC;IAEA;;;;;;;;;;;;;;;GAeC,GACD,MAAgBC,aAAaC,OAG5B,EAAmB;QAClB,MAAMC,iBAAiB,IAAI,CAACC,IAAI,CAACf,KAAK,IAAI,QAAQ,gBAAgB,IAAI,CAACe,IAAI,CAACf,KAAK;QAEjF,gCAAgC;QAChC,IAAIc,gBAAgB;YAClB,MAAME,gBACJ,gBAAgB,IAAI,CAAChB,KAAK,IAAI,OAAO,IAAI,CAACA,KAAK,CAAC,aAAa,KAAK,WAC9D,IAAI,CAACA,KAAK,CAAC,aAAa,GACxBiB;YAEN,IAAID,eAAe,OAAOA;QAC5B;QAEA,2DAA2D;QAC3D,IAAIH,SAASK,oBAAoB;YAC/B,MAAMC,kBACJN,QAAQK,kBAAkB,IAAI,IAAI,CAAClB,KAAK,IACxC,OAAO,IAAI,CAACA,KAAK,CAACa,QAAQK,kBAAkB,CAAC,KAAK,WAC7C,IAAI,CAAClB,KAAK,CAACa,QAAQK,kBAAkB,CAAC,GACvCD;YAEN,IAAIE,iBAAiB,OAAOA;QAC9B;QAEA,0BAA0B;QAC1B,IAAI;YACF,MAAMC,SAAS,MAAM,IAAI,CAAChC,YAAY;YACtC,MAAMiC,kBAAkBD,OAAOE,GAAG,EAAEC;YACpC,IAAIF,iBAAiB,OAAOA;QAC9B,EAAE,OAAOG,KAAK;YACZ,IAAI,CAAEA,CAAAA,eAAehC,wBAAuB,GAAI,MAAMgC;QACtD,mDAAmD;QACrD;QAEA,yDAAyD;QACzD,IAAIX,SAASY,UAAU;YACrB,IAAI;gBACF,OAAO,MAAMZ,QAAQY,QAAQ;YAC/B,EAAE,OAAOD,KAAK;gBACZ,IAAI,CAAEA,CAAAA,eAAejC,mBAAkB,GAAI,MAAMiC;gBACjD,qDAAqD;gBACrD,MAAM,IAAIhC,yBAAyB,kCAAkC;oBACnEkC,OAAOF;oBACPG,aAAa;2BACPb,iBAAiB;4BAAC;yBAAoD,GAAG,EAAE;wBAC/E;wBACA;qBACD;gBACH;YACF;QACF;QAEA,MAAM,IAAItB,yBAAyB,kCAAkC;YACnEmC,aAAa;mBACPb,iBAAiB;oBAAC;iBAAoD,GAAG,EAAE;gBAC/E;aACD;QACH;IACF;IAEA;;;;GAIC,GACD,AAAUJ,iBAA6C;QACrD,OAAOrB,gBAAgBuC,QAAQC,GAAG;IACpC;IAEA,MAAaC,OAAsB;QACjC,MAAM,EAAC/B,IAAI,EAAEC,KAAK,EAAC,GAAG,MAAM,IAAI,CAAC+B,KAAK,CAAC;YACrChC,MAAM,IAAI,CAACgB,IAAI,CAAChB,IAAI;YACpBiC,WAAW,AAAC,KAAK,CAACjB,KAA8BiB,SAAS;YACzDC,gBAAgB,IAAI,CAAClB,IAAI,CAACkB,cAAc;YACxCjC,OAAO,IAAI,CAACe,IAAI,CAACf,KAAK;YACtBkC,QAAQ,IAAI,CAACnB,IAAI,CAACmB,MAAM;QAC1B;QAEA,IAAI,CAACnC,IAAI,GAAGA;QACZ,IAAI,CAACC,KAAK,GAAGA;QACb,IAAI,CAACQ,SAAS,GAAGb;QAEjB,MAAM,KAAK,CAACmC;IACd;IAEA;;;;;;;;;;GAUC,GACD,AAAUK,eAAwB;QAChC,OAAO,IAAI,CAACnC,KAAK,CAACoC,GAAG,IAAI,CAAC,IAAI,CAACC,oBAAoB;IACrD;IAEA;;;;GAIC,GACD,AAAUA,uBAAgC;QACxC,OAAOzC;IACT;IAEA;;;;;;;GAOC,GACD,MAAgB0C,kBAAsC;QACpD,IAAI;YACF,OAAO,MAAM,IAAI,CAAClD,YAAY;QAChC,EAAE,OAAOoC,KAAK;YACZ,IAAI,CAAEA,CAAAA,eAAehC,wBAAuB,GAAI,MAAMgC;YACtD,OAAO,CAAC;QACV;IACF;AACF"}
1
+ {"version":3,"sources":["../src/SanityCommand.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Command, Interfaces} from '@oclif/core'\nimport {type CommandError} from '@oclif/core/interfaces'\n\nimport {getCliConfig} from './config/cli/getCliConfig.js'\nimport {type CliConfig} from './config/cli/types/cliConfig.js'\nimport {findProjectRoot} from './config/findProjectRoot.js'\nimport {type ProjectRootResult} from './config/util/recursivelyResolveProjectRoot.js'\nimport {subdebug} from './debug.js'\nimport {NonInteractiveError} from './errors/NonInteractiveError.js'\nimport {ProjectRootNotFoundError} from './errors/ProjectRootNotFoundError.js'\nimport {\n getGlobalCliClient,\n getProjectCliClient,\n type GlobalCliClientOptions,\n type ProjectCliClientOptions,\n} from './services/apiClient.js'\nimport {type CLITelemetryStore} from './telemetry/types.js'\nimport {type Output} from './types.js'\nimport {getCliTelemetry, reportCliTraceError} from './util/getCliTelemetry.js'\nimport {isInteractive} from './util/isInteractive.js'\n\ntype Flags<T extends typeof Command> = Interfaces.InferredFlags<\n (typeof SanityCommand)['baseFlags'] & T['flags']\n>\n\ntype Args<T extends typeof Command> = Interfaces.InferredArgs<T['args']>\n\nconst debug = subdebug('sanityCommand')\n\nexport abstract class SanityCommand<T extends typeof Command> extends Command {\n protected args!: Args<T>\n protected flags!: Flags<T>\n\n /**\n * Get the global API client.\n *\n * @param args - The global API client options.\n * @returns The global API client.\n *\n * @deprecated use `getGlobalCliClient` function directly instead.\n */\n protected getGlobalApiClient = (args: GlobalCliClientOptions) => getGlobalCliClient(args)\n\n /**\n * Get the project API client.\n *\n * @param args - The project API client options.\n * @returns The project API client.\n *\n * @deprecated use `getProjectCliClient` function directly instead.\n */\n protected getProjectApiClient = (args: ProjectCliClientOptions) => getProjectCliClient(args)\n\n /**\n * Helper for outputting to the console.\n *\n * @example\n * ```ts\n * this.output.log('Hello')\n * this.output.warn('Warning')\n * this.output.error('Error')\n * ```\n */\n protected output: Output = {\n error: this.error.bind(this),\n log: this.log.bind(this),\n warn: this.warn.bind(this),\n }\n\n /**\n * The telemetry store.\n *\n * @returns The telemetry store.\n */\n protected telemetry!: CLITelemetryStore\n\n /**\n * Report real command errors to the CLI command trace.\n * User aborts (SIGINT, ExitPromptError) are not reported — the trace is left\n * incomplete, which accurately represents that the command was interrupted.\n */\n protected override async catch(err: CommandError): Promise<void> {\n // ExitPromptError is thrown by `@inquirer/prompts` when the user cancels a prompt\n // The `message === 'SIGINT'` check matches oclif's own convention (see handle.js in @oclif/core)\n if (err.name === 'ExitPromptError' || err.message === 'SIGINT') {\n // 130 is the standard exit code for script termination by Ctrl+C\n this.logToStderr(styleText('yellow', '\\u{203A}') + ' Aborted by user')\n return this.exit(130)\n }\n\n // In other cases, we _do_ want to report the error\n reportCliTraceError(err)\n return super.catch(err)\n }\n\n /**\n * Get the CLI config.\n *\n * @returns The CLI config.\n */\n protected async getCliConfig(): Promise<CliConfig> {\n const root = await this.getProjectRoot()\n\n debug(`Using project root`, root)\n return getCliConfig(root.directory)\n }\n\n /**\n * Get the project ID from passed flags or (if not provided) the CLI config.\n *\n * Optionally accepts a `fallback` function that is called when no project ID\n * can be determined from flags or config. This allows commands to provide\n * interactive project selection while keeping the prompt logic in the CLI package.\n *\n * If the fallback throws a `NonInteractiveError` (e.g. because the terminal is\n * not interactive), it falls through to the standard error with suggestions.\n *\n * Optionally accepts a `deprecatedFlagName` for commands that have a deprecated\n * flag (e.g. `--project`) that should be checked after `--project-id` but before\n * the CLI config.\n *\n * @returns The project ID.\n */\n protected async getProjectId(options?: {\n deprecatedFlagName?: string\n fallback?: () => Promise<string>\n }): Promise<string> {\n const hasProjectFlag = this.ctor.flags != null && 'project-id' in this.ctor.flags\n\n // Check --project-id flag first\n if (hasProjectFlag) {\n const flagProjectId =\n 'project-id' in this.flags && typeof this.flags['project-id'] === 'string'\n ? this.flags['project-id']\n : undefined\n\n if (flagProjectId) return flagProjectId\n }\n\n // Check deprecated flag (e.g. --project) before CLI config\n if (options?.deprecatedFlagName) {\n const deprecatedValue =\n options.deprecatedFlagName in this.flags &&\n typeof this.flags[options.deprecatedFlagName] === 'string'\n ? (this.flags[options.deprecatedFlagName] as string)\n : undefined\n\n if (deprecatedValue) return deprecatedValue\n }\n\n // Fall back to CLI config\n try {\n const config = await this.getCliConfig()\n const configProjectId = config.api?.projectId\n if (configProjectId) return configProjectId\n } catch (err) {\n if (!(err instanceof ProjectRootNotFoundError)) throw err\n // No project root — fall through to fallback/error\n }\n\n // Offer interactive selection if a fallback was provided\n if (options?.fallback) {\n try {\n return await options.fallback()\n } catch (err) {\n if (!(err instanceof NonInteractiveError)) throw err\n // Non-interactive: throw with actionable suggestions\n throw new ProjectRootNotFoundError('Unable to determine project ID', {\n cause: err,\n suggestions: [\n ...(hasProjectFlag ? ['Providing a project ID: --project-id <project-id>'] : []),\n 'Running this command from within a Sanity project directory',\n 'Running in an interactive terminal to get a project selection prompt',\n ],\n })\n }\n }\n\n throw new ProjectRootNotFoundError('Unable to determine project ID', {\n suggestions: [\n ...(hasProjectFlag ? ['Providing a project ID: --project-id <project-id>'] : []),\n 'Running this command from within a Sanity project directory',\n ],\n })\n }\n\n /**\n * Get the project's root directory by resolving the config\n *\n * @returns The project root result.\n */\n protected getProjectRoot(): Promise<ProjectRootResult> {\n return findProjectRoot(process.cwd())\n }\n\n public async init(): Promise<void> {\n const {args, flags} = await this.parse({\n args: this.ctor.args,\n baseFlags: (super.ctor as typeof SanityCommand).baseFlags,\n enableJsonFlag: this.ctor.enableJsonFlag,\n flags: this.ctor.flags,\n strict: this.ctor.strict,\n })\n\n this.args = args as Args<T>\n this.flags = flags as Flags<T>\n this.telemetry = getCliTelemetry()\n\n await super.init()\n }\n\n /**\n * Check if the command is running in unattended mode.\n *\n * This means the command should not ask for user input, instead using defaults where\n * possible, and if that does not make sense (eg there's missing information), then we\n * should error out (remember to exit with a non-zero code).\n *\n * Most commands should take an explicit `--yes` flag to enable unattended mode, but\n * some commands may also be run in unattended mode if `process.stdin` is not a TTY\n * (eg when running in a CI environment).\n */\n protected isUnattended(): boolean {\n return this.flags.yes || !this.resolveIsInteractive()\n }\n\n /**\n * Resolver for checking if the terminal is interactive. Override in tests to provide mock values.\n *\n * @returns Whether the terminal is interactive.\n */\n protected resolveIsInteractive(): boolean {\n return isInteractive()\n }\n\n /**\n * Get the CLI config, returning an empty config if no project root is found.\n *\n * Use this instead of `getCliConfig()` in commands that can operate without a\n * project directory (e.g. when `--project-id` and `--dataset` flags are provided).\n *\n * @returns The CLI config, or an empty config object if no project root is found.\n */\n protected async tryGetCliConfig(): Promise<CliConfig> {\n try {\n return await this.getCliConfig()\n } catch (err) {\n if (!(err instanceof ProjectRootNotFoundError)) throw err\n return {}\n }\n }\n}\n"],"names":["styleText","Command","getCliConfig","findProjectRoot","subdebug","NonInteractiveError","ProjectRootNotFoundError","getGlobalCliClient","getProjectCliClient","getCliTelemetry","reportCliTraceError","isInteractive","debug","SanityCommand","args","flags","getGlobalApiClient","getProjectApiClient","output","error","bind","log","warn","telemetry","catch","err","name","message","logToStderr","exit","root","getProjectRoot","directory","getProjectId","options","hasProjectFlag","ctor","flagProjectId","undefined","deprecatedFlagName","deprecatedValue","config","configProjectId","api","projectId","fallback","cause","suggestions","process","cwd","init","parse","baseFlags","enableJsonFlag","strict","isUnattended","yes","resolveIsInteractive","tryGetCliConfig"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ClientConfig } from "@sanity/client";
2
2
  import { CLIError } from "@oclif/core/errors";
3
3
  import { Command } from "@oclif/core";
4
+ import { CommandError } from "@oclif/core/interfaces";
4
5
  import { ConfigEnv } from "vite";
5
6
  import ConfigStore from "configstore";
6
7
  import { ConsentStatus } from "@sanity/telemetry";
@@ -29,8 +30,8 @@ export declare function clearCliTelemetry(): void;
29
30
 
30
31
  /**
31
32
  * @public
32
- * Symbol used to store the CLI telemetry store on globalThis.
33
- * Use `getCliTelemetry()` to access the store instead of accessing this directly.
33
+ * Symbol used to store CLI telemetry state on globalThis.
34
+ * Use the accessor functions instead of accessing this directly.
34
35
  */
35
36
  export declare const CLI_TELEMETRY_SYMBOL: unique symbol;
36
37
 
@@ -1032,6 +1033,12 @@ export declare abstract class SanityCommand<
1032
1033
  * @returns The telemetry store.
1033
1034
  */
1034
1035
  protected telemetry: CLITelemetryStore;
1036
+ /**
1037
+ * Report real command errors to the CLI command trace.
1038
+ * User aborts (SIGINT, ExitPromptError) are not reported — the trace is left
1039
+ * incomplete, which accurately represents that the command was interrupted.
1040
+ */
1041
+ protected catch(err: CommandError): Promise<void>;
1035
1042
  /**
1036
1043
  * Get the CLI config.
1037
1044
  *
@@ -1104,10 +1111,15 @@ export declare type SanityOrgUser = {
1104
1111
  };
1105
1112
 
1106
1113
  /**
1107
- * Sets the global CLI telemetry store.
1114
+ * Sets the global CLI telemetry state.
1108
1115
  * @internal
1109
1116
  */
1110
- export declare function setCliTelemetry(telemetry: CLITelemetryStore): void;
1117
+ export declare function setCliTelemetry(
1118
+ telemetry: CLITelemetryStore,
1119
+ options?: {
1120
+ reportTraceError?: TraceErrorReporter;
1121
+ },
1122
+ ): void;
1111
1123
 
1112
1124
  /**
1113
1125
  * Set the config value for the given property.
@@ -1212,6 +1224,8 @@ declare interface TimeMeasurer {
1212
1224
  start: (name: string) => void;
1213
1225
  }
1214
1226
 
1227
+ declare type TraceErrorReporter = (error: Error) => void;
1228
+
1215
1229
  /**
1216
1230
  * Tries to find the studio config path, returning `undefined` if not found.
1217
1231
  *
@@ -2,26 +2,39 @@ import { ux } from '@oclif/core';
2
2
  import { noopLogger } from '@sanity/telemetry';
3
3
  /**
4
4
  * @public
5
- * Symbol used to store the CLI telemetry store on globalThis.
6
- * Use `getCliTelemetry()` to access the store instead of accessing this directly.
5
+ * Symbol used to store CLI telemetry state on globalThis.
6
+ * Use the accessor functions instead of accessing this directly.
7
7
  */ export const CLI_TELEMETRY_SYMBOL = Symbol.for('sanity.cli.telemetry');
8
+ function getState() {
9
+ return globalThis[CLI_TELEMETRY_SYMBOL];
10
+ }
8
11
  /**
9
12
  * @public
10
13
  */ export function getCliTelemetry() {
11
- const global = globalThis;
14
+ const state = getState();
12
15
  // This should never happen, but if it does, we return a noop logger to avoid errors.
13
- if (!global[CLI_TELEMETRY_SYMBOL]) {
16
+ if (!state) {
14
17
  ux.warn('CLI telemetry not initialized, returning noop logger');
15
18
  return noopLogger;
16
19
  }
17
- return global[CLI_TELEMETRY_SYMBOL];
20
+ return state.logger;
18
21
  }
19
22
  /**
20
- * Sets the global CLI telemetry store.
23
+ * Sets the global CLI telemetry state.
21
24
  * @internal
22
- */ export function setCliTelemetry(telemetry) {
23
- const global = globalThis;
24
- global[CLI_TELEMETRY_SYMBOL] = telemetry;
25
+ */ export function setCliTelemetry(telemetry, options) {
26
+ ;
27
+ globalThis[CLI_TELEMETRY_SYMBOL] = {
28
+ logger: telemetry,
29
+ reportTraceError: options?.reportTraceError
30
+ };
31
+ }
32
+ /**
33
+ * Reports an error to the CLI command trace. Called from SanityCommand.catch()
34
+ * for real command errors (not user aborts).
35
+ * @internal
36
+ */ export function reportCliTraceError(error) {
37
+ getState()?.reportTraceError?.(error);
25
38
  }
26
39
  /**
27
40
  * Clears the global CLI telemetry store.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/util/getCliTelemetry.ts"],"sourcesContent":["import {ux} from '@oclif/core'\nimport {noopLogger} from '@sanity/telemetry'\n\nimport {type CLITelemetryStore} from '../telemetry/types.js'\n\n/**\n * @public\n * Symbol used to store the CLI telemetry store on globalThis.\n * Use `getCliTelemetry()` to access the store instead of accessing this directly.\n */\nexport const CLI_TELEMETRY_SYMBOL = Symbol.for('sanity.cli.telemetry')\n\ntype GlobalWithTelemetry = typeof globalThis & {\n [CLI_TELEMETRY_SYMBOL]?: CLITelemetryStore\n}\n\n/**\n * @public\n */\nexport function getCliTelemetry(): CLITelemetryStore {\n const global = globalThis as GlobalWithTelemetry\n // This should never happen, but if it does, we return a noop logger to avoid errors.\n if (!global[CLI_TELEMETRY_SYMBOL]) {\n ux.warn('CLI telemetry not initialized, returning noop logger')\n return noopLogger\n }\n\n return global[CLI_TELEMETRY_SYMBOL]\n}\n\n/**\n * Sets the global CLI telemetry store.\n * @internal\n */\nexport function setCliTelemetry(telemetry: CLITelemetryStore): void {\n const global = globalThis as GlobalWithTelemetry\n global[CLI_TELEMETRY_SYMBOL] = telemetry\n}\n\n/**\n * Clears the global CLI telemetry store.\n * @internal\n */\nexport function clearCliTelemetry(): void {\n const global = globalThis as GlobalWithTelemetry\n delete global[CLI_TELEMETRY_SYMBOL]\n}\n"],"names":["ux","noopLogger","CLI_TELEMETRY_SYMBOL","Symbol","for","getCliTelemetry","global","globalThis","warn","setCliTelemetry","telemetry","clearCliTelemetry"],"mappings":"AAAA,SAAQA,EAAE,QAAO,cAAa;AAC9B,SAAQC,UAAU,QAAO,oBAAmB;AAI5C;;;;CAIC,GACD,OAAO,MAAMC,uBAAuBC,OAAOC,GAAG,CAAC,wBAAuB;AAMtE;;CAEC,GACD,OAAO,SAASC;IACd,MAAMC,SAASC;IACf,qFAAqF;IACrF,IAAI,CAACD,MAAM,CAACJ,qBAAqB,EAAE;QACjCF,GAAGQ,IAAI,CAAC;QACR,OAAOP;IACT;IAEA,OAAOK,MAAM,CAACJ,qBAAqB;AACrC;AAEA;;;CAGC,GACD,OAAO,SAASO,gBAAgBC,SAA4B;IAC1D,MAAMJ,SAASC;IACfD,MAAM,CAACJ,qBAAqB,GAAGQ;AACjC;AAEA;;;CAGC,GACD,OAAO,SAASC;IACd,MAAML,SAASC;IACf,OAAOD,MAAM,CAACJ,qBAAqB;AACrC"}
1
+ {"version":3,"sources":["../../src/util/getCliTelemetry.ts"],"sourcesContent":["import {ux} from '@oclif/core'\nimport {noopLogger} from '@sanity/telemetry'\n\nimport {type CLITelemetryStore} from '../telemetry/types.js'\n\n/**\n * @public\n * Symbol used to store CLI telemetry state on globalThis.\n * Use the accessor functions instead of accessing this directly.\n */\nexport const CLI_TELEMETRY_SYMBOL = Symbol.for('sanity.cli.telemetry')\n\ntype TraceErrorReporter = (error: Error) => void\n\ninterface CliTelemetryState {\n logger: CLITelemetryStore\n\n reportTraceError?: TraceErrorReporter\n}\n\ntype GlobalWithTelemetry = typeof globalThis & {\n [CLI_TELEMETRY_SYMBOL]?: CliTelemetryState\n}\n\nfunction getState(): CliTelemetryState | undefined {\n return (globalThis as GlobalWithTelemetry)[CLI_TELEMETRY_SYMBOL]\n}\n\n/**\n * @public\n */\nexport function getCliTelemetry(): CLITelemetryStore {\n const state = getState()\n // This should never happen, but if it does, we return a noop logger to avoid errors.\n if (!state) {\n ux.warn('CLI telemetry not initialized, returning noop logger')\n return noopLogger\n }\n\n return state.logger\n}\n\n/**\n * Sets the global CLI telemetry state.\n * @internal\n */\nexport function setCliTelemetry(\n telemetry: CLITelemetryStore,\n options?: {reportTraceError?: TraceErrorReporter},\n): void {\n ;(globalThis as GlobalWithTelemetry)[CLI_TELEMETRY_SYMBOL] = {\n logger: telemetry,\n reportTraceError: options?.reportTraceError,\n }\n}\n\n/**\n * Reports an error to the CLI command trace. Called from SanityCommand.catch()\n * for real command errors (not user aborts).\n * @internal\n */\nexport function reportCliTraceError(error: Error): void {\n getState()?.reportTraceError?.(error)\n}\n\n/**\n * Clears the global CLI telemetry store.\n * @internal\n */\nexport function clearCliTelemetry(): void {\n const global = globalThis as GlobalWithTelemetry\n delete global[CLI_TELEMETRY_SYMBOL]\n}\n"],"names":["ux","noopLogger","CLI_TELEMETRY_SYMBOL","Symbol","for","getState","globalThis","getCliTelemetry","state","warn","logger","setCliTelemetry","telemetry","options","reportTraceError","reportCliTraceError","error","clearCliTelemetry","global"],"mappings":"AAAA,SAAQA,EAAE,QAAO,cAAa;AAC9B,SAAQC,UAAU,QAAO,oBAAmB;AAI5C;;;;CAIC,GACD,OAAO,MAAMC,uBAAuBC,OAAOC,GAAG,CAAC,wBAAuB;AActE,SAASC;IACP,OAAO,AAACC,UAAkC,CAACJ,qBAAqB;AAClE;AAEA;;CAEC,GACD,OAAO,SAASK;IACd,MAAMC,QAAQH;IACd,qFAAqF;IACrF,IAAI,CAACG,OAAO;QACVR,GAAGS,IAAI,CAAC;QACR,OAAOR;IACT;IAEA,OAAOO,MAAME,MAAM;AACrB;AAEA;;;CAGC,GACD,OAAO,SAASC,gBACdC,SAA4B,EAC5BC,OAAiD;;IAE/CP,UAAkC,CAACJ,qBAAqB,GAAG;QAC3DQ,QAAQE;QACRE,kBAAkBD,SAASC;IAC7B;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASC,oBAAoBC,KAAY;IAC9CX,YAAYS,mBAAmBE;AACjC;AAEA;;;CAGC,GACD,OAAO,SAASC;IACd,MAAMC,SAASZ;IACf,OAAOY,MAAM,CAAChB,qBAAqB;AACrC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/cli-core",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Sanity CLI core package",
5
5
  "keywords": [
6
6
  "cli",
@@ -86,9 +86,9 @@
86
86
  "sanity": "^5.14.1",
87
87
  "typescript": "^5.9.3",
88
88
  "vitest": "^4.0.18",
89
- "@repo/tsconfig": "3.70.0",
90
89
  "@repo/package.config": "0.0.1",
91
- "@sanity/eslint-config-cli": "1.0.0"
90
+ "@sanity/eslint-config-cli": "1.0.0",
91
+ "@repo/tsconfig": "3.70.0"
92
92
  },
93
93
  "peerDependencies": {
94
94
  "@sanity/telemetry": ">=0.8.1 <0.9.0"