@sanity/cli 6.5.2 → 6.6.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
@@ -2489,13 +2489,14 @@ Log in to your Sanity account
2489
2489
 
2490
2490
  ```
2491
2491
  USAGE
2492
- $ sanity login [--open] [--provider <providerId> | --sso <slug>] [--sso-provider <name> ]
2492
+ $ sanity login [--open] [--provider <providerId> | --sso <slug> | --with-token] [--sso-provider <name> ]
2493
2493
 
2494
2494
  FLAGS
2495
2495
  --[no-]open Open a browser window to log in (`--no-open` only prints URL)
2496
2496
  --provider=<providerId> Log in using the given provider
2497
2497
  --sso=<slug> Log in using Single Sign-On, using the given organization slug
2498
2498
  --sso-provider=<name> Select a specific SSO provider by name (use with --sso)
2499
+ --with-token Read token from standard input
2499
2500
 
2500
2501
  DESCRIPTION
2501
2502
  Log in to your Sanity account
@@ -2516,6 +2517,10 @@ EXAMPLES
2516
2517
  Log in using a specific SSO provider within an organization
2517
2518
 
2518
2519
  $ sanity login --sso my-organization --sso-provider "Okta SSO"
2520
+
2521
+ Log in using a token from standard input
2522
+
2523
+ $ sanity login --with-token < token.txt
2519
2524
  ```
2520
2525
 
2521
2526
  ## `sanity logout`
package/bin/run.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import {execute} from '@oclif/core'
2
+ import {execute, settings} from '@oclif/core'
3
3
 
4
4
  var err = '\u001B[31m\u001B[1mERROR:\u001B[22m\u001B[39m '
5
5
  var nodeVersionParts = process.version.replace(/^v/i, '').split('.').map(Number)
@@ -30,4 +30,6 @@ if (!isSupportedNodeVersion(majorVersion, minorVersion, patchVersion)) {
30
30
  process.exit(1)
31
31
  }
32
32
 
33
+ settings.enableAutoTranspile = false
34
+
33
35
  await execute({dir: import.meta.url})
@@ -1,16 +1,19 @@
1
1
  import { getCliToken, getUserConfig, setCliUserConfig, subdebug } from '@sanity/cli-core';
2
2
  import { spinner } from '@sanity/cli-core/ux';
3
+ import { isHttpError } from '@sanity/client';
3
4
  import open from 'open';
4
5
  import { logout } from '../../../services/auth.js';
5
6
  import { LoginTrace } from '../../../telemetry/login.telemetry.js';
6
7
  import { canLaunchBrowser } from '../../../util/canLaunchBrowser.js';
7
8
  import { startServerForTokenCallback } from '../authServer.js';
8
9
  import { getProvider } from './getProvider.js';
10
+ import { isSanityApiToken, validateToken } from './validateToken.js';
9
11
  const debug = subdebug('login');
10
12
  /**
11
13
  * Trigger the authentication flow for the CLI.
12
14
  *
13
- * NOTE: This uses terminal prompts and will not work for non-interactive/programmatic uses.
15
+ * NOTE: Without a token option, this uses terminal prompts and will not work for
16
+ * non-interactive/programmatic uses.
14
17
  *
15
18
  * @param options - Options for the login operation
16
19
  * @returns Promise that resolves when the login operation is complete
@@ -19,9 +22,19 @@ const debug = subdebug('login');
19
22
  */ export async function login(options) {
20
23
  const { output, telemetry } = options;
21
24
  const previousToken = await getCliToken();
22
- const hasExistingToken = Boolean(previousToken);
23
25
  const trace = telemetry.trace(LoginTrace);
24
26
  trace.start();
27
+ if (typeof options.token === 'string') {
28
+ try {
29
+ const authToken = await validateToken(options.token);
30
+ await storeAuthToken(authToken, previousToken, output);
31
+ trace.complete();
32
+ } catch (err) {
33
+ trace.error(err);
34
+ throw err;
35
+ }
36
+ return;
37
+ }
25
38
  const provider = await getProvider({
26
39
  experimental: options.experimental,
27
40
  orgSlug: options.sso,
@@ -62,20 +75,30 @@ const debug = subdebug('login');
62
75
  server.close(()=>resolve());
63
76
  });
64
77
  }
65
- // Store the token
78
+ await storeAuthToken(authToken, previousToken, output);
79
+ trace.complete();
80
+ }
81
+ async function storeAuthToken(authToken, previousToken, output) {
66
82
  setCliUserConfig('authToken', authToken);
67
- // Clear cached telemetry consent
68
83
  getUserConfig().delete('telemetryConsent');
69
84
  // If we had a session previously, attempt to clear it
70
- if (hasExistingToken) {
71
- await logout(previousToken).catch((err)=>{
72
- const statusCode = err && err.response && err.response.statusCode;
73
- if (statusCode !== 401) {
74
- output.warn('Failed to invalidate previous session');
75
- }
76
- });
85
+ if (previousToken && previousToken !== authToken) {
86
+ await invalidateAuthToken(previousToken, output);
87
+ }
88
+ }
89
+ async function invalidateAuthToken(token, output) {
90
+ try {
91
+ if (await isSanityApiToken(token)) return;
92
+ } catch (err) {
93
+ if (isHttpError(err) && err.statusCode === 401) return;
94
+ }
95
+ try {
96
+ await logout(token);
97
+ } catch (err) {
98
+ if (!isHttpError(err) || err.statusCode !== 401) {
99
+ output.warn('Failed to invalidate previous session');
100
+ }
77
101
  }
78
- trace.complete();
79
102
  }
80
103
 
81
104
  //# sourceMappingURL=login.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/actions/auth/login/login.ts"],"sourcesContent":["import {\n type CLITelemetryStore,\n getCliToken,\n getUserConfig,\n type Output,\n setCliUserConfig,\n subdebug,\n} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport open from 'open'\n\nimport {logout} from '../../../services/auth.js'\nimport {LoginTrace} from '../../../telemetry/login.telemetry.js'\nimport {canLaunchBrowser} from '../../../util/canLaunchBrowser.js'\nimport {startServerForTokenCallback} from '../authServer.js'\nimport {getProvider} from './getProvider.js'\n\nconst debug = subdebug('login')\n\ninterface LoginOptions {\n output: Output\n\n telemetry: CLITelemetryStore\n\n experimental?: boolean\n open?: boolean\n provider?: string\n sso?: string\n ssoProvider?: string\n}\n\n/**\n * Trigger the authentication flow for the CLI.\n *\n * NOTE: This uses terminal prompts and will not work for non-interactive/programmatic uses.\n *\n * @param options - Options for the login operation\n * @returns Promise that resolves when the login operation is complete\n * @throws Will throw if login fails or is cancelled\n * @internal\n */\nexport async function login(options: LoginOptions) {\n const {output, telemetry} = options\n const previousToken = await getCliToken()\n const hasExistingToken = Boolean(previousToken)\n\n const trace = telemetry.trace(LoginTrace)\n trace.start()\n\n const provider = await getProvider({\n experimental: options.experimental,\n orgSlug: options.sso,\n specifiedProvider: options.provider,\n ssoProvider: options.ssoProvider,\n })\n\n trace.log({provider: provider?.name, step: 'selectProvider'})\n\n if (provider === undefined) {\n throw new Error('No authentication providers found')\n }\n\n const {loginUrl, server, token: tokenPromise} = await startServerForTokenCallback(provider.url)\n\n trace.log({step: 'waitForToken'})\n\n // Open a browser on the login page (or tell the user to)\n const shouldLaunchBrowser = canLaunchBrowser() && options.open !== false\n const actionText = shouldLaunchBrowser ? 'Opening browser at' : 'Please open a browser at'\n\n output.log(`\\n${actionText} ${loginUrl.href}\\n`)\n\n const spin = spinner('Waiting for browser login to complete... Press Ctrl + C to cancel').start()\n\n if (shouldLaunchBrowser) {\n open(loginUrl.href)\n }\n\n // Wait for a success/error on the HTTP callback server\n let authToken: string\n try {\n authToken = (await tokenPromise).token\n spin.stop()\n } catch (err: unknown) {\n spin.stop()\n trace.error(err as Error)\n debug('Error retrieving token: %O', err)\n throw err\n } finally {\n await new Promise<void>((resolve) => {\n server.close(() => resolve())\n })\n }\n\n // Store the token\n setCliUserConfig('authToken', authToken)\n\n // Clear cached telemetry consent\n getUserConfig().delete('telemetryConsent')\n\n // If we had a session previously, attempt to clear it\n if (hasExistingToken) {\n await logout(previousToken).catch((err) => {\n const statusCode = err && err.response && err.response.statusCode\n if (statusCode !== 401) {\n output.warn('Failed to invalidate previous session')\n }\n })\n }\n\n trace.complete()\n}\n"],"names":["getCliToken","getUserConfig","setCliUserConfig","subdebug","spinner","open","logout","LoginTrace","canLaunchBrowser","startServerForTokenCallback","getProvider","debug","login","options","output","telemetry","previousToken","hasExistingToken","Boolean","trace","start","provider","experimental","orgSlug","sso","specifiedProvider","ssoProvider","log","name","step","undefined","Error","loginUrl","server","token","tokenPromise","url","shouldLaunchBrowser","actionText","href","spin","authToken","stop","err","error","Promise","resolve","close","delete","catch","statusCode","response","warn","complete"],"mappings":"AAAA,SAEEA,WAAW,EACXC,aAAa,EAEbC,gBAAgB,EAChBC,QAAQ,QACH,mBAAkB;AACzB,SAAQC,OAAO,QAAO,sBAAqB;AAC3C,OAAOC,UAAU,OAAM;AAEvB,SAAQC,MAAM,QAAO,4BAA2B;AAChD,SAAQC,UAAU,QAAO,wCAAuC;AAChE,SAAQC,gBAAgB,QAAO,oCAAmC;AAClE,SAAQC,2BAA2B,QAAO,mBAAkB;AAC5D,SAAQC,WAAW,QAAO,mBAAkB;AAE5C,MAAMC,QAAQR,SAAS;AAcvB;;;;;;;;;CASC,GACD,OAAO,eAAeS,MAAMC,OAAqB;IAC/C,MAAM,EAACC,MAAM,EAAEC,SAAS,EAAC,GAAGF;IAC5B,MAAMG,gBAAgB,MAAMhB;IAC5B,MAAMiB,mBAAmBC,QAAQF;IAEjC,MAAMG,QAAQJ,UAAUI,KAAK,CAACZ;IAC9BY,MAAMC,KAAK;IAEX,MAAMC,WAAW,MAAMX,YAAY;QACjCY,cAAcT,QAAQS,YAAY;QAClCC,SAASV,QAAQW,GAAG;QACpBC,mBAAmBZ,QAAQQ,QAAQ;QACnCK,aAAab,QAAQa,WAAW;IAClC;IAEAP,MAAMQ,GAAG,CAAC;QAACN,UAAUA,UAAUO;QAAMC,MAAM;IAAgB;IAE3D,IAAIR,aAAaS,WAAW;QAC1B,MAAM,IAAIC,MAAM;IAClB;IAEA,MAAM,EAACC,QAAQ,EAAEC,MAAM,EAAEC,OAAOC,YAAY,EAAC,GAAG,MAAM1B,4BAA4BY,SAASe,GAAG;IAE9FjB,MAAMQ,GAAG,CAAC;QAACE,MAAM;IAAc;IAE/B,yDAAyD;IACzD,MAAMQ,sBAAsB7B,sBAAsBK,QAAQR,IAAI,KAAK;IACnE,MAAMiC,aAAaD,sBAAsB,uBAAuB;IAEhEvB,OAAOa,GAAG,CAAC,CAAC,EAAE,EAAEW,WAAW,CAAC,EAAEN,SAASO,IAAI,CAAC,EAAE,CAAC;IAE/C,MAAMC,OAAOpC,QAAQ,qEAAqEgB,KAAK;IAE/F,IAAIiB,qBAAqB;QACvBhC,KAAK2B,SAASO,IAAI;IACpB;IAEA,uDAAuD;IACvD,IAAIE;IACJ,IAAI;QACFA,YAAY,AAAC,CAAA,MAAMN,YAAW,EAAGD,KAAK;QACtCM,KAAKE,IAAI;IACX,EAAE,OAAOC,KAAc;QACrBH,KAAKE,IAAI;QACTvB,MAAMyB,KAAK,CAACD;QACZhC,MAAM,8BAA8BgC;QACpC,MAAMA;IACR,SAAU;QACR,MAAM,IAAIE,QAAc,CAACC;YACvBb,OAAOc,KAAK,CAAC,IAAMD;QACrB;IACF;IAEA,kBAAkB;IAClB5C,iBAAiB,aAAauC;IAE9B,iCAAiC;IACjCxC,gBAAgB+C,MAAM,CAAC;IAEvB,sDAAsD;IACtD,IAAI/B,kBAAkB;QACpB,MAAMX,OAAOU,eAAeiC,KAAK,CAAC,CAACN;YACjC,MAAMO,aAAaP,OAAOA,IAAIQ,QAAQ,IAAIR,IAAIQ,QAAQ,CAACD,UAAU;YACjE,IAAIA,eAAe,KAAK;gBACtBpC,OAAOsC,IAAI,CAAC;YACd;QACF;IACF;IAEAjC,MAAMkC,QAAQ;AAChB"}
1
+ {"version":3,"sources":["../../../../src/actions/auth/login/login.ts"],"sourcesContent":["import {\n type CLITelemetryStore,\n getCliToken,\n getUserConfig,\n type Output,\n setCliUserConfig,\n subdebug,\n} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {isHttpError} from '@sanity/client'\nimport open from 'open'\n\nimport {logout} from '../../../services/auth.js'\nimport {LoginTrace} from '../../../telemetry/login.telemetry.js'\nimport {canLaunchBrowser} from '../../../util/canLaunchBrowser.js'\nimport {startServerForTokenCallback} from '../authServer.js'\nimport {getProvider} from './getProvider.js'\nimport {isSanityApiToken, validateToken} from './validateToken.js'\n\nconst debug = subdebug('login')\n\ninterface LoginOptions {\n output: Output\n\n telemetry: CLITelemetryStore\n\n experimental?: boolean\n open?: boolean\n provider?: string\n sso?: string\n ssoProvider?: string\n token?: string\n}\n\n/**\n * Trigger the authentication flow for the CLI.\n *\n * NOTE: Without a token option, this uses terminal prompts and will not work for\n * non-interactive/programmatic uses.\n *\n * @param options - Options for the login operation\n * @returns Promise that resolves when the login operation is complete\n * @throws Will throw if login fails or is cancelled\n * @internal\n */\nexport async function login(options: LoginOptions) {\n const {output, telemetry} = options\n const previousToken = await getCliToken()\n\n const trace = telemetry.trace(LoginTrace)\n trace.start()\n\n if (typeof options.token === 'string') {\n try {\n const authToken = await validateToken(options.token)\n await storeAuthToken(authToken, previousToken, output)\n trace.complete()\n } catch (err: unknown) {\n trace.error(err as Error)\n throw err\n }\n return\n }\n\n const provider = await getProvider({\n experimental: options.experimental,\n orgSlug: options.sso,\n specifiedProvider: options.provider,\n ssoProvider: options.ssoProvider,\n })\n\n trace.log({provider: provider?.name, step: 'selectProvider'})\n\n if (provider === undefined) {\n throw new Error('No authentication providers found')\n }\n\n const {loginUrl, server, token: tokenPromise} = await startServerForTokenCallback(provider.url)\n\n trace.log({step: 'waitForToken'})\n\n // Open a browser on the login page (or tell the user to)\n const shouldLaunchBrowser = canLaunchBrowser() && options.open !== false\n const actionText = shouldLaunchBrowser ? 'Opening browser at' : 'Please open a browser at'\n\n output.log(`\\n${actionText} ${loginUrl.href}\\n`)\n\n const spin = spinner('Waiting for browser login to complete... Press Ctrl + C to cancel').start()\n\n if (shouldLaunchBrowser) {\n open(loginUrl.href)\n }\n\n // Wait for a success/error on the HTTP callback server\n let authToken: string\n try {\n authToken = (await tokenPromise).token\n spin.stop()\n } catch (err: unknown) {\n spin.stop()\n trace.error(err as Error)\n debug('Error retrieving token: %O', err)\n throw err\n } finally {\n await new Promise<void>((resolve) => {\n server.close(() => resolve())\n })\n }\n\n await storeAuthToken(authToken, previousToken, output)\n\n trace.complete()\n}\n\nasync function storeAuthToken(\n authToken: string,\n previousToken: string | undefined,\n output: Output,\n) {\n setCliUserConfig('authToken', authToken)\n getUserConfig().delete('telemetryConsent')\n\n // If we had a session previously, attempt to clear it\n if (previousToken && previousToken !== authToken) {\n await invalidateAuthToken(previousToken, output)\n }\n}\n\nasync function invalidateAuthToken(token: string, output: Output) {\n try {\n if (await isSanityApiToken(token)) return\n } catch (err) {\n if (isHttpError(err) && err.statusCode === 401) return\n }\n\n try {\n await logout(token)\n } catch (err) {\n if (!isHttpError(err) || err.statusCode !== 401) {\n output.warn('Failed to invalidate previous session')\n }\n }\n}\n"],"names":["getCliToken","getUserConfig","setCliUserConfig","subdebug","spinner","isHttpError","open","logout","LoginTrace","canLaunchBrowser","startServerForTokenCallback","getProvider","isSanityApiToken","validateToken","debug","login","options","output","telemetry","previousToken","trace","start","token","authToken","storeAuthToken","complete","err","error","provider","experimental","orgSlug","sso","specifiedProvider","ssoProvider","log","name","step","undefined","Error","loginUrl","server","tokenPromise","url","shouldLaunchBrowser","actionText","href","spin","stop","Promise","resolve","close","delete","invalidateAuthToken","statusCode","warn"],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { getGlobalCliClient } from '@sanity/cli-core';
2
+ import { isHttpError } from '@sanity/client';
3
+ import { USERS_API_VERSION } from '../../../services/user.js';
4
+ import { getErrorMessage } from '../../../util/getErrorMessage.js';
5
+ export async function validateToken(token) {
6
+ const trimmedToken = token.trim();
7
+ if (!trimmedToken) {
8
+ throw new Error('Token is required on standard input. Run `sanity login --with-token < token.txt`.');
9
+ }
10
+ try {
11
+ await getTokenUser(trimmedToken);
12
+ } catch (error) {
13
+ if (isHttpError(error) && (error.statusCode === 401 || error.statusCode === 403)) {
14
+ throw new Error('Token is invalid or expired. Check the token and try again.', {
15
+ cause: error
16
+ });
17
+ }
18
+ throw new Error(`Could not verify token: ${getErrorMessage(error)}`, {
19
+ cause: error
20
+ });
21
+ }
22
+ return trimmedToken;
23
+ }
24
+ export async function isSanityApiToken(token) {
25
+ return isSanityApiTokenUser(await getTokenUser(token));
26
+ }
27
+ async function getTokenUser(token) {
28
+ const client = await getGlobalCliClient({
29
+ apiVersion: USERS_API_VERSION,
30
+ requireUser: true,
31
+ token
32
+ });
33
+ return client.users.getById('me');
34
+ }
35
+ function isSanityApiTokenUser(user) {
36
+ return typeof user === 'object' && user !== null && 'provider' in user && user.provider === 'sanity-token';
37
+ }
38
+
39
+ //# sourceMappingURL=validateToken.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/actions/auth/login/validateToken.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\nimport {isHttpError} from '@sanity/client'\n\nimport {USERS_API_VERSION} from '../../../services/user.js'\nimport {getErrorMessage} from '../../../util/getErrorMessage.js'\n\nexport async function validateToken(token: string): Promise<string> {\n const trimmedToken = token.trim()\n\n if (!trimmedToken) {\n throw new Error(\n 'Token is required on standard input. Run `sanity login --with-token < token.txt`.',\n )\n }\n\n try {\n await getTokenUser(trimmedToken)\n } catch (error) {\n if (isHttpError(error) && (error.statusCode === 401 || error.statusCode === 403)) {\n throw new Error('Token is invalid or expired. Check the token and try again.', {cause: error})\n }\n\n throw new Error(`Could not verify token: ${getErrorMessage(error)}`, {cause: error})\n }\n\n return trimmedToken\n}\n\nexport async function isSanityApiToken(token: string): Promise<boolean> {\n return isSanityApiTokenUser(await getTokenUser(token))\n}\n\nasync function getTokenUser(token: string): Promise<unknown> {\n const client = await getGlobalCliClient({\n apiVersion: USERS_API_VERSION,\n requireUser: true,\n token,\n })\n\n return client.users.getById('me')\n}\n\nfunction isSanityApiTokenUser(user: unknown): boolean {\n return (\n typeof user === 'object' &&\n user !== null &&\n 'provider' in user &&\n (user as {provider?: unknown}).provider === 'sanity-token'\n )\n}\n"],"names":["getGlobalCliClient","isHttpError","USERS_API_VERSION","getErrorMessage","validateToken","token","trimmedToken","trim","Error","getTokenUser","error","statusCode","cause","isSanityApiToken","isSanityApiTokenUser","client","apiVersion","requireUser","users","getById","user","provider"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,mBAAkB;AACnD,SAAQC,WAAW,QAAO,iBAAgB;AAE1C,SAAQC,iBAAiB,QAAO,4BAA2B;AAC3D,SAAQC,eAAe,QAAO,mCAAkC;AAEhE,OAAO,eAAeC,cAAcC,KAAa;IAC/C,MAAMC,eAAeD,MAAME,IAAI;IAE/B,IAAI,CAACD,cAAc;QACjB,MAAM,IAAIE,MACR;IAEJ;IAEA,IAAI;QACF,MAAMC,aAAaH;IACrB,EAAE,OAAOI,OAAO;QACd,IAAIT,YAAYS,UAAWA,CAAAA,MAAMC,UAAU,KAAK,OAAOD,MAAMC,UAAU,KAAK,GAAE,GAAI;YAChF,MAAM,IAAIH,MAAM,+DAA+D;gBAACI,OAAOF;YAAK;QAC9F;QAEA,MAAM,IAAIF,MAAM,CAAC,wBAAwB,EAAEL,gBAAgBO,QAAQ,EAAE;YAACE,OAAOF;QAAK;IACpF;IAEA,OAAOJ;AACT;AAEA,OAAO,eAAeO,iBAAiBR,KAAa;IAClD,OAAOS,qBAAqB,MAAML,aAAaJ;AACjD;AAEA,eAAeI,aAAaJ,KAAa;IACvC,MAAMU,SAAS,MAAMf,mBAAmB;QACtCgB,YAAYd;QACZe,aAAa;QACbZ;IACF;IAEA,OAAOU,OAAOG,KAAK,CAACC,OAAO,CAAC;AAC9B;AAEA,SAASL,qBAAqBM,IAAa;IACzC,OACE,OAAOA,SAAS,YAChBA,SAAS,QACT,cAAcA,QACd,AAACA,KAA8BC,QAAQ,KAAK;AAEhD"}
@@ -1,3 +1,4 @@
1
+ import { text } from 'node:stream/consumers';
1
2
  import { Flags } from '@oclif/core';
2
3
  import { SanityCommand } from '@sanity/cli-core';
3
4
  import { login } from '../actions/auth/login/login.js';
@@ -19,6 +20,10 @@ export class LoginCommand extends SanityCommand {
19
20
  {
20
21
  command: '<%= config.bin %> <%= command.id %> --sso my-organization --sso-provider "Okta SSO"',
21
22
  description: 'Log in using a specific SSO provider within an organization'
23
+ },
24
+ {
25
+ command: '<%= config.bin %> <%= command.id %> --with-token < token.txt',
26
+ description: 'Log in using a token from standard input'
22
27
  }
23
28
  ];
24
29
  static flags = {
@@ -34,14 +39,16 @@ export class LoginCommand extends SanityCommand {
34
39
  provider: Flags.string({
35
40
  description: 'Log in using the given provider',
36
41
  exclusive: [
37
- 'sso'
42
+ 'sso',
43
+ 'with-token'
38
44
  ],
39
45
  helpValue: '<providerId>'
40
46
  }),
41
47
  sso: Flags.string({
42
48
  description: 'Log in using Single Sign-On, using the given organization slug',
43
49
  exclusive: [
44
- 'provider'
50
+ 'provider',
51
+ 'with-token'
45
52
  ],
46
53
  helpValue: '<slug>'
47
54
  }),
@@ -51,16 +58,26 @@ export class LoginCommand extends SanityCommand {
51
58
  ],
52
59
  description: 'Select a specific SSO provider by name (use with --sso)',
53
60
  helpValue: '<name>'
61
+ }),
62
+ 'with-token': Flags.boolean({
63
+ description: 'Read token from standard input',
64
+ exclusive: [
65
+ 'provider',
66
+ 'sso'
67
+ ]
54
68
  })
55
69
  };
56
70
  async run() {
57
71
  const { flags } = await this.parse(LoginCommand);
72
+ const { 'sso-provider': ssoProvider, 'with-token': withToken, ...loginFlags } = flags;
58
73
  try {
74
+ const token = withToken ? await readTokenFromStdin() : undefined;
59
75
  await login({
60
- ...flags,
76
+ ...loginFlags,
61
77
  output: this.output,
62
- ssoProvider: flags['sso-provider'],
63
- telemetry: this.telemetry
78
+ ssoProvider,
79
+ telemetry: this.telemetry,
80
+ token
64
81
  });
65
82
  this.log('Login successful');
66
83
  } catch (error) {
@@ -71,5 +88,11 @@ export class LoginCommand extends SanityCommand {
71
88
  }
72
89
  }
73
90
  }
91
+ async function readTokenFromStdin() {
92
+ if (process.stdin.isTTY) {
93
+ throw new Error('Token is required on standard input. Run `sanity login --with-token < token.txt`.');
94
+ }
95
+ return text(process.stdin);
96
+ }
74
97
 
75
98
  //# sourceMappingURL=login.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/login.ts"],"sourcesContent":["import {Command, Flags} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {SanityCommand} from '@sanity/cli-core'\n\nimport {login} from '../actions/auth/login/login.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 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 the given provider',\n exclusive: ['sso'],\n helpValue: '<providerId>',\n }),\n sso: Flags.string({\n description: 'Log in using Single Sign-On, using the given organization slug',\n exclusive: ['provider'],\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\n try {\n await login({\n ...flags,\n output: this.output,\n ssoProvider: flags['sso-provider'],\n telemetry: this.telemetry,\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: 1})\n }\n }\n}\n"],"names":["Flags","SanityCommand","login","LoginCommand","description","examples","command","flags","experimental","boolean","default","hidden","open","allowNo","provider","string","exclusive","helpValue","sso","dependsOn","run","parse","output","ssoProvider","telemetry","log","error","message","Error","String","exit"],"mappings":"AAAA,SAAiBA,KAAK,QAAO,cAAa;AAE1C,SAAQC,aAAa,QAAO,mBAAkB;AAE9C,SAAQC,KAAK,QAAO,iCAAgC;AAEpD,OAAO,MAAMC,qBAAqBF;IAChC,OAAgBG,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;KACD,CAAA;IACD,OAAgBG,QAAQ;QACtBC,cAAcR,MAAMS,OAAO,CAAC;YAC1BC,SAAS;YACTC,QAAQ;QACV;QACAC,MAAMZ,MAAMS,OAAO,CAAC;YAClBI,SAAS;YACTH,SAAS;YACTN,aAAa;QACf;QACAU,UAAUd,MAAMe,MAAM,CAAC;YACrBX,aAAa;YACbY,WAAW;gBAAC;aAAM;YAClBC,WAAW;QACb;QACAC,KAAKlB,MAAMe,MAAM,CAAC;YAChBX,aAAa;YACbY,WAAW;gBAAC;aAAW;YACvBC,WAAW;QACb;QACA,gBAAgBjB,MAAMe,MAAM,CAAC;YAC3BI,WAAW;gBAAC;aAAM;YAClBf,aAAa;YACba,WAAW;QACb;IACF,EAAqB;IAErB,MAAaG,MAAqB;QAChC,MAAM,EAACb,KAAK,EAAC,GAAG,MAAM,IAAI,CAACc,KAAK,CAAClB;QAEjC,IAAI;YACF,MAAMD,MAAM;gBACV,GAAGK,KAAK;gBACRe,QAAQ,IAAI,CAACA,MAAM;gBACnBC,aAAahB,KAAK,CAAC,eAAe;gBAClCiB,WAAW,IAAI,CAACA,SAAS;YAC3B;YACA,IAAI,CAACC,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,MAAM;YAAC;QACjD;IACF;AACF"}
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 {SanityCommand} from '@sanity/cli-core'\n\nimport {login} from '../actions/auth/login/login.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 the given provider',\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: 1})\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","SanityCommand","login","LoginCommand","description","examples","command","flags","experimental","boolean","default","hidden","open","allowNo","provider","string","exclusive","helpValue","sso","dependsOn","run","parse","ssoProvider","withToken","loginFlags","token","readTokenFromStdin","undefined","output","telemetry","log","error","message","Error","String","exit","process","stdin","isTTY"],"mappings":"AAAA,SAAQA,IAAI,QAAO,wBAAuB;AAE1C,SAAiBC,KAAK,QAAO,cAAa;AAE1C,SAAQC,aAAa,QAAO,mBAAkB;AAE9C,SAAQC,KAAK,QAAO,iCAAgC;AAEpD,OAAO,MAAMC,qBAAqBF;IAChC,OAAgBG,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,cAAcR,MAAMS,OAAO,CAAC;YAC1BC,SAAS;YACTC,QAAQ;QACV;QACAC,MAAMZ,MAAMS,OAAO,CAAC;YAClBI,SAAS;YACTH,SAAS;YACTN,aAAa;QACf;QACAU,UAAUd,MAAMe,MAAM,CAAC;YACrBX,aAAa;YACbY,WAAW;gBAAC;gBAAO;aAAa;YAChCC,WAAW;QACb;QACAC,KAAKlB,MAAMe,MAAM,CAAC;YAChBX,aAAa;YACbY,WAAW;gBAAC;gBAAY;aAAa;YACrCC,WAAW;QACb;QACA,gBAAgBjB,MAAMe,MAAM,CAAC;YAC3BI,WAAW;gBAAC;aAAM;YAClBf,aAAa;YACba,WAAW;QACb;QACA,cAAcjB,MAAMS,OAAO,CAAC;YAC1BL,aAAa;YACbY,WAAW;gBAAC;gBAAY;aAAM;QAChC;IACF,EAAqB;IAErB,MAAaI,MAAqB;QAChC,MAAM,EAACb,KAAK,EAAC,GAAG,MAAM,IAAI,CAACc,KAAK,CAAClB;QACjC,MAAM,EAAC,gBAAgBmB,WAAW,EAAE,cAAcC,SAAS,EAAE,GAAGC,YAAW,GAAGjB;QAE9E,IAAI;YACF,MAAMkB,QAAQF,YAAY,MAAMG,uBAAuBC;YAEvD,MAAMzB,MAAM;gBACV,GAAGsB,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,MAAM;YAAC;QACjD;IACF;AACF;AAEA,eAAeT;IACb,IAAIU,QAAQC,KAAK,CAACC,KAAK,EAAE;QACvB,MAAM,IAAIL,MACR;IAEJ;IAEA,OAAOlC,KAAKqC,QAAQC,KAAK;AAC3B"}
@@ -808,6 +808,10 @@
808
808
  {
809
809
  "command": "<%= config.bin %> <%= command.id %> --sso my-organization --sso-provider \"Okta SSO\"",
810
810
  "description": "Log in using a specific SSO provider within an organization"
811
+ },
812
+ {
813
+ "command": "<%= config.bin %> <%= command.id %> --with-token < token.txt",
814
+ "description": "Log in using a token from standard input"
811
815
  }
812
816
  ],
813
817
  "flags": {
@@ -826,7 +830,8 @@
826
830
  "provider": {
827
831
  "description": "Log in using the given provider",
828
832
  "exclusive": [
829
- "sso"
833
+ "sso",
834
+ "with-token"
830
835
  ],
831
836
  "name": "provider",
832
837
  "hasDynamicHelp": false,
@@ -837,7 +842,8 @@
837
842
  "sso": {
838
843
  "description": "Log in using Single Sign-On, using the given organization slug",
839
844
  "exclusive": [
840
- "provider"
845
+ "provider",
846
+ "with-token"
841
847
  ],
842
848
  "name": "sso",
843
849
  "hasDynamicHelp": false,
@@ -855,6 +861,16 @@
855
861
  "helpValue": "<name>",
856
862
  "multiple": false,
857
863
  "type": "option"
864
+ },
865
+ "with-token": {
866
+ "description": "Read token from standard input",
867
+ "exclusive": [
868
+ "provider",
869
+ "sso"
870
+ ],
871
+ "name": "with-token",
872
+ "allowNo": false,
873
+ "type": "boolean"
858
874
  }
859
875
  },
860
876
  "hasDynamicHelp": false,
@@ -5082,5 +5098,5 @@
5082
5098
  ]
5083
5099
  }
5084
5100
  },
5085
- "version": "6.5.2"
5101
+ "version": "6.6.0"
5086
5102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/cli",
3
- "version": "6.5.2",
3
+ "version": "6.6.0",
4
4
  "description": "Sanity CLI tool for managing Sanity projects and organizations",
5
5
  "keywords": [
6
6
  "cli",
@@ -116,7 +116,7 @@
116
116
  "which": "^6.0.1",
117
117
  "yaml": "^2.8.4",
118
118
  "zod": "^4.3.6",
119
- "@sanity/cli-build": "^0.1.0",
119
+ "@sanity/cli-build": "^0.1.1",
120
120
  "@sanity/cli-core": "^1.3.2"
121
121
  },
122
122
  "devDependencies": {