@pikku/inspector 0.12.60 → 0.12.62
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 +322 -0
- package/OPTIMIZATION-PLAN.md +19 -19
- package/dist/add/add-auth.js +1 -1
- package/dist/add/add-channel.d.ts +1 -1
- package/dist/add/add-cli.js +5 -0
- package/dist/add/add-functions.js +10 -1
- package/dist/add/add-http-route.js +18 -18
- package/dist/add/add-keyed-wiring.d.ts +2 -0
- package/dist/add/add-keyed-wiring.js +3 -1
- package/dist/add/add-personas.js +4 -1
- package/dist/add/add-secret.js +1 -0
- package/dist/add/add-variable.js +1 -0
- package/dist/add/add-wire-remote-addon.js +2 -1
- package/dist/add/add-workflow-graph.js +63 -6
- package/dist/add/add-workflow.d.ts +1 -1
- package/dist/error-codes.d.ts +3 -1
- package/dist/error-codes.js +3 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -0
- package/dist/inspector.js +12 -2
- package/dist/types.d.ts +25 -21
- package/dist/utils/annotate-http-route-auth.js +2 -1
- package/dist/utils/check-secret-sinks.js +2 -1
- package/dist/utils/compute-required-schemas.d.ts +1 -1
- package/dist/utils/contract-hashes.d.ts +2 -2
- package/dist/utils/contract-hashes.js +1 -1
- package/dist/utils/extract-function-name.js +1 -1
- package/dist/utils/extract-services.d.ts +1 -1
- package/dist/utils/extract-surface-usage.d.ts +29 -0
- package/dist/utils/extract-surface-usage.js +97 -0
- package/dist/utils/filter-inspector-state.js +53 -16
- package/dist/utils/filter-utils.d.ts +1 -1
- package/dist/utils/get-property-value.js +42 -3
- package/dist/utils/load-addon-functions-meta.js +31 -0
- package/dist/utils/middleware.d.ts +1 -1
- package/dist/utils/permissions.d.ts +1 -1
- package/dist/utils/post-process.d.ts +27 -7
- package/dist/utils/post-process.js +79 -14
- package/dist/utils/resolve-deploy-target.d.ts +1 -1
- package/dist/utils/resolve-function-meta.d.ts +1 -1
- package/dist/utils/resolve-function-types.d.ts +1 -1
- package/dist/utils/resolve-versions.js +1 -1
- package/dist/utils/schema-generator.d.ts +1 -1
- package/dist/utils/serialize-inspector-state.d.ts +2 -1
- package/dist/utils/serialize-inspector-state.js +2 -0
- package/dist/utils/serialize-openapi-json.d.ts +2 -2
- package/dist/utils/validate-auth-sessionless.js +2 -1
- package/dist/utils/workflow/derive-workflow-plan.d.ts +1 -1
- package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +1 -1
- package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +1 -1
- package/dist/utils/workflow/graph/finalize-workflows.js +1 -1
- package/dist/utils/workflow/graph/index.d.ts +2 -1
- package/dist/utils/workflow/graph/index.js +1 -1
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +6 -1
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +26 -2
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +11 -2
- package/package.json +10 -10
- package/run-tests.sh +0 -0
- package/src/add/add-agent.ts +2 -4
- package/src/add/add-auth.ts +2 -2
- package/src/add/add-channel.ts +1 -4
- package/src/add/add-cli.ts +7 -1
- package/src/add/add-credential.ts +3 -6
- package/src/add/add-file-with-factory.ts +2 -6
- package/src/add/add-functions.ts +24 -31
- package/src/add/add-gateway.ts +1 -1
- package/src/add/add-http-route-ref.test.ts +149 -0
- package/src/add/add-http-route.ts +26 -29
- package/src/add/add-keyed-wiring.ts +8 -7
- package/src/add/add-permission.ts +1 -1
- package/src/add/add-personas.test.ts +26 -2
- package/src/add/add-personas.ts +4 -3
- package/src/add/add-scenario-step.test.ts +37 -2
- package/src/add/add-scope.test.ts +15 -15
- package/src/add/add-scope.ts +5 -9
- package/src/add/add-secret.ts +1 -0
- package/src/add/add-system-role.test.ts +4 -4
- package/src/add/add-system-role.ts +2 -4
- package/src/add/add-variable.ts +1 -0
- package/src/add/add-wire-remote-addon.ts +4 -1
- package/src/add/add-workflow-graph-foreach.test.ts +174 -0
- package/src/add/add-workflow-graph-input.test.ts +1 -1
- package/src/add/add-workflow-graph.ts +78 -6
- package/src/add/add-workflow-sleep-expression.test.ts +3 -4
- package/src/add/add-workflow.ts +1 -4
- package/src/add/addon-contracts.test.ts +1 -1
- package/src/add/config-value-metadata.test.ts +94 -0
- package/src/add/secret-sink-check.test.ts +4 -1
- package/src/error-codes.ts +3 -1
- package/src/index.ts +19 -5
- package/src/inspector.ts +19 -2
- package/src/types.ts +25 -28
- package/src/utils/annotate-http-route-auth.test.ts +5 -7
- package/src/utils/annotate-http-route-auth.ts +2 -1
- package/src/utils/check-secret-sinks.ts +4 -1
- package/src/utils/compute-required-schemas.ts +1 -1
- package/src/utils/contract-hashes.test.ts +1 -1
- package/src/utils/contract-hashes.ts +3 -3
- package/src/utils/extract-function-name.ts +1 -1
- package/src/utils/extract-services.ts +1 -2
- package/src/utils/extract-surface-usage.test.ts +147 -0
- package/src/utils/extract-surface-usage.ts +121 -0
- package/src/utils/filter-inspector-state.test.ts +138 -12
- package/src/utils/filter-inspector-state.ts +82 -21
- package/src/utils/filter-utils.ts +1 -1
- package/src/utils/get-property-value.ts +43 -5
- package/src/utils/load-addon-functions-meta.ts +40 -0
- package/src/utils/middleware.ts +1 -1
- package/src/utils/permissions.ts +1 -1
- package/src/utils/post-process.test.ts +59 -2
- package/src/utils/post-process.ts +88 -25
- package/src/utils/resolve-deploy-target.ts +1 -1
- package/src/utils/resolve-function-meta.ts +1 -4
- package/src/utils/resolve-function-types.ts +1 -1
- package/src/utils/resolve-versions.ts +2 -5
- package/src/utils/schema-generator.ts +3 -3
- package/src/utils/serialize-inspector-state.ts +4 -1
- package/src/utils/serialize-mcp-json.ts +1 -1
- package/src/utils/serialize-openapi-json.ts +2 -2
- package/src/utils/validate-auth-sessionless.ts +2 -1
- package/src/utils/validate-tags-resolve-to-middleware.test.ts +4 -1
- package/src/utils/workflow/derive-workflow-plan.test.ts +1 -1
- package/src/utils/workflow/derive-workflow-plan.ts +1 -1
- package/src/utils/workflow/dsl/extract-approval-policy.test.ts +1 -1
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +2 -2
- package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +1 -1
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +1 -4
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +1 -1
- package/src/utils/workflow/graph/finalize-workflows.ts +2 -2
- package/src/utils/workflow/graph/index.ts +18 -1
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +34 -3
- package/src/utils/workflow/graph/workflow-graph.types.ts +12 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/add/add-ai-agent.d.ts +0 -4
- package/dist/add/add-ai-agent.js +0 -442
- package/dist/add/add-ai-scorer.d.ts +0 -2
- package/dist/add/add-ai-scorer.js +0 -61
- package/dist/utils/secret-brokers.d.ts +0 -2
- package/dist/utils/secret-brokers.js +0 -9
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { parseVersionedId } from '@pikku/core/
|
|
1
|
+
import { parseVersionedId } from '@pikku/core/utils';
|
|
2
|
+
import { makeContextBasedId } from './extract-function-name.js';
|
|
2
3
|
import { aggregateRequiredServices } from './post-process.js';
|
|
3
|
-
import { resolveDeployTarget } from './resolve-deploy-target.js';
|
|
4
|
+
import { IncompatibleDeployTargetError, resolveDeployTarget, } from './resolve-deploy-target.js';
|
|
4
5
|
// Module-level Set to track warned groups across multiple filter calls
|
|
5
6
|
const globalWarnedGroups = new Set();
|
|
6
7
|
/**
|
|
@@ -67,8 +68,9 @@ function repopulateFileSetFromMeta(meta, fallbackFiles, hasEntries) {
|
|
|
67
68
|
* Check if metadata matches the given filters
|
|
68
69
|
*/
|
|
69
70
|
function matchesFilters(filters, meta, logger, warnedGroups,
|
|
70
|
-
//
|
|
71
|
-
//
|
|
71
|
+
// Kept pikkuFuncIds under the deploy filter; null when the filter is
|
|
72
|
+
// inactive. Asked about one id at a time, which is what lets a conflict in an
|
|
73
|
+
// addon's own metadata surface only for a function this app actually wires.
|
|
72
74
|
keptByDeploy) {
|
|
73
75
|
// If no filters, allow everything
|
|
74
76
|
if (Object.keys(filters).length === 0)
|
|
@@ -137,9 +139,12 @@ keptByDeploy) {
|
|
|
137
139
|
return false;
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
|
-
const
|
|
141
|
-
const matchesNamePattern = (pattern) =>
|
|
142
|
-
|
|
142
|
+
const candidateNames = [meta.name, ...(meta.altNames ?? [])];
|
|
143
|
+
const matchesNamePattern = (pattern) => candidateNames.some((candidate) => {
|
|
144
|
+
const { baseName } = parseVersionedId(candidate);
|
|
145
|
+
return (matchesWildcard(candidate, pattern) ||
|
|
146
|
+
(baseName !== candidate && matchesWildcard(baseName, pattern)));
|
|
147
|
+
});
|
|
143
148
|
// Check name include filter (match against both full ID and base name for versioned functions)
|
|
144
149
|
if (filters.names && filters.names.length > 0) {
|
|
145
150
|
const nameMatches = filters.names.some(matchesNamePattern);
|
|
@@ -257,14 +262,48 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
257
262
|
: null;
|
|
258
263
|
const incompatible = new Set(filters.serverlessIncompatible ?? []);
|
|
259
264
|
const defaultTarget = filters.defaultTarget ?? 'serverless';
|
|
260
|
-
|
|
261
|
-
for
|
|
262
|
-
|
|
265
|
+
const kept = new Set();
|
|
266
|
+
// An addon publishes metadata for everything it exports, so a conflict in a
|
|
267
|
+
// function this app never wires is not this app's build error. Holding it
|
|
268
|
+
// here and raising it from `has` reports it at the one place the app is
|
|
269
|
+
// known to depend on that function.
|
|
270
|
+
const conflicts = new Map();
|
|
271
|
+
keptByDeploy = {
|
|
272
|
+
has: (funcId) => {
|
|
273
|
+
const conflict = conflicts.get(funcId);
|
|
274
|
+
if (conflict)
|
|
275
|
+
throw conflict;
|
|
276
|
+
return kept.has(funcId);
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
const keepByTarget = (funcId, funcMeta, incompatibleServices) => {
|
|
280
|
+
const target = resolveDeployTarget(funcMeta, incompatibleServices, funcId, defaultTarget);
|
|
263
281
|
if (allowed && !allowed.has(target))
|
|
264
|
-
|
|
282
|
+
return;
|
|
265
283
|
if (excluded && excluded.has(target))
|
|
266
|
-
|
|
267
|
-
|
|
284
|
+
return;
|
|
285
|
+
kept.add(funcId);
|
|
286
|
+
};
|
|
287
|
+
for (const [funcId, funcMeta] of Object.entries(state.functions.meta)) {
|
|
288
|
+
keepByTarget(funcId, funcMeta, incompatible);
|
|
289
|
+
}
|
|
290
|
+
// A wiring onto an addon function records the addon's own id, so the
|
|
291
|
+
// addon's published metadata is the only place its deploy target can be
|
|
292
|
+
// read from. Its serverlessIncompatible services are namespace-scoped —
|
|
293
|
+
// a service name means whatever the addon that declared it means.
|
|
294
|
+
for (const [namespace, addonMeta] of Object.entries(state.addonFunctions ?? {})) {
|
|
295
|
+
const addonIncompatible = new Set(state.addonServerlessIncompatible?.get(namespace) ?? []);
|
|
296
|
+
for (const [funcName, funcMeta] of Object.entries(addonMeta)) {
|
|
297
|
+
const funcId = `${namespace}:${funcName}`;
|
|
298
|
+
try {
|
|
299
|
+
keepByTarget(funcId, funcMeta, addonIncompatible);
|
|
300
|
+
}
|
|
301
|
+
catch (e) {
|
|
302
|
+
if (!(e instanceof IncompatibleDeployTargetError))
|
|
303
|
+
throw e;
|
|
304
|
+
conflicts.set(funcId, e);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
268
307
|
}
|
|
269
308
|
}
|
|
270
309
|
// Snapshot the original workflow graph meta before filtering prunes it
|
|
@@ -363,6 +402,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
363
402
|
const matches = matchesFilters(filters, {
|
|
364
403
|
type: 'http',
|
|
365
404
|
name: routeMeta.pikkuFuncId, // Use function name, not route
|
|
405
|
+
altNames: [makeContextBasedId('http', method, routeMeta.route)],
|
|
366
406
|
tags: routeMeta.tags,
|
|
367
407
|
filePath,
|
|
368
408
|
httpRoute: routeMeta.route,
|
|
@@ -376,9 +416,6 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
376
416
|
// Track used functions/middleware/permissions
|
|
377
417
|
if (routeMeta.pikkuFuncId) {
|
|
378
418
|
filteredState.serviceAggregation.usedFunctions.add(routeMeta.pikkuFuncId);
|
|
379
|
-
if (routeMeta.refTarget) {
|
|
380
|
-
filteredState.serviceAggregation.usedFunctions.add(routeMeta.refTarget);
|
|
381
|
-
}
|
|
382
419
|
// For workflow/agent routes, also add the base name
|
|
383
420
|
// so the workflow/agent definition survives pruning
|
|
384
421
|
const colonIdx = routeMeta.pikkuFuncId.indexOf(':');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InspectorFilters, InspectorLogger } from '../types.js';
|
|
2
|
-
import type { PikkuWiringTypes } from '@pikku/core/
|
|
2
|
+
import type { PikkuWiringTypes } from '@pikku/core/types';
|
|
3
3
|
/**
|
|
4
4
|
* Match a value against a pattern with wildcard support
|
|
5
5
|
* Supports "*" at the beginning, end, or both (e.g., "send*", "*Payment", "*process*")
|
|
@@ -94,6 +94,45 @@ export const assertStringLiteralProperty = (obj, propertyName, wiringType, logge
|
|
|
94
94
|
console.error(errorMsg);
|
|
95
95
|
}
|
|
96
96
|
};
|
|
97
|
+
/**
|
|
98
|
+
* Fold a node that is a compile-time constant string down to its value.
|
|
99
|
+
*
|
|
100
|
+
* The fallback below this is `getText()`, which returns the node's *source*.
|
|
101
|
+
* For a lone `'…'` that is indistinguishable from the value, so nobody noticed
|
|
102
|
+
* that anything written as an expression comes through raw: a `description`
|
|
103
|
+
* split across lines as `'Fetch the thing ' + 'from upstream'` reaches
|
|
104
|
+
* `infra.json` with the quotes and the `+` still in it, and is rendered that
|
|
105
|
+
* way in the fabric console.
|
|
106
|
+
*
|
|
107
|
+
* Deliberately checker-free, unlike `extractStringLiteral`. Every value this
|
|
108
|
+
* resolves is a literal in the same expression, so no symbol resolution is
|
|
109
|
+
* needed, and `getPropertyValue` is called from a dozen places that have no
|
|
110
|
+
* checker to hand. Anything it cannot fold — an identifier, a template with a
|
|
111
|
+
* substitution — returns `undefined` and takes the old path, so this only ever
|
|
112
|
+
* turns a wrong answer into a right one.
|
|
113
|
+
*/
|
|
114
|
+
const foldStaticString = (node) => {
|
|
115
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
116
|
+
return node.text;
|
|
117
|
+
}
|
|
118
|
+
if (ts.isParenthesizedExpression(node)) {
|
|
119
|
+
return foldStaticString(node.expression);
|
|
120
|
+
}
|
|
121
|
+
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
122
|
+
return foldStaticString(node.expression);
|
|
123
|
+
}
|
|
124
|
+
if (ts.isBinaryExpression(node) &&
|
|
125
|
+
node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
|
126
|
+
const left = foldStaticString(node.left);
|
|
127
|
+
if (left === undefined)
|
|
128
|
+
return undefined;
|
|
129
|
+
const right = foldStaticString(node.right);
|
|
130
|
+
if (right === undefined)
|
|
131
|
+
return undefined;
|
|
132
|
+
return left + right;
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
};
|
|
97
136
|
export const getPropertyValue = (obj, propertyName) => {
|
|
98
137
|
const property = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
99
138
|
ts.isIdentifier(p.name) &&
|
|
@@ -126,9 +165,9 @@ export const getPropertyValue = (obj, propertyName) => {
|
|
|
126
165
|
}
|
|
127
166
|
return Number(initializer.text);
|
|
128
167
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return
|
|
168
|
+
const staticString = foldStaticString(initializer);
|
|
169
|
+
if (staticString !== undefined) {
|
|
170
|
+
return staticString;
|
|
132
171
|
}
|
|
133
172
|
return initializer.getText();
|
|
134
173
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
1
2
|
import { readFile, readdir } from 'fs/promises';
|
|
2
3
|
import { createRequire } from 'module';
|
|
3
4
|
import { join, dirname } from 'path';
|
|
@@ -88,6 +89,35 @@ const resolveAddonMeta = (require, packageName, baseName) => {
|
|
|
88
89
|
}
|
|
89
90
|
return null;
|
|
90
91
|
};
|
|
92
|
+
/**
|
|
93
|
+
* Give the addon's input/output type names a declaration file to be imported
|
|
94
|
+
* from, so a wiring that references an addon function can name its contract.
|
|
95
|
+
*
|
|
96
|
+
* Without this the only thing that knows where `GetScenarioArtifactInput` lives
|
|
97
|
+
* is the type checker, and it only knows once the consumer's *own* rpc map has
|
|
98
|
+
* already been generated with the addon's routes in it — which is to say on the
|
|
99
|
+
* second run. A first, cold run resolved `ref('console:getScenarioArtifact')`
|
|
100
|
+
* against a map that did not mention it yet and fell back to the whole
|
|
101
|
+
* `FlattenedRPCMap`, so the same source produced a different http-map depending
|
|
102
|
+
* on whether `.pikku` happened to be there. The addon publishes these types at a
|
|
103
|
+
* fixed subpath; reading them from the metadata makes the first run agree with
|
|
104
|
+
* the second.
|
|
105
|
+
*/
|
|
106
|
+
const registerAddonTypes = (state, metaPath, meta) => {
|
|
107
|
+
// Derived from the metadata's own location rather than resolved as a package
|
|
108
|
+
// subpath: the addon exports that declaration under a `types`-only condition,
|
|
109
|
+
// which `require.resolve` will not follow.
|
|
110
|
+
const typesPath = join(dirname(metaPath), '..', 'rpc', 'pikku-rpc-wirings-map.internal.gen.d.ts');
|
|
111
|
+
if (!existsSync(typesPath))
|
|
112
|
+
return;
|
|
113
|
+
for (const funcMeta of Object.values(meta)) {
|
|
114
|
+
for (const name of [funcMeta.inputSchemaName, funcMeta.outputSchemaName]) {
|
|
115
|
+
if (!name || state.functions.typesMap.exists(name, typesPath))
|
|
116
|
+
continue;
|
|
117
|
+
state.functions.typesMap.addType(name, typesPath);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
91
121
|
/**
|
|
92
122
|
* After the setup sweep discovers wireAddon() declarations, load each addon
|
|
93
123
|
* package's function metadata so that wiring handlers (channels, HTTP routes,
|
|
@@ -116,6 +146,7 @@ export async function loadAddonFunctionsMeta(logger, state) {
|
|
|
116
146
|
const meta = JSON.parse(raw);
|
|
117
147
|
state.addonFunctions[namespace] = meta;
|
|
118
148
|
logger.debug(`Loaded ${Object.keys(meta).length} addon functions for '${namespace}' from ${decl.package}`);
|
|
149
|
+
registerAddonTypes(state, metaPath, meta);
|
|
119
150
|
// If wireAddon has mcp: true, expose addon functions with mcp: true as MCP tools
|
|
120
151
|
if (decl.mcp) {
|
|
121
152
|
for (const [funcName, funcMeta] of Object.entries(meta)) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import type { MiddlewareMetadata } from '@pikku/core/
|
|
2
|
+
import type { MiddlewareMetadata } from '@pikku/core/middleware';
|
|
3
3
|
import type { InspectorState } from '../types.js';
|
|
4
4
|
export interface MiddlewareRef {
|
|
5
5
|
definitionId: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import type { PermissionMetadata } from '@pikku/core/
|
|
2
|
+
import type { PermissionMetadata } from '@pikku/core/function';
|
|
3
3
|
import type { InspectorState } from '../types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Extract permission pikkuFuncIds from an expression (array or object literal)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { InspectorState, InspectorLogger, InspectorOptions } from '../types.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PermissionMetadata } from '@pikku/core/function';
|
|
3
|
+
import type { MiddlewareMetadata } from '@pikku/core/middleware';
|
|
3
4
|
import type ts from 'typescript';
|
|
4
5
|
/**
|
|
5
6
|
* Stamp the inspected authorize/callbacks service set onto the generated auth
|
|
@@ -33,10 +34,27 @@ export declare function validateSecretOverrides(logger: InspectorLogger, state:
|
|
|
33
34
|
export declare function validateCredentialOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
34
35
|
export declare function validateVariableOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
35
36
|
/**
|
|
37
|
+
* Every wired addon must be installed, in the bucket matching how it is used.
|
|
38
|
+
*
|
|
36
39
|
* A `wireRemoteAddon` package ships types only — its handlers run on the host —
|
|
37
40
|
* so it MUST be a devDependency, not a production dependency (a prod dep would
|
|
38
|
-
* drag in the runtime deps remote consumption exists to avoid).
|
|
39
|
-
*
|
|
41
|
+
* drag in the runtime deps remote consumption exists to avoid).
|
|
42
|
+
*
|
|
43
|
+
* A plain `wireAddon` only has to be *resolvable*. The failure it allows is
|
|
44
|
+
* quiet in a way the remote one is not — an absent package makes the metadata
|
|
45
|
+
* load fail, which is caught and downgraded to a warning, so every
|
|
46
|
+
* `ref('<namespace>:…')` resolves against nothing and the surface is dead at
|
|
47
|
+
* runtime. The generated console is the common case: `scaffold.console` emits
|
|
48
|
+
* `wireAddon({ package: '@pikku/addon-console' })` and refs into it, and a
|
|
49
|
+
* project that never installed the package gets a console that 404s with
|
|
50
|
+
* nothing in the build output saying why.
|
|
51
|
+
*
|
|
52
|
+
* Deliberately a resolution check and not a `package.json` one. An addon is
|
|
53
|
+
* discovered because it is *wired*, not because it is a declared dependency —
|
|
54
|
+
* addons arrive by workspace link, by a local `addons/` directory recorded in
|
|
55
|
+
* `pikku-addons.json`, and by plain install, and only the last of those shows
|
|
56
|
+
* up in `dependencies`. Asking the resolver is the only question that matches
|
|
57
|
+
* what the loader will actually do.
|
|
40
58
|
*/
|
|
41
59
|
export declare function validateRemoteAddonDependencies(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
42
60
|
/**
|
|
@@ -64,6 +82,7 @@ export declare function computeRequiredSchemas(state: InspectorState, options: I
|
|
|
64
82
|
* the functions that are actually invoked, so this blocks CI through
|
|
65
83
|
* `--fail-on-error` without failing a dev server over a function nobody calls.
|
|
66
84
|
*/
|
|
85
|
+
export declare function unresolvedSchemaReferences(state: InspectorState): string[];
|
|
67
86
|
export declare function validateSchemaReferences(logger: InspectorLogger, state: InspectorState): void;
|
|
68
87
|
/**
|
|
69
88
|
* A model is either concrete (`provider/model`) or an alias, which must appear
|
|
@@ -89,10 +108,11 @@ export declare function validateScenarioServices(logger: InspectorLogger, state:
|
|
|
89
108
|
*
|
|
90
109
|
* - the step target must be a static string literal (PKU678) — otherwise it
|
|
91
110
|
* can't be bundled, typed or drawn;
|
|
92
|
-
* - a step
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
111
|
+
* - a step that runs as a persona must be given one (PKU677) — declared by
|
|
112
|
+
* `actor: true`, or implied by a `browser` binding, since the browser context
|
|
113
|
+
* signs in as that persona. It is checked statically even though a browser
|
|
114
|
+
* binding only runs on a browser run: a step that can never be driven by a
|
|
115
|
+
* human is a latent failure, not a passing default run.
|
|
96
116
|
* - a scenario written as a step ladder must assert something (PKU680).
|
|
97
117
|
*
|
|
98
118
|
* It also marks the step's function as used, so `scenario run` boots the
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { extractTypeKeys } from './type-utils.js';
|
|
4
5
|
import { ErrorCode } from '../error-codes.js';
|
|
5
6
|
import { findSecretAliasServices } from './secret-alias-services.js';
|
|
6
7
|
import { relative } from 'node:path';
|
|
7
8
|
import { AUTH_HANDLER_FUNC_ID } from '../add/add-auth.js';
|
|
8
|
-
import { flattenScopeDefinitions } from '@pikku/core/
|
|
9
|
+
import { flattenScopeDefinitions } from '@pikku/core/scope';
|
|
9
10
|
import { DYNAMIC_SCENARIO_STEP_TARGET } from './workflow/dsl/patterns.js';
|
|
10
11
|
/**
|
|
11
12
|
* Stamp the inspected authorize/callbacks service set onto the generated auth
|
|
@@ -374,17 +375,74 @@ export function validateVariableOverrides(logger, state) {
|
|
|
374
375
|
}
|
|
375
376
|
}
|
|
376
377
|
/**
|
|
378
|
+
* Can `pkg` be resolved from `rootDir`, by any means the loader would use?
|
|
379
|
+
*
|
|
380
|
+
* Resolution is attempted against `<pkg>/package.json` first and the bare
|
|
381
|
+
* specifier second, because neither alone is conclusive. A package with a
|
|
382
|
+
* restrictive `exports` map refuses the first with
|
|
383
|
+
* `ERR_PACKAGE_PATH_NOT_EXPORTED` — which is not a failure at all: reaching the
|
|
384
|
+
* exports map means the package was found. Only a module-not-found from both
|
|
385
|
+
* attempts means it is genuinely absent.
|
|
386
|
+
*/
|
|
387
|
+
const isPackageResolvable = (pkg, rootDir) => {
|
|
388
|
+
const NOT_FOUND = new Set(['MODULE_NOT_FOUND', 'ERR_MODULE_NOT_FOUND']);
|
|
389
|
+
let req;
|
|
390
|
+
try {
|
|
391
|
+
req = createRequire(join(rootDir, 'package.json'));
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
return true; // cannot ask; do not accuse
|
|
395
|
+
}
|
|
396
|
+
for (const specifier of [`${pkg}/package.json`, pkg]) {
|
|
397
|
+
try {
|
|
398
|
+
req.resolve(specifier);
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
catch (e) {
|
|
402
|
+
if (!NOT_FOUND.has(e?.code))
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return false;
|
|
407
|
+
};
|
|
408
|
+
/**
|
|
409
|
+
* Every wired addon must be installed, in the bucket matching how it is used.
|
|
410
|
+
*
|
|
377
411
|
* A `wireRemoteAddon` package ships types only — its handlers run on the host —
|
|
378
412
|
* so it MUST be a devDependency, not a production dependency (a prod dep would
|
|
379
|
-
* drag in the runtime deps remote consumption exists to avoid).
|
|
380
|
-
*
|
|
413
|
+
* drag in the runtime deps remote consumption exists to avoid).
|
|
414
|
+
*
|
|
415
|
+
* A plain `wireAddon` only has to be *resolvable*. The failure it allows is
|
|
416
|
+
* quiet in a way the remote one is not — an absent package makes the metadata
|
|
417
|
+
* load fail, which is caught and downgraded to a warning, so every
|
|
418
|
+
* `ref('<namespace>:…')` resolves against nothing and the surface is dead at
|
|
419
|
+
* runtime. The generated console is the common case: `scaffold.console` emits
|
|
420
|
+
* `wireAddon({ package: '@pikku/addon-console' })` and refs into it, and a
|
|
421
|
+
* project that never installed the package gets a console that 404s with
|
|
422
|
+
* nothing in the build output saying why.
|
|
423
|
+
*
|
|
424
|
+
* Deliberately a resolution check and not a `package.json` one. An addon is
|
|
425
|
+
* discovered because it is *wired*, not because it is a declared dependency —
|
|
426
|
+
* addons arrive by workspace link, by a local `addons/` directory recorded in
|
|
427
|
+
* `pikku-addons.json`, and by plain install, and only the last of those shows
|
|
428
|
+
* up in `dependencies`. Asking the resolver is the only question that matches
|
|
429
|
+
* what the loader will actually do.
|
|
381
430
|
*/
|
|
382
431
|
export function validateRemoteAddonDependencies(logger, state) {
|
|
383
432
|
const { wireAddonDeclarations } = state.rpc;
|
|
384
433
|
if (!wireAddonDeclarations || wireAddonDeclarations.size === 0)
|
|
385
434
|
return;
|
|
386
|
-
|
|
387
|
-
|
|
435
|
+
// Local addons first, and independently of the manifest below: their check
|
|
436
|
+
// asks the resolver, not `package.json`, so a project without a manifest (as
|
|
437
|
+
// in some tests) must not skip it.
|
|
438
|
+
for (const [namespace, decl] of wireAddonDeclarations.entries()) {
|
|
439
|
+
if (decl.remote)
|
|
440
|
+
continue;
|
|
441
|
+
if (isPackageResolvable(decl.package, state.rootDir))
|
|
442
|
+
continue;
|
|
443
|
+
logger.critical(ErrorCode.ADDON_NOT_INSTALLED, `Addon '${namespace}' ('${decl.package}') is wired with wireAddon but cannot be resolved — every ref('${namespace}:…') will resolve to nothing and the surface will be dead at runtime. Install it, or remove the wireAddon call.`);
|
|
444
|
+
}
|
|
445
|
+
if (!Array.from(wireAddonDeclarations.values()).some((d) => d.remote))
|
|
388
446
|
return;
|
|
389
447
|
const pkgJsonPath = join(state.rootDir, 'package.json');
|
|
390
448
|
if (!existsSync(pkgJsonPath))
|
|
@@ -579,7 +637,7 @@ export function computeRequiredSchemas(state, options) {
|
|
|
579
637
|
* the functions that are actually invoked, so this blocks CI through
|
|
580
638
|
* `--fail-on-error` without failing a dev server over a function nobody calls.
|
|
581
639
|
*/
|
|
582
|
-
export function
|
|
640
|
+
export function unresolvedSchemaReferences(state) {
|
|
583
641
|
const { functions, schemas } = state;
|
|
584
642
|
const available = new Set(Object.keys(schemas ?? {}));
|
|
585
643
|
const unresolved = [];
|
|
@@ -604,6 +662,10 @@ export function validateSchemaReferences(logger, state) {
|
|
|
604
662
|
unresolved.push(`${funcName}.${role} → '${resolved}'`);
|
|
605
663
|
}
|
|
606
664
|
}
|
|
665
|
+
return unresolved;
|
|
666
|
+
}
|
|
667
|
+
export function validateSchemaReferences(logger, state) {
|
|
668
|
+
const unresolved = unresolvedSchemaReferences(state);
|
|
607
669
|
if (unresolved.length === 0)
|
|
608
670
|
return;
|
|
609
671
|
logger.diagnostic({
|
|
@@ -674,10 +736,11 @@ export function validateScenarioServices(logger, state) {
|
|
|
674
736
|
*
|
|
675
737
|
* - the step target must be a static string literal (PKU678) — otherwise it
|
|
676
738
|
* can't be bundled, typed or drawn;
|
|
677
|
-
* - a step
|
|
678
|
-
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
739
|
+
* - a step that runs as a persona must be given one (PKU677) — declared by
|
|
740
|
+
* `actor: true`, or implied by a `browser` binding, since the browser context
|
|
741
|
+
* signs in as that persona. It is checked statically even though a browser
|
|
742
|
+
* binding only runs on a browser run: a step that can never be driven by a
|
|
743
|
+
* human is a latent failure, not a passing default run.
|
|
681
744
|
* - a scenario written as a step ladder must assert something (PKU680).
|
|
682
745
|
*
|
|
683
746
|
* It also marks the step's function as used, so `scenario run` boots the
|
|
@@ -697,10 +760,12 @@ export function validateScenarioSteps(logger, state) {
|
|
|
697
760
|
}
|
|
698
761
|
state.serviceAggregation.usedFunctions.add(step.stepFunc);
|
|
699
762
|
const stepMeta = state.functions.meta[step.stepFunc];
|
|
700
|
-
if (stepMeta?.
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
763
|
+
if (stepMeta?.scenarioStepRequiresActor && !step.actor) {
|
|
764
|
+
const why = stepMeta.scenarioStepSurfaces?.includes('browser')
|
|
765
|
+
? 'declares a browser binding'
|
|
766
|
+
: 'is driven by a persona';
|
|
767
|
+
logger.critical(ErrorCode.SCENARIO_STEP_NEEDS_ACTOR, `Scenario '${workflowName}' calls step '${step.stepFunc}', which ${why}, without an actor. ` +
|
|
768
|
+
`Pass { actor: actors.<name> } so it runs as that persona.`);
|
|
704
769
|
}
|
|
705
770
|
continue;
|
|
706
771
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FunctionMeta } from '@pikku/core/
|
|
1
|
+
import type { FunctionMeta } from '@pikku/core/services';
|
|
2
2
|
/**
|
|
3
3
|
* Thrown when a function's explicit `deploy: 'serverless'` conflicts
|
|
4
4
|
* with one of its services being declared `serverlessIncompatible`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FunctionMeta, FunctionsMeta } from '@pikku/core/
|
|
1
|
+
import type { FunctionMeta, FunctionsMeta } from '@pikku/core/services';
|
|
2
2
|
/**
|
|
3
3
|
* Look up function metadata by pikkuFuncId, checking both local functions
|
|
4
4
|
* and addon functions. Addon functions use namespaced IDs like 'namespace:funcName'.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TypesMap } from '../types-map.js';
|
|
2
|
-
import type { FunctionsMeta } from '@pikku/core/
|
|
2
|
+
import type { FunctionsMeta } from '@pikku/core/services';
|
|
3
3
|
export declare function resolveFunctionIOTypes(pikkuFuncId: string, functionsMeta: FunctionsMeta, typesMap: TypesMap, requiredTypes: Set<string>): {
|
|
4
4
|
inputType: string;
|
|
5
5
|
outputType: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseVersionedId, formatVersionedId
|
|
1
|
+
import { parseVersionedId, formatVersionedId } from '@pikku/core/utils';
|
|
2
2
|
import { ErrorCode } from '../error-codes.js';
|
|
3
3
|
export function resolveLatestVersions(state, logger) {
|
|
4
4
|
const functionsMeta = state.functions.meta;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { JSONValue } from '@pikku/core/
|
|
1
|
+
import type { JSONValue } from '@pikku/core/utils';
|
|
2
2
|
import type { InspectorLogger, InspectorState } from '../types.js';
|
|
3
3
|
/** One file the generated schemas were derived from, as of when they were generated. */
|
|
4
4
|
export type SchemaDep = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SecretUsage } from './extract-secret-usage.js';
|
|
2
|
-
import type { JSONValue } from '@pikku/core/
|
|
2
|
+
import type { JSONValue } from '@pikku/core/utils';
|
|
3
3
|
import type { InspectorDiagnostic, InspectorFeature, InspectorState } from '../types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Serializable version of InspectorState that can be saved to JSON
|
|
@@ -242,6 +242,7 @@ export interface SerializableInspectorState {
|
|
|
242
242
|
files: string[];
|
|
243
243
|
usage: Record<string, SecretUsage>;
|
|
244
244
|
};
|
|
245
|
+
surfaceUsage: InspectorState['surfaceUsage'];
|
|
245
246
|
credentials: {
|
|
246
247
|
definitions: InspectorState['credentials']['definitions'];
|
|
247
248
|
files: string[];
|
|
@@ -118,6 +118,7 @@ export function serializeInspectorState(state) {
|
|
|
118
118
|
files: Array.from(state.secrets.files),
|
|
119
119
|
usage: Object.fromEntries(state.secrets.usage),
|
|
120
120
|
},
|
|
121
|
+
surfaceUsage: state.surfaceUsage,
|
|
121
122
|
credentials: {
|
|
122
123
|
definitions: state.credentials.definitions,
|
|
123
124
|
files: Array.from(state.credentials.files),
|
|
@@ -295,6 +296,7 @@ export function deserializeInspectorState(data) {
|
|
|
295
296
|
files: new Set(data.secrets?.files || []),
|
|
296
297
|
usage: new Map(Object.entries(data.secrets?.usage || {})),
|
|
297
298
|
},
|
|
299
|
+
surfaceUsage: data.surfaceUsage || {},
|
|
298
300
|
credentials: {
|
|
299
301
|
definitions: data.credentials?.definitions || [],
|
|
300
302
|
files: new Set(data.credentials?.files || []),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { FunctionsMeta } from '@pikku/core/
|
|
2
|
-
import type { HTTPWiringsMeta } from '@pikku/core/
|
|
1
|
+
import type { FunctionsMeta } from '@pikku/core/services';
|
|
2
|
+
import type { HTTPWiringsMeta } from '@pikku/core/http';
|
|
3
3
|
import type { InspectorLogger } from '../types.js';
|
|
4
4
|
interface ErrorDetails {
|
|
5
5
|
status: number;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getPropertyValue } from './get-property-value.js';
|
|
2
2
|
import { ErrorCode } from '../error-codes.js';
|
|
3
|
+
import { resolveFunctionMeta } from './resolve-function-meta.js';
|
|
3
4
|
export function validateAuthSessionless(logger, obj, state, funcName, wireDescription, inheritedAuth) {
|
|
4
|
-
const fnMeta = state
|
|
5
|
+
const fnMeta = resolveFunctionMeta(state, funcName);
|
|
5
6
|
if (!fnMeta)
|
|
6
7
|
return true;
|
|
7
8
|
const routeAuth = getPropertyValue(obj, 'auth');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Converts DSL (Domain Specific Language) step-based format to graph node format
|
|
3
3
|
*/
|
|
4
|
-
import type { WorkflowsMeta } from '@pikku/core/
|
|
4
|
+
import type { WorkflowsMeta } from '@pikku/core/workflow';
|
|
5
5
|
import type { SerializedWorkflowGraph } from './workflow-graph.types.js';
|
|
6
6
|
/**
|
|
7
7
|
* Convert a DSL workflow to graph format
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isVersionedId, formatVersionedId, parseVersionedId, } from '@pikku/core/
|
|
1
|
+
import { isVersionedId, formatVersionedId, parseVersionedId, } from '@pikku/core/utils';
|
|
2
2
|
import { canonicalJSON, hashString } from '../../hash.js';
|
|
3
3
|
import { convertDslToGraph } from './convert-dsl-to-graph.js';
|
|
4
4
|
import { deriveWorkflowPlan } from '../derive-workflow-plan.js';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow graph serialization exports
|
|
3
3
|
*/
|
|
4
|
-
export
|
|
4
|
+
export { isDataRef, isFunctionNode, ref, state, } from './workflow-graph.types.js';
|
|
5
|
+
export type { BranchCondition, ContextVariable, DataRef, FlowNode, ForEachMode, FunctionNode, NodeOptions, SerializedWorkflowGraph, SerializedWorkflowGraphs, WorkflowContext, } from './workflow-graph.types.js';
|
|
5
6
|
export { serializeWorkflowGraph } from './serialize-workflow-graph.js';
|
|
6
7
|
export { convertDslToGraph } from './convert-dsl-to-graph.js';
|
|
7
8
|
export { finalizeWorkflows } from './finalize-workflows.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow graph serialization exports
|
|
3
3
|
*/
|
|
4
|
-
export
|
|
4
|
+
export { isDataRef, isFunctionNode, ref, state, } from './workflow-graph.types.js';
|
|
5
5
|
export { serializeWorkflowGraph } from './serialize-workflow-graph.js';
|
|
6
6
|
export { convertDslToGraph } from './convert-dsl-to-graph.js';
|
|
7
7
|
export { finalizeWorkflows } from './finalize-workflows.js';
|
|
@@ -11,7 +11,12 @@ export declare function serializeWorkflowGraph(definition: {
|
|
|
11
11
|
func: {
|
|
12
12
|
name?: string;
|
|
13
13
|
};
|
|
14
|
-
input?: (ref: any) => Record<string, unknown>;
|
|
14
|
+
input?: (ref: any, template?: any, $item?: any) => Record<string, unknown>;
|
|
15
|
+
forEach?: string | {
|
|
16
|
+
nodeId: string;
|
|
17
|
+
path?: string;
|
|
18
|
+
} | ((ref: any) => unknown);
|
|
19
|
+
mode?: 'parallel' | 'sequential';
|
|
15
20
|
next?: string | string[] | Record<string, string | string[]>;
|
|
16
21
|
onError?: string | string[];
|
|
17
22
|
notes?: string;
|