@pikku/inspector 0.12.4 → 0.12.6
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 +18 -0
- package/dist/add/add-credential.d.ts +2 -0
- package/dist/add/add-credential.js +118 -0
- package/dist/add/add-secret.d.ts +1 -3
- package/dist/add/add-secret.js +0 -74
- package/dist/add/add-wire-addon.js +6 -0
- package/dist/inspector.js +6 -1
- package/dist/types.d.ts +6 -0
- package/dist/utils/post-process.d.ts +1 -0
- package/dist/utils/post-process.js +16 -0
- package/dist/utils/resolve-addon-package.d.ts +1 -0
- package/dist/utils/serialize-inspector-state.d.ts +5 -0
- package/dist/utils/serialize-inspector-state.js +8 -0
- package/dist/visit.js +3 -2
- package/package.json +2 -2
- package/src/add/add-credential.ts +178 -0
- package/src/add/add-secret.ts +0 -131
- package/src/add/add-wire-addon.ts +7 -0
- package/src/inspector.ts +6 -0
- package/src/types.ts +6 -0
- package/src/utils/post-process.ts +26 -0
- package/src/utils/resolve-addon-package.ts +1 -0
- package/src/utils/serialize-inspector-state.ts +13 -0
- package/src/visit.ts +3 -2
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
## 0.12.0
|
|
2
2
|
|
|
3
|
+
## 0.12.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 84f01ad: Add credentialOverrides to wireAddon for remapping credential names, fix credential services template to pass variables argument.
|
|
8
|
+
- Updated dependencies [84f01ad]
|
|
9
|
+
- @pikku/core@0.12.12
|
|
10
|
+
|
|
11
|
+
## 0.12.5
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 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.
|
|
16
|
+
- 0f59432: Add per-user credential system with CredentialService, OAuth2 route handlers, and KyselyCredentialService with envelope encryption
|
|
17
|
+
- Updated dependencies [0f59432]
|
|
18
|
+
- Updated dependencies [52b64d1]
|
|
19
|
+
- @pikku/core@0.12.10
|
|
20
|
+
|
|
3
21
|
## 0.12.4
|
|
4
22
|
|
|
5
23
|
### 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
|
+
};
|
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
|
-
};
|
|
@@ -35,6 +35,7 @@ export function addWireAddon(node, state, logger) {
|
|
|
35
35
|
let mcp;
|
|
36
36
|
let secretOverrides;
|
|
37
37
|
let variableOverrides;
|
|
38
|
+
let credentialOverrides;
|
|
38
39
|
for (const prop of firstArg.properties) {
|
|
39
40
|
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
40
41
|
continue;
|
|
@@ -61,6 +62,10 @@ export function addWireAddon(node, state, logger) {
|
|
|
61
62
|
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
62
63
|
variableOverrides = parseStringRecord(prop.initializer);
|
|
63
64
|
}
|
|
65
|
+
else if (key === 'credentialOverrides' &&
|
|
66
|
+
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
67
|
+
credentialOverrides = parseStringRecord(prop.initializer);
|
|
68
|
+
}
|
|
64
69
|
}
|
|
65
70
|
if (!name || !pkg)
|
|
66
71
|
return;
|
|
@@ -71,6 +76,7 @@ export function addWireAddon(node, state, logger) {
|
|
|
71
76
|
mcp,
|
|
72
77
|
secretOverrides,
|
|
73
78
|
variableOverrides,
|
|
79
|
+
credentialOverrides,
|
|
74
80
|
});
|
|
75
81
|
state.rpc.usedAddons.add(name);
|
|
76
82
|
state.rpc.wireAddonFiles.add(node.getSourceFile().fileName);
|
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, validateSchemaWiringSeparation, } from './utils/post-process.js';
|
|
7
|
+
import { aggregateRequiredServices, validateAgentModels, validateAgentOverrides, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, 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(),
|
|
@@ -261,6 +265,7 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
261
265
|
validateAgentOverrides(logger, state, options.modelConfig);
|
|
262
266
|
validateSecretOverrides(logger, state);
|
|
263
267
|
validateVariableOverrides(logger, state);
|
|
268
|
+
validateCredentialOverrides(logger, state);
|
|
264
269
|
}
|
|
265
270
|
state.program = program;
|
|
266
271
|
return state;
|
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';
|
|
@@ -321,6 +322,7 @@ export interface InspectorState {
|
|
|
321
322
|
mcp?: boolean;
|
|
322
323
|
secretOverrides?: Record<string, string>;
|
|
323
324
|
variableOverrides?: Record<string, string>;
|
|
325
|
+
credentialOverrides?: Record<string, string>;
|
|
324
326
|
}>;
|
|
325
327
|
wireAddonFiles: Set<string>;
|
|
326
328
|
};
|
|
@@ -349,6 +351,10 @@ export interface InspectorState {
|
|
|
349
351
|
definitions: SecretDefinitions;
|
|
350
352
|
files: Set<string>;
|
|
351
353
|
};
|
|
354
|
+
credentials: {
|
|
355
|
+
definitions: CredentialDefinitions;
|
|
356
|
+
files: Set<string>;
|
|
357
|
+
};
|
|
352
358
|
variables: {
|
|
353
359
|
definitions: VariableDefinitions;
|
|
354
360
|
files: Set<string>;
|
|
@@ -15,6 +15,7 @@ export declare function extractWireNames(list?: MiddlewareMetadata[] | Permissio
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function aggregateRequiredServices(state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
17
17
|
export declare function validateSecretOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
18
|
+
export declare function validateCredentialOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
18
19
|
export declare function validateVariableOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
19
20
|
export declare function computeResolvedIOTypes(state: InspectorState): void;
|
|
20
21
|
export declare function computeMiddlewareGroupsMeta(state: InspectorState): void;
|
|
@@ -177,6 +177,22 @@ export function validateSecretOverrides(logger, state) {
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
|
+
export function validateCredentialOverrides(logger, state) {
|
|
181
|
+
const { wireAddonDeclarations } = state.rpc;
|
|
182
|
+
if (!wireAddonDeclarations || wireAddonDeclarations.size === 0)
|
|
183
|
+
return;
|
|
184
|
+
const credentialNames = new Set(state.credentials?.definitions.map((d) => d.name) ?? []);
|
|
185
|
+
for (const [namespace, addonDecl] of wireAddonDeclarations.entries()) {
|
|
186
|
+
if (!addonDecl.credentialOverrides)
|
|
187
|
+
continue;
|
|
188
|
+
for (const credentialKey of Object.keys(addonDecl.credentialOverrides)) {
|
|
189
|
+
if (!credentialNames.has(credentialKey)) {
|
|
190
|
+
const availableCredentials = Array.from(credentialNames);
|
|
191
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Credential override '${credentialKey}' in addon '${namespace}' (${addonDecl.package}) does not exist. Available credentials: ${availableCredentials.join(', ') || 'none'}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
180
196
|
export function validateVariableOverrides(logger, state) {
|
|
181
197
|
const { wireAddonDeclarations } = state.rpc;
|
|
182
198
|
if (!wireAddonDeclarations || wireAddonDeclarations.size === 0)
|
|
@@ -13,4 +13,5 @@ export declare const resolveAddonName: (identifier: ts.Identifier, checker: ts.T
|
|
|
13
13
|
rpcEndpoint?: string;
|
|
14
14
|
secretOverrides?: Record<string, string>;
|
|
15
15
|
variableOverrides?: Record<string, string>;
|
|
16
|
+
credentialOverrides?: Record<string, string>;
|
|
16
17
|
}>) => string | null;
|
|
@@ -176,6 +176,7 @@ export interface SerializableInspectorState {
|
|
|
176
176
|
rpcEndpoint?: string;
|
|
177
177
|
secretOverrides?: Record<string, string>;
|
|
178
178
|
variableOverrides?: Record<string, string>;
|
|
179
|
+
credentialOverrides?: Record<string, string>;
|
|
179
180
|
}
|
|
180
181
|
]>;
|
|
181
182
|
wireAddonFiles: string[];
|
|
@@ -205,6 +206,10 @@ export interface SerializableInspectorState {
|
|
|
205
206
|
definitions: InspectorState['secrets']['definitions'];
|
|
206
207
|
files: string[];
|
|
207
208
|
};
|
|
209
|
+
credentials: {
|
|
210
|
+
definitions: InspectorState['credentials']['definitions'];
|
|
211
|
+
files: string[];
|
|
212
|
+
};
|
|
208
213
|
variables: {
|
|
209
214
|
definitions: InspectorState['variables']['definitions'];
|
|
210
215
|
files: string[];
|
|
@@ -101,6 +101,10 @@ export function serializeInspectorState(state) {
|
|
|
101
101
|
definitions: state.secrets.definitions,
|
|
102
102
|
files: Array.from(state.secrets.files),
|
|
103
103
|
},
|
|
104
|
+
credentials: {
|
|
105
|
+
definitions: state.credentials.definitions,
|
|
106
|
+
files: Array.from(state.credentials.files),
|
|
107
|
+
},
|
|
104
108
|
variables: {
|
|
105
109
|
definitions: state.variables.definitions,
|
|
106
110
|
files: Array.from(state.variables.files),
|
|
@@ -244,6 +248,10 @@ export function deserializeInspectorState(data) {
|
|
|
244
248
|
definitions: data.secrets?.definitions || [],
|
|
245
249
|
files: new Set(data.secrets?.files || []),
|
|
246
250
|
},
|
|
251
|
+
credentials: {
|
|
252
|
+
definitions: data.credentials?.definitions || [],
|
|
253
|
+
files: new Set(data.credentials?.files || []),
|
|
254
|
+
},
|
|
247
255
|
variables: {
|
|
248
256
|
definitions: data.variables?.definitions || [],
|
|
249
257
|
files: new Set(data.variables?.files || []),
|
package/dist/visit.js
CHANGED
|
@@ -17,7 +17,8 @@ import { addWireAddon } from './add/add-wire-addon.js';
|
|
|
17
17
|
import { addMiddleware } from './add/add-middleware.js';
|
|
18
18
|
import { addPermission } from './add/add-permission.js';
|
|
19
19
|
import { addCLI, addCLIRenderers } from './add/add-cli.js';
|
|
20
|
-
import { addSecret
|
|
20
|
+
import { addSecret } from './add/add-secret.js';
|
|
21
|
+
import { addCredential } from './add/add-credential.js';
|
|
21
22
|
import { addVariable } from './add/add-variable.js';
|
|
22
23
|
import { addWorkflowGraph } from './add/add-workflow-graph.js';
|
|
23
24
|
import { addAIAgent } from './add/add-ai-agent.js';
|
|
@@ -41,7 +42,7 @@ export const visitSetup = (logger, checker, node, state, options) => {
|
|
|
41
42
|
export const visitRoutes = (logger, checker, node, state, options) => {
|
|
42
43
|
addFunctions(logger, node, checker, state, options);
|
|
43
44
|
addSecret(logger, node, checker, state, options);
|
|
44
|
-
|
|
45
|
+
addCredential(logger, node, checker, state, options);
|
|
45
46
|
addVariable(logger, node, checker, state, options);
|
|
46
47
|
addHTTPRoute(logger, node, checker, state, options);
|
|
47
48
|
addHTTPRoutes(logger, node, checker, state, options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.6",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@openapi-contrib/json-schema-to-openapi-schema": "^4.3.1",
|
|
38
|
-
"@pikku/core": "^0.12.
|
|
38
|
+
"@pikku/core": "^0.12.12",
|
|
39
39
|
"path-to-regexp": "^8.3.0",
|
|
40
40
|
"ts-json-schema-generator": "^2.5.0",
|
|
41
41
|
"tsx": "^4.21.0",
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
import {
|
|
3
|
+
getPropertyValue,
|
|
4
|
+
getArrayPropertyValue,
|
|
5
|
+
} from '../utils/get-property-value.js'
|
|
6
|
+
import type { AddWiring } from '../types.js'
|
|
7
|
+
import { ErrorCode } from '../error-codes.js'
|
|
8
|
+
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js'
|
|
9
|
+
|
|
10
|
+
export const addCredential: AddWiring = (
|
|
11
|
+
logger,
|
|
12
|
+
node,
|
|
13
|
+
checker,
|
|
14
|
+
state,
|
|
15
|
+
_options
|
|
16
|
+
) => {
|
|
17
|
+
if (!ts.isCallExpression(node)) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const args = node.arguments
|
|
22
|
+
const firstArg = args[0]
|
|
23
|
+
const expression = node.expression
|
|
24
|
+
|
|
25
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireCredential') {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!firstArg) {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
34
|
+
const obj = firstArg
|
|
35
|
+
|
|
36
|
+
const nameValue = getPropertyValue(obj, 'name') as string | null
|
|
37
|
+
const displayNameValue = getPropertyValue(obj, 'displayName') as
|
|
38
|
+
| string
|
|
39
|
+
| null
|
|
40
|
+
const descriptionValue = getPropertyValue(obj, 'description') as
|
|
41
|
+
| string
|
|
42
|
+
| null
|
|
43
|
+
const typeValue = getPropertyValue(obj, 'type') as string | null
|
|
44
|
+
|
|
45
|
+
if (!nameValue) {
|
|
46
|
+
logger.critical(
|
|
47
|
+
ErrorCode.MISSING_NAME,
|
|
48
|
+
"Credential is missing the required 'name' property."
|
|
49
|
+
)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!displayNameValue) {
|
|
54
|
+
logger.critical(
|
|
55
|
+
ErrorCode.MISSING_NAME,
|
|
56
|
+
`Credential '${nameValue}' is missing the required 'displayName' property.`
|
|
57
|
+
)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!typeValue || (typeValue !== 'singleton' && typeValue !== 'wire')) {
|
|
62
|
+
logger.critical(
|
|
63
|
+
ErrorCode.MISSING_NAME,
|
|
64
|
+
`Credential '${nameValue}' is missing or has invalid 'type' property. Must be 'singleton' or 'wire'.`
|
|
65
|
+
)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let schemaVariableName: string | null = null
|
|
70
|
+
let schemaSourceFile: string | null = null
|
|
71
|
+
let schemaIdentifier: ts.Identifier | null = null
|
|
72
|
+
for (const prop of obj.properties) {
|
|
73
|
+
if (
|
|
74
|
+
ts.isPropertyAssignment(prop) &&
|
|
75
|
+
ts.isIdentifier(prop.name) &&
|
|
76
|
+
prop.name.text === 'schema'
|
|
77
|
+
) {
|
|
78
|
+
if (ts.isIdentifier(prop.initializer)) {
|
|
79
|
+
schemaVariableName = prop.initializer.text
|
|
80
|
+
schemaIdentifier = prop.initializer
|
|
81
|
+
|
|
82
|
+
const symbol = checker.getSymbolAtLocation(prop.initializer)
|
|
83
|
+
if (symbol) {
|
|
84
|
+
const decl = symbol.valueDeclaration || symbol.declarations?.[0]
|
|
85
|
+
if (decl) {
|
|
86
|
+
if (ts.isImportSpecifier(decl)) {
|
|
87
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol)
|
|
88
|
+
if (aliasedSymbol) {
|
|
89
|
+
const aliasedDecl =
|
|
90
|
+
aliasedSymbol.valueDeclaration ||
|
|
91
|
+
aliasedSymbol.declarations?.[0]
|
|
92
|
+
if (aliasedDecl) {
|
|
93
|
+
schemaSourceFile = aliasedDecl.getSourceFile().fileName
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
schemaSourceFile = decl.getSourceFile().fileName
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let oauth2: any = undefined
|
|
107
|
+
const oauth2Prop = obj.properties.find(
|
|
108
|
+
(p) =>
|
|
109
|
+
ts.isPropertyAssignment(p) &&
|
|
110
|
+
ts.isIdentifier(p.name) &&
|
|
111
|
+
p.name.text === 'oauth2'
|
|
112
|
+
)
|
|
113
|
+
if (
|
|
114
|
+
oauth2Prop &&
|
|
115
|
+
ts.isPropertyAssignment(oauth2Prop) &&
|
|
116
|
+
ts.isObjectLiteralExpression(oauth2Prop.initializer)
|
|
117
|
+
) {
|
|
118
|
+
const oauth2Obj = oauth2Prop.initializer
|
|
119
|
+
const appCredentialSecretId = getPropertyValue(
|
|
120
|
+
oauth2Obj,
|
|
121
|
+
'appCredentialSecretId'
|
|
122
|
+
) as string | null
|
|
123
|
+
const tokenSecretId = getPropertyValue(oauth2Obj, 'tokenSecretId') as
|
|
124
|
+
| string
|
|
125
|
+
| null
|
|
126
|
+
const authorizationUrl = getPropertyValue(
|
|
127
|
+
oauth2Obj,
|
|
128
|
+
'authorizationUrl'
|
|
129
|
+
) as string | null
|
|
130
|
+
const tokenUrl = getPropertyValue(oauth2Obj, 'tokenUrl') as string | null
|
|
131
|
+
const scopes = getArrayPropertyValue(oauth2Obj, 'scopes')
|
|
132
|
+
const pkce = getPropertyValue(oauth2Obj, 'pkce') as boolean | null
|
|
133
|
+
|
|
134
|
+
if (appCredentialSecretId && authorizationUrl && tokenUrl && scopes) {
|
|
135
|
+
oauth2 = {
|
|
136
|
+
appCredentialSecretId,
|
|
137
|
+
tokenSecretId: tokenSecretId || undefined,
|
|
138
|
+
authorizationUrl,
|
|
139
|
+
tokenUrl,
|
|
140
|
+
scopes,
|
|
141
|
+
pkce: pkce || undefined,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const sourceFile = node.getSourceFile().fileName
|
|
147
|
+
state.credentials.files.add(sourceFile)
|
|
148
|
+
|
|
149
|
+
let schemaLookupName: string | undefined
|
|
150
|
+
if (schemaVariableName && schemaSourceFile && schemaIdentifier) {
|
|
151
|
+
const vendor = detectSchemaVendorOrError(
|
|
152
|
+
schemaIdentifier,
|
|
153
|
+
checker,
|
|
154
|
+
logger,
|
|
155
|
+
`Credential '${nameValue}'`,
|
|
156
|
+
schemaSourceFile
|
|
157
|
+
)
|
|
158
|
+
if (vendor) {
|
|
159
|
+
schemaLookupName = `CredentialSchema_${nameValue}`
|
|
160
|
+
state.schemaLookup.set(schemaLookupName, {
|
|
161
|
+
variableName: schemaVariableName,
|
|
162
|
+
sourceFile: schemaSourceFile,
|
|
163
|
+
vendor,
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
state.credentials.definitions.push({
|
|
169
|
+
name: nameValue,
|
|
170
|
+
displayName: displayNameValue,
|
|
171
|
+
description: descriptionValue || undefined,
|
|
172
|
+
type: typeValue as 'singleton' | 'wire',
|
|
173
|
+
schema: schemaLookupName,
|
|
174
|
+
oauth2,
|
|
175
|
+
sourceFile,
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
}
|