@sanity/cli 8.23.0 → 8.25.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 +401 -87
- package/bin/run.js +7 -1
- package/dist/actions/codemods/dashboard.js +136 -8
- package/dist/actions/codemods/dashboard.js.map +1 -1
- package/dist/actions/codemods/types.js.map +1 -1
- package/dist/actions/dev/devAction.js +48 -44
- package/dist/actions/dev/devAction.js.map +1 -1
- package/dist/actions/init/initHelpers.js +1 -1
- package/dist/actions/init/initHelpers.js.map +1 -1
- package/dist/actions/init/initStudio.js +1 -1
- package/dist/actions/init/initStudio.js.map +1 -1
- package/dist/actions/init/project/getOrCreateDataset.js +10 -12
- package/dist/actions/init/project/getOrCreateDataset.js.map +1 -1
- package/dist/actions/init/project/getOrCreateProject.js +7 -5
- package/dist/actions/init/project/getOrCreateProject.js.map +1 -1
- package/dist/actions/init/project/promptUserForOrganization.js +6 -4
- package/dist/actions/init/project/promptUserForOrganization.js.map +1 -1
- package/dist/commands/api.js +1 -1
- package/dist/commands/api.js.map +1 -1
- package/dist/commands/assets/upload.js +1 -1
- package/dist/commands/assets/upload.js.map +1 -1
- package/dist/commands/codemod.js +1 -0
- package/dist/commands/codemod.js.map +1 -1
- package/dist/commands/cors/add.js +1 -1
- package/dist/commands/cors/add.js.map +1 -1
- package/dist/commands/cors/delete.js +1 -1
- package/dist/commands/cors/delete.js.map +1 -1
- package/dist/commands/cors/list.js +1 -1
- package/dist/commands/cors/list.js.map +1 -1
- package/dist/commands/datasets/alias/create.js +1 -1
- package/dist/commands/datasets/alias/create.js.map +1 -1
- package/dist/commands/datasets/import.js +0 -8
- package/dist/commands/datasets/import.js.map +1 -1
- package/dist/commands/datasets/list.js +1 -1
- package/dist/commands/datasets/list.js.map +1 -1
- package/dist/commands/documents/create.js +1 -1
- package/dist/commands/documents/create.js.map +1 -1
- package/dist/commands/documents/delete.js +1 -1
- package/dist/commands/documents/delete.js.map +1 -1
- package/dist/commands/documents/get.js +1 -1
- package/dist/commands/documents/get.js.map +1 -1
- package/dist/commands/documents/query.js +1 -9
- package/dist/commands/documents/query.js.map +1 -1
- package/dist/commands/documents/validate.js +2 -2
- package/dist/commands/documents/validate.js.map +1 -1
- package/dist/commands/graphql/list.js +1 -1
- package/dist/commands/graphql/list.js.map +1 -1
- package/dist/commands/graphql/undeploy.js +1 -11
- package/dist/commands/graphql/undeploy.js.map +1 -1
- package/dist/commands/hooks/attempt.js +1 -1
- package/dist/commands/hooks/attempt.js.map +1 -1
- package/dist/commands/hooks/create.js +1 -1
- package/dist/commands/hooks/create.js.map +1 -1
- package/dist/commands/hooks/delete.js +1 -1
- package/dist/commands/hooks/delete.js.map +1 -1
- package/dist/commands/hooks/list.js +1 -1
- package/dist/commands/hooks/list.js.map +1 -1
- package/dist/commands/hooks/logs.js +1 -1
- package/dist/commands/hooks/logs.js.map +1 -1
- package/dist/commands/init.js +3 -5
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/migrations/run.js +5 -5
- package/dist/commands/migrations/run.js.map +1 -1
- package/dist/commands/projects/unclaimed.js +6 -5
- package/dist/commands/projects/unclaimed.js.map +1 -1
- package/dist/commands/tokens/create.js +4 -4
- package/dist/commands/tokens/create.js.map +1 -1
- package/dist/commands/tokens/delete.js +2 -2
- package/dist/commands/tokens/delete.js.map +1 -1
- package/dist/commands/tokens/list.js +2 -2
- package/dist/commands/tokens/list.js.map +1 -1
- package/dist/commands/users/invite.js +1 -1
- package/dist/commands/users/invite.js.map +1 -1
- package/dist/commands/users/list.js +1 -1
- package/dist/commands/users/list.js.map +1 -1
- package/dist/exports/invokeSanityCli/index.js +1 -1
- package/dist/exports/invokeSanityCli/index.js.map +1 -1
- package/dist/hooks/prerun/injectEnvVariables.js +1 -1
- package/dist/hooks/prerun/injectEnvVariables.js.map +1 -1
- package/dist/util/searchChoices.js +52 -0
- package/dist/util/searchChoices.js.map +1 -0
- package/dist/util/sharedFlags.js +8 -4
- package/dist/util/sharedFlags.js.map +1 -1
- package/dist/util/warnOnNonProductionEnvironment.js +4 -3
- package/dist/util/warnOnNonProductionEnvironment.js.map +1 -1
- package/oclif.manifest.json +515 -351
- package/package.json +8 -8
- package/dist/util/getSanityEnv.js +0 -8
- package/dist/util/getSanityEnv.js.map +0 -1
- package/templates/shopify/.gitignore +0 -26
|
@@ -31,7 +31,7 @@ export class List extends SanityCommand {
|
|
|
31
31
|
description: 'List all users, sorted by role'
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
|
-
command: '<%= config.bin %> <%= command.id %> --project
|
|
34
|
+
command: '<%= config.bin %> <%= command.id %> --project abc123',
|
|
35
35
|
description: 'List users for a specific project'
|
|
36
36
|
}
|
|
37
37
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/users/list.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {SanityCommand} from '@sanity/cli-core'\n\nimport {getMembersForProject} from '../../actions/users/getMembersForProject.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {Table} from '../../util/responsiveTable.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\nimport {sortRowsByColumn} from '../../util/sortRowsByColumn.js'\n\nconst sortFields = ['id', 'name', 'role', 'date']\n\nfunction dimText(value: string, isDim: boolean): string {\n return isDim ? styleText('dim', value) : value\n}\n\nexport class List extends SanityCommand<typeof List> {\n static override description = 'List project members'\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'List all users of the project',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --no-invitations --no-robots',\n description: 'List all users of the project, but exclude pending invitations and robots',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --sort role',\n description: 'List all users, sorted by role',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/users/list.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {SanityCommand} from '@sanity/cli-core'\n\nimport {getMembersForProject} from '../../actions/users/getMembersForProject.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {Table} from '../../util/responsiveTable.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\nimport {sortRowsByColumn} from '../../util/sortRowsByColumn.js'\n\nconst sortFields = ['id', 'name', 'role', 'date']\n\nfunction dimText(value: string, isDim: boolean): string {\n return isDim ? styleText('dim', value) : value\n}\n\nexport class List extends SanityCommand<typeof List> {\n static override description = 'List project members'\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'List all users of the project',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --no-invitations --no-robots',\n description: 'List all users of the project, but exclude pending invitations and robots',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --sort role',\n description: 'List all users, sorted by role',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project abc123',\n description: 'List users for a specific project',\n },\n ]\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to list users for',\n semantics: 'override',\n }),\n invitations: Flags.boolean({\n allowNo: true,\n default: true,\n description: 'Includes or excludes pending invitations',\n }),\n order: Flags.string({\n default: 'asc',\n description: 'Sort output ascending/descending',\n options: ['asc', 'desc'],\n }),\n robots: Flags.boolean({\n allowNo: true,\n default: true,\n description: 'Includes or excludes robots (token users)',\n }),\n sort: Flags.string({\n default: 'date',\n description: 'Sort users by specified column',\n options: ['id', 'name', 'role', 'date'],\n }),\n }\n\n static override hiddenAliases: string[] = ['user:list']\n\n public async run(): Promise<void> {\n const {invitations, order, robots, sort} = this.flags\n\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({\n requiredPermissions: [\n {grant: 'read', permission: 'sanity.project'},\n {grant: 'read', permission: 'sanity.project.members'},\n ],\n }),\n })\n\n const members = await getMembersForProject({\n includeInvitations: invitations,\n includeRobots: robots,\n projectId,\n })\n\n if (members.length === 0) {\n this.log('No members found for this project.')\n return\n }\n\n const ordered = sortRowsByColumn(\n members.map(({date, id, name, roles}) => [\n id,\n name,\n roles\n ?.map((role) => role.title)\n .join(', ')\n .trim() || '-',\n date,\n ]),\n sortFields.indexOf(sort),\n )\n\n const rows = (order === 'asc' ? ordered : ordered.toReversed()).map(\n ([id, name, roles, date]) => [id, name, roles, date.split('T')[0]],\n )\n\n const table = new Table({\n columns: [\n {alignment: 'left', name: 'id', title: 'ID'},\n {alignment: 'left', name: 'name', title: 'Name'},\n {alignment: 'left', name: 'roles', title: 'Roles'},\n {alignment: 'left', name: 'date', title: 'Date'},\n ],\n })\n\n for (const [id, name, roles, date] of rows) {\n const isPending = id === '<pending>'\n table.addRow({\n date: dimText(date, isPending),\n id: dimText(id, isPending),\n name: dimText(name, isPending),\n roles: dimText(roles, isPending),\n })\n }\n\n this.log(table.render())\n }\n}\n"],"names":["styleText","Flags","SanityCommand","getMembersForProject","promptForProject","Table","getProjectIdFlag","sortRowsByColumn","sortFields","dimText","value","isDim","List","description","examples","command","flags","semantics","invitations","boolean","allowNo","default","order","string","options","robots","sort","hiddenAliases","run","projectId","getProjectId","fallback","requiredPermissions","grant","permission","members","includeInvitations","includeRobots","length","log","ordered","map","date","id","name","roles","role","title","join","trim","indexOf","rows","toReversed","split","table","columns","alignment","isPending","addRow","render"],"mappings":"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"}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* import {invokeSanityCli} from '@sanity/cli/invokeSanityCli'
|
|
13
13
|
*
|
|
14
14
|
* const {exitCode, output} = await invokeSanityCli({
|
|
15
|
-
* args: 'cors list --project
|
|
15
|
+
* args: 'cors list --project abc123',
|
|
16
16
|
* source: 'mcp',
|
|
17
17
|
* token: extra.authInfo.token,
|
|
18
18
|
* })
|
|
@@ -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. Commands contributed by\n * plugins are covered by the policies those plugins declare, and are denied\n * unless they declare one.\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 {fileURLToPath} from 'node:url'\n\nimport {Command, Config, Flags, Parser, settings} from '@oclif/core'\nimport {getHelpFlagAdditions, normalizeArgv} from '@oclif/core/help'\nimport {exitCodes} from '@sanity/cli-core'\nimport {\n type CommandPolicySet,\n deny,\n type InvocationSource,\n isConditionalInvocationPolicy,\n} from '@sanity/cli-core/commandPolicy'\nimport {runWithCliExecutionContext, type SanityEnvironment} from '@sanity/cli-core/executionContext'\nimport {type SanityCommand} from '@sanity/cli-core/SanityCommand'\nimport {type FetchFunction} from 'get-it'\nimport {parseArgsStringToArgv} from 'string-argv'\n\nimport {resolveTopicAliasInArgv} from '../../topicAliases.js'\nimport {resolveCommandPolicies} from './commandPolicies/index.js'\nimport {isHelpRequest, renderInvokableHelp} from './help.js'\nimport {prettyPrintError} from './prettyPrintError.js'\n\ntype InvokableCommand = Pick<SanityCommand<typeof Command>, 'runInExecutionContext'>\n\n/**\n * Whether a command can run in isolation, which in practice means it extends\n * `SanityCommand`: only that base class routes output, token resolution,\n * interactivity, and project discovery through the CLI execution context.\n *\n * This is a capability check, not a policy check. A plugin can declare a\n * policy for a command that still extends oclif's `Command` directly, and\n * such a command must not run here — the policy says the invocation is safe,\n * but nothing would hold it to the isolation guarantees that assessment\n * assumes.\n */\nfunction supportsIsolatedExecution(CommandClass: Command.Class): boolean {\n const prototype = CommandClass.prototype as Partial<InvokableCommand> | undefined\n return typeof prototype?.runInExecutionContext === 'function'\n}\n\n/**\n * Instantiate a policy-approved command for isolated execution.\n *\n * `runInExecutionContext()` runs the instance without oclif's static\n * `Command.run()` — the static runner re-enters `Config.load()` (filesystem\n * reads and a process-global config cache write) on every call. The manifest\n * types command classes as the abstract oclif `Command`, hence the\n * constructor cast.\n */\nfunction instantiateCommand(\n CommandClass: Command.Class,\n argv: string[],\n config: Config,\n): InvokableCommand {\n const ConcreteCommand = CommandClass as unknown as new (argv: string[], config: Config) => Command\n return new ConcreteCommand(argv, config) as unknown as InvokableCommand\n}\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. It only reads this package's own\n * installed files — process-lifetime initialization, not per-invocation host\n * state — so it happens outside any execution context.\n *\n * `userPlugins: false` keeps this surface to the plugins this package ships\n * with. oclif otherwise loads whatever `<dataDir>/package.json` lists.\n */\nfunction loadCliCommandConfig(): Promise<Config> {\n return Config.load({root: fileURLToPath(import.meta.url), userPlugins: false})\n}\n\nlet cachedConfig: Promise<Config> | undefined\n\nfunction withoutJsonFlag(argv: string[]): string[] {\n // Anything after `--` belongs to the command, not to us.\n const passThrough = argv.indexOf('--')\n const limit = passThrough === -1 ? argv.length : passThrough\n return argv.filter((token, index) => index >= limit || token !== '--json')\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 * Optional fetch implementation for API requests made by this invocation.\n * Scoped to this call via the CLI execution context; defaults to the CLI's\n * own transport. The execution context's transport hygiene (such as\n * stripping the embedding process's lineage header) is applied on top.\n */\n fetch?: FetchFunction\n\n /**\n * Sanity deployment environment for this invocation. Scoped to this call\n * via the CLI execution context and defaults to production. The embedding\n * process's `SANITY_INTERNAL_ENV` is never consulted.\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\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 options: InvokeSanityCliOptions,\n): Promise<InvokeSanityCliResult> {\n // Always load compiled command modules. Oclif's dev-mode source resolution\n // otherwise probes tsconfigs (reading the filesystem and process.cwd) on\n // every command load, and may register ts-node process-wide.\n settings.enableAutoTranspile = false\n\n const resolvedConfig = options.config ?? (await (cachedConfig ??= loadCliCommandConfig()))\n const output: string[] = []\n const sink = (line: string) => output.push(line)\n const {fetch, sanityEnv, token} = options\n\n // Establish the isolation boundary before rendering help, loading command\n // modules, parsing, or executing. Any code reached by an external\n // invocation can therefore fail closed on context.\n return runWithCliExecutionContext({fetch, sanityEnv, stderr: sink, stdout: sink, token}, () =>\n invokeSanityCliInContext(options, resolvedConfig, output),\n )\n}\n\nasync function invokeSanityCliInContext(\n {args, source}: InvokeSanityCliOptions,\n resolvedConfig: Config,\n output: string[],\n): Promise<InvokeSanityCliResult> {\n // Combines this package's own policies with those each plugin declares for\n // the commands it contributes. Resolved once per config, not per call.\n const policySet = await resolveCommandPolicies(resolvedConfig, source)\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 argv = resolveTopicAliasInArgv(argv)\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 = '', ...requestedArgv] = normalizeArgv(resolvedConfig, argv)\n const commandArgv = withoutJsonFlag(requestedArgv)\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 // A policy can only speak to whether an invocation is safe; it cannot make\n // a command isolatable. One that isn't is treated as if it did not exist,\n // like every other fail-closed path here.\n if (!supportsIsolatedExecution(CommandClass)) return unknownCommandResult(argv, policySet)\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 flags: {\n ...(CommandClass.enableJsonFlag ? {json: Flags.boolean()} : {}),\n ...CommandClass.baseFlags,\n ...CommandClass.flags,\n },\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 let command: InvokableCommand\n try {\n command = instantiateCommand(CommandClass, commandArgv, resolvedConfig)\n } catch (err) {\n return {\n commandId,\n exitCode: err.oclif?.exit ?? exitCodes.RUNTIME_ERROR,\n output: prettyPrintError(err) || String(err),\n }\n }\n\n try {\n await command.runInExecutionContext()\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\n return {\n commandId,\n exitCode: typeof exit === 'number' ? exit : exitCodes.RUNTIME_ERROR,\n output: output.join('\\n'),\n }\n }\n}\n"],"names":["fileURLToPath","Config","Flags","Parser","settings","getHelpFlagAdditions","normalizeArgv","exitCodes","deny","isConditionalInvocationPolicy","runWithCliExecutionContext","parseArgsStringToArgv","resolveTopicAliasInArgv","resolveCommandPolicies","isHelpRequest","renderInvokableHelp","prettyPrintError","supportsIsolatedExecution","CommandClass","prototype","runInExecutionContext","instantiateCommand","argv","config","ConcreteCommand","loadCliCommandConfig","load","root","url","userPlugins","cachedConfig","withoutJsonFlag","passThrough","indexOf","limit","length","filter","token","index","unknownCommandResult","policySet","available","Object","entries","policy","kind","map","id","replaceAll","toSorted","exitCode","USAGE_ERROR","output","slice","join","stripFlagQuotes","rawToken","match","exec","invokeSanityCli","options","enableAutoTranspile","resolvedConfig","sink","line","push","fetch","sanityEnv","stderr","stdout","invokeSanityCliInContext","args","source","t","helpArgv","result","SUCCESS","helpFlags","includes","err","RUNTIME_ERROR","Error","message","String","commandId","requestedArgv","commandArgv","commandDefinition","undefined","findCommand","invocation","parsed","parse","flags","enableJsonFlag","json","boolean","baseFlags","strict","validate","displayId","usedDeniedFlags","deniedFlags","name","command","oclif","exit"],"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. Commands contributed by\n * plugins are covered by the policies those plugins declare, and are denied\n * unless they declare one.\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 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 {fileURLToPath} from 'node:url'\n\nimport {Command, Config, Flags, Parser, settings} from '@oclif/core'\nimport {getHelpFlagAdditions, normalizeArgv} from '@oclif/core/help'\nimport {exitCodes} from '@sanity/cli-core'\nimport {\n type CommandPolicySet,\n deny,\n type InvocationSource,\n isConditionalInvocationPolicy,\n} from '@sanity/cli-core/commandPolicy'\nimport {runWithCliExecutionContext, type SanityEnvironment} from '@sanity/cli-core/executionContext'\nimport {type SanityCommand} from '@sanity/cli-core/SanityCommand'\nimport {type FetchFunction} from 'get-it'\nimport {parseArgsStringToArgv} from 'string-argv'\n\nimport {resolveTopicAliasInArgv} from '../../topicAliases.js'\nimport {resolveCommandPolicies} from './commandPolicies/index.js'\nimport {isHelpRequest, renderInvokableHelp} from './help.js'\nimport {prettyPrintError} from './prettyPrintError.js'\n\ntype InvokableCommand = Pick<SanityCommand<typeof Command>, 'runInExecutionContext'>\n\n/**\n * Whether a command can run in isolation, which in practice means it extends\n * `SanityCommand`: only that base class routes output, token resolution,\n * interactivity, and project discovery through the CLI execution context.\n *\n * This is a capability check, not a policy check. A plugin can declare a\n * policy for a command that still extends oclif's `Command` directly, and\n * such a command must not run here — the policy says the invocation is safe,\n * but nothing would hold it to the isolation guarantees that assessment\n * assumes.\n */\nfunction supportsIsolatedExecution(CommandClass: Command.Class): boolean {\n const prototype = CommandClass.prototype as Partial<InvokableCommand> | undefined\n return typeof prototype?.runInExecutionContext === 'function'\n}\n\n/**\n * Instantiate a policy-approved command for isolated execution.\n *\n * `runInExecutionContext()` runs the instance without oclif's static\n * `Command.run()` — the static runner re-enters `Config.load()` (filesystem\n * reads and a process-global config cache write) on every call. The manifest\n * types command classes as the abstract oclif `Command`, hence the\n * constructor cast.\n */\nfunction instantiateCommand(\n CommandClass: Command.Class,\n argv: string[],\n config: Config,\n): InvokableCommand {\n const ConcreteCommand = CommandClass as unknown as new (argv: string[], config: Config) => Command\n return new ConcreteCommand(argv, config) as unknown as InvokableCommand\n}\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. It only reads this package's own\n * installed files — process-lifetime initialization, not per-invocation host\n * state — so it happens outside any execution context.\n *\n * `userPlugins: false` keeps this surface to the plugins this package ships\n * with. oclif otherwise loads whatever `<dataDir>/package.json` lists.\n */\nfunction loadCliCommandConfig(): Promise<Config> {\n return Config.load({root: fileURLToPath(import.meta.url), userPlugins: false})\n}\n\nlet cachedConfig: Promise<Config> | undefined\n\nfunction withoutJsonFlag(argv: string[]): string[] {\n // Anything after `--` belongs to the command, not to us.\n const passThrough = argv.indexOf('--')\n const limit = passThrough === -1 ? argv.length : passThrough\n return argv.filter((token, index) => index >= limit || token !== '--json')\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 * Optional fetch implementation for API requests made by this invocation.\n * Scoped to this call via the CLI execution context; defaults to the CLI's\n * own transport. The execution context's transport hygiene (such as\n * stripping the embedding process's lineage header) is applied on top.\n */\n fetch?: FetchFunction\n\n /**\n * Sanity deployment environment for this invocation. Scoped to this call\n * via the CLI execution context and defaults to production. The embedding\n * process's `SANITY_INTERNAL_ENV` is never consulted.\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\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 options: InvokeSanityCliOptions,\n): Promise<InvokeSanityCliResult> {\n // Always load compiled command modules. Oclif's dev-mode source resolution\n // otherwise probes tsconfigs (reading the filesystem and process.cwd) on\n // every command load, and may register ts-node process-wide.\n settings.enableAutoTranspile = false\n\n const resolvedConfig = options.config ?? (await (cachedConfig ??= loadCliCommandConfig()))\n const output: string[] = []\n const sink = (line: string) => output.push(line)\n const {fetch, sanityEnv, token} = options\n\n // Establish the isolation boundary before rendering help, loading command\n // modules, parsing, or executing. Any code reached by an external\n // invocation can therefore fail closed on context.\n return runWithCliExecutionContext({fetch, sanityEnv, stderr: sink, stdout: sink, token}, () =>\n invokeSanityCliInContext(options, resolvedConfig, output),\n )\n}\n\nasync function invokeSanityCliInContext(\n {args, source}: InvokeSanityCliOptions,\n resolvedConfig: Config,\n output: string[],\n): Promise<InvokeSanityCliResult> {\n // Combines this package's own policies with those each plugin declares for\n // the commands it contributes. Resolved once per config, not per call.\n const policySet = await resolveCommandPolicies(resolvedConfig, source)\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 argv = resolveTopicAliasInArgv(argv)\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 = '', ...requestedArgv] = normalizeArgv(resolvedConfig, argv)\n const commandArgv = withoutJsonFlag(requestedArgv)\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 // A policy can only speak to whether an invocation is safe; it cannot make\n // a command isolatable. One that isn't is treated as if it did not exist,\n // like every other fail-closed path here.\n if (!supportsIsolatedExecution(CommandClass)) return unknownCommandResult(argv, policySet)\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 flags: {\n ...(CommandClass.enableJsonFlag ? {json: Flags.boolean()} : {}),\n ...CommandClass.baseFlags,\n ...CommandClass.flags,\n },\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 let command: InvokableCommand\n try {\n command = instantiateCommand(CommandClass, commandArgv, resolvedConfig)\n } catch (err) {\n return {\n commandId,\n exitCode: err.oclif?.exit ?? exitCodes.RUNTIME_ERROR,\n output: prettyPrintError(err) || String(err),\n }\n }\n\n try {\n await command.runInExecutionContext()\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\n return {\n commandId,\n exitCode: typeof exit === 'number' ? exit : exitCodes.RUNTIME_ERROR,\n output: output.join('\\n'),\n }\n }\n}\n"],"names":["fileURLToPath","Config","Flags","Parser","settings","getHelpFlagAdditions","normalizeArgv","exitCodes","deny","isConditionalInvocationPolicy","runWithCliExecutionContext","parseArgsStringToArgv","resolveTopicAliasInArgv","resolveCommandPolicies","isHelpRequest","renderInvokableHelp","prettyPrintError","supportsIsolatedExecution","CommandClass","prototype","runInExecutionContext","instantiateCommand","argv","config","ConcreteCommand","loadCliCommandConfig","load","root","url","userPlugins","cachedConfig","withoutJsonFlag","passThrough","indexOf","limit","length","filter","token","index","unknownCommandResult","policySet","available","Object","entries","policy","kind","map","id","replaceAll","toSorted","exitCode","USAGE_ERROR","output","slice","join","stripFlagQuotes","rawToken","match","exec","invokeSanityCli","options","enableAutoTranspile","resolvedConfig","sink","line","push","fetch","sanityEnv","stderr","stdout","invokeSanityCliInContext","args","source","t","helpArgv","result","SUCCESS","helpFlags","includes","err","RUNTIME_ERROR","Error","message","String","commandId","requestedArgv","commandArgv","commandDefinition","undefined","findCommand","invocation","parsed","parse","flags","enableJsonFlag","json","boolean","baseFlags","strict","validate","displayId","usedDeniedFlags","deniedFlags","name","command","oclif","exit"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
2
|
import { warn } from '@oclif/core/ux';
|
|
3
3
|
import { debug, findProjectRoot } from '@sanity/cli-core';
|
|
4
|
+
import { getSanityEnv } from '@sanity/cli-core/util';
|
|
4
5
|
import { loadEnv } from 'vite';
|
|
5
|
-
import { getSanityEnv } from '../../util/getSanityEnv.js';
|
|
6
6
|
export const injectEnvVariables = async function({ Command }) {
|
|
7
7
|
let workDir;
|
|
8
8
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/hooks/prerun/injectEnvVariables.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type Hook} from '@oclif/core'\nimport {warn} from '@oclif/core/ux'\nimport {debug, findProjectRoot, type ProjectRootResult} from '@sanity/cli-core'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/prerun/injectEnvVariables.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type Hook} from '@oclif/core'\nimport {warn} from '@oclif/core/ux'\nimport {debug, findProjectRoot, type ProjectRootResult} from '@sanity/cli-core'\nimport {getSanityEnv} from '@sanity/cli-core/util'\nimport {loadEnv} from 'vite'\n\nexport const injectEnvVariables: Hook.Prerun = async function ({Command}) {\n let workDir: ProjectRootResult | undefined\n try {\n workDir = await findProjectRoot(process.cwd())\n } catch {\n // Accept not finding a project root\n }\n\n if (!workDir) {\n return\n }\n\n // Use `production` for `sanity build` / `sanity deploy`,\n // but default to `development` for everything else unless `SANITY_ACTIVE_ENV` is set\n const isProdCmd = ['build', 'deploy'].includes(Command.id)\n let mode = process.env.SANITY_ACTIVE_ENV\n if (!mode && (isProdCmd || process.env.NODE_ENV === 'production')) {\n mode = 'production'\n } else if (!mode) {\n mode = 'development'\n }\n\n if (mode === 'production' && !isProdCmd) {\n warn(styleText('yellow', `Running in ${getSanityEnv()} environment mode\\n`))\n }\n\n debug('Loading environment files using %s mode', mode)\n\n // Empty prefix loads all variables from .env files, not just SANITY_STUDIO_/SANITY_APP_ prefixed ones.\n // Client bundle exposure is separately controlled by Vite's envPrefix in getViteConfig.ts.\n const studioEnv = loadEnv(mode, workDir.directory, '')\n Object.assign(process.env, studioEnv)\n}\n"],"names":["styleText","warn","debug","findProjectRoot","getSanityEnv","loadEnv","injectEnvVariables","Command","workDir","process","cwd","isProdCmd","includes","id","mode","env","SANITY_ACTIVE_ENV","NODE_ENV","studioEnv","directory","Object","assign"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAGnC,SAAQC,IAAI,QAAO,iBAAgB;AACnC,SAAQC,KAAK,EAAEC,eAAe,QAA+B,mBAAkB;AAC/E,SAAQC,YAAY,QAAO,wBAAuB;AAClD,SAAQC,OAAO,QAAO,OAAM;AAE5B,OAAO,MAAMC,qBAAkC,eAAgB,EAACC,OAAO,EAAC;IACtE,IAAIC;IACJ,IAAI;QACFA,UAAU,MAAML,gBAAgBM,QAAQC,GAAG;IAC7C,EAAE,OAAM;IACN,oCAAoC;IACtC;IAEA,IAAI,CAACF,SAAS;QACZ;IACF;IAEA,yDAAyD;IACzD,qFAAqF;IACrF,MAAMG,YAAY;QAAC;QAAS;KAAS,CAACC,QAAQ,CAACL,QAAQM,EAAE;IACzD,IAAIC,OAAOL,QAAQM,GAAG,CAACC,iBAAiB;IACxC,IAAI,CAACF,QAASH,CAAAA,aAAaF,QAAQM,GAAG,CAACE,QAAQ,KAAK,YAAW,GAAI;QACjEH,OAAO;IACT,OAAO,IAAI,CAACA,MAAM;QAChBA,OAAO;IACT;IAEA,IAAIA,SAAS,gBAAgB,CAACH,WAAW;QACvCV,KAAKD,UAAU,UAAU,CAAC,WAAW,EAAEI,eAAe,mBAAmB,CAAC;IAC5E;IAEAF,MAAM,2CAA2CY;IAEjD,uGAAuG;IACvG,2FAA2F;IAC3F,MAAMI,YAAYb,QAAQS,MAAMN,QAAQW,SAAS,EAAE;IACnDC,OAAOC,MAAM,CAACZ,QAAQM,GAAG,EAAEG;AAC7B,EAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
2
|
+
import { Separator } from '@sanity/cli-core/ux';
|
|
3
|
+
/** `search` prompt theme that adds a "type to search" hint to the key help line. */ export const searchTheme = {
|
|
4
|
+
style: {
|
|
5
|
+
keysHelpTip: (keys)=>[
|
|
6
|
+
[
|
|
7
|
+
'type',
|
|
8
|
+
'to search'
|
|
9
|
+
],
|
|
10
|
+
...keys
|
|
11
|
+
].map(([key, action])=>`${styleText('bold', key)} ${styleText('dim', action)}`).join(styleText('dim', ' • '))
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const isChoice = (choice)=>!Separator.isSeparator(choice);
|
|
15
|
+
/**
|
|
16
|
+
* Builds a `search` prompt source that keeps choices whose `name` contains every search word.
|
|
17
|
+
* Actions above the first separator (e.g. "Create new ...") stay visible, below the matches while
|
|
18
|
+
* searching so that Enter picks the top match. Without a selectable match the actions are
|
|
19
|
+
* disabled, so Enter does nothing.
|
|
20
|
+
*/ export function searchChoices(choices) {
|
|
21
|
+
const separatorIndex = choices.findIndex((choice)=>Separator.isSeparator(choice));
|
|
22
|
+
const actions = choices.slice(0, Math.max(separatorIndex, 0)).filter((choice)=>isChoice(choice));
|
|
23
|
+
const searchable = choices.slice(separatorIndex + 1).filter((choice)=>isChoice(choice));
|
|
24
|
+
return (term)=>{
|
|
25
|
+
const trimmed = term?.trim() ?? '';
|
|
26
|
+
if (!trimmed) {
|
|
27
|
+
return choices;
|
|
28
|
+
}
|
|
29
|
+
const words = trimmed.toLowerCase().split(/\s+/);
|
|
30
|
+
const matches = searchable.filter((choice)=>words.every((word)=>choice.name.toLowerCase().includes(word)));
|
|
31
|
+
if (actions.length === 0) {
|
|
32
|
+
return matches;
|
|
33
|
+
}
|
|
34
|
+
if (matches.some((choice)=>!choice.disabled)) {
|
|
35
|
+
return [
|
|
36
|
+
...matches,
|
|
37
|
+
new Separator(),
|
|
38
|
+
...actions
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
return [
|
|
42
|
+
...matches,
|
|
43
|
+
new Separator(matches.length > 0 ? undefined : `No matches for "${trimmed}"`),
|
|
44
|
+
...actions.map((action)=>({
|
|
45
|
+
...action,
|
|
46
|
+
disabled: '(clear search to select)'
|
|
47
|
+
}))
|
|
48
|
+
];
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=searchChoices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/util/searchChoices.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Separator} from '@sanity/cli-core/ux'\n\ntype Choice = {disabled?: boolean | string; name: string; value: string}\n\n/** `search` prompt theme that adds a \"type to search\" hint to the key help line. */\nexport const searchTheme = {\n style: {\n keysHelpTip: (keys: [key: string, action: string][]) =>\n [['type', 'to search'], ...keys]\n .map(([key, action]) => `${styleText('bold', key)} ${styleText('dim', action)}`)\n .join(styleText('dim', ' • ')),\n },\n}\n\nconst isChoice = (choice: Choice | Separator): choice is Choice => !Separator.isSeparator(choice)\n\n/**\n * Builds a `search` prompt source that keeps choices whose `name` contains every search word.\n * Actions above the first separator (e.g. \"Create new ...\") stay visible, below the matches while\n * searching so that Enter picks the top match. Without a selectable match the actions are\n * disabled, so Enter does nothing.\n */\nexport function searchChoices(choices: Array<Choice | Separator>) {\n const separatorIndex = choices.findIndex((choice) => Separator.isSeparator(choice))\n const actions = choices.slice(0, Math.max(separatorIndex, 0)).filter((choice) => isChoice(choice))\n const searchable = choices.slice(separatorIndex + 1).filter((choice) => isChoice(choice))\n\n return (term: string | undefined) => {\n const trimmed = term?.trim() ?? ''\n\n if (!trimmed) {\n return choices\n }\n\n const words = trimmed.toLowerCase().split(/\\s+/)\n const matches = searchable.filter((choice) =>\n words.every((word) => choice.name.toLowerCase().includes(word)),\n )\n\n if (actions.length === 0) {\n return matches\n }\n\n if (matches.some((choice) => !choice.disabled)) {\n return [...matches, new Separator(), ...actions]\n }\n\n return [\n ...matches,\n new Separator(matches.length > 0 ? undefined : `No matches for \"${trimmed}\"`),\n ...actions.map((action) => ({...action, disabled: '(clear search to select)'})),\n ]\n }\n}\n"],"names":["styleText","Separator","searchTheme","style","keysHelpTip","keys","map","key","action","join","isChoice","choice","isSeparator","searchChoices","choices","separatorIndex","findIndex","actions","slice","Math","max","filter","searchable","term","trimmed","trim","words","toLowerCase","split","matches","every","word","name","includes","length","some","disabled","undefined"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,SAAS,QAAO,sBAAqB;AAI7C,kFAAkF,GAClF,OAAO,MAAMC,cAAc;IACzBC,OAAO;QACLC,aAAa,CAACC,OACZ;gBAAC;oBAAC;oBAAQ;iBAAY;mBAAKA;aAAK,CAC7BC,GAAG,CAAC,CAAC,CAACC,KAAKC,OAAO,GAAK,GAAGR,UAAU,QAAQO,KAAK,CAAC,EAAEP,UAAU,OAAOQ,SAAS,EAC9EC,IAAI,CAACT,UAAU,OAAO;IAC7B;AACF,EAAC;AAED,MAAMU,WAAW,CAACC,SAAiD,CAACV,UAAUW,WAAW,CAACD;AAE1F;;;;;CAKC,GACD,OAAO,SAASE,cAAcC,OAAkC;IAC9D,MAAMC,iBAAiBD,QAAQE,SAAS,CAAC,CAACL,SAAWV,UAAUW,WAAW,CAACD;IAC3E,MAAMM,UAAUH,QAAQI,KAAK,CAAC,GAAGC,KAAKC,GAAG,CAACL,gBAAgB,IAAIM,MAAM,CAAC,CAACV,SAAWD,SAASC;IAC1F,MAAMW,aAAaR,QAAQI,KAAK,CAACH,iBAAiB,GAAGM,MAAM,CAAC,CAACV,SAAWD,SAASC;IAEjF,OAAO,CAACY;QACN,MAAMC,UAAUD,MAAME,UAAU;QAEhC,IAAI,CAACD,SAAS;YACZ,OAAOV;QACT;QAEA,MAAMY,QAAQF,QAAQG,WAAW,GAAGC,KAAK,CAAC;QAC1C,MAAMC,UAAUP,WAAWD,MAAM,CAAC,CAACV,SACjCe,MAAMI,KAAK,CAAC,CAACC,OAASpB,OAAOqB,IAAI,CAACL,WAAW,GAAGM,QAAQ,CAACF;QAG3D,IAAId,QAAQiB,MAAM,KAAK,GAAG;YACxB,OAAOL;QACT;QAEA,IAAIA,QAAQM,IAAI,CAAC,CAACxB,SAAW,CAACA,OAAOyB,QAAQ,GAAG;YAC9C,OAAO;mBAAIP;gBAAS,IAAI5B;mBAAgBgB;aAAQ;QAClD;QAEA,OAAO;eACFY;YACH,IAAI5B,UAAU4B,QAAQK,MAAM,GAAG,IAAIG,YAAY,CAAC,gBAAgB,EAAEb,QAAQ,CAAC,CAAC;eACzEP,QAAQX,GAAG,CAAC,CAACE,SAAY,CAAA;oBAAC,GAAGA,MAAM;oBAAE4B,UAAU;gBAA0B,CAAA;SAC7E;IACH;AACF"}
|
package/dist/util/sharedFlags.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
const OVERRIDE_SUFFIX = ' (overrides CLI configuration)';
|
|
3
3
|
/**
|
|
4
|
-
* Returns a `--project
|
|
4
|
+
* Returns a `--project` / `-p` flag definition with a deprecated `--project-id` alias.
|
|
5
5
|
*
|
|
6
|
-
* Locked: flag name (`project
|
|
6
|
+
* Locked: flag name (`project`), char (`p`), `helpValue` (`<id>`), and parse (trims + validates non-empty).
|
|
7
7
|
*/ export function getProjectIdFlag(options) {
|
|
8
8
|
const { description: baseDescription, helpGroup, semantics, ...rest } = options;
|
|
9
9
|
const isOverride = semantics === 'override';
|
|
10
10
|
const description = (baseDescription ?? 'Project ID to use') + (isOverride ? OVERRIDE_SUFFIX : '');
|
|
11
11
|
return {
|
|
12
|
-
|
|
12
|
+
project: Flags.string({
|
|
13
|
+
aliases: [
|
|
14
|
+
'project-id'
|
|
15
|
+
],
|
|
16
|
+
deprecateAliases: true,
|
|
13
17
|
description,
|
|
14
18
|
helpGroup: helpGroup ?? (isOverride ? 'OVERRIDE' : undefined),
|
|
15
19
|
helpValue: '<id>',
|
|
@@ -18,7 +22,7 @@ const OVERRIDE_SUFFIX = ' (overrides CLI configuration)';
|
|
|
18
22
|
parse: async (input)=>{
|
|
19
23
|
const trimmed = input.trim();
|
|
20
24
|
if (trimmed === '') {
|
|
21
|
-
throw new Error('`--project
|
|
25
|
+
throw new Error('`--project` cannot be empty if provided');
|
|
22
26
|
}
|
|
23
27
|
return trimmed;
|
|
24
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/util/sharedFlags.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\n\n/**\n * Controls how the flag relates to CLI configuration:\n *\n * - `'override'` — The command falls back to CLI config (sanity.cli.ts) when the flag is not\n * provided. The flag description automatically gets an \" (overrides CLI configuration)\" suffix,\n * and `helpGroup` defaults to `'OVERRIDE'`.\n *\n * - `'specify'` — The command does NOT fall back to CLI config; the flag is simply how the user\n * provides the value. No suffix is appended, and no default `helpGroup` is set.\n */\ntype FlagSemantics = 'override' | 'specify'\n\n/**\n * Options accepted by the shared flag getters.\n * Locked properties (char, parse, name, helpValue) are excluded to ensure\n * consistent behavior across all commands.\n */\ninterface SharedFlagOptions {\n /**\n * Controls description suffix and default helpGroup.\n * @see {@link FlagSemantics}\n */\n semantics: FlagSemantics\n\n dependsOn?: string[]\n description?: string\n env?: string\n exclusive?: string[]\n helpGroup?: string\n hidden?: boolean\n required?: boolean\n}\n\nconst OVERRIDE_SUFFIX = ' (overrides CLI configuration)'\n\n/**\n * Returns a `--project
|
|
1
|
+
{"version":3,"sources":["../../src/util/sharedFlags.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\n\n/**\n * Controls how the flag relates to CLI configuration:\n *\n * - `'override'` — The command falls back to CLI config (sanity.cli.ts) when the flag is not\n * provided. The flag description automatically gets an \" (overrides CLI configuration)\" suffix,\n * and `helpGroup` defaults to `'OVERRIDE'`.\n *\n * - `'specify'` — The command does NOT fall back to CLI config; the flag is simply how the user\n * provides the value. No suffix is appended, and no default `helpGroup` is set.\n */\ntype FlagSemantics = 'override' | 'specify'\n\n/**\n * Options accepted by the shared flag getters.\n * Locked properties (char, parse, name, helpValue) are excluded to ensure\n * consistent behavior across all commands.\n */\ninterface SharedFlagOptions {\n /**\n * Controls description suffix and default helpGroup.\n * @see {@link FlagSemantics}\n */\n semantics: FlagSemantics\n\n dependsOn?: string[]\n description?: string\n env?: string\n exclusive?: string[]\n helpGroup?: string\n hidden?: boolean\n required?: boolean\n}\n\nconst OVERRIDE_SUFFIX = ' (overrides CLI configuration)'\n\n/**\n * Returns a `--project` / `-p` flag definition with a deprecated `--project-id` alias.\n *\n * Locked: flag name (`project`), char (`p`), `helpValue` (`<id>`), and parse (trims + validates non-empty).\n */\nexport function getProjectIdFlag(options: SharedFlagOptions) {\n const {description: baseDescription, helpGroup, semantics, ...rest} = options\n const isOverride = semantics === 'override'\n const description = (baseDescription ?? 'Project ID to use') + (isOverride ? OVERRIDE_SUFFIX : '')\n\n return {\n project: Flags.string({\n aliases: ['project-id'],\n deprecateAliases: true,\n description,\n helpGroup: helpGroup ?? (isOverride ? 'OVERRIDE' : undefined),\n helpValue: '<id>',\n ...rest,\n char: 'p',\n parse: async (input: string) => {\n const trimmed = input.trim()\n if (trimmed === '') {\n throw new Error('`--project` cannot be empty if provided')\n }\n return trimmed\n },\n }),\n }\n}\n\n/**\n * Returns an `--organization` flag definition.\n *\n * Locked: flag name (`organization`), `helpValue` (`<id>`), and parse (trims + validates non-empty).\n */\nexport function getOrganizationFlag(options: SharedFlagOptions) {\n const {description: baseDescription, helpGroup, semantics, ...rest} = options\n const isOverride = semantics === 'override'\n const description =\n (baseDescription ?? 'Organization ID to use') + (isOverride ? OVERRIDE_SUFFIX : '')\n\n return {\n organization: Flags.string({\n description,\n helpGroup: helpGroup ?? (isOverride ? 'OVERRIDE' : undefined),\n helpValue: '<id>',\n ...rest,\n parse: async (input: string) => {\n const trimmed = input.trim()\n if (trimmed === '') {\n throw new Error('`--organization` cannot be empty if provided')\n }\n return trimmed\n },\n }),\n }\n}\n\n/**\n * Returns a `--dataset` / `-d` flag definition.\n *\n * Locked: flag name (`dataset`), char (`d`), `helpValue` (`<name>`), and parse (trims + validates non-empty).\n */\nexport function getDatasetFlag(options: SharedFlagOptions) {\n const {description: baseDescription, helpGroup, semantics, ...rest} = options\n const isOverride = semantics === 'override'\n const description = (baseDescription ?? 'Dataset to use') + (isOverride ? OVERRIDE_SUFFIX : '')\n\n return {\n dataset: Flags.string({\n description,\n helpGroup: helpGroup ?? (isOverride ? 'OVERRIDE' : undefined),\n helpValue: '<name>',\n ...rest,\n char: 'd',\n parse: async (input: string) => {\n const trimmed = input.trim()\n if (trimmed === '') {\n throw new Error('`--dataset` cannot be empty if provided')\n }\n return trimmed\n },\n }),\n }\n}\n"],"names":["Flags","OVERRIDE_SUFFIX","getProjectIdFlag","options","description","baseDescription","helpGroup","semantics","rest","isOverride","project","string","aliases","deprecateAliases","undefined","helpValue","char","parse","input","trimmed","trim","Error","getOrganizationFlag","organization","getDatasetFlag","dataset"],"mappings":"AAAA,SAAQA,KAAK,QAAO,cAAa;AAmCjC,MAAMC,kBAAkB;AAExB;;;;CAIC,GACD,OAAO,SAASC,iBAAiBC,OAA0B;IACzD,MAAM,EAACC,aAAaC,eAAe,EAAEC,SAAS,EAAEC,SAAS,EAAE,GAAGC,MAAK,GAAGL;IACtE,MAAMM,aAAaF,cAAc;IACjC,MAAMH,cAAc,AAACC,CAAAA,mBAAmB,mBAAkB,IAAMI,CAAAA,aAAaR,kBAAkB,EAAC;IAEhG,OAAO;QACLS,SAASV,MAAMW,MAAM,CAAC;YACpBC,SAAS;gBAAC;aAAa;YACvBC,kBAAkB;YAClBT;YACAE,WAAWA,aAAcG,CAAAA,aAAa,aAAaK,SAAQ;YAC3DC,WAAW;YACX,GAAGP,IAAI;YACPQ,MAAM;YACNC,OAAO,OAAOC;gBACZ,MAAMC,UAAUD,MAAME,IAAI;gBAC1B,IAAID,YAAY,IAAI;oBAClB,MAAM,IAAIE,MAAM;gBAClB;gBACA,OAAOF;YACT;QACF;IACF;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASG,oBAAoBnB,OAA0B;IAC5D,MAAM,EAACC,aAAaC,eAAe,EAAEC,SAAS,EAAEC,SAAS,EAAE,GAAGC,MAAK,GAAGL;IACtE,MAAMM,aAAaF,cAAc;IACjC,MAAMH,cACJ,AAACC,CAAAA,mBAAmB,wBAAuB,IAAMI,CAAAA,aAAaR,kBAAkB,EAAC;IAEnF,OAAO;QACLsB,cAAcvB,MAAMW,MAAM,CAAC;YACzBP;YACAE,WAAWA,aAAcG,CAAAA,aAAa,aAAaK,SAAQ;YAC3DC,WAAW;YACX,GAAGP,IAAI;YACPS,OAAO,OAAOC;gBACZ,MAAMC,UAAUD,MAAME,IAAI;gBAC1B,IAAID,YAAY,IAAI;oBAClB,MAAM,IAAIE,MAAM;gBAClB;gBACA,OAAOF;YACT;QACF;IACF;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASK,eAAerB,OAA0B;IACvD,MAAM,EAACC,aAAaC,eAAe,EAAEC,SAAS,EAAEC,SAAS,EAAE,GAAGC,MAAK,GAAGL;IACtE,MAAMM,aAAaF,cAAc;IACjC,MAAMH,cAAc,AAACC,CAAAA,mBAAmB,gBAAe,IAAMI,CAAAA,aAAaR,kBAAkB,EAAC;IAE7F,OAAO;QACLwB,SAASzB,MAAMW,MAAM,CAAC;YACpBP;YACAE,WAAWA,aAAcG,CAAAA,aAAa,aAAaK,SAAQ;YAC3DC,WAAW;YACX,GAAGP,IAAI;YACPQ,MAAM;YACNC,OAAO,OAAOC;gBACZ,MAAMC,UAAUD,MAAME,IAAI;gBAC1B,IAAID,YAAY,IAAI;oBAClB,MAAM,IAAIE,MAAM;gBAClB;gBACA,OAAOF;YACT;QACF;IACF;AACF"}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
2
|
import { warn } from '@oclif/core/ux';
|
|
3
|
-
import { getSanityEnv } from '
|
|
3
|
+
import { getSanityEnv } from '@sanity/cli-core/util';
|
|
4
4
|
const knownEnvs = new Set([
|
|
5
5
|
'development',
|
|
6
6
|
'production',
|
|
7
7
|
'staging'
|
|
8
8
|
]);
|
|
9
9
|
export function warnOnNonProductionEnvironment() {
|
|
10
|
-
|
|
10
|
+
const sanityEnv = getSanityEnv();
|
|
11
|
+
if (sanityEnv === 'production') {
|
|
11
12
|
return;
|
|
12
13
|
}
|
|
13
14
|
if (process.env.TEST !== 'true') {
|
|
14
|
-
warn(styleText('yellow', knownEnvs.has(
|
|
15
|
+
warn(styleText('yellow', knownEnvs.has(sanityEnv) ? `Running in ${sanityEnv} environment mode\n` : `Running in ${styleText('red', 'UNKNOWN')} "${sanityEnv}" environment mode\n`));
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/util/warnOnNonProductionEnvironment.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {warn} from '@oclif/core/ux'\
|
|
1
|
+
{"version":3,"sources":["../../src/util/warnOnNonProductionEnvironment.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {warn} from '@oclif/core/ux'\nimport {getSanityEnv} from '@sanity/cli-core/util'\n\nconst knownEnvs = new Set(['development', 'production', 'staging'])\n\nexport function warnOnNonProductionEnvironment(): void {\n const sanityEnv = getSanityEnv()\n if (sanityEnv === 'production') {\n return\n }\n\n if (process.env.TEST !== 'true') {\n warn(\n styleText(\n 'yellow',\n knownEnvs.has(sanityEnv)\n ? `Running in ${sanityEnv} environment mode\\n`\n : `Running in ${styleText('red', 'UNKNOWN')} \"${sanityEnv}\" environment mode\\n`,\n ),\n )\n }\n}\n"],"names":["styleText","warn","getSanityEnv","knownEnvs","Set","warnOnNonProductionEnvironment","sanityEnv","process","env","TEST","has"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,IAAI,QAAO,iBAAgB;AACnC,SAAQC,YAAY,QAAO,wBAAuB;AAElD,MAAMC,YAAY,IAAIC,IAAI;IAAC;IAAe;IAAc;CAAU;AAElE,OAAO,SAASC;IACd,MAAMC,YAAYJ;IAClB,IAAII,cAAc,cAAc;QAC9B;IACF;IAEA,IAAIC,QAAQC,GAAG,CAACC,IAAI,KAAK,QAAQ;QAC/BR,KACED,UACE,UACAG,UAAUO,GAAG,CAACJ,aACV,CAAC,WAAW,EAAEA,UAAU,mBAAmB,CAAC,GAC5C,CAAC,WAAW,EAAEN,UAAU,OAAO,WAAW,EAAE,EAAEM,UAAU,oBAAoB,CAAC;IAGvF;AACF"}
|