@pikku/inspector 0.12.46 → 0.12.48
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/CHANGELOG.md +197 -0
- package/README.md +28 -2
- package/dist/add/add-feature.d.ts +19 -0
- package/dist/add/add-feature.js +200 -0
- package/dist/add/add-functions.js +24 -9
- package/dist/add/add-workflow.js +22 -29
- package/dist/error-codes.d.ts +3 -0
- package/dist/error-codes.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/inspector.js +39 -2
- package/dist/types.d.ts +43 -0
- package/dist/utils/extract-function-name.js +27 -0
- package/dist/utils/post-process.d.ts +26 -0
- package/dist/utils/post-process.js +98 -0
- package/dist/utils/serialize-inspector-state.d.ts +2 -1
- package/dist/utils/serialize-inspector-state.js +2 -0
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +203 -19
- package/dist/utils/workflow/dsl/patterns.d.ts +21 -0
- package/dist/utils/workflow/dsl/patterns.js +30 -0
- package/dist/utils/workflow/dsl/validation.d.ts +5 -0
- package/dist/utils/workflow/dsl/validation.js +9 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +36 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +5 -1
- package/dist/visit.js +2 -0
- package/package.json +4 -3
- package/src/add/add-feature.test.ts +306 -0
- package/src/add/add-feature.ts +253 -0
- package/src/add/add-functions.ts +29 -9
- package/src/add/add-scenario-step.test.ts +311 -0
- package/src/add/add-workflow-silent-drops.test.ts +98 -0
- package/src/add/add-workflow.ts +25 -37
- package/src/add/scenario-not-extractable.test.ts +159 -0
- package/src/add/scenario-services.test.ts +103 -0
- package/src/add/scenario-skip.test.ts +83 -0
- package/src/error-codes.ts +3 -0
- package/src/index.ts +1 -0
- package/src/inspector.ts +50 -1
- package/src/tsconfig-paths.test.ts +90 -0
- package/src/types.ts +45 -0
- package/src/utils/extract-function-name.ts +36 -0
- package/src/utils/post-process.test.ts +36 -2
- package/src/utils/post-process.ts +111 -0
- package/src/utils/serialize-inspector-state.ts +8 -1
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +236 -22
- package/src/utils/workflow/dsl/patterns.ts +38 -0
- package/src/utils/workflow/dsl/validation.ts +12 -0
- package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +35 -0
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +36 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +5 -0
- package/src/visit.ts +2 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { resolveDeployTarget } from './utils/resolve-deploy-target.js';
|
|
|
12
12
|
export { generateCustomTypes, sanitizeTypeName, } from './utils/custom-types-generator.js';
|
|
13
13
|
export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
|
|
14
14
|
export type { ContractEntry, VersionHashEntry, VersionValidateError, VersionManifest, VersionManifestEntry, } from './utils/contract-hashes.js';
|
|
15
|
+
export type { InspectorFeature, InspectorFeatureEntry } from './types.js';
|
|
15
16
|
export { serializeMCPJson } from './utils/serialize-mcp-json.js';
|
|
16
17
|
export type { OpenAPISpecInfo } from './utils/serialize-openapi-json.js';
|
|
17
18
|
export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/index.js';
|
package/dist/inspector.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import { performance } from 'perf_hooks';
|
|
3
|
-
import { resolve } from 'path';
|
|
3
|
+
import { dirname, resolve } from 'path';
|
|
4
4
|
import { visitSetup, visitFunctions, visitRoutes } from './visit.js';
|
|
5
5
|
import { TypesMap } from './types-map.js';
|
|
6
6
|
import { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
7
7
|
import { findCommonAncestor } from './utils/find-root-dir.js';
|
|
8
8
|
import { createNestedProjectFilter } from './utils/nested-project-filter.js';
|
|
9
|
-
import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, validateSchemaWiringSeparation, validateWorkflowGraphAddons, } from './utils/post-process.js';
|
|
9
|
+
import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, validateSchemaWiringSeparation, validateScenarioServices, validateScenarioSteps, validateWorkflowGraphAddons, } from './utils/post-process.js';
|
|
10
10
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
|
|
11
11
|
import { pikkuState } from '@pikku/core/internal';
|
|
12
12
|
import { resolveLatestVersions } from './utils/resolve-versions.js';
|
|
@@ -86,6 +86,7 @@ export function getInitialInspectorState(rootDir) {
|
|
|
86
86
|
files: new Map(),
|
|
87
87
|
graphMeta: {},
|
|
88
88
|
graphFiles: new Map(),
|
|
89
|
+
featureFiles: new Map(),
|
|
89
90
|
invokedWorkflows: new Set(),
|
|
90
91
|
},
|
|
91
92
|
rpc: {
|
|
@@ -204,9 +205,43 @@ export function getInitialInspectorState(rootDir) {
|
|
|
204
205
|
program: null,
|
|
205
206
|
};
|
|
206
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* The path mappings the inspector's program has to share with the project.
|
|
210
|
+
*
|
|
211
|
+
* The inspector builds its own program rather than reusing the project's, which
|
|
212
|
+
* is what keeps a cold run affordable — but a project that reaches its generated
|
|
213
|
+
* code through a tsconfig `paths` entry resolves that import through those
|
|
214
|
+
* mappings and nothing else. Without them the factory an inspected function is
|
|
215
|
+
* passed to resolves to `any`, so a function whose input type comes from the
|
|
216
|
+
* factory's contextual type (rather than an explicit generic or a named schema)
|
|
217
|
+
* silently records no input at all.
|
|
218
|
+
*
|
|
219
|
+
* Only resolution keys are copied. Type-inference options such as `strict` stay
|
|
220
|
+
* the inspector's own, because changing those would change the recorded types.
|
|
221
|
+
*/
|
|
222
|
+
const readProjectPathMappings = (logger, tsconfig) => {
|
|
223
|
+
if (!tsconfig) {
|
|
224
|
+
return {};
|
|
225
|
+
}
|
|
226
|
+
const configPath = resolve(tsconfig);
|
|
227
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
228
|
+
if (configFile.error || !configFile.config) {
|
|
229
|
+
logger.debug(`Could not read ${configPath} for path mappings; imports that rely on them will not resolve`);
|
|
230
|
+
return {};
|
|
231
|
+
}
|
|
232
|
+
const { options: projectOptions } = ts.parseJsonConfigFileContent(configFile.config, ts.sys, dirname(configPath));
|
|
233
|
+
const { baseUrl, paths, rootDirs, pathsBasePath } = projectOptions;
|
|
234
|
+
return {
|
|
235
|
+
...(baseUrl !== undefined ? { baseUrl } : {}),
|
|
236
|
+
...(paths !== undefined ? { paths } : {}),
|
|
237
|
+
...(rootDirs !== undefined ? { rootDirs } : {}),
|
|
238
|
+
...(pathsBasePath !== undefined ? { pathsBasePath } : {}),
|
|
239
|
+
};
|
|
240
|
+
};
|
|
207
241
|
export const inspect = async (logger, routeFiles, options = {}) => {
|
|
208
242
|
const normalizedRouteFiles = routeFiles.map((file) => resolve(file));
|
|
209
243
|
const compilerOptions = {
|
|
244
|
+
...readProjectPathMappings(logger, options.tsconfig),
|
|
210
245
|
target: ts.ScriptTarget.ESNext,
|
|
211
246
|
module: ts.ModuleKind.Node16,
|
|
212
247
|
skipLibCheck: true,
|
|
@@ -310,6 +345,8 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
310
345
|
computeDiagnostics(state);
|
|
311
346
|
validateSchemaWiringSeparation(logger, state);
|
|
312
347
|
validateWorkflowGraphAddons(logger, state);
|
|
348
|
+
validateScenarioServices(logger, state);
|
|
349
|
+
validateScenarioSteps(logger, state);
|
|
313
350
|
if (options.openAPI) {
|
|
314
351
|
state.openAPISpec = await generateOpenAPISpec(logger, state.functions.meta, state.http.meta, state.schemas, options.openAPI.additionalInfo, pikkuState(null, 'misc', 'errors'));
|
|
315
352
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -205,6 +205,12 @@ export type InspectorOptions = Partial<{
|
|
|
205
205
|
rootDir: string;
|
|
206
206
|
isAddon: boolean;
|
|
207
207
|
sourceFile: ts.SourceFile;
|
|
208
|
+
/**
|
|
209
|
+
* The project's tsconfig, read for its path mappings only. The inspector's
|
|
210
|
+
* program needs them to resolve imports the project reaches through `paths`;
|
|
211
|
+
* unlike `schemaConfig.tsconfig` this is also needed on a setup-only run.
|
|
212
|
+
*/
|
|
213
|
+
tsconfig: string;
|
|
208
214
|
types: Partial<{
|
|
209
215
|
configFileType: string;
|
|
210
216
|
userSessionType: string;
|
|
@@ -346,6 +352,34 @@ export interface AuthDefinition {
|
|
|
346
352
|
*/
|
|
347
353
|
services: FunctionServicesMeta;
|
|
348
354
|
}
|
|
355
|
+
/** One statically resolved entry of a feature's `scenarios` array. */
|
|
356
|
+
export interface InspectorFeatureEntry {
|
|
357
|
+
/** The scenario's declared export name, with any import alias followed back. */
|
|
358
|
+
scenario: string;
|
|
359
|
+
/** A `{ scenario, data }` entry's literal data — gherkin's `Examples:`. */
|
|
360
|
+
data?: unknown;
|
|
361
|
+
}
|
|
362
|
+
/** A `pikkuFeature` export, as far as it can be read from the source. */
|
|
363
|
+
export interface InspectorFeature {
|
|
364
|
+
path: string;
|
|
365
|
+
exportedName: string;
|
|
366
|
+
name?: string;
|
|
367
|
+
description?: string;
|
|
368
|
+
tags?: string[];
|
|
369
|
+
entries: InspectorFeatureEntry[];
|
|
370
|
+
/**
|
|
371
|
+
* Entries that are not literal — a spread, a `.map()`, a call. Counted rather
|
|
372
|
+
* than dropped so the console can say the listing is partial instead of
|
|
373
|
+
* quietly showing a short feature.
|
|
374
|
+
*/
|
|
375
|
+
unresolvedEntries: number;
|
|
376
|
+
/**
|
|
377
|
+
* Feature hooks run once around the whole group, never per scenario, and are
|
|
378
|
+
* runtime-only — so only their presence is recorded.
|
|
379
|
+
*/
|
|
380
|
+
hasBefore: boolean;
|
|
381
|
+
hasAfter: boolean;
|
|
382
|
+
}
|
|
349
383
|
export interface InspectorState {
|
|
350
384
|
rootDir: string;
|
|
351
385
|
singletonServicesTypeImportMap: PathToNameAndType;
|
|
@@ -395,6 +429,15 @@ export interface InspectorState {
|
|
|
395
429
|
path: string;
|
|
396
430
|
exportedName: string;
|
|
397
431
|
}>;
|
|
432
|
+
/**
|
|
433
|
+
* `pikkuFeature` exports, keyed by export identifier.
|
|
434
|
+
*
|
|
435
|
+
* Everything the config states literally is extracted here, because a
|
|
436
|
+
* feature is the document structure the console renders. Entries built by
|
|
437
|
+
* an ordinary loop cannot be enumerated statically — those are counted in
|
|
438
|
+
* `unresolvedEntries` and resolved at runtime by object identity instead.
|
|
439
|
+
*/
|
|
440
|
+
featureFiles: Map<string, InspectorFeature>;
|
|
398
441
|
invokedWorkflows: Set<string>;
|
|
399
442
|
};
|
|
400
443
|
rpc: {
|
|
@@ -28,6 +28,32 @@ export function funcIdToTypeName(id) {
|
|
|
28
28
|
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
|
29
29
|
.join('');
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* `pikkuScenarioStep({ name })` is the one wrapper whose `name` is its identity:
|
|
33
|
+
* scenarios reference a step by that string, so it must become the pikkuFuncId
|
|
34
|
+
* (and hence the generated step map's key), not just a cosmetic label the way
|
|
35
|
+
* `pikkuMiddleware`/`pikkuAgent` use it.
|
|
36
|
+
*/
|
|
37
|
+
function extractScenarioStepName(callExpr, result) {
|
|
38
|
+
if (!ts.isCallExpression(callExpr) ||
|
|
39
|
+
!ts.isIdentifier(callExpr.expression) ||
|
|
40
|
+
callExpr.expression.text !== 'pikkuScenarioStep') {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const firstArg = callExpr.arguments[0];
|
|
44
|
+
if (!firstArg || !ts.isObjectLiteralExpression(firstArg)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
for (const prop of firstArg.properties) {
|
|
48
|
+
if (ts.isPropertyAssignment(prop) &&
|
|
49
|
+
ts.isIdentifier(prop.name) &&
|
|
50
|
+
prop.name.text === 'name' &&
|
|
51
|
+
ts.isStringLiteral(prop.initializer)) {
|
|
52
|
+
result.explicitName = prop.initializer.text;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
31
57
|
export function extractFunctionName(callExpr, checker, rootDir) {
|
|
32
58
|
const parent = callExpr.parent;
|
|
33
59
|
// Initialize the result
|
|
@@ -40,6 +66,7 @@ export function extractFunctionName(callExpr, checker, rootDir) {
|
|
|
40
66
|
isHelper: false,
|
|
41
67
|
version: null,
|
|
42
68
|
};
|
|
69
|
+
extractScenarioStepName(callExpr, result);
|
|
43
70
|
const workflowHelpers = new Set([
|
|
44
71
|
'workflow',
|
|
45
72
|
'workflowStart',
|
|
@@ -50,6 +50,32 @@ export declare function computeMiddlewareGroupsMeta(state: InspectorState): void
|
|
|
50
50
|
export declare function computePermissionsGroupsMeta(state: InspectorState): void;
|
|
51
51
|
export declare function computeRequiredSchemas(state: InspectorState, options: InspectorOptions): void;
|
|
52
52
|
export declare function validateAgentModels(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
53
|
+
/**
|
|
54
|
+
* Scenarios are pure stories of remote RPCs (same rule as client-side CLI
|
|
55
|
+
* renderers): the func may only destructure logger/config — everything else
|
|
56
|
+
* must go through actor steps so the flow runs against the TARGET
|
|
57
|
+
* environment, never local services.
|
|
58
|
+
*
|
|
59
|
+
* This runs in post-processing rather than in `addWorkflow` because
|
|
60
|
+
* `addWorkflow` runs in the `visitSetup` pass, which completes before
|
|
61
|
+
* `addFunctions` (`visitFunctions`) has populated `state.functions.meta` — so
|
|
62
|
+
* the check read `undefined` there and never fired.
|
|
63
|
+
*/
|
|
64
|
+
export declare function validateScenarioServices(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
65
|
+
/**
|
|
66
|
+
* Walk every scenario step, wherever it is nested, and validate the two things
|
|
67
|
+
* that can only be known once both the workflow meta and the function meta
|
|
68
|
+
* exist:
|
|
69
|
+
*
|
|
70
|
+
* - the step target must be a static string literal (PKU678) — otherwise it
|
|
71
|
+
* can't be bundled, typed or drawn;
|
|
72
|
+
* - a `browser: true` step must be given an actor (PKU677), since the browser
|
|
73
|
+
* context signs in as that persona.
|
|
74
|
+
*
|
|
75
|
+
* It also marks the step's function as used, so `scenario run` boots the
|
|
76
|
+
* singletons the step actually needs.
|
|
77
|
+
*/
|
|
78
|
+
export declare function validateScenarioSteps(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
53
79
|
/**
|
|
54
80
|
* A `pikkuWorkflowGraph` node that references the `graph:` namespace (e.g.
|
|
55
81
|
* `graph:editFields`) needs @pikku/addon-graph wired — otherwise the RPC never
|
|
@@ -4,6 +4,7 @@ import { extractTypeKeys } from './type-utils.js';
|
|
|
4
4
|
import { ErrorCode } from '../error-codes.js';
|
|
5
5
|
import { AUTH_HANDLER_FUNC_ID } from '../add/add-auth.js';
|
|
6
6
|
import { flattenScopeDefinitions } from '@pikku/core/scope';
|
|
7
|
+
import { DYNAMIC_SCENARIO_STEP_TARGET } from './workflow/dsl/patterns.js';
|
|
7
8
|
/**
|
|
8
9
|
* Stamp the inspected authorize/callbacks service set onto the generated auth
|
|
9
10
|
* handler's function meta.
|
|
@@ -120,6 +121,19 @@ export function aggregateRequiredServices(state) {
|
|
|
120
121
|
addServices(funcMeta.services);
|
|
121
122
|
}
|
|
122
123
|
});
|
|
124
|
+
// 1b. Services the auth factory touches, read from the definition rather than
|
|
125
|
+
// from the generated handler.
|
|
126
|
+
//
|
|
127
|
+
// `stampAuthHandlerServices` copies them onto that handler's meta, but on the
|
|
128
|
+
// first run in a clean checkout the handler's file has not been written yet,
|
|
129
|
+
// so there is nothing to stamp and nothing to aggregate — and the services map
|
|
130
|
+
// comes out claiming `kysely` (or whatever else `authorize` reads) is unused.
|
|
131
|
+
// The second run, with the file on disk, says the opposite. The definition is
|
|
132
|
+
// inspected from hand-written source either way, so taking the answer from
|
|
133
|
+
// there makes a clean build agree with an incremental one.
|
|
134
|
+
if (state.auth?.definition) {
|
|
135
|
+
addServices(state.auth.definition.services);
|
|
136
|
+
}
|
|
123
137
|
// 2. Services from used middleware (individual + groups)
|
|
124
138
|
usedMiddleware.forEach((middlewareName) => {
|
|
125
139
|
const middlewareMeta = state.middleware.definitions[middlewareName];
|
|
@@ -539,6 +553,90 @@ export function validateAgentModels(logger, state) {
|
|
|
539
553
|
}
|
|
540
554
|
}
|
|
541
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* Scenarios are pure stories of remote RPCs (same rule as client-side CLI
|
|
558
|
+
* renderers): the func may only destructure logger/config — everything else
|
|
559
|
+
* must go through actor steps so the flow runs against the TARGET
|
|
560
|
+
* environment, never local services.
|
|
561
|
+
*
|
|
562
|
+
* This runs in post-processing rather than in `addWorkflow` because
|
|
563
|
+
* `addWorkflow` runs in the `visitSetup` pass, which completes before
|
|
564
|
+
* `addFunctions` (`visitFunctions`) has populated `state.functions.meta` — so
|
|
565
|
+
* the check read `undefined` there and never fired.
|
|
566
|
+
*/
|
|
567
|
+
export function validateScenarioServices(logger, state) {
|
|
568
|
+
for (const [workflowName, meta] of Object.entries(state.workflows.meta)) {
|
|
569
|
+
if (!meta.scenario)
|
|
570
|
+
continue;
|
|
571
|
+
const funcMeta = state.functions.meta[meta.pikkuFuncId];
|
|
572
|
+
if (!funcMeta?.services)
|
|
573
|
+
continue;
|
|
574
|
+
const disallowed = funcMeta.services.services.filter((svc) => svc !== 'logger' && svc !== 'config');
|
|
575
|
+
if (disallowed.length > 0) {
|
|
576
|
+
logger.critical(ErrorCode.SCENARIO_HAS_SERVICES, `Scenario '${workflowName}' destructures services: ${disallowed.join(', ')}. ` +
|
|
577
|
+
`Scenarios may only use 'logger'/'config' — drive everything else through ` +
|
|
578
|
+
`actor steps (workflow.do(step, rpc, data, { actor: actors.x })) so the flow ` +
|
|
579
|
+
`runs against the target environment.`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Walk every scenario step, wherever it is nested, and validate the two things
|
|
585
|
+
* that can only be known once both the workflow meta and the function meta
|
|
586
|
+
* exist:
|
|
587
|
+
*
|
|
588
|
+
* - the step target must be a static string literal (PKU678) — otherwise it
|
|
589
|
+
* can't be bundled, typed or drawn;
|
|
590
|
+
* - a `browser: true` step must be given an actor (PKU677), since the browser
|
|
591
|
+
* context signs in as that persona.
|
|
592
|
+
*
|
|
593
|
+
* It also marks the step's function as used, so `scenario run` boots the
|
|
594
|
+
* singletons the step actually needs.
|
|
595
|
+
*/
|
|
596
|
+
export function validateScenarioSteps(logger, state) {
|
|
597
|
+
const visit = (workflowName, steps) => {
|
|
598
|
+
for (const step of steps) {
|
|
599
|
+
if (step.type === 'scenarioStep') {
|
|
600
|
+
if (step.stepFunc === DYNAMIC_SCENARIO_STEP_TARGET) {
|
|
601
|
+
logger.critical(ErrorCode.SCENARIO_STEP_TARGET_NOT_STATIC, `Scenario '${workflowName}' calls step '${step.stepName}' with a target that isn't a string literal. ` +
|
|
602
|
+
`A step target must be statically resolvable so it can be bundled, typed and drawn — ` +
|
|
603
|
+
`loop over data sets instead of computing the step name.`);
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
state.serviceAggregation.usedFunctions.add(step.stepFunc);
|
|
607
|
+
const stepMeta = state.functions.meta[step.stepFunc];
|
|
608
|
+
if (stepMeta?.scenarioStepBrowser && !step.actor) {
|
|
609
|
+
logger.critical(ErrorCode.SCENARIO_BROWSER_STEP_NEEDS_ACTOR, `Scenario '${workflowName}' calls browser step '${step.stepFunc}' without an actor. ` +
|
|
610
|
+
`Pass { actor: actors.<name> } so the browser context signs in as that persona.`);
|
|
611
|
+
}
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
if (step.type === 'branch') {
|
|
615
|
+
for (const branch of step.branches)
|
|
616
|
+
visit(workflowName, branch.steps);
|
|
617
|
+
if (step.elseSteps)
|
|
618
|
+
visit(workflowName, step.elseSteps);
|
|
619
|
+
}
|
|
620
|
+
else if (step.type === 'switch') {
|
|
621
|
+
for (const c of step.cases ?? []) {
|
|
622
|
+
if (c.steps)
|
|
623
|
+
visit(workflowName, c.steps);
|
|
624
|
+
}
|
|
625
|
+
if (step.defaultSteps)
|
|
626
|
+
visit(workflowName, step.defaultSteps);
|
|
627
|
+
}
|
|
628
|
+
else if (step.type === 'fanout' && step.body) {
|
|
629
|
+
visit(workflowName, step.body);
|
|
630
|
+
}
|
|
631
|
+
else if (step.type === 'parallel' && step.children) {
|
|
632
|
+
visit(workflowName, step.children);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
for (const [workflowName, meta] of Object.entries(state.workflows.meta)) {
|
|
637
|
+
visit(workflowName, meta.steps ?? []);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
542
640
|
/**
|
|
543
641
|
* A `pikkuWorkflowGraph` node that references the `graph:` namespace (e.g.
|
|
544
642
|
* `graph:editFields`) needs @pikku/addon-graph wired — otherwise the RPC never
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JSONValue } from '@pikku/core';
|
|
2
|
-
import type { InspectorDiagnostic, InspectorState } from '../types.js';
|
|
2
|
+
import type { InspectorDiagnostic, InspectorFeature, InspectorState } from '../types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Serializable version of InspectorState that can be saved to JSON
|
|
5
5
|
* Omits typesLookup (contains non-serializable ts.Type objects) and converts Maps/Sets to arrays
|
|
@@ -160,6 +160,7 @@ export interface SerializableInspectorState {
|
|
|
160
160
|
path: string;
|
|
161
161
|
exportedName: string;
|
|
162
162
|
}]>;
|
|
163
|
+
featureFiles: Array<[string, InspectorFeature]>;
|
|
163
164
|
invokedWorkflows: string[];
|
|
164
165
|
};
|
|
165
166
|
rpc: {
|
|
@@ -69,6 +69,7 @@ export function serializeInspectorState(state) {
|
|
|
69
69
|
files: Array.from(state.workflows.files.entries()),
|
|
70
70
|
graphMeta: state.workflows.graphMeta,
|
|
71
71
|
graphFiles: Array.from(state.workflows.graphFiles.entries()),
|
|
72
|
+
featureFiles: Array.from(state.workflows.featureFiles.entries()),
|
|
72
73
|
invokedWorkflows: Array.from(state.workflows.invokedWorkflows),
|
|
73
74
|
},
|
|
74
75
|
rpc: {
|
|
@@ -232,6 +233,7 @@ export function deserializeInspectorState(data) {
|
|
|
232
233
|
files: new Map(data.workflows.files),
|
|
233
234
|
graphMeta: data.workflows.graphMeta || {},
|
|
234
235
|
graphFiles: new Map(data.workflows.graphFiles || []),
|
|
236
|
+
featureFiles: new Map(data.workflows.featureFiles || []),
|
|
235
237
|
invokedWorkflows: new Set(data.workflows.invokedWorkflows || []),
|
|
236
238
|
},
|
|
237
239
|
rpc: {
|