@sanity/runtime-cli 17.0.0 → 17.1.0-next.gcfbd613
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 +24 -23
- package/dist/actions/blueprints/assets.d.ts +2 -1
- package/dist/actions/blueprints/assets.js +2 -2
- package/dist/actions/blueprints/blueprint.d.ts +1 -1
- package/dist/actions/blueprints/wait-for-operation.js +4 -0
- package/dist/actions/functions/test.js +1 -1
- package/dist/actions/sanity/examples.d.ts +4 -4
- package/dist/baseCommands.d.ts +4 -4
- package/dist/baseCommands.js +13 -2
- package/dist/commands/blueprints/add.d.ts +1 -1
- package/dist/commands/blueprints/config.d.ts +1 -1
- package/dist/commands/blueprints/deploy.d.ts +1 -1
- package/dist/commands/blueprints/destroy.d.ts +1 -1
- package/dist/commands/blueprints/doctor.d.ts +0 -1
- package/dist/commands/blueprints/doctor.js +3 -7
- package/dist/commands/blueprints/info.d.ts +1 -1
- package/dist/commands/blueprints/logs.d.ts +1 -1
- package/dist/commands/blueprints/mint-deploy-token.d.ts +1 -1
- package/dist/commands/blueprints/plan.d.ts +1 -1
- package/dist/commands/blueprints/promote.d.ts +1 -1
- package/dist/commands/blueprints/stacks.d.ts +1 -1
- package/dist/commands/functions/add.d.ts +1 -1
- package/dist/commands/functions/build.d.ts +1 -1
- package/dist/commands/functions/dev.d.ts +1 -1
- package/dist/commands/functions/env/add.d.ts +1 -1
- package/dist/commands/functions/env/list.d.ts +1 -1
- package/dist/commands/functions/env/remove.d.ts +1 -1
- package/dist/commands/functions/logs.d.ts +1 -1
- package/dist/commands/functions/test.d.ts +1 -1
- 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/blueprints/deploy.js +5 -4
- package/dist/cores/blueprints/doctor.js +15 -12
- package/dist/cores/functions/build.js +3 -2
- package/dist/cores/functions/test.js +48 -2
- package/dist/server/app.js +39 -5
- 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/favicon-dark.svg +1 -0
- package/dist/server/static/favicon.svg +1 -0
- package/dist/server/static/index.html +2 -2
- package/dist/server/static/vendor/vendor.bundle.js +115 -61
- package/dist/server/static/web-socket.js +41 -0
- package/dist/utils/functions/child-process-wrapper.js +62 -7
- package/dist/utils/functions/find-entry-point.d.ts +1 -1
- package/dist/utils/functions/find-entry-point.js +10 -12
- 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 +101 -0
- package/dist/utils/functions/package-json.d.ts +1 -2
- package/dist/utils/functions/package-json.js +4 -6
- package/dist/utils/functions/prepare-asset.d.ts +2 -2
- package/dist/utils/functions/prepare-asset.js +18 -7
- package/dist/utils/functions/resolve-dependencies.d.ts +4 -1
- package/dist/utils/functions/resolve-dependencies.js +5 -6
- package/dist/utils/functions/resource-to-arc.d.ts +1 -1
- package/dist/utils/functions/resource-to-arc.js +4 -5
- package/dist/utils/functions/should-transpile.d.ts +7 -1
- package/dist/utils/functions/should-transpile.js +10 -10
- package/dist/utils/functions/transpile/function.d.ts +4 -1
- package/dist/utils/functions/transpile/function.js +9 -14
- package/dist/utils/functions/transpile/verify-handler.js +1 -1
- package/dist/utils/isTrueish.d.ts +6 -0
- package/dist/utils/isTrueish.js +9 -0
- package/dist/utils/logger.d.ts +7 -5
- package/dist/utils/logger.js +33 -1
- package/dist/utils/progress.d.ts +5 -0
- package/dist/utils/progress.js +40 -0
- package/dist/utils/types.d.ts +67 -5
- package/oclif.manifest.json +5 -4
- package/package.json +28 -23
- package/dist/server/static/hot-reload.js +0 -16
- package/dist/utils/functions/invoke-local.js +0 -185
|
@@ -19,7 +19,8 @@ export async function blueprintDeployCore(options) {
|
|
|
19
19
|
const noWait = flags['no-wait'] || false;
|
|
20
20
|
const stackName = flags['new-stack-name'] ?? deployedStack.name;
|
|
21
21
|
const installer = flags['fn-installer'] || undefined;
|
|
22
|
-
const
|
|
22
|
+
const blueprintFileDir = dirname(blueprint.fileInfo.blueprintFilePath);
|
|
23
|
+
const workspaceWarnings = getWarnings({ dir: blueprintFileDir });
|
|
23
24
|
log(`Deploying "${stackName}" ${niceId(deployedStack.id)}...`);
|
|
24
25
|
try {
|
|
25
26
|
const { ok: checkOk, stack: currentStack } = await getStack({ stackId, auth, logger: log });
|
|
@@ -42,7 +43,7 @@ export async function blueprintDeployCore(options) {
|
|
|
42
43
|
if (assetResources.length > 0) {
|
|
43
44
|
log('Processing assets...');
|
|
44
45
|
for (const resource of assetResources) {
|
|
45
|
-
const preDeployResult = await preDeploy(resource, { auth, installer, log });
|
|
46
|
+
const preDeployResult = await preDeploy(resource, { auth, blueprintFileDir, installer, log });
|
|
46
47
|
if (!preDeployResult.success)
|
|
47
48
|
return preDeployResult;
|
|
48
49
|
const idx = resources.indexOf(resource);
|
|
@@ -152,7 +153,7 @@ export async function blueprintDeployCore(options) {
|
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
async function preDeploy(resource, options) {
|
|
155
|
-
const { auth, installer, log } = options;
|
|
156
|
+
const { auth, blueprintFileDir, installer, log } = options;
|
|
156
157
|
const fnSpinner = log.ora({ text: `Processing ${resource.name}...`, prefixText: ' ' }).start();
|
|
157
158
|
const warnTimer = setTimeout(() => {
|
|
158
159
|
fnSpinner.text = `Still processing ${resource.name}, this can take a moment...`;
|
|
@@ -161,7 +162,7 @@ async function preDeploy(resource, options) {
|
|
|
161
162
|
let result;
|
|
162
163
|
try {
|
|
163
164
|
result = await Promise.race([
|
|
164
|
-
stashAsset({ resource, auth, logger: log, installer }),
|
|
165
|
+
stashAsset({ resource, blueprintFileDir, auth, logger: log, installer }),
|
|
165
166
|
new Promise((_, reject) => {
|
|
166
167
|
assetTimeoutTimer = setTimeout(() => {
|
|
167
168
|
reject(new Error(`Processing ${resource.name} timed out after ${assetTimeoutS}s`));
|
|
@@ -2,13 +2,13 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { arch, cwd, env, version as nodeVersion, platform } from 'node:process';
|
|
4
4
|
import * as resolve from 'empathic/resolve';
|
|
5
|
-
import ora from 'ora';
|
|
6
5
|
import { loadAndParseBlueprint, } from '../../actions/blueprints/blueprint.js';
|
|
7
6
|
import { readConfigFile } from '../../actions/blueprints/config.js';
|
|
8
7
|
import { resolveIds } from '../../actions/blueprints/resolve.js';
|
|
9
8
|
import { getStack } from '../../actions/blueprints/stacks.js';
|
|
10
9
|
import config, { RUNTIME_CLI_VERSION } from '../../config.js';
|
|
11
10
|
import { formatDateTime } from '../../utils/display/dates.js';
|
|
11
|
+
import { presentBlueprintParserErrors } from '../../utils/display/errors.js';
|
|
12
12
|
import { advise, check, filePathRelativeToCwd, niceId, renderSection, severe, unsure, } from '../../utils/display/presenters.js';
|
|
13
13
|
import { styleText } from '../../utils/style-text.js';
|
|
14
14
|
import { createTracedFetch } from '../../utils/traced-fetch.js';
|
|
@@ -55,7 +55,7 @@ export async function blueprintDoctorCore(options) {
|
|
|
55
55
|
envRows.push(['CLI', bin]);
|
|
56
56
|
const configRows = [];
|
|
57
57
|
const stackRows = [];
|
|
58
|
-
const spinner = ora('Running diagnostics...').start();
|
|
58
|
+
const spinner = log.ora('Running diagnostics...').start();
|
|
59
59
|
// --- ONLINE ---
|
|
60
60
|
const fetchFn = createTracedFetch(log);
|
|
61
61
|
try {
|
|
@@ -109,6 +109,7 @@ export async function blueprintDoctorCore(options) {
|
|
|
109
109
|
status: false,
|
|
110
110
|
detail: `${blueprint.errors.length} error(s)`,
|
|
111
111
|
};
|
|
112
|
+
log.verbose(presentBlueprintParserErrors(blueprint.errors));
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
catch {
|
|
@@ -186,6 +187,8 @@ export async function blueprintDoctorCore(options) {
|
|
|
186
187
|
stackRows.push(['Operation', `${op.status}${timestamp}`]);
|
|
187
188
|
if (op.userMessage)
|
|
188
189
|
stackRows.push([' Message', `"${op.userMessage}"`]);
|
|
190
|
+
if (v && op.systemMessage)
|
|
191
|
+
stackRows.push([' System', `"${op.systemMessage}"`]);
|
|
189
192
|
}
|
|
190
193
|
if (stack.resources) {
|
|
191
194
|
stackRows.push(['Resources', `${stack.resources.length}`]);
|
|
@@ -275,20 +278,20 @@ export async function blueprintDoctorCore(options) {
|
|
|
275
278
|
}
|
|
276
279
|
// --- RENDER REPORT ---
|
|
277
280
|
spinner.stop();
|
|
278
|
-
// Environment
|
|
279
|
-
log
|
|
280
|
-
log
|
|
281
|
-
// Configuration
|
|
281
|
+
// Environment
|
|
282
|
+
log(renderSection('Environment', envRows));
|
|
283
|
+
log('');
|
|
284
|
+
// Configuration
|
|
282
285
|
if (configRows.length > 0) {
|
|
283
|
-
log
|
|
284
|
-
log
|
|
286
|
+
log(renderSection('Configuration', configRows));
|
|
287
|
+
log('');
|
|
285
288
|
}
|
|
286
|
-
// Deployment
|
|
289
|
+
// Deployment
|
|
287
290
|
if (stackRows.length > 0) {
|
|
288
|
-
log
|
|
289
|
-
log
|
|
291
|
+
log(renderSection('Deployment', stackRows));
|
|
292
|
+
log('');
|
|
290
293
|
}
|
|
291
|
-
// Checks
|
|
294
|
+
// Checks
|
|
292
295
|
const maxLabel = Math.max(...Object.values(diagLookup).map((l) => l.length));
|
|
293
296
|
log(styleText('bold', 'Checks'));
|
|
294
297
|
let allGood = true;
|
|
@@ -18,13 +18,14 @@ export async function functionBuildCore(options) {
|
|
|
18
18
|
functions = allFunctions;
|
|
19
19
|
}
|
|
20
20
|
const installer = flags['fn-installer'] || undefined;
|
|
21
|
-
const
|
|
21
|
+
const blueprintFileDir = path.dirname(blueprint.fileInfo.blueprintFilePath);
|
|
22
|
+
const outDir = flags['out-dir'] || path.join(blueprintFileDir, '.build');
|
|
22
23
|
await fs.promises.mkdir(outDir, { recursive: true });
|
|
23
24
|
const errors = [];
|
|
24
25
|
const built = [];
|
|
25
26
|
for (const resource of functions) {
|
|
26
27
|
const spinner = log.ora(`Building ${resource.name}...`).start();
|
|
27
|
-
const prepResult = await prepareAsset({ resource
|
|
28
|
+
const prepResult = await prepareAsset({ resource, blueprintFileDir, installer });
|
|
28
29
|
if (prepResult.success === false) {
|
|
29
30
|
spinner.fail(`Failed to build ${resource.name}`);
|
|
30
31
|
errors.push({ name: resource.name, error: prepResult.error ?? 'Unknown error' });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
3
|
import { cwd } from 'node:process';
|
|
4
4
|
import { testAction } from '../../actions/functions/test.js';
|
|
5
5
|
import config from '../../config.js';
|
|
@@ -8,6 +8,7 @@ import { resolveDeployedResources } from '../../utils/blueprints/resolve-deploye
|
|
|
8
8
|
import buildPayload from '../../utils/functions/build-payload.js';
|
|
9
9
|
import { fetchAsset, fetchDocument } from '../../utils/functions/fetch-document.js';
|
|
10
10
|
import { findFunctionInResources, getFunctionNames } from '../../utils/functions/find.js';
|
|
11
|
+
import { runFunction } from '../../utils/functions/invoke/nested.js';
|
|
11
12
|
import { parseJsonObject } from '../../utils/parse-json-object.js';
|
|
12
13
|
import { isEventType, } from '../../utils/types.js';
|
|
13
14
|
export async function functionTestCore(options) {
|
|
@@ -49,6 +50,7 @@ export async function functionTestCore(options) {
|
|
|
49
50
|
: undefined,
|
|
50
51
|
logger: log,
|
|
51
52
|
});
|
|
53
|
+
const blueprintFileDir = dirname(blueprint.fileInfo.blueprintFilePath);
|
|
52
54
|
const contextOptions = docFunction || mediaFunction
|
|
53
55
|
? {
|
|
54
56
|
clientOptions: {
|
|
@@ -62,8 +64,9 @@ export async function functionTestCore(options) {
|
|
|
62
64
|
functionResourceType: 'project',
|
|
63
65
|
functionResourceId: projectId || '',
|
|
64
66
|
resources: resolvedResources,
|
|
67
|
+
blueprintFileDir,
|
|
65
68
|
}
|
|
66
|
-
: { clientOptions: {}, resources: resolvedResources };
|
|
69
|
+
: { clientOptions: {}, resources: resolvedResources, blueprintFileDir };
|
|
67
70
|
// If the user sets the flag to use the real token set it in our options
|
|
68
71
|
if (withUserToken) {
|
|
69
72
|
contextOptions.clientOptions.token = config.token || undefined;
|
|
@@ -146,12 +149,54 @@ export async function functionTestCore(options) {
|
|
|
146
149
|
: {
|
|
147
150
|
event: 'scheduled',
|
|
148
151
|
};
|
|
152
|
+
// Records each nested context.invoke() so they can be surfaced in order after the spinner resolves.
|
|
153
|
+
const nestedRuns = [];
|
|
154
|
+
let nestedOrder = 0;
|
|
155
|
+
/**
|
|
156
|
+
* Re-entrant callback that resolves a nested context.invoke(name, payload):
|
|
157
|
+
* it finds the named function in the same blueprint, reuses the already
|
|
158
|
+
* resolved resources, and runs it via the shared invoke-chain helper (which
|
|
159
|
+
* applies the depth/cycle guard and extends the call stack). Passing itself
|
|
160
|
+
* as onNestedInvoke keeps it recursable. Every run is recorded so it can be
|
|
161
|
+
* surfaced after the spinner resolves, including failed ones — a downstream
|
|
162
|
+
* error is fire-and-forget (it doesn't reject the caller) but is still shown.
|
|
163
|
+
*/
|
|
164
|
+
const runNested = async (nestedName, nestedPayload, ctx, depth, callStack) => {
|
|
165
|
+
const order = ++nestedOrder;
|
|
166
|
+
const nestedResource = findFunctionInResources(blueprint.resources, nestedName);
|
|
167
|
+
const result = await runFunction(nestedResource, { payload: { data: nestedPayload }, event: 'nested' }, { ...ctx, resources: resolvedResources }, { depth, callStack, onNestedInvoke: runNested });
|
|
168
|
+
nestedRuns.push({ order, name: nestedName, logs: result.logs, error: result.error });
|
|
169
|
+
return result;
|
|
170
|
+
};
|
|
171
|
+
const logNestedRuns = () => {
|
|
172
|
+
for (const run of [...nestedRuns].sort((a, b) => a.order - b.order)) {
|
|
173
|
+
log(`↳ BEGIN: invoke('${run.name}')`);
|
|
174
|
+
// Show any logs received
|
|
175
|
+
if (run.logs?.trim()) {
|
|
176
|
+
log(run.logs.trim());
|
|
177
|
+
}
|
|
178
|
+
// Before any error occurs
|
|
179
|
+
if (run.error) {
|
|
180
|
+
log(run.error.stack || run.error.message || run.error.name);
|
|
181
|
+
}
|
|
182
|
+
log(`↳ END: invoke('${run.name}')`);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
149
185
|
const spinner = log.ora('Executing function...').start();
|
|
150
186
|
const { json: response, logs, error, } = await testAction(resource, invokeOptions, contextOptions, {
|
|
151
187
|
timeout: timeout ? timeout : resource.timeout,
|
|
188
|
+
onNestedInvoke: runNested,
|
|
189
|
+
depth: 0,
|
|
190
|
+
callStack: [fnName],
|
|
152
191
|
});
|
|
153
192
|
if (error) {
|
|
154
193
|
spinner.fail('Function execution failed.');
|
|
194
|
+
// Surface the function's own logs even on failure
|
|
195
|
+
if (logs?.trim()) {
|
|
196
|
+
log('Logs:');
|
|
197
|
+
log(logs);
|
|
198
|
+
}
|
|
199
|
+
logNestedRuns();
|
|
155
200
|
const errorMessage = error.stack || error.message || error.name;
|
|
156
201
|
const isTimeout = error.message?.includes('Timeout');
|
|
157
202
|
return {
|
|
@@ -166,6 +211,7 @@ export async function functionTestCore(options) {
|
|
|
166
211
|
spinner.succeed('Function execution succeeded.');
|
|
167
212
|
log('Logs:');
|
|
168
213
|
log(logs || '');
|
|
214
|
+
logNestedRuns();
|
|
169
215
|
if (response) {
|
|
170
216
|
log('Response:');
|
|
171
217
|
log(JSON.stringify(response, null, 2));
|
package/dist/server/app.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync, watchFile } from 'node:fs';
|
|
2
2
|
import * as http from 'node:http';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
3
4
|
import { default as mime } from 'mime-types';
|
|
4
|
-
import { WebSocketServer } from 'ws';
|
|
5
|
+
import { WebSocket, WebSocketServer } from 'ws';
|
|
5
6
|
import { findBlueprintFile, loadAndParseBlueprint } from '../actions/blueprints/blueprint.js';
|
|
6
7
|
import { readConfigFile } from '../actions/blueprints/config.js';
|
|
7
8
|
import { resolveIds } from '../actions/blueprints/resolve.js';
|
|
@@ -10,6 +11,35 @@ import { isRecord } from '../utils/is-record.js';
|
|
|
10
11
|
import { isEventType, isGroqContextOptions, } from '../utils/types.js';
|
|
11
12
|
import { handleInvokeRequest } from './handlers/invoke.js';
|
|
12
13
|
const app = (host, port, validateResources, executionOptions) => {
|
|
14
|
+
const wss = new WebSocketServer({ port: 8974 });
|
|
15
|
+
// Send a JSON envelope to every connected UI client.
|
|
16
|
+
const broadcast = (envelope) => {
|
|
17
|
+
const message = JSON.stringify(envelope);
|
|
18
|
+
for (const client of wss.clients) {
|
|
19
|
+
if (client.readyState === WebSocket.OPEN)
|
|
20
|
+
client.send(message);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
let nestedOrder = 0;
|
|
24
|
+
/**
|
|
25
|
+
* Re-entrant callback that resolves a nested `context.invoke(name, payload)` by
|
|
26
|
+
* re-entering the invoke pipeline (which spawns its own child) and streams the
|
|
27
|
+
* nested run to the UI. Passing `nestedInvoke` to itself keeps it recursable.
|
|
28
|
+
*/
|
|
29
|
+
const nestedInvoke = async (name, payload, ctx, depth, callStack) => {
|
|
30
|
+
const order = ++nestedOrder;
|
|
31
|
+
const result = await handleInvokeRequest(name, { data: payload }, { event: 'nested' }, ctx, validateResources, executionOptions, depth, callStack, nestedInvoke);
|
|
32
|
+
broadcast({
|
|
33
|
+
type: 'nested-invoke',
|
|
34
|
+
name,
|
|
35
|
+
order,
|
|
36
|
+
logs: result.logs,
|
|
37
|
+
json: result.json,
|
|
38
|
+
error: result.error,
|
|
39
|
+
timings: result.timings,
|
|
40
|
+
});
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
13
43
|
const requestListener = async (req, res) => {
|
|
14
44
|
res.setHeader('Content-Type', 'application/json');
|
|
15
45
|
switch (true) {
|
|
@@ -39,7 +69,11 @@ const app = (host, port, validateResources, executionOptions) => {
|
|
|
39
69
|
req.on('end', async () => {
|
|
40
70
|
res.setHeader('Content-Type', 'application/json');
|
|
41
71
|
try {
|
|
42
|
-
const
|
|
72
|
+
const fileInfo = findBlueprintFile();
|
|
73
|
+
if (!fileInfo) {
|
|
74
|
+
throw Error('Could not find a Blueprint manifest (sanity.blueprint.ts, .js, or .json). Run `blueprints init` to create one.');
|
|
75
|
+
}
|
|
76
|
+
const { data, func: functionName, metadata, } = parseInvokeRequest(Buffer.concat(body), dirname(fileInfo.blueprintFilePath));
|
|
43
77
|
const { context, event } = data;
|
|
44
78
|
// replace user token if required
|
|
45
79
|
if (isGroqContextOptions(context)) {
|
|
@@ -50,7 +84,7 @@ const app = (host, port, validateResources, executionOptions) => {
|
|
|
50
84
|
delete context.clientOptions.token;
|
|
51
85
|
}
|
|
52
86
|
}
|
|
53
|
-
const result = await handleInvokeRequest(functionName, event, metadata, context, validateResources, executionOptions);
|
|
87
|
+
const result = await handleInvokeRequest(functionName, event, metadata, context, validateResources, executionOptions, 0, [], nestedInvoke);
|
|
54
88
|
// Add Server-Timing header
|
|
55
89
|
const timingHeaders = [];
|
|
56
90
|
for (const [key, value] of Object.entries(result.timings)) {
|
|
@@ -235,7 +269,6 @@ const app = (host, port, validateResources, executionOptions) => {
|
|
|
235
269
|
};
|
|
236
270
|
const server = http.createServer(requestListener);
|
|
237
271
|
server.listen(port, host, () => { });
|
|
238
|
-
const wss = new WebSocketServer({ port: 8974 });
|
|
239
272
|
wss.on('connection', function connection(ws) {
|
|
240
273
|
ws.on('error', console.error);
|
|
241
274
|
const fileInfo = findBlueprintFile();
|
|
@@ -284,7 +317,7 @@ function buildApiUrl(projectId, dataset, docId, apiUrl) {
|
|
|
284
317
|
function buildAssetUrl(mediaLibraryId, docId, apiUrl) {
|
|
285
318
|
return `${apiUrl}/v2025-03-24/media-libraries/${mediaLibraryId}/doc/${docId}`;
|
|
286
319
|
}
|
|
287
|
-
function parseInvokeRequest(body) {
|
|
320
|
+
function parseInvokeRequest(body, blueprintFileDir) {
|
|
288
321
|
let json;
|
|
289
322
|
try {
|
|
290
323
|
json = JSON.parse(body.toString('utf8'));
|
|
@@ -376,6 +409,7 @@ function parseInvokeRequest(body) {
|
|
|
376
409
|
context: {
|
|
377
410
|
...context,
|
|
378
411
|
clientOptions,
|
|
412
|
+
blueprintFileDir,
|
|
379
413
|
// Provide default values for required properties if not present
|
|
380
414
|
eventResourceType: typeof context.eventResourceType === 'string' ? context.eventResourceType : '',
|
|
381
415
|
eventResourceId: typeof context.eventResourceId === 'string' ? context.eventResourceId : '',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InvocationResponse, InvokeContextOptions, InvokeExecutionOptions, InvokePayloadMetadata } from '../../utils/types.js';
|
|
2
|
-
export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: InvokePayloadMetadata, context: InvokeContextOptions, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions
|
|
1
|
+
import type { InvocationResponse, InvokeContextOptions, InvokeExecutionOptions, InvokePayloadMetadata, NestedInvoke } from '../../utils/types.js';
|
|
2
|
+
export declare function handleInvokeRequest(functionName: string, event: Record<string, unknown>, metadata: InvokePayloadMetadata, context: InvokeContextOptions, validateResources: boolean, executionOptions?: Partial<InvokeExecutionOptions>, depth?: number, callStack?: string[], onNestedInvoke?: NestedInvoke): Promise<InvocationResponse & {
|
|
3
3
|
timings: Record<string, number>;
|
|
4
4
|
}>;
|
|
@@ -2,10 +2,10 @@ import { readLocalBlueprint } from '../../actions/blueprints/blueprint.js';
|
|
|
2
2
|
import config from '../../config.js';
|
|
3
3
|
import { resolveDeployedResources } from '../../utils/blueprints/resolve-deployed-resources.js';
|
|
4
4
|
import { findFunctionInResources } from '../../utils/functions/find.js';
|
|
5
|
-
import
|
|
5
|
+
import { runFunction } from '../../utils/functions/invoke/nested.js';
|
|
6
6
|
import { Logger } from '../../utils/logger.js';
|
|
7
7
|
const silentLogger = Logger(() => { });
|
|
8
|
-
export async function handleInvokeRequest(functionName, event, metadata, context, validateResources, executionOptions) {
|
|
8
|
+
export async function handleInvokeRequest(functionName, event, metadata, context, validateResources, executionOptions, depth = 0, callStack = [], onNestedInvoke) {
|
|
9
9
|
const start = performance.now();
|
|
10
10
|
const { resources, stackId, scopeType, scopeId } = await readLocalBlueprint(silentLogger, {
|
|
11
11
|
resources: validateResources,
|
|
@@ -22,9 +22,12 @@ export async function handleInvokeRequest(functionName, event, metadata, context
|
|
|
22
22
|
payload: event,
|
|
23
23
|
...metadata,
|
|
24
24
|
};
|
|
25
|
-
const response = await
|
|
25
|
+
const response = await runFunction(resource, payload, { ...context, resources: resolvedResources }, {
|
|
26
26
|
forceColor: executionOptions?.forceColor ?? false,
|
|
27
|
-
timeout: executionOptions?.timeout
|
|
27
|
+
timeout: executionOptions?.timeout,
|
|
28
|
+
depth,
|
|
29
|
+
callStack,
|
|
30
|
+
onNestedInvoke,
|
|
28
31
|
});
|
|
29
32
|
const timings = { ...response.timings, 'blueprint:read': readBlueprintTime };
|
|
30
33
|
return { ...response, timings };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* globals customElements document */
|
|
1
|
+
/* globals customElements document window */
|
|
2
2
|
import {ApiBaseElement} from './api-base.js'
|
|
3
3
|
import {getSharedStyleSheets} from './shared-styles.js'
|
|
4
4
|
|
|
@@ -36,6 +36,16 @@ template.innerHTML = `
|
|
|
36
36
|
`
|
|
37
37
|
|
|
38
38
|
class ConsolePanel extends ApiBaseElement {
|
|
39
|
+
/**
|
|
40
|
+
* Nested context.invoke() output for the current run, kept as a list so the
|
|
41
|
+
* final render can order it by call order. Nested runs finish innermost-first
|
|
42
|
+
* (A → B → C resolves C then B), so each carries an `order` stamped when it was
|
|
43
|
+
* initiated; the final render sorts by it. Buffered separately so it survives
|
|
44
|
+
* the result render, which would otherwise overwrite the console when Preserve
|
|
45
|
+
* Log is off.
|
|
46
|
+
*/
|
|
47
|
+
nestedEntries = []
|
|
48
|
+
|
|
39
49
|
constructor() {
|
|
40
50
|
super()
|
|
41
51
|
this.attachShadow({mode: 'open'}).appendChild(template.content.cloneNode(true))
|
|
@@ -45,25 +55,59 @@ class ConsolePanel extends ApiBaseElement {
|
|
|
45
55
|
if (!this.consoleOutput || !result) return
|
|
46
56
|
|
|
47
57
|
const {error, logs} = result
|
|
48
|
-
|
|
58
|
+
// Always show the logs. Then append the error, rather than replacing the logs with it.
|
|
59
|
+
let update = logs ?? ''
|
|
49
60
|
if (error) {
|
|
50
|
-
|
|
51
|
-
update = (
|
|
52
|
-
} else {
|
|
53
|
-
// Display regular logs
|
|
54
|
-
update = logs ?? '' // Handle case where logs might be null/undefined
|
|
61
|
+
const errText = (error.stack || error.message || error.name) ?? 'An error occurred.'
|
|
62
|
+
update = update.trim() ? `${update.trimEnd()}\n${errText}` : errText
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
if (this.api.store.preserveLog) {
|
|
66
|
+
// Nested output was already appended live during the run; keep it in place.
|
|
58
67
|
this.consoleOutput.innerText = this.consoleOutput.innerText + update
|
|
59
68
|
} else {
|
|
60
|
-
|
|
69
|
+
// Re-render so the nested output is shown in call order rather than the
|
|
70
|
+
// completion order it streamed in (and so it survives this overwrite).
|
|
71
|
+
this.consoleOutput.innerText = update + this.renderNestedEntries()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Join the nested runs in call order (the order they were initiated).
|
|
76
|
+
renderNestedEntries = () =>
|
|
77
|
+
[...this.nestedEntries]
|
|
78
|
+
.sort((a, b) => a.order - b.order)
|
|
79
|
+
.map((e) => e.text)
|
|
80
|
+
.join('')
|
|
81
|
+
|
|
82
|
+
updateNested = (event) => {
|
|
83
|
+
const nestedInvoke = event.detail
|
|
84
|
+
if (!this.consoleOutput || !nestedInvoke) return
|
|
85
|
+
|
|
86
|
+
const {name, order, logs, json, error} = nestedInvoke
|
|
87
|
+
// Always show the logs. Then append the error, rather than replacing the logs with it.
|
|
88
|
+
let body = logs ?? ''
|
|
89
|
+
if (error) {
|
|
90
|
+
const errText = (error.stack || error.message || error.name) ?? 'An error occurred.'
|
|
91
|
+
body = body.trim() ? `${body.trimEnd()}\n${errText}` : errText
|
|
92
|
+
} else if (typeof json !== 'undefined') {
|
|
93
|
+
body += `${body && !body.endsWith('\n') ? '\n' : ''}→ returned ${JSON.stringify(json)}\n`
|
|
61
94
|
}
|
|
95
|
+
|
|
96
|
+
const text = `↳ BEGIN: invoke('${name}')\n${body.trim()}\n↳ END: invoke('${name}')\n`
|
|
97
|
+
this.nestedEntries.push({order, text})
|
|
98
|
+
// Append live for immediate feedback; the final result render reorders.
|
|
99
|
+
this.consoleOutput.innerText = this.consoleOutput.innerText + text
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// A new invocation is starting; drop the previous run's nested output.
|
|
103
|
+
resetNested = ({inprogress}) => {
|
|
104
|
+
if (inprogress) this.nestedEntries = []
|
|
62
105
|
}
|
|
63
106
|
|
|
64
107
|
clear = () => {
|
|
65
108
|
const backUp = this.api.store.preserveLog
|
|
66
109
|
this.api.store.result = {logs: undefined, error: undefined}
|
|
110
|
+
this.nestedEntries = []
|
|
67
111
|
this.consoleOutput.innerText = ''
|
|
68
112
|
this.api.store.preserveLog = backUp
|
|
69
113
|
}
|
|
@@ -75,9 +119,14 @@ class ConsolePanel extends ApiBaseElement {
|
|
|
75
119
|
this.consoleOutput = this.shadowRoot.querySelector('#console-output')
|
|
76
120
|
this.addEventListener('clear-console', this.clear)
|
|
77
121
|
|
|
122
|
+
// Nested context.invoke() runs arrive as a stream of discrete DOM events
|
|
123
|
+
// (not state) so none are dropped when several land in one frame.
|
|
124
|
+
window.addEventListener('nested-invoke', this.updateNested)
|
|
125
|
+
|
|
78
126
|
// Subscribe to changes in the result state to update the console
|
|
79
127
|
if (this.api) {
|
|
80
128
|
this.api.subscribe(this.updateConsole, ['result'])
|
|
129
|
+
this.api.subscribe(this.resetNested, ['inprogress'])
|
|
81
130
|
// Initial update in case result is already populated
|
|
82
131
|
if (this.api.store.result) {
|
|
83
132
|
this.updateConsole({result: this.api.store.result})
|
|
@@ -90,9 +139,11 @@ class ConsolePanel extends ApiBaseElement {
|
|
|
90
139
|
|
|
91
140
|
disconnectedCallback() {
|
|
92
141
|
this.removeEventListener('clear-console', this.clear)
|
|
142
|
+
window.removeEventListener('nested-invoke', this.updateNested)
|
|
93
143
|
// Unsubscribe when the element is removed from the DOM
|
|
94
144
|
if (this.api) {
|
|
95
145
|
this.api.unsubscribe(this.updateConsole)
|
|
146
|
+
this.api.unsubscribe(this.resetNested)
|
|
96
147
|
}
|
|
97
148
|
}
|
|
98
149
|
}
|
|
@@ -43,14 +43,17 @@ class RulePanel extends ApiBaseElement {
|
|
|
43
43
|
break
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
// Guard against the sate not being setup
|
|
47
|
+
if (rule?.state) {
|
|
48
|
+
const transaction = rule.state.update({
|
|
49
|
+
changes: {
|
|
50
|
+
from: 0,
|
|
51
|
+
insert: func.event ? JSON.stringify(func.event, null, 2) : '',
|
|
52
|
+
to: rule.state.doc.length,
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
rule.dispatch(transaction)
|
|
56
|
+
}
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
async connectedCallback() {
|
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
/* globals fetch */
|
|
2
2
|
|
|
3
|
-
let
|
|
3
|
+
let sharedStyleSheetsPromise = null
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
async function loadSharedStyleSheets() {
|
|
6
|
+
// Fetch both stylesheets
|
|
7
|
+
const [mCssResponse, appCssResponse] = await Promise.all([
|
|
8
|
+
fetch('./vendor/m-.css'),
|
|
9
|
+
fetch('./components/app.css'),
|
|
10
|
+
])
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
// Fetch both stylesheets
|
|
12
|
-
const [mCssResponse, appCssResponse] = await Promise.all([
|
|
13
|
-
fetch('./vendor/m-.css'),
|
|
14
|
-
fetch('./components/app.css'),
|
|
15
|
-
])
|
|
12
|
+
const [mCssText, appCssText] = await Promise.all([mCssResponse.text(), appCssResponse.text()])
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
// Create CSSStyleSheet objects
|
|
15
|
+
const mSheet = new CSSStyleSheet()
|
|
16
|
+
const appSheet = new CSSStyleSheet()
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
const mSheet = new CSSStyleSheet()
|
|
21
|
-
const appSheet = new CSSStyleSheet()
|
|
18
|
+
await Promise.all([mSheet.replace(mCssText), appSheet.replace(appCssText)])
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
return [mSheet, appSheet]
|
|
21
|
+
}
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
export function getSharedStyleSheets() {
|
|
24
|
+
if (!sharedStyleSheetsPromise) {
|
|
25
|
+
sharedStyleSheetsPromise = loadSharedStyleSheets().catch((error) => {
|
|
26
|
+
console.error('Failed to load shared stylesheets:', error)
|
|
27
|
+
// Reset so a later caller can retry instead of being stuck with [].
|
|
28
|
+
sharedStyleSheetsPromise = null
|
|
29
|
+
return []
|
|
30
|
+
})
|
|
30
31
|
}
|
|
32
|
+
|
|
33
|
+
return sharedStyleSheetsPromise
|
|
31
34
|
}
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<filters-component></filters-component>
|
|
34
34
|
<input type="checkbox" id="nav-toggle" class="hidden">
|
|
35
35
|
<nav id="sidebar" class="relative y-scroll max-h-100 pad-t-5 border-right overflow-auto nav-min-width">
|
|
36
|
-
<label for="nav-toggle" id="nav-toggle-label" title="Toggle Functions list" class="z-10 nav-label"></label>
|
|
36
|
+
<label for="nav-toggle" id="nav-toggle-label" aria-label="Toggle Functions list" title="Toggle Functions list" class="z-10 nav-label"></label>
|
|
37
37
|
<h4 class="config-label mar-t-0 mar-b-3 pad-l-3">Functions</h4>
|
|
38
38
|
<function-list></function-list>
|
|
39
39
|
</nav>
|
|
@@ -64,6 +64,6 @@
|
|
|
64
64
|
<script src="./components/clear-button.js" type="module"></script>
|
|
65
65
|
<script src="./components/help-button.js" type="module"></script>
|
|
66
66
|
<script src="./components/fetch-button.js" type="module"></script>
|
|
67
|
-
<script src="./
|
|
67
|
+
<script src="./web-socket.js" type="module"></script>
|
|
68
68
|
</body>
|
|
69
69
|
</html>
|