@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
|
@@ -127,6 +127,46 @@ export const assertStringLiteralProperty = (
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Fold a node that is a compile-time constant string down to its value.
|
|
132
|
+
*
|
|
133
|
+
* The fallback below this is `getText()`, which returns the node's *source*.
|
|
134
|
+
* For a lone `'…'` that is indistinguishable from the value, so nobody noticed
|
|
135
|
+
* that anything written as an expression comes through raw: a `description`
|
|
136
|
+
* split across lines as `'Fetch the thing ' + 'from upstream'` reaches
|
|
137
|
+
* `infra.json` with the quotes and the `+` still in it, and is rendered that
|
|
138
|
+
* way in the fabric console.
|
|
139
|
+
*
|
|
140
|
+
* Deliberately checker-free, unlike `extractStringLiteral`. Every value this
|
|
141
|
+
* resolves is a literal in the same expression, so no symbol resolution is
|
|
142
|
+
* needed, and `getPropertyValue` is called from a dozen places that have no
|
|
143
|
+
* checker to hand. Anything it cannot fold — an identifier, a template with a
|
|
144
|
+
* substitution — returns `undefined` and takes the old path, so this only ever
|
|
145
|
+
* turns a wrong answer into a right one.
|
|
146
|
+
*/
|
|
147
|
+
const foldStaticString = (node: ts.Node): string | undefined => {
|
|
148
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
149
|
+
return node.text
|
|
150
|
+
}
|
|
151
|
+
if (ts.isParenthesizedExpression(node)) {
|
|
152
|
+
return foldStaticString(node.expression)
|
|
153
|
+
}
|
|
154
|
+
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
155
|
+
return foldStaticString(node.expression)
|
|
156
|
+
}
|
|
157
|
+
if (
|
|
158
|
+
ts.isBinaryExpression(node) &&
|
|
159
|
+
node.operatorToken.kind === ts.SyntaxKind.PlusToken
|
|
160
|
+
) {
|
|
161
|
+
const left = foldStaticString(node.left)
|
|
162
|
+
if (left === undefined) return undefined
|
|
163
|
+
const right = foldStaticString(node.right)
|
|
164
|
+
if (right === undefined) return undefined
|
|
165
|
+
return left + right
|
|
166
|
+
}
|
|
167
|
+
return undefined
|
|
168
|
+
}
|
|
169
|
+
|
|
130
170
|
export const getPropertyValue = (
|
|
131
171
|
obj: ts.ObjectLiteralExpression,
|
|
132
172
|
propertyName: string
|
|
@@ -174,11 +214,9 @@ export const getPropertyValue = (
|
|
|
174
214
|
return Number(initializer.text)
|
|
175
215
|
}
|
|
176
216
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
) {
|
|
181
|
-
return initializer.text
|
|
217
|
+
const staticString = foldStaticString(initializer)
|
|
218
|
+
if (staticString !== undefined) {
|
|
219
|
+
return staticString
|
|
182
220
|
}
|
|
183
221
|
|
|
184
222
|
return initializer.getText()
|
|
@@ -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'
|
|
@@ -141,6 +142,44 @@ const resolveAddonMeta = (
|
|
|
141
142
|
return null
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Give the addon's input/output type names a declaration file to be imported
|
|
147
|
+
* from, so a wiring that references an addon function can name its contract.
|
|
148
|
+
*
|
|
149
|
+
* Without this the only thing that knows where `GetScenarioArtifactInput` lives
|
|
150
|
+
* is the type checker, and it only knows once the consumer's *own* rpc map has
|
|
151
|
+
* already been generated with the addon's routes in it — which is to say on the
|
|
152
|
+
* second run. A first, cold run resolved `ref('console:getScenarioArtifact')`
|
|
153
|
+
* against a map that did not mention it yet and fell back to the whole
|
|
154
|
+
* `FlattenedRPCMap`, so the same source produced a different http-map depending
|
|
155
|
+
* on whether `.pikku` happened to be there. The addon publishes these types at a
|
|
156
|
+
* fixed subpath; reading them from the metadata makes the first run agree with
|
|
157
|
+
* the second.
|
|
158
|
+
*/
|
|
159
|
+
const registerAddonTypes = (
|
|
160
|
+
state: InspectorState,
|
|
161
|
+
metaPath: string,
|
|
162
|
+
meta: Record<string, any>
|
|
163
|
+
): void => {
|
|
164
|
+
// Derived from the metadata's own location rather than resolved as a package
|
|
165
|
+
// subpath: the addon exports that declaration under a `types`-only condition,
|
|
166
|
+
// which `require.resolve` will not follow.
|
|
167
|
+
const typesPath = join(
|
|
168
|
+
dirname(metaPath),
|
|
169
|
+
'..',
|
|
170
|
+
'rpc',
|
|
171
|
+
'pikku-rpc-wirings-map.internal.gen.d.ts'
|
|
172
|
+
)
|
|
173
|
+
if (!existsSync(typesPath)) return
|
|
174
|
+
|
|
175
|
+
for (const funcMeta of Object.values(meta)) {
|
|
176
|
+
for (const name of [funcMeta.inputSchemaName, funcMeta.outputSchemaName]) {
|
|
177
|
+
if (!name || state.functions.typesMap.exists(name, typesPath)) continue
|
|
178
|
+
state.functions.typesMap.addType(name, typesPath)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
144
183
|
/**
|
|
145
184
|
* After the setup sweep discovers wireAddon() declarations, load each addon
|
|
146
185
|
* package's function metadata so that wiring handlers (channels, HTTP routes,
|
|
@@ -177,6 +216,7 @@ export async function loadAddonFunctionsMeta(
|
|
|
177
216
|
logger.debug(
|
|
178
217
|
`Loaded ${Object.keys(meta).length} addon functions for '${namespace}' from ${decl.package}`
|
|
179
218
|
)
|
|
219
|
+
registerAddonTypes(state, metaPath, meta)
|
|
180
220
|
|
|
181
221
|
// If wireAddon has mcp: true, expose addon functions with mcp: true as MCP tools
|
|
182
222
|
if (decl.mcp) {
|
package/src/utils/middleware.ts
CHANGED
|
@@ -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 { extractFunctionName } from './extract-function-name.js'
|
|
4
4
|
import type { InspectorState } from '../types.js'
|
|
5
5
|
|
package/src/utils/permissions.ts
CHANGED
|
@@ -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 { extractFunctionName } from './extract-function-name.js'
|
|
4
4
|
import type { InspectorState } from '../types.js'
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { strict as assert } from 'assert'
|
|
2
2
|
import { describe, test } from 'node:test'
|
|
3
|
-
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
|
|
3
|
+
import { mkdirSync, mkdtempSync, writeFileSync, rmSync } from 'node:fs'
|
|
4
4
|
import { tmpdir } from 'node:os'
|
|
5
5
|
import { join } from 'node:path'
|
|
6
6
|
import {
|
|
@@ -579,10 +579,67 @@ describe('validateRemoteAddonDependencies (wireRemoteAddon must be a devDependen
|
|
|
579
579
|
}
|
|
580
580
|
})
|
|
581
581
|
|
|
582
|
-
test('
|
|
582
|
+
test('a non-remote (wireAddon) declaration is judged on resolvability, not package.json', () => {
|
|
583
|
+
// Being listed as a dependency is not what makes an addon usable — it is
|
|
584
|
+
// discovered because it is wired, and it can arrive by workspace link or a
|
|
585
|
+
// local addons/ directory without ever appearing here. So a declared but
|
|
586
|
+
// absent package is still an error.
|
|
583
587
|
const { logger, criticals } = makeCriticalLogger()
|
|
584
588
|
const dir = writePkg({ '@addon/local': '1.0.0' }, {})
|
|
585
589
|
try {
|
|
590
|
+
validateRemoteAddonDependencies(
|
|
591
|
+
logger,
|
|
592
|
+
makeRemoteState(dir, { package: '@addon/local' })
|
|
593
|
+
)
|
|
594
|
+
assert.equal(criticals.length, 1)
|
|
595
|
+
assert.equal(criticals[0]!.code, ErrorCode.ADDON_NOT_INSTALLED)
|
|
596
|
+
} finally {
|
|
597
|
+
rmSync(dir, { recursive: true, force: true })
|
|
598
|
+
}
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
test('a non-remote addon present only in node_modules passes', () => {
|
|
602
|
+
// The inverse, and the case that matters: installed by link, absent from
|
|
603
|
+
// package.json. Reported as missing, this would fail every project whose
|
|
604
|
+
// addon lives in a local addons/ directory.
|
|
605
|
+
const { logger, criticals } = makeCriticalLogger()
|
|
606
|
+
const dir = writePkg({}, {})
|
|
607
|
+
try {
|
|
608
|
+
const pkgDir = join(dir, 'node_modules', '@addon', 'local')
|
|
609
|
+
mkdirSync(pkgDir, { recursive: true })
|
|
610
|
+
writeFileSync(
|
|
611
|
+
join(pkgDir, 'package.json'),
|
|
612
|
+
JSON.stringify({ name: '@addon/local', version: '1.0.0' })
|
|
613
|
+
)
|
|
614
|
+
validateRemoteAddonDependencies(
|
|
615
|
+
logger,
|
|
616
|
+
makeRemoteState(dir, { package: '@addon/local' })
|
|
617
|
+
)
|
|
618
|
+
assert.deepEqual(criticals, [])
|
|
619
|
+
} finally {
|
|
620
|
+
rmSync(dir, { recursive: true, force: true })
|
|
621
|
+
}
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
test('an exports map that refuses package.json is not read as missing', () => {
|
|
625
|
+
// `require.resolve('<pkg>/package.json')` throws ERR_PACKAGE_PATH_NOT_EXPORTED
|
|
626
|
+
// on a package with a restrictive exports map. Reaching the exports map
|
|
627
|
+
// means the package was found, so that must not count as absent.
|
|
628
|
+
const { logger, criticals } = makeCriticalLogger()
|
|
629
|
+
const dir = writePkg({}, {})
|
|
630
|
+
try {
|
|
631
|
+
const pkgDir = join(dir, 'node_modules', '@addon', 'local')
|
|
632
|
+
mkdirSync(pkgDir, { recursive: true })
|
|
633
|
+
writeFileSync(
|
|
634
|
+
join(pkgDir, 'package.json'),
|
|
635
|
+
JSON.stringify({
|
|
636
|
+
name: '@addon/local',
|
|
637
|
+
version: '1.0.0',
|
|
638
|
+
main: 'index.js',
|
|
639
|
+
exports: { '.': './index.js' },
|
|
640
|
+
})
|
|
641
|
+
)
|
|
642
|
+
writeFileSync(join(pkgDir, 'index.js'), 'module.exports = {}\n')
|
|
586
643
|
validateRemoteAddonDependencies(
|
|
587
644
|
logger,
|
|
588
645
|
makeRemoteState(dir, { package: '@addon/local' })
|
|
@@ -7,19 +7,20 @@ import type {
|
|
|
7
7
|
} from '../types.js'
|
|
8
8
|
import type {
|
|
9
9
|
FunctionServicesMeta,
|
|
10
|
-
MiddlewareMetadata,
|
|
11
10
|
PermissionMetadata,
|
|
12
|
-
} from '@pikku/core/
|
|
11
|
+
} from '@pikku/core/function'
|
|
12
|
+
import type { MiddlewareMetadata } from '@pikku/core/middleware'
|
|
13
13
|
import type ts from 'typescript'
|
|
14
14
|
import { existsSync, readFileSync } from 'node:fs'
|
|
15
|
+
import { createRequire } from 'node:module'
|
|
15
16
|
import { join } from 'node:path'
|
|
16
17
|
import { extractTypeKeys } from './type-utils.js'
|
|
17
18
|
import { ErrorCode } from '../error-codes.js'
|
|
18
19
|
import { findSecretAliasServices } from './secret-alias-services.js'
|
|
19
20
|
import { relative } from 'node:path'
|
|
20
21
|
import { AUTH_HANDLER_FUNC_ID } from '../add/add-auth.js'
|
|
21
|
-
import { flattenScopeDefinitions } from '@pikku/core/
|
|
22
|
-
import type { WorkflowStepMeta } from '@pikku/core/
|
|
22
|
+
import { flattenScopeDefinitions } from '@pikku/core/scope'
|
|
23
|
+
import type { WorkflowStepMeta } from '@pikku/core/workflow'
|
|
23
24
|
import { DYNAMIC_SCENARIO_STEP_TARGET } from './workflow/dsl/patterns.js'
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -480,10 +481,56 @@ export function validateVariableOverrides(
|
|
|
480
481
|
}
|
|
481
482
|
|
|
482
483
|
/**
|
|
484
|
+
* Can `pkg` be resolved from `rootDir`, by any means the loader would use?
|
|
485
|
+
*
|
|
486
|
+
* Resolution is attempted against `<pkg>/package.json` first and the bare
|
|
487
|
+
* specifier second, because neither alone is conclusive. A package with a
|
|
488
|
+
* restrictive `exports` map refuses the first with
|
|
489
|
+
* `ERR_PACKAGE_PATH_NOT_EXPORTED` — which is not a failure at all: reaching the
|
|
490
|
+
* exports map means the package was found. Only a module-not-found from both
|
|
491
|
+
* attempts means it is genuinely absent.
|
|
492
|
+
*/
|
|
493
|
+
const isPackageResolvable = (pkg: string, rootDir: string): boolean => {
|
|
494
|
+
const NOT_FOUND = new Set(['MODULE_NOT_FOUND', 'ERR_MODULE_NOT_FOUND'])
|
|
495
|
+
let req: ReturnType<typeof createRequire>
|
|
496
|
+
try {
|
|
497
|
+
req = createRequire(join(rootDir, 'package.json'))
|
|
498
|
+
} catch {
|
|
499
|
+
return true // cannot ask; do not accuse
|
|
500
|
+
}
|
|
501
|
+
for (const specifier of [`${pkg}/package.json`, pkg]) {
|
|
502
|
+
try {
|
|
503
|
+
req.resolve(specifier)
|
|
504
|
+
return true
|
|
505
|
+
} catch (e: any) {
|
|
506
|
+
if (!NOT_FOUND.has(e?.code)) return true
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return false
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Every wired addon must be installed, in the bucket matching how it is used.
|
|
514
|
+
*
|
|
483
515
|
* A `wireRemoteAddon` package ships types only — its handlers run on the host —
|
|
484
516
|
* so it MUST be a devDependency, not a production dependency (a prod dep would
|
|
485
|
-
* drag in the runtime deps remote consumption exists to avoid).
|
|
486
|
-
*
|
|
517
|
+
* drag in the runtime deps remote consumption exists to avoid).
|
|
518
|
+
*
|
|
519
|
+
* A plain `wireAddon` only has to be *resolvable*. The failure it allows is
|
|
520
|
+
* quiet in a way the remote one is not — an absent package makes the metadata
|
|
521
|
+
* load fail, which is caught and downgraded to a warning, so every
|
|
522
|
+
* `ref('<namespace>:…')` resolves against nothing and the surface is dead at
|
|
523
|
+
* runtime. The generated console is the common case: `scaffold.console` emits
|
|
524
|
+
* `wireAddon({ package: '@pikku/addon-console' })` and refs into it, and a
|
|
525
|
+
* project that never installed the package gets a console that 404s with
|
|
526
|
+
* nothing in the build output saying why.
|
|
527
|
+
*
|
|
528
|
+
* Deliberately a resolution check and not a `package.json` one. An addon is
|
|
529
|
+
* discovered because it is *wired*, not because it is a declared dependency —
|
|
530
|
+
* addons arrive by workspace link, by a local `addons/` directory recorded in
|
|
531
|
+
* `pikku-addons.json`, and by plain install, and only the last of those shows
|
|
532
|
+
* up in `dependencies`. Asking the resolver is the only question that matches
|
|
533
|
+
* what the loader will actually do.
|
|
487
534
|
*/
|
|
488
535
|
export function validateRemoteAddonDependencies(
|
|
489
536
|
logger: InspectorLogger,
|
|
@@ -492,10 +539,19 @@ export function validateRemoteAddonDependencies(
|
|
|
492
539
|
const { wireAddonDeclarations } = state.rpc
|
|
493
540
|
if (!wireAddonDeclarations || wireAddonDeclarations.size === 0) return
|
|
494
541
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
)
|
|
498
|
-
|
|
542
|
+
// Local addons first, and independently of the manifest below: their check
|
|
543
|
+
// asks the resolver, not `package.json`, so a project without a manifest (as
|
|
544
|
+
// in some tests) must not skip it.
|
|
545
|
+
for (const [namespace, decl] of wireAddonDeclarations.entries()) {
|
|
546
|
+
if (decl.remote) continue
|
|
547
|
+
if (isPackageResolvable(decl.package, state.rootDir)) continue
|
|
548
|
+
logger.critical(
|
|
549
|
+
ErrorCode.ADDON_NOT_INSTALLED,
|
|
550
|
+
`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.`
|
|
551
|
+
)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (!Array.from(wireAddonDeclarations.values()).some((d) => d.remote)) return
|
|
499
555
|
|
|
500
556
|
const pkgJsonPath = join(state.rootDir, 'package.json')
|
|
501
557
|
if (!existsSync(pkgJsonPath)) return // no manifest to check (e.g. some tests)
|
|
@@ -724,10 +780,7 @@ export function computeRequiredSchemas(
|
|
|
724
780
|
* the functions that are actually invoked, so this blocks CI through
|
|
725
781
|
* `--fail-on-error` without failing a dev server over a function nobody calls.
|
|
726
782
|
*/
|
|
727
|
-
export function
|
|
728
|
-
logger: InspectorLogger,
|
|
729
|
-
state: InspectorState
|
|
730
|
-
): void {
|
|
783
|
+
export function unresolvedSchemaReferences(state: InspectorState): string[] {
|
|
731
784
|
const { functions, schemas } = state
|
|
732
785
|
const available = new Set(Object.keys(schemas ?? {}))
|
|
733
786
|
const unresolved: string[] = []
|
|
@@ -752,6 +805,15 @@ export function validateSchemaReferences(
|
|
|
752
805
|
}
|
|
753
806
|
}
|
|
754
807
|
|
|
808
|
+
return unresolved
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
export function validateSchemaReferences(
|
|
812
|
+
logger: InspectorLogger,
|
|
813
|
+
state: InspectorState
|
|
814
|
+
): void {
|
|
815
|
+
const unresolved = unresolvedSchemaReferences(state)
|
|
816
|
+
|
|
755
817
|
if (unresolved.length === 0) return
|
|
756
818
|
|
|
757
819
|
logger.diagnostic({
|
|
@@ -842,10 +904,11 @@ export function validateScenarioServices(
|
|
|
842
904
|
*
|
|
843
905
|
* - the step target must be a static string literal (PKU678) — otherwise it
|
|
844
906
|
* can't be bundled, typed or drawn;
|
|
845
|
-
* - a step
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
907
|
+
* - a step that runs as a persona must be given one (PKU677) — declared by
|
|
908
|
+
* `actor: true`, or implied by a `browser` binding, since the browser context
|
|
909
|
+
* signs in as that persona. It is checked statically even though a browser
|
|
910
|
+
* binding only runs on a browser run: a step that can never be driven by a
|
|
911
|
+
* human is a latent failure, not a passing default run.
|
|
849
912
|
* - a scenario written as a step ladder must assert something (PKU680).
|
|
850
913
|
*
|
|
851
914
|
* It also marks the step's function as used, so `scenario run` boots the
|
|
@@ -872,14 +935,14 @@ export function validateScenarioSteps(
|
|
|
872
935
|
}
|
|
873
936
|
state.serviceAggregation.usedFunctions.add(step.stepFunc)
|
|
874
937
|
const stepMeta = state.functions.meta[step.stepFunc]
|
|
875
|
-
if (
|
|
876
|
-
stepMeta
|
|
877
|
-
|
|
878
|
-
|
|
938
|
+
if (stepMeta?.scenarioStepRequiresActor && !step.actor) {
|
|
939
|
+
const why = stepMeta.scenarioStepSurfaces?.includes('browser')
|
|
940
|
+
? 'declares a browser binding'
|
|
941
|
+
: 'is driven by a persona'
|
|
879
942
|
logger.critical(
|
|
880
|
-
ErrorCode.
|
|
881
|
-
`Scenario '${workflowName}' calls step '${step.stepFunc}', which
|
|
882
|
-
`Pass { actor: actors.<name> } so
|
|
943
|
+
ErrorCode.SCENARIO_STEP_NEEDS_ACTOR,
|
|
944
|
+
`Scenario '${workflowName}' calls step '${step.stepFunc}', which ${why}, without an actor. ` +
|
|
945
|
+
`Pass { actor: actors.<name> } so it runs as that persona.`
|
|
883
946
|
)
|
|
884
947
|
}
|
|
885
948
|
continue
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
formatVersionedId,
|
|
4
|
-
} from '@pikku/core/ecosystem/types'
|
|
5
|
-
import type { CLICommandMeta } from '@pikku/core/ecosystem/cli'
|
|
1
|
+
import { parseVersionedId, formatVersionedId } from '@pikku/core/utils'
|
|
2
|
+
import type { CLICommandMeta } from '@pikku/core/cli'
|
|
6
3
|
import type { InspectorState, InspectorLogger } from '../types.js'
|
|
7
4
|
import { ErrorCode } from '../error-codes.js'
|
|
8
5
|
|
|
@@ -12,9 +12,9 @@ import { createGenerator, RootlessError } from 'ts-json-schema-generator'
|
|
|
12
12
|
import { register } from 'tsx/esm/api'
|
|
13
13
|
import * as z from 'zod'
|
|
14
14
|
import { zodToTs, createAuxiliaryTypeStore } from 'zod-to-ts'
|
|
15
|
-
import type { FunctionsMeta } from '@pikku/core/
|
|
16
|
-
import type { JSONValue } from '@pikku/core/
|
|
17
|
-
import type { HTTPWiringsMeta } from '@pikku/core/
|
|
15
|
+
import type { FunctionsMeta } from '@pikku/core/services'
|
|
16
|
+
import type { JSONValue } from '@pikku/core/utils'
|
|
17
|
+
import type { HTTPWiringsMeta } from '@pikku/core/http'
|
|
18
18
|
import type { TypesMap } from '../types-map.js'
|
|
19
19
|
import { ErrorCode } from '../error-codes.js'
|
|
20
20
|
import type { InspectorLogger, InspectorState, SchemaRef } from '../types.js'
|
|
@@ -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 {
|
|
4
4
|
InspectorDiagnostic,
|
|
5
5
|
InspectorFeature,
|
|
@@ -212,6 +212,7 @@ export interface SerializableInspectorState {
|
|
|
212
212
|
files: string[]
|
|
213
213
|
usage: Record<string, SecretUsage>
|
|
214
214
|
}
|
|
215
|
+
surfaceUsage: InspectorState['surfaceUsage']
|
|
215
216
|
credentials: {
|
|
216
217
|
definitions: InspectorState['credentials']['definitions']
|
|
217
218
|
files: string[]
|
|
@@ -440,6 +441,7 @@ export function serializeInspectorState(
|
|
|
440
441
|
files: Array.from(state.secrets.files),
|
|
441
442
|
usage: Object.fromEntries(state.secrets.usage),
|
|
442
443
|
},
|
|
444
|
+
surfaceUsage: state.surfaceUsage,
|
|
443
445
|
credentials: {
|
|
444
446
|
definitions: state.credentials.definitions,
|
|
445
447
|
files: Array.from(state.credentials.files),
|
|
@@ -648,6 +650,7 @@ export function deserializeInspectorState(
|
|
|
648
650
|
files: new Set(data.secrets?.files || []),
|
|
649
651
|
usage: new Map(Object.entries(data.secrets?.usage || {})),
|
|
650
652
|
},
|
|
653
|
+
surfaceUsage: data.surfaceUsage || {},
|
|
651
654
|
credentials: {
|
|
652
655
|
definitions: data.credentials?.definitions || [],
|
|
653
656
|
files: new Set(data.credentials?.files || []),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InspectorLogger, InspectorState } from '../types.js'
|
|
2
|
-
import type { JSONValue } from '@pikku/core/
|
|
2
|
+
import type { JSONValue } from '@pikku/core/utils'
|
|
3
3
|
import { resolveFunctionMeta } from './resolve-function-meta.js'
|
|
4
4
|
|
|
5
5
|
interface MCPEndpoint {
|
|
@@ -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
|
import _convertSchema from '@openapi-contrib/json-schema-to-openapi-schema'
|
|
5
5
|
const convertSchema =
|
|
@@ -2,6 +2,7 @@ import type * as ts from 'typescript'
|
|
|
2
2
|
import { getPropertyValue } from './get-property-value.js'
|
|
3
3
|
import { ErrorCode } from '../error-codes.js'
|
|
4
4
|
import type { InspectorLogger, InspectorState } from '../types.js'
|
|
5
|
+
import { resolveFunctionMeta } from './resolve-function-meta.js'
|
|
5
6
|
|
|
6
7
|
export function validateAuthSessionless(
|
|
7
8
|
logger: InspectorLogger,
|
|
@@ -11,7 +12,7 @@ export function validateAuthSessionless(
|
|
|
11
12
|
wireDescription: string,
|
|
12
13
|
inheritedAuth?: boolean
|
|
13
14
|
): boolean {
|
|
14
|
-
const fnMeta = state
|
|
15
|
+
const fnMeta = resolveFunctionMeta(state, funcName)
|
|
15
16
|
if (!fnMeta) return true
|
|
16
17
|
|
|
17
18
|
const routeAuth = getPropertyValue(obj, 'auth')
|
|
@@ -59,7 +59,10 @@ describe('validateTagsResolveToMiddleware', () => {
|
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
test('stays quiet when http middleware is registered for the tag', () => {
|
|
62
|
-
assert.deepEqual(
|
|
62
|
+
assert.deepEqual(
|
|
63
|
+
run(stateWith({ console: { tags: ['admin'] } }, ['admin'])),
|
|
64
|
+
[]
|
|
65
|
+
)
|
|
63
66
|
})
|
|
64
67
|
|
|
65
68
|
test('stays quiet when only channel middleware is registered for the tag', () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { strict as assert } from 'assert'
|
|
2
2
|
import { describe, test } from 'node:test'
|
|
3
|
-
import type { WorkflowStepMeta } from '@pikku/core/
|
|
3
|
+
import type { WorkflowStepMeta } from '@pikku/core/workflow'
|
|
4
4
|
import { deriveWorkflowPlan } from './derive-workflow-plan.js'
|
|
5
5
|
|
|
6
6
|
const rpc = (stepName: string, rpcName = 'rpc.fn'): WorkflowStepMeta =>
|
|
@@ -2,7 +2,7 @@ import { describe, test } from 'node:test'
|
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
3
|
import * as ts from 'typescript'
|
|
4
4
|
import { extractDSLWorkflow } from './extract-dsl-workflow.js'
|
|
5
|
-
import type { ApprovalStepMeta } from '@pikku/core/
|
|
5
|
+
import type { ApprovalStepMeta } from '@pikku/core/workflow'
|
|
6
6
|
|
|
7
7
|
const checker = {
|
|
8
8
|
getSymbolAtLocation: () => undefined,
|
|
@@ -20,8 +20,8 @@ import type {
|
|
|
20
20
|
Condition,
|
|
21
21
|
WorkflowContext,
|
|
22
22
|
ContextVariable,
|
|
23
|
-
} from '@pikku/core/
|
|
24
|
-
import type { ScenarioStepMeta } from '@pikku/core/
|
|
23
|
+
} from '@pikku/core/workflow'
|
|
24
|
+
import type { ScenarioStepMeta } from '@pikku/core/scenario'
|
|
25
25
|
import {
|
|
26
26
|
isWorkflowDoCall,
|
|
27
27
|
isWorkflowExpectEventuallyCall,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { test } from 'node:test'
|
|
2
2
|
import assert from 'node:assert'
|
|
3
3
|
import { convertDslToGraph } from './convert-dsl-to-graph.js'
|
|
4
|
-
import type { WorkflowsMeta } from '@pikku/core/
|
|
4
|
+
import type { WorkflowsMeta } from '@pikku/core/workflow'
|
|
5
5
|
|
|
6
6
|
const scenarioMeta = (
|
|
7
7
|
overrides: Partial<WorkflowsMeta[string]> = {}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Converts DSL (Domain Specific Language) step-based format to graph node format
|
|
3
3
|
*/
|
|
4
|
-
import type {
|
|
5
|
-
WorkflowStepMeta,
|
|
6
|
-
WorkflowsMeta,
|
|
7
|
-
} from '@pikku/core/ecosystem/workflow'
|
|
4
|
+
import type { WorkflowStepMeta, WorkflowsMeta } from '@pikku/core/workflow'
|
|
8
5
|
import type {
|
|
9
6
|
SerializedGraphNode,
|
|
10
7
|
SerializedWorkflowGraph,
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
SerializedWorkflowGraph,
|
|
4
4
|
WorkflowWires,
|
|
5
5
|
} from './workflow-graph.types.js'
|
|
6
|
-
import type { CLICommandMeta } from '@pikku/core/
|
|
6
|
+
import type { CLICommandMeta } from '@pikku/core/cli'
|
|
7
7
|
|
|
8
8
|
function parseWorkflowFuncId(
|
|
9
9
|
pikkuFuncId: string
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { FunctionsMeta } from '@pikku/core/
|
|
1
|
+
import type { FunctionsMeta } from '@pikku/core/services'
|
|
2
2
|
import {
|
|
3
3
|
isVersionedId,
|
|
4
4
|
formatVersionedId,
|
|
5
5
|
parseVersionedId,
|
|
6
|
-
} from '@pikku/core/
|
|
6
|
+
} from '@pikku/core/utils'
|
|
7
7
|
import type { SerializedWorkflowGraph } from './workflow-graph.types.js'
|
|
8
8
|
import { canonicalJSON, hashString } from '../../hash.js'
|
|
9
9
|
import { convertDslToGraph } from './convert-dsl-to-graph.js'
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow graph serialization exports
|
|
3
3
|
*/
|
|
4
|
-
export
|
|
4
|
+
export {
|
|
5
|
+
isDataRef,
|
|
6
|
+
isFunctionNode,
|
|
7
|
+
ref,
|
|
8
|
+
state,
|
|
9
|
+
} from './workflow-graph.types.js'
|
|
10
|
+
export type {
|
|
11
|
+
BranchCondition,
|
|
12
|
+
ContextVariable,
|
|
13
|
+
DataRef,
|
|
14
|
+
FlowNode,
|
|
15
|
+
ForEachMode,
|
|
16
|
+
FunctionNode,
|
|
17
|
+
NodeOptions,
|
|
18
|
+
SerializedWorkflowGraph,
|
|
19
|
+
SerializedWorkflowGraphs,
|
|
20
|
+
WorkflowContext,
|
|
21
|
+
} from './workflow-graph.types.js'
|
|
5
22
|
export { serializeWorkflowGraph } from './serialize-workflow-graph.js'
|
|
6
23
|
export { convertDslToGraph } from './convert-dsl-to-graph.js'
|
|
7
24
|
export { finalizeWorkflows } from './finalize-workflows.js'
|