@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.
Files changed (137) hide show
  1. package/README.md +190 -76
  2. package/dist/actions/blueprints/assets.d.ts +3 -1
  3. package/dist/actions/blueprints/assets.js +15 -5
  4. package/dist/actions/blueprints/blueprint.d.ts +2 -1
  5. package/dist/actions/blueprints/blueprint.js +3 -1
  6. package/dist/actions/blueprints/config.d.ts +5 -2
  7. package/dist/actions/blueprints/config.js +4 -4
  8. package/dist/actions/blueprints/logs-streaming.d.ts +4 -2
  9. package/dist/actions/blueprints/logs-streaming.js +5 -2
  10. package/dist/actions/blueprints/logs.d.ts +2 -1
  11. package/dist/actions/blueprints/logs.js +4 -2
  12. package/dist/actions/blueprints/resources.d.ts +2 -1
  13. package/dist/actions/blueprints/resources.js +2 -2
  14. package/dist/actions/blueprints/stacks.d.ts +12 -6
  15. package/dist/actions/blueprints/stacks.js +18 -11
  16. package/dist/actions/functions/dev.d.ts +2 -1
  17. package/dist/actions/functions/dev.js +2 -2
  18. package/dist/actions/functions/env/list.d.ts +2 -1
  19. package/dist/actions/functions/env/list.js +4 -2
  20. package/dist/actions/functions/env/remove.d.ts +2 -1
  21. package/dist/actions/functions/env/remove.js +4 -2
  22. package/dist/actions/functions/env/update.d.ts +2 -1
  23. package/dist/actions/functions/env/update.js +4 -2
  24. package/dist/actions/functions/logs.d.ts +4 -3
  25. package/dist/actions/functions/logs.js +10 -6
  26. package/dist/actions/node.d.ts +2 -1
  27. package/dist/actions/node.js +2 -2
  28. package/dist/actions/sanity/examples.d.ts +5 -2
  29. package/dist/actions/sanity/examples.js +6 -6
  30. package/dist/actions/sanity/projects.d.ts +7 -3
  31. package/dist/actions/sanity/projects.js +11 -7
  32. package/dist/baseCommands.d.ts +47 -7
  33. package/dist/baseCommands.js +90 -12
  34. package/dist/commands/blueprints/add.d.ts +3 -2
  35. package/dist/commands/blueprints/add.js +14 -10
  36. package/dist/commands/blueprints/config.d.ts +3 -2
  37. package/dist/commands/blueprints/config.js +12 -6
  38. package/dist/commands/blueprints/deploy.d.ts +3 -2
  39. package/dist/commands/blueprints/deploy.js +10 -4
  40. package/dist/commands/blueprints/destroy.d.ts +3 -2
  41. package/dist/commands/blueprints/destroy.js +10 -4
  42. package/dist/commands/blueprints/doctor.d.ts +6 -4
  43. package/dist/commands/blueprints/doctor.js +17 -14
  44. package/dist/commands/blueprints/info.d.ts +3 -2
  45. package/dist/commands/blueprints/info.js +11 -5
  46. package/dist/commands/blueprints/init.d.ts +3 -2
  47. package/dist/commands/blueprints/init.js +26 -20
  48. package/dist/commands/blueprints/logs.d.ts +3 -2
  49. package/dist/commands/blueprints/logs.js +10 -4
  50. package/dist/commands/blueprints/plan.d.ts +3 -2
  51. package/dist/commands/blueprints/plan.js +8 -4
  52. package/dist/commands/blueprints/stacks.d.ts +3 -2
  53. package/dist/commands/blueprints/stacks.js +10 -6
  54. package/dist/commands/functions/add.d.ts +3 -2
  55. package/dist/commands/functions/add.js +10 -4
  56. package/dist/commands/functions/dev.d.ts +3 -2
  57. package/dist/commands/functions/dev.js +16 -5
  58. package/dist/commands/functions/env/add.d.ts +4 -3
  59. package/dist/commands/functions/env/add.js +8 -4
  60. package/dist/commands/functions/env/list.d.ts +4 -3
  61. package/dist/commands/functions/env/list.js +8 -4
  62. package/dist/commands/functions/env/remove.d.ts +4 -3
  63. package/dist/commands/functions/env/remove.js +8 -4
  64. package/dist/commands/functions/logs.d.ts +5 -4
  65. package/dist/commands/functions/logs.js +11 -5
  66. package/dist/commands/functions/test.d.ts +5 -4
  67. package/dist/commands/functions/test.js +13 -6
  68. package/dist/cores/blueprints/config.d.ts +2 -5
  69. package/dist/cores/blueprints/config.js +9 -9
  70. package/dist/cores/blueprints/deploy.js +14 -17
  71. package/dist/cores/blueprints/destroy.d.ts +2 -5
  72. package/dist/cores/blueprints/destroy.js +6 -6
  73. package/dist/cores/blueprints/doctor.js +32 -29
  74. package/dist/cores/blueprints/info.js +5 -5
  75. package/dist/cores/blueprints/init.d.ts +3 -3
  76. package/dist/cores/blueprints/init.js +15 -8
  77. package/dist/cores/blueprints/logs.js +6 -7
  78. package/dist/cores/blueprints/plan.js +1 -0
  79. package/dist/cores/blueprints/stacks.d.ts +2 -5
  80. package/dist/cores/blueprints/stacks.js +4 -4
  81. package/dist/cores/functions/add.js +8 -3
  82. package/dist/cores/functions/dev.js +2 -2
  83. package/dist/cores/functions/env/add.js +3 -4
  84. package/dist/cores/functions/env/list.js +3 -4
  85. package/dist/cores/functions/env/remove.js +3 -4
  86. package/dist/cores/functions/index.d.ts +3 -9
  87. package/dist/cores/functions/logs.d.ts +3 -1
  88. package/dist/cores/functions/logs.js +19 -11
  89. package/dist/cores/functions/test.d.ts +3 -1
  90. package/dist/cores/functions/test.js +18 -10
  91. package/dist/cores/index.d.ts +4 -7
  92. package/dist/cores/index.js +3 -3
  93. package/dist/index.d.ts +1 -2
  94. package/dist/index.js +1 -2
  95. package/dist/server/app.d.ts +2 -1
  96. package/dist/server/app.js +4 -4
  97. package/dist/server/handlers/invoke.d.ts +2 -1
  98. package/dist/server/handlers/invoke.js +2 -2
  99. package/dist/server/static/components/app.css +0 -116
  100. package/dist/server/static/components/clear-button.js +1 -1
  101. package/dist/server/static/components/console-panel.js +27 -6
  102. package/dist/server/static/components/fetch-button.js +1 -1
  103. package/dist/server/static/components/filter-api-version.js +39 -3
  104. package/dist/server/static/components/filter-document-id.js +39 -3
  105. package/dist/server/static/components/filter-with-token.js +27 -4
  106. package/dist/server/static/components/filters.js +127 -62
  107. package/dist/server/static/components/function-list.js +33 -13
  108. package/dist/server/static/components/network-spinner.js +6 -4
  109. package/dist/server/static/components/payload-panel.js +46 -24
  110. package/dist/server/static/components/response-panel.js +33 -6
  111. package/dist/server/static/components/rule-panel.js +13 -4
  112. package/dist/server/static/components/run-panel.js +14 -7
  113. package/dist/server/static/components/select-dropdown.js +34 -5
  114. package/dist/server/static/components/shared-styles.js +31 -0
  115. package/dist/server/static/components/toggle-switch.js +11 -2
  116. package/dist/utils/display/blueprints-formatting.d.ts +2 -2
  117. package/dist/utils/display/blueprints-formatting.js +31 -26
  118. package/dist/utils/display/prompt.d.ts +5 -2
  119. package/dist/utils/display/prompt.js +5 -4
  120. package/dist/utils/find-function.d.ts +4 -0
  121. package/dist/utils/find-function.js +6 -0
  122. package/dist/utils/functions/fetch-document.d.ts +3 -2
  123. package/dist/utils/functions/fetch-document.js +7 -6
  124. package/dist/utils/index.d.ts +2 -0
  125. package/dist/utils/index.js +2 -0
  126. package/dist/utils/logger.d.ts +13 -0
  127. package/dist/utils/logger.js +61 -0
  128. package/dist/utils/other/github.d.ts +2 -1
  129. package/dist/utils/other/github.js +4 -2
  130. package/dist/utils/other/npmjs.d.ts +2 -1
  131. package/dist/utils/other/npmjs.js +4 -2
  132. package/dist/utils/traced-fetch.d.ts +35 -0
  133. package/dist/utils/traced-fetch.js +238 -0
  134. package/dist/utils/validated-token.d.ts +3 -2
  135. package/dist/utils/validated-token.js +6 -4
  136. package/oclif.manifest.json +455 -75
  137. 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
- export async function validToken(maybeToken) {
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 fetch(url, {
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) {