@sanity/runtime-cli 10.9.0 → 10.9.2

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-run COMMAND
21
21
  running command...
22
22
  $ sanity-run (--version)
23
- @sanity/runtime-cli/10.9.0 linux-x64 node-v22.19.0
23
+ @sanity/runtime-cli/10.9.2 linux-x64 node-v22.19.0
24
24
  $ sanity-run --help [COMMAND]
25
25
  USAGE
26
26
  $ sanity-run COMMAND
@@ -88,7 +88,7 @@ EXAMPLES
88
88
  $ sanity-run blueprints add function --name my-function --fn-type document-create --fn-type document-update --lang js
89
89
  ```
90
90
 
91
- _See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/blueprints/add.ts)_
91
+ _See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/blueprints/add.ts)_
92
92
 
93
93
  ## `sanity-run blueprints config`
94
94
 
@@ -119,7 +119,7 @@ EXAMPLES
119
119
  $ sanity-run blueprints config --edit --project-id <projectId> --stack-id <stackId>
120
120
  ```
121
121
 
122
- _See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/blueprints/config.ts)_
122
+ _See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/blueprints/config.ts)_
123
123
 
124
124
  ## `sanity-run blueprints deploy`
125
125
 
@@ -141,7 +141,7 @@ EXAMPLES
141
141
  $ sanity-run blueprints deploy --no-wait
142
142
  ```
143
143
 
144
- _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/blueprints/deploy.ts)_
144
+ _See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/blueprints/deploy.ts)_
145
145
 
146
146
  ## `sanity-run blueprints destroy`
147
147
 
@@ -166,7 +166,7 @@ EXAMPLES
166
166
  $ sanity-run blueprints destroy --stack-id <stackId> --project-id <projectId> --force --no-wait
167
167
  ```
168
168
 
169
- _See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/blueprints/destroy.ts)_
169
+ _See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/blueprints/destroy.ts)_
170
170
 
171
171
  ## `sanity-run blueprints info`
172
172
 
@@ -188,7 +188,7 @@ EXAMPLES
188
188
  $ sanity-run blueprints info --stack-id <stackId>
189
189
  ```
190
190
 
191
- _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/blueprints/info.ts)_
191
+ _See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/blueprints/info.ts)_
192
192
 
193
193
  ## `sanity-run blueprints init [DIR]`
194
194
 
@@ -226,7 +226,7 @@ EXAMPLES
226
226
  $ sanity-run blueprints init --blueprint-type <json|js|ts> --stack-name <stackName>
227
227
  ```
228
228
 
229
- _See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/blueprints/init.ts)_
229
+ _See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/blueprints/init.ts)_
230
230
 
231
231
  ## `sanity-run blueprints logs`
232
232
 
@@ -248,7 +248,7 @@ EXAMPLES
248
248
  $ sanity-run blueprints logs --watch
249
249
  ```
250
250
 
251
- _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/blueprints/logs.ts)_
251
+ _See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/blueprints/logs.ts)_
252
252
 
253
253
  ## `sanity-run blueprints plan`
254
254
 
@@ -265,7 +265,7 @@ EXAMPLES
265
265
  $ sanity-run blueprints plan
266
266
  ```
267
267
 
268
- _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/blueprints/plan.ts)_
268
+ _See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/blueprints/plan.ts)_
269
269
 
270
270
  ## `sanity-run blueprints stacks`
271
271
 
@@ -287,7 +287,7 @@ EXAMPLES
287
287
  $ sanity-run blueprints stacks --project-id <projectId>
288
288
  ```
289
289
 
290
- _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/blueprints/stacks.ts)_
290
+ _See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/blueprints/stacks.ts)_
291
291
 
292
292
  ## `sanity-run functions dev`
293
293
 
@@ -307,7 +307,7 @@ EXAMPLES
307
307
  $ sanity-run functions dev --port 8974
308
308
  ```
309
309
 
310
- _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/functions/dev.ts)_
310
+ _See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/functions/dev.ts)_
311
311
 
312
312
  ## `sanity-run functions env add NAME KEY VALUE`
313
313
 
@@ -329,7 +329,7 @@ EXAMPLES
329
329
  $ sanity-run functions env add MyFunction API_URL https://api.example.com/
330
330
  ```
331
331
 
332
- _See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/functions/env/add.ts)_
332
+ _See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/functions/env/add.ts)_
333
333
 
334
334
  ## `sanity-run functions env list NAME`
335
335
 
@@ -349,7 +349,7 @@ EXAMPLES
349
349
  $ sanity-run functions env list MyFunction
350
350
  ```
351
351
 
352
- _See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/functions/env/list.ts)_
352
+ _See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/functions/env/list.ts)_
353
353
 
354
354
  ## `sanity-run functions env remove NAME KEY`
355
355
 
@@ -370,7 +370,7 @@ EXAMPLES
370
370
  $ sanity-run functions env remove MyFunction API_URL
371
371
  ```
372
372
 
373
- _See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/functions/env/remove.ts)_
373
+ _See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/functions/env/remove.ts)_
374
374
 
375
375
  ## `sanity-run functions logs NAME`
376
376
 
@@ -404,7 +404,7 @@ EXAMPLES
404
404
  $ sanity-run functions logs <name> --delete
405
405
  ```
406
406
 
407
- _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/functions/logs.ts)_
407
+ _See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/functions/logs.ts)_
408
408
 
409
409
  ## `sanity-run functions test NAME`
410
410
 
@@ -451,7 +451,7 @@ EXAMPLES
451
451
  $ sanity-run functions test <name> --event update --data-before '{ "title": "before" }' --data-after '{ "title": "after" }'
452
452
  ```
453
453
 
454
- _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.0/src/commands/functions/test.ts)_
454
+ _See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v10.9.2/src/commands/functions/test.ts)_
455
455
 
456
456
  ## `sanity-run help [COMMAND]`
457
457
 
@@ -1,15 +1,14 @@
1
1
  import { type Blueprint } from '@sanity/blueprints-parser';
2
2
  import type { BlueprintParserError, Resource } from '../../utils/types.js';
3
- export { BLUEPRINT_CONFIG_FILE, BLUEPRINT_CONFIG_VERSION, BLUEPRINT_DIR } from '../../config.js';
3
+ import { type ScopeType } from '../../utils/types.js';
4
4
  declare const SUPPORTED_FILE_EXTENSIONS: readonly [".json", ".js", ".mjs", ".ts"];
5
5
  type BlueprintFileExtension = (typeof SUPPORTED_FILE_EXTENSIONS)[number];
6
6
  export declare const JSON_BLUEPRINT_CONTENT: {
7
7
  blueprintVersion: string;
8
8
  resources: never[];
9
9
  };
10
- export declare const TS_BLUEPRINT_CONTENT = "import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'\n\nexport default defineBlueprint({\n resources: [\n // defineDocumentFunction({name: 'my-function'}),\n ],\n})\n";
11
- export declare const GITIGNORE_FOR_FUNCTIONS = "\n# Sanity Functions\nfunctions/**/.env*\nfunctions/**/.build/\nfunctions/**/node_modules/\n";
12
- export declare const GITIGNORE_TEMPLATE = "node_modules\n.env\n\n# Sanity Functions\nfunctions/**/.env*\nfunctions/**/.build/\nfunctions/**/node_modules/\n\n";
10
+ export declare const TS_BLUEPRINT_CONTENT: string;
11
+ /** Function type with attached attributes */
13
12
  export type BlueprintModule = ((args?: unknown) => Record<string, unknown>) & {
14
13
  organizationId?: string;
15
14
  projectId?: string;
@@ -26,20 +25,21 @@ type FileInfo = {
26
25
  * @returns The path, file name, and extension of the blueprint file
27
26
  */
28
27
  export declare function findBlueprintFile(blueprintPath?: string): FileInfo | null;
29
- /**
30
- * Result of the blueprint read operation
31
- */
28
+ /** Result of the blueprint read operation */
32
29
  export interface ReadBlueprintResult {
33
30
  fileInfo: FileInfo;
34
31
  rawBlueprint: Record<string, unknown>;
35
32
  parsedBlueprint: Blueprint;
36
33
  errors: BlueprintParserError[];
37
34
  configPath?: string;
35
+ scopeType?: ScopeType;
36
+ scopeId?: string;
37
+ organizationId?: string;
38
38
  projectId?: string;
39
39
  stackId?: string;
40
40
  }
41
41
  /**
42
- * Reads the blueprint file from disk and parses it. Greedily looks for project and stack config
42
+ * Reads the blueprint file from disk and parses it. Greedily looks for Blueprint config
43
43
  * @param blueprintPath - The path of the blueprint file or directory- will search up the directory tree!
44
44
  * @returns Known information about the Blueprint, config, and Stack
45
45
  */
@@ -48,24 +48,8 @@ export declare function writeBlueprintToDisk({ blueprintFilePath, jsonContent, }
48
48
  blueprintFilePath: string;
49
49
  jsonContent?: Blueprint;
50
50
  }): string;
51
- export declare function writeOrUpdateNodeDependency({ blueprintFilePath, dependency, }: {
52
- blueprintFilePath: string;
53
- dependency: string;
54
- }): Promise<void>;
55
- export declare function readConfigFile(blueprintFilePath: string): {
56
- configPath: string;
57
- projectId?: string;
58
- stackId?: string;
59
- } | null;
60
- export declare function writeConfigFile({ blueprintFilePath, projectId, stackId, }: {
61
- blueprintFilePath?: string;
62
- projectId: string;
63
- stackId?: string;
64
- }): void;
65
- export declare function writeGitignoreFile({ blueprintFilePath, }: {
66
- blueprintFilePath: string;
67
- }): string | null;
68
51
  export declare function addResourceToBlueprint({ blueprintFilePath, resource, }: {
69
52
  blueprintFilePath?: string;
70
53
  resource: Resource;
71
54
  }): Resource | undefined;
55
+ export {};
@@ -1,14 +1,12 @@
1
1
  import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
- import { basename, dirname, extname, join } from 'node:path';
2
+ import { basename, dirname, extname } from 'node:path';
3
3
  import { cwd, env } from 'node:process';
4
4
  import blueprintParserValidator from '@sanity/blueprints-parser';
5
5
  import { findUpSync } from 'find-up';
6
6
  import { createJiti } from 'jiti';
7
- import { BLUEPRINT_CONFIG_FILE, BLUEPRINT_CONFIG_VERSION, BLUEPRINT_DIR } from '../../config.js';
8
- import { getLatestNpmVersion } from '../../utils/other/npmjs.js';
9
7
  import { isLocalFunctionResource } from '../../utils/types.js';
10
8
  import { validateFunctionResource } from '../../utils/validate/resource.js';
11
- export { BLUEPRINT_CONFIG_FILE, BLUEPRINT_CONFIG_VERSION, BLUEPRINT_DIR } from '../../config.js';
9
+ import { readConfigFile } from './config.js';
12
10
  const SUPPORTED_FILE_EXTENSIONS = ['.json', '.js', '.mjs', '.ts'];
13
11
  let SUPPORTED_FILE_NAMES = SUPPORTED_FILE_EXTENSIONS.map((ext) => `blueprint${ext}`);
14
12
  SUPPORTED_FILE_NAMES = [
@@ -19,24 +17,15 @@ export const JSON_BLUEPRINT_CONTENT = {
19
17
  blueprintVersion: '2024-10-01',
20
18
  resources: [],
21
19
  };
22
- export const TS_BLUEPRINT_CONTENT = `import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'
20
+ export const TS_BLUEPRINT_CONTENT = `
21
+ import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'
23
22
 
24
23
  export default defineBlueprint({
25
24
  resources: [
26
25
  // defineDocumentFunction({name: 'my-function'}),
27
26
  ],
28
27
  })
29
- `;
30
- export const GITIGNORE_FOR_FUNCTIONS = `
31
- # Sanity Functions
32
- functions/**/.env*
33
- functions/**/.build/
34
- functions/**/node_modules/
35
- `;
36
- export const GITIGNORE_TEMPLATE = `node_modules
37
- .env
38
- ${GITIGNORE_FOR_FUNCTIONS}
39
- `;
28
+ `.trim();
40
29
  /**
41
30
  * Finds the blueprint file in the given path or current working directory
42
31
  * @param blueprintPath - The path of the blueprint file or directory
@@ -74,7 +63,7 @@ export function findBlueprintFile(blueprintPath) {
74
63
  };
75
64
  }
76
65
  /**
77
- * Reads the blueprint file from disk and parses it. Greedily looks for project and stack config
66
+ * Reads the blueprint file from disk and parses it. Greedily looks for Blueprint config
78
67
  * @param blueprintPath - The path of the blueprint file or directory- will search up the directory tree!
79
68
  * @returns Known information about the Blueprint, config, and Stack
80
69
  */
@@ -111,11 +100,20 @@ export async function readLocalBlueprint(blueprintPath) {
111
100
  catch (err) {
112
101
  throw Error(`Unable to parse Blueprint file: ${fileName}\n${err}`);
113
102
  }
103
+ /**
104
+ * Org, Project, Stack IDs can be set...
105
+ * - in the environment,
106
+ * - on the blueprint module,
107
+ * - and in the config file.
108
+ * The precedence is: environment > blueprint module > config file
109
+ */
110
+ let moduleOrganizationId;
114
111
  let moduleProjectId;
115
112
  let moduleStackId;
116
113
  if (blueprintModule) {
117
114
  if (typeof blueprintModule === 'function') {
118
115
  try {
116
+ moduleOrganizationId = blueprintModule.organizationId;
119
117
  moduleProjectId = blueprintModule.projectId;
120
118
  moduleStackId = blueprintModule.stackId;
121
119
  rawBlueprint = blueprintModule();
@@ -131,19 +129,16 @@ export async function readLocalBlueprint(blueprintPath) {
131
129
  if (typeof rawBlueprint === 'undefined') {
132
130
  throw Error('Unable to read Blueprint');
133
131
  }
134
- const parserResult = blueprintParserValidator(rawBlueprint);
135
- const parsedBlueprint = parserResult.result === 'valid' ? parserResult.blueprint : undefined;
136
- const errors = parserResult.result !== 'valid' ? parserResult.errors : [];
137
- // further validation - remove once validator is updated
138
- if (parsedBlueprint?.resources) {
139
- // validate function resources
140
- const functionResources = parsedBlueprint.resources.filter(isLocalFunctionResource);
141
- const fnErrors = functionResources.map((r) => validateFunctionResource(r));
142
- errors.push(...fnErrors.flat());
143
- }
144
- const { SANITY_PROJECT_ID: envProjectId, SANITY_BLUEPRINT_STACK_ID: envStackId } = env;
132
+ const { SANITY_ORGANIZATION_ID: envOrganizationId, SANITY_PROJECT_ID: envProjectId, SANITY_BLUEPRINT_STACK_ID: envStackId, } = env;
145
133
  const configIds = readConfigFile(foundFilePath);
146
134
  const configPath = configIds?.configPath;
135
+ let organizationId;
136
+ if (envOrganizationId)
137
+ organizationId = envOrganizationId;
138
+ else if (moduleOrganizationId)
139
+ organizationId = moduleOrganizationId;
140
+ else if (configIds?.organizationId)
141
+ organizationId = configIds.organizationId;
147
142
  let projectId;
148
143
  if (envProjectId)
149
144
  projectId = envProjectId;
@@ -161,10 +156,34 @@ export async function readLocalBlueprint(blueprintPath) {
161
156
  // LAUNCH LIMIT: 1 Stack per Project - infer stackId from projectId
162
157
  if (!stackId && projectId)
163
158
  stackId = `ST-${projectId}`;
159
+ let scopeType;
160
+ let scopeId;
161
+ // Scope is as specific as possible; project > organization
162
+ if (projectId) {
163
+ scopeType = 'project';
164
+ scopeId = projectId;
165
+ }
166
+ else if (organizationId) {
167
+ scopeType = 'organization';
168
+ scopeId = organizationId;
169
+ }
170
+ const parserResult = blueprintParserValidator(rawBlueprint);
171
+ const parsedBlueprint = parserResult.result === 'valid' ? parserResult.blueprint : undefined;
172
+ const errors = parserResult.result !== 'valid' ? parserResult.errors : [];
173
+ // further validation - remove if validator handles specific resource type validation
174
+ if (parsedBlueprint?.resources) {
175
+ // validate function resources
176
+ const functionResources = parsedBlueprint.resources.filter(isLocalFunctionResource);
177
+ const fnErrors = functionResources.map((r) => validateFunctionResource(r));
178
+ errors.push(...fnErrors.flat());
179
+ }
164
180
  return {
165
181
  fileInfo: { blueprintFilePath: foundFilePath, fileName, extension },
166
182
  rawBlueprint,
167
183
  errors,
184
+ scopeType,
185
+ scopeId,
186
+ organizationId,
168
187
  projectId,
169
188
  stackId,
170
189
  configPath,
@@ -195,93 +214,6 @@ export function writeBlueprintToDisk({ blueprintFilePath, jsonContent = JSON_BLU
195
214
  writeFileSync(blueprintFilePath, blueprintContent);
196
215
  return blueprintContent;
197
216
  }
198
- export async function writeOrUpdateNodeDependency({ blueprintFilePath, dependency, }) {
199
- const dir = dirname(blueprintFilePath);
200
- const extension = extname(blueprintFilePath);
201
- if (extension === '.json')
202
- return;
203
- const version = await getLatestNpmVersion(dependency);
204
- const packageJsonPath = join(dir, 'package.json');
205
- const packageExists = existsSync(packageJsonPath);
206
- if (!packageExists) {
207
- writeFileSync(packageJsonPath, JSON.stringify({ type: 'module', devDependencies: { [dependency]: version } }, null, 2));
208
- return;
209
- }
210
- const packageJson = readFileSync(packageJsonPath, 'utf8');
211
- let packageJsonObject;
212
- try {
213
- packageJsonObject = JSON.parse(packageJson);
214
- }
215
- catch (err) {
216
- throw Error(`Unable to parse package.json: ${err}`);
217
- }
218
- const allDependencies = { ...packageJsonObject.dependencies, ...packageJsonObject.devDependencies };
219
- if (allDependencies[dependency])
220
- return;
221
- packageJsonObject.devDependencies = packageJsonObject.devDependencies || {};
222
- packageJsonObject.devDependencies[dependency] = version;
223
- writeFileSync(packageJsonPath, JSON.stringify(packageJsonObject, null, 2));
224
- }
225
- export function readConfigFile(blueprintFilePath) {
226
- const blueprintDir = dirname(blueprintFilePath);
227
- const configPath = join(blueprintDir, BLUEPRINT_DIR, BLUEPRINT_CONFIG_FILE);
228
- if (existsSync(configPath)) {
229
- try {
230
- const config = JSON.parse(readFileSync(configPath, 'utf8'));
231
- return { configPath, ...config };
232
- }
233
- catch {
234
- return null;
235
- }
236
- }
237
- const configFilePath = join(cwd(), BLUEPRINT_DIR, BLUEPRINT_CONFIG_FILE);
238
- if (!existsSync(configFilePath))
239
- return null;
240
- try {
241
- const config = JSON.parse(readFileSync(configFilePath, 'utf8'));
242
- return config || null;
243
- }
244
- catch {
245
- return null;
246
- }
247
- }
248
- export function writeConfigFile({ blueprintFilePath, projectId, stackId, }) {
249
- const blueprintDir = blueprintFilePath ? dirname(blueprintFilePath) : cwd();
250
- const configDir = join(blueprintDir, BLUEPRINT_DIR);
251
- const configPath = join(configDir, BLUEPRINT_CONFIG_FILE);
252
- if (!existsSync(configDir)) {
253
- mkdirSync(configDir, { recursive: true });
254
- }
255
- let config = {};
256
- if (existsSync(configPath)) {
257
- try {
258
- config = JSON.parse(readFileSync(configPath, 'utf8'));
259
- }
260
- catch {
261
- // config broken, start fresh
262
- }
263
- }
264
- config.projectId = projectId;
265
- config.stackId = stackId;
266
- config.blueprintConfigVersion = BLUEPRINT_CONFIG_VERSION;
267
- config.updatedAt = Date.now();
268
- writeFileSync(configPath, JSON.stringify(config, null, 2));
269
- }
270
- export function writeGitignoreFile({ blueprintFilePath, }) {
271
- const blueprintDir = blueprintFilePath ? dirname(blueprintFilePath) : cwd();
272
- const gitignorePath = join(blueprintDir, '.gitignore');
273
- const gitignoreExists = existsSync(gitignorePath);
274
- let content = GITIGNORE_TEMPLATE;
275
- if (gitignoreExists) {
276
- // append GITIGNORE_FOR_FUNCTIONS to existing .gitignore
277
- const existingContent = readFileSync(gitignorePath, 'utf8').toString();
278
- if (existingContent.includes(GITIGNORE_FOR_FUNCTIONS))
279
- return null;
280
- content = `${existingContent}\n${GITIGNORE_FOR_FUNCTIONS}`;
281
- }
282
- writeFileSync(gitignorePath, content);
283
- return content;
284
- }
285
217
  export function addResourceToBlueprint({ blueprintFilePath, resource, }) {
286
218
  const blueprintFile = findBlueprintFile(blueprintFilePath);
287
219
  if (!blueprintFile)
@@ -0,0 +1,17 @@
1
+ export interface BlueprintsConfig {
2
+ configPath: string;
3
+ organizationId?: string;
4
+ projectId?: string;
5
+ stackId?: string;
6
+ }
7
+ export declare function readConfigFile(blueprintFilePath: string): BlueprintsConfig | null;
8
+ export declare function writeConfigFile({ blueprintFilePath, organizationId, projectId, stackId, }: {
9
+ blueprintFilePath?: string;
10
+ stackId?: string;
11
+ } & ({
12
+ organizationId: string;
13
+ projectId?: never;
14
+ } | {
15
+ projectId: string;
16
+ organizationId?: never;
17
+ })): void;
@@ -0,0 +1,50 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { cwd } from 'node:process';
4
+ import { BLUEPRINT_CONFIG_FILE, BLUEPRINT_CONFIG_VERSION, BLUEPRINT_DIR } from '../../config.js';
5
+ export function readConfigFile(blueprintFilePath) {
6
+ const blueprintDir = dirname(blueprintFilePath);
7
+ const configPath = join(blueprintDir, BLUEPRINT_DIR, BLUEPRINT_CONFIG_FILE);
8
+ if (existsSync(configPath)) {
9
+ try {
10
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
11
+ return { configPath, ...config };
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ const configFilePath = join(cwd(), BLUEPRINT_DIR, BLUEPRINT_CONFIG_FILE);
18
+ if (!existsSync(configFilePath))
19
+ return null;
20
+ try {
21
+ const config = JSON.parse(readFileSync(configFilePath, 'utf8'));
22
+ return config || null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ export function writeConfigFile({ blueprintFilePath, organizationId, projectId, stackId, }) {
29
+ const blueprintDir = blueprintFilePath ? dirname(blueprintFilePath) : cwd();
30
+ const configDir = join(blueprintDir, BLUEPRINT_DIR);
31
+ const configPath = join(configDir, BLUEPRINT_CONFIG_FILE);
32
+ if (!existsSync(configDir)) {
33
+ mkdirSync(configDir, { recursive: true });
34
+ }
35
+ let config = {};
36
+ if (existsSync(configPath)) {
37
+ try {
38
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
39
+ }
40
+ catch {
41
+ // config broken, start fresh
42
+ }
43
+ }
44
+ config.organizationId = organizationId;
45
+ config.projectId = projectId;
46
+ config.stackId = stackId;
47
+ config.blueprintConfigVersion = BLUEPRINT_CONFIG_VERSION;
48
+ config.updatedAt = Date.now();
49
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
50
+ }
@@ -4,6 +4,7 @@
4
4
  export * as projects from '../sanity/projects.js';
5
5
  export * as assets from './assets.js';
6
6
  export * as blueprint from './blueprint.js';
7
+ export * as config from './config.js';
7
8
  export * as logs from './logs.js';
8
9
  export * as resources from './resources.js';
9
10
  export * as stacks from './stacks.js';
@@ -4,6 +4,7 @@
4
4
  export * as projects from '../sanity/projects.js';
5
5
  export * as assets from './assets.js';
6
6
  export * as blueprint from './blueprint.js';
7
+ export * as config from './config.js';
7
8
  export * as logs from './logs.js';
8
9
  export * as resources from './resources.js';
9
10
  export * as stacks from './stacks.js';
@@ -15,6 +15,7 @@ export interface StreamLogsOptions {
15
15
  onError: (error: string) => void;
16
16
  }
17
17
  export declare function streamLogs({ stackId, after, auth, onLog, onOpen, onError, }: StreamLogsOptions): () => void;
18
+ /** Check if a log is newer than a given timestamp */
18
19
  export declare function isNewerLog(log: BlueprintLog, timestamp: number): boolean;
19
20
  /**
20
21
  * Sets up log streaming for operations like deploy or destroy with spinner integration
@@ -59,7 +59,7 @@ export function streamLogs({ stackId, after, auth, onLog, onOpen, onError, }) {
59
59
  eventSource.close();
60
60
  };
61
61
  }
62
- // Check if a log is newer than a given timestamp
62
+ /** Check if a log is newer than a given timestamp */
63
63
  export function isNewerLog(log, timestamp) {
64
64
  const logTimestamp = new Date(log.timestamp).getTime();
65
65
  return logTimestamp > timestamp;
@@ -78,12 +78,16 @@ export async function setupLogStreaming(config) {
78
78
  newestTimestamp = new Date(logEntry.timestamp).getTime();
79
79
  log(formatLogEntry(logEntry, true));
80
80
  };
81
+ let alreadyOpened = false;
81
82
  const onStreamOpen = () => {
82
- if (showBanner)
83
+ if (!alreadyOpened && showBanner)
83
84
  log(`Streaming logs... ${chalk.bold('ctrl+c')} to cancel`);
85
+ if (alreadyOpened)
86
+ log(`${chalk.green('Reconnected')}`);
87
+ alreadyOpened = true;
84
88
  };
85
89
  const onStreamError = (error) => {
86
- log(`${chalk.yellow('Stream error:')} ${error}`);
90
+ log(`${chalk.red('Stream error:')} ${error}`);
87
91
  };
88
92
  return streamLogs({
89
93
  stackId,
@@ -3,7 +3,8 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { cwd } from 'node:process';
5
5
  import chalk from 'chalk';
6
- import { addResourceToBlueprint, writeOrUpdateNodeDependency } from './blueprint.js';
6
+ import { writeOrUpdateNodeDependency } from '../node.js';
7
+ import { addResourceToBlueprint } from './blueprint.js';
7
8
  const DEFAULT_FUNCTION_TEMPLATE = /*js*/ `export async function handler({context, event}) {
8
9
  const time = new Date().toLocaleTimeString()
9
10
  console.log(\`👋 Your Sanity Function was called at \${time}\`)
@@ -43,7 +44,7 @@ export async function createFunctionResource(options) {
43
44
  writeFileSync(indexPath, addHelpers ? DEFAULT_HELPER_FUNCTION_TEMPLATE : DEFAULT_FUNCTION_TEMPLATE);
44
45
  if (addHelpers && blueprintFilePath) {
45
46
  try {
46
- await writeOrUpdateNodeDependency({ blueprintFilePath, dependency: '@sanity/functions' });
47
+ await writeOrUpdateNodeDependency(blueprintFilePath, '@sanity/functions');
47
48
  }
48
49
  catch (err) {
49
50
  throw new Error('Unable to add @sanity/functions to your project.', { cause: err });
@@ -0,0 +1,3 @@
1
+ export declare const GITIGNORE_FOR_FUNCTIONS = "\n# Sanity Functions\nfunctions/**/.env*\nfunctions/**/.build/\nfunctions/**/node_modules/\n";
2
+ export declare const GITIGNORE_TEMPLATE = "node_modules\n.env\n\n# Sanity Functions\nfunctions/**/.env*\nfunctions/**/.build/\nfunctions/**/node_modules/\n\n";
3
+ export declare function writeGitignoreFile(nearFilePath: string): string | null;
@@ -0,0 +1,28 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { cwd } from 'node:process';
4
+ export const GITIGNORE_FOR_FUNCTIONS = `
5
+ # Sanity Functions
6
+ functions/**/.env*
7
+ functions/**/.build/
8
+ functions/**/node_modules/
9
+ `;
10
+ export const GITIGNORE_TEMPLATE = `node_modules
11
+ .env
12
+ ${GITIGNORE_FOR_FUNCTIONS}
13
+ `;
14
+ export function writeGitignoreFile(nearFilePath) {
15
+ const dir = nearFilePath ? dirname(nearFilePath) : cwd();
16
+ const gitignorePath = join(dir, '.gitignore');
17
+ const gitignoreExists = existsSync(gitignorePath);
18
+ let content = GITIGNORE_TEMPLATE;
19
+ if (gitignoreExists) {
20
+ // append GITIGNORE_FOR_FUNCTIONS to existing .gitignore
21
+ const existingContent = readFileSync(gitignorePath, 'utf8').toString();
22
+ if (existingContent.includes(GITIGNORE_FOR_FUNCTIONS))
23
+ return null;
24
+ content = `${existingContent}\n${GITIGNORE_FOR_FUNCTIONS}`;
25
+ }
26
+ writeFileSync(gitignorePath, content);
27
+ return content;
28
+ }
@@ -0,0 +1 @@
1
+ export declare function writeOrUpdateNodeDependency(nearFilePath: string, dependency: string): Promise<void>;
@@ -0,0 +1,30 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, extname, join } from 'node:path';
3
+ import { getLatestNpmVersion } from '../utils/other/npmjs.js';
4
+ export async function writeOrUpdateNodeDependency(nearFilePath, dependency) {
5
+ const dir = dirname(nearFilePath);
6
+ const extension = extname(nearFilePath);
7
+ if (extension === '.json')
8
+ return; // ? why do we bail out here?
9
+ const version = await getLatestNpmVersion(dependency);
10
+ const packageJsonPath = join(dir, 'package.json');
11
+ const packageExists = existsSync(packageJsonPath);
12
+ if (!packageExists) {
13
+ writeFileSync(packageJsonPath, JSON.stringify({ type: 'module', devDependencies: { [dependency]: version } }, null, 2));
14
+ return;
15
+ }
16
+ const packageJson = readFileSync(packageJsonPath, 'utf8');
17
+ let packageJsonObject;
18
+ try {
19
+ packageJsonObject = JSON.parse(packageJson);
20
+ }
21
+ catch (err) {
22
+ throw Error(`Unable to parse package.json: ${err}`);
23
+ }
24
+ const allDependencies = { ...packageJsonObject.dependencies, ...packageJsonObject.devDependencies };
25
+ if (allDependencies[dependency])
26
+ return;
27
+ packageJsonObject.devDependencies = packageJsonObject.devDependencies || {};
28
+ packageJsonObject.devDependencies[dependency] = version;
29
+ writeFileSync(packageJsonPath, JSON.stringify(packageJsonObject, null, 2));
30
+ }
@@ -2,9 +2,10 @@ import { highlight } from 'cardinal';
2
2
  import chalk from 'chalk';
3
3
  import inquirer from 'inquirer';
4
4
  import ora from 'ora';
5
- import { BLUEPRINT_CONFIG_FILE, BLUEPRINT_DIR, writeConfigFile, } from '../../actions/blueprints/blueprint.js';
5
+ import { writeConfigFile } from '../../actions/blueprints/config.js';
6
6
  import { createStack, getStack } from '../../actions/blueprints/stacks.js';
7
7
  import { getProject } from '../../actions/sanity/projects.js';
8
+ import { BLUEPRINT_CONFIG_FILE, BLUEPRINT_DIR } from '../../config.js';
8
9
  import { niceId, warn } from '../../utils/display/presenters.js';
9
10
  import { promptForProject } from '../../utils/display/prompt.js';
10
11
  export async function blueprintConfigCore(options) {
@@ -2,10 +2,14 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import chalk from 'chalk';
4
4
  import inquirer from 'inquirer';
5
- import { BLUEPRINT_CONFIG_FILE, BLUEPRINT_DIR, findBlueprintFile, writeBlueprintToDisk, writeConfigFile, writeGitignoreFile, writeOrUpdateNodeDependency, } from '../../actions/blueprints/blueprint.js';
5
+ import { findBlueprintFile, writeBlueprintToDisk } from '../../actions/blueprints/blueprint.js';
6
+ import { writeConfigFile } from '../../actions/blueprints/config.js';
6
7
  import { createEmptyStack, getStack } from '../../actions/blueprints/stacks.js';
8
+ import { writeGitignoreFile } from '../../actions/git.js';
9
+ import { writeOrUpdateNodeDependency } from '../../actions/node.js';
7
10
  import { verifyExampleExists, writeExample } from '../../actions/sanity/examples.js';
8
11
  import { getProject } from '../../actions/sanity/projects.js';
12
+ import { BLUEPRINT_CONFIG_FILE, BLUEPRINT_DIR } from '../../config.js';
9
13
  import { check, warn } from '../../utils/display/presenters.js';
10
14
  import { promptForProject, promptForStackId } from '../../utils/display/prompt.js';
11
15
  const LAUNCH_LIMIT_STACK_PER_PROJECT = true;
@@ -127,15 +131,12 @@ export async function blueprintInitCore(options) {
127
131
  log(check(`${chalk.bold('Created Blueprint:')} ${providedDir ?? '.'}/${blueprintFileName}`));
128
132
  writeConfigFile({ blueprintFilePath, projectId, stackId });
129
133
  log(check(`${chalk.bold('Added configuration:')} ${providedDir ?? '.'}/${BLUEPRINT_DIR}/${BLUEPRINT_CONFIG_FILE}`));
130
- writeGitignoreFile({ blueprintFilePath });
134
+ writeGitignoreFile(blueprintFilePath);
131
135
  log(check(`${chalk.bold('Added .gitignore:')} ${providedDir ?? '.'}/.gitignore`));
132
136
  if (blueprintExtension !== 'json') {
133
137
  try {
134
138
  // check for || create package.json and add @sanity/blueprints to dependencies
135
- await writeOrUpdateNodeDependency({
136
- blueprintFilePath,
137
- dependency: '@sanity/blueprints',
138
- });
139
+ await writeOrUpdateNodeDependency(blueprintFilePath, '@sanity/blueprints');
139
140
  log(check(`${chalk.bold('Added dependency:')} @sanity/blueprints`));
140
141
  }
141
142
  catch {
@@ -42,9 +42,11 @@ async function streamLogs({ name, externalId, auth, log, }) {
42
42
  spinner.stop();
43
43
  log(`Streaming log session for function ${niceId(name)}`);
44
44
  log(`Watching for new logs... ${chalk.bold('ctrl+c')} to stop`);
45
+ let alreadyOpened = false;
45
46
  const onOpen = () => {
46
- // TODO delete log("Watching for new logs...") and uncomment this when Fns service is updated
47
- // log(`Watching for new logs... ${chalk.bold('ctrl+c')} to stop`)
47
+ if (alreadyOpened)
48
+ log(`${chalk.green('Reconnected')}`);
49
+ alreadyOpened = true;
48
50
  };
49
51
  const renderLog = (logEntry) => {
50
52
  const { time, level, message } = logEntry;
@@ -4,10 +4,9 @@ import { default as mime } from 'mime-types';
4
4
  import { WebSocketServer } from 'ws';
5
5
  import { readLocalBlueprint } from '../actions/blueprints/blueprint.js';
6
6
  import config from '../config.js';
7
- import { findFunctionByName } from '../utils/find-function.js';
8
- import invoke from '../utils/invoke-local.js';
9
7
  import { isRecord } from '../utils/is-record.js';
10
8
  import { isEventType } from '../utils/types.js';
9
+ import { handleInvokeRequest } from './handlers/invoke.js';
11
10
  const host = 'localhost';
12
11
  const app = (port) => {
13
12
  const requestListener = async (req, res) => {
@@ -42,26 +41,16 @@ const app = (port) => {
42
41
  else {
43
42
  delete context.clientOptions.token;
44
43
  }
45
- const start = performance.now();
46
- const { parsedBlueprint } = await readLocalBlueprint();
47
- const resource = findFunctionByName(parsedBlueprint, functionName);
48
- const readBlueprintTime = performance.now() - start;
49
- const payload = {
50
- payload: event,
51
- ...metadata,
52
- };
53
- const response = await invoke(resource, payload, context, {
54
- forceColor: false,
55
- });
56
- const timings = { ...response.timings, 'blueprint:read': readBlueprintTime };
44
+ const result = await handleInvokeRequest(functionName, event, metadata, context);
45
+ // Add Server-Timing header
57
46
  const timingHeaders = [];
58
- for (const [key, value] of Object.entries(timings)) {
47
+ for (const [key, value] of Object.entries(result.timings)) {
59
48
  timingHeaders.push(`${key.replace(/:/g, '-')};dur=${Math.abs(value).toFixed(1)}`);
60
49
  }
61
50
  if (timingHeaders.length > 0) {
62
51
  res.setHeader('Server-Timing', timingHeaders.join(', '));
63
52
  }
64
- res.end(JSON.stringify(response));
53
+ res.end(JSON.stringify(result));
65
54
  }
66
55
  catch (error) {
67
56
  console.error(error);
@@ -0,0 +1,4 @@
1
+ import type { InvocationResponse, InvokeContextOptions, InvokePayloadOptions } from '../../utils/types.js';
2
+ export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: Pick<InvokePayloadOptions, 'event' | 'before' | 'after'>, context: InvokeContextOptions): Promise<InvocationResponse & {
3
+ timings: Record<string, number>;
4
+ }>;
@@ -0,0 +1,19 @@
1
+ import { readLocalBlueprint } from '../../actions/blueprints/blueprint.js';
2
+ import { findFunctionByName } from '../../utils/find-function.js';
3
+ import invoke from '../../utils/invoke-local.js';
4
+ export async function handleInvokeRequest(functionName, event, metadata, context) {
5
+ const start = performance.now();
6
+ const { parsedBlueprint } = await readLocalBlueprint();
7
+ const resource = findFunctionByName(parsedBlueprint, functionName);
8
+ const readBlueprintTime = performance.now() - start;
9
+ const payload = {
10
+ payload: event,
11
+ ...metadata,
12
+ };
13
+ const response = await invoke(resource, payload, context, {
14
+ forceColor: false,
15
+ timeout: resource.timeout,
16
+ });
17
+ const timings = { ...response.timings, 'blueprint:read': readBlueprintTime };
18
+ return { ...response, timings };
19
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Blueprint } from '@sanity/blueprints-parser';
2
+ export type ScopeType = 'organization' | 'project';
2
3
  /** Result utility type */
3
4
  export type Result<T, E = string> = {
4
5
  ok: true;
@@ -1132,5 +1132,5 @@
1132
1132
  ]
1133
1133
  }
1134
1134
  },
1135
- "version": "10.9.0"
1135
+ "version": "10.9.2"
1136
1136
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sanity/runtime-cli",
3
3
  "description": "Sanity's Runtime CLI for Blueprints and Functions",
4
- "version": "10.9.0",
4
+ "version": "10.9.2",
5
5
  "author": "Sanity Runtime Team",
6
6
  "type": "module",
7
7
  "license": "MIT",