@sanity/runtime-cli 17.0.0 → 17.1.0-next.g3cb2d2e
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 +21 -21
- package/dist/actions/functions/test.js +1 -1
- package/dist/baseCommands.js +13 -2
- package/dist/config.d.ts +1 -0
- package/dist/config.js +7 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/cores/functions/test.js +44 -0
- package/dist/server/app.js +31 -3
- package/dist/server/handlers/invoke.d.ts +2 -2
- package/dist/server/handlers/invoke.js +7 -4
- package/dist/server/static/components/console-panel.js +59 -8
- package/dist/server/static/components/rule-panel.js +11 -8
- package/dist/server/static/components/shared-styles.js +24 -21
- package/dist/server/static/index.html +1 -1
- package/dist/server/static/vendor/vendor.bundle.js +110 -61
- package/dist/server/static/web-socket.js +41 -0
- package/dist/utils/functions/child-process-wrapper.js +62 -7
- package/dist/utils/functions/{invoke-local.d.ts → invoke/local.d.ts} +1 -1
- package/dist/utils/functions/invoke/local.js +306 -0
- package/dist/utils/functions/invoke/nested.d.ts +25 -0
- package/dist/utils/functions/invoke/nested.js +41 -0
- package/dist/utils/functions/invoke/prepare.d.ts +20 -0
- package/dist/utils/functions/invoke/prepare.js +90 -0
- package/dist/utils/isTrueish.d.ts +6 -0
- package/dist/utils/isTrueish.js +9 -0
- package/dist/utils/types.d.ts +62 -4
- package/oclif.manifest.json +1 -1
- package/package.json +6 -2
- package/dist/server/static/hot-reload.js +0 -16
- package/dist/utils/functions/invoke-local.js +0 -185
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { performance } from 'node:perf_hooks';
|
|
3
|
-
import { cwd } from 'node:process';
|
|
4
|
-
import { setTimeout } from 'node:timers';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import * as groq from 'groq-js';
|
|
7
|
-
import config from '../../config.js';
|
|
8
|
-
import { groupBlueprintResources } from '../blueprints/group-resources.js';
|
|
9
|
-
import { isGroqContextOptions, } from '../types.js';
|
|
10
|
-
import { findFunctionEntryPoint } from './find-entry-point.js';
|
|
11
|
-
import { cleanupTempPackageJson, createTempPackageJson, doesPackageJsonExists, } from './package-json.js';
|
|
12
|
-
import { resolveResourceDependencies } from './resolve-dependencies.js';
|
|
13
|
-
import { shouldAutoResolveDependencies } from './should-auto-resolve-deps.js';
|
|
14
|
-
import { shouldTranspileFunction } from './should-transpile.js';
|
|
15
|
-
import { transpileFunction } from './transpile/function.js';
|
|
16
|
-
function getChildProcessWrapperPath() {
|
|
17
|
-
return fileURLToPath(new URL('./child-process-wrapper.js', import.meta.url));
|
|
18
|
-
}
|
|
19
|
-
export function sanitizeLogs(logs) {
|
|
20
|
-
return logs.replace(/([a-zA-Z0-9]{10})[a-zA-Z0-9]{65,}/g, '$1**********');
|
|
21
|
-
}
|
|
22
|
-
export async function applyGroqRule(resource, payload, projectId, dataset) {
|
|
23
|
-
const { before, after, payload: data = null } = payload;
|
|
24
|
-
// If there is no rule set return everything
|
|
25
|
-
if (!resource.event)
|
|
26
|
-
return data;
|
|
27
|
-
// default groq rule is: gimme full doc content. otherwise, parse + eval custom rule
|
|
28
|
-
// applying the GROQ filter may result in a slimmer set of documents
|
|
29
|
-
if (resource.event.filter || resource.event.projection) {
|
|
30
|
-
const query = `*[${resource.event.filter || ''}]${resource.event.projection || ''}`;
|
|
31
|
-
try {
|
|
32
|
-
const rule = groq.parse(query, { mode: 'delta' });
|
|
33
|
-
const sanity = projectId && dataset ? { projectId, dataset } : undefined;
|
|
34
|
-
const queryResults = await groq.evaluate(rule, { dataset: [data], before, after, sanity });
|
|
35
|
-
const currentFunctionDocumentSet = await queryResults.get();
|
|
36
|
-
return currentFunctionDocumentSet[0];
|
|
37
|
-
}
|
|
38
|
-
catch (groqErr) {
|
|
39
|
-
const detail = groqErr instanceof Error && `; ${groqErr.message}`;
|
|
40
|
-
throw new Error(`Invalid rule configuration: ${query}${detail}`, { cause: groqErr });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
// default groq rule so just return the data
|
|
44
|
-
return data;
|
|
45
|
-
}
|
|
46
|
-
export default async function invoke(resource, payload, context, options) {
|
|
47
|
-
if (!resource.src) {
|
|
48
|
-
throw new Error(`Function resource "${resource.name}" is missing the 'src' property.`);
|
|
49
|
-
}
|
|
50
|
-
let cleanupBundle = async () => { };
|
|
51
|
-
let functionPath = '';
|
|
52
|
-
let bundleTimings;
|
|
53
|
-
// If no package.json exists we will create a temporary one to avoid the
|
|
54
|
-
// Module Type warning from node
|
|
55
|
-
const existingPackageJson = doesPackageJsonExists(resource);
|
|
56
|
-
const shouldTranspile = await shouldTranspileFunction(resource);
|
|
57
|
-
if (shouldTranspile) {
|
|
58
|
-
const transpileResult = await transpileFunction(resource);
|
|
59
|
-
functionPath = await findFunctionEntryPoint(transpileResult.outputDir);
|
|
60
|
-
bundleTimings = transpileResult.timings;
|
|
61
|
-
cleanupBundle = transpileResult.cleanup;
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
functionPath = await findFunctionEntryPoint(resource.src, resource.displayName ?? resource.name);
|
|
65
|
-
}
|
|
66
|
-
const shouldResolveDependencies = await shouldAutoResolveDependencies(resource);
|
|
67
|
-
if (shouldResolveDependencies && existingPackageJson) {
|
|
68
|
-
await resolveResourceDependencies(resource, { transpiled: shouldTranspile });
|
|
69
|
-
}
|
|
70
|
-
if (!existingPackageJson) {
|
|
71
|
-
createTempPackageJson(functionPath);
|
|
72
|
-
}
|
|
73
|
-
const { forceColor = true, timeout = 10 } = options;
|
|
74
|
-
let filteredData = payload?.payload?.data || {};
|
|
75
|
-
if (resource.type === 'sanity.function.document' ||
|
|
76
|
-
resource.type === 'sanity.function.media-library.asset') {
|
|
77
|
-
if (!isGroqContextOptions(context)) {
|
|
78
|
-
throw new Error('GROQ-based functions require a context with clientOptions');
|
|
79
|
-
}
|
|
80
|
-
const { projectId, dataset } = context.clientOptions;
|
|
81
|
-
if ('before' in payload) {
|
|
82
|
-
filteredData = await applyGroqRule(resource, payload, projectId, dataset);
|
|
83
|
-
if (typeof filteredData === 'undefined') {
|
|
84
|
-
return {
|
|
85
|
-
logs: `⚠️ Filter "${resource.event?.filter}" returned an empty result. Skipping invoke.`,
|
|
86
|
-
error: undefined,
|
|
87
|
-
json: undefined,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return new Promise((resolve, reject) => {
|
|
93
|
-
let child;
|
|
94
|
-
let timer;
|
|
95
|
-
let executionStart;
|
|
96
|
-
function start() {
|
|
97
|
-
executionStart = performance.now();
|
|
98
|
-
child = spawn('node', ['--enable-source-maps', getChildProcessWrapperPath()], {
|
|
99
|
-
cwd: cwd(),
|
|
100
|
-
stdio: ['pipe', 'pipe', 'pipe', 'ipc'], // changed: use 'pipe' so we can capture output
|
|
101
|
-
env: { ...process.env, ...resource.env, FORCE_COLOR: forceColor ? '1' : '0' },
|
|
102
|
-
});
|
|
103
|
-
// NEW: capture stdout and stderr
|
|
104
|
-
let stdout = '';
|
|
105
|
-
let stderr = '';
|
|
106
|
-
child.stdout?.on('data', (data) => {
|
|
107
|
-
stdout += data.toString();
|
|
108
|
-
});
|
|
109
|
-
child.stderr?.on('data', (data) => {
|
|
110
|
-
stderr += data.toString();
|
|
111
|
-
});
|
|
112
|
-
child.on('message', (data) => {
|
|
113
|
-
const executionTimeMs = performance.now() - executionStart;
|
|
114
|
-
const { json, logs, error } = JSON.parse(data.toString());
|
|
115
|
-
shutdown();
|
|
116
|
-
resolve({
|
|
117
|
-
json,
|
|
118
|
-
logs: sanitizeLogs(logs),
|
|
119
|
-
error,
|
|
120
|
-
timings: {
|
|
121
|
-
...bundleTimings,
|
|
122
|
-
execute: executionTimeMs,
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
child.on('error', (error) => {
|
|
127
|
-
shutdown();
|
|
128
|
-
reject(new Error(`encountered error ${error.message}`));
|
|
129
|
-
});
|
|
130
|
-
child.on('exit', (code) => {
|
|
131
|
-
const executionTimeMs = performance.now() - executionStart;
|
|
132
|
-
shutdown();
|
|
133
|
-
if (code !== 0) {
|
|
134
|
-
// use captured stderr if available
|
|
135
|
-
const message = stderr.trim().length > 0
|
|
136
|
-
? `Process failed with code ${code}\n${stderr}`
|
|
137
|
-
: `exited with code ${code}`;
|
|
138
|
-
reject(new Error(message));
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
resolve({
|
|
142
|
-
json: undefined,
|
|
143
|
-
logs: sanitizeLogs(stdout),
|
|
144
|
-
error: undefined,
|
|
145
|
-
timings: {
|
|
146
|
-
...bundleTimings,
|
|
147
|
-
execute: executionTimeMs,
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
timer = setTimeout(() => {
|
|
153
|
-
shutdown();
|
|
154
|
-
reject(new Error(`Timeout: Function exceeded the ${timeout}-second time limit.`));
|
|
155
|
-
}, timeout * 1000);
|
|
156
|
-
const payload = {
|
|
157
|
-
data: { ...filteredData },
|
|
158
|
-
context: {
|
|
159
|
-
...context,
|
|
160
|
-
local: true,
|
|
161
|
-
resources: groupBlueprintResources(context.resources ?? []),
|
|
162
|
-
...(isGroqContextOptions(context) && {
|
|
163
|
-
clientOptions: {
|
|
164
|
-
...context.clientOptions,
|
|
165
|
-
apiHost: config.apiUrl,
|
|
166
|
-
},
|
|
167
|
-
}),
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
child.send(JSON.stringify({ srcPath: functionPath, payload }, null, 2));
|
|
171
|
-
}
|
|
172
|
-
function shutdown() {
|
|
173
|
-
clearTimeout(timer);
|
|
174
|
-
if (child && !child.killed) {
|
|
175
|
-
child.kill();
|
|
176
|
-
}
|
|
177
|
-
// If we previously created a temporary package.json, let's clean it up
|
|
178
|
-
if (!existingPackageJson) {
|
|
179
|
-
cleanupTempPackageJson(functionPath);
|
|
180
|
-
}
|
|
181
|
-
cleanupBundle().catch((err) => console.warn('Bundle cleanup failed:', err));
|
|
182
|
-
}
|
|
183
|
-
start();
|
|
184
|
-
});
|
|
185
|
-
}
|