@sanity/runtime-cli 4.4.0 → 4.5.1

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 (82) hide show
  1. package/README.md +49 -77
  2. package/dist/actions/blueprints/assets.d.ts +1 -0
  3. package/dist/actions/blueprints/assets.js +21 -4
  4. package/dist/actions/blueprints/blueprint.d.ts +6 -12
  5. package/dist/actions/blueprints/blueprint.js +38 -45
  6. package/dist/actions/blueprints/index.d.ts +33 -0
  7. package/dist/actions/blueprints/index.js +32 -0
  8. package/dist/actions/blueprints/projects.d.ts +9 -0
  9. package/dist/actions/blueprints/projects.js +12 -0
  10. package/dist/actions/blueprints/stacks.d.ts +0 -12
  11. package/dist/actions/blueprints/stacks.js +3 -30
  12. package/dist/actions/functions/test.d.ts +2 -2
  13. package/dist/actions/functions/test.js +2 -2
  14. package/dist/baseCommands.d.ts +24 -0
  15. package/dist/baseCommands.js +69 -0
  16. package/dist/commands/blueprints/add.d.ts +1 -1
  17. package/dist/commands/blueprints/add.js +7 -6
  18. package/dist/commands/blueprints/config.d.ts +1 -1
  19. package/dist/commands/blueprints/config.js +24 -11
  20. package/dist/commands/blueprints/deploy.d.ts +2 -2
  21. package/dist/commands/blueprints/deploy.js +18 -33
  22. package/dist/commands/blueprints/destroy.d.ts +4 -3
  23. package/dist/commands/blueprints/destroy.js +32 -35
  24. package/dist/commands/blueprints/info.d.ts +2 -2
  25. package/dist/commands/blueprints/info.js +16 -36
  26. package/dist/commands/blueprints/init.d.ts +10 -2
  27. package/dist/commands/blueprints/init.js +85 -26
  28. package/dist/commands/blueprints/logs.d.ts +2 -2
  29. package/dist/commands/blueprints/logs.js +18 -32
  30. package/dist/commands/blueprints/plan.d.ts +2 -2
  31. package/dist/commands/blueprints/plan.js +10 -16
  32. package/dist/commands/blueprints/stacks.d.ts +3 -2
  33. package/dist/commands/blueprints/stacks.js +10 -29
  34. package/dist/commands/functions/env/add.d.ts +2 -2
  35. package/dist/commands/functions/env/add.js +6 -17
  36. package/dist/commands/functions/env/list.d.ts +2 -2
  37. package/dist/commands/functions/env/list.js +10 -17
  38. package/dist/commands/functions/env/remove.d.ts +2 -2
  39. package/dist/commands/functions/env/remove.js +6 -17
  40. package/dist/commands/functions/invoke.d.ts +2 -2
  41. package/dist/commands/functions/invoke.js +7 -14
  42. package/dist/commands/functions/logs.d.ts +3 -7
  43. package/dist/commands/functions/logs.js +21 -37
  44. package/dist/commands/functions/test.d.ts +3 -3
  45. package/dist/commands/functions/test.js +13 -14
  46. package/dist/server/app.js +82 -14
  47. package/dist/server/static/api.js +24 -3
  48. package/dist/server/static/components/function-list.js +4 -4
  49. package/dist/server/static/components/response-panel.js +14 -3
  50. package/dist/server/static/index.html +1 -0
  51. package/dist/utils/build-payload.d.ts +1 -1
  52. package/dist/utils/build-payload.js +3 -3
  53. package/dist/utils/bundle/bundle-function.d.ts +8 -0
  54. package/dist/utils/bundle/bundle-function.js +125 -0
  55. package/dist/utils/bundle/cleanup-source-maps.d.ts +10 -0
  56. package/dist/utils/bundle/cleanup-source-maps.js +53 -0
  57. package/dist/utils/bundle/find-up.d.ts +16 -0
  58. package/dist/utils/bundle/find-up.js +39 -0
  59. package/dist/utils/bundle/verify-handler.d.ts +2 -0
  60. package/dist/utils/bundle/verify-handler.js +13 -0
  61. package/dist/utils/child-process-wrapper.js +8 -6
  62. package/dist/utils/display/blueprints-formatting.js +2 -2
  63. package/dist/utils/display/errors.d.ts +4 -0
  64. package/dist/utils/display/errors.js +27 -0
  65. package/dist/utils/display/index.d.ts +1 -0
  66. package/dist/utils/display/index.js +1 -0
  67. package/dist/utils/functions/find-entry-point.d.ts +11 -0
  68. package/dist/utils/functions/find-entry-point.js +75 -0
  69. package/dist/utils/functions/should-bundle.d.ts +2 -0
  70. package/dist/utils/functions/should-bundle.js +23 -0
  71. package/dist/utils/invoke-local.d.ts +2 -2
  72. package/dist/utils/invoke-local.js +49 -8
  73. package/dist/utils/is-record.d.ts +1 -0
  74. package/dist/utils/is-record.js +3 -0
  75. package/dist/utils/parse-json-object.d.ts +1 -0
  76. package/dist/utils/parse-json-object.js +10 -0
  77. package/dist/utils/types.d.ts +13 -4
  78. package/dist/utils/types.js +9 -3
  79. package/oclif.manifest.json +59 -37
  80. package/package.json +5 -1
  81. package/dist/utils/is-json.d.ts +0 -1
  82. package/dist/utils/is-json.js +0 -12
@@ -0,0 +1,10 @@
1
+ /**
2
+ * "Clean up" source maps by removing absolute paths and making paths relative to the
3
+ * _input_ (eg source) rather than the _output_ (eg bundle) directory. Note that this
4
+ * process is not critical since the source content is inlined, but it helps with
5
+ * debugging to have (approximate) paths to the original source files.
6
+ *
7
+ * @param inputDir - The directory where the source files are located
8
+ * @param outputDir - The directory where the bundled files are located
9
+ */
10
+ export declare function cleanupSourceMaps(inputDir: string, outputDir: string): Promise<void>;
@@ -0,0 +1,53 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { isAbsolute, join, relative } from 'node:path';
3
+ /**
4
+ * "Clean up" source maps by removing absolute paths and making paths relative to the
5
+ * _input_ (eg source) rather than the _output_ (eg bundle) directory. Note that this
6
+ * process is not critical since the source content is inlined, but it helps with
7
+ * debugging to have (approximate) paths to the original source files.
8
+ *
9
+ * @param inputDir - The directory where the source files are located
10
+ * @param outputDir - The directory where the bundled files are located
11
+ */
12
+ export async function cleanupSourceMaps(inputDir, outputDir) {
13
+ const entries = await fs.readdir(outputDir, { withFileTypes: true });
14
+ const sourceMaps = entries.filter((entry) => entry.isFile() && entry.name.endsWith('.map'));
15
+ for (const entry of sourceMaps) {
16
+ const filePath = join(outputDir, entry.name);
17
+ let raw;
18
+ try {
19
+ raw = await fs.readFile(filePath, 'utf8');
20
+ }
21
+ catch {
22
+ return;
23
+ }
24
+ let map;
25
+ try {
26
+ const json = JSON.parse(raw);
27
+ map = isRelevantSourceMap(json) ? json : undefined;
28
+ }
29
+ catch {
30
+ return;
31
+ }
32
+ if (!map) {
33
+ return;
34
+ }
35
+ map.sources = map.sources.map((source) => {
36
+ const fullPath = isAbsolute(source) ? source : join(outputDir, source);
37
+ return relative(inputDir, fullPath);
38
+ });
39
+ try {
40
+ await fs.writeFile(filePath, JSON.stringify(map));
41
+ }
42
+ catch {
43
+ // ignore write errors
44
+ }
45
+ }
46
+ }
47
+ function isRelevantSourceMap(map) {
48
+ return (typeof map === 'object' &&
49
+ map !== null &&
50
+ 'sources' in map &&
51
+ Array.isArray(map.sources) &&
52
+ map.sources.every((source) => typeof source === 'string'));
53
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Walks up the directory tree from a starting point to find a given file/directory.
3
+ *
4
+ * @param fileName - The name of the file/directory to find.
5
+ * @param startDir - The directory to start searching from (default is the current working directory).
6
+ * @returns The path to the file if found, otherwise undefined.
7
+ */
8
+ export declare function findUp(fileName: string, startDir?: string): Promise<string | undefined>;
9
+ /**
10
+ * Finds the directory containing a specific file/directory by walking up the directory tree.
11
+ *
12
+ * @param fileName - The name of the file/directory to find.
13
+ * @param startDir - The directory to start searching from (default is the current working directory).
14
+ * @returns The directory containing the file if found, otherwise undefined.
15
+ */
16
+ export declare function findDirUp(fileName: string, startDir?: string): Promise<string | undefined>;
@@ -0,0 +1,39 @@
1
+ import { access } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ /**
4
+ * Walks up the directory tree from a starting point to find a given file/directory.
5
+ *
6
+ * @param fileName - The name of the file/directory to find.
7
+ * @param startDir - The directory to start searching from (default is the current working directory).
8
+ * @returns The path to the file if found, otherwise undefined.
9
+ */
10
+ export async function findUp(fileName, startDir = process.cwd()) {
11
+ let dir = path.resolve(startDir);
12
+ while (true) {
13
+ const candidate = path.join(dir, fileName);
14
+ try {
15
+ await access(candidate);
16
+ return candidate;
17
+ }
18
+ catch {
19
+ const parent = path.dirname(dir);
20
+ if (parent === dir)
21
+ break; // Reached root
22
+ dir = parent;
23
+ }
24
+ }
25
+ return undefined;
26
+ }
27
+ /**
28
+ * Finds the directory containing a specific file/directory by walking up the directory tree.
29
+ *
30
+ * @param fileName - The name of the file/directory to find.
31
+ * @param startDir - The directory to start searching from (default is the current working directory).
32
+ * @returns The directory containing the file if found, otherwise undefined.
33
+ */
34
+ export async function findDirUp(fileName, startDir = process.cwd()) {
35
+ const filePath = await findUp(fileName, startDir);
36
+ if (!filePath)
37
+ return undefined;
38
+ return path.dirname(filePath);
39
+ }
@@ -0,0 +1,2 @@
1
+ import type { build } from 'vite';
2
+ export declare function verifyHandler(result: Awaited<ReturnType<typeof build>>): Promise<void>;
@@ -0,0 +1,13 @@
1
+ export async function verifyHandler(result) {
2
+ if ('close' in result) {
3
+ throw new Error('Incorrect build output, got watcher');
4
+ }
5
+ const outputs = (Array.isArray(result) ? result : [result]).flatMap(({ output }) => output);
6
+ const bundledIndex = outputs.find((output) => output.type === 'chunk' && output.isEntry && output.name === 'index');
7
+ if (!bundledIndex || bundledIndex.type !== 'chunk') {
8
+ throw new Error('Unexpected build output, no bundled index found');
9
+ }
10
+ if (!bundledIndex.exports.includes('handler')) {
11
+ throw new Error('Unexpected build output, no `handler` export found');
12
+ }
13
+ }
@@ -1,16 +1,18 @@
1
1
  import {existsSync, statSync} from 'node:fs'
2
- import {join} from 'node:path'
2
+ import {isAbsolute, join} from 'node:path'
3
3
  import process from 'node:process'
4
4
 
5
5
  export function getFunctionSource(src) {
6
- if (statSync(src).isDirectory()) {
7
- const indexPath = join(src, 'index.js')
6
+ const pathToCheck = isAbsolute(src) ? src : join(process.cwd(), src)
7
+
8
+ if (statSync(pathToCheck).isDirectory()) {
9
+ const indexPath = join(pathToCheck, 'index.js')
8
10
  if (!existsSync(indexPath)) {
9
- throw Error(`Function directory ${src} has no index.js`)
11
+ throw Error(`Function directory ${pathToCheck} has no index.js`)
10
12
  }
11
13
  return indexPath
12
14
  }
13
- return src
15
+ return pathToCheck
14
16
  }
15
17
 
16
18
  // Start when payload data arrives from parent process
@@ -28,7 +30,7 @@ process.on('message', async (data) => {
28
30
  let json = null
29
31
 
30
32
  // Import the function code
31
- const {handler} = await import(getFunctionSource(join(process.cwd(), srcPath)))
33
+ const {handler} = await import(getFunctionSource(srcPath))
32
34
 
33
35
  // backup stdout
34
36
  const originalStdoutWrite = process.stdout.write.bind(process.stdout)
@@ -6,10 +6,10 @@ export function formatTitle(title, name) {
6
6
  }
7
7
  export function formatResourceTree(resources) {
8
8
  if (!resources || resources.length === 0) {
9
- return ' Zero resources in this stack';
9
+ return ' Zero deployed resources';
10
10
  }
11
11
  const output = [];
12
- output.push(`${blue('Stack Resources')} [${resources.length}]`);
12
+ output.push(`${blue('Blueprint Resources')} [${resources.length}]`);
13
13
  const functionResources = resources.filter((r) => r.type?.startsWith('sanity.function.'));
14
14
  const otherResources = resources.filter((r) => !r.type?.startsWith('sanity.function.'));
15
15
  const hasOtherResources = otherResources.length > 0;
@@ -0,0 +1,4 @@
1
+ import type { BlueprintIssue } from '../../actions/blueprints/index.js';
2
+ import type { BlueprintParserError } from '../types.js';
3
+ export declare function presentBlueprintIssues(issues: BlueprintIssue[]): string;
4
+ export declare function presentBlueprintParserErrors(errors: BlueprintParserError[]): string;
@@ -0,0 +1,27 @@
1
+ export function presentBlueprintIssues(issues) {
2
+ const report = [];
3
+ for (const issue of issues) {
4
+ switch (issue.code) {
5
+ case 'PARSE_ERROR':
6
+ report.push(issue.message);
7
+ if (issue.errors)
8
+ report.push(presentBlueprintParserErrors(issue.errors));
9
+ break;
10
+ case 'NO_STACK_ID':
11
+ report.push('Existing deployment not found.');
12
+ break;
13
+ case 'NO_PROJECT_ID':
14
+ report.push('Project ID not found.');
15
+ break;
16
+ case 'NO_STACK':
17
+ report.push('Existing deployment not found.');
18
+ break;
19
+ default:
20
+ report.push(issue.message);
21
+ }
22
+ }
23
+ return report.join('\n');
24
+ }
25
+ export function presentBlueprintParserErrors(errors) {
26
+ return errors.map((e) => e.message).join('\n');
27
+ }
@@ -2,3 +2,4 @@ export * as blueprintsFormatting from './blueprints-formatting.js';
2
2
  export * as colors from './colors.js';
3
3
  export * as dates from './dates.js';
4
4
  export * as logsFormatting from './logs-formatting.js';
5
+ export * as errors from './errors.js';
@@ -2,3 +2,4 @@ export * as blueprintsFormatting from './blueprints-formatting.js';
2
2
  export * as colors from './colors.js';
3
3
  export * as dates from './dates.js';
4
4
  export * as logsFormatting from './logs-formatting.js';
5
+ export * as errors from './errors.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Resolves the source path to an executable entry file path.
3
+ *
4
+ * If the source path is a directory, it looks for `package.json#main`, then `index.ts`, then `index.js`.
5
+ *
6
+ * @param srcPath - The source path (can be a file or directory).
7
+ * @param displayName - Optional display name for the function, used in error messages.
8
+ * @returns The absolute path to the entry file.
9
+ * @throws If the entry file cannot be determined.
10
+ */
11
+ export declare function findFunctionEntryPoint(srcPath: string, displayName?: string): Promise<string>;
@@ -0,0 +1,75 @@
1
+ import { readFile, stat } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ import { cwd } from 'node:process';
4
+ /**
5
+ * Resolves the source path to an executable entry file path.
6
+ *
7
+ * If the source path is a directory, it looks for `package.json#main`, then `index.ts`, then `index.js`.
8
+ *
9
+ * @param srcPath - The source path (can be a file or directory).
10
+ * @param displayName - Optional display name for the function, used in error messages.
11
+ * @returns The absolute path to the entry file.
12
+ * @throws If the entry file cannot be determined.
13
+ */
14
+ export async function findFunctionEntryPoint(srcPath, displayName) {
15
+ const absolutePath = resolve(cwd(), srcPath);
16
+ let stats;
17
+ try {
18
+ stats = await stat(absolutePath);
19
+ }
20
+ catch (err) {
21
+ throw new Error(`Source path not found or inaccessible: ${srcPath}`, { cause: err });
22
+ }
23
+ if (stats.isFile()) {
24
+ // It's already an entry file path
25
+ return absolutePath;
26
+ }
27
+ if (stats.isDirectory()) {
28
+ // 1. Check package.json#main
29
+ try {
30
+ const pkgJsonPath = join(absolutePath, 'package.json');
31
+ const pkgJsonContent = await readFile(pkgJsonPath, 'utf8');
32
+ const pkgJson = JSON.parse(pkgJsonContent);
33
+ if (pkgJson.main) {
34
+ const mainPath = resolve(absolutePath, pkgJson.main);
35
+ if (await fileExists(mainPath)) {
36
+ return mainPath;
37
+ }
38
+ // If pkgJson.main points to a non-existent file, we continue checking index files
39
+ }
40
+ }
41
+ catch {
42
+ // Ignore errors (missing package.json, invalid JSON, etc.)
43
+ // Consider warning the user on invalid JSON though?
44
+ }
45
+ // 2. Check index.ts
46
+ const indexTs = join(absolutePath, 'index.ts');
47
+ if (await fileExists(indexTs)) {
48
+ return indexTs;
49
+ }
50
+ // 3. Check index.js
51
+ const indexJs = join(absolutePath, 'index.js');
52
+ if (await fileExists(indexJs)) {
53
+ return indexJs;
54
+ }
55
+ const nameHint = displayName ? ` for function "${displayName}"` : '';
56
+ throw new Error(`Could not determine entry file${nameHint} in directory: ${srcPath}. Looked for package.json#main, index.ts, index.js.`);
57
+ }
58
+ // Should not happen if stat succeeded, but defensively handle
59
+ throw new Error(`Source path is neither a file nor a directory: ${srcPath}`);
60
+ }
61
+ /**
62
+ * Checks if a file exists and is a file.
63
+ */
64
+ async function fileExists(filePath) {
65
+ try {
66
+ const stats = await stat(filePath);
67
+ return stats.isFile();
68
+ }
69
+ catch (err) {
70
+ if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
71
+ return false;
72
+ }
73
+ throw err; // Re-throw other errors
74
+ }
75
+ }
@@ -0,0 +1,2 @@
1
+ import type { LocalFunctionResource } from '../types.js';
2
+ export declare function shouldBundleFunction(resource: LocalFunctionResource): Promise<boolean>;
@@ -0,0 +1,23 @@
1
+ import { findFunctionEntryPoint } from './find-entry-point.js';
2
+ export async function shouldBundleFunction(resource) {
3
+ // 1. Explicit configuration takes precedence
4
+ if (typeof resource.bundle === 'boolean') {
5
+ return resource.bundle;
6
+ }
7
+ if (!resource.src) {
8
+ // Cannot determine without a source path
9
+ return false;
10
+ }
11
+ try {
12
+ // 2. Find the actual entry point
13
+ const entryPoint = await findFunctionEntryPoint(resource.src, resource.displayName ?? resource.name);
14
+ // 3. Check if the resolved entry point is a TypeScript file
15
+ return entryPoint.endsWith('.ts');
16
+ }
17
+ catch (err) {
18
+ // If we cannot find the entry point, we cannot determine if it's TS.
19
+ // Log a warning and default to false (don't bundle).
20
+ console.warn(`[warn] Could not determine entry point for function "${resource.displayName ?? resource.name}" while checking if bundling is needed: ${err instanceof Error ? err.message : err}`);
21
+ return false;
22
+ }
23
+ }
@@ -1,3 +1,3 @@
1
- import type { InvocationResponse, InvokeContextOptions } from './types.js';
1
+ import type { InvocationResponse, InvokeContextOptions, LocalFunctionResource } from './types.js';
2
2
  export declare function sanitizeLogs(logs: string): string;
3
- export default function invoke(srcPath: string, data: null | object, context: InvokeContextOptions, timeout?: number): Promise<InvocationResponse>;
3
+ export default function invoke(resource: LocalFunctionResource, data: Record<string, unknown> | null, context: InvokeContextOptions, timeout?: number): Promise<InvocationResponse>;
@@ -1,42 +1,80 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import { performance } from 'node:perf_hooks';
2
3
  import { cwd } from 'node:process';
3
4
  import { setTimeout } from 'node:timers';
4
5
  import config from '../config.js';
6
+ import { bundleFunction } from './bundle/bundle-function.js';
7
+ import { findFunctionEntryPoint } from './functions/find-entry-point.js';
8
+ import { shouldBundleFunction } from './functions/should-bundle.js';
5
9
  function getChildProcessWrapperPath() {
6
10
  return new URL('./child-process-wrapper.js', import.meta.url).pathname;
7
11
  }
8
12
  export function sanitizeLogs(logs) {
9
13
  return logs.replace(/([a-zA-Z0-9]{10})[a-zA-Z0-9]{65,}/g, '$1**********');
10
14
  }
11
- export default async function invoke(srcPath, data, context, timeout = 5) {
15
+ export default async function invoke(resource, data, context, timeout = 5) {
16
+ if (!resource.src) {
17
+ throw new Error(`Function resource "${resource.name}" is missing the 'src' property.`);
18
+ }
19
+ let cleanupBundle = async () => { };
20
+ let functionPath = '';
21
+ let bundleTimings = undefined;
22
+ if (await shouldBundleFunction(resource)) {
23
+ const bundleResult = await bundleFunction(resource);
24
+ functionPath = await findFunctionEntryPoint(bundleResult.outputDir);
25
+ bundleTimings = bundleResult.timings;
26
+ cleanupBundle = bundleResult.cleanup;
27
+ }
28
+ else {
29
+ functionPath = await findFunctionEntryPoint(resource.src, resource.displayName ?? resource.name);
30
+ }
12
31
  return new Promise((resolve, reject) => {
13
32
  let child;
14
33
  let timer;
34
+ let executionStart;
15
35
  function start() {
16
- child = spawn('node', [getChildProcessWrapperPath()], {
36
+ executionStart = performance.now();
37
+ child = spawn('node', ['--enable-source-maps', getChildProcessWrapperPath()], {
17
38
  cwd: cwd(),
18
39
  stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
19
40
  });
20
- // Note: start a timeout so child process doesn't run forever
21
41
  child.on('message', (data) => {
42
+ const executionTimeMs = performance.now() - executionStart;
22
43
  const { json, logs } = JSON.parse(data.toString());
23
44
  shutdown();
24
- resolve({ json, logs: sanitizeLogs(logs), error: '' });
45
+ resolve({
46
+ json,
47
+ logs: sanitizeLogs(logs),
48
+ error: undefined,
49
+ timings: {
50
+ ...bundleTimings,
51
+ execute: executionTimeMs,
52
+ },
53
+ });
25
54
  });
26
55
  child.on('error', (error) => {
56
+ shutdown();
27
57
  reject(new Error(`encountered error ${error.message}`));
28
58
  });
29
59
  child.on('exit', (code) => {
60
+ const executionTimeMs = performance.now() - executionStart;
30
61
  shutdown();
31
62
  if (code !== 0) {
32
63
  reject(new Error(`exited with code ${code}`));
33
64
  }
34
65
  else {
35
- resolve({ json: {}, logs: '', error: '' });
66
+ resolve({
67
+ json: {},
68
+ logs: '',
69
+ error: undefined,
70
+ timings: {
71
+ ...bundleTimings,
72
+ execute: executionTimeMs,
73
+ },
74
+ });
36
75
  }
37
76
  });
38
77
  timer = setTimeout(() => {
39
- // timedOut = true
40
78
  shutdown();
41
79
  reject(new Error(`Timed out after hitting its ${timeout}s timeout!`));
42
80
  }, timeout * 1000);
@@ -51,11 +89,14 @@ export default async function invoke(srcPath, data, context, timeout = 5) {
51
89
  },
52
90
  },
53
91
  };
54
- child.send(JSON.stringify({ srcPath, payload }, null, 2));
92
+ child.send(JSON.stringify({ srcPath: functionPath, payload }, null, 2));
55
93
  }
56
94
  function shutdown() {
57
95
  clearTimeout(timer);
58
- child.kill();
96
+ if (child && !child.killed) {
97
+ child.kill();
98
+ }
99
+ cleanupBundle().catch((err) => console.warn('Bundle cleanup failed:', err));
59
100
  }
60
101
  start();
61
102
  });
@@ -0,0 +1 @@
1
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
@@ -0,0 +1,3 @@
1
+ export function isRecord(value) {
2
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
3
+ }
@@ -0,0 +1 @@
1
+ export declare function parseJsonObject(jsonString: string): null | Record<string, unknown>;
@@ -0,0 +1,10 @@
1
+ import { isRecord } from './is-record.js';
2
+ export function parseJsonObject(jsonString) {
3
+ try {
4
+ const o = JSON.parse(jsonString);
5
+ return isRecord(o) ? o : null;
6
+ }
7
+ catch {
8
+ return null;
9
+ }
10
+ }
@@ -12,7 +12,7 @@ export interface LocalBlueprint {
12
12
  /** @link https://github.com/sanity-io/blueprints-rfc/blob/main/readme.md#outputs */
13
13
  outputs?: Array<Record<string, unknown>>;
14
14
  /** @link https://github.com/sanity-io/blueprints-rfc/blob/main/readme.md#resources */
15
- resources?: Array<LocalResource>;
15
+ resources?: Array<LocalResource | LocalFunctionResource>;
16
16
  /** @link https://github.com/sanity-io/blueprints-rfc/blob/main/readme.md#parameters */
17
17
  parameters?: Array<{
18
18
  name: string;
@@ -51,6 +51,7 @@ export declare function isLocalFunctionResource(r: LocalResource): r is LocalFun
51
51
  /** @internal */
52
52
  export interface LocalFunctionResource extends LocalResource {
53
53
  src?: string;
54
+ bundle?: boolean;
54
55
  memory?: number;
55
56
  timeout?: number;
56
57
  env?: Record<string, string>;
@@ -95,6 +96,7 @@ export interface StackPayload {
95
96
  name: string;
96
97
  projectId: string;
97
98
  document: LocalBlueprint;
99
+ useProjectBasedId?: boolean;
98
100
  }
99
101
  /** @internal */
100
102
  export interface InvokePayloadOptions {
@@ -115,6 +117,7 @@ export interface InvocationResponse {
115
117
  error: undefined | unknown;
116
118
  json: object | undefined;
117
119
  logs: string | undefined;
120
+ timings?: Record<string, number>;
118
121
  }
119
122
  /** @internal */
120
123
  export interface BlueprintLog {
@@ -133,10 +136,16 @@ export interface FunctionLog {
133
136
  }
134
137
  /** @internal */
135
138
  export declare enum BlueprintParserErrorType {
136
- MissingProject = "missing_project",
137
- MissingStack = "missing_stack",
138
139
  InvalidProperty = "invalid_property",
139
- InvalidStack = "invalid_stack"
140
+ InvalidVersion = "invalid_version",
141
+ InvalidType = "invalid_type",
142
+ MissingRequiredProperty = "missing_required_property",
143
+ DuplicateName = "duplicate_name",
144
+ InvalidFormat = "invalid_format",
145
+ InvalidValue = "invalid_value",
146
+ JsonValidationError = "json_validation_error",
147
+ InvalidInput = "invalid_input",
148
+ MissingParameter = "missing_parameter"
140
149
  }
141
150
  /** @internal */
142
151
  export interface BlueprintParserError {
@@ -4,8 +4,14 @@ export function isLocalFunctionResource(r) {
4
4
  /** @internal */
5
5
  export var BlueprintParserErrorType;
6
6
  (function (BlueprintParserErrorType) {
7
- BlueprintParserErrorType["MissingProject"] = "missing_project";
8
- BlueprintParserErrorType["MissingStack"] = "missing_stack";
9
7
  BlueprintParserErrorType["InvalidProperty"] = "invalid_property";
10
- BlueprintParserErrorType["InvalidStack"] = "invalid_stack";
8
+ BlueprintParserErrorType["InvalidVersion"] = "invalid_version";
9
+ BlueprintParserErrorType["InvalidType"] = "invalid_type";
10
+ BlueprintParserErrorType["MissingRequiredProperty"] = "missing_required_property";
11
+ BlueprintParserErrorType["DuplicateName"] = "duplicate_name";
12
+ BlueprintParserErrorType["InvalidFormat"] = "invalid_format";
13
+ BlueprintParserErrorType["InvalidValue"] = "invalid_value";
14
+ BlueprintParserErrorType["JsonValidationError"] = "json_validation_error";
15
+ BlueprintParserErrorType["InvalidInput"] = "invalid_input";
16
+ BlueprintParserErrorType["MissingParameter"] = "missing_parameter";
11
17
  })(BlueprintParserErrorType || (BlueprintParserErrorType = {}));