@sanity/runtime-cli 1.2.1 → 1.3.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 CHANGED
@@ -20,7 +20,7 @@ $ npm install -g @sanity/runtime-cli
20
20
  $ sanity COMMAND
21
21
  running command...
22
22
  $ sanity (--version)
23
- @sanity/runtime-cli/1.2.1 linux-x64 node-v22.14.0
23
+ @sanity/runtime-cli/1.3.0 linux-x64 node-v22.14.0
24
24
  $ sanity --help [COMMAND]
25
25
  USAGE
26
26
  $ sanity COMMAND
@@ -31,6 +31,7 @@ USAGE
31
31
  <!-- commands -->
32
32
  * [`sanity blueprints deploy`](#sanity-blueprints-deploy)
33
33
  * [`sanity blueprints info`](#sanity-blueprints-info)
34
+ * [`sanity blueprints logs`](#sanity-blueprints-logs)
34
35
  * [`sanity blueprints plan`](#sanity-blueprints-plan)
35
36
  * [`sanity functions dev`](#sanity-functions-dev)
36
37
  * [`sanity functions invoke ID`](#sanity-functions-invoke-id)
@@ -63,7 +64,7 @@ EXAMPLES
63
64
  $ sanity blueprints deploy
64
65
  ```
65
66
 
66
- _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v1.2.1/src/commands/blueprints/deploy.ts)_
67
+ _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v1.3.0/src/commands/blueprints/deploy.ts)_
67
68
 
68
69
  ## `sanity blueprints info`
69
70
 
@@ -80,7 +81,32 @@ EXAMPLES
80
81
  $ sanity blueprints info
81
82
  ```
82
83
 
83
- _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v1.2.1/src/commands/blueprints/info.ts)_
84
+ _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v1.3.0/src/commands/blueprints/info.ts)_
85
+
86
+ ## `sanity blueprints logs`
87
+
88
+ Display logs for a Blueprint stack
89
+
90
+ ```
91
+ USAGE
92
+ $ sanity blueprints logs [-s <value>] [-w]
93
+
94
+ FLAGS
95
+ -s, --stack-id=<value> Stack ID to fetch logs for (optional if running in a blueprint directory)
96
+ -w, --watch Watch for new logs (streaming mode)
97
+
98
+ DESCRIPTION
99
+ Display logs for a Blueprint stack
100
+
101
+ EXAMPLES
102
+ $ sanity blueprints logs
103
+
104
+ $ sanity blueprints logs --stack-id <stack-id>
105
+
106
+ $ sanity blueprints logs --watch
107
+ ```
108
+
109
+ _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v1.3.0/src/commands/blueprints/logs.ts)_
84
110
 
85
111
  ## `sanity blueprints plan`
86
112
 
@@ -97,7 +123,7 @@ EXAMPLES
97
123
  $ sanity blueprints plan
98
124
  ```
99
125
 
100
- _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v1.2.1/src/commands/blueprints/plan.ts)_
126
+ _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v1.3.0/src/commands/blueprints/plan.ts)_
101
127
 
102
128
  ## `sanity functions dev`
103
129
 
@@ -117,7 +143,7 @@ EXAMPLES
117
143
  $ sanity functions dev --port 8974
118
144
  ```
119
145
 
120
- _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v1.2.1/src/commands/functions/dev.ts)_
146
+ _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v1.3.0/src/commands/functions/dev.ts)_
121
147
 
122
148
  ## `sanity functions invoke ID`
123
149
 
@@ -143,7 +169,7 @@ EXAMPLES
143
169
  $ sanity functions invoke <ID> --file 'payload.json'
144
170
  ```
145
171
 
146
- _See code: [src/commands/functions/invoke.ts](https://github.com/sanity-io/runtime-cli/blob/v1.2.1/src/commands/functions/invoke.ts)_
172
+ _See code: [src/commands/functions/invoke.ts](https://github.com/sanity-io/runtime-cli/blob/v1.3.0/src/commands/functions/invoke.ts)_
147
173
 
148
174
  ## `sanity functions logs ID`
149
175
 
@@ -163,7 +189,7 @@ EXAMPLES
163
189
  $ sanity functions logs <ID>
164
190
  ```
165
191
 
166
- _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v1.2.1/src/commands/functions/logs.ts)_
192
+ _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v1.3.0/src/commands/functions/logs.ts)_
167
193
 
168
194
  ## `sanity functions test PATH`
169
195
 
@@ -192,7 +218,7 @@ EXAMPLES
192
218
  $ sanity functions test ./test.ts --data '{ "id": 1 }' --timeout 60
193
219
  ```
194
220
 
195
- _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v1.2.1/src/commands/functions/test.ts)_
221
+ _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v1.3.0/src/commands/functions/test.ts)_
196
222
 
197
223
  ## `sanity help [COMMAND]`
198
224
 
@@ -0,0 +1,7 @@
1
+ import type { BlueprintLog } from '../../utils/types.js';
2
+ export declare function getLogs(stackId: string, projectId: string, token: string): Promise<{
3
+ logs: BlueprintLog[];
4
+ ok: boolean;
5
+ error: string | null;
6
+ }>;
7
+ export declare function streamLogs(stackId: string, projectId: string, token: string, onLog: (log: BlueprintLog) => void, onOpen: () => void, onError: (error: string) => void): () => void;
@@ -0,0 +1,79 @@
1
+ import { EventSource } from 'eventsource';
2
+ import config from '../../config.js';
3
+ const { blueprints } = config.server;
4
+ export async function getLogs(stackId, projectId, token) {
5
+ const response = await fetch(`${blueprints}/vX/blueprints/logs?stackId=${stackId}`, {
6
+ headers: {
7
+ Accept: 'application/json',
8
+ 'Content-Type': 'application/json',
9
+ Authorization: `Bearer ${token}`,
10
+ 'X-Sanity-Scope-Type': 'project',
11
+ 'X-Sanity-Scope-Id': projectId,
12
+ },
13
+ method: 'GET',
14
+ });
15
+ const result = await response.json();
16
+ return {
17
+ ok: response.ok,
18
+ error: response.ok ? null : result.message || 'Unknown error',
19
+ logs: response.ok ? result : [],
20
+ };
21
+ }
22
+ export function streamLogs(stackId, projectId, token, onLog, onOpen, onError) {
23
+ const url = new URL(`${blueprints}/vX/blueprints/logs/stream`);
24
+ url.searchParams.append('stackId', stackId);
25
+ const headers = {
26
+ Authorization: `Bearer ${token}`,
27
+ 'X-Sanity-Scope-Type': 'project',
28
+ 'X-Sanity-Scope-Id': projectId,
29
+ };
30
+ const eventSource = new EventSource(url.toString(), {
31
+ fetch: (input, init) => fetch(input, {
32
+ ...init,
33
+ headers: {
34
+ ...init?.headers,
35
+ ...headers,
36
+ },
37
+ }),
38
+ });
39
+ eventSource.onopen = onOpen;
40
+ eventSource.onmessage = (event) => {
41
+ try {
42
+ const log = JSON.parse(event.data);
43
+ onLog(log);
44
+ }
45
+ catch (err) {
46
+ onError(`Failed to parse log data: ${err instanceof Error ? err.message : String(err)}`);
47
+ }
48
+ };
49
+ eventSource.addEventListener('logs', (event) => {
50
+ try {
51
+ const logData = JSON.parse(event.data);
52
+ // usually an array
53
+ if (Array.isArray(logData)) {
54
+ for (const log of logData)
55
+ onLog(log);
56
+ }
57
+ else {
58
+ onLog(logData);
59
+ }
60
+ }
61
+ catch (err) {
62
+ console.error('Error parsing logs event:', err);
63
+ }
64
+ });
65
+ eventSource.onerror = (err) => {
66
+ onError('Connection to log stream failed or was closed');
67
+ if (eventSource.readyState === eventSource.CLOSED) {
68
+ console.log('Connection is CLOSED');
69
+ }
70
+ else if (eventSource.readyState === eventSource.CONNECTING) {
71
+ console.log('Connection is attempting to reconnect...');
72
+ return; // Don't close if trying to reconnect
73
+ }
74
+ eventSource.close();
75
+ };
76
+ return () => {
77
+ eventSource.close();
78
+ };
79
+ }
@@ -0,0 +1,18 @@
1
+ import type { BlueprintOperation } from '../../utils/types.js';
2
+ export declare function getOperation({ stackId, operationId, projectId, }: {
3
+ stackId: string;
4
+ operationId: string;
5
+ projectId: string;
6
+ }): Promise<{
7
+ ok: boolean;
8
+ error: string | null;
9
+ operation: BlueprintOperation | null;
10
+ }>;
11
+ export declare function listOperations({ stackId, projectId, }: {
12
+ stackId: string;
13
+ projectId: string;
14
+ }): Promise<{
15
+ ok: boolean;
16
+ error: string | null;
17
+ operations: BlueprintOperation[];
18
+ }>;
@@ -0,0 +1,52 @@
1
+ import config from '../../config.js';
2
+ const { blueprints } = config.server;
3
+ function getHeaders(projectId) {
4
+ return {
5
+ Authorization: `Bearer ${config.token}`,
6
+ 'Content-Type': 'application/json',
7
+ 'X-Sanity-Scope-Type': 'project',
8
+ 'X-Sanity-Scope-Id': projectId,
9
+ };
10
+ }
11
+ export async function getOperation({ stackId, operationId, projectId, }) {
12
+ const path = `${blueprints}/vX/blueprints/stacks/${stackId}/operations/${operationId}`;
13
+ const response = await fetch(path, {
14
+ method: 'GET',
15
+ headers: getHeaders(projectId),
16
+ });
17
+ if (!response.ok) {
18
+ const errorText = await response.text();
19
+ return {
20
+ ok: false,
21
+ error: errorText || 'Failed to fetch operation details',
22
+ operation: null,
23
+ };
24
+ }
25
+ const operation = await response.json();
26
+ return {
27
+ ok: true,
28
+ error: null,
29
+ operation,
30
+ };
31
+ }
32
+ export async function listOperations({ stackId, projectId, }) {
33
+ const path = `${blueprints}/vX/blueprints/stacks/${stackId}/operations`;
34
+ const response = await fetch(path, {
35
+ method: 'GET',
36
+ headers: getHeaders(projectId),
37
+ });
38
+ if (!response.ok) {
39
+ const errorText = await response.text();
40
+ return {
41
+ ok: false,
42
+ error: errorText || 'Failed to fetch operations',
43
+ operations: [],
44
+ };
45
+ }
46
+ const operations = await response.json();
47
+ return {
48
+ ok: true,
49
+ error: null,
50
+ operations,
51
+ };
52
+ }
@@ -1,2 +1,7 @@
1
1
  import type { Blueprint } from '../../utils/types.js';
2
- export default function readBlueprint(): Blueprint;
2
+ export default function readBlueprintOnDisk(): {
3
+ document: Blueprint;
4
+ path: string;
5
+ fileName: string;
6
+ extension: string;
7
+ };
@@ -1,17 +1,43 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
2
+ import { extname, join } from 'node:path';
3
3
  import { cwd } from 'node:process';
4
- export default function readBlueprint() {
4
+ const SUPPORTED_FILE_NAMES_IN_PRIORITY_ORDER = [
5
+ 'blueprint.json',
6
+ // 'blueprint.js',
7
+ // 'blueprint.mjs',
8
+ // 'blueprint.cjs',
9
+ // 'blueprint.ts',
10
+ ];
11
+ function findBlueprintFile() {
12
+ for (const fileName of SUPPORTED_FILE_NAMES_IN_PRIORITY_ORDER) {
13
+ const filePath = join(cwd(), fileName);
14
+ if (existsSync(filePath)) {
15
+ return { path: filePath, fileName, extension: extname(filePath) };
16
+ }
17
+ }
18
+ return null;
19
+ }
20
+ export default function readBlueprintOnDisk() {
5
21
  try {
6
- const blueprintPath = join(cwd(), 'blueprint.json');
7
- if (!existsSync(blueprintPath)) {
8
- throw Error('Could not find blueprint.json');
22
+ const blueprintPath = findBlueprintFile();
23
+ if (!blueprintPath)
24
+ throw Error('Could not find Blueprint file');
25
+ const { path, fileName, extension } = blueprintPath;
26
+ let readBlueprint;
27
+ let blueprintJson;
28
+ if (extension === '.json') {
29
+ readBlueprint = readFileSync(path, 'utf8').toString();
30
+ blueprintJson = JSON.parse(readBlueprint);
31
+ // } else if (extension === '.js' || extension === '.mjs') {
32
+ // const blueprintModule = require(path)
33
+ // blueprintJson = blueprintModule.default
34
+ }
35
+ else {
36
+ throw Error(`Unsupported blueprint file extension: ${extension}`);
9
37
  }
10
- const readBlueprint = readFileSync(blueprintPath, 'utf8').toString();
11
- const blueprintJson = JSON.parse(readBlueprint);
12
- return blueprintJson;
38
+ return { document: blueprintJson, path, fileName, extension };
13
39
  }
14
40
  catch (err) {
15
- throw Error('Unable to parse blueprint.json');
41
+ throw Error(`Unable to parse Blueprint file: ${err}`);
16
42
  }
17
43
  }
@@ -1,22 +1,50 @@
1
1
  import type { BlueprintJob } from '../../utils/types.js';
2
- declare function listStacks(): Promise<{
2
+ declare function listStacks({ projectId }: {
3
+ projectId: string;
4
+ }): Promise<{
3
5
  ok: boolean;
4
6
  error: any;
5
7
  stacks: any;
6
8
  }>;
7
- declare function getStack(stackId: string): Promise<{
9
+ declare function getStackByName({ name, projectId }: {
10
+ name: string;
11
+ projectId: string;
12
+ }): Promise<{
13
+ ok: boolean;
14
+ error: null;
15
+ stack: null;
16
+ stackId: null;
17
+ availableStacks: any;
18
+ } | {
19
+ ok: boolean;
20
+ error: any;
21
+ stack: any;
22
+ stackId: any;
23
+ availableStacks?: undefined;
24
+ }>;
25
+ declare function getStack({ stackId, projectId }: {
26
+ stackId: string;
27
+ projectId: string;
28
+ }): Promise<{
8
29
  ok: boolean;
9
30
  error: any;
10
31
  stack: any;
11
32
  }>;
12
- declare function createStack(blueprint: BlueprintJob): Promise<{
33
+ declare function createStack({ blueprint, projectId }: {
34
+ blueprint: BlueprintJob;
35
+ projectId: string;
36
+ }): Promise<{
13
37
  ok: boolean;
14
38
  error: any;
15
39
  stack: any;
16
40
  }>;
17
- declare function updateStack(stackId: string, blueprint: BlueprintJob): Promise<{
41
+ declare function updateStack({ stackId, blueprint, projectId, }: {
42
+ stackId: string;
43
+ blueprint: BlueprintJob;
44
+ projectId: string;
45
+ }): Promise<{
18
46
  ok: boolean;
19
47
  error: any;
20
48
  stack: any;
21
49
  }>;
22
- export { listStacks, getStack, createStack, updateStack };
50
+ export { listStacks, getStack, createStack, updateStack, getStackByName };
@@ -1,13 +1,18 @@
1
1
  import config from '../../config.js';
2
2
  const { blueprints } = config.server;
3
- const HEADERS = {
4
- Authorization: `Bearer ${config.token}`,
5
- 'Content-Type': 'application/json',
6
- };
7
- async function listStacks() {
8
- const response = await fetch(`${blueprints}/vX/stacks`, {
3
+ function getHeaders(projectId) {
4
+ return {
5
+ Authorization: `Bearer ${config.token}`,
6
+ 'Content-Type': 'application/json',
7
+ 'X-Sanity-Scope-Type': 'project',
8
+ 'X-Sanity-Scope-Id': projectId,
9
+ };
10
+ }
11
+ async function listStacks({ projectId }) {
12
+ const path = `${blueprints}/vX/blueprints/stacks`;
13
+ const response = await fetch(path, {
9
14
  method: 'GET',
10
- headers: HEADERS,
15
+ headers: getHeaders(projectId),
11
16
  });
12
17
  const stacks = await response.json();
13
18
  return {
@@ -16,10 +21,39 @@ async function listStacks() {
16
21
  stacks,
17
22
  };
18
23
  }
19
- async function getStack(stackId) {
20
- const response = await fetch(`${blueprints}/vX/stacks/${stackId}`, {
24
+ async function getStackByName({ name, projectId }) {
25
+ const { ok, stacks, error } = await listStacks({ projectId });
26
+ if (!ok || !stacks) {
27
+ return {
28
+ ok: false,
29
+ error: error || 'Failed to retrieve stacks',
30
+ stack: null,
31
+ stackId: null,
32
+ };
33
+ }
34
+ const foundStack = stacks.find((stack) => stack.name === name);
35
+ if (!foundStack) {
36
+ return {
37
+ ok: true,
38
+ error: null,
39
+ stack: null,
40
+ stackId: null,
41
+ availableStacks: stacks.map((s) => s.name),
42
+ };
43
+ }
44
+ const stackResult = await getStack({ stackId: foundStack.id, projectId });
45
+ return {
46
+ ok: stackResult.ok,
47
+ error: stackResult.error,
48
+ stack: stackResult.stack,
49
+ stackId: foundStack.id,
50
+ };
51
+ }
52
+ async function getStack({ stackId, projectId }) {
53
+ const path = `${blueprints}/vX/blueprints/stacks/${stackId}`;
54
+ const response = await fetch(path, {
21
55
  method: 'GET',
22
- headers: HEADERS,
56
+ headers: getHeaders(projectId),
23
57
  });
24
58
  const stack = await response.json();
25
59
  return {
@@ -28,10 +62,11 @@ async function getStack(stackId) {
28
62
  stack,
29
63
  };
30
64
  }
31
- async function createStack(blueprint) {
32
- const response = await fetch(`${blueprints}/vX/stacks`, {
65
+ async function createStack({ blueprint, projectId }) {
66
+ const path = `${blueprints}/vX/blueprints/stacks`;
67
+ const response = await fetch(path, {
33
68
  method: 'POST',
34
- headers: HEADERS,
69
+ headers: getHeaders(projectId),
35
70
  body: JSON.stringify(blueprint),
36
71
  });
37
72
  const stack = await response.json();
@@ -41,10 +76,11 @@ async function createStack(blueprint) {
41
76
  stack,
42
77
  };
43
78
  }
44
- async function updateStack(stackId, blueprint) {
45
- const response = await fetch(`${blueprints}/vX/stacks/${stackId}`, {
79
+ async function updateStack({ stackId, blueprint, projectId, }) {
80
+ const path = `${blueprints}/vX/blueprints/stacks/${stackId}`;
81
+ const response = await fetch(path, {
46
82
  method: 'PUT',
47
- headers: HEADERS,
83
+ headers: getHeaders(projectId),
48
84
  body: JSON.stringify(blueprint),
49
85
  });
50
86
  const stack = await response.json();
@@ -54,4 +90,4 @@ async function updateStack(stackId, blueprint) {
54
90
  stack,
55
91
  };
56
92
  }
57
- export { listStacks, getStack, createStack, updateStack };
93
+ export { listStacks, getStack, createStack, updateStack, getStackByName };
@@ -1,5 +1,8 @@
1
1
  import type { BlueprintResource } from '../../utils/types.js';
2
- export declare function stashAsset(resource: BlueprintResource): Promise<{
2
+ export declare function stashAsset({ resource, projectId, }: {
3
+ resource: BlueprintResource;
4
+ projectId: string;
5
+ }): Promise<{
3
6
  success: boolean;
4
7
  assetId: any;
5
8
  error?: undefined;
@@ -4,7 +4,15 @@ import { cwd } from 'node:process';
4
4
  import JSZip from 'jszip';
5
5
  import config from '../../config.js';
6
6
  const { blueprints } = config.server;
7
- export async function stashAsset(resource) {
7
+ function getHeaders(projectId) {
8
+ return {
9
+ Authorization: `Bearer ${config.token}`,
10
+ 'Content-Type': 'application/json',
11
+ 'X-Sanity-Scope-Type': 'project',
12
+ 'X-Sanity-Scope-Id': projectId,
13
+ };
14
+ }
15
+ export async function stashAsset({ resource, projectId, }) {
8
16
  try {
9
17
  // Read the function source
10
18
  const source = await fs.readFileSync(path.join(cwd(), resource.src), 'utf8');
@@ -14,19 +22,16 @@ export async function stashAsset(resource) {
14
22
  const zipBuffer = await zip.generateAsync({ type: 'nodebuffer' });
15
23
  const base64Zip = zipBuffer.toString('base64');
16
24
  // Stash the asset
17
- const assetResponse = await fetch(`${blueprints}/assets/stash`, {
25
+ const assetResponse = await fetch(`${blueprints}/vX/blueprints/assets/stash`, {
18
26
  method: 'POST',
19
- headers: {
20
- Authorization: `Bearer ${config.token}`,
21
- 'Content-Type': 'application/json',
22
- },
27
+ headers: getHeaders(projectId),
23
28
  body: JSON.stringify({
24
29
  file: base64Zip,
25
30
  filename: `${resource.name}.zip`,
26
31
  }),
27
32
  });
28
33
  const assetJson = await assetResponse.json();
29
- console.debug('ASSET RESPONSE:', assetJson);
34
+ // console.debug('ASSET RESPONSE:', assetJson)
30
35
  if (assetResponse.ok) {
31
36
  return { success: true, assetId: assetJson.id };
32
37
  }
@@ -1,15 +1,18 @@
1
1
  import config from '../../config.js';
2
2
  import buildPayload from '../../utils/build-payload.js';
3
3
  const { functions } = config.server;
4
+ function getHeaders() {
5
+ return {
6
+ Authorization: `Bearer ${config.token}`,
7
+ 'Content-Type': 'application/json',
8
+ Accept: 'application/json',
9
+ };
10
+ }
4
11
  export async function invoke(id, options) {
5
12
  const payload = buildPayload(options);
6
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
7
13
  const response = await fetch(`${functions}/vX/functions/${id}/invoke`, {
8
14
  body: JSON.stringify({ data: payload }),
9
- headers: {
10
- Accept: 'application/json',
11
- 'Content-Type': 'application/json',
12
- },
15
+ headers: getHeaders(),
13
16
  method: 'POST',
14
17
  });
15
18
  const json = await response.json();
@@ -1,70 +1,64 @@
1
1
  import { Command } from '@oclif/core';
2
- import readBlueprint from '../../actions/blueprints/read-blueprint.js';
3
- import { createStack, listStacks, updateStack } from '../../actions/blueprints/stacks.js';
2
+ import readBlueprintOnDisk from '../../actions/blueprints/read-blueprint.js';
3
+ import { createStack, getStackByName, updateStack } from '../../actions/blueprints/stacks.js';
4
4
  import { stashAsset } from '../../actions/blueprints/stash-asset.js';
5
+ import { green, red, yellow } from '../../utils/display/colors.js';
5
6
  import Spinner from '../../utils/spinner.js';
6
7
  export default class Deploy extends Command {
7
8
  static description = 'Deploy a Blueprint';
8
9
  static examples = ['<%= config.bin %> <%= command.id %>'];
9
10
  async run() {
10
- const { displayName, name, projectId, resources } = readBlueprint();
11
+ const { document } = readBlueprintOnDisk();
12
+ const { displayName, name, projectId, resources } = document;
11
13
  const s = new Spinner();
12
- s.start('Deploying blueprint...');
13
14
  const functionResources = resources.filter((r) => r.kind === 'function');
14
15
  // First stash all function assets
15
16
  if (functionResources.length > 0) {
16
- s.stop('Preparing functions...');
17
17
  for (const resource of functionResources) {
18
18
  const fnSpinner = new Spinner();
19
19
  fnSpinner.start(`Processing ${resource.name}...`);
20
- const result = await stashAsset(resource);
20
+ const result = await stashAsset({ resource, projectId });
21
21
  if (result.success) {
22
- resource.assetId = result.assetId;
23
- fnSpinner.stop(`✓ ${resource.name} (${result.assetId})`);
24
- this.log(` Source: ${resource.src}`);
22
+ const src = resource.src;
23
+ resource.src = result.assetId; // TODO: properly reference asset - for now, the API expects the assetId
24
+ fnSpinner.stop(`${green('✓')} ${resource.name} <${yellow(result.assetId)}>`);
25
+ this.log(` Source: ${src}`);
25
26
  }
26
27
  else {
27
- fnSpinner.stop(`✗ Failed to process ${resource.name}`);
28
+ fnSpinner.stop(`${red('✗')} Failed to process ${resource.name}`);
28
29
  this.log(` Error: ${result.error}`);
29
30
  return;
30
31
  }
31
32
  }
32
- this.log('│');
33
- s.start('Looking for existing stack...');
34
33
  }
35
- // Check for existing stack
36
- const { ok: stacksOk, stacks, error: stacksError } = await listStacks();
37
- if (!stacksOk) {
38
- s.stop('Failed to list stacks');
39
- this.log(`Error: ${stacksError || 'Unknown error'}`);
34
+ s.start('Looking for existing stack...');
35
+ const { ok, error, stackId: existingStackId, } = await getStackByName({
36
+ name,
37
+ projectId,
38
+ });
39
+ if (!ok) {
40
+ s.stop(`${red('Failed')} to list stacks`);
41
+ this.log(`Error: ${error || 'Unknown error'}`);
40
42
  return;
41
43
  }
42
- const existingStack = stacks.find((st) => st.name === name);
43
44
  const blueprint = {
44
45
  name,
45
46
  projectId,
46
- document: {
47
- resources: resources.map((r) => {
48
- if (r.kind === 'function') {
49
- return { ...r, src: r.assetId };
50
- }
51
- return r;
52
- }),
53
- },
47
+ document: { resources },
54
48
  };
55
49
  this.debug('BLUEPRINT DOCUMENT:', blueprint);
56
- // Create or update stack
57
- const { ok, stack, error } = existingStack
58
- ? await updateStack(existingStack.id, blueprint)
59
- : await createStack(blueprint);
50
+ const { ok: deployOk, stack, error: deployError, } = existingStackId
51
+ ? await updateStack({ stackId: existingStackId, blueprint, projectId })
52
+ : await createStack({ blueprint, projectId });
60
53
  this.debug('STACK RESPONSE:', stack);
61
- if (ok) {
62
- s.stop(`Stack ${existingStack ? 'updated' : 'created'}! (${stack.id})`);
63
- this.log('Use `blueprints info` to check deployment status');
54
+ if (deployOk) {
55
+ s.stop(`${green('Success!')} Stack ${existingStackId ? 'updated' : 'created'} <${yellow(stack.id)}>`);
56
+ this.log('\nUse `sanity blueprints info` to check deployment status');
64
57
  }
65
58
  else {
66
- s.stop(`Failed to ${existingStack ? 'update' : 'create'} stack`);
67
- this.log(`Error: ${error || 'Unknown error'}`);
59
+ this.debug('STACK ERROR RESPONSE:', stack);
60
+ s.stop(`${red('Failed')} to ${existingStackId ? 'update' : 'create'} stack`);
61
+ this.log(`Error: ${deployError || 'Unknown error'}`);
68
62
  }
69
63
  }
70
64
  }
@@ -1,68 +1,74 @@
1
1
  import { Command } from '@oclif/core';
2
- import readBlueprint from '../../actions/blueprints/read-blueprint.js';
3
- import { getStack, listStacks } from '../../actions/blueprints/stacks.js';
2
+ import readBlueprintOnDisk from '../../actions/blueprints/read-blueprint.js';
3
+ import { getStackByName } from '../../actions/blueprints/stacks.js';
4
+ import { formatResourceTree, formatStacksList, formatTitle, } from '../../utils/display/blueprints-formatting.js';
5
+ import { bold, green, red, yellow } from '../../utils/display/colors.js';
6
+ import { formatDate, formatDuration } from '../../utils/display/dates.js';
4
7
  import Spinner from '../../utils/spinner.js';
5
8
  export default class Info extends Command {
6
9
  static description = 'Show information about a Blueprint';
7
10
  static examples = ['<%= config.bin %> <%= command.id %>'];
8
11
  async run() {
9
- const { displayName, name, projectId, resources } = readBlueprint();
12
+ const { document } = readBlueprintOnDisk();
13
+ const { displayName, name, projectId } = document;
10
14
  const s = new Spinner();
11
15
  if (!projectId) {
12
- this.log(`No project for Blueprint "${displayName}"`);
16
+ this.log(`Cannot determine project for Blueprint "${displayName}"`);
13
17
  return;
14
18
  }
15
19
  s.start(`Retrieving info for "${displayName}"`);
16
20
  try {
17
- const { ok, stacks, error } = await listStacks();
21
+ const { ok, stack, stackId: foundStackId, error, availableStacks, } = await getStackByName({
22
+ name,
23
+ projectId,
24
+ });
18
25
  if (!ok) {
19
- s.stop('Failed to retrieve stacks');
26
+ s.stop(`${red('Failed')} to retrieve stacks`);
20
27
  this.log(`Error: ${error || 'Unknown error'}`);
21
28
  return;
22
29
  }
23
- if (stacks.length === 0) {
24
- s.stop(`No stacks found for Blueprint "${displayName}"`);
30
+ if (!foundStackId || !stack) {
31
+ s.stop(`${red('Failed')} to find Stack for ${formatTitle('Blueprint', displayName)}`);
32
+ if (availableStacks && availableStacks.length > 0) {
33
+ this.log(formatStacksList(availableStacks));
34
+ }
25
35
  return;
26
36
  }
27
- const foundStack = stacks.find((st) => st.name === name);
28
- const { ok: stackOk, stack, error: stackError } = await getStack(foundStack.id);
29
- if (!stackOk) {
30
- s.stop('Failed to retrieve stack');
31
- this.log(`Error: ${stackError || 'Unknown error'}`);
32
- return;
37
+ const foundStack = { id: foundStackId };
38
+ s.stop(`${formatTitle('Blueprint', displayName)} Info\n`);
39
+ this.log(`Stack name: ${bold(stack.name)}`);
40
+ this.log(`Stack ID: ${yellow(foundStack.id)}`);
41
+ if (stack.createdAt) {
42
+ this.log(`Created: ${formatDate(stack.createdAt)}`);
33
43
  }
34
- s.stop(`Stack "${stack.name}" (${stack.recentOperation.status})`);
35
- // Show resources
36
- if (stack?.resources) {
37
- const { resources: stackResources } = stack;
38
- const functionResources = stackResources.filter((r) => r.kind === 'function');
39
- const otherResources = stackResources.filter((r) => r.kind !== 'function');
40
- if (functionResources.length > 0) {
41
- this.log(`├─ Functions (${functionResources.length})`);
42
- for (const fn of functionResources) {
43
- this.log(`│ ├─ ${fn.name} <${fn.externalId}>`);
44
- }
44
+ if (stack.updatedAt) {
45
+ this.log(`Updated: ${formatDate(stack.updatedAt)}`);
46
+ }
47
+ if (stack.recentOperation) {
48
+ const operation = stack.recentOperation;
49
+ if (operation.id) {
50
+ this.log(`Recent Operation <${yellow(operation.id)}>:`);
45
51
  }
46
- if (otherResources.length > 0) {
47
- this.log(`└─Other Resources (${otherResources.length})`);
48
- for (const other of otherResources) {
49
- this.log(` ├─ ${other.name || other.src}`);
50
- }
52
+ if (operation.status) {
53
+ const operationColor = operation.status === 'COMPLETED' ? green : red;
54
+ const status = operation.status || 'UNKNOWN';
55
+ this.log(` Status: ${operationColor(status)}`);
51
56
  }
52
- }
53
- // Show operation details if not completed
54
- if (stack.recentOperation.status !== 'COMPLETED') {
55
- this.log('\nOperation Details:');
56
- if (stack.recentOperation.error) {
57
- this.log(`Error: ${stack.recentOperation.error}`);
57
+ if (operation.createdAt) {
58
+ this.log(` Started : ${formatDate(operation.createdAt)}`);
58
59
  }
59
- if (stack.recentOperation.progress) {
60
- this.log(`Progress: ${stack.recentOperation.progress}`);
60
+ if (operation.status === 'COMPLETED' && operation.completedAt && operation.createdAt) {
61
+ this.log(` Completed: ${formatDate(operation.completedAt)}`);
62
+ this.log(` Duration: ${yellow(formatDuration(operation.createdAt, operation.completedAt))}`);
61
63
  }
62
64
  }
65
+ this.log('');
66
+ if (stack?.resources) {
67
+ formatResourceTree(stack.resources, this.log.bind(this));
68
+ }
63
69
  }
64
70
  catch (err) {
65
- s.stop('Failed to retrieve info');
71
+ s.stop('Failed to parse Stack info');
66
72
  if (err instanceof Error) {
67
73
  this.log(`Error: ${err.message}`);
68
74
  }
@@ -0,0 +1,10 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Logs extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'stack-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ watch: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,166 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { getLogs, streamLogs } from '../../actions/blueprints/logs.js';
3
+ import readBlueprintOnDisk from '../../actions/blueprints/read-blueprint.js';
4
+ import { getStackByName } from '../../actions/blueprints/stacks.js';
5
+ import config from '../../config.js';
6
+ import { formatStacksList, formatTitle } from '../../utils/display/blueprints-formatting.js';
7
+ import { blue, bold, green, red, yellow } from '../../utils/display/colors.js';
8
+ import Spinner from '../../utils/spinner.js';
9
+ export default class Logs extends Command {
10
+ static description = 'Display logs for a Blueprint stack';
11
+ static examples = [
12
+ '<%= config.bin %> <%= command.id %>',
13
+ '<%= config.bin %> <%= command.id %> --stack-id <stack-id>',
14
+ '<%= config.bin %> <%= command.id %> --watch',
15
+ ];
16
+ static flags = {
17
+ 'stack-id': Flags.string({
18
+ char: 's',
19
+ description: 'Stack ID to fetch logs for (optional if running in a blueprint directory)',
20
+ required: false,
21
+ }),
22
+ watch: Flags.boolean({
23
+ char: 'w',
24
+ description: 'Watch for new logs (streaming mode)',
25
+ required: false,
26
+ default: false,
27
+ }),
28
+ };
29
+ async run() {
30
+ const { flags } = await this.parse(Logs);
31
+ const s = new Spinner();
32
+ const watchMode = flags.watch;
33
+ try {
34
+ const { document } = readBlueprintOnDisk();
35
+ const { name, displayName, projectId } = document;
36
+ if (!projectId) {
37
+ this.log('Cannot determine project ID for this Blueprint');
38
+ return;
39
+ }
40
+ let stackId = flags['stack-id'];
41
+ if (!stackId) {
42
+ s.start(`Finding stack for blueprint "${displayName}"`);
43
+ const { ok, stackId: foundStackId, error, availableStacks, } = await getStackByName({
44
+ name,
45
+ projectId,
46
+ });
47
+ if (!ok) {
48
+ s.stop(`${red('Failed')} to retrieve stacks`);
49
+ this.log(`Error: ${error || 'Unknown error'}`);
50
+ return;
51
+ }
52
+ if (!foundStackId) {
53
+ s.stop(`${red('Failed')} to find Stack for ${formatTitle('Blueprint', displayName)}`);
54
+ if (availableStacks && availableStacks.length > 0) {
55
+ this.log(formatStacksList(availableStacks));
56
+ }
57
+ return;
58
+ }
59
+ stackId = foundStackId;
60
+ s.stop(`Found stack ${stackId} for blueprint "${displayName}"`);
61
+ }
62
+ if (!stackId) {
63
+ s.stop(`${red('Failed')} to determine stack ID`);
64
+ return;
65
+ }
66
+ if (watchMode) {
67
+ // recent logs
68
+ s.start(`Fetching recent logs for stack ${stackId}`);
69
+ const { ok, logs, error } = await getLogs(stackId, projectId, config.token);
70
+ if (!ok) {
71
+ s.stop(`${red('Failed')} to retrieve logs`);
72
+ this.log(`Error: ${error || 'Unknown error'}`);
73
+ return;
74
+ }
75
+ s.stop(`${formatTitle('Blueprint', displayName)} ${bold('Live')} Logs`);
76
+ if (logs.length > 0) {
77
+ this.log('\nMost recent logs:');
78
+ const sortedLogs = [...logs].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
79
+ const recentLogs = sortedLogs.slice(-10);
80
+ for (const log of recentLogs) {
81
+ const date = new Date(log.timestamp);
82
+ const time = date.toLocaleTimeString();
83
+ const day = date.toLocaleDateString();
84
+ this.log(` ${bold(day)} ${yellow(time)} ${log.message}`);
85
+ }
86
+ }
87
+ else {
88
+ this.log(`No recent logs found for stack ${yellow(stackId)}`);
89
+ }
90
+ const onOpen = () => {
91
+ this.log(`\n${bold('Watching')} for new logs...`);
92
+ this.log(`Press ${bold('Ctrl+C')} to stop\n`);
93
+ };
94
+ let newestTimestamp = 0;
95
+ if (logs.length > 0) {
96
+ for (const log of logs) {
97
+ const timestamp = new Date(log.timestamp).getTime();
98
+ if (timestamp > newestTimestamp) {
99
+ newestTimestamp = timestamp;
100
+ }
101
+ }
102
+ }
103
+ const renderLog = (log) => {
104
+ const logTimestamp = new Date(log.timestamp).getTime();
105
+ if (logTimestamp <= newestTimestamp) {
106
+ return;
107
+ }
108
+ newestTimestamp = logTimestamp;
109
+ const date = new Date(log.timestamp);
110
+ const time = date.toLocaleTimeString();
111
+ const day = date.toLocaleDateString();
112
+ this.log(`${green('>')} ${bold(day)} ${yellow(time)} ${log.message}`);
113
+ };
114
+ this.debug(`${yellow('Debug:')} Connecting to streaming endpoint for stack ${stackId}...`);
115
+ streamLogs(stackId, projectId, config.token, renderLog, onOpen, (error) => this.log(`${red('Error:')} ${error}`));
116
+ return new Promise(() => {
117
+ // hold the line until the user terminates with Ctrl+C
118
+ });
119
+ }
120
+ s.start(`Fetching logs for stack ${stackId}`);
121
+ const { ok, logs, error } = await getLogs(stackId, projectId, config.token);
122
+ if (!ok) {
123
+ s.stop(`${red('Failed')} to retrieve logs`);
124
+ this.log(`Error: ${error || 'Unknown error'}`);
125
+ return;
126
+ }
127
+ if (logs.length === 0) {
128
+ s.stop(`No logs found for stack ${stackId}`);
129
+ return;
130
+ }
131
+ s.stop(`${formatTitle('Blueprint', displayName)} Logs`);
132
+ this.log(`Found ${bold(logs.length.toString())} log entries for stack ${yellow(stackId)}\n`);
133
+ const logsByDay = new Map();
134
+ for (const log of logs) {
135
+ const date = new Date(log.timestamp);
136
+ const day = date.toLocaleDateString();
137
+ if (!logsByDay.has(day)) {
138
+ logsByDay.set(day, []);
139
+ }
140
+ logsByDay.get(day)?.push(log);
141
+ }
142
+ const sortedDays = Array.from(logsByDay.keys()).sort((a, b) => {
143
+ return new Date(a).getTime() - new Date(b).getTime();
144
+ });
145
+ for (const day of sortedDays) {
146
+ this.log(`${blue('Date:')} ${bold(day)}`);
147
+ const dayLogs = logsByDay.get(day) || [];
148
+ dayLogs.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
149
+ for (const [i, log] of dayLogs.entries()) {
150
+ const date = new Date(log.timestamp);
151
+ const time = date.toLocaleTimeString();
152
+ const isLast = i === dayLogs.length - 1;
153
+ this.log(` ${isLast ? '└─' : '├─'} ${yellow(time)} ${log.message}`);
154
+ }
155
+ // new line between days
156
+ this.log('');
157
+ }
158
+ }
159
+ catch (err) {
160
+ s.stop('Failed to retrieve logs');
161
+ if (err instanceof Error) {
162
+ this.log(`Error: ${err.message}`);
163
+ }
164
+ }
165
+ }
166
+ }
@@ -1,31 +1,18 @@
1
1
  import { Command } from '@oclif/core';
2
- import readBlueprint from '../../actions/blueprints/read-blueprint.js';
2
+ import readBlueprintOnDisk from '../../actions/blueprints/read-blueprint.js';
3
+ import { formatResourceTree, formatTitle } from '../../utils/display/blueprints-formatting.js';
3
4
  export default class Plan extends Command {
4
5
  static description = 'Enumerate resources to be deployed - will not modify any resources';
5
6
  static examples = ['<%= config.bin %> <%= command.id %>'];
6
7
  async run() {
7
- const { displayName, resources } = readBlueprint();
8
- this.log(`Blueprint: "${displayName}"`);
9
- const functionResources = resources.filter((r) => r.kind === 'function');
10
- const otherResources = resources.filter((r) => r.kind === 'test');
11
- this.log('Tracked resources:');
12
- if (functionResources.length > 0) {
13
- this.log(`├─ Functions (${functionResources.length})`);
14
- for (const fn of functionResources) {
15
- this.log(`│ ├─ ${fn.name}: ${fn.src}`);
16
- }
17
- }
18
- if (otherResources.length > 0) {
19
- this.log(`└─Other (${otherResources.length})`);
20
- for (const other of otherResources) {
21
- this.log(` ├─ ${other.name}`);
22
- }
23
- }
24
- if (resources.length === 0) {
25
- this.log('No resources to deploy');
26
- }
27
- else {
28
- this.log('\nRun `blueprints deploy` to deploy these changes');
8
+ const { document, fileName } = readBlueprintOnDisk();
9
+ const { displayName, name, resources } = document;
10
+ this.log(`${formatTitle('Blueprint', displayName)} Plan\n`);
11
+ this.log(`Blueprint document: "${name}" (${fileName})`);
12
+ this.log('');
13
+ formatResourceTree(resources, this.log.bind(this));
14
+ if (resources.length > 0) {
15
+ this.log('\nRun `sanity blueprints deploy` to deploy these changes');
29
16
  }
30
17
  }
31
18
  }
package/dist/config.js CHANGED
@@ -9,8 +9,8 @@ const functionsUrls = {
9
9
  default: 'http://localhost:4567',
10
10
  };
11
11
  const blueprintsUrls = {
12
- production: 'https://api.sanity.io/',
13
- staging: 'https://api.sanity.work/',
12
+ production: 'https://api.sanity.io',
13
+ staging: 'https://api.sanity.work',
14
14
  default: 'http://localhost:4567',
15
15
  };
16
16
  const functionsUrl = new URL(functionsUrls[nodeEnv] ?? functionsUrls.default);
@@ -0,0 +1,4 @@
1
+ import type { BlueprintResource } from '../types.js';
2
+ export declare function formatTitle(title: string, name: string): string;
3
+ export declare function formatStacksList(stacks: string[]): string;
4
+ export declare function formatResourceTree(resources: BlueprintResource[], logger: (msg: string) => void): void;
@@ -0,0 +1,42 @@
1
+ import { blue, bold, boldnblue, green, yellow } from './colors.js';
2
+ export function formatTitle(title, name) {
3
+ return `${boldnblue(title)} ${bold(`"${name}"`)}`;
4
+ }
5
+ export function formatStacksList(stacks) {
6
+ if (!stacks || stacks.length === 0) {
7
+ return 'No stacks found';
8
+ }
9
+ let result = '\nFound Stacks:\n';
10
+ for (const name of stacks) {
11
+ result += ` ${name}\n`;
12
+ }
13
+ return result;
14
+ }
15
+ export function formatResourceTree(resources, logger) {
16
+ if (!resources || resources.length === 0) {
17
+ logger(' No resources in this stack');
18
+ return;
19
+ }
20
+ logger(`${blue('Stack Resources')} [${resources.length}]`);
21
+ const functionResources = resources.filter((r) => r.kind === 'function');
22
+ const otherResources = resources.filter((r) => r.kind !== 'function');
23
+ const hasOtherResources = otherResources.length > 0;
24
+ if (functionResources.length > 0) {
25
+ logger(` ${hasOtherResources ? '├─' : '└─'} ${bold('Functions')} [${functionResources.length}]`);
26
+ for (const [i, fn] of functionResources.entries()) {
27
+ const isLast = i === functionResources.length - 1;
28
+ const prefix = hasOtherResources ? '│' : ' ';
29
+ const connector = isLast ? '└─' : '├─';
30
+ const name = green(fn.displayName || fn.name);
31
+ const externalId = fn.externalId ? `Fn<${yellow(fn.externalId)}>` : '';
32
+ logger(` ${prefix} ${connector} "${name}" ${externalId}`);
33
+ }
34
+ }
35
+ if (hasOtherResources) {
36
+ logger(` └─ ${bold('Other Resources')} [${otherResources.length}]`);
37
+ for (const [i, other] of otherResources.entries()) {
38
+ const isLast = i === otherResources.length - 1;
39
+ logger(` ${isLast ? '└─' : '├─'} "${yellow(other.displayName || other.name || other.src)}"`);
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,6 @@
1
+ export declare function bold(str: string): string;
2
+ export declare function blue(str: string): string;
3
+ export declare function green(str: string): string;
4
+ export declare function red(str: string): string;
5
+ export declare function yellow(str: string): string;
6
+ export declare function boldnblue(str: string): string;
@@ -0,0 +1,18 @@
1
+ export function bold(str) {
2
+ return `\x1b[1m${str}\x1b[0m`;
3
+ }
4
+ export function blue(str) {
5
+ return `\x1b[34m${str}\x1b[0m`;
6
+ }
7
+ export function green(str) {
8
+ return `\x1b[32m${str}\x1b[0m`;
9
+ }
10
+ export function red(str) {
11
+ return `\x1b[31m${str}\x1b[0m`;
12
+ }
13
+ export function yellow(str) {
14
+ return `\x1b[33m${str}\x1b[0m`;
15
+ }
16
+ export function boldnblue(str) {
17
+ return bold(blue(str));
18
+ }
@@ -0,0 +1,2 @@
1
+ export declare function formatDate(dateString: string): string;
2
+ export declare function formatDuration(startDate: string, endDate: string): string;
@@ -0,0 +1,26 @@
1
+ export function formatDate(dateString) {
2
+ const date = new Date(dateString);
3
+ return date.toLocaleString();
4
+ }
5
+ export function formatDuration(startDate, endDate) {
6
+ const start = new Date(startDate).getTime();
7
+ const end = new Date(endDate).getTime();
8
+ if (start > end) {
9
+ return 'Invalid duration';
10
+ }
11
+ const durationMs = end - start;
12
+ if (durationMs < 1000) {
13
+ return `${durationMs}ms`;
14
+ }
15
+ if (durationMs < 60000) {
16
+ return `${Math.round(durationMs / 1000)}s`;
17
+ }
18
+ if (durationMs < 3600000) {
19
+ const minutes = Math.floor(durationMs / 60000);
20
+ const seconds = Math.floor((durationMs % 60000) / 1000);
21
+ return `${minutes}m ${seconds}s`;
22
+ }
23
+ const hours = Math.floor(durationMs / 3600000);
24
+ const minutes = Math.floor((durationMs % 3600000) / 60000);
25
+ return `${hours}h ${minutes}m`;
26
+ }
@@ -48,17 +48,36 @@ export interface BlueprintJob {
48
48
  * @internal
49
49
  */
50
50
  export interface BlueprintResource {
51
+ id: string;
52
+ displayName: string;
51
53
  name: string;
52
54
  kind: string;
53
55
  src: string;
54
- assetId: string;
56
+ externalId: string;
55
57
  }
56
58
  /**
57
59
  * @internal
58
60
  */
61
+ export interface BlueprintOperation {
62
+ id: string;
63
+ status: string;
64
+ createdAt?: string;
65
+ completedAt?: string;
66
+ }
59
67
  export interface BlueprintStack {
68
+ id: string;
60
69
  name: string;
61
- kind: string;
62
- src: string;
70
+ displayName: string;
71
+ resources: Array<BlueprintResource>;
72
+ createdAt?: string;
73
+ updatedAt?: string;
74
+ recentOperation?: BlueprintOperation;
75
+ }
76
+ /**
77
+ * @internal
78
+ */
79
+ export interface BlueprintLog {
80
+ timestamp: string;
81
+ message: string;
63
82
  }
64
83
  export {};
@@ -48,6 +48,50 @@
48
48
  "info.js"
49
49
  ]
50
50
  },
51
+ "blueprints:logs": {
52
+ "aliases": [],
53
+ "args": {},
54
+ "description": "Display logs for a Blueprint stack",
55
+ "examples": [
56
+ "<%= config.bin %> <%= command.id %>",
57
+ "<%= config.bin %> <%= command.id %> --stack-id <stack-id>",
58
+ "<%= config.bin %> <%= command.id %> --watch"
59
+ ],
60
+ "flags": {
61
+ "stack-id": {
62
+ "char": "s",
63
+ "description": "Stack ID to fetch logs for (optional if running in a blueprint directory)",
64
+ "name": "stack-id",
65
+ "required": false,
66
+ "hasDynamicHelp": false,
67
+ "multiple": false,
68
+ "type": "option"
69
+ },
70
+ "watch": {
71
+ "char": "w",
72
+ "description": "Watch for new logs (streaming mode)",
73
+ "name": "watch",
74
+ "required": false,
75
+ "allowNo": false,
76
+ "type": "boolean"
77
+ }
78
+ },
79
+ "hasDynamicHelp": false,
80
+ "hiddenAliases": [],
81
+ "id": "blueprints:logs",
82
+ "pluginAlias": "@sanity/runtime-cli",
83
+ "pluginName": "@sanity/runtime-cli",
84
+ "pluginType": "core",
85
+ "strict": true,
86
+ "enableJsonFlag": false,
87
+ "isESM": true,
88
+ "relativePath": [
89
+ "dist",
90
+ "commands",
91
+ "blueprints",
92
+ "logs.js"
93
+ ]
94
+ },
51
95
  "blueprints:plan": {
52
96
  "aliases": [],
53
97
  "args": {},
@@ -247,5 +291,5 @@
247
291
  ]
248
292
  }
249
293
  },
250
- "version": "1.2.1"
294
+ "version": "1.3.0"
251
295
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sanity/runtime-cli",
3
3
  "description": "A new CLI generated with oclif",
4
- "version": "1.2.1",
4
+ "version": "1.3.0",
5
5
  "author": "Sanity Runtime Team",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -22,7 +22,8 @@
22
22
  "sanity": "./bin/run.js"
23
23
  },
24
24
  "scripts": {
25
- "build": "rollup -c && shx rm -rf dist && tsc -b && npm run build:static",
25
+ "build": "rollup -c && npm run build:bin && npm run build:static",
26
+ "build:bin": "shx rm -rf dist && tsc -b",
26
27
  "build:static": "npm run copy:wrapper && npm run copy:server",
27
28
  "copy:server": "shx cp -r ./src/server/static ./dist/server/static",
28
29
  "copy:wrapper": "shx cp ./src/utils/child-process-wrapper.js ./dist/utils/child-process-wrapper.js",
@@ -38,6 +39,7 @@
38
39
  "@oclif/core": "^4",
39
40
  "@oclif/plugin-help": "^6",
40
41
  "@oclif/plugin-plugins": "^5",
42
+ "eventsource": "^3.0.5",
41
43
  "jszip": "^3.10.1",
42
44
  "mime-types": "^2.1.35",
43
45
  "xdg-basedir": "^5.1.0"