@sanity/cli 7.14.0 → 7.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -528,13 +528,12 @@ Diagnose potential issues with local Blueprint and remote Stack configuration
528
528
 
529
529
  ```
530
530
  USAGE
531
- $ sanity blueprints doctor [--json] [-p <value>] [--verbose] [--fix]
531
+ $ sanity blueprints doctor [--json] [-p <value>] [--fix]
532
532
 
533
533
  FLAGS
534
534
  -p, --path=<value> [env: SANITY_BLUEPRINT_PATH] Path to a Blueprint file or directory containing one
535
535
  --fix Interactively fix configuration issues
536
536
  --json Format output as json
537
- --[no-]verbose Verbose output; defaults to true
538
537
 
539
538
  DESCRIPTION
540
539
  Diagnose potential issues with local Blueprint and remote Stack configuration
@@ -545,6 +544,8 @@ DESCRIPTION
545
544
  Run this command when encountering errors with other Blueprint commands. Use --fix to interactively resolve detected
546
545
  issues.
547
546
 
547
+ Supports --json for programmatic consumption of diagnostic results.
548
+
548
549
  EXAMPLES
549
550
  $ sanity blueprints doctor
550
551
 
@@ -2071,7 +2072,7 @@ Add or set an environment variable for a deployed function
2071
2072
 
2072
2073
  ```
2073
2074
  USAGE
2074
- $ sanity functions env add NAME KEY VALUE [--json]
2075
+ $ sanity functions env add NAME KEY VALUE [--json] [--stack <value>]
2075
2076
 
2076
2077
  ARGUMENTS
2077
2078
  NAME The name of the Sanity Function
@@ -2079,7 +2080,8 @@ ARGUMENTS
2079
2080
  VALUE The value of the environment variable
2080
2081
 
2081
2082
  FLAGS
2082
- --json Format output as json
2083
+ --json Format output as json
2084
+ --stack=<value> Stack name or ID to use instead of the locally configured Stack
2083
2085
 
2084
2086
  DESCRIPTION
2085
2087
  Add or set an environment variable for a deployed function
@@ -2091,6 +2093,8 @@ DESCRIPTION
2091
2093
 
2092
2094
  EXAMPLES
2093
2095
  $ sanity functions env add MyFunction API_URL https://api.example.com/
2096
+
2097
+ $ sanity functions env add --stack <name-or-id> MyFunction API_URL https://api.example.com/
2094
2098
  ```
2095
2099
 
2096
2100
  ## `sanity functions env list NAME`
@@ -2099,13 +2103,14 @@ List environment variables for a deployed function
2099
2103
 
2100
2104
  ```
2101
2105
  USAGE
2102
- $ sanity functions env list NAME [--json]
2106
+ $ sanity functions env list NAME [--json] [--stack <value>]
2103
2107
 
2104
2108
  ARGUMENTS
2105
2109
  NAME The name of the Sanity Function
2106
2110
 
2107
2111
  FLAGS
2108
- --json Format output as json
2112
+ --json Format output as json
2113
+ --stack=<value> Stack name or ID to use instead of the locally configured Stack
2109
2114
 
2110
2115
  DESCRIPTION
2111
2116
  List environment variables for a deployed function
@@ -2116,6 +2121,8 @@ DESCRIPTION
2116
2121
 
2117
2122
  EXAMPLES
2118
2123
  $ sanity functions env list MyFunction
2124
+
2125
+ $ sanity functions env list --stack <name-or-id> MyFunction
2119
2126
  ```
2120
2127
 
2121
2128
  ## `sanity functions env remove NAME KEY`
@@ -2124,14 +2131,15 @@ Remove an environment variable from a deployed function
2124
2131
 
2125
2132
  ```
2126
2133
  USAGE
2127
- $ sanity functions env remove NAME KEY [--json]
2134
+ $ sanity functions env remove NAME KEY [--json] [--stack <value>]
2128
2135
 
2129
2136
  ARGUMENTS
2130
2137
  NAME The name of the Sanity Function
2131
2138
  KEY The name of the environment variable
2132
2139
 
2133
2140
  FLAGS
2134
- --json Format output as json
2141
+ --json Format output as json
2142
+ --stack=<value> Stack name or ID to use instead of the locally configured Stack
2135
2143
 
2136
2144
  DESCRIPTION
2137
2145
  Remove an environment variable from a deployed function
@@ -2143,6 +2151,8 @@ DESCRIPTION
2143
2151
 
2144
2152
  EXAMPLES
2145
2153
  $ sanity functions env remove MyFunction API_URL
2154
+
2155
+ $ sanity functions env remove --stack <name-or-id> MyFunction API_URL
2146
2156
  ```
2147
2157
 
2148
2158
  ## `sanity functions logs [NAME]`
@@ -2616,14 +2626,14 @@ Log in to your Sanity account
2616
2626
 
2617
2627
  ```
2618
2628
  USAGE
2619
- $ sanity login [--open] [--provider <providerId> | --sso <slug> | --with-token] [--sso-provider <name> ]
2629
+ $ sanity login [--with-token | --provider <providerId> | --sso <slug>] [--open] [--sso-provider <name> ]
2620
2630
 
2621
2631
  FLAGS
2632
+ --with-token Read token from standard input
2622
2633
  --[no-]open Open a browser window to log in (`--no-open` only prints URL)
2623
2634
  --provider=<providerId> Log in using a provider ID (google, github, sanity, vercel)
2624
2635
  --sso=<slug> Log in using Single Sign-On, using the given organization slug
2625
2636
  --sso-provider=<name> Select a specific SSO provider by name (use with --sso)
2626
- --with-token Read token from standard input
2627
2637
 
2628
2638
  DESCRIPTION
2629
2639
  Log in to your Sanity account
@@ -2633,6 +2643,10 @@ EXAMPLES
2633
2643
 
2634
2644
  $ sanity login
2635
2645
 
2646
+ Log in using a token from standard input
2647
+
2648
+ $ sanity login --with-token < token.txt
2649
+
2636
2650
  Login with GitHub provider, but do not open a browser window automatically
2637
2651
 
2638
2652
  $ sanity login --provider github --no-open
@@ -2644,10 +2658,6 @@ EXAMPLES
2644
2658
  Log in using a specific SSO provider within an organization
2645
2659
 
2646
2660
  $ sanity login --sso my-organization --sso-provider "Okta SSO"
2647
-
2648
- Log in using a token from standard input
2649
-
2650
- $ sanity login --with-token < token.txt
2651
2661
  ```
2652
2662
 
2653
2663
  ## `sanity logout`
@@ -38,6 +38,16 @@ const IS_README_GENERATION = (process.argv[process.argv.indexOf('readme') - 1] ?
38
38
  formatTopic(topic) {
39
39
  return prefixBinName(super.formatTopic(topic));
40
40
  }
41
+ getCommandHelpClass(command) {
42
+ const commandHelp = super.getCommandHelpClass(command);
43
+ if (command.id === 'login') {
44
+ commandHelp.opts = {
45
+ ...commandHelp.opts,
46
+ flagSortOrder: 'none'
47
+ };
48
+ }
49
+ return commandHelp;
50
+ }
41
51
  async showHelp(argv) {
42
52
  return super.showHelp(resolveTopicAliasInArgv(argv));
43
53
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/SanityHelp.ts"],"sourcesContent":["import {Command, Help, Interfaces} from '@oclif/core'\nimport {getBinCommand, getRunningPackageManager} from '@sanity/cli-core/package-manager'\n\nimport {topicAliases} from './topicAliases.js'\n\n// Reverse map: alias name → canonical topic name\nconst aliasToCanonical = new Map<string, string>()\nfor (const [canonical, aliases] of Object.entries(topicAliases)) {\n for (const alias of aliases) {\n aliasToCanonical.set(alias, canonical)\n }\n}\n\n// Running `oclif readme`, we don't want to apply the `prefixBinName` transformation,\n// as it will include whatever pm was used to spawn the script in the generated readme.\n// argv will contain something like [nodeBinPath, oclifBinPath, 'readme', …] so check\n// for 'readme' with a preceeding argument that includes 'oclif' to be sure.\nconst IS_README_GENERATION = (process.argv[process.argv.indexOf('readme') - 1] ?? '').includes(\n 'oclif',\n)\n\n/**\n * Custom Help class for Sanity CLI that overrides the default help formatting to\n * prefix the bin name (e.g., `npx sanity`, `yarn sanity`, etc.) in the help text,\n * and to replace `sanity init` references with the appropriate `create` command\n * for the detected package manager when needed.\n *\n * @internal\n */\nexport default class SanityHelp extends Help {\n protected formatCommand(command: Command.Loadable): string {\n let help = super.formatCommand(command)\n\n // When `sanity init` is called, but originates from the `create-sanity`\n // package/binary (eg the one used by `npm create sanity@latest` etc), we want to\n // customize the help text to show that command instead of `sanity init`.\n const isFromCreate = process.argv.includes('--from-create') && command.id === 'init'\n if (isFromCreate) {\n help = replaceInitWithCreateCommand(help)\n }\n\n return prefixBinName(help)\n }\n\n protected formatRoot(): string {\n return prefixBinName(super.formatRoot())\n }\n\n protected formatTopic(topic: Interfaces.Topic): string {\n return prefixBinName(super.formatTopic(topic))\n }\n\n async showHelp(argv: string[]): Promise<void> {\n return super.showHelp(resolveTopicAliasInArgv(argv))\n }\n}\n\n/**\n * @internal\n */\nexport function prefixBinName(help: string): string {\n if (IS_README_GENERATION) return help\n const binCommand = getBinCommand()\n if (binCommand === 'sanity') return help\n return help.replaceAll('$ sanity', `$ ${binCommand}`)\n}\n\n/**\n * Replace `sanity init` references in help text with the equivalent `create` command\n * for the detected package manager. Lines ending in just `sanity init\\n` (no flags)\n * are replaced without a flag separator, while lines with flags get the separator\n * (eg `--` for npm) so the flags are forwarded correctly.\n *\n * @internal\n */\nexport function replaceInitWithCreateCommand(help: string): string {\n const createCmd = guessCreateCommand()\n const flagSeparator = needsFlagSeparator() ? ' --' : ''\n\n // First replace all `sanity init` references that ends with a newline with the\n // create variant that does not include any flag separator (eg `--`). Then replace\n // the other references that do. Most package managers do not require the `--`\n // separator, but npm does. Only include it if we need to, as the commands look\n // cleaner without it.\n return help\n .replaceAll(/(\\s+)sanity\\s+init\\s*\\n/g, `$1${createCmd}\\n`)\n .replaceAll(/(\\s+)sanity(\\s+)init/g, `$1${createCmd}${flagSeparator}`)\n}\n\nfunction guessCreateCommand() {\n const pm = getRunningPackageManager()\n if (pm === 'yarn') return `yarn create sanity`\n if (pm === 'bun') return `bun create sanity@latest`\n if (pm === 'pnpm') return `pnpm create sanity@latest`\n return `npm create sanity@latest`\n}\n\nfunction needsFlagSeparator() {\n const pm = getRunningPackageManager()\n return pm === 'npm' || !pm\n}\n\n/**\n * Replace the first positional argument in argv with the canonical topic name\n * if it is a known topic alias. This ensures that `sanity dataset --help`\n * resolves to the same help output as `sanity datasets --help`.\n *\n * Without this, `--help` bypasses the command_not_found hook (which normally\n * handles alias resolution) and the help system fails to find the topic.\n *\n * @internal\n */\nexport function resolveTopicAliasInArgv(argv: string[]): string[] {\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n if (arg === '--') break\n if (arg.startsWith('-')) continue\n\n // First positional argument is the topic/command name\n const canonical = aliasToCanonical.get(arg)\n if (canonical) {\n const resolved = [...argv]\n resolved[i] = canonical\n return resolved\n }\n break\n }\n return argv\n}\n"],"names":["Help","getBinCommand","getRunningPackageManager","topicAliases","aliasToCanonical","Map","canonical","aliases","Object","entries","alias","set","IS_README_GENERATION","process","argv","indexOf","includes","SanityHelp","formatCommand","command","help","isFromCreate","id","replaceInitWithCreateCommand","prefixBinName","formatRoot","formatTopic","topic","showHelp","resolveTopicAliasInArgv","binCommand","replaceAll","createCmd","guessCreateCommand","flagSeparator","needsFlagSeparator","pm","i","length","arg","startsWith","get","resolved"],"mappings":"AAAA,SAAiBA,IAAI,QAAmB,cAAa;AACrD,SAAQC,aAAa,EAAEC,wBAAwB,QAAO,mCAAkC;AAExF,SAAQC,YAAY,QAAO,oBAAmB;AAE9C,iDAAiD;AACjD,MAAMC,mBAAmB,IAAIC;AAC7B,KAAK,MAAM,CAACC,WAAWC,QAAQ,IAAIC,OAAOC,OAAO,CAACN,cAAe;IAC/D,KAAK,MAAMO,SAASH,QAAS;QAC3BH,iBAAiBO,GAAG,CAACD,OAAOJ;IAC9B;AACF;AAEA,qFAAqF;AACrF,uFAAuF;AACvF,qFAAqF;AACrF,4EAA4E;AAC5E,MAAMM,uBAAuB,AAACC,CAAAA,QAAQC,IAAI,CAACD,QAAQC,IAAI,CAACC,OAAO,CAAC,YAAY,EAAE,IAAI,EAAC,EAAGC,QAAQ,CAC5F;AAGF;;;;;;;CAOC,GACD,eAAe,MAAMC,mBAAmBjB;IAC5BkB,cAAcC,OAAyB,EAAU;QACzD,IAAIC,OAAO,KAAK,CAACF,cAAcC;QAE/B,wEAAwE;QACxE,iFAAiF;QACjF,yEAAyE;QACzE,MAAME,eAAeR,QAAQC,IAAI,CAACE,QAAQ,CAAC,oBAAoBG,QAAQG,EAAE,KAAK;QAC9E,IAAID,cAAc;YAChBD,OAAOG,6BAA6BH;QACtC;QAEA,OAAOI,cAAcJ;IACvB;IAEUK,aAAqB;QAC7B,OAAOD,cAAc,KAAK,CAACC;IAC7B;IAEUC,YAAYC,KAAuB,EAAU;QACrD,OAAOH,cAAc,KAAK,CAACE,YAAYC;IACzC;IAEA,MAAMC,SAASd,IAAc,EAAiB;QAC5C,OAAO,KAAK,CAACc,SAASC,wBAAwBf;IAChD;AACF;AAEA;;CAEC,GACD,OAAO,SAASU,cAAcJ,IAAY;IACxC,IAAIR,sBAAsB,OAAOQ;IACjC,MAAMU,aAAa7B;IACnB,IAAI6B,eAAe,UAAU,OAAOV;IACpC,OAAOA,KAAKW,UAAU,CAAC,YAAY,CAAC,EAAE,EAAED,YAAY;AACtD;AAEA;;;;;;;CAOC,GACD,OAAO,SAASP,6BAA6BH,IAAY;IACvD,MAAMY,YAAYC;IAClB,MAAMC,gBAAgBC,uBAAuB,QAAQ;IAErD,+EAA+E;IAC/E,kFAAkF;IAClF,8EAA8E;IAC9E,+EAA+E;IAC/E,sBAAsB;IACtB,OAAOf,KACJW,UAAU,CAAC,4BAA4B,CAAC,EAAE,EAAEC,UAAU,EAAE,CAAC,EACzDD,UAAU,CAAC,yBAAyB,CAAC,EAAE,EAAEC,YAAYE,eAAe;AACzE;AAEA,SAASD;IACP,MAAMG,KAAKlC;IACX,IAAIkC,OAAO,QAAQ,OAAO,CAAC,kBAAkB,CAAC;IAC9C,IAAIA,OAAO,OAAO,OAAO,CAAC,wBAAwB,CAAC;IACnD,IAAIA,OAAO,QAAQ,OAAO,CAAC,yBAAyB,CAAC;IACrD,OAAO,CAAC,wBAAwB,CAAC;AACnC;AAEA,SAASD;IACP,MAAMC,KAAKlC;IACX,OAAOkC,OAAO,SAAS,CAACA;AAC1B;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASP,wBAAwBf,IAAc;IACpD,IAAK,IAAIuB,IAAI,GAAGA,IAAIvB,KAAKwB,MAAM,EAAED,IAAK;QACpC,MAAME,MAAMzB,IAAI,CAACuB,EAAE;QACnB,IAAIE,QAAQ,MAAM;QAClB,IAAIA,IAAIC,UAAU,CAAC,MAAM;QAEzB,sDAAsD;QACtD,MAAMlC,YAAYF,iBAAiBqC,GAAG,CAACF;QACvC,IAAIjC,WAAW;YACb,MAAMoC,WAAW;mBAAI5B;aAAK;YAC1B4B,QAAQ,CAACL,EAAE,GAAG/B;YACd,OAAOoC;QACT;QACA;IACF;IACA,OAAO5B;AACT"}
1
+ {"version":3,"sources":["../src/SanityHelp.ts"],"sourcesContent":["import {Command, CommandHelp, Help, Interfaces} from '@oclif/core'\nimport {getBinCommand, getRunningPackageManager} from '@sanity/cli-core/package-manager'\n\nimport {topicAliases} from './topicAliases.js'\n\n// Reverse map: alias name → canonical topic name\nconst aliasToCanonical = new Map<string, string>()\nfor (const [canonical, aliases] of Object.entries(topicAliases)) {\n for (const alias of aliases) {\n aliasToCanonical.set(alias, canonical)\n }\n}\n\n// Running `oclif readme`, we don't want to apply the `prefixBinName` transformation,\n// as it will include whatever pm was used to spawn the script in the generated readme.\n// argv will contain something like [nodeBinPath, oclifBinPath, 'readme', …] so check\n// for 'readme' with a preceeding argument that includes 'oclif' to be sure.\nconst IS_README_GENERATION = (process.argv[process.argv.indexOf('readme') - 1] ?? '').includes(\n 'oclif',\n)\n\n/**\n * Custom Help class for Sanity CLI that overrides the default help formatting to\n * prefix the bin name (e.g., `npx sanity`, `yarn sanity`, etc.) in the help text,\n * and to replace `sanity init` references with the appropriate `create` command\n * for the detected package manager when needed.\n *\n * @internal\n */\nexport default class SanityHelp extends Help {\n protected formatCommand(command: Command.Loadable): string {\n let help = super.formatCommand(command)\n\n // When `sanity init` is called, but originates from the `create-sanity`\n // package/binary (eg the one used by `npm create sanity@latest` etc), we want to\n // customize the help text to show that command instead of `sanity init`.\n const isFromCreate = process.argv.includes('--from-create') && command.id === 'init'\n if (isFromCreate) {\n help = replaceInitWithCreateCommand(help)\n }\n\n return prefixBinName(help)\n }\n\n protected formatRoot(): string {\n return prefixBinName(super.formatRoot())\n }\n\n protected formatTopic(topic: Interfaces.Topic): string {\n return prefixBinName(super.formatTopic(topic))\n }\n\n protected override getCommandHelpClass(command: Command.Loadable): CommandHelp {\n const commandHelp = super.getCommandHelpClass(command)\n if (command.id === 'login') {\n commandHelp.opts = {...commandHelp.opts, flagSortOrder: 'none'}\n }\n return commandHelp\n }\n\n async showHelp(argv: string[]): Promise<void> {\n return super.showHelp(resolveTopicAliasInArgv(argv))\n }\n}\n\n/**\n * @internal\n */\nexport function prefixBinName(help: string): string {\n if (IS_README_GENERATION) return help\n const binCommand = getBinCommand()\n if (binCommand === 'sanity') return help\n return help.replaceAll('$ sanity', `$ ${binCommand}`)\n}\n\n/**\n * Replace `sanity init` references in help text with the equivalent `create` command\n * for the detected package manager. Lines ending in just `sanity init\\n` (no flags)\n * are replaced without a flag separator, while lines with flags get the separator\n * (eg `--` for npm) so the flags are forwarded correctly.\n *\n * @internal\n */\nexport function replaceInitWithCreateCommand(help: string): string {\n const createCmd = guessCreateCommand()\n const flagSeparator = needsFlagSeparator() ? ' --' : ''\n\n // First replace all `sanity init` references that ends with a newline with the\n // create variant that does not include any flag separator (eg `--`). Then replace\n // the other references that do. Most package managers do not require the `--`\n // separator, but npm does. Only include it if we need to, as the commands look\n // cleaner without it.\n return help\n .replaceAll(/(\\s+)sanity\\s+init\\s*\\n/g, `$1${createCmd}\\n`)\n .replaceAll(/(\\s+)sanity(\\s+)init/g, `$1${createCmd}${flagSeparator}`)\n}\n\nfunction guessCreateCommand() {\n const pm = getRunningPackageManager()\n if (pm === 'yarn') return `yarn create sanity`\n if (pm === 'bun') return `bun create sanity@latest`\n if (pm === 'pnpm') return `pnpm create sanity@latest`\n return `npm create sanity@latest`\n}\n\nfunction needsFlagSeparator() {\n const pm = getRunningPackageManager()\n return pm === 'npm' || !pm\n}\n\n/**\n * Replace the first positional argument in argv with the canonical topic name\n * if it is a known topic alias. This ensures that `sanity dataset --help`\n * resolves to the same help output as `sanity datasets --help`.\n *\n * Without this, `--help` bypasses the command_not_found hook (which normally\n * handles alias resolution) and the help system fails to find the topic.\n *\n * @internal\n */\nexport function resolveTopicAliasInArgv(argv: string[]): string[] {\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n if (arg === '--') break\n if (arg.startsWith('-')) continue\n\n // First positional argument is the topic/command name\n const canonical = aliasToCanonical.get(arg)\n if (canonical) {\n const resolved = [...argv]\n resolved[i] = canonical\n return resolved\n }\n break\n }\n return argv\n}\n"],"names":["Help","getBinCommand","getRunningPackageManager","topicAliases","aliasToCanonical","Map","canonical","aliases","Object","entries","alias","set","IS_README_GENERATION","process","argv","indexOf","includes","SanityHelp","formatCommand","command","help","isFromCreate","id","replaceInitWithCreateCommand","prefixBinName","formatRoot","formatTopic","topic","getCommandHelpClass","commandHelp","opts","flagSortOrder","showHelp","resolveTopicAliasInArgv","binCommand","replaceAll","createCmd","guessCreateCommand","flagSeparator","needsFlagSeparator","pm","i","length","arg","startsWith","get","resolved"],"mappings":"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"}
@@ -10,6 +10,10 @@ export class LoginCommand extends SanityCommand {
10
10
  command: '<%= config.bin %> <%= command.id %>',
11
11
  description: 'Log in using default settings'
12
12
  },
13
+ {
14
+ command: '<%= config.bin %> <%= command.id %> --with-token < token.txt',
15
+ description: 'Log in using a token from standard input'
16
+ },
13
17
  {
14
18
  command: '<%= config.bin %> <%= command.id %> --provider github --no-open',
15
19
  description: 'Login with GitHub provider, but do not open a browser window automatically'
@@ -21,13 +25,17 @@ export class LoginCommand extends SanityCommand {
21
25
  {
22
26
  command: '<%= config.bin %> <%= command.id %> --sso my-organization --sso-provider "Okta SSO"',
23
27
  description: 'Log in using a specific SSO provider within an organization'
24
- },
25
- {
26
- command: '<%= config.bin %> <%= command.id %> --with-token < token.txt',
27
- description: 'Log in using a token from standard input'
28
28
  }
29
29
  ];
30
30
  static flags = {
31
+ 'with-token': Flags.boolean({
32
+ description: 'Read token from standard input',
33
+ exclusive: [
34
+ 'provider',
35
+ 'sso'
36
+ ]
37
+ }),
38
+ // eslint-disable-next-line perfectionist/sort-objects -- Keep token login first in generated usage.
31
39
  experimental: Flags.boolean({
32
40
  default: false,
33
41
  hidden: true
@@ -59,13 +67,6 @@ export class LoginCommand extends SanityCommand {
59
67
  ],
60
68
  description: 'Select a specific SSO provider by name (use with --sso)',
61
69
  helpValue: '<name>'
62
- }),
63
- 'with-token': Flags.boolean({
64
- description: 'Read token from standard input',
65
- exclusive: [
66
- 'provider',
67
- 'sso'
68
- ]
69
70
  })
70
71
  };
71
72
  async run() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/login.ts"],"sourcesContent":["import {text} from 'node:stream/consumers'\n\nimport {Command, Flags} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {exitCodes, SanityCommand} from '@sanity/cli-core'\n\nimport {login} from '../actions/auth/login/login.js'\nimport {LOGIN_PROVIDER_IDS} from '../actions/auth/login/loginInstructions.js'\n\nexport class LoginCommand extends SanityCommand<typeof LoginCommand> {\n static override description = 'Log in to your Sanity account'\n static override examples: Array<Command.Example> = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Log in using default settings',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --provider github --no-open',\n description: 'Login with GitHub provider, but do not open a browser window automatically',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --sso my-organization',\n description: 'Log in using Single Sign-On with the \"my-organization\" slug',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> --sso my-organization --sso-provider \"Okta SSO\"',\n description: 'Log in using a specific SSO provider within an organization',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --with-token < token.txt',\n description: 'Log in using a token from standard input',\n },\n ]\n static override flags = {\n experimental: Flags.boolean({\n default: false,\n hidden: true,\n }),\n open: Flags.boolean({\n allowNo: true,\n default: true,\n description: 'Open a browser window to log in (`--no-open` only prints URL)',\n }),\n provider: Flags.string({\n description: `Log in using a provider ID (${LOGIN_PROVIDER_IDS.join(', ')})`,\n exclusive: ['sso', 'with-token'],\n helpValue: '<providerId>',\n }),\n sso: Flags.string({\n description: 'Log in using Single Sign-On, using the given organization slug',\n exclusive: ['provider', 'with-token'],\n helpValue: '<slug>',\n }),\n 'sso-provider': Flags.string({\n dependsOn: ['sso'],\n description: 'Select a specific SSO provider by name (use with --sso)',\n helpValue: '<name>',\n }),\n 'with-token': Flags.boolean({\n description: 'Read token from standard input',\n exclusive: ['provider', 'sso'],\n }),\n } satisfies FlagInput\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(LoginCommand)\n const {'sso-provider': ssoProvider, 'with-token': withToken, ...loginFlags} = flags\n\n try {\n const token = withToken ? await readTokenFromStdin() : undefined\n\n await login({\n ...loginFlags,\n output: this.output,\n ssoProvider,\n telemetry: this.telemetry,\n token,\n })\n this.log('Login successful')\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n this.error(`Login failed: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n }\n}\n\nasync function readTokenFromStdin(): Promise<string> {\n if (process.stdin.isTTY) {\n throw new Error(\n 'Token is required on standard input. Run `sanity login --with-token < token.txt`.',\n )\n }\n\n return text(process.stdin)\n}\n"],"names":["text","Flags","exitCodes","SanityCommand","login","LOGIN_PROVIDER_IDS","LoginCommand","description","examples","command","flags","experimental","boolean","default","hidden","open","allowNo","provider","string","join","exclusive","helpValue","sso","dependsOn","run","parse","ssoProvider","withToken","loginFlags","token","readTokenFromStdin","undefined","output","telemetry","log","error","message","Error","String","exit","RUNTIME_ERROR","process","stdin","isTTY"],"mappings":"AAAA,SAAQA,IAAI,QAAO,wBAAuB;AAE1C,SAAiBC,KAAK,QAAO,cAAa;AAE1C,SAAQC,SAAS,EAAEC,aAAa,QAAO,mBAAkB;AAEzD,SAAQC,KAAK,QAAO,iCAAgC;AACpD,SAAQC,kBAAkB,QAAO,6CAA4C;AAE7E,OAAO,MAAMC,qBAAqBH;IAChC,OAAgBI,cAAc,gCAA+B;IAC7D,OAAgBC,WAAmC;QACjD;YACEC,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SACE;YACFF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IACD,OAAgBG,QAAQ;QACtBC,cAAcV,MAAMW,OAAO,CAAC;YAC1BC,SAAS;YACTC,QAAQ;QACV;QACAC,MAAMd,MAAMW,OAAO,CAAC;YAClBI,SAAS;YACTH,SAAS;YACTN,aAAa;QACf;QACAU,UAAUhB,MAAMiB,MAAM,CAAC;YACrBX,aAAa,CAAC,4BAA4B,EAAEF,mBAAmBc,IAAI,CAAC,MAAM,CAAC,CAAC;YAC5EC,WAAW;gBAAC;gBAAO;aAAa;YAChCC,WAAW;QACb;QACAC,KAAKrB,MAAMiB,MAAM,CAAC;YAChBX,aAAa;YACba,WAAW;gBAAC;gBAAY;aAAa;YACrCC,WAAW;QACb;QACA,gBAAgBpB,MAAMiB,MAAM,CAAC;YAC3BK,WAAW;gBAAC;aAAM;YAClBhB,aAAa;YACbc,WAAW;QACb;QACA,cAAcpB,MAAMW,OAAO,CAAC;YAC1BL,aAAa;YACba,WAAW;gBAAC;gBAAY;aAAM;QAChC;IACF,EAAqB;IAErB,MAAaI,MAAqB;QAChC,MAAM,EAACd,KAAK,EAAC,GAAG,MAAM,IAAI,CAACe,KAAK,CAACnB;QACjC,MAAM,EAAC,gBAAgBoB,WAAW,EAAE,cAAcC,SAAS,EAAE,GAAGC,YAAW,GAAGlB;QAE9E,IAAI;YACF,MAAMmB,QAAQF,YAAY,MAAMG,uBAAuBC;YAEvD,MAAM3B,MAAM;gBACV,GAAGwB,UAAU;gBACbI,QAAQ,IAAI,CAACA,MAAM;gBACnBN;gBACAO,WAAW,IAAI,CAACA,SAAS;gBACzBJ;YACF;YACA,IAAI,CAACK,GAAG,CAAC;QACX,EAAE,OAAOC,OAAO;YACd,MAAMC,UAAUD,iBAAiBE,QAAQF,MAAMC,OAAO,GAAGE,OAAOH;YAChE,IAAI,CAACA,KAAK,CAAC,CAAC,cAAc,EAAEC,SAAS,EAAE;gBAACG,MAAMrC,UAAUsC,aAAa;YAAA;QACvE;IACF;AACF;AAEA,eAAeV;IACb,IAAIW,QAAQC,KAAK,CAACC,KAAK,EAAE;QACvB,MAAM,IAAIN,MACR;IAEJ;IAEA,OAAOrC,KAAKyC,QAAQC,KAAK;AAC3B"}
1
+ {"version":3,"sources":["../../src/commands/login.ts"],"sourcesContent":["import {text} from 'node:stream/consumers'\n\nimport {Command, Flags} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {exitCodes, SanityCommand} from '@sanity/cli-core'\n\nimport {login} from '../actions/auth/login/login.js'\nimport {LOGIN_PROVIDER_IDS} from '../actions/auth/login/loginInstructions.js'\n\nexport class LoginCommand extends SanityCommand<typeof LoginCommand> {\n static override description = 'Log in to your Sanity account'\n static override examples: Array<Command.Example> = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Log in using default settings',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --with-token < token.txt',\n description: 'Log in using a token from standard input',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --provider github --no-open',\n description: 'Login with GitHub provider, but do not open a browser window automatically',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --sso my-organization',\n description: 'Log in using Single Sign-On with the \"my-organization\" slug',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> --sso my-organization --sso-provider \"Okta SSO\"',\n description: 'Log in using a specific SSO provider within an organization',\n },\n ]\n static override flags = {\n 'with-token': Flags.boolean({\n description: 'Read token from standard input',\n exclusive: ['provider', 'sso'],\n }),\n // eslint-disable-next-line perfectionist/sort-objects -- Keep token login first in generated usage.\n experimental: Flags.boolean({\n default: false,\n hidden: true,\n }),\n open: Flags.boolean({\n allowNo: true,\n default: true,\n description: 'Open a browser window to log in (`--no-open` only prints URL)',\n }),\n provider: Flags.string({\n description: `Log in using a provider ID (${LOGIN_PROVIDER_IDS.join(', ')})`,\n exclusive: ['sso', 'with-token'],\n helpValue: '<providerId>',\n }),\n sso: Flags.string({\n description: 'Log in using Single Sign-On, using the given organization slug',\n exclusive: ['provider', 'with-token'],\n helpValue: '<slug>',\n }),\n 'sso-provider': Flags.string({\n dependsOn: ['sso'],\n description: 'Select a specific SSO provider by name (use with --sso)',\n helpValue: '<name>',\n }),\n } satisfies FlagInput\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(LoginCommand)\n const {'sso-provider': ssoProvider, 'with-token': withToken, ...loginFlags} = flags\n\n try {\n const token = withToken ? await readTokenFromStdin() : undefined\n\n await login({\n ...loginFlags,\n output: this.output,\n ssoProvider,\n telemetry: this.telemetry,\n token,\n })\n this.log('Login successful')\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n this.error(`Login failed: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n }\n}\n\nasync function readTokenFromStdin(): Promise<string> {\n if (process.stdin.isTTY) {\n throw new Error(\n 'Token is required on standard input. Run `sanity login --with-token < token.txt`.',\n )\n }\n\n return text(process.stdin)\n}\n"],"names":["text","Flags","exitCodes","SanityCommand","login","LOGIN_PROVIDER_IDS","LoginCommand","description","examples","command","flags","boolean","exclusive","experimental","default","hidden","open","allowNo","provider","string","join","helpValue","sso","dependsOn","run","parse","ssoProvider","withToken","loginFlags","token","readTokenFromStdin","undefined","output","telemetry","log","error","message","Error","String","exit","RUNTIME_ERROR","process","stdin","isTTY"],"mappings":"AAAA,SAAQA,IAAI,QAAO,wBAAuB;AAE1C,SAAiBC,KAAK,QAAO,cAAa;AAE1C,SAAQC,SAAS,EAAEC,aAAa,QAAO,mBAAkB;AAEzD,SAAQC,KAAK,QAAO,iCAAgC;AACpD,SAAQC,kBAAkB,QAAO,6CAA4C;AAE7E,OAAO,MAAMC,qBAAqBH;IAChC,OAAgBI,cAAc,gCAA+B;IAC7D,OAAgBC,WAAmC;QACjD;YACEC,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SACE;YACFF,aAAa;QACf;KACD,CAAA;IACD,OAAgBG,QAAQ;QACtB,cAAcT,MAAMU,OAAO,CAAC;YAC1BJ,aAAa;YACbK,WAAW;gBAAC;gBAAY;aAAM;QAChC;QACA,oGAAoG;QACpGC,cAAcZ,MAAMU,OAAO,CAAC;YAC1BG,SAAS;YACTC,QAAQ;QACV;QACAC,MAAMf,MAAMU,OAAO,CAAC;YAClBM,SAAS;YACTH,SAAS;YACTP,aAAa;QACf;QACAW,UAAUjB,MAAMkB,MAAM,CAAC;YACrBZ,aAAa,CAAC,4BAA4B,EAAEF,mBAAmBe,IAAI,CAAC,MAAM,CAAC,CAAC;YAC5ER,WAAW;gBAAC;gBAAO;aAAa;YAChCS,WAAW;QACb;QACAC,KAAKrB,MAAMkB,MAAM,CAAC;YAChBZ,aAAa;YACbK,WAAW;gBAAC;gBAAY;aAAa;YACrCS,WAAW;QACb;QACA,gBAAgBpB,MAAMkB,MAAM,CAAC;YAC3BI,WAAW;gBAAC;aAAM;YAClBhB,aAAa;YACbc,WAAW;QACb;IACF,EAAqB;IAErB,MAAaG,MAAqB;QAChC,MAAM,EAACd,KAAK,EAAC,GAAG,MAAM,IAAI,CAACe,KAAK,CAACnB;QACjC,MAAM,EAAC,gBAAgBoB,WAAW,EAAE,cAAcC,SAAS,EAAE,GAAGC,YAAW,GAAGlB;QAE9E,IAAI;YACF,MAAMmB,QAAQF,YAAY,MAAMG,uBAAuBC;YAEvD,MAAM3B,MAAM;gBACV,GAAGwB,UAAU;gBACbI,QAAQ,IAAI,CAACA,MAAM;gBACnBN;gBACAO,WAAW,IAAI,CAACA,SAAS;gBACzBJ;YACF;YACA,IAAI,CAACK,GAAG,CAAC;QACX,EAAE,OAAOC,OAAO;YACd,MAAMC,UAAUD,iBAAiBE,QAAQF,MAAMC,OAAO,GAAGE,OAAOH;YAChE,IAAI,CAACA,KAAK,CAAC,CAAC,cAAc,EAAEC,SAAS,EAAE;gBAACG,MAAMrC,UAAUsC,aAAa;YAAA;QACvE;IACF;AACF;AAEA,eAAeV;IACb,IAAIW,QAAQC,KAAK,CAACC,KAAK,EAAE;QACvB,MAAM,IAAIN,MACR;IAEJ;IAEA,OAAOrC,KAAKyC,QAAQC,KAAK;AAC3B"}
@@ -7,6 +7,11 @@ import { allow, conditionalDenyFlags, conditionalPolicy, deny } from './policy.j
7
7
  const separatorIndex = field.indexOf('=');
8
8
  return separatorIndex > 0 && field[separatorIndex + 1] === '@';
9
9
  }
10
+ /** A `-H/--header` value that replaces the invocation's authentication. */ function headerOverridesAuthentication(header) {
11
+ if (typeof header !== 'string') return false;
12
+ const separatorIndex = header.indexOf(':');
13
+ return separatorIndex > 0 && header.slice(0, separatorIndex).trim().toLowerCase() === 'authorization';
14
+ }
10
15
  /**
11
16
  * MCP programmatic mode disables local project/config discovery (see the CLI
12
17
  * execution context). Missing project or dataset values may therefore produce
@@ -19,14 +24,16 @@ import { allow, conditionalDenyFlags, conditionalPolicy, deny } from './policy.j
19
24
  * - deny: no invocation is safe
20
25
  */ export const mcpPolicy = {
21
26
  // Special exception, this can be very dangerous but is also super useful
22
- // to expose. Only the host input channels are refused: `--input` reads the
23
- // request body from the host's filesystem or stdin, and `-F key=@<file>` /
24
- // `-F key=@-` field values do the same.
27
+ // to expose. Refuse authentication overrides and host input channels:
28
+ // `--token` and an Authorization header replace the MCP user's token,
29
+ // `--input` reads the request body from the host's filesystem or stdin, and
30
+ // `-F key=@<file>` / `-F key=@-` field values do the same.
25
31
  api: conditionalPolicy({
26
32
  deniedFlags: [
27
- 'input'
33
+ 'input',
34
+ 'token'
28
35
  ],
29
- validate: ({ flags })=>!Array.isArray(flags.field) || !flags.field.some((field)=>fieldReadsFromHost(field))
36
+ validate: ({ flags })=>(!Array.isArray(flags.field) || !flags.field.some((field)=>fieldReadsFromHost(field))) && (!Array.isArray(flags.header) || !flags.header.some((header)=>headerOverridesAuthentication(header)))
30
37
  }),
31
38
  'backups:disable': allow,
32
39
  // Writes a downloaded backup to the local filesystem.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/exports/invokeSanityCli/commandPolicies/mcpPolicy.ts"],"sourcesContent":["import {\n allow,\n type CommandPolicySet,\n conditionalDenyFlags,\n conditionalPolicy,\n deny,\n} from './policy.js'\n\n/**\n * A typed `-F/--field` value of `@<file>` or `@-` makes the `api` command\n * read the host's filesystem or stdin (raw `-f` fields are always verbatim).\n */\nfunction fieldReadsFromHost(field: unknown): boolean {\n if (typeof field !== 'string') return false\n const separatorIndex = field.indexOf('=')\n return separatorIndex > 0 && field[separatorIndex + 1] === '@'\n}\n\n/**\n * MCP programmatic mode disables local project/config discovery (see the CLI\n * execution context). Missing project or dataset values may therefore produce\n * a usage error, but cannot cause local filesystem access. Destructive remote\n * operations are allowed and do not by themselves make a command unsafe.\n *\n * Every manifest command must have exactly one policy here:\n * - allow: every valid invocation is safe\n * - conditional: safety depends on parsed arguments or flags\n * - deny: no invocation is safe\n */\nexport const mcpPolicy: CommandPolicySet = {\n // Special exception, this can be very dangerous but is also super useful\n // to expose. Only the host input channels are refused: `--input` reads the\n // request body from the host's filesystem or stdin, and `-F key=@<file>` /\n // `-F key=@-` field values do the same.\n api: conditionalPolicy({\n deniedFlags: ['input'],\n validate: ({flags}) =>\n !Array.isArray(flags.field) || !flags.field.some((field) => fieldReadsFromHost(field)),\n }),\n\n 'backups:disable': allow,\n // Writes a downloaded backup to the local filesystem.\n 'backups:download': deny,\n 'backups:enable': allow,\n 'backups:list': allow,\n\n // Requires a local Studio project and writes build output to disk.\n build: deny,\n\n // Reads and rewrites local source code.\n codemod: deny,\n\n 'cors:add': allow,\n 'cors:delete': allow,\n 'cors:list': allow,\n\n 'datasets:alias:create': allow,\n 'datasets:alias:delete': allow,\n 'datasets:alias:link': allow,\n 'datasets:alias:unlink': allow,\n 'datasets:copy': allow,\n 'datasets:create': allow,\n 'datasets:delete': allow,\n 'datasets:embeddings:disable': allow,\n 'datasets:embeddings:enable': allow,\n 'datasets:embeddings:status': allow,\n // Writes dataset contents and assets to the local filesystem.\n 'datasets:export': deny,\n // Reads import data from the local filesystem and may replace documents.\n 'datasets:import': deny,\n 'datasets:list': allow,\n 'datasets:visibility:get': allow,\n 'datasets:visibility:set': allow,\n\n // Inspects local project files and can print authentication secrets.\n debug: deny,\n\n // Reads, builds, and deploys a local Studio project.\n deploy: deny,\n\n // Loads a local Studio project and starts a development server.\n dev: deny,\n\n // Opens a browser on the machine running the MCP server.\n 'docs:browse': deny,\n // --web opens a browser on the machine running the MCP server.\n 'docs:read': conditionalDenyFlags('web'),\n 'docs:search': allow,\n\n // Reads and executes local project configuration for diagnostics.\n doctor: deny,\n\n // Reads document input from disk or launches a local editor.\n 'documents:create': deny,\n 'documents:delete': allow,\n 'documents:get': allow,\n 'documents:query': allow,\n // Loads a local Studio schema to validate documents.\n 'documents:validate': deny,\n\n // Executes arbitrary code in the local Studio context.\n exec: deny,\n\n // Loads a local schema and deploys a GraphQL API.\n 'graphql:deploy': deny,\n 'graphql:list': allow,\n // --api loads GraphQL definitions from the local project; explicit project/dataset flags are safe.\n 'graphql:undeploy': conditionalDenyFlags('api'),\n\n 'hooks:attempt': allow,\n 'hooks:create': allow,\n 'hooks:delete': allow,\n 'hooks:list': allow,\n 'hooks:logs': allow,\n\n // Creates or modifies a local project and may install dependencies.\n init: deny,\n\n // Installs packages into the local project.\n install: deny,\n\n // Opens a browser on the machine running the MCP server.\n learn: deny,\n\n // Performs an authentication flow.\n login: deny,\n\n // Performs an authentication operation and clears local credentials.\n logout: deny,\n\n // Reads local project configuration and opens a browser.\n manage: deny,\n\n // Loads local Studio configuration and writes manifest files.\n 'manifest:extract': deny,\n\n // Reads and writes local MCP client configuration.\n 'mcp:configure': deny,\n\n // Writes an aspect definition to the local filesystem.\n 'media:create-aspect': deny,\n 'media:delete-aspect': allow,\n // Reads a local aspect definition before deploying it.\n 'media:deploy-aspect': deny,\n // Writes media assets to the local filesystem.\n 'media:export': deny,\n // Reads media assets from the local filesystem.\n 'media:import': deny,\n\n // Creates migration source files in the local project.\n 'migrations:create': deny,\n // Reads and loads migration definitions from the local project.\n 'migrations:list': deny,\n // Executes local migration code that may perform arbitrary document mutations.\n 'migrations:run': deny,\n\n // --web opens a browser on the machine running the MCP server.\n 'openapi:get': conditionalDenyFlags('web'),\n 'openapi:list': conditionalDenyFlags('web'),\n\n 'organizations:create': allow,\n 'organizations:delete': allow,\n 'organizations:get': allow,\n 'organizations:list': allow,\n 'organizations:update': allow,\n\n // Serves a local production build.\n preview: deny,\n\n 'projects:create': allow,\n 'projects:list': allow,\n\n // Loads the local Studio configuration to resolve the datasets containing each schema.\n 'schemas:delete': deny,\n // Loads local schema files before deploying them.\n 'schemas:deploy': deny,\n // Loads local Studio configuration and writes an extracted schema to disk.\n 'schemas:extract': deny,\n // Requires a local project and loads its schema configuration.\n 'schemas:list': deny,\n // Loads and executes a local Studio schema.\n 'schemas:validate': deny,\n\n // Installs skills into local editor configuration directories.\n 'skills:install': deny,\n\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:disable': deny,\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:enable': deny,\n 'telemetry:status': allow,\n\n // Creates authentication credentials.\n 'tokens:create': deny,\n // Deletes authentication credentials.\n 'tokens:delete': deny,\n // Exposes authentication credential metadata.\n 'tokens:list': deny,\n\n // Loads local CLI and workbench configuration to identify the deployed Studio or application.\n undeploy: deny,\n\n // Grants a user access to a project.\n 'users:invite': deny,\n 'users:list': allow,\n\n // Reads the local project and installed package tree.\n versions: deny,\n}\n"],"names":["allow","conditionalDenyFlags","conditionalPolicy","deny","fieldReadsFromHost","field","separatorIndex","indexOf","mcpPolicy","api","deniedFlags","validate","flags","Array","isArray","some","build","codemod","debug","deploy","dev","doctor","exec","init","install","learn","login","logout","manage","preview","undeploy","versions"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/exports/invokeSanityCli/commandPolicies/mcpPolicy.ts"],"sourcesContent":["import {\n allow,\n type CommandPolicySet,\n conditionalDenyFlags,\n conditionalPolicy,\n deny,\n} from './policy.js'\n\n/**\n * A typed `-F/--field` value of `@<file>` or `@-` makes the `api` command\n * read the host's filesystem or stdin (raw `-f` fields are always verbatim).\n */\nfunction fieldReadsFromHost(field: unknown): boolean {\n if (typeof field !== 'string') return false\n const separatorIndex = field.indexOf('=')\n return separatorIndex > 0 && field[separatorIndex + 1] === '@'\n}\n\n/** A `-H/--header` value that replaces the invocation's authentication. */\nfunction headerOverridesAuthentication(header: unknown): boolean {\n if (typeof header !== 'string') return false\n const separatorIndex = header.indexOf(':')\n return (\n separatorIndex > 0 && header.slice(0, separatorIndex).trim().toLowerCase() === 'authorization'\n )\n}\n\n/**\n * MCP programmatic mode disables local project/config discovery (see the CLI\n * execution context). Missing project or dataset values may therefore produce\n * a usage error, but cannot cause local filesystem access. Destructive remote\n * operations are allowed and do not by themselves make a command unsafe.\n *\n * Every manifest command must have exactly one policy here:\n * - allow: every valid invocation is safe\n * - conditional: safety depends on parsed arguments or flags\n * - deny: no invocation is safe\n */\nexport const mcpPolicy: CommandPolicySet = {\n // Special exception, this can be very dangerous but is also super useful\n // to expose. Refuse authentication overrides and host input channels:\n // `--token` and an Authorization header replace the MCP user's token,\n // `--input` reads the request body from the host's filesystem or stdin, and\n // `-F key=@<file>` / `-F key=@-` field values do the same.\n api: conditionalPolicy({\n deniedFlags: ['input', 'token'],\n validate: ({flags}) =>\n (!Array.isArray(flags.field) || !flags.field.some((field) => fieldReadsFromHost(field))) &&\n (!Array.isArray(flags.header) ||\n !flags.header.some((header) => headerOverridesAuthentication(header))),\n }),\n\n 'backups:disable': allow,\n // Writes a downloaded backup to the local filesystem.\n 'backups:download': deny,\n 'backups:enable': allow,\n 'backups:list': allow,\n\n // Requires a local Studio project and writes build output to disk.\n build: deny,\n\n // Reads and rewrites local source code.\n codemod: deny,\n\n 'cors:add': allow,\n 'cors:delete': allow,\n 'cors:list': allow,\n\n 'datasets:alias:create': allow,\n 'datasets:alias:delete': allow,\n 'datasets:alias:link': allow,\n 'datasets:alias:unlink': allow,\n 'datasets:copy': allow,\n 'datasets:create': allow,\n 'datasets:delete': allow,\n 'datasets:embeddings:disable': allow,\n 'datasets:embeddings:enable': allow,\n 'datasets:embeddings:status': allow,\n // Writes dataset contents and assets to the local filesystem.\n 'datasets:export': deny,\n // Reads import data from the local filesystem and may replace documents.\n 'datasets:import': deny,\n 'datasets:list': allow,\n 'datasets:visibility:get': allow,\n 'datasets:visibility:set': allow,\n\n // Inspects local project files and can print authentication secrets.\n debug: deny,\n\n // Reads, builds, and deploys a local Studio project.\n deploy: deny,\n\n // Loads a local Studio project and starts a development server.\n dev: deny,\n\n // Opens a browser on the machine running the MCP server.\n 'docs:browse': deny,\n // --web opens a browser on the machine running the MCP server.\n 'docs:read': conditionalDenyFlags('web'),\n 'docs:search': allow,\n\n // Reads and executes local project configuration for diagnostics.\n doctor: deny,\n\n // Reads document input from disk or launches a local editor.\n 'documents:create': deny,\n 'documents:delete': allow,\n 'documents:get': allow,\n 'documents:query': allow,\n // Loads a local Studio schema to validate documents.\n 'documents:validate': deny,\n\n // Executes arbitrary code in the local Studio context.\n exec: deny,\n\n // Loads a local schema and deploys a GraphQL API.\n 'graphql:deploy': deny,\n 'graphql:list': allow,\n // --api loads GraphQL definitions from the local project; explicit project/dataset flags are safe.\n 'graphql:undeploy': conditionalDenyFlags('api'),\n\n 'hooks:attempt': allow,\n 'hooks:create': allow,\n 'hooks:delete': allow,\n 'hooks:list': allow,\n 'hooks:logs': allow,\n\n // Creates or modifies a local project and may install dependencies.\n init: deny,\n\n // Installs packages into the local project.\n install: deny,\n\n // Opens a browser on the machine running the MCP server.\n learn: deny,\n\n // Performs an authentication flow.\n login: deny,\n\n // Performs an authentication operation and clears local credentials.\n logout: deny,\n\n // Reads local project configuration and opens a browser.\n manage: deny,\n\n // Loads local Studio configuration and writes manifest files.\n 'manifest:extract': deny,\n\n // Reads and writes local MCP client configuration.\n 'mcp:configure': deny,\n\n // Writes an aspect definition to the local filesystem.\n 'media:create-aspect': deny,\n 'media:delete-aspect': allow,\n // Reads a local aspect definition before deploying it.\n 'media:deploy-aspect': deny,\n // Writes media assets to the local filesystem.\n 'media:export': deny,\n // Reads media assets from the local filesystem.\n 'media:import': deny,\n\n // Creates migration source files in the local project.\n 'migrations:create': deny,\n // Reads and loads migration definitions from the local project.\n 'migrations:list': deny,\n // Executes local migration code that may perform arbitrary document mutations.\n 'migrations:run': deny,\n\n // --web opens a browser on the machine running the MCP server.\n 'openapi:get': conditionalDenyFlags('web'),\n 'openapi:list': conditionalDenyFlags('web'),\n\n 'organizations:create': allow,\n 'organizations:delete': allow,\n 'organizations:get': allow,\n 'organizations:list': allow,\n 'organizations:update': allow,\n\n // Serves a local production build.\n preview: deny,\n\n 'projects:create': allow,\n 'projects:list': allow,\n\n // Loads the local Studio configuration to resolve the datasets containing each schema.\n 'schemas:delete': deny,\n // Loads local schema files before deploying them.\n 'schemas:deploy': deny,\n // Loads local Studio configuration and writes an extracted schema to disk.\n 'schemas:extract': deny,\n // Requires a local project and loads its schema configuration.\n 'schemas:list': deny,\n // Loads and executes a local Studio schema.\n 'schemas:validate': deny,\n\n // Installs skills into local editor configuration directories.\n 'skills:install': deny,\n\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:disable': deny,\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:enable': deny,\n 'telemetry:status': allow,\n\n // Creates authentication credentials.\n 'tokens:create': deny,\n // Deletes authentication credentials.\n 'tokens:delete': deny,\n // Exposes authentication credential metadata.\n 'tokens:list': deny,\n\n // Loads local CLI and workbench configuration to identify the deployed Studio or application.\n undeploy: deny,\n\n // Grants a user access to a project.\n 'users:invite': deny,\n 'users:list': allow,\n\n // Reads the local project and installed package tree.\n versions: deny,\n}\n"],"names":["allow","conditionalDenyFlags","conditionalPolicy","deny","fieldReadsFromHost","field","separatorIndex","indexOf","headerOverridesAuthentication","header","slice","trim","toLowerCase","mcpPolicy","api","deniedFlags","validate","flags","Array","isArray","some","build","codemod","debug","deploy","dev","doctor","exec","init","install","learn","login","logout","manage","preview","undeploy","versions"],"mappings":"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"}
@@ -60,6 +60,7 @@ import { isConditionalInvocationPolicy } from './commandPolicies/policy.js';
60
60
  * Output is collected in {@link InvokableHelp.lines} instead of being written
61
61
  * to the process streams.
62
62
  */ class InvokableHelp extends Help {
63
+ commandId;
63
64
  lines = [];
64
65
  commandIds;
65
66
  policySet;
@@ -92,6 +93,7 @@ import { isConditionalInvocationPolicy } from './commandPolicies/policy.js';
92
93
  }
93
94
  async showCommandHelp(command) {
94
95
  if (!this.commandIds.has(command.id)) throw new NotInvokableError(command.id);
96
+ this.commandId = command.id;
95
97
  return super.showCommandHelp(withoutDeniedFlags(command, this.policySet[command.id]));
96
98
  }
97
99
  async showTopicHelp(topic) {
@@ -123,7 +125,12 @@ import { isConditionalInvocationPolicy } from './commandPolicies/policy.js';
123
125
  if (err instanceof NotInvokableError || err.oclif) return undefined;
124
126
  throw err;
125
127
  }
126
- return help.lines.join('\n').trimEnd();
128
+ return {
129
+ ...help.commandId && {
130
+ commandId: help.commandId
131
+ },
132
+ output: help.lines.join('\n').trimEnd()
133
+ };
127
134
  }
128
135
  /**
129
136
  * Whether `argv` asks for help rather than a command invocation: a leading
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/exports/invokeSanityCli/help.ts"],"sourcesContent":["import {type Command, type Config, Help, type Interfaces} from '@oclif/core'\nimport {getHelpFlagAdditions} from '@oclif/core/help'\n\nimport {\n type CommandPolicy,\n type CommandPolicySet,\n isConditionalInvocationPolicy,\n} from './commandPolicies/policy.js'\n\n/** Command ids a policy exposes: entries that are not denied. */\nfunction visibleCommandIds(policySet: CommandPolicySet): Set<string> {\n return new Set(\n Object.entries(policySet)\n .filter(([, policy]) => policy.kind !== 'deny')\n .map(([id]) => id),\n )\n}\n\n/** Topic names covering the visible commands: every prefix of every id. */\nfunction visibleTopicNames(commandIds: Set<string>): Set<string> {\n const names = new Set<string>()\n for (const id of commandIds) {\n const parts = id.split(':')\n for (let length = 1; length < parts.length; length++) {\n names.add(parts.slice(0, length).join(':'))\n }\n }\n return names\n}\n\n/** Thrown when help is requested for a subject outside the policy surface. */\nclass NotInvokableError extends Error {}\n\n/**\n * A copy of `command` without the policy's denied flags, and without examples\n * that use them (under any spelling: `--web`, `--w`, `-w`), so rendered help\n * only advertises invocations the policy accepts.\n */\nfunction withoutDeniedFlags(command: Command.Loadable, policy?: CommandPolicy): Command.Loadable {\n if (!policy || !isConditionalInvocationPolicy(policy)) return {...command}\n\n const denied = policy.deniedFlags\n if (denied.length === 0) return {...command}\n\n const flags = {...command.flags}\n const spellings: string[] = []\n for (const name of denied) {\n const definition = flags[name]\n if (!definition) continue\n spellings.push(`--${name}`)\n if (definition.char) spellings.push(`-${definition.char}`)\n for (const alias of definition.aliases ?? []) spellings.push(`--${alias}`, `-${alias}`)\n delete flags[name]\n }\n\n const usesDeniedFlag = (example: Command.Example) => {\n const text = typeof example === 'string' ? example : example.command\n return spellings.some((spelling) =>\n new RegExp(String.raw`(^|\\s)${spelling}(=|\\s|$)`).test(text ?? ''),\n )\n }\n\n return {\n ...command,\n examples: command.examples?.filter((example) => !usesDeniedFlag(example)),\n flags,\n }\n}\n\n/**\n * oclif help renderer scoped to a policy's command surface. Subject\n * resolution (root vs topic vs command help) is oclif's own; the policy is\n * enforced at the rendering entry points so the rest of the CLI stays\n * invisible to embedding hosts, and listings are filtered to non-denied\n * commands and their topics only.\n *\n * Output is collected in {@link InvokableHelp.lines} instead of being written\n * to the process streams.\n */\nclass InvokableHelp extends Help {\n public readonly lines: string[] = []\n\n private readonly commandIds: Set<string>\n private readonly policySet: CommandPolicySet\n private readonly topicNames: Set<string>\n\n constructor(config: Config, policySet: CommandPolicySet) {\n super(config, {stripAnsi: true})\n this.policySet = policySet\n this.commandIds = visibleCommandIds(policySet)\n this.topicNames = visibleTopicNames(this.commandIds)\n }\n\n // The copies below are load-bearing: oclif's formatters rewrite ids/names\n // in place (`cors:list` → `cors list`). Without copies those writes corrupt\n // the shared (cached) config, breaking the policy checks on subsequent\n // invocations.\n\n protected override get sortedCommands(): Command.Loadable[] {\n return super.sortedCommands\n .filter((command) => this.commandIds.has(command.id))\n .map((command) => ({...command}))\n }\n\n protected override get sortedTopics(): Interfaces.Topic[] {\n // Topic names and descriptions come from the oclif config (oclif.config.js)\n return super.sortedTopics\n .filter((topic) => this.topicNames.has(topic.name))\n .map((topic) => ({...topic}))\n }\n\n protected override log(...args: string[]): void {\n this.lines.push(...args)\n }\n\n public override async showCommandHelp(command: Command.Loadable): Promise<void> {\n if (!this.commandIds.has(command.id)) throw new NotInvokableError(command.id)\n return super.showCommandHelp(withoutDeniedFlags(command, this.policySet[command.id]))\n }\n\n protected override async showTopicHelp(topic: Interfaces.Topic): Promise<void> {\n if (!this.topicNames.has(topic.name)) throw new NotInvokableError(topic.name)\n return super.showTopicHelp({...topic})\n }\n}\n\n/**\n * Render help text for a policy's command surface: root help for a bare help\n * request, or topic/command help when `argv` names a subject (e.g.\n * `['cors', '--help']`), exactly as oclif would resolve it. ANSI styling is\n * stripped so programmatic callers get plain text.\n *\n * Returns `undefined` when the subject is unknown or denied — callers should\n * respond the same way as for an unknown command, so hosts cannot probe the\n * full CLI surface through help.\n *\n * @internal\n */\nexport async function renderInvokableHelp(\n config: Config,\n argv: string[],\n policySet: CommandPolicySet,\n): Promise<string | undefined> {\n const help = new InvokableHelp(config, policySet)\n try {\n await help.showHelp(argv)\n } catch (err) {\n // Subjects outside the policy surface (NotInvokableError) and subjects\n // oclif itself cannot resolve (CLIError, marked with an `oclif` property)\n // both yield no help output.\n if (err instanceof NotInvokableError || err.oclif) return undefined\n throw err\n }\n return help.lines.join('\\n').trimEnd()\n}\n\n/**\n * Whether `argv` asks for help rather than a command invocation: a leading\n * `help` (oclif's help command), or a recognized help flag before any `--`\n * terminator. The flags come from {@link getHelpFlagAdditions} — the same\n * source oclif's own dispatch consults — so this surface recognizes exactly\n * what the regular CLI recognizes.\n *\n * @internal\n */\nexport function isHelpRequest(argv: string[], config: Config): boolean {\n if (argv[0] === 'help') return true\n const helpFlags = getHelpFlagAdditions(config)\n for (const token of argv) {\n if (token === '--') return false\n if (helpFlags.includes(token)) return true\n }\n return false\n}\n"],"names":["Help","getHelpFlagAdditions","isConditionalInvocationPolicy","visibleCommandIds","policySet","Set","Object","entries","filter","policy","kind","map","id","visibleTopicNames","commandIds","names","parts","split","length","add","slice","join","NotInvokableError","Error","withoutDeniedFlags","command","denied","deniedFlags","flags","spellings","name","definition","push","char","alias","aliases","usesDeniedFlag","example","text","some","spelling","RegExp","String","raw","test","examples","InvokableHelp","lines","topicNames","config","stripAnsi","sortedCommands","has","sortedTopics","topic","log","args","showCommandHelp","showTopicHelp","renderInvokableHelp","argv","help","showHelp","err","oclif","undefined","trimEnd","isHelpRequest","helpFlags","token","includes"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/exports/invokeSanityCli/help.ts"],"sourcesContent":["import {type Command, type Config, Help, type Interfaces} from '@oclif/core'\nimport {getHelpFlagAdditions} from '@oclif/core/help'\n\nimport {\n type CommandPolicy,\n type CommandPolicySet,\n isConditionalInvocationPolicy,\n} from './commandPolicies/policy.js'\n\n/** Command ids a policy exposes: entries that are not denied. */\nfunction visibleCommandIds(policySet: CommandPolicySet): Set<string> {\n return new Set(\n Object.entries(policySet)\n .filter(([, policy]) => policy.kind !== 'deny')\n .map(([id]) => id),\n )\n}\n\n/** Topic names covering the visible commands: every prefix of every id. */\nfunction visibleTopicNames(commandIds: Set<string>): Set<string> {\n const names = new Set<string>()\n for (const id of commandIds) {\n const parts = id.split(':')\n for (let length = 1; length < parts.length; length++) {\n names.add(parts.slice(0, length).join(':'))\n }\n }\n return names\n}\n\n/** Thrown when help is requested for a subject outside the policy surface. */\nclass NotInvokableError extends Error {}\n\n/**\n * A copy of `command` without the policy's denied flags, and without examples\n * that use them (under any spelling: `--web`, `--w`, `-w`), so rendered help\n * only advertises invocations the policy accepts.\n */\nfunction withoutDeniedFlags(command: Command.Loadable, policy?: CommandPolicy): Command.Loadable {\n if (!policy || !isConditionalInvocationPolicy(policy)) return {...command}\n\n const denied = policy.deniedFlags\n if (denied.length === 0) return {...command}\n\n const flags = {...command.flags}\n const spellings: string[] = []\n for (const name of denied) {\n const definition = flags[name]\n if (!definition) continue\n spellings.push(`--${name}`)\n if (definition.char) spellings.push(`-${definition.char}`)\n for (const alias of definition.aliases ?? []) spellings.push(`--${alias}`, `-${alias}`)\n delete flags[name]\n }\n\n const usesDeniedFlag = (example: Command.Example) => {\n const text = typeof example === 'string' ? example : example.command\n return spellings.some((spelling) =>\n new RegExp(String.raw`(^|\\s)${spelling}(=|\\s|$)`).test(text ?? ''),\n )\n }\n\n return {\n ...command,\n examples: command.examples?.filter((example) => !usesDeniedFlag(example)),\n flags,\n }\n}\n\n/**\n * oclif help renderer scoped to a policy's command surface. Subject\n * resolution (root vs topic vs command help) is oclif's own; the policy is\n * enforced at the rendering entry points so the rest of the CLI stays\n * invisible to embedding hosts, and listings are filtered to non-denied\n * commands and their topics only.\n *\n * Output is collected in {@link InvokableHelp.lines} instead of being written\n * to the process streams.\n */\nclass InvokableHelp extends Help {\n public commandId: string | undefined\n public readonly lines: string[] = []\n\n private readonly commandIds: Set<string>\n private readonly policySet: CommandPolicySet\n private readonly topicNames: Set<string>\n\n constructor(config: Config, policySet: CommandPolicySet) {\n super(config, {stripAnsi: true})\n this.policySet = policySet\n this.commandIds = visibleCommandIds(policySet)\n this.topicNames = visibleTopicNames(this.commandIds)\n }\n\n // The copies below are load-bearing: oclif's formatters rewrite ids/names\n // in place (`cors:list` → `cors list`). Without copies those writes corrupt\n // the shared (cached) config, breaking the policy checks on subsequent\n // invocations.\n\n protected override get sortedCommands(): Command.Loadable[] {\n return super.sortedCommands\n .filter((command) => this.commandIds.has(command.id))\n .map((command) => ({...command}))\n }\n\n protected override get sortedTopics(): Interfaces.Topic[] {\n // Topic names and descriptions come from the oclif config (oclif.config.js)\n return super.sortedTopics\n .filter((topic) => this.topicNames.has(topic.name))\n .map((topic) => ({...topic}))\n }\n\n protected override log(...args: string[]): void {\n this.lines.push(...args)\n }\n\n public override async showCommandHelp(command: Command.Loadable): Promise<void> {\n if (!this.commandIds.has(command.id)) throw new NotInvokableError(command.id)\n this.commandId = command.id\n return super.showCommandHelp(withoutDeniedFlags(command, this.policySet[command.id]))\n }\n\n protected override async showTopicHelp(topic: Interfaces.Topic): Promise<void> {\n if (!this.topicNames.has(topic.name)) throw new NotInvokableError(topic.name)\n return super.showTopicHelp({...topic})\n }\n}\n\n/**\n * Render help text for a policy's command surface: root help for a bare help\n * request, or topic/command help when `argv` names a subject (e.g.\n * `['cors', '--help']`), exactly as oclif would resolve it. ANSI styling is\n * stripped so programmatic callers get plain text.\n *\n * Returns `undefined` when the subject is unknown or denied — callers should\n * respond the same way as for an unknown command, so hosts cannot probe the\n * full CLI surface through help.\n *\n * @internal\n */\nexport async function renderInvokableHelp(\n config: Config,\n argv: string[],\n policySet: CommandPolicySet,\n): Promise<{commandId?: string; output: string} | undefined> {\n const help = new InvokableHelp(config, policySet)\n try {\n await help.showHelp(argv)\n } catch (err) {\n // Subjects outside the policy surface (NotInvokableError) and subjects\n // oclif itself cannot resolve (CLIError, marked with an `oclif` property)\n // both yield no help output.\n if (err instanceof NotInvokableError || err.oclif) return undefined\n throw err\n }\n return {\n ...(help.commandId && {commandId: help.commandId}),\n output: help.lines.join('\\n').trimEnd(),\n }\n}\n\n/**\n * Whether `argv` asks for help rather than a command invocation: a leading\n * `help` (oclif's help command), or a recognized help flag before any `--`\n * terminator. The flags come from {@link getHelpFlagAdditions} — the same\n * source oclif's own dispatch consults — so this surface recognizes exactly\n * what the regular CLI recognizes.\n *\n * @internal\n */\nexport function isHelpRequest(argv: string[], config: Config): boolean {\n if (argv[0] === 'help') return true\n const helpFlags = getHelpFlagAdditions(config)\n for (const token of argv) {\n if (token === '--') return false\n if (helpFlags.includes(token)) return true\n }\n return false\n}\n"],"names":["Help","getHelpFlagAdditions","isConditionalInvocationPolicy","visibleCommandIds","policySet","Set","Object","entries","filter","policy","kind","map","id","visibleTopicNames","commandIds","names","parts","split","length","add","slice","join","NotInvokableError","Error","withoutDeniedFlags","command","denied","deniedFlags","flags","spellings","name","definition","push","char","alias","aliases","usesDeniedFlag","example","text","some","spelling","RegExp","String","raw","test","examples","InvokableHelp","commandId","lines","topicNames","config","stripAnsi","sortedCommands","has","sortedTopics","topic","log","args","showCommandHelp","showTopicHelp","renderInvokableHelp","argv","help","showHelp","err","oclif","undefined","output","trimEnd","isHelpRequest","helpFlags","token","includes"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { Config } from "@oclif/core";
2
+ import { SanityEnvironment } from "@sanity/cli-core/executionContext";
2
3
 
3
4
  /** Where an invocation originates; selects the policy to enforce. */
4
5
  declare type InvocationSource = "mcp";
@@ -15,6 +16,7 @@ declare type InvocationSource = "mcp";
15
16
  export declare function invokeSanityCli({
16
17
  args,
17
18
  config,
19
+ sanityEnv,
18
20
  source,
19
21
  token,
20
22
  }: InvokeSanityCliOptions): Promise<InvokeSanityCliResult>;
@@ -45,6 +47,12 @@ export declare interface InvokeSanityCliOptions {
45
47
  * package's config, loaded once and cached across invocations.
46
48
  */
47
49
  config?: Config;
50
+ /**
51
+ * Sanity deployment environment for this invocation. Scoped to this call
52
+ * via the CLI execution context and defaults to `SANITY_INTERNAL_ENV` (or
53
+ * production when it is unset).
54
+ */
55
+ sanityEnv?: SanityEnvironment;
48
56
  }
49
57
 
50
58
  /**
@@ -55,6 +63,11 @@ export declare interface InvokeSanityCliResult {
55
63
  exitCode: number;
56
64
  /** Combined stdout and stderr output, in emission order. */
57
65
  output: string;
66
+ /**
67
+ * Canonical oclif command id when the invocation resolved to a command
68
+ * exposed by the selected policy (for example, `datasets:create`).
69
+ */
70
+ commandId?: string;
58
71
  }
59
72
 
60
73
  export {};
@@ -27,6 +27,7 @@ import { parseArgsStringToArgv } from 'string-argv';
27
27
  import { commandPolicies } from './commandPolicies/index.js';
28
28
  import { deny, isConditionalInvocationPolicy } from './commandPolicies/policy.js';
29
29
  import { isHelpRequest, renderInvokableHelp } from './help.js';
30
+ import { prettyPrintError } from './prettyPrintError.js';
30
31
  /**
31
32
  * Load the oclif `Config` for this package, needed to resolve, load, and run
32
33
  * commands. Loading it once and reusing it across invocations avoids
@@ -62,7 +63,7 @@ let cachedConfig;
62
63
  * relay them verbatim.
63
64
  *
64
65
  * @internal
65
- */ export async function invokeSanityCli({ args, config, source, token }) {
66
+ */ export async function invokeSanityCli({ args, config, sanityEnv, source, token }) {
66
67
  const resolvedConfig = config ?? await (cachedConfig ??= loadCliCommandConfig());
67
68
  const policySet = commandPolicies[source];
68
69
  // Commands log through the global telemetry store; default it to a noop
@@ -88,10 +89,10 @@ let cachedConfig;
88
89
  // Drop a leading `help` so the rest is the subject, mirroring how
89
90
  // oclif's dispatch consumes the token before the help command sees argv
90
91
  const helpArgv = argv[0] === 'help' ? argv.slice(1) : argv;
91
- const output = await renderInvokableHelp(resolvedConfig, helpArgv, policySet);
92
- if (output !== undefined) return {
93
- exitCode: exitCodes.SUCCESS,
94
- output
92
+ const result = await renderInvokableHelp(resolvedConfig, helpArgv, policySet);
93
+ if (result) return {
94
+ ...result,
95
+ exitCode: exitCodes.SUCCESS
95
96
  };
96
97
  const helpFlags = getHelpFlagAdditions(resolvedConfig);
97
98
  return unknownCommandResult(helpArgv.filter((token)=>!helpFlags.includes(token)), policySet);
@@ -128,6 +129,7 @@ let cachedConfig;
128
129
  };
129
130
  } catch (err) {
130
131
  return {
132
+ commandId,
131
133
  exitCode: exitCodes.USAGE_ERROR,
132
134
  output: err instanceof Error ? err.message : String(err)
133
135
  };
@@ -137,9 +139,12 @@ let cachedConfig;
137
139
  let output = `This invocation of \`${displayId}\` is not supported here`;
138
140
  if (isConditionalInvocationPolicy(policy)) {
139
141
  const usedDeniedFlags = policy.deniedFlags.filter((name)=>invocation.flags[name] !== undefined && invocation.flags[name] !== false);
140
- output = `\nThe ${usedDeniedFlags.map((name)=>`--${name}`).join(', ')} flag is not supported here for \`${displayId}\``;
142
+ if (usedDeniedFlags.length > 0) {
143
+ output = `\nThe ${usedDeniedFlags.map((name)=>`--${name}`).join(', ')} flag is not supported here for \`${displayId}\``;
144
+ }
141
145
  }
142
146
  return {
147
+ commandId,
143
148
  exitCode: exitCodes.USAGE_ERROR,
144
149
  output
145
150
  };
@@ -148,11 +153,13 @@ let cachedConfig;
148
153
  const sink = (line)=>output.push(line);
149
154
  try {
150
155
  await runWithCliExecutionContext({
156
+ sanityEnv,
151
157
  stderr: sink,
152
158
  stdout: sink,
153
159
  token
154
160
  }, ()=>CommandClass.run(commandArgv, resolvedConfig));
155
161
  return {
162
+ commandId,
156
163
  exitCode: exitCodes.SUCCESS,
157
164
  output: output.join('\n')
158
165
  };
@@ -161,13 +168,15 @@ let cachedConfig;
161
168
  // `this.exit(0)` throws an ExitError but is a successful outcome
162
169
  if (exit === exitCodes.SUCCESS) {
163
170
  return {
171
+ commandId,
164
172
  exitCode: exitCodes.SUCCESS,
165
173
  output: output.join('\n')
166
174
  };
167
175
  }
168
- const message = err instanceof Error ? err.message : String(err);
176
+ const message = prettyPrintError(err) || String(err);
169
177
  if (message) output.push(message);
170
178
  return {
179
+ commandId,
171
180
  exitCode: typeof exit === 'number' ? exit : exitCodes.RUNTIME_ERROR,
172
181
  output: output.join('\n')
173
182
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/exports/invokeSanityCli/index.ts"],"sourcesContent":["/**\n * Programmatic (in-process) invocation of CLI commands, e.g. from an MCP\n * server. The invokable surface is governed by a per-source command policy\n * (see ./commandPolicies): every CLI command is explicitly allowed, denied,\n * or allowed conditionally on the parsed invocation.\n *\n * {@link invokeSanityCli} handles arg parsing, policy enforcement, command\n * dispatch, per-invocation auth, and output capture.\n * ```ts\n * import {invokeSanityCli} from '@sanity/cli/invokeSanityCli'\n *\n * const {exitCode, output} = await invokeSanityCli({\n * args: 'cors list --project-id abc123',\n * source: 'mcp',\n * token: extra.authInfo.token,\n * })\n * ```\n *\n * Help works like the regular CLI, scoped to the source's policy: `--help`\n * (or `help`) renders root help listing the invokable topics, and a subject\n * (`cors --help`, `cors list --help`) renders topic or command help.\n */\nimport {Config, Parser} from '@oclif/core'\nimport {getHelpFlagAdditions, normalizeArgv} from '@oclif/core/help'\nimport {CLI_TELEMETRY_SYMBOL, exitCodes, noopLogger, setCliTelemetry} from '@sanity/cli-core'\nimport {runWithCliExecutionContext} from '@sanity/cli-core/executionContext'\nimport {parseArgsStringToArgv} from 'string-argv'\n\nimport {commandPolicies} from './commandPolicies/index.js'\nimport {\n type CommandPolicySet,\n deny,\n type InvocationSource,\n isConditionalInvocationPolicy,\n} from './commandPolicies/policy.js'\nimport {isHelpRequest, renderInvokableHelp} from './help.js'\n\n/**\n * Load the oclif `Config` for this package, needed to resolve, load, and run\n * commands. Loading it once and reusing it across invocations avoids\n * re-reading the command manifest per call.\n */\nfunction loadCliCommandConfig(): Promise<Config> {\n return Config.load(import.meta.url)\n}\n\nfunction unknownCommandResult(argv: string[], policySet: CommandPolicySet): InvokeSanityCliResult {\n const available = Object.entries(policySet)\n .filter(([, policy]) => policy.kind !== 'deny')\n .map(([id]) => id.replaceAll(':', ' '))\n .toSorted()\n return {\n exitCode: exitCodes.USAGE_ERROR,\n output: [\n `Unknown or unsupported command: ${argv.slice(0, 2).join(' ') || '(none)'}`,\n `Available commands: ${available.join(', ')}`,\n ].join('\\n'),\n }\n}\n\n/**\n * @internal\n */\nexport interface InvokeSanityCliOptions {\n /**\n * Arguments after `sanity` (a leading `sanity` token is tolerated), either\n * as a single string — shell-style quoting is supported, but no shell is\n * ever executed — or as a pre-split argv array.\n */\n args: string | string[]\n\n /**\n * Where this invocation originates. Selects the command policy to enforce:\n * which commands are invokable and which invocations of them are permitted.\n */\n source: InvocationSource\n\n /**\n * Auth token for this invocation. Scoped to this call via the CLI execution\n * context: it never touches process env or the process-wide token cache, so\n * concurrent invocations with different tokens are fully isolated.\n */\n token: string\n\n /**\n * Optional oclif config override (mainly for tests). Defaults to this\n * package's config, loaded once and cached across invocations.\n */\n config?: Config\n}\n\n/**\n * @internal\n */\nexport interface InvokeSanityCliResult {\n /** `0` on success, the command's exit code otherwise. */\n exitCode: number\n\n /** Combined stdout and stderr output, in emission order. */\n output: string\n}\n\nlet cachedConfig: Promise<Config> | undefined\n\n/**\n * Unlike a shell, string-argv keeps quotes that are glued to unquoted text:\n * `--name=\"my project\"` tokenizes with the quotes intact. Strip a matching\n * wrapping quote pair from the value side of `--flag=`-shaped tokens so the\n * common shell-style form yields the value the caller intended.\n */\nfunction stripFlagQuotes(rawToken: string): string {\n const match = /^(-{1,2}[^\\s=]+=)(['\"])([\\s\\S]*)\\2$/.exec(rawToken)\n return match ? match[1] + match[3] : rawToken\n}\n\n/**\n * Run a policy-permitted CLI command in-process and capture its result.\n *\n * Command-level failures (unknown command, bad flags, API errors) are\n * reported through `exitCode`/`output` rather than thrown, so callers can\n * relay them verbatim.\n *\n * @internal\n */\nexport async function invokeSanityCli({\n args,\n config,\n source,\n token,\n}: InvokeSanityCliOptions): Promise<InvokeSanityCliResult> {\n const resolvedConfig = config ?? (await (cachedConfig ??= loadCliCommandConfig()))\n const policySet = commandPolicies[source]\n\n // Commands log through the global telemetry store; default it to a noop\n // store so embedding hosts need no telemetry wiring (and see no warnings),\n // without clobbering a store the host may have installed itself.\n if (!(globalThis as Record<symbol, unknown>)[CLI_TELEMETRY_SYMBOL]) {\n setCliTelemetry(noopLogger)\n }\n\n // Pre-split argv arrays are taken verbatim; only string input goes through\n // shell-style tokenization and quote normalization.\n let argv =\n typeof args === 'string'\n ? parseArgsStringToArgv(args).map((t) => stripFlagQuotes(t))\n : [...args]\n if (argv[0] === 'sanity') argv = argv.slice(1)\n\n // Help requests are routed through oclif's help system, scoped to the\n // source's policy: root help for a bare request, topic/command help when a\n // subject is given. Denied subjects get the standard unknown-command\n // response (identical to a truly unknown command, so hosts can't probe the\n // full CLI surface through help), and a help request never executes a\n // command.\n if (isHelpRequest(argv, resolvedConfig)) {\n try {\n // Drop a leading `help` so the rest is the subject, mirroring how\n // oclif's dispatch consumes the token before the help command sees argv\n const helpArgv = argv[0] === 'help' ? argv.slice(1) : argv\n const output = await renderInvokableHelp(resolvedConfig, helpArgv, policySet)\n if (output !== undefined) return {exitCode: exitCodes.SUCCESS, output}\n const helpFlags = getHelpFlagAdditions(resolvedConfig)\n return unknownCommandResult(\n helpArgv.filter((token) => !helpFlags.includes(token)),\n policySet,\n )\n } catch (err) {\n return {\n exitCode: exitCodes.RUNTIME_ERROR,\n output: err instanceof Error ? err.message : String(err),\n }\n }\n }\n\n // Resolve the command id the same way oclif's dispatch would (collating\n // space-separated topics, accepting colon-separated ids as-is), then apply\n // the policy. Denied and uncategorized ids fail closed, indistinguishable\n // from commands that don't exist.\n const [commandId = '', ...commandArgv] = normalizeArgv(resolvedConfig, argv)\n const policy = policySet[commandId] ?? deny\n const commandDefinition =\n policy.kind === 'deny' ? undefined : resolvedConfig.findCommand(commandId)\n if (!commandDefinition) return unknownCommandResult(argv, policySet)\n\n const CommandClass = await commandDefinition.load()\n\n // Parse with the command's real definitions (without executing anything) so\n // conditional policies are evaluated against typed args/flags, not tokens.\n let invocation: {args: Record<string, unknown>; flags: Record<string, unknown>}\n try {\n const parsed = await Parser.parse(commandArgv, {\n args: CommandClass.args,\n baseFlags: CommandClass.baseFlags,\n enableJsonFlag: CommandClass.enableJsonFlag,\n flags: CommandClass.flags,\n strict: CommandClass.strict,\n })\n invocation = {\n args: parsed.args as Record<string, unknown>,\n flags: parsed.flags as Record<string, unknown>,\n }\n } catch (err) {\n return {\n exitCode: exitCodes.USAGE_ERROR,\n output: err instanceof Error ? err.message : String(err),\n }\n }\n\n if (!policy.validate(invocation)) {\n const displayId = commandId.replaceAll(':', ' ')\n let output = `This invocation of \\`${displayId}\\` is not supported here`\n\n if (isConditionalInvocationPolicy(policy)) {\n const usedDeniedFlags = policy.deniedFlags.filter(\n (name) => invocation.flags[name] !== undefined && invocation.flags[name] !== false,\n )\n output = `\\nThe ${usedDeniedFlags.map((name) => `--${name}`).join(', ')} flag is not supported here for \\`${displayId}\\``\n }\n\n return {\n exitCode: exitCodes.USAGE_ERROR,\n output,\n }\n }\n\n const output: string[] = []\n const sink = (line: string) => output.push(line)\n\n try {\n await runWithCliExecutionContext({stderr: sink, stdout: sink, token}, () =>\n CommandClass.run(commandArgv, resolvedConfig),\n )\n return {exitCode: exitCodes.SUCCESS, output: output.join('\\n')}\n } catch (err) {\n const exit = err.oclif?.exit\n\n // `this.exit(0)` throws an ExitError but is a successful outcome\n if (exit === exitCodes.SUCCESS) {\n return {exitCode: exitCodes.SUCCESS, output: output.join('\\n')}\n }\n\n const message = err instanceof Error ? err.message : String(err)\n if (message) output.push(message)\n return {\n exitCode: typeof exit === 'number' ? exit : exitCodes.RUNTIME_ERROR,\n output: output.join('\\n'),\n }\n }\n}\n"],"names":["Config","Parser","getHelpFlagAdditions","normalizeArgv","CLI_TELEMETRY_SYMBOL","exitCodes","noopLogger","setCliTelemetry","runWithCliExecutionContext","parseArgsStringToArgv","commandPolicies","deny","isConditionalInvocationPolicy","isHelpRequest","renderInvokableHelp","loadCliCommandConfig","load","url","unknownCommandResult","argv","policySet","available","Object","entries","filter","policy","kind","map","id","replaceAll","toSorted","exitCode","USAGE_ERROR","output","slice","join","cachedConfig","stripFlagQuotes","rawToken","match","exec","invokeSanityCli","args","config","source","token","resolvedConfig","globalThis","t","helpArgv","undefined","SUCCESS","helpFlags","includes","err","RUNTIME_ERROR","Error","message","String","commandId","commandArgv","commandDefinition","findCommand","CommandClass","invocation","parsed","parse","baseFlags","enableJsonFlag","flags","strict","validate","displayId","usedDeniedFlags","deniedFlags","name","sink","line","push","stderr","stdout","run","exit","oclif"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/exports/invokeSanityCli/index.ts"],"sourcesContent":["/**\n * Programmatic (in-process) invocation of CLI commands, e.g. from an MCP\n * server. The invokable surface is governed by a per-source command policy\n * (see ./commandPolicies): every CLI command is explicitly allowed, denied,\n * or allowed conditionally on the parsed invocation.\n *\n * {@link invokeSanityCli} handles arg parsing, policy enforcement, command\n * dispatch, per-invocation auth, and output capture.\n * ```ts\n * import {invokeSanityCli} from '@sanity/cli/invokeSanityCli'\n *\n * const {exitCode, output} = await invokeSanityCli({\n * args: 'cors list --project-id abc123',\n * source: 'mcp',\n * token: extra.authInfo.token,\n * })\n * ```\n *\n * Help works like the regular CLI, scoped to the source's policy: `--help`\n * (or `help`) renders root help listing the invokable topics, and a subject\n * (`cors --help`, `cors list --help`) renders topic or command help.\n */\nimport {Config, Parser} from '@oclif/core'\nimport {getHelpFlagAdditions, normalizeArgv} from '@oclif/core/help'\nimport {CLI_TELEMETRY_SYMBOL, exitCodes, noopLogger, setCliTelemetry} from '@sanity/cli-core'\nimport {runWithCliExecutionContext, type SanityEnvironment} from '@sanity/cli-core/executionContext'\nimport {parseArgsStringToArgv} from 'string-argv'\n\nimport {commandPolicies} from './commandPolicies/index.js'\nimport {\n type CommandPolicySet,\n deny,\n type InvocationSource,\n isConditionalInvocationPolicy,\n} from './commandPolicies/policy.js'\nimport {isHelpRequest, renderInvokableHelp} from './help.js'\nimport {prettyPrintError} from './prettyPrintError.js'\n\n/**\n * Load the oclif `Config` for this package, needed to resolve, load, and run\n * commands. Loading it once and reusing it across invocations avoids\n * re-reading the command manifest per call.\n */\nfunction loadCliCommandConfig(): Promise<Config> {\n return Config.load(import.meta.url)\n}\n\nfunction unknownCommandResult(argv: string[], policySet: CommandPolicySet): InvokeSanityCliResult {\n const available = Object.entries(policySet)\n .filter(([, policy]) => policy.kind !== 'deny')\n .map(([id]) => id.replaceAll(':', ' '))\n .toSorted()\n return {\n exitCode: exitCodes.USAGE_ERROR,\n output: [\n `Unknown or unsupported command: ${argv.slice(0, 2).join(' ') || '(none)'}`,\n `Available commands: ${available.join(', ')}`,\n ].join('\\n'),\n }\n}\n\n/**\n * @internal\n */\nexport interface InvokeSanityCliOptions {\n /**\n * Arguments after `sanity` (a leading `sanity` token is tolerated), either\n * as a single string — shell-style quoting is supported, but no shell is\n * ever executed — or as a pre-split argv array.\n */\n args: string | string[]\n\n /**\n * Where this invocation originates. Selects the command policy to enforce:\n * which commands are invokable and which invocations of them are permitted.\n */\n source: InvocationSource\n\n /**\n * Auth token for this invocation. Scoped to this call via the CLI execution\n * context: it never touches process env or the process-wide token cache, so\n * concurrent invocations with different tokens are fully isolated.\n */\n token: string\n\n /**\n * Optional oclif config override (mainly for tests). Defaults to this\n * package's config, loaded once and cached across invocations.\n */\n config?: Config\n\n /**\n * Sanity deployment environment for this invocation. Scoped to this call\n * via the CLI execution context and defaults to `SANITY_INTERNAL_ENV` (or\n * production when it is unset).\n */\n sanityEnv?: SanityEnvironment\n}\n\n/**\n * @internal\n */\nexport interface InvokeSanityCliResult {\n /** `0` on success, the command's exit code otherwise. */\n exitCode: number\n\n /** Combined stdout and stderr output, in emission order. */\n output: string\n\n /**\n * Canonical oclif command id when the invocation resolved to a command\n * exposed by the selected policy (for example, `datasets:create`).\n */\n commandId?: string\n}\n\nlet cachedConfig: Promise<Config> | undefined\n\n/**\n * Unlike a shell, string-argv keeps quotes that are glued to unquoted text:\n * `--name=\"my project\"` tokenizes with the quotes intact. Strip a matching\n * wrapping quote pair from the value side of `--flag=`-shaped tokens so the\n * common shell-style form yields the value the caller intended.\n */\nfunction stripFlagQuotes(rawToken: string): string {\n const match = /^(-{1,2}[^\\s=]+=)(['\"])([\\s\\S]*)\\2$/.exec(rawToken)\n return match ? match[1] + match[3] : rawToken\n}\n\n/**\n * Run a policy-permitted CLI command in-process and capture its result.\n *\n * Command-level failures (unknown command, bad flags, API errors) are\n * reported through `exitCode`/`output` rather than thrown, so callers can\n * relay them verbatim.\n *\n * @internal\n */\nexport async function invokeSanityCli({\n args,\n config,\n sanityEnv,\n source,\n token,\n}: InvokeSanityCliOptions): Promise<InvokeSanityCliResult> {\n const resolvedConfig = config ?? (await (cachedConfig ??= loadCliCommandConfig()))\n const policySet = commandPolicies[source]\n\n // Commands log through the global telemetry store; default it to a noop\n // store so embedding hosts need no telemetry wiring (and see no warnings),\n // without clobbering a store the host may have installed itself.\n if (!(globalThis as Record<symbol, unknown>)[CLI_TELEMETRY_SYMBOL]) {\n setCliTelemetry(noopLogger)\n }\n\n // Pre-split argv arrays are taken verbatim; only string input goes through\n // shell-style tokenization and quote normalization.\n let argv =\n typeof args === 'string'\n ? parseArgsStringToArgv(args).map((t) => stripFlagQuotes(t))\n : [...args]\n if (argv[0] === 'sanity') argv = argv.slice(1)\n\n // Help requests are routed through oclif's help system, scoped to the\n // source's policy: root help for a bare request, topic/command help when a\n // subject is given. Denied subjects get the standard unknown-command\n // response (identical to a truly unknown command, so hosts can't probe the\n // full CLI surface through help), and a help request never executes a\n // command.\n if (isHelpRequest(argv, resolvedConfig)) {\n try {\n // Drop a leading `help` so the rest is the subject, mirroring how\n // oclif's dispatch consumes the token before the help command sees argv\n const helpArgv = argv[0] === 'help' ? argv.slice(1) : argv\n const result = await renderInvokableHelp(resolvedConfig, helpArgv, policySet)\n if (result) return {...result, exitCode: exitCodes.SUCCESS}\n const helpFlags = getHelpFlagAdditions(resolvedConfig)\n return unknownCommandResult(\n helpArgv.filter((token) => !helpFlags.includes(token)),\n policySet,\n )\n } catch (err) {\n return {\n exitCode: exitCodes.RUNTIME_ERROR,\n output: err instanceof Error ? err.message : String(err),\n }\n }\n }\n\n // Resolve the command id the same way oclif's dispatch would (collating\n // space-separated topics, accepting colon-separated ids as-is), then apply\n // the policy. Denied and uncategorized ids fail closed, indistinguishable\n // from commands that don't exist.\n const [commandId = '', ...commandArgv] = normalizeArgv(resolvedConfig, argv)\n const policy = policySet[commandId] ?? deny\n const commandDefinition =\n policy.kind === 'deny' ? undefined : resolvedConfig.findCommand(commandId)\n if (!commandDefinition) return unknownCommandResult(argv, policySet)\n\n const CommandClass = await commandDefinition.load()\n\n // Parse with the command's real definitions (without executing anything) so\n // conditional policies are evaluated against typed args/flags, not tokens.\n let invocation: {args: Record<string, unknown>; flags: Record<string, unknown>}\n try {\n const parsed = await Parser.parse(commandArgv, {\n args: CommandClass.args,\n baseFlags: CommandClass.baseFlags,\n enableJsonFlag: CommandClass.enableJsonFlag,\n flags: CommandClass.flags,\n strict: CommandClass.strict,\n })\n invocation = {\n args: parsed.args as Record<string, unknown>,\n flags: parsed.flags as Record<string, unknown>,\n }\n } catch (err) {\n return {\n commandId,\n exitCode: exitCodes.USAGE_ERROR,\n output: err instanceof Error ? err.message : String(err),\n }\n }\n\n if (!policy.validate(invocation)) {\n const displayId = commandId.replaceAll(':', ' ')\n let output = `This invocation of \\`${displayId}\\` is not supported here`\n\n if (isConditionalInvocationPolicy(policy)) {\n const usedDeniedFlags = policy.deniedFlags.filter(\n (name) => invocation.flags[name] !== undefined && invocation.flags[name] !== false,\n )\n if (usedDeniedFlags.length > 0) {\n output = `\\nThe ${usedDeniedFlags.map((name) => `--${name}`).join(', ')} flag is not supported here for \\`${displayId}\\``\n }\n }\n\n return {\n commandId,\n exitCode: exitCodes.USAGE_ERROR,\n output,\n }\n }\n\n const output: string[] = []\n const sink = (line: string) => output.push(line)\n\n try {\n await runWithCliExecutionContext({sanityEnv, stderr: sink, stdout: sink, token}, () =>\n CommandClass.run(commandArgv, resolvedConfig),\n )\n return {commandId, exitCode: exitCodes.SUCCESS, output: output.join('\\n')}\n } catch (err) {\n const exit = err.oclif?.exit\n\n // `this.exit(0)` throws an ExitError but is a successful outcome\n if (exit === exitCodes.SUCCESS) {\n return {commandId, exitCode: exitCodes.SUCCESS, output: output.join('\\n')}\n }\n\n const message = prettyPrintError(err) || String(err)\n if (message) output.push(message)\n return {\n commandId,\n exitCode: typeof exit === 'number' ? exit : exitCodes.RUNTIME_ERROR,\n output: output.join('\\n'),\n }\n }\n}\n"],"names":["Config","Parser","getHelpFlagAdditions","normalizeArgv","CLI_TELEMETRY_SYMBOL","exitCodes","noopLogger","setCliTelemetry","runWithCliExecutionContext","parseArgsStringToArgv","commandPolicies","deny","isConditionalInvocationPolicy","isHelpRequest","renderInvokableHelp","prettyPrintError","loadCliCommandConfig","load","url","unknownCommandResult","argv","policySet","available","Object","entries","filter","policy","kind","map","id","replaceAll","toSorted","exitCode","USAGE_ERROR","output","slice","join","cachedConfig","stripFlagQuotes","rawToken","match","exec","invokeSanityCli","args","config","sanityEnv","source","token","resolvedConfig","globalThis","t","helpArgv","result","SUCCESS","helpFlags","includes","err","RUNTIME_ERROR","Error","message","String","commandId","commandArgv","commandDefinition","undefined","findCommand","CommandClass","invocation","parsed","parse","baseFlags","enableJsonFlag","flags","strict","validate","displayId","usedDeniedFlags","deniedFlags","name","length","sink","line","push","stderr","stdout","run","exit","oclif"],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ const formatSuggestions = (suggestions)=>{
2
+ const label = 'Try this:';
3
+ if (!suggestions || suggestions.length === 0) return undefined;
4
+ if (suggestions.length === 1) return `${label} ${suggestions[0]}`;
5
+ const multiple = suggestions.map((suggestion)=>` * ${suggestion}`).join('\n');
6
+ return `${label}\n${multiple}`;
7
+ };
8
+ function isCombinedError(error) {
9
+ return error !== null && typeof error === 'object' && 'name' in error && 'message' in error;
10
+ }
11
+ /**
12
+ * Adapted from oclif's unexported `prettyPrint` implementation:
13
+ * https://github.com/oclif/core/blob/4.11.14/src/errors/errors/pretty-print.ts
14
+ *
15
+ * Terminal wrapping, indentation, ANSI decoration, and debug stack handling are omitted so programmatic callers receive stable plain text.
16
+ */ export function prettyPrintError(error) {
17
+ const prettyPrintedErrors = [];
18
+ let currentError = error;
19
+ let isDeep = false;
20
+ while(isCombinedError(currentError)){
21
+ const { code, message, name: errorSuffix, ref, suggestions } = currentError;
22
+ const formattedHeader = message ? `${errorSuffix || 'Error'}: ${message}` : undefined;
23
+ const formattedCode = code ? `Code: ${code}` : undefined;
24
+ const formattedSuggestions = formatSuggestions(suggestions);
25
+ const formattedReference = ref ? `Reference: ${ref}` : undefined;
26
+ const formatted = [
27
+ formattedHeader,
28
+ formattedCode,
29
+ formattedSuggestions,
30
+ formattedReference
31
+ ].filter(Boolean).join('\n');
32
+ prettyPrintedErrors.push(`${isDeep ? 'Caused by: ' : ''}${formatted}`);
33
+ isDeep = true;
34
+ currentError = currentError.cause ?? null;
35
+ }
36
+ return prettyPrintedErrors.join('\n');
37
+ }
38
+
39
+ //# sourceMappingURL=prettyPrintError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/exports/invokeSanityCli/prettyPrintError.ts"],"sourcesContent":["import {type Interfaces} from '@oclif/core'\n\nconst formatSuggestions = (suggestions?: string[]): string | undefined => {\n const label = 'Try this:'\n if (!suggestions || suggestions.length === 0) return undefined\n if (suggestions.length === 1) return `${label} ${suggestions[0]}`\n\n const multiple = suggestions.map((suggestion) => ` * ${suggestion}`).join('\\n')\n return `${label}\\n${multiple}`\n}\n\ntype CombinedError = Error & Interfaces.PrettyPrintableError\n\nfunction isCombinedError(error: unknown): error is CombinedError {\n return error !== null && typeof error === 'object' && 'name' in error && 'message' in error\n}\n\n/**\n * Adapted from oclif's unexported `prettyPrint` implementation:\n * https://github.com/oclif/core/blob/4.11.14/src/errors/errors/pretty-print.ts\n *\n * Terminal wrapping, indentation, ANSI decoration, and debug stack handling are omitted so programmatic callers receive stable plain text.\n */\nexport function prettyPrintError(error: unknown): string {\n const prettyPrintedErrors: string[] = []\n let currentError = error\n let isDeep = false\n\n while (isCombinedError(currentError)) {\n const {code, message, name: errorSuffix, ref, suggestions} = currentError\n const formattedHeader = message ? `${errorSuffix || 'Error'}: ${message}` : undefined\n const formattedCode = code ? `Code: ${code}` : undefined\n const formattedSuggestions = formatSuggestions(suggestions)\n const formattedReference = ref ? `Reference: ${ref}` : undefined\n const formatted = [formattedHeader, formattedCode, formattedSuggestions, formattedReference]\n .filter(Boolean)\n .join('\\n')\n\n prettyPrintedErrors.push(`${isDeep ? 'Caused by: ' : ''}${formatted}`)\n isDeep = true\n currentError = currentError.cause ?? null\n }\n\n return prettyPrintedErrors.join('\\n')\n}\n"],"names":["formatSuggestions","suggestions","label","length","undefined","multiple","map","suggestion","join","isCombinedError","error","prettyPrintError","prettyPrintedErrors","currentError","isDeep","code","message","name","errorSuffix","ref","formattedHeader","formattedCode","formattedSuggestions","formattedReference","formatted","filter","Boolean","push","cause"],"mappings":"AAEA,MAAMA,oBAAoB,CAACC;IACzB,MAAMC,QAAQ;IACd,IAAI,CAACD,eAAeA,YAAYE,MAAM,KAAK,GAAG,OAAOC;IACrD,IAAIH,YAAYE,MAAM,KAAK,GAAG,OAAO,GAAGD,MAAM,CAAC,EAAED,WAAW,CAAC,EAAE,EAAE;IAEjE,MAAMI,WAAWJ,YAAYK,GAAG,CAAC,CAACC,aAAe,CAAC,IAAI,EAAEA,YAAY,EAAEC,IAAI,CAAC;IAC3E,OAAO,GAAGN,MAAM,EAAE,EAAEG,UAAU;AAChC;AAIA,SAASI,gBAAgBC,KAAc;IACrC,OAAOA,UAAU,QAAQ,OAAOA,UAAU,YAAY,UAAUA,SAAS,aAAaA;AACxF;AAEA;;;;;CAKC,GACD,OAAO,SAASC,iBAAiBD,KAAc;IAC7C,MAAME,sBAAgC,EAAE;IACxC,IAAIC,eAAeH;IACnB,IAAII,SAAS;IAEb,MAAOL,gBAAgBI,cAAe;QACpC,MAAM,EAACE,IAAI,EAAEC,OAAO,EAAEC,MAAMC,WAAW,EAAEC,GAAG,EAAElB,WAAW,EAAC,GAAGY;QAC7D,MAAMO,kBAAkBJ,UAAU,GAAGE,eAAe,QAAQ,EAAE,EAAEF,SAAS,GAAGZ;QAC5E,MAAMiB,gBAAgBN,OAAO,CAAC,MAAM,EAAEA,MAAM,GAAGX;QAC/C,MAAMkB,uBAAuBtB,kBAAkBC;QAC/C,MAAMsB,qBAAqBJ,MAAM,CAAC,WAAW,EAAEA,KAAK,GAAGf;QACvD,MAAMoB,YAAY;YAACJ;YAAiBC;YAAeC;YAAsBC;SAAmB,CACzFE,MAAM,CAACC,SACPlB,IAAI,CAAC;QAERI,oBAAoBe,IAAI,CAAC,GAAGb,SAAS,gBAAgB,KAAKU,WAAW;QACrEV,SAAS;QACTD,eAAeA,aAAae,KAAK,IAAI;IACvC;IAEA,OAAOhB,oBAAoBJ,IAAI,CAAC;AAClC"}
@@ -1,3 +1,8 @@
1
- export const getSanityEnv = ()=>process.env.SANITY_INTERNAL_ENV || 'production';
1
+ import { getCliExecutionContext } from '@sanity/cli-core/executionContext';
2
+ export function getSanityEnv() {
3
+ const context = getCliExecutionContext();
4
+ if (context) return context.sanityEnv ?? 'production';
5
+ return process.env.SANITY_INTERNAL_ENV ?? 'production';
6
+ }
2
7
 
3
8
  //# sourceMappingURL=getSanityEnv.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/util/getSanityEnv.ts"],"sourcesContent":["export const getSanityEnv = () => process.env.SANITY_INTERNAL_ENV || 'production'\n"],"names":["getSanityEnv","process","env","SANITY_INTERNAL_ENV"],"mappings":"AAAA,OAAO,MAAMA,eAAe,IAAMC,QAAQC,GAAG,CAACC,mBAAmB,IAAI,aAAY"}
1
+ {"version":3,"sources":["../../src/util/getSanityEnv.ts"],"sourcesContent":["import {getCliExecutionContext} from '@sanity/cli-core/executionContext'\n\nexport function getSanityEnv() {\n const context = getCliExecutionContext()\n\n if (context) return context.sanityEnv ?? 'production'\n\n return process.env.SANITY_INTERNAL_ENV ?? 'production'\n}\n"],"names":["getCliExecutionContext","getSanityEnv","context","sanityEnv","process","env","SANITY_INTERNAL_ENV"],"mappings":"AAAA,SAAQA,sBAAsB,QAAO,oCAAmC;AAExE,OAAO,SAASC;IACd,MAAMC,UAAUF;IAEhB,IAAIE,SAAS,OAAOA,QAAQC,SAAS,IAAI;IAEzC,OAAOC,QAAQC,GAAG,CAACC,mBAAmB,IAAI;AAC5C"}
@@ -1,8 +1,9 @@
1
1
  import path from 'node:path';
2
2
  import { styleText } from 'node:util';
3
+ import { isStaging } from '@sanity/cli-core/util';
3
4
  import { logSymbols } from '@sanity/cli-core/ux';
4
- const baseUrl = process.env.SANITY_INTERNAL_ENV === 'staging' ? 'https://sanity.work' : 'https://www.sanity.io';
5
5
  export function warnAboutMissingAppId({ appType, cliConfigPath, output, projectId }) {
6
+ const baseUrl = isStaging() ? 'https://sanity.work' : 'https://www.sanity.io';
6
7
  const manageUrl = `${baseUrl}/manage${projectId ? `/project/${projectId}/studios` : ''}`;
7
8
  const cliConfigFile = cliConfigPath ? path.basename(cliConfigPath) : 'sanity.cli.ts/.js';
8
9
  output.warn(`${logSymbols.warning} No ${styleText('bold', 'appId')} configured. This ${appType} will auto-update to the ${styleText([
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/util/warnAboutMissingAppId.ts"],"sourcesContent":["import path from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {type Output} from '@sanity/cli-core'\nimport {logSymbols} from '@sanity/cli-core/ux'\n\nconst baseUrl =\n process.env.SANITY_INTERNAL_ENV === 'staging' ? 'https://sanity.work' : 'https://www.sanity.io'\n\nexport function warnAboutMissingAppId({\n appType,\n cliConfigPath,\n output,\n projectId,\n}: {\n appType: 'app' | 'studio'\n cliConfigPath?: string\n output: Output\n projectId?: string\n}) {\n const manageUrl = `${baseUrl}/manage${projectId ? `/project/${projectId}/studios` : ''}`\n const cliConfigFile = cliConfigPath ? path.basename(cliConfigPath) : 'sanity.cli.ts/.js'\n output.warn(\n `${logSymbols.warning} No ${styleText('bold', 'appId')} configured. This ${appType} will auto-update to the ${styleText(['green', 'bold'], 'latest')} channel. To enable fine grained version selection, head over to ${styleText('cyan', manageUrl)} and add the appId to the ${styleText('bold', 'deployment')} section in ${styleText('bold', cliConfigFile)}.\n `,\n )\n}\n"],"names":["path","styleText","logSymbols","baseUrl","process","env","SANITY_INTERNAL_ENV","warnAboutMissingAppId","appType","cliConfigPath","output","projectId","manageUrl","cliConfigFile","basename","warn","warning"],"mappings":"AAAA,OAAOA,UAAU,YAAW;AAC5B,SAAQC,SAAS,QAAO,YAAW;AAGnC,SAAQC,UAAU,QAAO,sBAAqB;AAE9C,MAAMC,UACJC,QAAQC,GAAG,CAACC,mBAAmB,KAAK,YAAY,wBAAwB;AAE1E,OAAO,SAASC,sBAAsB,EACpCC,OAAO,EACPC,aAAa,EACbC,MAAM,EACNC,SAAS,EAMV;IACC,MAAMC,YAAY,GAAGT,QAAQ,OAAO,EAAEQ,YAAY,CAAC,SAAS,EAAEA,UAAU,QAAQ,CAAC,GAAG,IAAI;IACxF,MAAME,gBAAgBJ,gBAAgBT,KAAKc,QAAQ,CAACL,iBAAiB;IACrEC,OAAOK,IAAI,CACT,GAAGb,WAAWc,OAAO,CAAC,IAAI,EAAEf,UAAU,QAAQ,SAAS,kBAAkB,EAAEO,QAAQ,yBAAyB,EAAEP,UAAU;QAAC;QAAS;KAAO,EAAE,UAAU,iEAAiE,EAAEA,UAAU,QAAQW,WAAW,0BAA0B,EAAEX,UAAU,QAAQ,cAAc,YAAY,EAAEA,UAAU,QAAQY,eAAe;QAC5V,CAAC;AAET"}
1
+ {"version":3,"sources":["../../src/util/warnAboutMissingAppId.ts"],"sourcesContent":["import path from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {type Output} from '@sanity/cli-core'\nimport {isStaging} from '@sanity/cli-core/util'\nimport {logSymbols} from '@sanity/cli-core/ux'\n\nexport function warnAboutMissingAppId({\n appType,\n cliConfigPath,\n output,\n projectId,\n}: {\n appType: 'app' | 'studio'\n cliConfigPath?: string\n output: Output\n projectId?: string\n}) {\n const baseUrl = isStaging() ? 'https://sanity.work' : 'https://www.sanity.io'\n const manageUrl = `${baseUrl}/manage${projectId ? `/project/${projectId}/studios` : ''}`\n const cliConfigFile = cliConfigPath ? path.basename(cliConfigPath) : 'sanity.cli.ts/.js'\n output.warn(\n `${logSymbols.warning} No ${styleText('bold', 'appId')} configured. This ${appType} will auto-update to the ${styleText(['green', 'bold'], 'latest')} channel. To enable fine grained version selection, head over to ${styleText('cyan', manageUrl)} and add the appId to the ${styleText('bold', 'deployment')} section in ${styleText('bold', cliConfigFile)}.\n `,\n )\n}\n"],"names":["path","styleText","isStaging","logSymbols","warnAboutMissingAppId","appType","cliConfigPath","output","projectId","baseUrl","manageUrl","cliConfigFile","basename","warn","warning"],"mappings":"AAAA,OAAOA,UAAU,YAAW;AAC5B,SAAQC,SAAS,QAAO,YAAW;AAGnC,SAAQC,SAAS,QAAO,wBAAuB;AAC/C,SAAQC,UAAU,QAAO,sBAAqB;AAE9C,OAAO,SAASC,sBAAsB,EACpCC,OAAO,EACPC,aAAa,EACbC,MAAM,EACNC,SAAS,EAMV;IACC,MAAMC,UAAUP,cAAc,wBAAwB;IACtD,MAAMQ,YAAY,GAAGD,QAAQ,OAAO,EAAED,YAAY,CAAC,SAAS,EAAEA,UAAU,QAAQ,CAAC,GAAG,IAAI;IACxF,MAAMG,gBAAgBL,gBAAgBN,KAAKY,QAAQ,CAACN,iBAAiB;IACrEC,OAAOM,IAAI,CACT,GAAGV,WAAWW,OAAO,CAAC,IAAI,EAAEb,UAAU,QAAQ,SAAS,kBAAkB,EAAEI,QAAQ,yBAAyB,EAAEJ,UAAU;QAAC;QAAS;KAAO,EAAE,UAAU,iEAAiE,EAAEA,UAAU,QAAQS,WAAW,0BAA0B,EAAET,UAAU,QAAQ,cAAc,YAAY,EAAEA,UAAU,QAAQU,eAAe;QAC5V,CAAC;AAET"}
@@ -1013,6 +1013,10 @@
1013
1013
  "command": "<%= config.bin %> <%= command.id %>",
1014
1014
  "description": "Log in using default settings"
1015
1015
  },
1016
+ {
1017
+ "command": "<%= config.bin %> <%= command.id %> --with-token < token.txt",
1018
+ "description": "Log in using a token from standard input"
1019
+ },
1016
1020
  {
1017
1021
  "command": "<%= config.bin %> <%= command.id %> --provider github --no-open",
1018
1022
  "description": "Login with GitHub provider, but do not open a browser window automatically"
@@ -1024,13 +1028,19 @@
1024
1028
  {
1025
1029
  "command": "<%= config.bin %> <%= command.id %> --sso my-organization --sso-provider \"Okta SSO\"",
1026
1030
  "description": "Log in using a specific SSO provider within an organization"
1027
- },
1028
- {
1029
- "command": "<%= config.bin %> <%= command.id %> --with-token < token.txt",
1030
- "description": "Log in using a token from standard input"
1031
1031
  }
1032
1032
  ],
1033
1033
  "flags": {
1034
+ "with-token": {
1035
+ "description": "Read token from standard input",
1036
+ "exclusive": [
1037
+ "provider",
1038
+ "sso"
1039
+ ],
1040
+ "name": "with-token",
1041
+ "allowNo": false,
1042
+ "type": "boolean"
1043
+ },
1034
1044
  "experimental": {
1035
1045
  "hidden": true,
1036
1046
  "name": "experimental",
@@ -1077,16 +1087,6 @@
1077
1087
  "helpValue": "<name>",
1078
1088
  "multiple": false,
1079
1089
  "type": "option"
1080
- },
1081
- "with-token": {
1082
- "description": "Read token from standard input",
1083
- "exclusive": [
1084
- "provider",
1085
- "sso"
1086
- ],
1087
- "name": "with-token",
1088
- "allowNo": false,
1089
- "type": "boolean"
1090
1090
  }
1091
1091
  },
1092
1092
  "hasDynamicHelp": false,
@@ -3409,45 +3409,6 @@
3409
3409
  "logs.js"
3410
3410
  ]
3411
3411
  },
3412
- "manifest:extract": {
3413
- "aliases": [],
3414
- "args": {},
3415
- "description": "Extract studio configuration as JSON manifest files.\n\nNote: This command is experimental and subject to change. It is currently intended for use with Create only.",
3416
- "examples": [
3417
- {
3418
- "command": "<%= config.bin %> <%= command.id %>",
3419
- "description": "Extracts manifests"
3420
- },
3421
- {
3422
- "command": "<%= config.bin %> <%= command.id %> --path /public/static",
3423
- "description": "Extracts manifests into /public/static"
3424
- }
3425
- ],
3426
- "flags": {
3427
- "path": {
3428
- "description": "Optional path to specify destination directory of the manifest files",
3429
- "name": "path",
3430
- "default": "dist/static",
3431
- "hasDynamicHelp": false,
3432
- "multiple": false,
3433
- "type": "option"
3434
- }
3435
- },
3436
- "hasDynamicHelp": false,
3437
- "hiddenAliases": [],
3438
- "id": "manifest:extract",
3439
- "pluginAlias": "@sanity/cli",
3440
- "pluginName": "@sanity/cli",
3441
- "pluginType": "core",
3442
- "strict": true,
3443
- "isESM": true,
3444
- "relativePath": [
3445
- "dist",
3446
- "commands",
3447
- "manifest",
3448
- "extract.js"
3449
- ]
3450
- },
3451
3412
  "mcp:configure": {
3452
3413
  "aliases": [],
3453
3414
  "args": {},
@@ -5313,6 +5274,45 @@
5313
5274
  "list.js"
5314
5275
  ]
5315
5276
  },
5277
+ "manifest:extract": {
5278
+ "aliases": [],
5279
+ "args": {},
5280
+ "description": "Extract studio configuration as JSON manifest files.\n\nNote: This command is experimental and subject to change. It is currently intended for use with Create only.",
5281
+ "examples": [
5282
+ {
5283
+ "command": "<%= config.bin %> <%= command.id %>",
5284
+ "description": "Extracts manifests"
5285
+ },
5286
+ {
5287
+ "command": "<%= config.bin %> <%= command.id %> --path /public/static",
5288
+ "description": "Extracts manifests into /public/static"
5289
+ }
5290
+ ],
5291
+ "flags": {
5292
+ "path": {
5293
+ "description": "Optional path to specify destination directory of the manifest files",
5294
+ "name": "path",
5295
+ "default": "dist/static",
5296
+ "hasDynamicHelp": false,
5297
+ "multiple": false,
5298
+ "type": "option"
5299
+ }
5300
+ },
5301
+ "hasDynamicHelp": false,
5302
+ "hiddenAliases": [],
5303
+ "id": "manifest:extract",
5304
+ "pluginAlias": "@sanity/cli",
5305
+ "pluginName": "@sanity/cli",
5306
+ "pluginType": "core",
5307
+ "strict": true,
5308
+ "isESM": true,
5309
+ "relativePath": [
5310
+ "dist",
5311
+ "commands",
5312
+ "manifest",
5313
+ "extract.js"
5314
+ ]
5315
+ },
5316
5316
  "datasets:alias:create": {
5317
5317
  "aliases": [],
5318
5318
  "args": {
@@ -5830,5 +5830,5 @@
5830
5830
  ]
5831
5831
  }
5832
5832
  },
5833
- "version": "7.14.0"
5833
+ "version": "7.15.0"
5834
5834
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/cli",
3
- "version": "7.14.0",
3
+ "version": "7.15.0",
4
4
  "description": "Sanity CLI tool for managing Sanity projects and organizations",
5
5
  "keywords": [
6
6
  "cli",
@@ -69,12 +69,12 @@
69
69
  "@sanity/generate-help-url": "^4.0.0",
70
70
  "@sanity/id-utils": "^1.0.0",
71
71
  "@sanity/import": "^6.0.3",
72
- "@sanity/migrate": "^8.0.0",
73
- "@sanity/runtime-cli": "^17.1.0",
74
- "@sanity/schema": "^6.5.0",
72
+ "@sanity/migrate": "^8.0.1",
73
+ "@sanity/runtime-cli": "^17.3.0",
74
+ "@sanity/schema": "^6.7.0",
75
75
  "@sanity/telemetry": "^1.1.0",
76
76
  "@sanity/template-validator": "^3.1.0",
77
- "@sanity/types": "^6.5.0",
77
+ "@sanity/types": "^6.7.0",
78
78
  "@sanity/worker-channels": "^2.0.0",
79
79
  "@vercel/frameworks": "3.29.0",
80
80
  "chokidar": "^5.0.0",
@@ -123,14 +123,14 @@
123
123
  "which": "^6.0.1",
124
124
  "yaml": "^2.9.0",
125
125
  "zod": "^4.4.3",
126
- "@sanity/cli-build": "^5.0.0",
127
- "@sanity/cli-core": "^2.6.0",
128
- "@sanity/workbench-cli": "^1.7.2"
126
+ "@sanity/cli-core": "^2.7.0",
127
+ "@sanity/cli-build": "^5.1.0",
128
+ "@sanity/workbench-cli": "^1.8.0"
129
129
  },
130
130
  "devDependencies": {
131
131
  "@eslint/compat": "^2.1.0",
132
132
  "@sanity/pkg-utils": "^11.0.9",
133
- "@sanity/ui": "^3.3.5",
133
+ "@sanity/ui": "^3.5.0",
134
134
  "@swc/cli": "^0.8.1",
135
135
  "@swc/core": "^1.15.43",
136
136
  "@types/debug": "^4.1.13",
@@ -155,12 +155,12 @@
155
155
  "oclif": "^4.23.27",
156
156
  "publint": "^0.3.21",
157
157
  "rimraf": "^6.0.1",
158
- "sanity": "^6.5.0",
158
+ "sanity": "^6.7.0",
159
159
  "typescript": "^6.0.3",
160
160
  "vitest": "^4.1.10",
161
- "@repo/tsconfig": "3.70.0",
162
161
  "@repo/package.config": "0.0.1",
163
- "@sanity/cli-test": "7.0.0",
162
+ "@repo/tsconfig": "3.70.0",
163
+ "@sanity/cli-test": "8.0.0",
164
164
  "@sanity/eslint-config-cli": "1.1.3"
165
165
  },
166
166
  "peerDependencies": {