@pikku/inspector 0.12.3 → 0.12.5
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 +42 -0
- package/dist/add/add-credential.d.ts +2 -0
- package/dist/add/add-credential.js +118 -0
- package/dist/add/add-middleware.js +6 -10
- package/dist/add/add-permission.js +10 -12
- package/dist/add/add-secret.d.ts +1 -3
- package/dist/add/add-secret.js +0 -74
- package/dist/add/add-workflow.js +7 -1
- package/dist/error-codes.d.ts +1 -0
- package/dist/error-codes.js +2 -0
- package/dist/inspector.js +18 -7
- package/dist/types.d.ts +7 -0
- package/dist/utils/custom-types-generator.js +1 -0
- package/dist/utils/post-process.d.ts +9 -0
- package/dist/utils/post-process.js +46 -0
- package/dist/utils/schema-generator.js +26 -6
- package/dist/utils/serialize-inspector-state.d.ts +4 -0
- package/dist/utils/serialize-inspector-state.js +9 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +1 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +2 -0
- package/dist/visit.js +3 -2
- package/package.json +4 -3
- package/src/add/add-credential.ts +178 -0
- package/src/add/add-middleware.ts +6 -14
- package/src/add/add-permission.ts +10 -16
- package/src/add/add-secret.ts +0 -131
- package/src/add/add-workflow.ts +11 -1
- package/src/error-codes.ts +3 -0
- package/src/inspector.ts +25 -6
- package/src/types.ts +7 -0
- package/src/utils/custom-types-generator.ts +1 -0
- package/src/utils/post-process.ts +59 -0
- package/src/utils/schema-generator.ts +38 -10
- package/src/utils/serialize-inspector-state.ts +13 -0
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +1 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +2 -0
- package/src/visit.ts +3 -2
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
## 0.12.0
|
|
2
2
|
|
|
3
|
+
## 0.12.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 65eccc6: Cache Zod schema generation between re-inspection passes and batch imports by source file. Schemas are cached using a fingerprint of schemaLookup entries + file mtimes, so reinspections skip Zod generation entirely when schemas haven't changed. Source file imports are grouped so each file is imported once instead of per-schema. Reduces `pikku all` from ~5 minutes to ~13 seconds on projects with many Zod schemas.
|
|
8
|
+
- 0f59432: Add per-user credential system with CredentialService, OAuth2 route handlers, and KyselyCredentialService with envelope encryption
|
|
9
|
+
- Updated dependencies [0f59432]
|
|
10
|
+
- Updated dependencies [52b64d1]
|
|
11
|
+
- @pikku/core@0.12.10
|
|
12
|
+
|
|
13
|
+
## 0.12.4
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 5866b66: Add critical error (PKU490) when Zod schemas and wiring calls (wireHTTPRoutes, addPermission, addHTTPMiddleware) coexist in the same file. The CLI uses tsImport to extract Zod schemas at runtime, which executes all top-level code — wiring side-effects crash in this context because pikku state metadata doesn't exist. Schemas and wirings must be in separate files.
|
|
18
|
+
- e412b4d: Optimize CLI codegen performance: 12x faster `pikku all`
|
|
19
|
+
|
|
20
|
+
- Reuse schemas across re-inspections (skip redundant `ts-json-schema-generator` runs)
|
|
21
|
+
- Cache TS schemas to disk (`.pikku/schema-cache.json`) for cross-run reuse
|
|
22
|
+
- Pass `oldProgram` to `ts.createProgram` for incremental TS compilation
|
|
23
|
+
- Cache parsed tsconfig in schema generator between runs
|
|
24
|
+
- Auto-include direct `addPermission`/`addHTTPMiddleware` in bootstrap via side-effect imports
|
|
25
|
+
- Skip `pikkuAuth()` errors when nested inside `addPermission`/`addHTTPPermission`
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [e412b4d]
|
|
28
|
+
- Updated dependencies [53dc8c8]
|
|
29
|
+
- Updated dependencies [0a1cc51]
|
|
30
|
+
- Updated dependencies [0a1cc51]
|
|
31
|
+
- Updated dependencies [0a1cc51]
|
|
32
|
+
- Updated dependencies [0a1cc51]
|
|
33
|
+
- Updated dependencies [0a1cc51]
|
|
34
|
+
- Updated dependencies [0a1cc51]
|
|
35
|
+
- Updated dependencies [0a1cc51]
|
|
36
|
+
- Updated dependencies [0a1cc51]
|
|
37
|
+
- Updated dependencies [0a1cc51]
|
|
38
|
+
- Updated dependencies [8b9b2e9]
|
|
39
|
+
- Updated dependencies [8b9b2e9]
|
|
40
|
+
- Updated dependencies [b973d44]
|
|
41
|
+
- Updated dependencies [8b9b2e9]
|
|
42
|
+
- Updated dependencies [8b9b2e9]
|
|
43
|
+
- @pikku/core@0.12.9
|
|
44
|
+
|
|
3
45
|
## 0.12.3
|
|
4
46
|
|
|
5
47
|
### Patch Changes
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { getPropertyValue, getArrayPropertyValue, } from '../utils/get-property-value.js';
|
|
3
|
+
import { ErrorCode } from '../error-codes.js';
|
|
4
|
+
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
|
|
5
|
+
export const addCredential = (logger, node, checker, state, _options) => {
|
|
6
|
+
if (!ts.isCallExpression(node)) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const args = node.arguments;
|
|
10
|
+
const firstArg = args[0];
|
|
11
|
+
const expression = node.expression;
|
|
12
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireCredential') {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (!firstArg) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
19
|
+
const obj = firstArg;
|
|
20
|
+
const nameValue = getPropertyValue(obj, 'name');
|
|
21
|
+
const displayNameValue = getPropertyValue(obj, 'displayName');
|
|
22
|
+
const descriptionValue = getPropertyValue(obj, 'description');
|
|
23
|
+
const typeValue = getPropertyValue(obj, 'type');
|
|
24
|
+
if (!nameValue) {
|
|
25
|
+
logger.critical(ErrorCode.MISSING_NAME, "Credential is missing the required 'name' property.");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!displayNameValue) {
|
|
29
|
+
logger.critical(ErrorCode.MISSING_NAME, `Credential '${nameValue}' is missing the required 'displayName' property.`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!typeValue || (typeValue !== 'singleton' && typeValue !== 'wire')) {
|
|
33
|
+
logger.critical(ErrorCode.MISSING_NAME, `Credential '${nameValue}' is missing or has invalid 'type' property. Must be 'singleton' or 'wire'.`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let schemaVariableName = null;
|
|
37
|
+
let schemaSourceFile = null;
|
|
38
|
+
let schemaIdentifier = null;
|
|
39
|
+
for (const prop of obj.properties) {
|
|
40
|
+
if (ts.isPropertyAssignment(prop) &&
|
|
41
|
+
ts.isIdentifier(prop.name) &&
|
|
42
|
+
prop.name.text === 'schema') {
|
|
43
|
+
if (ts.isIdentifier(prop.initializer)) {
|
|
44
|
+
schemaVariableName = prop.initializer.text;
|
|
45
|
+
schemaIdentifier = prop.initializer;
|
|
46
|
+
const symbol = checker.getSymbolAtLocation(prop.initializer);
|
|
47
|
+
if (symbol) {
|
|
48
|
+
const decl = symbol.valueDeclaration || symbol.declarations?.[0];
|
|
49
|
+
if (decl) {
|
|
50
|
+
if (ts.isImportSpecifier(decl)) {
|
|
51
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol);
|
|
52
|
+
if (aliasedSymbol) {
|
|
53
|
+
const aliasedDecl = aliasedSymbol.valueDeclaration ||
|
|
54
|
+
aliasedSymbol.declarations?.[0];
|
|
55
|
+
if (aliasedDecl) {
|
|
56
|
+
schemaSourceFile = aliasedDecl.getSourceFile().fileName;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
schemaSourceFile = decl.getSourceFile().fileName;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
let oauth2 = undefined;
|
|
70
|
+
const oauth2Prop = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
71
|
+
ts.isIdentifier(p.name) &&
|
|
72
|
+
p.name.text === 'oauth2');
|
|
73
|
+
if (oauth2Prop &&
|
|
74
|
+
ts.isPropertyAssignment(oauth2Prop) &&
|
|
75
|
+
ts.isObjectLiteralExpression(oauth2Prop.initializer)) {
|
|
76
|
+
const oauth2Obj = oauth2Prop.initializer;
|
|
77
|
+
const appCredentialSecretId = getPropertyValue(oauth2Obj, 'appCredentialSecretId');
|
|
78
|
+
const tokenSecretId = getPropertyValue(oauth2Obj, 'tokenSecretId');
|
|
79
|
+
const authorizationUrl = getPropertyValue(oauth2Obj, 'authorizationUrl');
|
|
80
|
+
const tokenUrl = getPropertyValue(oauth2Obj, 'tokenUrl');
|
|
81
|
+
const scopes = getArrayPropertyValue(oauth2Obj, 'scopes');
|
|
82
|
+
const pkce = getPropertyValue(oauth2Obj, 'pkce');
|
|
83
|
+
if (appCredentialSecretId && authorizationUrl && tokenUrl && scopes) {
|
|
84
|
+
oauth2 = {
|
|
85
|
+
appCredentialSecretId,
|
|
86
|
+
tokenSecretId: tokenSecretId || undefined,
|
|
87
|
+
authorizationUrl,
|
|
88
|
+
tokenUrl,
|
|
89
|
+
scopes,
|
|
90
|
+
pkce: pkce || undefined,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const sourceFile = node.getSourceFile().fileName;
|
|
95
|
+
state.credentials.files.add(sourceFile);
|
|
96
|
+
let schemaLookupName;
|
|
97
|
+
if (schemaVariableName && schemaSourceFile && schemaIdentifier) {
|
|
98
|
+
const vendor = detectSchemaVendorOrError(schemaIdentifier, checker, logger, `Credential '${nameValue}'`, schemaSourceFile);
|
|
99
|
+
if (vendor) {
|
|
100
|
+
schemaLookupName = `CredentialSchema_${nameValue}`;
|
|
101
|
+
state.schemaLookup.set(schemaLookupName, {
|
|
102
|
+
variableName: schemaVariableName,
|
|
103
|
+
sourceFile: schemaSourceFile,
|
|
104
|
+
vendor,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
state.credentials.definitions.push({
|
|
109
|
+
name: nameValue,
|
|
110
|
+
displayName: displayNameValue,
|
|
111
|
+
description: descriptionValue || undefined,
|
|
112
|
+
type: typeValue,
|
|
113
|
+
schema: schemaLookupName,
|
|
114
|
+
oauth2,
|
|
115
|
+
sourceFile,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
@@ -189,12 +189,10 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
189
189
|
return;
|
|
190
190
|
}
|
|
191
191
|
const refs = extractMiddlewareRefs(middlewareArrayArg, checker, state.rootDir);
|
|
192
|
-
if (refs.length === 0) {
|
|
193
|
-
logger.warn(`• addMiddleware('${tag}', ...) has empty middleware array`);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
192
|
const definitionIds = refs.map((r) => r.definitionId);
|
|
197
|
-
|
|
193
|
+
if (definitionIds.length > 0) {
|
|
194
|
+
renameTempDefinitions(state, definitionIds, 'tag', tag);
|
|
195
|
+
}
|
|
198
196
|
const sourceFile = node.getSourceFile().fileName;
|
|
199
197
|
const instanceIds = [];
|
|
200
198
|
for (let i = 0; i < refs.length; i++) {
|
|
@@ -273,12 +271,10 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
273
271
|
return;
|
|
274
272
|
}
|
|
275
273
|
const refs = extractMiddlewareRefs(middlewareArrayArg, checker, state.rootDir);
|
|
276
|
-
if (refs.length === 0) {
|
|
277
|
-
logger.warn(`• addHTTPMiddleware('${pattern}', ...) has empty middleware array`);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
274
|
const definitionIds = refs.map((r) => r.definitionId);
|
|
281
|
-
|
|
275
|
+
if (definitionIds.length > 0) {
|
|
276
|
+
renameTempDefinitions(state, definitionIds, 'http', pattern);
|
|
277
|
+
}
|
|
282
278
|
const sourceFile = node.getSourceFile().fileName;
|
|
283
279
|
const instanceIds = [];
|
|
284
280
|
for (let i = 0; i < refs.length; i++) {
|
|
@@ -21,12 +21,14 @@ function renameTempDefinitions(state, definitionIds, groupType, groupKey) {
|
|
|
21
21
|
definitionIds[idx] = newId;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
function
|
|
24
|
+
function isInsidePermissionContainer(node) {
|
|
25
25
|
let current = node.parent;
|
|
26
26
|
while (current) {
|
|
27
27
|
if (ts.isCallExpression(current) &&
|
|
28
28
|
ts.isIdentifier(current.expression) &&
|
|
29
|
-
current.expression.text === 'pikkuPermissionFactory'
|
|
29
|
+
(current.expression.text === 'pikkuPermissionFactory' ||
|
|
30
|
+
current.expression.text === 'addPermission' ||
|
|
31
|
+
current.expression.text === 'addHTTPPermission')) {
|
|
30
32
|
return true;
|
|
31
33
|
}
|
|
32
34
|
current = current.parent;
|
|
@@ -47,7 +49,7 @@ export const addPermission = (logger, node, checker, state) => {
|
|
|
47
49
|
// Handle pikkuPermission(...) - individual permission function definition
|
|
48
50
|
if (expression.text === 'pikkuPermission') {
|
|
49
51
|
// Skip if nested inside pikkuPermissionFactory — the factory handler extracts services itself
|
|
50
|
-
if (
|
|
52
|
+
if (isInsidePermissionContainer(node))
|
|
51
53
|
return;
|
|
52
54
|
const arg = args[0];
|
|
53
55
|
if (!arg)
|
|
@@ -110,7 +112,7 @@ export const addPermission = (logger, node, checker, state) => {
|
|
|
110
112
|
return;
|
|
111
113
|
}
|
|
112
114
|
if (expression.text === 'pikkuAuth') {
|
|
113
|
-
if (
|
|
115
|
+
if (isInsidePermissionContainer(node))
|
|
114
116
|
return;
|
|
115
117
|
const arg = args[0];
|
|
116
118
|
if (!arg)
|
|
@@ -269,11 +271,9 @@ export const addPermission = (logger, node, checker, state) => {
|
|
|
269
271
|
}
|
|
270
272
|
// Extract permission pikkuFuncIds from array
|
|
271
273
|
const permissionNames = extractPermissionPikkuNames(permissionsArrayArg, checker, state.rootDir);
|
|
272
|
-
if (permissionNames.length
|
|
273
|
-
|
|
274
|
-
return;
|
|
274
|
+
if (permissionNames.length > 0) {
|
|
275
|
+
renameTempDefinitions(state, permissionNames, 'tag', tag);
|
|
275
276
|
}
|
|
276
|
-
renameTempDefinitions(state, permissionNames, 'tag', tag);
|
|
277
277
|
const allServices = new Set();
|
|
278
278
|
for (const permissionName of permissionNames) {
|
|
279
279
|
const permissionMeta = state.permissions.definitions[permissionName];
|
|
@@ -348,11 +348,9 @@ export const addPermission = (logger, node, checker, state) => {
|
|
|
348
348
|
}
|
|
349
349
|
// Extract permission pikkuFuncIds from array
|
|
350
350
|
const permissionNames = extractPermissionPikkuNames(permissionsArrayArg, checker, state.rootDir);
|
|
351
|
-
if (permissionNames.length
|
|
352
|
-
|
|
353
|
-
return;
|
|
351
|
+
if (permissionNames.length > 0) {
|
|
352
|
+
renameTempDefinitions(state, permissionNames, 'http', pattern);
|
|
354
353
|
}
|
|
355
|
-
renameTempDefinitions(state, permissionNames, 'http', pattern);
|
|
356
354
|
const allServices = new Set();
|
|
357
355
|
for (const permissionName of permissionNames) {
|
|
358
356
|
const permissionMeta = state.permissions.definitions[permissionName];
|
package/dist/add/add-secret.d.ts
CHANGED
package/dist/add/add-secret.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import * as ts from 'typescript';
|
|
2
|
-
import { getPropertyValue, getArrayPropertyValue, } from '../utils/get-property-value.js';
|
|
3
|
-
import { ErrorCode } from '../error-codes.js';
|
|
4
1
|
import { createAddKeyedWiring } from './add-keyed-wiring.js';
|
|
5
2
|
export const addSecret = createAddKeyedWiring({
|
|
6
3
|
functionName: 'wireSecret',
|
|
@@ -9,74 +6,3 @@ export const addSecret = createAddKeyedWiring({
|
|
|
9
6
|
schemaPrefix: 'SecretSchema',
|
|
10
7
|
getState: (state) => state.secrets,
|
|
11
8
|
});
|
|
12
|
-
export const addOAuth2Credential = (logger, node, _checker, state, _options) => {
|
|
13
|
-
if (!ts.isCallExpression(node)) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
const args = node.arguments;
|
|
17
|
-
const firstArg = args[0];
|
|
18
|
-
const expression = node.expression;
|
|
19
|
-
if (!ts.isIdentifier(expression) ||
|
|
20
|
-
expression.text !== 'wireOAuth2Credential') {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
if (!firstArg) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
27
|
-
const obj = firstArg;
|
|
28
|
-
const nameValue = getPropertyValue(obj, 'name');
|
|
29
|
-
const displayNameValue = getPropertyValue(obj, 'displayName');
|
|
30
|
-
const descriptionValue = getPropertyValue(obj, 'description');
|
|
31
|
-
const secretIdValue = getPropertyValue(obj, 'secretId');
|
|
32
|
-
const tokenSecretIdValue = getPropertyValue(obj, 'tokenSecretId');
|
|
33
|
-
const authorizationUrlValue = getPropertyValue(obj, 'authorizationUrl');
|
|
34
|
-
const tokenUrlValue = getPropertyValue(obj, 'tokenUrl');
|
|
35
|
-
const scopesValue = getArrayPropertyValue(obj, 'scopes');
|
|
36
|
-
const pkceValue = getPropertyValue(obj, 'pkce');
|
|
37
|
-
if (!nameValue) {
|
|
38
|
-
logger.critical(ErrorCode.MISSING_NAME, "OAuth2 Credential is missing the required 'name' property.");
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (!displayNameValue) {
|
|
42
|
-
logger.critical(ErrorCode.MISSING_NAME, `OAuth2 Credential '${nameValue}' is missing the required 'displayName' property.`);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (!secretIdValue) {
|
|
46
|
-
logger.critical(ErrorCode.MISSING_NAME, `OAuth2 Credential '${nameValue}' is missing the required 'secretId' property.`);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (!tokenSecretIdValue) {
|
|
50
|
-
logger.critical(ErrorCode.MISSING_NAME, `OAuth2 Credential '${nameValue}' is missing the required 'tokenSecretId' property.`);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
if (!authorizationUrlValue) {
|
|
54
|
-
logger.critical(ErrorCode.MISSING_NAME, `OAuth2 Credential '${nameValue}' is missing the required 'authorizationUrl' property.`);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
if (!tokenUrlValue) {
|
|
58
|
-
logger.critical(ErrorCode.MISSING_NAME, `OAuth2 Credential '${nameValue}' is missing the required 'tokenUrl' property.`);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (!scopesValue || scopesValue.length === 0) {
|
|
62
|
-
logger.critical(ErrorCode.MISSING_NAME, `OAuth2 Credential '${nameValue}' is missing the required 'scopes' property.`);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const sourceFile = node.getSourceFile().fileName;
|
|
66
|
-
state.secrets.files.add(sourceFile);
|
|
67
|
-
state.secrets.definitions.push({
|
|
68
|
-
name: nameValue,
|
|
69
|
-
displayName: displayNameValue,
|
|
70
|
-
description: descriptionValue || undefined,
|
|
71
|
-
secretId: secretIdValue,
|
|
72
|
-
oauth2: {
|
|
73
|
-
tokenSecretId: tokenSecretIdValue,
|
|
74
|
-
authorizationUrl: authorizationUrlValue,
|
|
75
|
-
tokenUrl: tokenUrlValue,
|
|
76
|
-
scopes: scopesValue,
|
|
77
|
-
pkce: pkceValue || undefined,
|
|
78
|
-
},
|
|
79
|
-
sourceFile,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
};
|
package/dist/add/add-workflow.js
CHANGED
|
@@ -3,7 +3,7 @@ import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
|
3
3
|
import { extractFunctionNode } from '../utils/extract-function-node.js';
|
|
4
4
|
import { ErrorCode } from '../error-codes.js';
|
|
5
5
|
import { extractStringLiteral, isStringLike, isFunctionLike, extractDescription, extractDuration, } from '../utils/extract-node-value.js';
|
|
6
|
-
import { getCommonWireMetaData } from '../utils/get-property-value.js';
|
|
6
|
+
import { getCommonWireMetaData, getPropertyValue, } from '../utils/get-property-value.js';
|
|
7
7
|
import { extractDSLWorkflow } from '../utils/workflow/dsl/extract-dsl-workflow.js';
|
|
8
8
|
/**
|
|
9
9
|
* Recursively check if any step has inline type (non-serializable)
|
|
@@ -182,6 +182,7 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
182
182
|
let summary;
|
|
183
183
|
let description;
|
|
184
184
|
let errors;
|
|
185
|
+
let inline;
|
|
185
186
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
186
187
|
const metadata = getCommonWireMetaData(firstArg, 'Workflow', workflowName, logger);
|
|
187
188
|
if (metadata.disabled)
|
|
@@ -190,6 +191,10 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
190
191
|
summary = metadata.summary;
|
|
191
192
|
description = metadata.description;
|
|
192
193
|
errors = metadata.errors;
|
|
194
|
+
const inlineProp = getPropertyValue(firstArg, 'inline');
|
|
195
|
+
if (inlineProp === true) {
|
|
196
|
+
inline = true;
|
|
197
|
+
}
|
|
193
198
|
}
|
|
194
199
|
// Validate that we got a valid function
|
|
195
200
|
if (ts.isObjectLiteralExpression(firstArg) &&
|
|
@@ -272,5 +277,6 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
272
277
|
description,
|
|
273
278
|
errors,
|
|
274
279
|
tags,
|
|
280
|
+
inline,
|
|
275
281
|
};
|
|
276
282
|
};
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export declare enum ErrorCode {
|
|
|
49
49
|
MANIFEST_INTEGRITY_ERROR = "PKU865",
|
|
50
50
|
MISSING_MODEL = "PKU145",
|
|
51
51
|
INVALID_MODEL = "PKU146",
|
|
52
|
+
SCHEMA_AND_WIRING_COLOCATED = "PKU490",
|
|
52
53
|
SERVICES_NOT_DESTRUCTURED = "PKU410",
|
|
53
54
|
WIRES_NOT_DESTRUCTURED = "PKU411",
|
|
54
55
|
WORKFLOW_MULTI_QUEUE_NOT_SUPPORTED = "PKU901"
|
package/dist/error-codes.js
CHANGED
|
@@ -58,6 +58,8 @@ export var ErrorCode;
|
|
|
58
58
|
// Model configuration errors
|
|
59
59
|
ErrorCode["MISSING_MODEL"] = "PKU145";
|
|
60
60
|
ErrorCode["INVALID_MODEL"] = "PKU146";
|
|
61
|
+
// File structure errors
|
|
62
|
+
ErrorCode["SCHEMA_AND_WIRING_COLOCATED"] = "PKU490";
|
|
61
63
|
// Optimization diagnostics
|
|
62
64
|
ErrorCode["SERVICES_NOT_DESTRUCTURED"] = "PKU410";
|
|
63
65
|
ErrorCode["WIRES_NOT_DESTRUCTURED"] = "PKU411";
|
package/dist/inspector.js
CHANGED
|
@@ -4,7 +4,7 @@ import { visitSetup, visitRoutes } from './visit.js';
|
|
|
4
4
|
import { TypesMap } from './types-map.js';
|
|
5
5
|
import { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
6
6
|
import { findCommonAncestor } from './utils/find-root-dir.js';
|
|
7
|
-
import { aggregateRequiredServices, validateAgentModels, validateAgentOverrides, validateSecretOverrides, validateVariableOverrides, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, } from './utils/post-process.js';
|
|
7
|
+
import { aggregateRequiredServices, validateAgentModels, validateAgentOverrides, validateSecretOverrides, validateVariableOverrides, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, validateSchemaWiringSeparation, } from './utils/post-process.js';
|
|
8
8
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
|
|
9
9
|
import { pikkuState } from '@pikku/core/internal';
|
|
10
10
|
import { resolveLatestVersions } from './utils/resolve-versions.js';
|
|
@@ -118,6 +118,10 @@ export function getInitialInspectorState(rootDir) {
|
|
|
118
118
|
definitions: [],
|
|
119
119
|
files: new Set(),
|
|
120
120
|
},
|
|
121
|
+
credentials: {
|
|
122
|
+
definitions: [],
|
|
123
|
+
files: new Set(),
|
|
124
|
+
},
|
|
121
125
|
variables: {
|
|
122
126
|
definitions: [],
|
|
123
127
|
files: new Set(),
|
|
@@ -174,11 +178,11 @@ export function getInitialInspectorState(rootDir) {
|
|
|
174
178
|
openAPISpec: null,
|
|
175
179
|
diagnostics: [],
|
|
176
180
|
addonFunctions: {},
|
|
181
|
+
program: null,
|
|
177
182
|
};
|
|
178
183
|
}
|
|
179
184
|
export const inspect = async (logger, routeFiles, options = {}) => {
|
|
180
|
-
const
|
|
181
|
-
const program = ts.createProgram(routeFiles, {
|
|
185
|
+
const compilerOptions = {
|
|
182
186
|
target: ts.ScriptTarget.ESNext,
|
|
183
187
|
module: ts.ModuleKind.Node16,
|
|
184
188
|
skipLibCheck: true,
|
|
@@ -187,8 +191,11 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
187
191
|
types: [],
|
|
188
192
|
allowJs: false,
|
|
189
193
|
checkJs: false,
|
|
190
|
-
}
|
|
191
|
-
|
|
194
|
+
};
|
|
195
|
+
const startProgram = performance.now();
|
|
196
|
+
const program = ts.createProgram(routeFiles, compilerOptions, undefined, // host
|
|
197
|
+
options.oldProgram);
|
|
198
|
+
logger.debug(`Created program in ${(performance.now() - startProgram).toFixed(0)}ms (${routeFiles.length} files${options.oldProgram ? ', incremental' : ''})`);
|
|
192
199
|
const startChecker = performance.now();
|
|
193
200
|
const checker = program.getTypeChecker();
|
|
194
201
|
logger.debug(`Got type checker in ${(performance.now() - startChecker).toFixed(2)}ms`);
|
|
@@ -207,7 +214,7 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
207
214
|
for (const sourceFile of sourceFiles) {
|
|
208
215
|
ts.forEachChild(sourceFile, (child) => visitSetup(logger, checker, child, state, options));
|
|
209
216
|
}
|
|
210
|
-
logger.debug(`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(
|
|
217
|
+
logger.debug(`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(0)}ms`);
|
|
211
218
|
// Load addon function metadata so wirings can reference addon functions
|
|
212
219
|
await loadAddonFunctionsMeta(logger, state);
|
|
213
220
|
if (!options.setupOnly) {
|
|
@@ -216,10 +223,12 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
216
223
|
for (const sourceFile of sourceFiles) {
|
|
217
224
|
ts.forEachChild(sourceFile, (child) => visitRoutes(logger, checker, child, state, options));
|
|
218
225
|
}
|
|
219
|
-
logger.debug(`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(
|
|
226
|
+
logger.debug(`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(0)}ms`);
|
|
220
227
|
resolveLatestVersions(state, logger);
|
|
221
228
|
if (options.schemaConfig) {
|
|
229
|
+
const startSchemas = performance.now();
|
|
222
230
|
state.schemas = await generateAllSchemas(logger, options.schemaConfig, state);
|
|
231
|
+
logger.debug(`generateAllSchemas took ${(performance.now() - startSchemas).toFixed(0)}ms`);
|
|
223
232
|
computeContractHashes(state.schemas, state.functions.typesMap, state.functions.meta);
|
|
224
233
|
computeRequiredSchemas(state, options);
|
|
225
234
|
}
|
|
@@ -248,6 +257,7 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
248
257
|
computeMiddlewareGroupsMeta(state);
|
|
249
258
|
computePermissionsGroupsMeta(state);
|
|
250
259
|
computeDiagnostics(state);
|
|
260
|
+
validateSchemaWiringSeparation(logger, state);
|
|
251
261
|
if (options.openAPI) {
|
|
252
262
|
state.openAPISpec = await generateOpenAPISpec(logger, state.functions.meta, state.http.meta, state.schemas, options.openAPI.additionalInfo, pikkuState(null, 'misc', 'errors'));
|
|
253
263
|
}
|
|
@@ -256,5 +266,6 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
256
266
|
validateSecretOverrides(logger, state);
|
|
257
267
|
validateVariableOverrides(logger, state);
|
|
258
268
|
}
|
|
269
|
+
state.program = program;
|
|
259
270
|
return state;
|
|
260
271
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { AIAgentMeta } from '@pikku/core/ai-agent';
|
|
|
11
11
|
import type { CLIMeta } from '@pikku/core/cli';
|
|
12
12
|
import type { NodesMeta } from '@pikku/core/node';
|
|
13
13
|
import type { SecretDefinitions } from '@pikku/core/secret';
|
|
14
|
+
import type { CredentialDefinitions } from '@pikku/core/credential';
|
|
14
15
|
import type { VariableDefinitions } from '@pikku/core/variable';
|
|
15
16
|
import type { TypesMap } from './types-map.js';
|
|
16
17
|
import type { FunctionsMeta, FunctionServicesMeta, FunctionWiresMeta, JSONValue } from '@pikku/core';
|
|
@@ -193,6 +194,7 @@ export type InspectorOptions = Partial<{
|
|
|
193
194
|
tags: string[];
|
|
194
195
|
manifest: VersionManifest;
|
|
195
196
|
modelConfig: InspectorModelConfig;
|
|
197
|
+
oldProgram: ts.Program | undefined;
|
|
196
198
|
}>;
|
|
197
199
|
export interface InspectorLogger {
|
|
198
200
|
info: (message: string) => void;
|
|
@@ -348,6 +350,10 @@ export interface InspectorState {
|
|
|
348
350
|
definitions: SecretDefinitions;
|
|
349
351
|
files: Set<string>;
|
|
350
352
|
};
|
|
353
|
+
credentials: {
|
|
354
|
+
definitions: CredentialDefinitions;
|
|
355
|
+
files: Set<string>;
|
|
356
|
+
};
|
|
351
357
|
variables: {
|
|
352
358
|
definitions: VariableDefinitions;
|
|
353
359
|
files: Set<string>;
|
|
@@ -393,4 +399,5 @@ export interface InspectorState {
|
|
|
393
399
|
openAPISpec: Record<string, any> | null;
|
|
394
400
|
diagnostics: InspectorDiagnostic[];
|
|
395
401
|
addonFunctions: Record<string, FunctionsMeta>;
|
|
402
|
+
program: ts.Program | null;
|
|
396
403
|
}
|
|
@@ -9,6 +9,7 @@ export function sanitizeTypeName(name) {
|
|
|
9
9
|
}
|
|
10
10
|
export function generateCustomTypes(typesMap, requiredTypes) {
|
|
11
11
|
const typeDeclarations = Array.from(typesMap.customTypes.entries())
|
|
12
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
12
13
|
.filter(([_name, { type }]) => {
|
|
13
14
|
const hasUndefinedGeneric = /\b(Name|In|Out|Key)\b/.test(type) && /\[.*\]/.test(type);
|
|
14
15
|
return !hasUndefinedGeneric;
|
|
@@ -22,4 +22,13 @@ export declare function computePermissionsGroupsMeta(state: InspectorState): voi
|
|
|
22
22
|
export declare function computeRequiredSchemas(state: InspectorState, options: InspectorOptions): void;
|
|
23
23
|
export declare function validateAgentModels(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>, modelConfig?: InspectorModelConfig): void;
|
|
24
24
|
export declare function validateAgentOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>, modelConfig?: InspectorModelConfig): void;
|
|
25
|
+
/**
|
|
26
|
+
* Validates that Zod schemas and wiring side-effects (wireHTTPRoutes,
|
|
27
|
+
* addPermission, addHTTPMiddleware, etc.) do not coexist in the same file.
|
|
28
|
+
*
|
|
29
|
+
* The CLI uses tsImport to extract Zod schemas at runtime, which executes
|
|
30
|
+
* all top-level code in the file. Wiring calls crash during this process
|
|
31
|
+
* because the pikku state metadata doesn't exist in the CLI context.
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateSchemaWiringSeparation(logger: InspectorLogger, state: InspectorState): void;
|
|
25
34
|
export declare function computeDiagnostics(state: InspectorState): void;
|
|
@@ -378,6 +378,52 @@ export function validateAgentOverrides(logger, state, modelConfig) {
|
|
|
378
378
|
}
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Validates that Zod schemas and wiring side-effects (wireHTTPRoutes,
|
|
383
|
+
* addPermission, addHTTPMiddleware, etc.) do not coexist in the same file.
|
|
384
|
+
*
|
|
385
|
+
* The CLI uses tsImport to extract Zod schemas at runtime, which executes
|
|
386
|
+
* all top-level code in the file. Wiring calls crash during this process
|
|
387
|
+
* because the pikku state metadata doesn't exist in the CLI context.
|
|
388
|
+
*/
|
|
389
|
+
export function validateSchemaWiringSeparation(logger, state) {
|
|
390
|
+
// Collect files that contain schemas
|
|
391
|
+
const schemaFiles = new Set();
|
|
392
|
+
for (const ref of state.schemaLookup.values()) {
|
|
393
|
+
schemaFiles.add(ref.sourceFile);
|
|
394
|
+
}
|
|
395
|
+
// Collect files that contain wiring side-effects
|
|
396
|
+
const wiringFiles = new Set();
|
|
397
|
+
// HTTP route wirings
|
|
398
|
+
for (const file of state.http.files) {
|
|
399
|
+
wiringFiles.add(file);
|
|
400
|
+
}
|
|
401
|
+
// Permission wirings (addPermission calls)
|
|
402
|
+
for (const meta of state.permissions.tagPermissions.values()) {
|
|
403
|
+
wiringFiles.add(meta.sourceFile);
|
|
404
|
+
}
|
|
405
|
+
for (const meta of state.http.routePermissions.values()) {
|
|
406
|
+
wiringFiles.add(meta.sourceFile);
|
|
407
|
+
}
|
|
408
|
+
// Middleware wirings (addHTTPMiddleware calls)
|
|
409
|
+
for (const meta of state.http.routeMiddleware.values()) {
|
|
410
|
+
wiringFiles.add(meta.sourceFile);
|
|
411
|
+
}
|
|
412
|
+
for (const meta of state.middleware.tagMiddleware.values()) {
|
|
413
|
+
wiringFiles.add(meta.sourceFile);
|
|
414
|
+
}
|
|
415
|
+
// Check for overlap
|
|
416
|
+
for (const file of schemaFiles) {
|
|
417
|
+
if (wiringFiles.has(file)) {
|
|
418
|
+
const schemas = Array.from(state.schemaLookup.entries())
|
|
419
|
+
.filter(([, ref]) => ref.sourceFile === file)
|
|
420
|
+
.map(([name]) => name);
|
|
421
|
+
logger.critical(ErrorCode.SCHEMA_AND_WIRING_COLOCATED, `File '${file}' contains both Zod schemas (${schemas.join(', ')}) and wiring calls (wireHTTPRoutes, addPermission, etc.). ` +
|
|
422
|
+
`These must be in separate files because the CLI imports schema files at runtime, which triggers wiring side-effects that crash without server context. ` +
|
|
423
|
+
`Move the route/wiring definitions to a dedicated wiring file.`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
381
427
|
export function computeDiagnostics(state) {
|
|
382
428
|
const diagnostics = [];
|
|
383
429
|
for (const [id, meta] of Object.entries(state.functions.meta)) {
|
|
@@ -46,14 +46,25 @@ function primitiveTypeToSchema(typeStr) {
|
|
|
46
46
|
}
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
|
+
// Cached state for schema program reuse across inspect() calls
|
|
50
|
+
let cachedSchemaProgram;
|
|
51
|
+
let cachedParsedConfig;
|
|
52
|
+
let cachedTsconfigPath;
|
|
53
|
+
let cachedCustomTypesContent;
|
|
54
|
+
let cachedTSSchemas;
|
|
49
55
|
function createProgramWithVirtualFile(tsconfig, virtualFilePath, virtualFileContent) {
|
|
50
56
|
const configPath = resolve(tsconfig);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
// Cache the parsed tsconfig — it doesn't change between runs
|
|
58
|
+
if (!cachedParsedConfig || cachedTsconfigPath !== configPath) {
|
|
59
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
60
|
+
const basePath = dirname(configPath);
|
|
61
|
+
cachedParsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, basePath);
|
|
62
|
+
cachedTsconfigPath = configPath;
|
|
63
|
+
cachedSchemaProgram = undefined;
|
|
64
|
+
}
|
|
54
65
|
const resolvedVirtualPath = resolve(virtualFilePath);
|
|
55
|
-
const fileNames = [...
|
|
56
|
-
const defaultHost = ts.createCompilerHost(
|
|
66
|
+
const fileNames = [...cachedParsedConfig.fileNames, resolvedVirtualPath];
|
|
67
|
+
const defaultHost = ts.createCompilerHost(cachedParsedConfig.options);
|
|
57
68
|
const customHost = {
|
|
58
69
|
...defaultHost,
|
|
59
70
|
getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) {
|
|
@@ -73,7 +84,10 @@ function createProgramWithVirtualFile(tsconfig, virtualFilePath, virtualFileCont
|
|
|
73
84
|
return defaultHost.readFile(fileName);
|
|
74
85
|
},
|
|
75
86
|
};
|
|
76
|
-
|
|
87
|
+
const program = ts.createProgram(fileNames, cachedParsedConfig.options, customHost, cachedSchemaProgram // reuse previous program for incremental compilation
|
|
88
|
+
);
|
|
89
|
+
cachedSchemaProgram = program;
|
|
90
|
+
return program;
|
|
77
91
|
}
|
|
78
92
|
function generateTSSchemas(logger, tsconfig, customTypesContent, typesMap, functionMeta, httpWiringsMeta, additionalTypes, additionalProperties = false, schemaLookup) {
|
|
79
93
|
const schemasSet = new Set(typesMap.customTypes.keys());
|
|
@@ -204,6 +218,12 @@ export async function generateAllSchemas(logger, config, state) {
|
|
|
204
218
|
const zodSchemas = await generateZodSchemas(logger, state.schemaLookup, state.functions.typesMap);
|
|
205
219
|
const requiredTypes = new Set();
|
|
206
220
|
const customTypesContent = generateCustomTypes(state.functions.typesMap, requiredTypes);
|
|
221
|
+
if (cachedTSSchemas && cachedCustomTypesContent === customTypesContent) {
|
|
222
|
+
logger.debug('Reusing cached TS schemas (types unchanged)');
|
|
223
|
+
return { ...cachedTSSchemas, ...zodSchemas };
|
|
224
|
+
}
|
|
207
225
|
const tsSchemas = generateTSSchemas(logger, config.tsconfig, customTypesContent, state.functions.typesMap, state.functions.meta, state.http.meta, config.schemasFromTypes, config.schema?.additionalProperties, state.schemaLookup);
|
|
226
|
+
cachedCustomTypesContent = customTypesContent;
|
|
227
|
+
cachedTSSchemas = tsSchemas;
|
|
208
228
|
return { ...tsSchemas, ...zodSchemas };
|
|
209
229
|
}
|