@pikku/inspector 0.12.14 → 0.12.16
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 +22 -0
- package/dist/add/add-ai-agent.js +1 -1
- package/dist/add/add-channel.js +25 -7
- package/dist/add/add-functions.js +28 -13
- package/dist/add/add-gateway.js +1 -1
- package/dist/add/add-http-route.js +23 -1
- package/dist/add/add-mcp-prompt.js +1 -1
- package/dist/add/add-mcp-resource.js +1 -1
- package/dist/add/add-queue-worker.js +1 -1
- package/dist/add/add-schedule.js +1 -1
- package/dist/add/add-trigger.js +1 -1
- package/dist/add/add-workflow.js +1 -1
- package/dist/utils/check-pii-output.d.ts +9 -4
- package/dist/utils/check-pii-output.js +17 -7
- package/dist/utils/ensure-function-metadata.js +1 -1
- package/dist/utils/extract-node-value.d.ts +1 -1
- package/dist/utils/extract-node-value.js +10 -1
- package/dist/utils/get-property-value.d.ts +1 -1
- package/dist/utils/get-property-value.js +35 -9
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +20 -9
- package/package.json +1 -1
- package/src/add/add-ai-agent.ts +1 -1
- package/src/add/add-channel.ts +37 -7
- package/src/add/add-functions.ts +44 -13
- package/src/add/add-gateway.ts +1 -1
- package/src/add/add-http-route.ts +26 -1
- package/src/add/add-mcp-prompt.ts +1 -1
- package/src/add/add-mcp-resource.ts +1 -1
- package/src/add/add-queue-worker.ts +1 -1
- package/src/add/add-schedule.ts +1 -1
- package/src/add/add-trigger.ts +1 -1
- package/src/add/add-workflow.test.ts +152 -0
- package/src/add/add-workflow.ts +2 -1
- package/src/add/pii-check.test.ts +70 -28
- package/src/utils/check-pii-output.ts +27 -11
- package/src/utils/ensure-function-metadata.ts +3 -1
- package/src/utils/extract-node-value.test.ts +12 -10
- package/src/utils/extract-node-value.ts +15 -1
- package/src/utils/get-property-value.ts +33 -13
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +22 -9
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import * as ts from 'typescript'
|
|
2
2
|
|
|
3
|
+
export type ClassifiedField = {
|
|
4
|
+
path: string
|
|
5
|
+
classification: 'private' | 'pii' | 'secret' | string
|
|
6
|
+
}
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* Recursively walks a resolved TypeScript type looking for `__classification__` brands —
|
|
5
|
-
* the structural marker emitted by `Private<T
|
|
10
|
+
* the structural marker emitted by `Private<T>`, `Pii<T>`, and `Secret<T>`.
|
|
6
11
|
*
|
|
7
12
|
* `Private<T> = T & { readonly __classification__: 'private' }` shows up in the TS type
|
|
8
13
|
* system as an intersection whose constituents include a type with a `__classification__`
|
|
9
14
|
* property. We detect that by checking whether any constituent of an
|
|
10
15
|
* intersection exposes a property named `__classification__`.
|
|
11
16
|
*
|
|
12
|
-
* Returns the list of
|
|
13
|
-
* (e.g. `['email', '
|
|
17
|
+
* Returns the list of classified fields found, each with its dotted path and
|
|
18
|
+
* classification level (e.g. `[{ path: 'email', classification: 'private' }]`).
|
|
19
|
+
* An empty array means clean.
|
|
14
20
|
*/
|
|
15
21
|
export function findPiiPaths(
|
|
16
22
|
checker: ts.TypeChecker,
|
|
@@ -18,23 +24,33 @@ export function findPiiPaths(
|
|
|
18
24
|
path = '',
|
|
19
25
|
depth = 0,
|
|
20
26
|
seen = new Set<ts.Type>()
|
|
21
|
-
):
|
|
27
|
+
): ClassifiedField[] {
|
|
22
28
|
if (depth > 8 || seen.has(type)) return []
|
|
23
29
|
seen.add(type)
|
|
24
30
|
|
|
25
31
|
// ── Is this type itself branded? ─────────────────────────────────────────
|
|
26
32
|
// Private<T> = T & { readonly __classification__: 'private' } → isIntersection()
|
|
27
|
-
// where one constituent has a `__classification__` property.
|
|
33
|
+
// where one constituent has a `__classification__` property whose type is a string literal.
|
|
28
34
|
if (type.isIntersection()) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
for (const t of type.types) {
|
|
36
|
+
const classificationProp = t
|
|
37
|
+
.getProperties()
|
|
38
|
+
.find((p) => p.name === '__classification__')
|
|
39
|
+
if (classificationProp) {
|
|
40
|
+
const decl =
|
|
41
|
+
classificationProp.valueDeclaration ??
|
|
42
|
+
classificationProp.declarations?.[0]
|
|
43
|
+
const classification = decl
|
|
44
|
+
? ((
|
|
45
|
+
checker.getTypeOfSymbolAtLocation(classificationProp, decl) as any
|
|
46
|
+
)?.value ?? 'private')
|
|
47
|
+
: 'private'
|
|
48
|
+
return [{ path: path || '<return value>', classification }]
|
|
49
|
+
}
|
|
34
50
|
}
|
|
35
51
|
}
|
|
36
52
|
|
|
37
|
-
const violations:
|
|
53
|
+
const violations: ClassifiedField[] = []
|
|
38
54
|
|
|
39
55
|
// ── Union: check every branch ─────────────────────────────────────────────
|
|
40
56
|
if (type.isUnion()) {
|
|
@@ -280,7 +280,9 @@ export function ensureFunctionMetadata(
|
|
|
280
280
|
const { tags } = getCommonWireMetaData(
|
|
281
281
|
firstArg,
|
|
282
282
|
'Function',
|
|
283
|
-
fallbackName || pikkuFuncId
|
|
283
|
+
fallbackName || pikkuFuncId,
|
|
284
|
+
undefined,
|
|
285
|
+
checker
|
|
284
286
|
)
|
|
285
287
|
if (tags) {
|
|
286
288
|
meta.tags = tags
|
|
@@ -46,18 +46,20 @@ describe('extractDescription', () => {
|
|
|
46
46
|
assert.equal(extractDescription(obj, checker), 'my step')
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
test('
|
|
49
|
+
test('extracts concatenated string literals in description', () => {
|
|
50
50
|
const { checker, sourceFile } = createChecker(
|
|
51
|
-
`const
|
|
51
|
+
`const data = { description: 'line one ' + 'line two' }`
|
|
52
52
|
)
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
const obj = findObjectLiteral(sourceFile)!
|
|
54
|
+
assert.equal(extractDescription(obj, checker), 'line one line two')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('extracts deeply nested concatenation in description', () => {
|
|
58
|
+
const { checker, sourceFile } = createChecker(
|
|
59
|
+
`const data = { description: 'a' + 'b' + 'c' }`
|
|
60
|
+
)
|
|
61
|
+
const obj = findObjectLiteral(sourceFile)!
|
|
62
|
+
assert.equal(extractDescription(obj, checker), 'abc')
|
|
61
63
|
})
|
|
62
64
|
|
|
63
65
|
test('returns null for non-object node', () => {
|
|
@@ -32,6 +32,16 @@ export function extractStringLiteral(
|
|
|
32
32
|
return extractStringLiteral(node.expression, checker)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
if (
|
|
36
|
+
ts.isBinaryExpression(node) &&
|
|
37
|
+
node.operatorToken.kind === ts.SyntaxKind.PlusToken
|
|
38
|
+
) {
|
|
39
|
+
return (
|
|
40
|
+
extractStringLiteral(node.left, checker) +
|
|
41
|
+
extractStringLiteral(node.right, checker)
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
// Try to evaluate constant identifiers
|
|
36
46
|
if (ts.isIdentifier(node)) {
|
|
37
47
|
const symbol = checker.getSymbolAtLocation(node)
|
|
@@ -52,7 +62,7 @@ export function extractStringLiteral(
|
|
|
52
62
|
/**
|
|
53
63
|
* Check if node is string-like (string literal or template expression)
|
|
54
64
|
*/
|
|
55
|
-
export function isStringLike(node: ts.Node,
|
|
65
|
+
export function isStringLike(node: ts.Node, checker: ts.TypeChecker): boolean {
|
|
56
66
|
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
57
67
|
return true
|
|
58
68
|
}
|
|
@@ -60,6 +70,10 @@ export function isStringLike(node: ts.Node, _checker: ts.TypeChecker): boolean {
|
|
|
60
70
|
if (ts.isTemplateExpression(node)) {
|
|
61
71
|
return true
|
|
62
72
|
}
|
|
73
|
+
// Unwrap type assertions: `expr as Type` or `<Type>expr`
|
|
74
|
+
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
75
|
+
return isStringLike(node.expression, checker)
|
|
76
|
+
}
|
|
63
77
|
return false
|
|
64
78
|
}
|
|
65
79
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as ts from 'typescript'
|
|
2
2
|
import { ErrorCode } from '../error-codes.js'
|
|
3
|
+
import { extractStringLiteral } from './extract-node-value.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Extracts an array of strings from an object property.
|
|
@@ -137,7 +138,8 @@ export const getCommonWireMetaData = (
|
|
|
137
138
|
obj: ts.ObjectLiteralExpression,
|
|
138
139
|
wiringType: string,
|
|
139
140
|
wiringName: string | null,
|
|
140
|
-
logger?: { critical: (code: ErrorCode, message: string) => void }
|
|
141
|
+
logger?: { critical: (code: ErrorCode, message: string) => void },
|
|
142
|
+
checker?: ts.TypeChecker
|
|
141
143
|
): {
|
|
142
144
|
disabled?: true
|
|
143
145
|
title?: string
|
|
@@ -166,18 +168,36 @@ export const getCommonWireMetaData = (
|
|
|
166
168
|
prop.initializer.kind === ts.SyntaxKind.TrueKeyword
|
|
167
169
|
) {
|
|
168
170
|
metadata.disabled = true
|
|
169
|
-
} else if (propName === 'title'
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
) {
|
|
180
|
-
|
|
171
|
+
} else if (propName === 'title') {
|
|
172
|
+
try {
|
|
173
|
+
metadata.title = checker
|
|
174
|
+
? extractStringLiteral(prop.initializer, checker)
|
|
175
|
+
: ts.isStringLiteral(prop.initializer)
|
|
176
|
+
? prop.initializer.text
|
|
177
|
+
: undefined
|
|
178
|
+
} catch {
|
|
179
|
+
// non-static title — skip
|
|
180
|
+
}
|
|
181
|
+
} else if (propName === 'summary') {
|
|
182
|
+
try {
|
|
183
|
+
metadata.summary = checker
|
|
184
|
+
? extractStringLiteral(prop.initializer, checker)
|
|
185
|
+
: ts.isStringLiteral(prop.initializer)
|
|
186
|
+
? prop.initializer.text
|
|
187
|
+
: undefined
|
|
188
|
+
} catch {
|
|
189
|
+
// non-static summary — skip
|
|
190
|
+
}
|
|
191
|
+
} else if (propName === 'description') {
|
|
192
|
+
try {
|
|
193
|
+
metadata.description = checker
|
|
194
|
+
? extractStringLiteral(prop.initializer, checker)
|
|
195
|
+
: ts.isStringLiteral(prop.initializer)
|
|
196
|
+
? prop.initializer.text
|
|
197
|
+
: undefined
|
|
198
|
+
} catch {
|
|
199
|
+
// non-static description — skip
|
|
200
|
+
}
|
|
181
201
|
} else if (propName === 'tags') {
|
|
182
202
|
if (ts.isArrayLiteralExpression(prop.initializer)) {
|
|
183
203
|
metadata.tags = prop.initializer.elements
|
|
@@ -436,21 +436,34 @@ function extractExpressionStatement(
|
|
|
436
436
|
outputVar = expr.left.text
|
|
437
437
|
|
|
438
438
|
// Check if this is an assignment to a context variable (set step)
|
|
439
|
+
// But if the RHS is a workflow.do() call, fall through to RPC extraction —
|
|
440
|
+
// reassigning a pre-declared variable with a workflow step is valid and common.
|
|
439
441
|
if (context.contextVars.has(outputVar)) {
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
+
const rhs = expr.right
|
|
443
|
+
const rhsCall =
|
|
444
|
+
ts.isAwaitExpression(rhs) && ts.isCallExpression(rhs.expression)
|
|
445
|
+
? rhs.expression
|
|
446
|
+
: null
|
|
447
|
+
const isWorkflowCall = rhsCall
|
|
448
|
+
? isWorkflowDoCall(rhsCall, context.checker)
|
|
449
|
+
: false
|
|
450
|
+
|
|
451
|
+
if (!isWorkflowCall) {
|
|
452
|
+
const literalValue = extractLiteralValue(expr.right)
|
|
453
|
+
if (literalValue !== undefined) {
|
|
454
|
+
return {
|
|
455
|
+
type: 'set',
|
|
456
|
+
variable: outputVar,
|
|
457
|
+
value: literalValue,
|
|
458
|
+
} as SetStepMeta
|
|
459
|
+
}
|
|
460
|
+
// Non-literal assignment to context var - use expression as string
|
|
442
461
|
return {
|
|
443
462
|
type: 'set',
|
|
444
463
|
variable: outputVar,
|
|
445
|
-
value:
|
|
464
|
+
value: getSourceText(expr.right),
|
|
446
465
|
} as SetStepMeta
|
|
447
466
|
}
|
|
448
|
-
// Non-literal assignment to context var - use expression as string
|
|
449
|
-
return {
|
|
450
|
-
type: 'set',
|
|
451
|
-
variable: outputVar,
|
|
452
|
-
value: getSourceText(expr.right),
|
|
453
|
-
} as SetStepMeta
|
|
454
467
|
}
|
|
455
468
|
}
|
|
456
469
|
// Use right side as the expression to extract from
|