@sanity/runtime-cli 12.3.0 → 13.0.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 +190 -76
- package/dist/actions/blueprints/assets.d.ts +3 -1
- package/dist/actions/blueprints/assets.js +15 -5
- package/dist/actions/blueprints/blueprint.d.ts +2 -1
- package/dist/actions/blueprints/blueprint.js +3 -1
- package/dist/actions/blueprints/config.d.ts +5 -2
- package/dist/actions/blueprints/config.js +4 -4
- package/dist/actions/blueprints/logs-streaming.d.ts +4 -2
- package/dist/actions/blueprints/logs-streaming.js +5 -2
- package/dist/actions/blueprints/logs.d.ts +2 -1
- package/dist/actions/blueprints/logs.js +4 -2
- package/dist/actions/blueprints/resources.d.ts +2 -1
- package/dist/actions/blueprints/resources.js +2 -2
- package/dist/actions/blueprints/stacks.d.ts +12 -6
- package/dist/actions/blueprints/stacks.js +18 -11
- package/dist/actions/functions/dev.d.ts +2 -1
- package/dist/actions/functions/dev.js +2 -2
- package/dist/actions/functions/env/list.d.ts +2 -1
- package/dist/actions/functions/env/list.js +4 -2
- package/dist/actions/functions/env/remove.d.ts +2 -1
- package/dist/actions/functions/env/remove.js +4 -2
- package/dist/actions/functions/env/update.d.ts +2 -1
- package/dist/actions/functions/env/update.js +4 -2
- package/dist/actions/functions/logs.d.ts +4 -3
- package/dist/actions/functions/logs.js +10 -6
- package/dist/actions/node.d.ts +2 -1
- package/dist/actions/node.js +2 -2
- package/dist/actions/sanity/examples.d.ts +5 -2
- package/dist/actions/sanity/examples.js +6 -6
- package/dist/actions/sanity/projects.d.ts +7 -3
- package/dist/actions/sanity/projects.js +11 -7
- package/dist/baseCommands.d.ts +47 -7
- package/dist/baseCommands.js +90 -12
- package/dist/commands/blueprints/add.d.ts +3 -2
- package/dist/commands/blueprints/add.js +14 -10
- package/dist/commands/blueprints/config.d.ts +3 -2
- package/dist/commands/blueprints/config.js +12 -6
- package/dist/commands/blueprints/deploy.d.ts +3 -2
- package/dist/commands/blueprints/deploy.js +10 -4
- package/dist/commands/blueprints/destroy.d.ts +3 -2
- package/dist/commands/blueprints/destroy.js +10 -4
- package/dist/commands/blueprints/doctor.d.ts +6 -4
- package/dist/commands/blueprints/doctor.js +17 -14
- package/dist/commands/blueprints/info.d.ts +3 -2
- package/dist/commands/blueprints/info.js +11 -5
- package/dist/commands/blueprints/init.d.ts +3 -2
- package/dist/commands/blueprints/init.js +26 -20
- package/dist/commands/blueprints/logs.d.ts +3 -2
- package/dist/commands/blueprints/logs.js +10 -4
- package/dist/commands/blueprints/plan.d.ts +3 -2
- package/dist/commands/blueprints/plan.js +8 -4
- package/dist/commands/blueprints/stacks.d.ts +3 -2
- package/dist/commands/blueprints/stacks.js +10 -6
- package/dist/commands/functions/add.d.ts +3 -2
- package/dist/commands/functions/add.js +10 -4
- package/dist/commands/functions/dev.d.ts +3 -2
- package/dist/commands/functions/dev.js +16 -5
- package/dist/commands/functions/env/add.d.ts +4 -3
- package/dist/commands/functions/env/add.js +8 -4
- package/dist/commands/functions/env/list.d.ts +4 -3
- package/dist/commands/functions/env/list.js +8 -4
- package/dist/commands/functions/env/remove.d.ts +4 -3
- package/dist/commands/functions/env/remove.js +8 -4
- package/dist/commands/functions/logs.d.ts +5 -4
- package/dist/commands/functions/logs.js +11 -5
- package/dist/commands/functions/test.d.ts +5 -4
- package/dist/commands/functions/test.js +13 -6
- package/dist/cores/blueprints/config.d.ts +2 -5
- package/dist/cores/blueprints/config.js +9 -9
- package/dist/cores/blueprints/deploy.js +14 -17
- package/dist/cores/blueprints/destroy.d.ts +2 -5
- package/dist/cores/blueprints/destroy.js +6 -6
- package/dist/cores/blueprints/doctor.js +32 -29
- package/dist/cores/blueprints/info.js +5 -5
- package/dist/cores/blueprints/init.d.ts +3 -3
- package/dist/cores/blueprints/init.js +15 -8
- package/dist/cores/blueprints/logs.js +6 -7
- package/dist/cores/blueprints/plan.js +1 -0
- package/dist/cores/blueprints/stacks.d.ts +2 -5
- package/dist/cores/blueprints/stacks.js +4 -4
- package/dist/cores/functions/add.js +8 -3
- package/dist/cores/functions/dev.js +2 -2
- package/dist/cores/functions/env/add.js +3 -4
- package/dist/cores/functions/env/list.js +3 -4
- package/dist/cores/functions/env/remove.js +3 -4
- package/dist/cores/functions/index.d.ts +3 -9
- package/dist/cores/functions/logs.d.ts +3 -1
- package/dist/cores/functions/logs.js +19 -11
- package/dist/cores/functions/test.d.ts +3 -1
- package/dist/cores/functions/test.js +18 -10
- package/dist/cores/index.d.ts +4 -7
- package/dist/cores/index.js +3 -3
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/server/app.d.ts +2 -1
- package/dist/server/app.js +4 -4
- package/dist/server/handlers/invoke.d.ts +2 -1
- package/dist/server/handlers/invoke.js +2 -2
- package/dist/server/static/components/app.css +0 -116
- package/dist/server/static/components/clear-button.js +1 -1
- package/dist/server/static/components/console-panel.js +27 -6
- package/dist/server/static/components/fetch-button.js +1 -1
- package/dist/server/static/components/filter-api-version.js +39 -3
- package/dist/server/static/components/filter-document-id.js +39 -3
- package/dist/server/static/components/filter-with-token.js +27 -4
- package/dist/server/static/components/filters.js +127 -62
- package/dist/server/static/components/function-list.js +33 -13
- package/dist/server/static/components/network-spinner.js +6 -4
- package/dist/server/static/components/payload-panel.js +46 -24
- package/dist/server/static/components/response-panel.js +33 -6
- package/dist/server/static/components/rule-panel.js +13 -4
- package/dist/server/static/components/run-panel.js +14 -7
- package/dist/server/static/components/select-dropdown.js +34 -5
- package/dist/server/static/components/shared-styles.js +31 -0
- package/dist/server/static/components/toggle-switch.js +11 -2
- package/dist/utils/display/blueprints-formatting.d.ts +2 -2
- package/dist/utils/display/blueprints-formatting.js +31 -26
- package/dist/utils/display/prompt.d.ts +5 -2
- package/dist/utils/display/prompt.js +5 -4
- package/dist/utils/find-function.d.ts +4 -0
- package/dist/utils/find-function.js +6 -0
- package/dist/utils/functions/fetch-document.d.ts +3 -2
- package/dist/utils/functions/fetch-document.js +7 -6
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.js +61 -0
- package/dist/utils/other/github.d.ts +2 -1
- package/dist/utils/other/github.js +4 -2
- package/dist/utils/other/npmjs.d.ts +2 -1
- package/dist/utils/other/npmjs.js +4 -2
- package/dist/utils/traced-fetch.d.ts +35 -0
- package/dist/utils/traced-fetch.js +238 -0
- package/dist/utils/validated-token.d.ts +3 -2
- package/dist/utils/validated-token.js +6 -4
- package/oclif.manifest.json +455 -75
- package/package.json +14 -6
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
3
|
+
import { env } from 'node:process';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
let defaultMaxLength = env.SANITY_TRACE_MAX_LENGTH
|
|
6
|
+
? Number.parseInt(env.SANITY_TRACE_MAX_LENGTH, 10)
|
|
7
|
+
: 500;
|
|
8
|
+
if (Number.isNaN(defaultMaxLength))
|
|
9
|
+
defaultMaxLength = 500;
|
|
10
|
+
const DEFAULT_OPTIONS = {
|
|
11
|
+
logRequestHeaders: true,
|
|
12
|
+
logResponseHeaders: true,
|
|
13
|
+
logRequestBody: true,
|
|
14
|
+
logResponseBody: true,
|
|
15
|
+
maxBodyLength: defaultMaxLength,
|
|
16
|
+
redactedHeaders: [
|
|
17
|
+
'authorization',
|
|
18
|
+
'cookie',
|
|
19
|
+
'set-cookie',
|
|
20
|
+
'x-api-key',
|
|
21
|
+
'x-auth-token',
|
|
22
|
+
'proxy-authorization',
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Content types that are safe to log as text
|
|
27
|
+
*/
|
|
28
|
+
const SAFE_CONTENT_TYPES = [
|
|
29
|
+
'application/json',
|
|
30
|
+
'application/xml',
|
|
31
|
+
'application/x-www-form-urlencoded',
|
|
32
|
+
'text/',
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Check if content type is safe to log
|
|
36
|
+
*/
|
|
37
|
+
function isSafeContentType(contentType) {
|
|
38
|
+
const lower = contentType.toLowerCase();
|
|
39
|
+
return SAFE_CONTENT_TYPES.some((safe) => lower.startsWith(safe));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Redact sensitive headers
|
|
43
|
+
*/
|
|
44
|
+
function redactHeaders(headers, redactedKeys) {
|
|
45
|
+
if (!headers)
|
|
46
|
+
return {};
|
|
47
|
+
const result = {};
|
|
48
|
+
const redactedLower = redactedKeys.map((k) => k.toLowerCase());
|
|
49
|
+
// Handle Headers object
|
|
50
|
+
if (headers instanceof Headers) {
|
|
51
|
+
headers.forEach((value, key) => {
|
|
52
|
+
result[key] = redactedLower.includes(key.toLowerCase()) ? '[REDACTED]' : value;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Handle array of tuples
|
|
56
|
+
else if (Array.isArray(headers)) {
|
|
57
|
+
for (const [key, value] of headers) {
|
|
58
|
+
result[key] = redactedLower.includes(key.toLowerCase()) ? '[REDACTED]' : value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Handle plain object
|
|
62
|
+
else {
|
|
63
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
64
|
+
result[key] = redactedLower.includes(key.toLowerCase()) ? '[REDACTED]' : value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a preview of body content
|
|
71
|
+
*/
|
|
72
|
+
function createBodyPreview(body, maxLength, contentType) {
|
|
73
|
+
if (body === null) {
|
|
74
|
+
return '[empty]';
|
|
75
|
+
}
|
|
76
|
+
// Handle string bodies
|
|
77
|
+
if (typeof body === 'string') {
|
|
78
|
+
if (!contentType || isSafeContentType(contentType)) {
|
|
79
|
+
if (body.length <= maxLength) {
|
|
80
|
+
return body;
|
|
81
|
+
}
|
|
82
|
+
return `${body.slice(0, maxLength)}... [truncated]`;
|
|
83
|
+
}
|
|
84
|
+
return `[non-text content, ${body.length} chars]`;
|
|
85
|
+
}
|
|
86
|
+
// Handle Buffer
|
|
87
|
+
if (Buffer.isBuffer(body)) {
|
|
88
|
+
if (contentType && isSafeContentType(contentType)) {
|
|
89
|
+
const text = body.toString('utf8');
|
|
90
|
+
if (text.length <= maxLength) {
|
|
91
|
+
return text;
|
|
92
|
+
}
|
|
93
|
+
return `${text.slice(0, maxLength)}... [truncated]`;
|
|
94
|
+
}
|
|
95
|
+
return `[${contentType} binary, ${body.length} bytes]`;
|
|
96
|
+
}
|
|
97
|
+
return '[unknown content type]';
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Extract body from RequestInit if present
|
|
101
|
+
*/
|
|
102
|
+
async function extractRequestBody(init) {
|
|
103
|
+
if (!init?.body) {
|
|
104
|
+
return { body: null, contentType: null };
|
|
105
|
+
}
|
|
106
|
+
const contentType = init.headers ? new Headers(init.headers).get('content-type') : null;
|
|
107
|
+
// init.body in node can be string, buffer, blob, formdata, url search params or a readable stream, so handle each case.
|
|
108
|
+
if (typeof init.body === 'string') {
|
|
109
|
+
return { body: init.body, contentType };
|
|
110
|
+
}
|
|
111
|
+
if (init.body instanceof FormData) {
|
|
112
|
+
return { body: '[FormData]', contentType };
|
|
113
|
+
}
|
|
114
|
+
if (init.body instanceof URLSearchParams) {
|
|
115
|
+
return { body: init.body.toString(), contentType };
|
|
116
|
+
}
|
|
117
|
+
if (init.body instanceof Blob) {
|
|
118
|
+
return { body: '[FormData]', contentType: contentType || init.body.type };
|
|
119
|
+
}
|
|
120
|
+
if (init.body instanceof ReadableStream) {
|
|
121
|
+
return { body: '[ReadableStream]', contentType };
|
|
122
|
+
}
|
|
123
|
+
if (Buffer.isBuffer(init.body)) {
|
|
124
|
+
return { body: init.body, contentType };
|
|
125
|
+
}
|
|
126
|
+
return { body: '[unknown body type]', contentType };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Extract body from Response
|
|
130
|
+
*/
|
|
131
|
+
async function extractResponseBody(response, maxLength) {
|
|
132
|
+
const contentType = response.headers.get('content-type');
|
|
133
|
+
// Don't try to read body for streaming responses (event-stream)
|
|
134
|
+
if (contentType?.includes('text/event-stream')) {
|
|
135
|
+
return '[streaming response]';
|
|
136
|
+
}
|
|
137
|
+
// Clone response to avoid consuming it
|
|
138
|
+
const cloned = response.clone();
|
|
139
|
+
try {
|
|
140
|
+
// Try to read as text
|
|
141
|
+
const text = await cloned.text();
|
|
142
|
+
if (!contentType || isSafeContentType(contentType)) {
|
|
143
|
+
if (text.length <= maxLength) {
|
|
144
|
+
return text;
|
|
145
|
+
}
|
|
146
|
+
return `${text.slice(0, maxLength)}... [truncated]`;
|
|
147
|
+
}
|
|
148
|
+
return `[binary content, ${text.length} bytes]`;
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
return `[unable to read body: ${error instanceof Error ? error.message : String(error)}]`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Creates a traced fetch function with HTTP request/response introspection.
|
|
156
|
+
* Logs HTTP details to the provided logger when set to TRACE log level.
|
|
157
|
+
* Includes request timing, headers, and optional body previews.
|
|
158
|
+
* Headers containing sensitive data are redacted by default.
|
|
159
|
+
* @param logger - Logger instance from Logger() factory
|
|
160
|
+
* @param options - Configuration options for logging behavior
|
|
161
|
+
* @returns A fetch-compatible function with tracing capabilities
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* const logger = Logger(console.log, {trace: true})
|
|
166
|
+
* const tracedFetch = createTracedFetch(logger)
|
|
167
|
+
* const response = await tracedFetch('https://api.example.com/data')
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
export function createTracedFetch(logger, options) {
|
|
171
|
+
const opts = {
|
|
172
|
+
...DEFAULT_OPTIONS,
|
|
173
|
+
...options,
|
|
174
|
+
};
|
|
175
|
+
return async (input, init) => {
|
|
176
|
+
const requestId = randomUUID().slice(0, 3);
|
|
177
|
+
const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
|
|
178
|
+
const method = init?.method || 'GET';
|
|
179
|
+
const startTime = performance.now();
|
|
180
|
+
function trace(type, msgTemplate, ...args) {
|
|
181
|
+
const arrow = type === 'req' ? '→' : type === 'res' ? '←' : chalk.red('✗');
|
|
182
|
+
logger.trace(`${chalk.dim('[%s]')} HTTP ${arrow} ${msgTemplate}`, requestId, ...args);
|
|
183
|
+
}
|
|
184
|
+
// Log request URL
|
|
185
|
+
trace('req', '%s %s', method, url);
|
|
186
|
+
// Log request headers
|
|
187
|
+
if (opts.logRequestHeaders && init?.headers) {
|
|
188
|
+
const redacted = redactHeaders(init.headers, opts.redactedHeaders);
|
|
189
|
+
trace('req', 'Headers: %j', redacted);
|
|
190
|
+
}
|
|
191
|
+
// Log request body
|
|
192
|
+
if (opts.logRequestBody && init?.body) {
|
|
193
|
+
try {
|
|
194
|
+
const { body, contentType } = await extractRequestBody(init);
|
|
195
|
+
const preview = createBodyPreview(body, opts.maxBodyLength, contentType);
|
|
196
|
+
trace('req', 'Body: %s', preview);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
trace('err', 'Body: [error reading body: %s]', error instanceof Error ? error.message : String(error));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
// Make the actual fetch call
|
|
204
|
+
const response = await fetch(input, init);
|
|
205
|
+
const endTime = performance.now();
|
|
206
|
+
const duration = Math.round(endTime - startTime);
|
|
207
|
+
// Log response
|
|
208
|
+
trace('res', '%d %s (%dms)', response.status, response.statusText, duration);
|
|
209
|
+
// Log response headers
|
|
210
|
+
if (opts.logResponseHeaders) {
|
|
211
|
+
const headers = {};
|
|
212
|
+
response.headers.forEach((value, key) => {
|
|
213
|
+
headers[key] = value;
|
|
214
|
+
});
|
|
215
|
+
trace('res', 'Headers: %j', headers);
|
|
216
|
+
}
|
|
217
|
+
// Log response body
|
|
218
|
+
if (opts.logResponseBody) {
|
|
219
|
+
try {
|
|
220
|
+
const bodyPreview = await extractResponseBody(response, opts.maxBodyLength);
|
|
221
|
+
trace('res', 'Body: %s', bodyPreview);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
trace('err', 'Body: [error reading body: %s]', error instanceof Error ? error.message : String(error));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return response;
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
const endTime = performance.now();
|
|
231
|
+
const duration = Math.round(endTime - startTime);
|
|
232
|
+
// Log error
|
|
233
|
+
trace('err', '%s %s (%dms) - %s', method, url, duration, error instanceof Error ? error.message : String(error));
|
|
234
|
+
// Re-throw the error
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { Logger } from './logger.js';
|
|
1
2
|
import type { Result } from './types.js';
|
|
2
|
-
export declare function validToken(maybeToken?: string): Promise<string>;
|
|
3
|
-
export declare function validTokenOrErrorMessage(maybeToken?: string): Promise<Result<string, {
|
|
3
|
+
export declare function validToken(logger: ReturnType<typeof Logger>, maybeToken?: string): Promise<string>;
|
|
4
|
+
export declare function validTokenOrErrorMessage(logger: ReturnType<typeof Logger>, maybeToken?: string): Promise<Result<string, {
|
|
4
5
|
e: Error | unknown;
|
|
5
6
|
message: string;
|
|
6
7
|
}>>;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import config from '../config.js';
|
|
2
|
-
|
|
2
|
+
import { createTracedFetch } from './traced-fetch.js';
|
|
3
|
+
export async function validToken(logger, maybeToken) {
|
|
3
4
|
if (config.isTest)
|
|
4
5
|
return maybeToken ?? 'token';
|
|
6
|
+
const fetchFn = createTracedFetch(logger);
|
|
5
7
|
const token = maybeToken ?? config.token;
|
|
6
8
|
if (!token)
|
|
7
9
|
throw new Error('NO_TOKEN');
|
|
8
10
|
const url = `${config.apiUrl}v2025-04-23/users/me`;
|
|
9
|
-
const response = await
|
|
11
|
+
const response = await fetchFn(url, {
|
|
10
12
|
method: 'GET',
|
|
11
13
|
headers: {
|
|
12
14
|
Accept: 'application/json',
|
|
@@ -21,9 +23,9 @@ export async function validToken(maybeToken) {
|
|
|
21
23
|
}
|
|
22
24
|
throw new Error('SERVER_ERROR', { cause: response.statusText });
|
|
23
25
|
}
|
|
24
|
-
export async function validTokenOrErrorMessage(maybeToken) {
|
|
26
|
+
export async function validTokenOrErrorMessage(logger, maybeToken) {
|
|
25
27
|
try {
|
|
26
|
-
const token = await validToken(maybeToken);
|
|
28
|
+
const token = await validToken(logger, maybeToken);
|
|
27
29
|
return { ok: true, value: token };
|
|
28
30
|
}
|
|
29
31
|
catch (e) {
|