@sanity/runtime-cli 4.3.3 → 4.3.5-bundle.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 +17 -17
- package/dist/actions/blueprints/assets.d.ts +1 -0
- package/dist/actions/blueprints/assets.js +21 -4
- package/dist/actions/functions/test.d.ts +2 -2
- package/dist/actions/functions/test.js +2 -2
- package/dist/commands/blueprints/config.d.ts +1 -1
- package/dist/commands/blueprints/config.js +8 -8
- package/dist/commands/blueprints/deploy.js +3 -4
- package/dist/commands/blueprints/init.d.ts +1 -1
- package/dist/commands/blueprints/init.js +8 -8
- package/dist/commands/functions/logs.d.ts +1 -1
- package/dist/commands/functions/logs.js +8 -8
- package/dist/commands/functions/test.js +3 -6
- package/dist/server/app.js +80 -12
- package/dist/server/static/api.js +24 -3
- package/dist/server/static/components/app.css +915 -54
- package/dist/server/static/components/dataset-dropdown.js +5 -3
- package/dist/server/static/components/filters.d.ts +1 -0
- package/dist/server/static/components/filters.js +20 -0
- package/dist/server/static/components/function-list.js +7 -7
- package/dist/server/static/components/payload-panel.js +18 -17
- package/dist/server/static/components/projects-dropdown.js +5 -3
- package/dist/server/static/components/response-panel.js +38 -28
- package/dist/server/static/index.html +11 -30
- package/dist/server/static/vendor/vendor.bundle.d.ts +2 -2
- package/dist/utils/build-payload.d.ts +1 -1
- package/dist/utils/build-payload.js +3 -3
- package/dist/utils/bundle/bundle-function.d.ts +8 -0
- package/dist/utils/bundle/bundle-function.js +125 -0
- package/dist/utils/bundle/cleanup-source-maps.d.ts +10 -0
- package/dist/utils/bundle/cleanup-source-maps.js +53 -0
- package/dist/utils/bundle/find-up.d.ts +16 -0
- package/dist/utils/bundle/find-up.js +39 -0
- package/dist/utils/bundle/verify-handler.d.ts +2 -0
- package/dist/utils/bundle/verify-handler.js +13 -0
- package/dist/utils/child-process-wrapper.js +8 -6
- package/dist/utils/functions/find-entry-point.d.ts +11 -0
- package/dist/utils/functions/find-entry-point.js +75 -0
- package/dist/utils/functions/should-bundle.d.ts +2 -0
- package/dist/utils/functions/should-bundle.js +23 -0
- package/dist/utils/invoke-local.d.ts +2 -2
- package/dist/utils/invoke-local.js +48 -7
- package/dist/utils/is-record.d.ts +1 -0
- package/dist/utils/is-record.js +3 -0
- package/dist/utils/parse-json-object.d.ts +1 -0
- package/dist/utils/parse-json-object.js +10 -0
- package/dist/utils/types.d.ts +3 -1
- package/oclif.manifest.json +1 -1
- package/package.json +4 -1
- package/dist/utils/is-json.d.ts +0 -1
- package/dist/utils/is-json.js +0 -12
|
@@ -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
|
-
|
|
7
|
-
|
|
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 ${
|
|
11
|
+
throw Error(`Function directory ${pathToCheck} has no index.js`)
|
|
10
12
|
}
|
|
11
13
|
return indexPath
|
|
12
14
|
}
|
|
13
|
-
return
|
|
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(
|
|
33
|
+
const {handler} = await import(getFunctionSource(srcPath))
|
|
32
34
|
|
|
33
35
|
// backup stdout
|
|
34
36
|
const originalStdoutWrite = process.stdout.write.bind(process.stdout)
|
|
@@ -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,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(
|
|
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(
|
|
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() {
|
|
36
|
+
executionStart = performance.now();
|
|
16
37
|
child = spawn('node', [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({
|
|
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({
|
|
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.
|
|
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 @@
|
|
|
1
|
+
export declare function parseJsonObject(jsonString: string): null | Record<string, unknown>;
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -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>;
|
|
@@ -115,6 +116,7 @@ export interface InvocationResponse {
|
|
|
115
116
|
error: undefined | unknown;
|
|
116
117
|
json: object | undefined;
|
|
117
118
|
logs: string | undefined;
|
|
119
|
+
timings?: Record<string, number>;
|
|
118
120
|
}
|
|
119
121
|
/** @internal */
|
|
120
122
|
export interface BlueprintLog {
|
package/oclif.manifest.json
CHANGED
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": "4.3.
|
|
4
|
+
"version": "4.3.5-bundle.0",
|
|
5
5
|
"author": "Sanity Runtime Team",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"postbuild": "oclif manifest && oclif readme",
|
|
48
48
|
"build:ts": "shx rm -rf dist *.tsbuildinfo && tsc -b",
|
|
49
49
|
"build:static": "npm run copy:wrapper && npm run copy:server",
|
|
50
|
+
"clean": "shx rm -rf dist oclif.manifest.json",
|
|
50
51
|
"copy:server": "shx cp -r ./src/server/static ./dist/server",
|
|
51
52
|
"copy:wrapper": "shx cp ./src/utils/child-process-wrapper.js ./dist/utils/child-process-wrapper.js",
|
|
52
53
|
"lint": "biome ci",
|
|
@@ -68,6 +69,8 @@
|
|
|
68
69
|
"eventsource": "^3.0.6",
|
|
69
70
|
"inquirer": "^12.5.2",
|
|
70
71
|
"mime-types": "^3.0.1",
|
|
72
|
+
"vite": "^6.3.3",
|
|
73
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
71
74
|
"xdg-basedir": "^5.1.0",
|
|
72
75
|
"yocto-spinner": "^0.2.1"
|
|
73
76
|
},
|
package/dist/utils/is-json.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default function isJson(jsonString: string): null | object;
|