@sanity/cli 7.12.1 → 7.13.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.
@@ -0,0 +1,294 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { buffer } from 'node:stream/consumers';
4
+ import { styleText } from 'node:util';
5
+ import { Args, Flags } from '@oclif/core';
6
+ import { colorizeJson, exitCodes, SanityCommand, subdebug } from '@sanity/cli-core';
7
+ import { ApiUsageError, ProjectIdRequiredError } from '../actions/api/errors.js';
8
+ import { fieldsToQuery, parseFields } from '../actions/api/parseFields.js';
9
+ import { resolveEndpoint } from '../actions/api/resolveEndpoint.js';
10
+ import { apiRoutes } from '../generated/apiRoutes.js';
11
+ import { promptForProject } from '../prompts/promptForProject.js';
12
+ import { performApiRequest } from '../services/api.js';
13
+ import { getDatasetFlag, getProjectIdFlag } from '../util/sharedFlags.js';
14
+ const apiDebug = subdebug('api');
15
+ const METHOD_RE = /^[a-z]+$/i;
16
+ const BODYLESS_METHODS = new Set([
17
+ 'GET',
18
+ 'HEAD'
19
+ ]);
20
+ export class ApiCommand extends SanityCommand {
21
+ static args = {
22
+ endpoint: Args.string({
23
+ description: 'API path (eg "projects" or "data/query/{dataset}"), optionally with placeholders, or a full https://*.api.sanity.io URL',
24
+ required: true
25
+ })
26
+ };
27
+ static description = `Make an authenticated HTTP request to a Sanity API
28
+
29
+ The endpoint argument is an API path as documented in the published OpenAPI
30
+ specifications - list them with "sanity openapi list" and inspect one with
31
+ "sanity openapi get <slug>". Paths can be copied verbatim from the specs:
32
+ {projectId} and {dataset} placeholders are filled in from flags or the CLI
33
+ configuration, and the API host (api.sanity.io or <projectId>.api.sanity.io)
34
+ is chosen based on the specs' routing information.
35
+
36
+ The default request method is GET, or POST when fields or --input are
37
+ provided. For GET/HEAD requests, fields are sent as query parameters;
38
+ otherwise they are combined into a JSON request body sent with
39
+ "Content-Type: application/json". Raw --input bodies are sent without a
40
+ default Content-Type - provide one with -H when the API requires it. The
41
+ response body is written to stdout.
42
+
43
+ Requests are authenticated with the token from "sanity login". To use a
44
+ specific token instead - for example in CI or when the CLI is not logged in
45
+ - pass --token or set the SANITY_AUTH_TOKEN environment variable. Pass
46
+ --anonymous to send no token at all.`;
47
+ static examples = [
48
+ {
49
+ command: '<%= config.bin %> <%= command.id %> users/me',
50
+ description: 'Get the current user'
51
+ },
52
+ {
53
+ command: '<%= config.bin %> <%= command.id %> projects/{projectId}',
54
+ description: 'Get the current project (placeholder filled from CLI config)'
55
+ },
56
+ {
57
+ command: `<%= config.bin %> <%= command.id %> 'data/query/{dataset}' -f query='*[_type == "movie"][0..2]'`,
58
+ description: 'Run a GROQ query against the project host'
59
+ },
60
+ {
61
+ command: '<%= config.bin %> <%= command.id %> projects/{projectId} -X PATCH -F displayName="My project"',
62
+ description: 'Send a JSON body built from typed fields'
63
+ },
64
+ {
65
+ command: `echo '{"mutations": []}' | <%= config.bin %> <%= command.id %> 'data/mutate/{dataset}' --input - -H 'Content-Type: application/json'`,
66
+ description: 'Send a raw request body from stdin'
67
+ },
68
+ {
69
+ command: '<%= config.bin %> <%= command.id %> jobs/123 --include --api-version v2025-02-19',
70
+ description: 'Include the response status and headers, pinning the API version'
71
+ },
72
+ {
73
+ command: 'SANITY_AUTH_TOKEN=<token> <%= config.bin %> <%= command.id %> users/me',
74
+ description: 'Authenticate with a specific token instead of the logged-in session'
75
+ }
76
+ ];
77
+ static flags = {
78
+ ...getProjectIdFlag({
79
+ description: 'Project ID for {projectId} placeholders and project-hosted APIs',
80
+ semantics: 'override'
81
+ }),
82
+ ...getDatasetFlag({
83
+ description: 'Dataset for {dataset} placeholders',
84
+ semantics: 'override'
85
+ }),
86
+ anonymous: Flags.boolean({
87
+ default: false,
88
+ description: 'Send the request without an authorization token'
89
+ }),
90
+ 'api-version': Flags.string({
91
+ description: 'API version to use (eg v2025-02-19). Defaults to a version embedded in the endpoint path, or the version from the matching OpenAPI spec',
92
+ helpValue: '<version>'
93
+ }),
94
+ field: Flags.string({
95
+ char: 'F',
96
+ description: 'Add a typed parameter (key=value): true/false/null and numbers are converted, @file reads the value from a file, @- from stdin',
97
+ helpValue: '<key=value>',
98
+ multiple: true
99
+ }),
100
+ global: Flags.boolean({
101
+ description: 'Force the request to the global API host (api.sanity.io)',
102
+ exclusive: [
103
+ 'project-hosted'
104
+ ]
105
+ }),
106
+ header: Flags.string({
107
+ char: 'H',
108
+ description: 'Add an HTTP request header (key: value)',
109
+ helpValue: '<key:value>',
110
+ multiple: true
111
+ }),
112
+ include: Flags.boolean({
113
+ char: 'i',
114
+ default: false,
115
+ description: 'Include the HTTP response status and headers in the output'
116
+ }),
117
+ input: Flags.string({
118
+ description: 'Read the raw request body from a file (use "-" for stdin). Sent without a default Content-Type - provide one with -H when the API requires it',
119
+ exclusive: [
120
+ 'field',
121
+ 'raw-field'
122
+ ],
123
+ helpValue: '<file>'
124
+ }),
125
+ method: Flags.string({
126
+ char: 'X',
127
+ description: 'HTTP method to use (default GET, or POST when fields or --input are provided)',
128
+ helpValue: '<method>'
129
+ }),
130
+ pretty: Flags.boolean({
131
+ default: false,
132
+ description: 'Colorize JSON output'
133
+ }),
134
+ 'project-hosted': Flags.boolean({
135
+ description: 'Force the request to the project API host (<projectId>.api.sanity.io)',
136
+ exclusive: [
137
+ 'global'
138
+ ]
139
+ }),
140
+ 'raw-field': Flags.string({
141
+ char: 'f',
142
+ description: 'Add a string parameter (key=value)',
143
+ helpValue: '<key=value>',
144
+ multiple: true
145
+ }),
146
+ token: Flags.string({
147
+ char: 't',
148
+ description: 'API token to authenticate with, instead of the logged-in user token',
149
+ exclusive: [
150
+ 'anonymous'
151
+ ],
152
+ helpValue: '<token>'
153
+ })
154
+ };
155
+ /**
156
+ * Read the raw body for `--input`, or the content referenced by a `@-`
157
+ * field value. Returns raw bytes so binary bodies survive unmodified.
158
+ * Extracted so tests can substitute stdin.
159
+ */ readStdin() {
160
+ return buffer(process.stdin);
161
+ }
162
+ async run() {
163
+ const { args, flags } = await this.parse(ApiCommand);
164
+ try {
165
+ await this.performRequest(args.endpoint, flags);
166
+ } catch (error) {
167
+ if (error instanceof ApiUsageError) {
168
+ this.error(error.message, {
169
+ exit: exitCodes.USAGE_ERROR
170
+ });
171
+ }
172
+ throw error;
173
+ }
174
+ }
175
+ async performRequest(endpoint, flags) {
176
+ const stdin = await this.resolveStdin(flags);
177
+ const fields = parseFields({
178
+ fields: flags.field,
179
+ rawFields: flags['raw-field'],
180
+ readFile: (path)=>readFileSync(path, 'utf8'),
181
+ stdin: stdin?.toString('utf8')
182
+ });
183
+ const hasFields = Object.keys(fields).length > 0;
184
+ const method = this.resolveMethod(flags.method, hasFields || flags.input !== undefined);
185
+ const useQueryFields = BODYLESS_METHODS.has(method);
186
+ const body = await this.resolveBody(flags, fields, hasFields, useQueryFields, stdin);
187
+ const query = useQueryFields && hasFields ? fieldsToQuery(fields) : {};
188
+ const resolved = await this.resolveTarget(endpoint, flags);
189
+ apiDebug('Resolved endpoint', resolved);
190
+ const response = await performApiRequest({
191
+ ...body === undefined ? {} : {
192
+ body
193
+ },
194
+ headers: parseHeaders(flags.header ?? []),
195
+ method,
196
+ query,
197
+ resolved,
198
+ token: flags.token,
199
+ unauthenticated: flags.anonymous
200
+ });
201
+ this.printResponse(response, flags);
202
+ if (response.statusCode >= 400) {
203
+ const statusLine = `HTTP ${response.statusCode}${response.statusMessage ? ` ${response.statusMessage}` : ''}`;
204
+ const loginHint = response.statusCode === 401 && !flags.anonymous ? `. You may need to login again with ${styleText('cyan', 'sanity login')}` : '';
205
+ this.error(`${statusLine}${loginHint}`, {
206
+ exit: exitCodes.RUNTIME_ERROR
207
+ });
208
+ }
209
+ }
210
+ printResponse(response, flags) {
211
+ if (flags.include) {
212
+ this.log(`HTTP ${response.statusCode}${response.statusMessage ? ` ${response.statusMessage}` : ''}`);
213
+ for (const [key, value] of Object.entries(response.headers)){
214
+ this.log(`${key}: ${value}`);
215
+ }
216
+ this.log('');
217
+ }
218
+ if (!response.jsonBody) {
219
+ if (response.rawBody !== '') this.log(response.rawBody);
220
+ return;
221
+ }
222
+ this.log(flags.pretty ? colorizeJson(response.body) : JSON.stringify(response.body, null, 2));
223
+ }
224
+ async resolveBody(flags, fields, hasFields, useQueryFields, stdin) {
225
+ if (flags.input !== undefined) {
226
+ if (flags.input === '-') return stdin;
227
+ try {
228
+ // Read raw bytes - `--input` bodies may be binary (eg asset uploads)
229
+ return await readFile(flags.input);
230
+ } catch (error) {
231
+ const message = error instanceof Error ? error.message : String(error);
232
+ throw new ApiUsageError(`Failed to read --input file "${flags.input}": ${message}`);
233
+ }
234
+ }
235
+ return hasFields && !useQueryFields ? fields : undefined;
236
+ }
237
+ resolveMethod(methodFlag, hasBody) {
238
+ if (methodFlag === undefined) {
239
+ return hasBody ? 'POST' : 'GET';
240
+ }
241
+ if (!METHOD_RE.test(methodFlag)) {
242
+ throw new ApiUsageError(`Invalid HTTP method "${methodFlag}"`);
243
+ }
244
+ return methodFlag.toUpperCase();
245
+ }
246
+ async resolveStdin(flags) {
247
+ const needsStdin = flags.input === '-' || (flags.field ?? []).some((field)=>field.endsWith('=@-'));
248
+ return needsStdin ? this.readStdin() : undefined;
249
+ }
250
+ async resolveTarget(endpoint, flags) {
251
+ const cliConfig = await this.tryGetCliConfig();
252
+ const baseOptions = {
253
+ apiVersion: flags['api-version'],
254
+ dataset: flags.dataset ?? cliConfig.api?.dataset,
255
+ endpoint,
256
+ forceHost: flags.global ? 'global' : undefined,
257
+ routes: apiRoutes
258
+ };
259
+ const forceHost = flags['project-hosted'] ? 'project' : baseOptions.forceHost;
260
+ try {
261
+ return resolveEndpoint({
262
+ ...baseOptions,
263
+ forceHost,
264
+ projectId: flags['project-id'] ?? cliConfig.api?.projectId
265
+ });
266
+ } catch (error) {
267
+ if (!(error instanceof ProjectIdRequiredError)) throw error;
268
+ // The request needs a project ID that flags/config didn't provide -
269
+ // resolve one interactively (or fail with actionable suggestions).
270
+ const projectId = await this.getProjectId({
271
+ fallback: ()=>promptForProject({})
272
+ });
273
+ return resolveEndpoint({
274
+ ...baseOptions,
275
+ forceHost,
276
+ projectId
277
+ });
278
+ }
279
+ }
280
+ }
281
+ function parseHeaders(headerFlags) {
282
+ // Null-prototype for the same reason as the field containers in parseFields
283
+ const headers = Object.create(null);
284
+ for (const header of headerFlags){
285
+ const separatorIndex = header.indexOf(':');
286
+ if (separatorIndex < 1) {
287
+ throw new ApiUsageError(`Invalid --header "${header}": expected "key: value" format`);
288
+ }
289
+ headers[header.slice(0, separatorIndex).trim()] = header.slice(separatorIndex + 1).trim();
290
+ }
291
+ return headers;
292
+ }
293
+
294
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/commands/api.ts"],"sourcesContent":["import {readFileSync} from 'node:fs'\nimport {readFile} from 'node:fs/promises'\nimport {buffer} from 'node:stream/consumers'\nimport {styleText} from 'node:util'\n\nimport {Args, Flags} from '@oclif/core'\nimport {colorizeJson, exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\n\nimport {ApiUsageError, ProjectIdRequiredError} from '../actions/api/errors.js'\nimport {fieldsToQuery, type FieldValue, parseFields} from '../actions/api/parseFields.js'\nimport {type ResolvedEndpoint, resolveEndpoint} from '../actions/api/resolveEndpoint.js'\nimport {apiRoutes} from '../generated/apiRoutes.js'\nimport {promptForProject} from '../prompts/promptForProject.js'\nimport {type ApiResponse, performApiRequest} from '../services/api.js'\nimport {getDatasetFlag, getProjectIdFlag} from '../util/sharedFlags.js'\n\nconst apiDebug = subdebug('api')\n\nconst METHOD_RE = /^[a-z]+$/i\n\nconst BODYLESS_METHODS = new Set(['GET', 'HEAD'])\n\nexport class ApiCommand extends SanityCommand<typeof ApiCommand> {\n static override args = {\n endpoint: Args.string({\n description:\n 'API path (eg \"projects\" or \"data/query/{dataset}\"), optionally with placeholders, or a full https://*.api.sanity.io URL',\n required: true,\n }),\n }\n\n static override description = `Make an authenticated HTTP request to a Sanity API\n\nThe endpoint argument is an API path as documented in the published OpenAPI\nspecifications - list them with \"sanity openapi list\" and inspect one with\n\"sanity openapi get <slug>\". Paths can be copied verbatim from the specs:\n{projectId} and {dataset} placeholders are filled in from flags or the CLI\nconfiguration, and the API host (api.sanity.io or <projectId>.api.sanity.io)\nis chosen based on the specs' routing information.\n\nThe default request method is GET, or POST when fields or --input are\nprovided. For GET/HEAD requests, fields are sent as query parameters;\notherwise they are combined into a JSON request body sent with\n\"Content-Type: application/json\". Raw --input bodies are sent without a\ndefault Content-Type - provide one with -H when the API requires it. The\nresponse body is written to stdout.\n\nRequests are authenticated with the token from \"sanity login\". To use a\nspecific token instead - for example in CI or when the CLI is not logged in\n- pass --token or set the SANITY_AUTH_TOKEN environment variable. Pass\n--anonymous to send no token at all.`\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> users/me',\n description: 'Get the current user',\n },\n {\n command: '<%= config.bin %> <%= command.id %> projects/{projectId}',\n description: 'Get the current project (placeholder filled from CLI config)',\n },\n {\n command: `<%= config.bin %> <%= command.id %> 'data/query/{dataset}' -f query='*[_type == \"movie\"][0..2]'`,\n description: 'Run a GROQ query against the project host',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> projects/{projectId} -X PATCH -F displayName=\"My project\"',\n description: 'Send a JSON body built from typed fields',\n },\n {\n command: `echo '{\"mutations\": []}' | <%= config.bin %> <%= command.id %> 'data/mutate/{dataset}' --input - -H 'Content-Type: application/json'`,\n description: 'Send a raw request body from stdin',\n },\n {\n command: '<%= config.bin %> <%= command.id %> jobs/123 --include --api-version v2025-02-19',\n description: 'Include the response status and headers, pinning the API version',\n },\n {\n command: 'SANITY_AUTH_TOKEN=<token> <%= config.bin %> <%= command.id %> users/me',\n description: 'Authenticate with a specific token instead of the logged-in session',\n },\n ]\n\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID for {projectId} placeholders and project-hosted APIs',\n semantics: 'override',\n }),\n ...getDatasetFlag({\n description: 'Dataset for {dataset} placeholders',\n semantics: 'override',\n }),\n anonymous: Flags.boolean({\n default: false,\n description: 'Send the request without an authorization token',\n }),\n 'api-version': Flags.string({\n description:\n 'API version to use (eg v2025-02-19). Defaults to a version embedded in the endpoint path, or the version from the matching OpenAPI spec',\n helpValue: '<version>',\n }),\n field: Flags.string({\n char: 'F',\n description:\n 'Add a typed parameter (key=value): true/false/null and numbers are converted, @file reads the value from a file, @- from stdin',\n helpValue: '<key=value>',\n multiple: true,\n }),\n global: Flags.boolean({\n description: 'Force the request to the global API host (api.sanity.io)',\n exclusive: ['project-hosted'],\n }),\n header: Flags.string({\n char: 'H',\n description: 'Add an HTTP request header (key: value)',\n helpValue: '<key:value>',\n multiple: true,\n }),\n include: Flags.boolean({\n char: 'i',\n default: false,\n description: 'Include the HTTP response status and headers in the output',\n }),\n input: Flags.string({\n description:\n 'Read the raw request body from a file (use \"-\" for stdin). Sent without a default Content-Type - provide one with -H when the API requires it',\n exclusive: ['field', 'raw-field'],\n helpValue: '<file>',\n }),\n method: Flags.string({\n char: 'X',\n description: 'HTTP method to use (default GET, or POST when fields or --input are provided)',\n helpValue: '<method>',\n }),\n pretty: Flags.boolean({\n default: false,\n description: 'Colorize JSON output',\n }),\n 'project-hosted': Flags.boolean({\n description: 'Force the request to the project API host (<projectId>.api.sanity.io)',\n exclusive: ['global'],\n }),\n 'raw-field': Flags.string({\n char: 'f',\n description: 'Add a string parameter (key=value)',\n helpValue: '<key=value>',\n multiple: true,\n }),\n token: Flags.string({\n char: 't',\n description: 'API token to authenticate with, instead of the logged-in user token',\n exclusive: ['anonymous'],\n helpValue: '<token>',\n }),\n }\n\n /**\n * Read the raw body for `--input`, or the content referenced by a `@-`\n * field value. Returns raw bytes so binary bodies survive unmodified.\n * Extracted so tests can substitute stdin.\n */\n protected readStdin(): Promise<Buffer> {\n return buffer(process.stdin)\n }\n\n public async run(): Promise<void> {\n const {args, flags} = await this.parse(ApiCommand)\n\n try {\n await this.performRequest(args.endpoint, flags)\n } catch (error) {\n if (error instanceof ApiUsageError) {\n this.error(error.message, {exit: exitCodes.USAGE_ERROR})\n }\n throw error\n }\n }\n\n private async performRequest(\n endpoint: string,\n flags: (typeof ApiCommand.prototype)['flags'],\n ): Promise<void> {\n const stdin = await this.resolveStdin(flags)\n\n const fields = parseFields({\n fields: flags.field,\n rawFields: flags['raw-field'],\n readFile: (path) => readFileSync(path, 'utf8'),\n stdin: stdin?.toString('utf8'),\n })\n const hasFields = Object.keys(fields).length > 0\n\n const method = this.resolveMethod(flags.method, hasFields || flags.input !== undefined)\n const useQueryFields = BODYLESS_METHODS.has(method)\n\n const body = await this.resolveBody(flags, fields, hasFields, useQueryFields, stdin)\n const query = useQueryFields && hasFields ? fieldsToQuery(fields) : {}\n\n const resolved = await this.resolveTarget(endpoint, flags)\n apiDebug('Resolved endpoint', resolved)\n\n const response = await performApiRequest({\n ...(body === undefined ? {} : {body}),\n headers: parseHeaders(flags.header ?? []),\n method,\n query,\n resolved,\n token: flags.token,\n unauthenticated: flags.anonymous,\n })\n\n this.printResponse(response, flags)\n\n if (response.statusCode >= 400) {\n const statusLine = `HTTP ${response.statusCode}${\n response.statusMessage ? ` ${response.statusMessage}` : ''\n }`\n const loginHint =\n response.statusCode === 401 && !flags.anonymous\n ? `. You may need to login again with ${styleText('cyan', 'sanity login')}`\n : ''\n this.error(`${statusLine}${loginHint}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n }\n\n private printResponse(response: ApiResponse, flags: {include: boolean; pretty: boolean}): void {\n if (flags.include) {\n this.log(\n `HTTP ${response.statusCode}${response.statusMessage ? ` ${response.statusMessage}` : ''}`,\n )\n for (const [key, value] of Object.entries(response.headers)) {\n this.log(`${key}: ${value}`)\n }\n this.log('')\n }\n\n if (!response.jsonBody) {\n if (response.rawBody !== '') this.log(response.rawBody)\n return\n }\n\n this.log(flags.pretty ? colorizeJson(response.body) : JSON.stringify(response.body, null, 2))\n }\n\n private async resolveBody(\n flags: {input?: string},\n fields: Record<string, FieldValue>,\n hasFields: boolean,\n useQueryFields: boolean,\n stdin: Buffer | undefined,\n ): Promise<Buffer | unknown | undefined> {\n if (flags.input !== undefined) {\n if (flags.input === '-') return stdin\n try {\n // Read raw bytes - `--input` bodies may be binary (eg asset uploads)\n return await readFile(flags.input)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new ApiUsageError(`Failed to read --input file \"${flags.input}\": ${message}`)\n }\n }\n\n return hasFields && !useQueryFields ? fields : undefined\n }\n\n private resolveMethod(methodFlag: string | undefined, hasBody: boolean): string {\n if (methodFlag === undefined) {\n return hasBody ? 'POST' : 'GET'\n }\n if (!METHOD_RE.test(methodFlag)) {\n throw new ApiUsageError(`Invalid HTTP method \"${methodFlag}\"`)\n }\n return methodFlag.toUpperCase()\n }\n\n private async resolveStdin(flags: {\n field?: string[]\n input?: string\n }): Promise<Buffer | undefined> {\n const needsStdin =\n flags.input === '-' || (flags.field ?? []).some((field) => field.endsWith('=@-'))\n return needsStdin ? this.readStdin() : undefined\n }\n\n private async resolveTarget(\n endpoint: string,\n flags: {\n 'api-version'?: string\n dataset?: string\n global?: boolean\n 'project-hosted'?: boolean\n 'project-id'?: string\n },\n ): Promise<ResolvedEndpoint> {\n const cliConfig = await this.tryGetCliConfig()\n\n const baseOptions = {\n apiVersion: flags['api-version'],\n dataset: flags.dataset ?? cliConfig.api?.dataset,\n endpoint,\n forceHost: flags.global ? ('global' as const) : undefined,\n routes: apiRoutes,\n }\n const forceHost = flags['project-hosted'] ? ('project' as const) : baseOptions.forceHost\n\n try {\n return resolveEndpoint({\n ...baseOptions,\n forceHost,\n projectId: flags['project-id'] ?? cliConfig.api?.projectId,\n })\n } catch (error) {\n if (!(error instanceof ProjectIdRequiredError)) throw error\n\n // The request needs a project ID that flags/config didn't provide -\n // resolve one interactively (or fail with actionable suggestions).\n const projectId = await this.getProjectId({fallback: () => promptForProject({})})\n return resolveEndpoint({...baseOptions, forceHost, projectId})\n }\n }\n}\n\nfunction parseHeaders(headerFlags: string[]): Record<string, string> {\n // Null-prototype for the same reason as the field containers in parseFields\n const headers: Record<string, string> = Object.create(null)\n\n for (const header of headerFlags) {\n const separatorIndex = header.indexOf(':')\n if (separatorIndex < 1) {\n throw new ApiUsageError(`Invalid --header \"${header}\": expected \"key: value\" format`)\n }\n headers[header.slice(0, separatorIndex).trim()] = header.slice(separatorIndex + 1).trim()\n }\n\n return headers\n}\n"],"names":["readFileSync","readFile","buffer","styleText","Args","Flags","colorizeJson","exitCodes","SanityCommand","subdebug","ApiUsageError","ProjectIdRequiredError","fieldsToQuery","parseFields","resolveEndpoint","apiRoutes","promptForProject","performApiRequest","getDatasetFlag","getProjectIdFlag","apiDebug","METHOD_RE","BODYLESS_METHODS","Set","ApiCommand","args","endpoint","string","description","required","examples","command","flags","semantics","anonymous","boolean","default","helpValue","field","char","multiple","global","exclusive","header","include","input","method","pretty","token","readStdin","process","stdin","run","parse","performRequest","error","message","exit","USAGE_ERROR","resolveStdin","fields","rawFields","path","toString","hasFields","Object","keys","length","resolveMethod","undefined","useQueryFields","has","body","resolveBody","query","resolved","resolveTarget","response","headers","parseHeaders","unauthenticated","printResponse","statusCode","statusLine","statusMessage","loginHint","RUNTIME_ERROR","log","key","value","entries","jsonBody","rawBody","JSON","stringify","Error","String","methodFlag","hasBody","test","toUpperCase","needsStdin","some","endsWith","cliConfig","tryGetCliConfig","baseOptions","apiVersion","dataset","api","forceHost","routes","projectId","getProjectId","fallback","headerFlags","create","separatorIndex","indexOf","slice","trim"],"mappings":"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"}