@sanity/cli 7.12.0 → 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.
- package/README.md +88 -0
- package/dist/actions/api/constants.js +13 -0
- package/dist/actions/api/constants.js.map +1 -0
- package/dist/actions/api/distillApiRoutes.js +106 -0
- package/dist/actions/api/distillApiRoutes.js.map +1 -0
- package/dist/actions/api/errors.js +18 -0
- package/dist/actions/api/errors.js.map +1 -0
- package/dist/actions/api/parseFields.js +183 -0
- package/dist/actions/api/parseFields.js.map +1 -0
- package/dist/actions/api/resolveEndpoint.js +159 -0
- package/dist/actions/api/resolveEndpoint.js.map +1 -0
- package/dist/actions/api/types.js +10 -0
- package/dist/actions/api/types.js.map +1 -0
- package/dist/actions/build/buildApp.js +4 -2
- package/dist/actions/build/buildApp.js.map +1 -1
- package/dist/actions/build/buildStudio.js +105 -14
- package/dist/actions/build/buildStudio.js.map +1 -1
- package/dist/actions/build/eventListenerFactory.js +70 -0
- package/dist/actions/build/eventListenerFactory.js.map +1 -0
- package/dist/actions/dev/servers/startStudioDevServer.js +5 -1
- package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -1
- package/dist/commands/api.js +294 -0
- package/dist/commands/api.js.map +1 -0
- package/dist/commands/backups/disable.js +4 -4
- package/dist/commands/backups/disable.js.map +1 -1
- package/dist/commands/backups/download.js +7 -7
- package/dist/commands/backups/download.js.map +1 -1
- package/dist/commands/backups/enable.js +4 -4
- package/dist/commands/backups/enable.js.map +1 -1
- package/dist/commands/backups/list.js +7 -7
- package/dist/commands/backups/list.js.map +1 -1
- package/dist/commands/codemod.js +5 -5
- 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 +4 -4
- package/dist/commands/cors/delete.js.map +1 -1
- package/dist/commands/cors/list.js +2 -2
- package/dist/commands/cors/list.js.map +1 -1
- package/dist/commands/datasets/alias/create.js +5 -5
- package/dist/commands/datasets/alias/create.js.map +1 -1
- package/dist/commands/datasets/alias/delete.js +2 -2
- package/dist/commands/datasets/alias/delete.js.map +1 -1
- package/dist/commands/datasets/alias/link.js +5 -5
- package/dist/commands/datasets/alias/link.js.map +1 -1
- package/dist/commands/datasets/alias/unlink.js +3 -3
- package/dist/commands/datasets/alias/unlink.js.map +1 -1
- package/dist/commands/datasets/copy.js +7 -7
- package/dist/commands/datasets/copy.js.map +1 -1
- package/dist/commands/datasets/create.js +3 -3
- package/dist/commands/datasets/create.js.map +1 -1
- package/dist/commands/datasets/delete.js +2 -2
- package/dist/commands/datasets/delete.js.map +1 -1
- package/dist/commands/datasets/embeddings/disable.js +3 -3
- package/dist/commands/datasets/embeddings/disable.js.map +1 -1
- package/dist/commands/datasets/embeddings/enable.js +5 -5
- package/dist/commands/datasets/embeddings/enable.js.map +1 -1
- package/dist/commands/datasets/embeddings/status.js +3 -3
- package/dist/commands/datasets/embeddings/status.js.map +1 -1
- package/dist/commands/datasets/export.js +6 -6
- package/dist/commands/datasets/export.js.map +1 -1
- package/dist/commands/datasets/import.js +3 -3
- package/dist/commands/datasets/import.js.map +1 -1
- package/dist/commands/datasets/list.js +2 -2
- package/dist/commands/datasets/list.js.map +1 -1
- package/dist/commands/datasets/visibility/get.js +2 -2
- package/dist/commands/datasets/visibility/get.js.map +1 -1
- package/dist/commands/datasets/visibility/set.js +3 -3
- package/dist/commands/datasets/visibility/set.js.map +1 -1
- package/dist/commands/deploy.js +2 -2
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.js +2 -2
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/docs/read.js +1 -1
- package/dist/commands/docs/read.js.map +1 -1
- package/dist/commands/docs/search.js +3 -3
- package/dist/commands/docs/search.js.map +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/documents/create.js +3 -3
- package/dist/commands/documents/create.js.map +1 -1
- package/dist/commands/documents/delete.js +3 -3
- package/dist/commands/documents/delete.js.map +1 -1
- package/dist/commands/documents/get.js +2 -2
- package/dist/commands/documents/get.js.map +1 -1
- package/dist/commands/documents/query.js +2 -2
- package/dist/commands/documents/query.js.map +1 -1
- package/dist/commands/documents/validate.js +3 -3
- package/dist/commands/documents/validate.js.map +1 -1
- package/dist/commands/graphql/deploy.js +13 -13
- package/dist/commands/graphql/deploy.js.map +1 -1
- package/dist/commands/graphql/list.js +2 -2
- package/dist/commands/graphql/list.js.map +1 -1
- package/dist/commands/graphql/undeploy.js +2 -2
- package/dist/commands/graphql/undeploy.js.map +1 -1
- package/dist/commands/hooks/attempt.js +2 -2
- package/dist/commands/hooks/attempt.js.map +1 -1
- package/dist/commands/hooks/create.js +3 -3
- package/dist/commands/hooks/create.js.map +1 -1
- package/dist/commands/hooks/delete.js +4 -4
- package/dist/commands/hooks/delete.js.map +1 -1
- package/dist/commands/hooks/list.js +2 -2
- package/dist/commands/hooks/list.js.map +1 -1
- package/dist/commands/hooks/logs.js +5 -5
- package/dist/commands/hooks/logs.js.map +1 -1
- package/dist/commands/login.js +2 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.js +2 -2
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/manifest/extract.js +3 -3
- package/dist/commands/manifest/extract.js.map +1 -1
- package/dist/commands/mcp/configure.js +4 -4
- package/dist/commands/mcp/configure.js.map +1 -1
- package/dist/commands/media/create-aspect.js +3 -3
- package/dist/commands/media/create-aspect.js.map +1 -1
- package/dist/commands/media/delete-aspect.js +1 -1
- package/dist/commands/media/delete-aspect.js.map +1 -1
- package/dist/commands/media/deploy-aspect.js +4 -4
- package/dist/commands/media/deploy-aspect.js.map +1 -1
- package/dist/commands/media/export.js +7 -7
- package/dist/commands/media/export.js.map +1 -1
- package/dist/commands/media/import.js +6 -6
- package/dist/commands/media/import.js.map +1 -1
- package/dist/commands/migrations/create.js +4 -4
- package/dist/commands/migrations/create.js.map +1 -1
- package/dist/commands/migrations/list.js +2 -2
- package/dist/commands/migrations/list.js.map +1 -1
- package/dist/commands/migrations/run.js +14 -14
- package/dist/commands/migrations/run.js.map +1 -1
- package/dist/commands/openapi/get.js +3 -3
- package/dist/commands/openapi/get.js.map +1 -1
- package/dist/commands/openapi/list.js +2 -2
- package/dist/commands/openapi/list.js.map +1 -1
- package/dist/commands/organizations/create.js +5 -5
- package/dist/commands/organizations/create.js.map +1 -1
- package/dist/commands/organizations/delete.js +6 -6
- package/dist/commands/organizations/delete.js.map +1 -1
- package/dist/commands/organizations/get.js +3 -3
- package/dist/commands/organizations/get.js.map +1 -1
- package/dist/commands/organizations/list.js +2 -2
- package/dist/commands/organizations/list.js.map +1 -1
- package/dist/commands/organizations/update.js +6 -6
- package/dist/commands/organizations/update.js.map +1 -1
- package/dist/commands/preview.js +2 -2
- package/dist/commands/preview.js.map +1 -1
- package/dist/commands/projects/create.js +2 -2
- package/dist/commands/projects/create.js.map +1 -1
- package/dist/commands/projects/list.js +2 -2
- package/dist/commands/projects/list.js.map +1 -1
- package/dist/commands/schemas/delete.js +2 -2
- package/dist/commands/schemas/delete.js.map +1 -1
- package/dist/commands/schemas/deploy.js +2 -2
- package/dist/commands/schemas/deploy.js.map +1 -1
- package/dist/commands/schemas/list.js +1 -1
- package/dist/commands/schemas/list.js.map +1 -1
- package/dist/commands/schemas/validate.js +2 -1
- package/dist/commands/schemas/validate.js.map +1 -1
- package/dist/commands/skills/install.js +2 -2
- package/dist/commands/skills/install.js.map +1 -1
- package/dist/commands/telemetry/disable.js +2 -2
- package/dist/commands/telemetry/disable.js.map +1 -1
- package/dist/commands/telemetry/enable.js +2 -2
- package/dist/commands/telemetry/enable.js.map +1 -1
- package/dist/commands/tokens/create.js +2 -2
- 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 +3 -3
- package/dist/commands/users/invite.js.map +1 -1
- package/dist/generated/apiRoutes.js +401 -0
- package/dist/generated/apiRoutes.js.map +1 -0
- package/dist/services/api.js +116 -0
- package/dist/services/api.js.map +1 -0
- package/oclif.manifest.json +433 -252
- package/package.json +5 -3
|
@@ -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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"}
|
|
@@ -59,12 +59,12 @@ export class DisableBackupCommand extends SanityCommand {
|
|
|
59
59
|
const message = error instanceof Error ? error.message : String(error);
|
|
60
60
|
disableBackupDebug(`Failed to list datasets: ${message}`, error);
|
|
61
61
|
this.error(`Failed to list datasets: ${message}`, {
|
|
62
|
-
exit:
|
|
62
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
if (datasets.length === 0) {
|
|
66
66
|
this.error('No datasets found in this project.', {
|
|
67
|
-
exit:
|
|
67
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
if (dataset) {
|
|
@@ -89,7 +89,7 @@ export class DisableBackupCommand extends SanityCommand {
|
|
|
89
89
|
const message = error instanceof Error ? error.message : String(error);
|
|
90
90
|
disableBackupDebug(`Failed to disable backup for dataset`, error);
|
|
91
91
|
this.error(`Disabling dataset backup failed: ${message}`, {
|
|
92
|
-
exit:
|
|
92
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -107,7 +107,7 @@ export class DisableBackupCommand extends SanityCommand {
|
|
|
107
107
|
const err = error;
|
|
108
108
|
disableBackupDebug(`Error fetching datasets`, err);
|
|
109
109
|
this.error(`Failed to fetch datasets:\n${err.message}`, {
|
|
110
|
-
exit:
|
|
110
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/backups/disable.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Args} from '@oclif/core'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {select} from '@sanity/cli-core/ux'\nimport {type DatasetsResponse} from '@sanity/client'\n\nimport {assertDatasetExists} from '../../actions/backup/assertDatasetExist.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {setBackup} from '../../services/backup.js'\nimport {listDatasets} from '../../services/datasets.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst disableBackupDebug = subdebug('backup:disable')\n\nexport class DisableBackupCommand extends SanityCommand<typeof DisableBackupCommand> {\n static override args = {\n dataset: Args.string({\n description: 'Dataset name to disable backup for',\n required: false,\n }),\n }\n\n static override description = 'Disable backup for a dataset'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Interactively disable backup for a dataset',\n },\n {\n command: '<%= config.bin %> <%= command.id %> production',\n description: 'Disable backup for the production dataset',\n },\n ]\n\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to disable backups for',\n semantics: 'override',\n }),\n }\n\n static override hiddenAliases: string[] = ['backup:disable']\n\n public async run(): Promise<void> {\n const {args} = await this.parse(DisableBackupCommand)\n let {dataset} = args\n\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({\n requiredPermissions: [\n {grant: 'read', permission: 'sanity.project.datasets'},\n {grant: 'update', permission: 'sanity.project.datasets'},\n ],\n }),\n })\n\n let datasets: DatasetsResponse\n\n try {\n datasets = await listDatasets(projectId)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n disableBackupDebug(`Failed to list datasets: ${message}`, error)\n this.error(`Failed to list datasets: ${message}`, {exit:
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/backups/disable.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Args} from '@oclif/core'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {select} from '@sanity/cli-core/ux'\nimport {type DatasetsResponse} from '@sanity/client'\n\nimport {assertDatasetExists} from '../../actions/backup/assertDatasetExist.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {setBackup} from '../../services/backup.js'\nimport {listDatasets} from '../../services/datasets.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst disableBackupDebug = subdebug('backup:disable')\n\nexport class DisableBackupCommand extends SanityCommand<typeof DisableBackupCommand> {\n static override args = {\n dataset: Args.string({\n description: 'Dataset name to disable backup for',\n required: false,\n }),\n }\n\n static override description = 'Disable backup for a dataset'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Interactively disable backup for a dataset',\n },\n {\n command: '<%= config.bin %> <%= command.id %> production',\n description: 'Disable backup for the production dataset',\n },\n ]\n\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to disable backups for',\n semantics: 'override',\n }),\n }\n\n static override hiddenAliases: string[] = ['backup:disable']\n\n public async run(): Promise<void> {\n const {args} = await this.parse(DisableBackupCommand)\n let {dataset} = args\n\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({\n requiredPermissions: [\n {grant: 'read', permission: 'sanity.project.datasets'},\n {grant: 'update', permission: 'sanity.project.datasets'},\n ],\n }),\n })\n\n let datasets: DatasetsResponse\n\n try {\n datasets = await listDatasets(projectId)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n disableBackupDebug(`Failed to list datasets: ${message}`, error)\n this.error(`Failed to list datasets: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n if (datasets.length === 0) {\n this.error('No datasets found in this project.', {exit: exitCodes.RUNTIME_ERROR})\n }\n\n if (dataset) {\n assertDatasetExists(datasets, dataset, this.output)\n } else if (this.isUnattended()) {\n this.error('Dataset is required in unattended mode. Pass it as the `<dataset>` argument.', {\n exit: exitCodes.USAGE_ERROR,\n })\n } else {\n dataset = await this.promptForDataset(datasets)\n }\n\n try {\n await setBackup({dataset, projectId, status: false})\n\n this.log(`${styleText('green', `Disabled daily backups for dataset ${dataset}.\\n`)}`)\n this.log(\n `${styleText('yellow', 'Note: Existing backups will be retained according to your retention policy.\\n')}`,\n )\n\n disableBackupDebug(`Successfully disabled backup for dataset ${dataset}`)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n disableBackupDebug(`Failed to disable backup for dataset`, error)\n this.error(`Disabling dataset backup failed: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n }\n\n private async promptForDataset(datasets: DatasetsResponse): Promise<string> {\n try {\n const choices = datasets.map((dataset) => ({\n name: dataset.name,\n value: dataset.name,\n }))\n\n return select({\n choices,\n message: 'Select the dataset name:',\n })\n } catch (error) {\n const err = error as Error\n disableBackupDebug(`Error fetching datasets`, err)\n this.error(`Failed to fetch datasets:\\n${err.message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n }\n}\n"],"names":["styleText","Args","exitCodes","SanityCommand","subdebug","select","assertDatasetExists","promptForProject","setBackup","listDatasets","getProjectIdFlag","disableBackupDebug","DisableBackupCommand","args","dataset","string","description","required","examples","command","flags","semantics","hiddenAliases","run","parse","projectId","getProjectId","fallback","requiredPermissions","grant","permission","datasets","error","message","Error","String","exit","RUNTIME_ERROR","length","output","isUnattended","USAGE_ERROR","promptForDataset","status","log","choices","map","name","value","err"],"mappings":"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"}
|
|
@@ -111,12 +111,12 @@ export class DownloadBackupCommand extends SanityCommand {
|
|
|
111
111
|
const message = error instanceof Error ? error.message : String(error);
|
|
112
112
|
backupDownloadDebug(`Failed to list datasets: ${message}`, error);
|
|
113
113
|
this.error(`Failed to list datasets: ${message}`, {
|
|
114
|
-
exit:
|
|
114
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
117
|
if (datasets.length === 0) {
|
|
118
118
|
this.error('No datasets found in this project.', {
|
|
119
|
-
exit:
|
|
119
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
122
|
if (dataset) {
|
|
@@ -196,7 +196,7 @@ ${styleText('bold', 'backupId')}: ${styleText('cyan', opts.backupId)}`, {
|
|
|
196
196
|
const message = error instanceof Error ? error.message : String(error);
|
|
197
197
|
backupDownloadDebug(`Downloading dataset backup failed: ${message}`, error);
|
|
198
198
|
this.error(`Downloading dataset backup failed: ${message}`, {
|
|
199
|
-
exit:
|
|
199
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
200
200
|
});
|
|
201
201
|
}
|
|
202
202
|
docOutStream.end();
|
|
@@ -216,7 +216,7 @@ ${styleText('bold', 'backupId')}: ${styleText('cyan', opts.backupId)}`, {
|
|
|
216
216
|
const message = err instanceof Error ? err.message : String(err);
|
|
217
217
|
backupDownloadDebug(`Archiving backup failed: ${message}`, err);
|
|
218
218
|
this.error(`Archiving backup failed: ${message}`, {
|
|
219
|
-
exit:
|
|
219
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
220
220
|
});
|
|
221
221
|
}
|
|
222
222
|
progressSpinner.set({
|
|
@@ -248,7 +248,7 @@ ${styleText('bold', 'backupId')}: ${styleText('cyan', opts.backupId)}`, {
|
|
|
248
248
|
const err = validateDatasetName(datasetName);
|
|
249
249
|
if (err) {
|
|
250
250
|
this.error(err, {
|
|
251
|
-
exit:
|
|
251
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
252
252
|
});
|
|
253
253
|
}
|
|
254
254
|
const backupId = String(this.flags['backup-id'] || await this.promptForBackupId(projectId, datasetName));
|
|
@@ -302,7 +302,7 @@ ${styleText('bold', 'backupId')}: ${styleText('cyan', opts.backupId)}`, {
|
|
|
302
302
|
});
|
|
303
303
|
if (!response?.backups?.length) {
|
|
304
304
|
this.error('No backups found', {
|
|
305
|
-
exit:
|
|
305
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
306
306
|
});
|
|
307
307
|
}
|
|
308
308
|
const backupIdChoices = response.backups.map((backup)=>({
|
|
@@ -318,7 +318,7 @@ ${styleText('bold', 'backupId')}: ${styleText('cyan', opts.backupId)}`, {
|
|
|
318
318
|
const message = err instanceof Error ? err.message : String(err);
|
|
319
319
|
backupDownloadDebug(`Failed to fetch backups for dataset ${datasetName}: ${message}`, err);
|
|
320
320
|
this.error(`Failed to fetch backups for dataset ${datasetName}: ${message}`, {
|
|
321
|
-
exit:
|
|
321
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
322
322
|
});
|
|
323
323
|
}
|
|
324
324
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/backups/download.ts"],"sourcesContent":["import {createWriteStream} from 'node:fs'\nimport {access, mkdir, mkdtemp} from 'node:fs/promises'\nimport {tmpdir} from 'node:os'\nimport path from 'node:path'\nimport {finished} from 'node:stream/promises'\nimport {styleText} from 'node:util'\n\nimport {Args, Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand} from '@sanity/cli-core'\nimport {boxen, confirm, input, select} from '@sanity/cli-core/ux'\nimport {type DatasetsResponse} from '@sanity/client'\nimport pMap from 'p-map'\nimport prettyMs from 'pretty-ms'\n\nimport {archiveDir} from '../../actions/backup/archiveDir.js'\nimport {assertDatasetExists} from '../../actions/backup/assertDatasetExist.js'\nimport {backupDownloadDebug} from '../../actions/backup/backupDownloadDebug.js'\nimport {cleanupTmpDir} from '../../actions/backup/cleanupTmpDir.js'\nimport {downloadAsset} from '../../actions/backup/downloadAsset.js'\nimport {downloadDocument} from '../../actions/backup/downloadDocument.js'\nimport {type File, PaginatedGetBackupStream} from '../../actions/backup/fetchNextBackupPage.js'\nimport {newProgress} from '../../actions/backup/progressSpinner.js'\nimport {validateDatasetName} from '../../actions/dataset/validateDatasetName.js'\nimport {promptForDataset} from '../../prompts/promptForDataset.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {type BackupItem, listBackups} from '../../services/backup.js'\nimport {listDatasets} from '../../services/datasets.js'\nimport {formatCliErrorMessages} from '../../util/formatCliErrorMessages.js'\nimport {humanFileSize} from '../../util/humanFileSize.js'\nimport {isPathDirName} from '../../util/isPathDirName.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst DEFAULT_DOWNLOAD_CONCURRENCY = 10\nconst MAX_DOWNLOAD_CONCURRENCY = 24\n\ninterface DownloadBackupOptions {\n backupId: string\n concurrency: number\n datasetName: string\n outDir: string\n outFileName: string\n overwrite: boolean\n projectId: string\n}\n\nexport class DownloadBackupCommand extends SanityCommand<typeof DownloadBackupCommand> {\n static override args = {\n dataset: Args.string({\n description: 'Dataset name to download backup from',\n required: false,\n }),\n }\n\n static override description = 'Download a dataset backup to a local file'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Interactively download a backup',\n },\n {\n command: '<%= config.bin %> <%= command.id %> production --backup-id 2024-01-01-backup-1',\n description: 'Download a specific backup for the production dataset',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> production --backup-id 2024-01-01-backup-2 --out /path/to/file',\n description: 'Download backup to a specific file',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> production --backup-id 2024-01-01-backup-3 --out /path/to/file --overwrite',\n description: 'Download backup and overwrite existing file',\n },\n ]\n\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to download backup from',\n semantics: 'override',\n }),\n 'backup-id': Flags.string({\n description: 'The backup ID to download',\n }),\n concurrency: Flags.integer({\n default: DEFAULT_DOWNLOAD_CONCURRENCY,\n description: `Concurrent number of backup item downloads (max: ${MAX_DOWNLOAD_CONCURRENCY})`,\n }),\n out: Flags.string({\n description: 'The file or directory path the backup should download to',\n }),\n overwrite: Flags.boolean({\n default: false,\n description: 'Allows overwriting of existing backup file',\n }),\n }\n\n static override hiddenAliases: string[] = ['backup:download']\n\n public async run(): Promise<void> {\n const {args, flags} = await this.parse(DownloadBackupCommand)\n let {dataset} = args\n\n if (this.isUnattended()) {\n const errors: string[] = []\n\n if (!dataset) {\n errors.push('Dataset is required in unattended mode. Pass it as the `<dataset>` argument.')\n }\n if (!flags['backup-id']) {\n errors.push('Backup ID is required in unattended mode. Pass it with `--backup-id <id>`.')\n }\n\n if (errors.length > 0) {\n this.error(formatCliErrorMessages(errors), {exit: exitCodes.USAGE_ERROR})\n }\n }\n\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({\n requiredPermissions: [{grant: 'read', permission: 'sanity.project.datasets'}],\n }),\n })\n\n let datasets: DatasetsResponse\n\n try {\n datasets = await listDatasets(projectId)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n backupDownloadDebug(`Failed to list datasets: ${message}`, error)\n this.error(`Failed to list datasets: ${message}`, {exit: 1})\n }\n\n if (datasets.length === 0) {\n this.error('No datasets found in this project.', {exit: 1})\n }\n\n if (dataset) {\n assertDatasetExists(datasets, dataset, this.output)\n } else {\n dataset = await promptForDataset({allowCreation: false, datasets})\n }\n\n const opts = await this.prepareBackupOptions(projectId, dataset)\n const outFilePath = path.join(opts.outDir, opts.outFileName)\n\n this.log(\n boxen(\n `Downloading backup for:\n\n${styleText('bold', 'projectId')}: ${styleText('cyan', opts.projectId)}\n${styleText('bold', 'dataset')}: ${styleText('cyan', opts.datasetName)}\n${styleText('bold', 'backupId')}: ${styleText('cyan', opts.backupId)}`,\n {\n borderColor: 'cyan',\n borderStyle: 'round',\n padding: 1,\n },\n ),\n )\n this.log('')\n this.log(`Downloading backup to \"${styleText('cyan', outFilePath)}\"`)\n\n const start = Date.now()\n const progressSpinner = newProgress('Setting up backup environment...')\n\n // Create a unique temporary directory to store files before bundling them into the archive at outputPath.\n // Temporary directories are normally deleted at the end of backup process, any unexpected exit may leave them\n // behind, hence it is important to create a unique directory for each attempt.\n const tmpOutDir = await mkdtemp(path.join(tmpdir(), `sanity-backup-`))\n\n // Create required directories if they don't exist.\n for (const dir of [\n opts.outDir,\n path.join(tmpOutDir, 'images'),\n path.join(tmpOutDir, 'files'),\n ]) {\n await mkdir(dir, {recursive: true})\n }\n\n backupDownloadDebug('Writing to temporary directory %s', tmpOutDir)\n const tmpOutDocumentsFile = path.join(tmpOutDir, 'data.ndjson')\n\n const docOutStream = createWriteStream(tmpOutDocumentsFile)\n\n try {\n const backupFileStream = new PaginatedGetBackupStream(\n opts.projectId,\n opts.datasetName,\n opts.backupId,\n )\n\n const files: File[] = []\n let i = 0\n for await (const file of backupFileStream) {\n files.push(file)\n i++\n progressSpinner.set({\n current: i,\n step: `Reading backup files...`,\n total: backupFileStream.totalFiles,\n update: true,\n })\n }\n\n let totalItemsDownloaded = 0\n await pMap(\n files,\n async (file: File) => {\n if (file.type === 'file' || file.type === 'image') {\n await downloadAsset(file.url, file.name, file.type, tmpOutDir)\n } else {\n const doc = await downloadDocument(file.url)\n docOutStream.write(`${doc}\\n`)\n }\n\n totalItemsDownloaded += 1\n progressSpinner.set({\n current: totalItemsDownloaded,\n step: `Downloading documents and assets...`,\n total: backupFileStream.totalFiles,\n update: true,\n })\n },\n {concurrency: opts.concurrency},\n )\n } catch (error) {\n progressSpinner.fail()\n const message = error instanceof Error ? error.message : String(error)\n backupDownloadDebug(`Downloading dataset backup failed: ${message}`, error)\n this.error(`Downloading dataset backup failed: ${message}`, {exit: 1})\n }\n\n docOutStream.end()\n await finished(docOutStream)\n\n progressSpinner.set({step: `Archiving files into a tarball...`, update: true})\n try {\n await archiveDir(tmpOutDir, outFilePath, (processedBytes: number) => {\n progressSpinner.update({\n step: `Archiving files into a tarball, ${humanFileSize(processedBytes)} bytes written...`,\n })\n })\n } catch (err) {\n progressSpinner.fail()\n const message = err instanceof Error ? err.message : String(err)\n backupDownloadDebug(`Archiving backup failed: ${message}`, err)\n this.error(`Archiving backup failed: ${message}`, {exit: 1})\n }\n\n progressSpinner.set({\n step: `Cleaning up temporary files at ${styleText('cyan', `${tmpOutDir}`)}`,\n })\n await cleanupTmpDir(tmpOutDir)\n\n progressSpinner.set({\n step: `Backup download complete [${prettyMs(Date.now() - start)}]`,\n })\n progressSpinner.succeed()\n }\n\n private async getOutputPath(defaultOutFileName: string): Promise<string> {\n if (this.flags.out !== undefined) {\n // Rewrite the output path to an absolute path, if it is not already.\n return path.resolve(this.flags.out)\n }\n\n const workDir = process.cwd()\n const defaultPath = path.join(workDir, defaultOutFileName)\n if (this.isUnattended()) {\n return defaultPath\n }\n\n const inputResult = await input({\n default: defaultPath,\n message: 'Output path:',\n })\n return path.resolve(inputResult)\n }\n\n private async prepareBackupOptions(\n projectId: string,\n datasetName: string,\n ): Promise<DownloadBackupOptions> {\n const err = validateDatasetName(datasetName)\n if (err) {\n this.error(err, {exit: 1})\n }\n\n const backupId = String(\n this.flags['backup-id'] || (await this.promptForBackupId(projectId, datasetName)),\n )\n\n if (\n 'concurrency' in this.flags &&\n (this.flags.concurrency < 1 || this.flags.concurrency > MAX_DOWNLOAD_CONCURRENCY)\n ) {\n this.error(`\\`--concurrency\\` must be between 1 and ${MAX_DOWNLOAD_CONCURRENCY}.`, {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n\n const defaultOutFileName = `${datasetName}-backup-${backupId}.tar.gz`\n let out = await this.getOutputPath(defaultOutFileName)\n\n // If path is a directory name, then add a default file name to the path.\n if (isPathDirName(out)) {\n out = path.join(out, defaultOutFileName)\n }\n\n const exists = await access(out).then(\n () => true,\n () => false,\n )\n // If the file already exists, ask for confirmation if it should be overwritten.\n if (!this.flags.overwrite && exists) {\n if (this.isUnattended()) {\n this.error(`File \"${out}\" already exists. Pass \\`--overwrite\\` to replace it.`, {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n const shouldOverwrite = await confirm({\n default: false,\n message: `File \"${out}\" already exists, would you like to overwrite it?`,\n })\n\n // If the user does not want to overwrite the file, cancel the operation.\n if (!shouldOverwrite) {\n this.error('Operation cancelled.', {exit: exitCodes.USER_ABORT})\n }\n }\n\n return {\n backupId,\n concurrency: this.flags.concurrency || DEFAULT_DOWNLOAD_CONCURRENCY,\n datasetName,\n outDir: path.dirname(out),\n outFileName: path.basename(out),\n overwrite: this.flags.overwrite,\n projectId,\n }\n }\n\n private async promptForBackupId(projectId: string, datasetName: string): Promise<string> {\n const maxBackupIdsShown = 100\n\n try {\n const response = await listBackups({\n datasetName,\n limit: maxBackupIdsShown,\n projectId,\n })\n\n if (!response?.backups?.length) {\n this.error('No backups found', {exit: 1})\n }\n\n const backupIdChoices = response.backups.map((backup: BackupItem) => ({\n name: backup.id,\n value: backup.id,\n }))\n\n const hint =\n backupIdChoices.length === maxBackupIdsShown\n ? ` (only last ${maxBackupIdsShown} shown)`\n : ''\n\n return select({\n choices: backupIdChoices,\n message: `Select backup ID to use${hint}`,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n backupDownloadDebug(`Failed to fetch backups for dataset ${datasetName}: ${message}`, err)\n this.error(`Failed to fetch backups for dataset ${datasetName}: ${message}`, {exit: 1})\n }\n }\n}\n"],"names":["createWriteStream","access","mkdir","mkdtemp","tmpdir","path","finished","styleText","Args","Flags","exitCodes","SanityCommand","boxen","confirm","input","select","pMap","prettyMs","archiveDir","assertDatasetExists","backupDownloadDebug","cleanupTmpDir","downloadAsset","downloadDocument","PaginatedGetBackupStream","newProgress","validateDatasetName","promptForDataset","promptForProject","listBackups","listDatasets","formatCliErrorMessages","humanFileSize","isPathDirName","getProjectIdFlag","DEFAULT_DOWNLOAD_CONCURRENCY","MAX_DOWNLOAD_CONCURRENCY","DownloadBackupCommand","args","dataset","string","description","required","examples","command","flags","semantics","concurrency","integer","default","out","overwrite","boolean","hiddenAliases","run","parse","isUnattended","errors","push","length","error","exit","USAGE_ERROR","projectId","getProjectId","fallback","requiredPermissions","grant","permission","datasets","message","Error","String","output","allowCreation","opts","prepareBackupOptions","outFilePath","join","outDir","outFileName","log","datasetName","backupId","borderColor","borderStyle","padding","start","Date","now","progressSpinner","tmpOutDir","dir","recursive","tmpOutDocumentsFile","docOutStream","backupFileStream","files","i","file","set","current","step","total","totalFiles","update","totalItemsDownloaded","type","url","name","doc","write","fail","end","processedBytes","err","succeed","getOutputPath","defaultOutFileName","undefined","resolve","workDir","process","cwd","defaultPath","inputResult","promptForBackupId","exists","then","shouldOverwrite","USER_ABORT","dirname","basename","maxBackupIdsShown","response","limit","backups","backupIdChoices","map","backup","id","value","hint","choices"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/backups/download.ts"],"sourcesContent":["import {createWriteStream} from 'node:fs'\nimport {access, mkdir, mkdtemp} from 'node:fs/promises'\nimport {tmpdir} from 'node:os'\nimport path from 'node:path'\nimport {finished} from 'node:stream/promises'\nimport {styleText} from 'node:util'\n\nimport {Args, Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand} from '@sanity/cli-core'\nimport {boxen, confirm, input, select} from '@sanity/cli-core/ux'\nimport {type DatasetsResponse} from '@sanity/client'\nimport pMap from 'p-map'\nimport prettyMs from 'pretty-ms'\n\nimport {archiveDir} from '../../actions/backup/archiveDir.js'\nimport {assertDatasetExists} from '../../actions/backup/assertDatasetExist.js'\nimport {backupDownloadDebug} from '../../actions/backup/backupDownloadDebug.js'\nimport {cleanupTmpDir} from '../../actions/backup/cleanupTmpDir.js'\nimport {downloadAsset} from '../../actions/backup/downloadAsset.js'\nimport {downloadDocument} from '../../actions/backup/downloadDocument.js'\nimport {type File, PaginatedGetBackupStream} from '../../actions/backup/fetchNextBackupPage.js'\nimport {newProgress} from '../../actions/backup/progressSpinner.js'\nimport {validateDatasetName} from '../../actions/dataset/validateDatasetName.js'\nimport {promptForDataset} from '../../prompts/promptForDataset.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {type BackupItem, listBackups} from '../../services/backup.js'\nimport {listDatasets} from '../../services/datasets.js'\nimport {formatCliErrorMessages} from '../../util/formatCliErrorMessages.js'\nimport {humanFileSize} from '../../util/humanFileSize.js'\nimport {isPathDirName} from '../../util/isPathDirName.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nconst DEFAULT_DOWNLOAD_CONCURRENCY = 10\nconst MAX_DOWNLOAD_CONCURRENCY = 24\n\ninterface DownloadBackupOptions {\n backupId: string\n concurrency: number\n datasetName: string\n outDir: string\n outFileName: string\n overwrite: boolean\n projectId: string\n}\n\nexport class DownloadBackupCommand extends SanityCommand<typeof DownloadBackupCommand> {\n static override args = {\n dataset: Args.string({\n description: 'Dataset name to download backup from',\n required: false,\n }),\n }\n\n static override description = 'Download a dataset backup to a local file'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Interactively download a backup',\n },\n {\n command: '<%= config.bin %> <%= command.id %> production --backup-id 2024-01-01-backup-1',\n description: 'Download a specific backup for the production dataset',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> production --backup-id 2024-01-01-backup-2 --out /path/to/file',\n description: 'Download backup to a specific file',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> production --backup-id 2024-01-01-backup-3 --out /path/to/file --overwrite',\n description: 'Download backup and overwrite existing file',\n },\n ]\n\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to download backup from',\n semantics: 'override',\n }),\n 'backup-id': Flags.string({\n description: 'The backup ID to download',\n }),\n concurrency: Flags.integer({\n default: DEFAULT_DOWNLOAD_CONCURRENCY,\n description: `Concurrent number of backup item downloads (max: ${MAX_DOWNLOAD_CONCURRENCY})`,\n }),\n out: Flags.string({\n description: 'The file or directory path the backup should download to',\n }),\n overwrite: Flags.boolean({\n default: false,\n description: 'Allows overwriting of existing backup file',\n }),\n }\n\n static override hiddenAliases: string[] = ['backup:download']\n\n public async run(): Promise<void> {\n const {args, flags} = await this.parse(DownloadBackupCommand)\n let {dataset} = args\n\n if (this.isUnattended()) {\n const errors: string[] = []\n\n if (!dataset) {\n errors.push('Dataset is required in unattended mode. Pass it as the `<dataset>` argument.')\n }\n if (!flags['backup-id']) {\n errors.push('Backup ID is required in unattended mode. Pass it with `--backup-id <id>`.')\n }\n\n if (errors.length > 0) {\n this.error(formatCliErrorMessages(errors), {exit: exitCodes.USAGE_ERROR})\n }\n }\n\n const projectId = await this.getProjectId({\n fallback: () =>\n promptForProject({\n requiredPermissions: [{grant: 'read', permission: 'sanity.project.datasets'}],\n }),\n })\n\n let datasets: DatasetsResponse\n\n try {\n datasets = await listDatasets(projectId)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n backupDownloadDebug(`Failed to list datasets: ${message}`, error)\n this.error(`Failed to list datasets: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n if (datasets.length === 0) {\n this.error('No datasets found in this project.', {exit: exitCodes.RUNTIME_ERROR})\n }\n\n if (dataset) {\n assertDatasetExists(datasets, dataset, this.output)\n } else {\n dataset = await promptForDataset({allowCreation: false, datasets})\n }\n\n const opts = await this.prepareBackupOptions(projectId, dataset)\n const outFilePath = path.join(opts.outDir, opts.outFileName)\n\n this.log(\n boxen(\n `Downloading backup for:\n\n${styleText('bold', 'projectId')}: ${styleText('cyan', opts.projectId)}\n${styleText('bold', 'dataset')}: ${styleText('cyan', opts.datasetName)}\n${styleText('bold', 'backupId')}: ${styleText('cyan', opts.backupId)}`,\n {\n borderColor: 'cyan',\n borderStyle: 'round',\n padding: 1,\n },\n ),\n )\n this.log('')\n this.log(`Downloading backup to \"${styleText('cyan', outFilePath)}\"`)\n\n const start = Date.now()\n const progressSpinner = newProgress('Setting up backup environment...')\n\n // Create a unique temporary directory to store files before bundling them into the archive at outputPath.\n // Temporary directories are normally deleted at the end of backup process, any unexpected exit may leave them\n // behind, hence it is important to create a unique directory for each attempt.\n const tmpOutDir = await mkdtemp(path.join(tmpdir(), `sanity-backup-`))\n\n // Create required directories if they don't exist.\n for (const dir of [\n opts.outDir,\n path.join(tmpOutDir, 'images'),\n path.join(tmpOutDir, 'files'),\n ]) {\n await mkdir(dir, {recursive: true})\n }\n\n backupDownloadDebug('Writing to temporary directory %s', tmpOutDir)\n const tmpOutDocumentsFile = path.join(tmpOutDir, 'data.ndjson')\n\n const docOutStream = createWriteStream(tmpOutDocumentsFile)\n\n try {\n const backupFileStream = new PaginatedGetBackupStream(\n opts.projectId,\n opts.datasetName,\n opts.backupId,\n )\n\n const files: File[] = []\n let i = 0\n for await (const file of backupFileStream) {\n files.push(file)\n i++\n progressSpinner.set({\n current: i,\n step: `Reading backup files...`,\n total: backupFileStream.totalFiles,\n update: true,\n })\n }\n\n let totalItemsDownloaded = 0\n await pMap(\n files,\n async (file: File) => {\n if (file.type === 'file' || file.type === 'image') {\n await downloadAsset(file.url, file.name, file.type, tmpOutDir)\n } else {\n const doc = await downloadDocument(file.url)\n docOutStream.write(`${doc}\\n`)\n }\n\n totalItemsDownloaded += 1\n progressSpinner.set({\n current: totalItemsDownloaded,\n step: `Downloading documents and assets...`,\n total: backupFileStream.totalFiles,\n update: true,\n })\n },\n {concurrency: opts.concurrency},\n )\n } catch (error) {\n progressSpinner.fail()\n const message = error instanceof Error ? error.message : String(error)\n backupDownloadDebug(`Downloading dataset backup failed: ${message}`, error)\n this.error(`Downloading dataset backup failed: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n docOutStream.end()\n await finished(docOutStream)\n\n progressSpinner.set({step: `Archiving files into a tarball...`, update: true})\n try {\n await archiveDir(tmpOutDir, outFilePath, (processedBytes: number) => {\n progressSpinner.update({\n step: `Archiving files into a tarball, ${humanFileSize(processedBytes)} bytes written...`,\n })\n })\n } catch (err) {\n progressSpinner.fail()\n const message = err instanceof Error ? err.message : String(err)\n backupDownloadDebug(`Archiving backup failed: ${message}`, err)\n this.error(`Archiving backup failed: ${message}`, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n progressSpinner.set({\n step: `Cleaning up temporary files at ${styleText('cyan', `${tmpOutDir}`)}`,\n })\n await cleanupTmpDir(tmpOutDir)\n\n progressSpinner.set({\n step: `Backup download complete [${prettyMs(Date.now() - start)}]`,\n })\n progressSpinner.succeed()\n }\n\n private async getOutputPath(defaultOutFileName: string): Promise<string> {\n if (this.flags.out !== undefined) {\n // Rewrite the output path to an absolute path, if it is not already.\n return path.resolve(this.flags.out)\n }\n\n const workDir = process.cwd()\n const defaultPath = path.join(workDir, defaultOutFileName)\n if (this.isUnattended()) {\n return defaultPath\n }\n\n const inputResult = await input({\n default: defaultPath,\n message: 'Output path:',\n })\n return path.resolve(inputResult)\n }\n\n private async prepareBackupOptions(\n projectId: string,\n datasetName: string,\n ): Promise<DownloadBackupOptions> {\n const err = validateDatasetName(datasetName)\n if (err) {\n this.error(err, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n const backupId = String(\n this.flags['backup-id'] || (await this.promptForBackupId(projectId, datasetName)),\n )\n\n if (\n 'concurrency' in this.flags &&\n (this.flags.concurrency < 1 || this.flags.concurrency > MAX_DOWNLOAD_CONCURRENCY)\n ) {\n this.error(`\\`--concurrency\\` must be between 1 and ${MAX_DOWNLOAD_CONCURRENCY}.`, {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n\n const defaultOutFileName = `${datasetName}-backup-${backupId}.tar.gz`\n let out = await this.getOutputPath(defaultOutFileName)\n\n // If path is a directory name, then add a default file name to the path.\n if (isPathDirName(out)) {\n out = path.join(out, defaultOutFileName)\n }\n\n const exists = await access(out).then(\n () => true,\n () => false,\n )\n // If the file already exists, ask for confirmation if it should be overwritten.\n if (!this.flags.overwrite && exists) {\n if (this.isUnattended()) {\n this.error(`File \"${out}\" already exists. Pass \\`--overwrite\\` to replace it.`, {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n const shouldOverwrite = await confirm({\n default: false,\n message: `File \"${out}\" already exists, would you like to overwrite it?`,\n })\n\n // If the user does not want to overwrite the file, cancel the operation.\n if (!shouldOverwrite) {\n this.error('Operation cancelled.', {exit: exitCodes.USER_ABORT})\n }\n }\n\n return {\n backupId,\n concurrency: this.flags.concurrency || DEFAULT_DOWNLOAD_CONCURRENCY,\n datasetName,\n outDir: path.dirname(out),\n outFileName: path.basename(out),\n overwrite: this.flags.overwrite,\n projectId,\n }\n }\n\n private async promptForBackupId(projectId: string, datasetName: string): Promise<string> {\n const maxBackupIdsShown = 100\n\n try {\n const response = await listBackups({\n datasetName,\n limit: maxBackupIdsShown,\n projectId,\n })\n\n if (!response?.backups?.length) {\n this.error('No backups found', {exit: exitCodes.RUNTIME_ERROR})\n }\n\n const backupIdChoices = response.backups.map((backup: BackupItem) => ({\n name: backup.id,\n value: backup.id,\n }))\n\n const hint =\n backupIdChoices.length === maxBackupIdsShown\n ? ` (only last ${maxBackupIdsShown} shown)`\n : ''\n\n return select({\n choices: backupIdChoices,\n message: `Select backup ID to use${hint}`,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n backupDownloadDebug(`Failed to fetch backups for dataset ${datasetName}: ${message}`, err)\n this.error(`Failed to fetch backups for dataset ${datasetName}: ${message}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n}\n"],"names":["createWriteStream","access","mkdir","mkdtemp","tmpdir","path","finished","styleText","Args","Flags","exitCodes","SanityCommand","boxen","confirm","input","select","pMap","prettyMs","archiveDir","assertDatasetExists","backupDownloadDebug","cleanupTmpDir","downloadAsset","downloadDocument","PaginatedGetBackupStream","newProgress","validateDatasetName","promptForDataset","promptForProject","listBackups","listDatasets","formatCliErrorMessages","humanFileSize","isPathDirName","getProjectIdFlag","DEFAULT_DOWNLOAD_CONCURRENCY","MAX_DOWNLOAD_CONCURRENCY","DownloadBackupCommand","args","dataset","string","description","required","examples","command","flags","semantics","concurrency","integer","default","out","overwrite","boolean","hiddenAliases","run","parse","isUnattended","errors","push","length","error","exit","USAGE_ERROR","projectId","getProjectId","fallback","requiredPermissions","grant","permission","datasets","message","Error","String","RUNTIME_ERROR","output","allowCreation","opts","prepareBackupOptions","outFilePath","join","outDir","outFileName","log","datasetName","backupId","borderColor","borderStyle","padding","start","Date","now","progressSpinner","tmpOutDir","dir","recursive","tmpOutDocumentsFile","docOutStream","backupFileStream","files","i","file","set","current","step","total","totalFiles","update","totalItemsDownloaded","type","url","name","doc","write","fail","end","processedBytes","err","succeed","getOutputPath","defaultOutFileName","undefined","resolve","workDir","process","cwd","defaultPath","inputResult","promptForBackupId","exists","then","shouldOverwrite","USER_ABORT","dirname","basename","maxBackupIdsShown","response","limit","backups","backupIdChoices","map","backup","id","value","hint","choices"],"mappings":"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"}
|
|
@@ -60,13 +60,13 @@ export class EnableBackupCommand extends SanityCommand {
|
|
|
60
60
|
const message = error instanceof Error ? error.message : String(error);
|
|
61
61
|
enableBackupDebug(`Failed to list datasets: ${message}`, error);
|
|
62
62
|
this.error(`Failed to list datasets: ${message}`, {
|
|
63
|
-
exit:
|
|
63
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
const hasProduction = datasets.some((dataset)=>dataset.name === 'production');
|
|
67
67
|
if (datasets.length === 0) {
|
|
68
68
|
this.error('No datasets found in this project.', {
|
|
69
|
-
exit:
|
|
69
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
if (dataset) {
|
|
@@ -94,7 +94,7 @@ export class EnableBackupCommand extends SanityCommand {
|
|
|
94
94
|
const message = error instanceof Error ? error.message : String(error);
|
|
95
95
|
enableBackupDebug(`Failed to create dataset ${newDatasetName}: ${message}`, error);
|
|
96
96
|
this.error(`Failed to create dataset ${newDatasetName}: ${message}`, {
|
|
97
|
-
exit:
|
|
97
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
}
|
|
@@ -112,7 +112,7 @@ export class EnableBackupCommand extends SanityCommand {
|
|
|
112
112
|
const message = error instanceof Error ? error.message : String(error);
|
|
113
113
|
enableBackupDebug(`Failed to enable backup for dataset`, error);
|
|
114
114
|
this.error(`Enabling dataset backup failed: ${message}`, {
|
|
115
|
-
exit:
|
|
115
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
118
|
}
|