@pikku/inspector 0.12.103 → 0.12.106
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 +50 -0
- package/dist/add/add-cli.js +21 -11
- package/dist/add/add-functions.js +8 -8
- package/dist/add/add-virtual-users.d.ts +13 -0
- package/dist/add/{add-personas.js → add-virtual-users.js} +33 -33
- package/dist/add/add-workflow.js +14 -10
- package/dist/error-codes.d.ts +4 -3
- package/dist/error-codes.js +4 -3
- package/dist/inspector.js +5 -3
- package/dist/types.d.ts +3 -3
- package/dist/utils/extract-function-name.js +1 -1
- package/dist/utils/get-property-value.js +1 -1
- package/dist/utils/load-addon-functions-meta.js +1 -0
- package/dist/utils/post-process.d.ts +36 -36
- package/dist/utils/post-process.js +198 -173
- package/dist/utils/serialize-inspector-state.d.ts +2 -2
- package/dist/utils/serialize-inspector-state.js +6 -6
- package/dist/utils/single-declaration.d.ts +3 -3
- package/dist/utils/single-declaration.js +3 -3
- package/dist/utils/validate-single-step-workflows.d.ts +3 -0
- package/dist/utils/validate-single-step-workflows.js +32 -0
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +3 -3
- package/dist/utils/workflow/dsl/patterns.d.ts +3 -3
- package/dist/utils/workflow/dsl/patterns.js +4 -4
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +6 -6
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +5 -5
- package/dist/visit.js +2 -2
- package/knowledge/decisions/a-channel-with-auth-false-marks-its-connect-and-disconnect-sessionless.md +22 -0
- package/knowledge/decisions/a-declared-scope-id-never-shadows-a-definition.md +20 -0
- package/knowledge/decisions/a-dsl-step-virtual-user-is-the-literal-name-in-its-options.md +27 -0
- package/knowledge/decisions/a-feature-flags-anyof-is-read-from-the-ast-and-an-empty-list-is-refused.md +20 -0
- package/knowledge/decisions/a-non-literal-string-array-reads-as-absent.md +25 -0
- package/knowledge/decisions/a-permission-with-an-underscore-data-parameter-takes-no-data.md +20 -0
- package/knowledge/decisions/a-provider-login-virtual-user-is-never-runnable.md +27 -0
- package/knowledge/decisions/a-remote-addon-bound-credential-or-secret-must-be-wired.md +27 -0
- package/knowledge/decisions/a-route-auth-overrides-its-groups-auth.md +22 -0
- package/knowledge/decisions/a-route-requires-a-session-when-any-auth-signal-says-so.md +29 -0
- package/knowledge/decisions/a-webhook-source-with-verify-gets-a-singleton-secret-credential.md +21 -0
- package/knowledge/decisions/a-wire-with-auth-disabled-cannot-point-at-a-pikkufunc.md +21 -0
- package/knowledge/decisions/addon-contracts-cannot-wire-or-declare-handlers.md +25 -0
- package/knowledge/decisions/addon-declarations-name-the-secrets-and-credentials-the-app-lends-it.md +19 -0
- package/knowledge/decisions/an-inline-wiring-function-is-sessionless-unless-built-with-pikkufunc.md +23 -0
- package/knowledge/decisions/better-auth-admin-plugin-is-refused.md +24 -0
- package/knowledge/decisions/define-screens-scopes-must-be-string-literals.md +18 -0
- package/knowledge/decisions/index.md +38 -0
- package/knowledge/decisions/inspector-records-a-non-literal-global-grant-as-source-text.md +20 -0
- package/knowledge/decisions/only-one-pikku-better-auth-export-per-codebase.md +19 -0
- package/knowledge/decisions/scope-references-and-secret-aliases-are-critical-diagnostics.md +23 -0
- package/knowledge/decisions/scope-segments-may-not-be-wildcards-or-contain-colons.md +23 -0
- package/knowledge/decisions/shorthand-permission-entries-are-extracted.md +22 -0
- package/knowledge/decisions/system-roles-declare-literal-unqualified-scopes.md +28 -0
- package/knowledge/decisions/the-inspector-records-agent-permissions-only-to-aggregate-services.md +14 -0
- package/knowledge/decisions/vault-secrets-reach-no-sink-and-pii-stays-out-of-logs-and-webhooks.md +30 -0
- package/knowledge/decisions/webhook-gateway-wrappers-are-sessionless-and-gateway-auth-runs-inside-the-handler.md +26 -0
- package/knowledge/index.md +14 -0
- package/knowledge/questions/does-a-non-literal-remote-addon-auth-get-checked.md +26 -0
- package/knowledge/questions/index.md +16 -0
- package/knowledge/questions/is-an-incomplete-oauth2-block-dropped-without-a-log.md +23 -0
- package/knowledge/questions/should-app-functions-named-like-scenario-instrumentation-be-refused.md +28 -0
- package/knowledge/questions/should-the-openapi-document-default-every-operation-to-a-bearer-or-api-key.md +22 -0
- package/package.json +3 -3
- package/dist/add/add-personas.d.ts +0 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
|
+
## 0.12.106
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- fdcc821: Fabric is gone from the open toolchain. The previous names are removed with no aliases.
|
|
6
|
+
|
|
7
|
+
The CLI group is `pikku cloud …` and the export is `@pikku/cli/cloud`. The project link is the `cloud` key of `pikku.config.json` (`cloud.projectId`), the CLI keeps its local state in `~/.pikkustudio` (run `pikku cloud login` again), and the project config file is `pikkustudio.config.json`.
|
|
8
|
+
|
|
9
|
+
Every environment variable drops the `FABRIC_` prefix for `PIKKUSTUDIO_` with the same suffix: `PIKKUSTUDIO_API_URL`, `PIKKUSTUDIO_PROJECT_ID`, `PIKKUSTUDIO_TOKEN`, `PIKKUSTUDIO_OPERATOR_TOKEN`, `PIKKUSTUDIO_AUTH_PUBLIC_KEY`, `PIKKUSTUDIO_STAGE_ID` and the rest. Rename them wherever you set them.
|
|
10
|
+
|
|
11
|
+
Wire ids: the operator sign-in route is `/sign-in/cloud`, the plugin and mode literal is `'cloud'`, the Better Auth plugin is `pikkuCloud`, its session field is `pikkustudioadmin`, and the signature header is `x-cloud-sig`. The cloud API calls are `getCloudLogs`, `getCloudLogsByStageKind`, `cloudCliProjects`, `cloudCliDeployments`, `providedByCloud` and `recordCloudAnalyticsEvents`. Addon packages are `@pikkustudio/*`, scaffolded files live under `scaffold/cloud` and `__cloud_*__`, and the API hosts are the `pikkustudio.app` and `pikkustudio.dev` domains.
|
|
12
|
+
|
|
13
|
+
`pikku skills install --fabric` is `--cloud`, the install group is `cloud`, and the skills are `pikku-cloud`, `pikku-cloud-debug`, `pikku-admin-to-cloud` and `pikku-blueprint-to-cloud`.
|
|
14
|
+
|
|
15
|
+
The stale analytics forwarder copies are removed from the online-shop example and the starter template.
|
|
16
|
+
|
|
17
|
+
The sealed-box salt stays `pikkufabric/sealed-box/v1`: it is a cryptographic constant that already-sealed secrets depend on.
|
|
18
|
+
|
|
19
|
+
- 33b102d: Virtual users now have one name everywhere. The id column of `virtual_user_run` and `virtual_user_schedule` is `virtual_user_id` (a migration renames it in place), the CLI group is `pikku virtual-user …`, and the declarations and imports live under `@pikku/core/virtual-user`. The previous names are removed with no aliases.
|
|
20
|
+
|
|
21
|
+
The default virtual user email domain is now `virtualuser.invalid`: apps that rely on the default domain get new accounts at the next provisioning (`pikku virtual-user sync`, deploy, or `pikku dev` start), and the old rows stay unused.
|
|
22
|
+
|
|
23
|
+
The sign-in surface carries the same name. The route is `/sign-in/virtual-user`, the environment variables are `PIKKU_ALLOW_VIRTUAL_USER_SIGN_IN` and the `PIKKU_DEV_VIRTUAL_USER_SIGN_IN` / `VITE_*` equivalents, the Better Auth plugin is `pikkuVirtualUser`, the scenario step option and wire member are `virtualUser` and `virtualUsers`, and the Better Auth user flag column is `virtual_user`. Rename the environment variables wherever you set them, and add a migration that renames the flag column on the `user` table. Nothing is aliased.
|
|
24
|
+
|
|
25
|
+
The conversation runner is now `@pikku/core/virtual-user/conversation`, replacing the old `-flow` subpath. Update the import; the old subpath is gone.
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [fdcc821]
|
|
28
|
+
- Updated dependencies [33b102d]
|
|
29
|
+
- Updated dependencies [6a25c70]
|
|
30
|
+
- Updated dependencies [a084c3b]
|
|
31
|
+
- @pikku/core@0.12.142
|
|
32
|
+
|
|
33
|
+
## 0.12.105
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- b04e444: Fix CLI commands that come from an addon package. Auth and permissions set on such a command are now merged into the addon's own function instead of replacing it, and the command's input is shaped by the addon's own schema (for example a comma list becomes an array). The inspector now also resolves `...refCLI('addon:contract')`, spreads and `defineCLICommands` identifiers inside a command's `subcommands`.
|
|
38
|
+
- Updated dependencies [b04e444]
|
|
39
|
+
- Updated dependencies [d3d8a98]
|
|
40
|
+
- Updated dependencies [658da50]
|
|
41
|
+
- @pikku/core@0.12.141
|
|
42
|
+
|
|
43
|
+
## 0.12.104
|
|
44
|
+
|
|
45
|
+
### Patch Changes
|
|
46
|
+
|
|
47
|
+
- 5464b86: Add the `pikku verify` command over `@pikku/code-edit/verify`: codegen, the backend and frontend type-checks and static correctness checks as structured findings with fix hints. The inspector warns about single-step workflows (PKU644) and PKU111 now names the file and line.
|
|
48
|
+
- Updated dependencies [f396538]
|
|
49
|
+
- @pikku/core@0.12.140
|
|
50
|
+
|
|
1
51
|
## 0.12.103
|
|
2
52
|
|
|
3
53
|
### Patch Changes
|
package/dist/add/add-cli.js
CHANGED
|
@@ -325,15 +325,23 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
|
|
|
325
325
|
}
|
|
326
326
|
break;
|
|
327
327
|
case 'subcommands': {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
328
|
+
const subcommands = {};
|
|
329
|
+
const collect = (node) => {
|
|
330
|
+
const refContract = resolveRefContract(node, 'refCLI', inspectorState.exportedContracts.addonCli);
|
|
331
|
+
if (refContract) {
|
|
332
|
+
Object.assign(subcommands, refContract.contract);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const target = ts.isIdentifier(node)
|
|
336
|
+
? resolveIdentifier(node, typeChecker, ['defineCLICommands'])
|
|
337
|
+
: node;
|
|
338
|
+
if (!target || !ts.isObjectLiteralExpression(target))
|
|
339
|
+
return;
|
|
340
|
+
for (const subProp of target.properties) {
|
|
341
|
+
if (ts.isSpreadAssignment(subProp)) {
|
|
342
|
+
collect(subProp.expression);
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
337
345
|
if (!ts.isPropertyAssignment(subProp))
|
|
338
346
|
continue;
|
|
339
347
|
const subName = getPropertyName(subProp);
|
|
@@ -341,10 +349,12 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
|
|
|
341
349
|
continue;
|
|
342
350
|
const subCommand = processCommand(logger, inspectorState, options, subName, subProp.initializer, sourceFile, typeChecker, programName, fullPath, programTags);
|
|
343
351
|
if (subCommand) {
|
|
344
|
-
|
|
352
|
+
subcommands[subName] = subCommand;
|
|
345
353
|
}
|
|
346
354
|
}
|
|
347
|
-
}
|
|
355
|
+
};
|
|
356
|
+
collect(prop.initializer);
|
|
357
|
+
meta.subcommands = subcommands;
|
|
348
358
|
break;
|
|
349
359
|
}
|
|
350
360
|
case 'isDefault':
|
|
@@ -21,7 +21,7 @@ const SCENARIO_SURFACE_KEYS = [
|
|
|
21
21
|
* copied into an addon package by a consumer, a called identifier cannot.
|
|
22
22
|
*/
|
|
23
23
|
const SCENARIO_STEP_KINDS = new Map([
|
|
24
|
-
['pikkuScenarioStep', '
|
|
24
|
+
['pikkuScenarioStep', 'virtualUser'],
|
|
25
25
|
['pikkuPlatformScenarioStep', 'platform'],
|
|
26
26
|
['pikkuAddonScenarioStep', 'addon'],
|
|
27
27
|
]);
|
|
@@ -424,7 +424,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
424
424
|
let workflowRetries;
|
|
425
425
|
let workflowTimeout;
|
|
426
426
|
let scenarioStepSurfaces;
|
|
427
|
-
let
|
|
427
|
+
let scenarioStepRequiresVirtualUser;
|
|
428
428
|
let scenarioStepAddon;
|
|
429
429
|
let scenarioStepTemplate;
|
|
430
430
|
let scopes;
|
|
@@ -560,9 +560,9 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
560
560
|
? declaredSurfaces
|
|
561
561
|
: undefined;
|
|
562
562
|
// A browser binding cannot be provisioned without a window to open, so it
|
|
563
|
-
// requires
|
|
564
|
-
|
|
565
|
-
getPropertyValue(firstArg, '
|
|
563
|
+
// requires a virtual user whether or not the step said so.
|
|
564
|
+
scenarioStepRequiresVirtualUser =
|
|
565
|
+
getPropertyValue(firstArg, 'virtualUser') === true ||
|
|
566
566
|
declaredSurfaces.includes('browser')
|
|
567
567
|
? true
|
|
568
568
|
: undefined;
|
|
@@ -1083,10 +1083,10 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
1083
1083
|
workflowTimeout: workflowTimeout ?? undefined,
|
|
1084
1084
|
compensate: compensateInitializer ? true : undefined,
|
|
1085
1085
|
scenarioStepSurfaces,
|
|
1086
|
-
|
|
1087
|
-
// `
|
|
1086
|
+
scenarioStepRequiresVirtualUser,
|
|
1087
|
+
// `virtualUser` is the default reading of an unmarked step, so it is left off
|
|
1088
1088
|
// rather than written into every step meta a project already has.
|
|
1089
|
-
scenarioStepKind: scenarioStepKind && scenarioStepKind !== '
|
|
1089
|
+
scenarioStepKind: scenarioStepKind && scenarioStepKind !== 'virtualUser'
|
|
1090
1090
|
? scenarioStepKind
|
|
1091
1091
|
: undefined,
|
|
1092
1092
|
scenarioStepAddon,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AddWiring } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Inspector for `defineVirtualUsers()` calls.
|
|
4
|
+
*
|
|
5
|
+
* A virtual user is entirely declaration — a person, their roles, what they want —
|
|
6
|
+
* so there is no code body and nothing to resolve at runtime. What is read here
|
|
7
|
+
* is the whole thing, and it is generated straight to `virtual-users.gen.json` for
|
|
8
|
+
* the CLI and the console to read without loading the app.
|
|
9
|
+
*
|
|
10
|
+
* `name` is the one field that cannot be defaulted. A virtual user without one is
|
|
11
|
+
* not a person, and the console has nothing to show.
|
|
12
|
+
*/
|
|
13
|
+
export declare const addVirtualUsers: AddWiring;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import { DISPOSITIONS } from '@pikku/core/virtual-user';
|
|
3
|
-
import {
|
|
3
|
+
import { isRunnableVirtualUser } from '@pikku/core/virtual-user';
|
|
4
4
|
import { ErrorCode } from '../error-codes.js';
|
|
5
5
|
import { claimSingleDeclaration } from '../utils/single-declaration.js';
|
|
6
6
|
import { booleanProperty, getProperty, numberProperty, stringArrayProperty, stringProperty, unwrapAs, } from '../utils/literal-properties.js';
|
|
@@ -15,7 +15,7 @@ const RATES = [
|
|
|
15
15
|
*
|
|
16
16
|
* The ranges are checked here rather than at run time because these are the
|
|
17
17
|
* kind of typo that does not throw: `repeatRate: 18` meaning 18% would silently
|
|
18
|
-
* double every call the
|
|
18
|
+
* double every call the virtual user makes, and you would read the run as a product
|
|
19
19
|
* bug.
|
|
20
20
|
*/
|
|
21
21
|
const tuningProperty = (config, onError) => {
|
|
@@ -72,7 +72,7 @@ const accountProperty = (value) => {
|
|
|
72
72
|
const provider = stringProperty(value, 'provider');
|
|
73
73
|
return provider === undefined ? {} : { provider };
|
|
74
74
|
};
|
|
75
|
-
const linkedAccountsProperty = (config,
|
|
75
|
+
const linkedAccountsProperty = (config, virtualUserId, logger) => {
|
|
76
76
|
const value = getProperty(config, 'linkedAccounts');
|
|
77
77
|
if (!value || !ts.isObjectLiteralExpression(value)) {
|
|
78
78
|
return undefined;
|
|
@@ -83,12 +83,12 @@ const linkedAccountsProperty = (config, personaId, logger) => {
|
|
|
83
83
|
continue;
|
|
84
84
|
}
|
|
85
85
|
if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) {
|
|
86
|
-
logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, `
|
|
86
|
+
logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, `VirtualUser '${virtualUserId}' has a linked account whose key is not a literal.`);
|
|
87
87
|
continue;
|
|
88
88
|
}
|
|
89
89
|
const account = accountProperty(prop.initializer);
|
|
90
90
|
if (!account) {
|
|
91
|
-
logger.critical(ErrorCode.INVALID_VALUE, `
|
|
91
|
+
logger.critical(ErrorCode.INVALID_VALUE, `VirtualUser '${virtualUserId}' linked account '${prop.name.text}' must be an object literal.`);
|
|
92
92
|
continue;
|
|
93
93
|
}
|
|
94
94
|
accounts[prop.name.text] = account;
|
|
@@ -96,22 +96,22 @@ const linkedAccountsProperty = (config, personaId, logger) => {
|
|
|
96
96
|
return Object.keys(accounts).length ? accounts : undefined;
|
|
97
97
|
};
|
|
98
98
|
/**
|
|
99
|
-
* Inspector for `
|
|
99
|
+
* Inspector for `defineVirtualUsers()` calls.
|
|
100
100
|
*
|
|
101
|
-
* A
|
|
101
|
+
* A virtual user is entirely declaration — a person, their roles, what they want —
|
|
102
102
|
* so there is no code body and nothing to resolve at runtime. What is read here
|
|
103
|
-
* is the whole thing, and it is generated straight to `
|
|
103
|
+
* is the whole thing, and it is generated straight to `virtual-users.gen.json` for
|
|
104
104
|
* the CLI and the console to read without loading the app.
|
|
105
105
|
*
|
|
106
|
-
* `name` is the one field that cannot be defaulted. A
|
|
106
|
+
* `name` is the one field that cannot be defaulted. A virtual user without one is
|
|
107
107
|
* not a person, and the console has nothing to show.
|
|
108
108
|
*/
|
|
109
|
-
export const
|
|
109
|
+
export const addVirtualUsers = (logger, node, _checker, state) => {
|
|
110
110
|
if (!ts.isCallExpression(node)) {
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
const expression = node.expression;
|
|
114
|
-
if (!ts.isIdentifier(expression) || expression.text !== '
|
|
114
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'defineVirtualUsers') {
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
const firstArg = node.arguments[0];
|
|
@@ -120,11 +120,11 @@ export const addPersonas = (logger, node, _checker, state) => {
|
|
|
120
120
|
}
|
|
121
121
|
const unwrapped = unwrapAs(firstArg);
|
|
122
122
|
if (!ts.isObjectLiteralExpression(unwrapped)) {
|
|
123
|
-
logger.critical(ErrorCode.INVALID_VALUE, '
|
|
123
|
+
logger.critical(ErrorCode.INVALID_VALUE, 'defineVirtualUsers() needs an inline object — virtual users are read statically, never evaluated.');
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
126
|
const sourceFile = node.getSourceFile().fileName;
|
|
127
|
-
if (!claimSingleDeclaration(logger, state.
|
|
127
|
+
if (!claimSingleDeclaration(logger, state.virtualUsers.files, ErrorCode.DUPLICATE_VIRTUAL_USERS_DEFINITION, 'defineVirtualUsers', sourceFile)) {
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
130
|
const known = Object.keys(DISPOSITIONS);
|
|
@@ -133,46 +133,46 @@ export const addPersonas = (logger, node, _checker, state) => {
|
|
|
133
133
|
continue;
|
|
134
134
|
}
|
|
135
135
|
if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) {
|
|
136
|
-
logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, 'A
|
|
136
|
+
logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, 'A virtual user is declared with a key that is not a literal.');
|
|
137
137
|
continue;
|
|
138
138
|
}
|
|
139
139
|
const id = prop.name.text;
|
|
140
140
|
if (!ts.isObjectLiteralExpression(prop.initializer)) {
|
|
141
|
-
logger.critical(ErrorCode.INVALID_VALUE, `
|
|
141
|
+
logger.critical(ErrorCode.INVALID_VALUE, `VirtualUser '${id}' must be an object literal.`);
|
|
142
142
|
continue;
|
|
143
143
|
}
|
|
144
144
|
const config = prop.initializer;
|
|
145
145
|
const name = stringProperty(config, 'name');
|
|
146
146
|
if (!name) {
|
|
147
|
-
logger.critical(ErrorCode.INVALID_VALUE, `
|
|
147
|
+
logger.critical(ErrorCode.INVALID_VALUE, `VirtualUser '${id}' needs a literal 'name' — a virtual user is a person, and the console has nothing to show without one.`);
|
|
148
148
|
continue;
|
|
149
149
|
}
|
|
150
|
-
// The generated `
|
|
150
|
+
// The generated `defineVirtualUsers` types this as a union, so a bad value only
|
|
151
151
|
// reaches here in source that does not compile. Caught anyway: a
|
|
152
152
|
// disposition is the entire behaviour, and defaulting a typo to `realistic`
|
|
153
153
|
// would run a different person than the one that was written.
|
|
154
154
|
const disposition = stringProperty(config, 'disposition');
|
|
155
155
|
if (disposition !== undefined && !known.includes(disposition)) {
|
|
156
|
-
logger.critical(ErrorCode.INVALID_VALUE, `
|
|
156
|
+
logger.critical(ErrorCode.INVALID_VALUE, `VirtualUser '${id}' has an unknown disposition '${disposition}'. One of: ${known.join(', ')}`);
|
|
157
157
|
continue;
|
|
158
158
|
}
|
|
159
159
|
let tuningFailed = false;
|
|
160
160
|
const tuning = tuningProperty(config, (message) => {
|
|
161
161
|
tuningFailed = true;
|
|
162
|
-
logger.critical(ErrorCode.INVALID_VALUE, `
|
|
162
|
+
logger.critical(ErrorCode.INVALID_VALUE, `VirtualUser '${id}': ${message}`);
|
|
163
163
|
});
|
|
164
164
|
if (tuningFailed) {
|
|
165
165
|
continue;
|
|
166
166
|
}
|
|
167
167
|
const account = accountProperty(getProperty(config, 'account'));
|
|
168
168
|
const declaredRunnable = booleanProperty(config, 'runnable');
|
|
169
|
-
const
|
|
169
|
+
const virtualUser = {
|
|
170
170
|
id,
|
|
171
171
|
name,
|
|
172
172
|
roles: stringArrayProperty(config, 'roles') ?? [],
|
|
173
173
|
goals: stringArrayProperty(config, 'goals') ?? [],
|
|
174
174
|
tags: stringArrayProperty(config, 'tags') ?? [],
|
|
175
|
-
runnable:
|
|
175
|
+
runnable: isRunnableVirtualUser({
|
|
176
176
|
...(declaredRunnable !== undefined
|
|
177
177
|
? { runnable: declaredRunnable }
|
|
178
178
|
: {}),
|
|
@@ -182,38 +182,38 @@ export const addPersonas = (logger, node, _checker, state) => {
|
|
|
182
182
|
};
|
|
183
183
|
const jobTitle = stringProperty(config, 'jobTitle');
|
|
184
184
|
if (jobTitle !== undefined)
|
|
185
|
-
|
|
185
|
+
virtualUser.jobTitle = jobTitle;
|
|
186
186
|
const description = stringProperty(config, 'description');
|
|
187
187
|
if (description !== undefined)
|
|
188
|
-
|
|
188
|
+
virtualUser.description = description;
|
|
189
189
|
const avatarUrl = stringProperty(config, 'avatarUrl');
|
|
190
190
|
if (avatarUrl !== undefined)
|
|
191
|
-
|
|
191
|
+
virtualUser.avatarUrl = avatarUrl;
|
|
192
192
|
const personality = stringProperty(config, 'personality');
|
|
193
193
|
if (personality !== undefined)
|
|
194
|
-
|
|
194
|
+
virtualUser.personality = personality;
|
|
195
195
|
const app = stringProperty(config, 'app');
|
|
196
196
|
if (app !== undefined)
|
|
197
|
-
|
|
197
|
+
virtualUser.app = app;
|
|
198
198
|
if (disposition !== undefined) {
|
|
199
|
-
|
|
199
|
+
virtualUser.disposition = disposition;
|
|
200
200
|
}
|
|
201
201
|
if (tuning !== undefined)
|
|
202
|
-
|
|
202
|
+
virtualUser.tuning = tuning;
|
|
203
203
|
const fixtures = stringArrayProperty(config, 'fixtures');
|
|
204
204
|
if (fixtures !== undefined)
|
|
205
|
-
|
|
205
|
+
virtualUser.fixtures = fixtures;
|
|
206
206
|
// Left absent rather than resolved here: "everywhere but production" is a
|
|
207
207
|
// fact about the config, and the inspector has no business baking today's
|
|
208
208
|
// environment list into a meta file that outlives it.
|
|
209
209
|
const environments = stringArrayProperty(config, 'environments');
|
|
210
210
|
if (environments !== undefined)
|
|
211
|
-
|
|
211
|
+
virtualUser.environments = environments;
|
|
212
212
|
if (account !== undefined)
|
|
213
|
-
|
|
213
|
+
virtualUser.account = account;
|
|
214
214
|
const linkedAccounts = linkedAccountsProperty(config, id, logger);
|
|
215
215
|
if (linkedAccounts !== undefined)
|
|
216
|
-
|
|
217
|
-
state.
|
|
216
|
+
virtualUser.linkedAccounts = linkedAccounts;
|
|
217
|
+
state.virtualUsers.definitions.push(virtualUser);
|
|
218
218
|
}
|
|
219
219
|
};
|
package/dist/add/add-workflow.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
+
import { relative } from 'node:path';
|
|
2
3
|
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
3
4
|
import { extractFunctionNode } from '../utils/extract-function-node.js';
|
|
4
5
|
import { ErrorCode } from '../error-codes.js';
|
|
5
6
|
import { extractStringLiteral, isStringLike, isFunctionLike, extractDescription, extractDuration, } from '../utils/extract-node-value.js';
|
|
6
7
|
import { getCommonWireMetaData, getPropertyValue, } from '../utils/get-property-value.js';
|
|
7
8
|
import { extractDSLWorkflow } from '../utils/workflow/dsl/extract-dsl-workflow.js';
|
|
8
|
-
import { getSourceText,
|
|
9
|
+
import { getSourceText, extractVirtualUserFromOptions, } from '../utils/workflow/dsl/patterns.js';
|
|
9
10
|
/**
|
|
10
11
|
* Extract a workflow step's display name without letting a non-static name
|
|
11
12
|
* (e.g. a function call) abort the scan. The step name is cosmetic, so a
|
|
@@ -141,7 +142,7 @@ function getWorkflowInvocations(node, checker, state, workflowName, steps) {
|
|
|
141
142
|
type: 'rpc',
|
|
142
143
|
stepName,
|
|
143
144
|
rpcName,
|
|
144
|
-
|
|
145
|
+
virtualUser: extractVirtualUserFromOptions(optionsArg),
|
|
145
146
|
});
|
|
146
147
|
state.rpc.invokedFunctions.add(rpcName);
|
|
147
148
|
}
|
|
@@ -164,7 +165,7 @@ function getWorkflowInvocations(node, checker, state, workflowName, steps) {
|
|
|
164
165
|
stepName,
|
|
165
166
|
rpcName,
|
|
166
167
|
expectEventually: true,
|
|
167
|
-
|
|
168
|
+
virtualUser: extractVirtualUserFromOptions(args.length >= 5 ? args[4] : undefined),
|
|
168
169
|
});
|
|
169
170
|
state.rpc.invokedFunctions.add(rpcName);
|
|
170
171
|
}
|
|
@@ -229,7 +230,7 @@ export const addWorkflow = (logger, node, checker, state, options) => {
|
|
|
229
230
|
wrapperType = 'complex';
|
|
230
231
|
}
|
|
231
232
|
else if (expression.text === 'pikkuScenario') {
|
|
232
|
-
// A scenario is a complex workflow whose steps run as
|
|
233
|
+
// A scenario is a complex workflow whose steps run as virtual users over the
|
|
233
234
|
// real transport — same extraction rules as complex, distinct meta.
|
|
234
235
|
wrapperType = 'scenario';
|
|
235
236
|
}
|
|
@@ -243,7 +244,10 @@ export const addWorkflow = (logger, node, checker, state, options) => {
|
|
|
243
244
|
const { pikkuFuncId, name, exportedName } = extractFunctionName(node, checker, state.rootDir);
|
|
244
245
|
const workflowName = exportedName || name;
|
|
245
246
|
if (!workflowName) {
|
|
246
|
-
|
|
247
|
+
const sourceFile = node.getSourceFile();
|
|
248
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
249
|
+
logger.critical(ErrorCode.MISSING_NAME, `Could not determine workflow name from export at ${relative(state.rootDir, sourceFile.fileName)}:${line + 1}. ` +
|
|
250
|
+
`Assign the \`${expression.text}(...)\` call to an exported const; that name is how it is registered.`);
|
|
247
251
|
return;
|
|
248
252
|
}
|
|
249
253
|
// Extract the function node (either direct function or from config.func)
|
|
@@ -358,11 +362,11 @@ export const addWorkflow = (logger, node, checker, state, options) => {
|
|
|
358
362
|
const sink = steps.length > 0 ? [] : steps;
|
|
359
363
|
getWorkflowInvocations(resolvedFunc, checker, state, workflowName, sink);
|
|
360
364
|
}
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
const
|
|
365
|
+
// Virtual user names the flow's steps run as (scenarios) — powers the console
|
|
366
|
+
// virtual users view and the Scenario graph badges.
|
|
367
|
+
const virtualUserNames = [
|
|
364
368
|
...new Set(steps
|
|
365
|
-
.map((s) => ('
|
|
369
|
+
.map((s) => ('virtualUser' in s ? s.virtualUser : undefined))
|
|
366
370
|
.filter((a) => typeof a === 'string')),
|
|
367
371
|
];
|
|
368
372
|
state.workflows.meta[workflowName] = {
|
|
@@ -380,7 +384,7 @@ export const addWorkflow = (logger, node, checker, state, options) => {
|
|
|
380
384
|
skip,
|
|
381
385
|
scenario: wrapperType === 'scenario' ? true : undefined,
|
|
382
386
|
asserts,
|
|
383
|
-
|
|
387
|
+
virtualUsers: virtualUserNames.length > 0 ? virtualUserNames : undefined,
|
|
384
388
|
};
|
|
385
389
|
// The scenario "logger/config only" rule (PKU673) and the implicit
|
|
386
390
|
// workflow platform-service injection both used to live here, reading
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -20,16 +20,17 @@ export declare enum ErrorCode {
|
|
|
20
20
|
CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = "PKU672",
|
|
21
21
|
SCENARIO_HAS_SERVICES = "PKU673",
|
|
22
22
|
EXPECT_EVENTUALLY_SCENARIO_ONLY = "PKU675",
|
|
23
|
-
|
|
23
|
+
SCENARIO_STEP_NEEDS_VIRTUAL_USER = "PKU677",
|
|
24
24
|
SCENARIO_STEP_TARGET_NOT_STATIC = "PKU678",
|
|
25
25
|
SCENARIO_NOT_EXTRACTABLE = "PKU679",
|
|
26
26
|
SCENARIO_HAS_NO_ASSERTION = "PKU680",
|
|
27
|
-
|
|
27
|
+
SCENARIO_STEP_PROSE_NAMES_VIRTUAL_USER = "PKU681",
|
|
28
28
|
SCENARIO_HAS_NO_FEATURE = "PKU682",
|
|
29
29
|
WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED = "PKU600",
|
|
30
30
|
INVALID_DSL_WORKFLOW = "PKU641",
|
|
31
31
|
WORKFLOW_GRAPH_ADDON_NOT_WIRED = "PKU642",
|
|
32
32
|
COMPLEX_WORKFLOW_NOT_ALLOWED = "PKU643",
|
|
33
|
+
SINGLE_STEP_WORKFLOW = "PKU644",
|
|
33
34
|
REMOTE_ADDON_NOT_DEV_DEPENDENCY = "PKU338",
|
|
34
35
|
REMOTE_ADDON_AUTH_UNRESOLVED = "PKU339",
|
|
35
36
|
ADDON_NOT_INSTALLED = "PKU340",
|
|
@@ -53,7 +54,7 @@ export declare enum ErrorCode {
|
|
|
53
54
|
FUNCTION_METADATA_NOT_FOUND = "PKU559",
|
|
54
55
|
DUPLICATE_AUTH_DEFINITION = "PKU581",
|
|
55
56
|
AUTH_NOT_EXPORTED = "PKU582",
|
|
56
|
-
|
|
57
|
+
DUPLICATE_VIRTUAL_USERS_DEFINITION = "PKU585",
|
|
57
58
|
ROUTE_PARAM_MISMATCH = "PKU571",
|
|
58
59
|
ROUTE_QUERY_MISMATCH = "PKU572",
|
|
59
60
|
AUTH_DISABLED_REQUIRES_SESSIONLESS = "PKU573",
|
package/dist/error-codes.js
CHANGED
|
@@ -22,16 +22,17 @@ export var ErrorCode;
|
|
|
22
22
|
ErrorCode["CLI_CLIENTSIDE_RENDERER_HAS_SERVICES"] = "PKU672";
|
|
23
23
|
ErrorCode["SCENARIO_HAS_SERVICES"] = "PKU673";
|
|
24
24
|
ErrorCode["EXPECT_EVENTUALLY_SCENARIO_ONLY"] = "PKU675";
|
|
25
|
-
ErrorCode["
|
|
25
|
+
ErrorCode["SCENARIO_STEP_NEEDS_VIRTUAL_USER"] = "PKU677";
|
|
26
26
|
ErrorCode["SCENARIO_STEP_TARGET_NOT_STATIC"] = "PKU678";
|
|
27
27
|
ErrorCode["SCENARIO_NOT_EXTRACTABLE"] = "PKU679";
|
|
28
28
|
ErrorCode["SCENARIO_HAS_NO_ASSERTION"] = "PKU680";
|
|
29
|
-
ErrorCode["
|
|
29
|
+
ErrorCode["SCENARIO_STEP_PROSE_NAMES_VIRTUAL_USER"] = "PKU681";
|
|
30
30
|
ErrorCode["SCENARIO_HAS_NO_FEATURE"] = "PKU682";
|
|
31
31
|
ErrorCode["WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED"] = "PKU600";
|
|
32
32
|
ErrorCode["INVALID_DSL_WORKFLOW"] = "PKU641";
|
|
33
33
|
ErrorCode["WORKFLOW_GRAPH_ADDON_NOT_WIRED"] = "PKU642";
|
|
34
34
|
ErrorCode["COMPLEX_WORKFLOW_NOT_ALLOWED"] = "PKU643";
|
|
35
|
+
ErrorCode["SINGLE_STEP_WORKFLOW"] = "PKU644";
|
|
35
36
|
// Remote addon (wireRemoteAddon) validation
|
|
36
37
|
ErrorCode["REMOTE_ADDON_NOT_DEV_DEPENDENCY"] = "PKU338";
|
|
37
38
|
ErrorCode["REMOTE_ADDON_AUTH_UNRESOLVED"] = "PKU339";
|
|
@@ -65,7 +66,7 @@ export var ErrorCode;
|
|
|
65
66
|
// scopes and roles held this rule until the generated `admin` tree made it
|
|
66
67
|
// unsatisfiable, and get it back once `admin` is a default scope nobody
|
|
67
68
|
// declares. Do not reuse those two numbers for anything else.
|
|
68
|
-
ErrorCode["
|
|
69
|
+
ErrorCode["DUPLICATE_VIRTUAL_USERS_DEFINITION"] = "PKU585";
|
|
69
70
|
// HTTP Route errors
|
|
70
71
|
ErrorCode["ROUTE_PARAM_MISMATCH"] = "PKU571";
|
|
71
72
|
ErrorCode["ROUTE_QUERY_MISMATCH"] = "PKU572";
|
package/dist/inspector.js
CHANGED
|
@@ -9,9 +9,10 @@ import { findCommonAncestor } from './utils/find-root-dir.js';
|
|
|
9
9
|
import { createSourceFileCacheHost } from './source-file-cache.js';
|
|
10
10
|
import { createNestedProjectFilter } from './utils/nested-project-filter.js';
|
|
11
11
|
import { toProgramPath } from './utils/program-path.js';
|
|
12
|
-
import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateAgentToolReferences, validateAgentScorerReferences, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateAddonUses, validateAddonContentGrants, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, validateSystemRoleScopes,
|
|
12
|
+
import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateAgentToolReferences, validateAgentScorerReferences, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateAddonUses, validateAddonContentGrants, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, validateSystemRoleScopes, validateVirtualUserRoles, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, validateNoSecretAliasServices, registerDerivedOAuth2AppSecrets, validateSecretUsage, computeDiagnostics, validateSchemaReferences, validateSchemaWiringSeparation, validateScenarioServices, validateScenarioSteps, validateScenarioFeatures, validateWorkflowGraphAddons, } from './utils/post-process.js';
|
|
13
13
|
import { validateExposedFunctionsGated } from './utils/validate-exposed-functions-gated.js';
|
|
14
14
|
import { validateTagsResolveToMiddleware } from './utils/validate-tags-resolve-to-middleware.js';
|
|
15
|
+
import { validateSingleStepWorkflows } from './utils/validate-single-step-workflows.js';
|
|
15
16
|
import { annotateHttpRouteAuth } from './utils/annotate-http-route-auth.js';
|
|
16
17
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
|
|
17
18
|
import { pikkuState } from '@pikku/core/state';
|
|
@@ -169,7 +170,7 @@ export function getInitialInspectorState(rootDir) {
|
|
|
169
170
|
definitions: [],
|
|
170
171
|
files: new Set(),
|
|
171
172
|
},
|
|
172
|
-
|
|
173
|
+
virtualUsers: {
|
|
173
174
|
definitions: [],
|
|
174
175
|
files: new Set(),
|
|
175
176
|
},
|
|
@@ -408,6 +409,7 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
408
409
|
computeDiagnostics(state);
|
|
409
410
|
validateSchemaWiringSeparation(logger, state);
|
|
410
411
|
validateWorkflowGraphAddons(logger, state);
|
|
412
|
+
validateSingleStepWorkflows(logger, state);
|
|
411
413
|
validateScenarioServices(logger, state);
|
|
412
414
|
validateScenarioSteps(logger, state);
|
|
413
415
|
validateScenarioFeatures(logger, state);
|
|
@@ -434,7 +436,7 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
434
436
|
applyDeclaredScopes(state);
|
|
435
437
|
validateScopeReferences(logger, state);
|
|
436
438
|
validateSystemRoleScopes(logger, state);
|
|
437
|
-
|
|
439
|
+
validateVirtualUserRoles(logger, state);
|
|
438
440
|
logger.endValidationPass?.();
|
|
439
441
|
}
|
|
440
442
|
const cpu = process.cpuUsage(cpuStart);
|
package/dist/types.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type { CredentialDefinitions } from '@pikku/core/credential';
|
|
|
19
19
|
import type { ScopeDefinitions } from '@pikku/core/scope';
|
|
20
20
|
import type { SystemRoleDefinitions } from '@pikku/core/role';
|
|
21
21
|
import type { FeatureFlagDefinitions } from '@pikku/core/flag';
|
|
22
|
-
import type {
|
|
22
|
+
import type { VirtualUserDefinitions } from '@pikku/core/virtual-user';
|
|
23
23
|
import type { VariableDefinitions } from '@pikku/core/variable';
|
|
24
24
|
import type { TypesMap } from './types-map.js';
|
|
25
25
|
import type { FunctionsMeta } from '@pikku/core/services';
|
|
@@ -784,8 +784,8 @@ export interface InspectorState {
|
|
|
784
784
|
definitions: FeatureFlagDefinitions;
|
|
785
785
|
files: Set<string>;
|
|
786
786
|
};
|
|
787
|
-
|
|
788
|
-
definitions:
|
|
787
|
+
virtualUsers: {
|
|
788
|
+
definitions: VirtualUserDefinitions;
|
|
789
789
|
files: Set<string>;
|
|
790
790
|
};
|
|
791
791
|
variables: {
|
|
@@ -30,7 +30,7 @@ export function funcIdToTypeName(id) {
|
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
32
|
* The step wrappers, whichever kind of subject acts in them. A scenario calls a
|
|
33
|
-
* platform or addon step by name exactly as it calls a
|
|
33
|
+
* platform or addon step by name exactly as it calls a virtual user one.
|
|
34
34
|
*/
|
|
35
35
|
const SCENARIO_STEP_WRAPPERS = new Set([
|
|
36
36
|
'pikkuScenarioStep',
|
|
@@ -102,7 +102,7 @@ export const assertStringLiteralProperty = (obj, propertyName, wiringType, logge
|
|
|
102
102
|
* that anything written as an expression comes through raw: a `description`
|
|
103
103
|
* split across lines as `'Fetch the thing ' + 'from upstream'` reaches
|
|
104
104
|
* `infra.json` with the quotes and the `+` still in it, and is rendered that
|
|
105
|
-
* way in the
|
|
105
|
+
* way in the cloud console.
|
|
106
106
|
*
|
|
107
107
|
* Deliberately checker-free, unlike `extractStringLiteral`. Every value this
|
|
108
108
|
* resolves is a literal in the same expression, so no symbol resolution is
|
|
@@ -45,6 +45,7 @@ const applyPackageToCLICommands = (commands, packageName, namespace) => {
|
|
|
45
45
|
}
|
|
46
46
|
if (namespace &&
|
|
47
47
|
typeof command.pikkuFuncId === 'string' &&
|
|
48
|
+
command.pikkuFuncId !== '' &&
|
|
48
49
|
!command.pikkuFuncId.includes(':')) {
|
|
49
50
|
command.pikkuFuncId = `${namespace}:${command.pikkuFuncId}`;
|
|
50
51
|
}
|