@swell/cli 2.9.2 → 2.9.3

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.
@@ -0,0 +1,311 @@
1
+ import * as fs from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ const require = createRequire(import.meta.url);
4
+ const ts = require('typescript');
5
+ const ORDINARY_TRIGGER_KEYS = ['route', 'model', 'cron'];
6
+ const WORKFLOW_FORBIDDEN_KEYS = [
7
+ 'route',
8
+ 'model',
9
+ 'cron',
10
+ 'extension',
11
+ 'timeout',
12
+ ];
13
+ export async function analyzeFunctionSource(filePath) {
14
+ const sourceText = await fs.promises.readFile(filePath, 'utf8');
15
+ return analyzeFunctionSourceText(sourceText, filePath);
16
+ }
17
+ export function analyzeFunctionSourceText(sourceText, filePath = 'function.ts') {
18
+ const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') || filePath.endsWith('.ts')
19
+ ? ts.ScriptKind.TS
20
+ : ts.ScriptKind.JS);
21
+ const diagnostics = [];
22
+ const parseDiagnostics = sourceFile
23
+ .parseDiagnostics ?? [];
24
+ diagnostics.push(...parseDiagnostics.map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')));
25
+ const configExport = findConfigExport(sourceFile);
26
+ let config;
27
+ let kind = 'unknown';
28
+ if (configExport?.initializer) {
29
+ const initializer = unwrapExpression(configExport.initializer);
30
+ const kindValue = readConfigKind(initializer);
31
+ if (kindValue === 'dynamic') {
32
+ diagnostics.push('config.kind must be a static string literal.');
33
+ }
34
+ const evalResult = evaluateStatic(initializer);
35
+ if (evalResult.errors.length > 0) {
36
+ if (kindValue === 'workflow') {
37
+ diagnostics.push(...evalResult.errors);
38
+ }
39
+ }
40
+ else if (isRecord(evalResult.value)) {
41
+ config = evalResult.value;
42
+ }
43
+ const configKind = config?.kind ?? kindValue;
44
+ if (configKind === undefined) {
45
+ kind = config ? 'function' : 'unknown';
46
+ }
47
+ else if (configKind === 'function' || configKind === 'workflow') {
48
+ kind = configKind;
49
+ }
50
+ else if (typeof configKind === 'string') {
51
+ diagnostics.push(`Unsupported config.kind "${configKind}".`);
52
+ }
53
+ }
54
+ const analysis = {
55
+ config,
56
+ kind,
57
+ diagnostics,
58
+ };
59
+ if (kind === 'workflow') {
60
+ analysis.defaultExport = validateWorkflowDefaultExport(sourceFile);
61
+ analysis.diagnostics.push(...validateWorkflowConfig(config));
62
+ if (!analysis.defaultExport.validWorkflowClass) {
63
+ analysis.diagnostics.push(analysis.defaultExport.reason ||
64
+ 'Workflow default export must be a same-file class with a run method.');
65
+ }
66
+ }
67
+ return analysis;
68
+ }
69
+ export function getFunctionTriggers(config) {
70
+ return ORDINARY_TRIGGER_KEYS.filter((key) => Boolean(config[key]));
71
+ }
72
+ export function hasKindDiagnostic(analysis) {
73
+ return analysis.diagnostics.some((diagnostic) => diagnostic === 'config.kind must be a static string literal.' ||
74
+ diagnostic.startsWith('Unsupported config.kind '));
75
+ }
76
+ export function workflowStaticConfigError() {
77
+ return new Error('Workflow configs must export a static object literal so the CLI can choose the workflow bundling path before compiling.');
78
+ }
79
+ function findConfigExport(sourceFile) {
80
+ const localVariables = new Map();
81
+ let exportedConfigLocalName;
82
+ let directExport;
83
+ for (const statement of sourceFile.statements) {
84
+ if (ts.isVariableStatement(statement)) {
85
+ const isExported = hasModifier(statement, ts.SyntaxKind.ExportKeyword);
86
+ for (const declaration of statement.declarationList.declarations) {
87
+ if (!ts.isIdentifier(declaration.name)) {
88
+ continue;
89
+ }
90
+ localVariables.set(declaration.name.text, declaration.initializer);
91
+ if (isExported && declaration.name.text === 'config') {
92
+ directExport = { initializer: declaration.initializer };
93
+ }
94
+ }
95
+ }
96
+ if (ts.isExportDeclaration(statement) &&
97
+ statement.exportClause &&
98
+ ts.isNamedExports(statement.exportClause)) {
99
+ for (const element of statement.exportClause.elements) {
100
+ const exportedName = element.name.text;
101
+ if (exportedName === 'config') {
102
+ exportedConfigLocalName = (element.propertyName || element.name).text;
103
+ }
104
+ }
105
+ }
106
+ }
107
+ if (directExport) {
108
+ return directExport;
109
+ }
110
+ if (exportedConfigLocalName) {
111
+ return { initializer: localVariables.get(exportedConfigLocalName) };
112
+ }
113
+ }
114
+ function readConfigKind(expression) {
115
+ const unwrapped = unwrapExpression(expression);
116
+ if (!ts.isObjectLiteralExpression(unwrapped)) {
117
+ return undefined;
118
+ }
119
+ for (const property of unwrapped.properties) {
120
+ if (!ts.isPropertyAssignment(property)) {
121
+ continue;
122
+ }
123
+ const name = staticPropertyName(property.name);
124
+ if (name !== 'kind') {
125
+ continue;
126
+ }
127
+ const value = unwrapExpression(property.initializer);
128
+ return ts.isStringLiteral(value) ? value.text : 'dynamic';
129
+ }
130
+ }
131
+ function evaluateStatic(expression) {
132
+ const unwrapped = unwrapExpression(expression);
133
+ if (ts.isObjectLiteralExpression(unwrapped)) {
134
+ const value = {};
135
+ const errors = [];
136
+ for (const property of unwrapped.properties) {
137
+ if (ts.isSpreadAssignment(property)) {
138
+ errors.push('config must not use object spreads.');
139
+ continue;
140
+ }
141
+ if (!ts.isPropertyAssignment(property)) {
142
+ errors.push('config must contain only static property assignments.');
143
+ continue;
144
+ }
145
+ const name = staticPropertyName(property.name);
146
+ if (!name) {
147
+ errors.push('config must not use computed property keys.');
148
+ continue;
149
+ }
150
+ const result = evaluateStatic(property.initializer);
151
+ if (result.errors.length > 0) {
152
+ errors.push(...result.errors.map((error) => `${name}: ${error}`));
153
+ continue;
154
+ }
155
+ value[name] = result.value;
156
+ }
157
+ return { errors, value };
158
+ }
159
+ if (ts.isArrayLiteralExpression(unwrapped)) {
160
+ const value = [];
161
+ const errors = [];
162
+ for (const element of unwrapped.elements) {
163
+ if (ts.isSpreadElement(element)) {
164
+ errors.push('arrays must not use spreads.');
165
+ continue;
166
+ }
167
+ const result = evaluateStatic(element);
168
+ if (result.errors.length > 0) {
169
+ errors.push(...result.errors);
170
+ continue;
171
+ }
172
+ value.push(result.value);
173
+ }
174
+ return { errors, value };
175
+ }
176
+ if (ts.isStringLiteral(unwrapped) ||
177
+ ts.isNoSubstitutionTemplateLiteral(unwrapped)) {
178
+ return { errors: [], value: unwrapped.text };
179
+ }
180
+ if (ts.isNumericLiteral(unwrapped)) {
181
+ return { errors: [], value: Number(unwrapped.text) };
182
+ }
183
+ if (unwrapped.kind === ts.SyntaxKind.TrueKeyword) {
184
+ return { errors: [], value: true };
185
+ }
186
+ if (unwrapped.kind === ts.SyntaxKind.FalseKeyword) {
187
+ return { errors: [], value: false };
188
+ }
189
+ if (unwrapped.kind === ts.SyntaxKind.NullKeyword) {
190
+ return { errors: [], value: null };
191
+ }
192
+ if (ts.isPrefixUnaryExpression(unwrapped) &&
193
+ ts.isNumericLiteral(unwrapped.operand) &&
194
+ (unwrapped.operator === ts.SyntaxKind.MinusToken ||
195
+ unwrapped.operator === ts.SyntaxKind.PlusToken)) {
196
+ const value = Number(unwrapped.operand.text);
197
+ return {
198
+ errors: [],
199
+ value: unwrapped.operator === ts.SyntaxKind.MinusToken ? -value : value,
200
+ };
201
+ }
202
+ return { errors: ['value must be static JSON-like metadata.'] };
203
+ }
204
+ function validateWorkflowConfig(config) {
205
+ if (!config) {
206
+ return ['Workflow config must be a static object literal.'];
207
+ }
208
+ const diagnostics = [];
209
+ for (const key of WORKFLOW_FORBIDDEN_KEYS) {
210
+ if (key in config) {
211
+ diagnostics.push(`Workflow config must not specify ${key}.`);
212
+ }
213
+ }
214
+ return diagnostics;
215
+ }
216
+ function validateWorkflowDefaultExport(sourceFile) {
217
+ const classes = new Map();
218
+ const defaultExports = [];
219
+ for (const statement of sourceFile.statements) {
220
+ if (ts.isClassDeclaration(statement) && statement.name) {
221
+ classes.set(statement.name.text, statement);
222
+ }
223
+ if (ts.isClassDeclaration(statement) &&
224
+ hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
225
+ defaultExports.push(statement);
226
+ }
227
+ else if (ts.isFunctionDeclaration(statement) &&
228
+ hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
229
+ defaultExports.push(statement);
230
+ }
231
+ else if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
232
+ defaultExports.push(statement);
233
+ }
234
+ else if (ts.isExportDeclaration(statement) &&
235
+ statement.exportClause &&
236
+ ts.isNamedExports(statement.exportClause)) {
237
+ for (const element of statement.exportClause.elements) {
238
+ if (element.name.text === 'default') {
239
+ defaultExports.push(statement);
240
+ }
241
+ }
242
+ }
243
+ }
244
+ if (defaultExports.length !== 1) {
245
+ return {
246
+ validWorkflowClass: false,
247
+ reason: 'Workflow files must have exactly one default export.',
248
+ };
249
+ }
250
+ const defaultExport = defaultExports[0];
251
+ let workflowClass;
252
+ if (ts.isClassDeclaration(defaultExport)) {
253
+ workflowClass = defaultExport;
254
+ }
255
+ else if (ts.isExportAssignment(defaultExport)) {
256
+ const expression = unwrapExpression(defaultExport.expression);
257
+ if (ts.isIdentifier(expression)) {
258
+ workflowClass = classes.get(expression.text);
259
+ }
260
+ }
261
+ if (!workflowClass) {
262
+ return {
263
+ validWorkflowClass: false,
264
+ reason: 'Workflow default export must resolve to a class declared in the same file.',
265
+ };
266
+ }
267
+ if (!hasInstanceRunMethod(workflowClass)) {
268
+ return {
269
+ validWorkflowClass: false,
270
+ name: workflowClass.name?.text,
271
+ reason: 'Workflow default export class must define an instance run method.',
272
+ };
273
+ }
274
+ return {
275
+ validWorkflowClass: true,
276
+ name: workflowClass.name?.text,
277
+ };
278
+ }
279
+ function hasInstanceRunMethod(classDeclaration) {
280
+ return classDeclaration.members.some((member) => {
281
+ if (!ts.isMethodDeclaration(member) ||
282
+ hasModifier(member, ts.SyntaxKind.StaticKeyword)) {
283
+ return false;
284
+ }
285
+ return staticPropertyName(member.name) === 'run';
286
+ });
287
+ }
288
+ function unwrapExpression(expression) {
289
+ let current = expression;
290
+ while (ts.isParenthesizedExpression(current) ||
291
+ ts.isAsExpression(current) ||
292
+ ts.isTypeAssertionExpression(current) ||
293
+ ts.isSatisfiesExpression(current)) {
294
+ current = current.expression;
295
+ }
296
+ return current;
297
+ }
298
+ function staticPropertyName(name) {
299
+ if (ts.isIdentifier(name) ||
300
+ ts.isStringLiteral(name) ||
301
+ ts.isNumericLiteral(name)) {
302
+ return name.text;
303
+ }
304
+ }
305
+ function hasModifier(node, kind) {
306
+ return Boolean(ts.canHaveModifiers(node) &&
307
+ ts.getModifiers(node)?.some((modifier) => modifier.kind === kind));
308
+ }
309
+ function isRecord(value) {
310
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
311
+ }
@@ -19,5 +19,7 @@ export declare class LoggedItem implements LogItem {
19
19
  type: string;
20
20
  constructor(log: any);
21
21
  private prepareRequest;
22
+ private prepareRequestWorkflow;
22
23
  private prepareRequestFunction;
24
+ private prepareConsoleLines;
23
25
  }
@@ -42,6 +42,10 @@ export class LoggedItem {
42
42
  request = this.prepareRequestFunction(logMessage);
43
43
  break;
44
44
  }
45
+ case 'workflow': {
46
+ request = this.prepareRequestWorkflow(logMessage);
47
+ break;
48
+ }
45
49
  case 'webhook': {
46
50
  const { event } = logMessage;
47
51
  request = `Webhook ${event.type} > ${style.logMethod('POST')} ${url}`;
@@ -61,10 +65,32 @@ export class LoggedItem {
61
65
  }
62
66
  return request;
63
67
  }
68
+ prepareRequestWorkflow(logMessage) {
69
+ const { error, logs, phase, status, step_name: stepName, workflow_instance_id: instanceId, workflow_name: workflowName, } = logMessage;
70
+ const parts = ['Workflow', workflowName || '-', instanceId || '-'];
71
+ if (phase) {
72
+ parts.push(phase);
73
+ }
74
+ if (stepName) {
75
+ parts.push(`"${stepName}"`);
76
+ }
77
+ if (status) {
78
+ parts.push(String(status));
79
+ }
80
+ if (error) {
81
+ parts.push(style.funcError(String(error)));
82
+ }
83
+ const logLines = this.prepareConsoleLines(logs);
84
+ return `${parts.join(' ')}${logLines ? `\n${logLines}` : ''}`;
85
+ }
64
86
  prepareRequestFunction(logMessage) {
65
87
  const { event } = logMessage;
66
88
  const { logs, method, name } = logMessage;
67
- const logLines = logs
89
+ const logLines = this.prepareConsoleLines(logs);
90
+ return `Function ${event ? `${event.hook ? `${event.hook}:` : ''}${event.type} >` : ''} ${style.logMethod(method.toUpperCase())} /${name} ${logLines ? `\n${logLines}` : ''}`;
91
+ }
92
+ prepareConsoleLines(logs) {
93
+ return logs
68
94
  ?.map((l) => {
69
95
  const line = l.line?.join('\n');
70
96
  switch (l.level) {
@@ -83,6 +109,5 @@ export class LoggedItem {
83
109
  }
84
110
  })
85
111
  .join('\n');
86
- return `Function ${event ? `${event.hook ? `${event.hook}:` : ''}${event.type} >` : ''} ${style.logMethod(method.toUpperCase())} /${name} ${logLines ? `\n${logLines}` : ''}`;
87
112
  }
88
113
  }
@@ -48,6 +48,11 @@ declare function executeModuleHandler(req: SwellRequest, context: Event): Promis
48
48
  * @returns {boolean}
49
49
  */
50
50
  declare function isOrdinaryObject(val: any): boolean;
51
+ declare function validateWorkflowParams(params: any): any;
52
+ declare function validateWorkflowParamValue(value: any, seen?: WeakSet<object>): void;
53
+ declare function validateWorkflowParamArray(value: any, seen: any): void;
54
+ declare function validateWorkflowParamObject(value: any, seen: any): void;
55
+ declare function createWorkflowParamsError(code: any, message: any): SwellError;
51
56
  declare namespace originalConsole {
52
57
  let log: {
53
58
  (...data: any[]): void;
@@ -70,6 +75,7 @@ declare namespace originalConsole {
70
75
  (message?: any, ...optionalParams: any[]): void;
71
76
  };
72
77
  }
78
+ declare const WORKFLOW_PARAMS_MAX_BYTES: number;
73
79
  /**
74
80
  * Class representing a Swell request.
75
81
  */
@@ -121,6 +127,9 @@ declare class SwellAPI {
121
127
  baseUrl: any;
122
128
  basicAuth: string;
123
129
  context: any;
130
+ workflows: {
131
+ create: (name: any, params: any) => Promise<any>;
132
+ };
124
133
  toBase64(inputString: any): string;
125
134
  stringifyQuery(queryObject: any, prefix: any): any;
126
135
  makeRequest(method: any, url: any, data: any): Promise<any>;
@@ -129,6 +138,7 @@ declare class SwellAPI {
129
138
  post(url: any, data: any): Promise<any>;
130
139
  delete(url: any, data: any): Promise<any>;
131
140
  settings(id?: any): Promise<any>;
141
+ createWorkflow(name: any, params: any): Promise<any>;
132
142
  /**
133
143
  * Atomic multi-op write. Throws SwellError with a stable `error.code`
134
144
  * (transaction_conflict | transaction_timeout | transaction_throttled
@@ -6,6 +6,7 @@ const originalConsole = {
6
6
  warn: console.warn,
7
7
  error: console.error,
8
8
  };
9
+ const WORKFLOW_PARAMS_MAX_BYTES = 128 * 1024;
9
10
  addEventListener('fetch', (event) => {
10
11
  event.respondWith(request(event.request, event.env, event));
11
12
  });
@@ -240,6 +241,9 @@ class SwellAPI {
240
241
  this.baseUrl = req.apiHost;
241
242
  this.basicAuth = `${req.storeId}:${req.accessToken}`;
242
243
  this.context = context;
244
+ this.workflows = {
245
+ create: (name, params) => this.createWorkflow(name, params),
246
+ };
243
247
  }
244
248
  toBase64(inputString) {
245
249
  const utf8Bytes = new TextEncoder().encode(inputString);
@@ -324,6 +328,15 @@ class SwellAPI {
324
328
  async settings(id = this.request.appId) {
325
329
  return this.makeRequest('GET', `/settings/${id}`);
326
330
  }
331
+ async createWorkflow(name, params) {
332
+ const data = {
333
+ workflow_name: name,
334
+ };
335
+ if (params !== undefined) {
336
+ data.params = validateWorkflowParams(params);
337
+ }
338
+ return this.makeRequest('POST', '/:workflows/instances', data);
339
+ }
327
340
  /**
328
341
  * Atomic multi-op write. Throws SwellError with a stable `error.code`
329
342
  * (transaction_conflict | transaction_timeout | transaction_throttled
@@ -499,3 +512,89 @@ function isOrdinaryObject(val) {
499
512
  val !== null &&
500
513
  Object.getPrototypeOf(val) === Object.prototype);
501
514
  }
515
+ function validateWorkflowParams(params) {
516
+ validateWorkflowParamValue(params);
517
+ const serialized = JSON.stringify(params);
518
+ const size = new TextEncoder().encode(serialized).length;
519
+ if (size > WORKFLOW_PARAMS_MAX_BYTES) {
520
+ throw createWorkflowParamsError('workflow_params_too_large', 'Workflow params are too large; pass identifiers and re-fetch data inside the workflow');
521
+ }
522
+ return params;
523
+ }
524
+ function validateWorkflowParamValue(value, seen = new WeakSet()) {
525
+ if (value === null) {
526
+ return;
527
+ }
528
+ switch (typeof value) {
529
+ case 'string':
530
+ case 'boolean':
531
+ return;
532
+ case 'number':
533
+ if (Number.isFinite(value)) {
534
+ return;
535
+ }
536
+ break;
537
+ case 'object': {
538
+ if (Array.isArray(value)) {
539
+ validateWorkflowParamArray(value, seen);
540
+ return;
541
+ }
542
+ const prototype = Object.getPrototypeOf(value);
543
+ if (prototype !== Object.prototype && prototype !== null) {
544
+ break;
545
+ }
546
+ validateWorkflowParamObject(value, seen);
547
+ return;
548
+ }
549
+ default:
550
+ break;
551
+ }
552
+ throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
553
+ }
554
+ function validateWorkflowParamArray(value, seen) {
555
+ if (seen.has(value)) {
556
+ throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
557
+ }
558
+ seen.add(value);
559
+ if (Object.getOwnPropertySymbols(value).length > 0) {
560
+ throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
561
+ }
562
+ for (const key of Object.keys(value)) {
563
+ if (!/^(0|[1-9]\d*)$/.test(key) || Number(key) >= value.length) {
564
+ throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
565
+ }
566
+ }
567
+ for (let i = 0; i < value.length; i += 1) {
568
+ if (!Object.prototype.hasOwnProperty.call(value, i)) {
569
+ throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
570
+ }
571
+ validateWorkflowParamValue(value[i], seen);
572
+ }
573
+ seen.delete(value);
574
+ }
575
+ function validateWorkflowParamObject(value, seen) {
576
+ if (seen.has(value)) {
577
+ throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
578
+ }
579
+ seen.add(value);
580
+ if (Object.getOwnPropertySymbols(value).length > 0) {
581
+ throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
582
+ }
583
+ for (const item of Object.values(value)) {
584
+ validateWorkflowParamValue(item, seen);
585
+ }
586
+ seen.delete(value);
587
+ }
588
+ function createWorkflowParamsError(code, message) {
589
+ return new SwellError({
590
+ error: {
591
+ code,
592
+ message,
593
+ status: 400,
594
+ retryable: false,
595
+ },
596
+ }, {
597
+ code,
598
+ status: 400,
599
+ });
600
+ }
@@ -0,0 +1,68 @@
1
+ import Api from '../api.js';
2
+ import { InspectScope } from '../apps/inspect-scope.js';
3
+ export declare const WORKFLOW_STATUSES: string[];
4
+ export declare const WORKFLOW_INSTANCE_ID_PATTERN: RegExp;
5
+ export interface WorkflowManifestRecord {
6
+ app_id?: string;
7
+ enabled?: boolean;
8
+ id?: string;
9
+ kind?: string;
10
+ name?: string;
11
+ summary?: WorkflowSummary;
12
+ trigger?: string;
13
+ workflow_name?: string;
14
+ }
15
+ export interface WorkflowSummary {
16
+ active_instances?: number;
17
+ completed_recent?: number;
18
+ failed_instances?: number;
19
+ last_failure?: WorkflowFailure;
20
+ last_create_failure?: WorkflowFailure;
21
+ last_runtime_failure?: WorkflowFailure;
22
+ terminated_recent?: number;
23
+ }
24
+ export interface WorkflowFailure {
25
+ code?: string;
26
+ date?: string;
27
+ message?: string;
28
+ }
29
+ export interface WorkflowRunRecord {
30
+ app_id?: string;
31
+ cf_instance_id?: string;
32
+ date_completed?: string;
33
+ date_created?: string;
34
+ date_failed?: string;
35
+ date_started?: string;
36
+ date_terminated?: string;
37
+ error?: {
38
+ code?: string;
39
+ message?: string;
40
+ };
41
+ failure_phase?: string;
42
+ status?: string;
43
+ trigger?: string;
44
+ workflow_id?: string;
45
+ workflow_instance_id?: string;
46
+ workflow_name?: string;
47
+ }
48
+ export interface WorkflowOperationRef {
49
+ appId: string;
50
+ appSlug?: string;
51
+ query: {
52
+ workflow_id?: string;
53
+ workflow_name?: string;
54
+ };
55
+ }
56
+ export declare function resolveWorkflowOperationRef(api: Api, workflow: string, flags: {
57
+ app?: string;
58
+ }): Promise<WorkflowOperationRef>;
59
+ export declare function resolveWorkflowScope(api: Api, flags: {
60
+ app?: string;
61
+ }): Promise<InspectScope>;
62
+ export declare function isWorkflowInstanceIdentifier(value: string): boolean;
63
+ export declare function workflowDisplayName(record: WorkflowManifestRecord): string;
64
+ export declare function workflowListMeta(record: WorkflowManifestRecord): string;
65
+ export declare function workflowRunDate(run: WorkflowRunRecord): string | undefined;
66
+ export declare function workflowRunMeta(run: WorkflowRunRecord): string;
67
+ export declare function formatWorkflowRelativeTime(value: string, now?: number): string;
68
+ export declare function redactWorkflowPayload(value: unknown): unknown;