@pikku/inspector 0.12.42 → 0.12.44
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 +266 -0
- package/dist/add/add-addon-bans.js +1 -0
- package/dist/add/add-ai-agent.d.ts +3 -1
- package/dist/add/add-ai-agent.js +82 -0
- package/dist/add/add-credential.js +5 -1
- package/dist/add/add-functions.js +10 -9
- package/dist/add/add-gateway.js +3 -0
- package/dist/add/add-http-route.js +2 -2
- package/dist/add/add-keyed-wiring.js +2 -0
- package/dist/add/add-mcp-prompt.js +0 -1
- package/dist/add/add-mcp-resource.js +0 -1
- package/dist/add/add-permission.d.ts +1 -1
- package/dist/add/add-permission.js +3 -177
- package/dist/add/add-queue-worker.js +3 -0
- package/dist/add/add-schedule.js +3 -0
- package/dist/add/add-scope.d.ts +2 -0
- package/dist/add/add-scope.js +146 -0
- package/dist/add/add-trigger.js +3 -0
- package/dist/add/add-wire-remote-addon.d.ts +10 -0
- package/dist/add/add-wire-remote-addon.js +66 -0
- package/dist/add/add-workflow-graph.js +32 -3
- package/dist/add/add-workflow.js +9 -4
- package/dist/error-codes.d.ts +3 -0
- package/dist/error-codes.js +4 -0
- package/dist/inspector.js +11 -5
- package/dist/types.d.ts +19 -13
- package/dist/utils/custom-types-generator.js +2 -1
- package/dist/utils/ensure-function-metadata.d.ts +15 -0
- package/dist/utils/ensure-function-metadata.js +24 -0
- package/dist/utils/filter-inspector-state.js +9 -0
- package/dist/utils/get-property-value.d.ts +7 -2
- package/dist/utils/get-property-value.js +48 -1
- package/dist/utils/load-addon-functions-meta.js +81 -6
- package/dist/utils/permissions.d.ts +1 -21
- package/dist/utils/permissions.js +17 -108
- package/dist/utils/post-process.d.ts +30 -1
- package/dist/utils/post-process.js +150 -72
- package/dist/utils/schema-generator.js +19 -4
- package/dist/utils/serialize-inspector-state.d.ts +13 -8
- package/dist/utils/serialize-inspector-state.js +12 -6
- package/dist/utils/serialize-permissions-groups-meta.d.ts +0 -2
- package/dist/utils/serialize-permissions-groups-meta.js +0 -26
- package/dist/utils/workflow/derive-workflow-plan.js +9 -3
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +373 -91
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +274 -100
- package/dist/utils/workflow/dsl/patterns.d.ts +4 -0
- package/dist/utils/workflow/dsl/patterns.js +11 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +76 -5
- package/dist/utils/workflow/graph/finalize-workflows.d.ts +7 -0
- package/dist/utils/workflow/graph/finalize-workflows.js +11 -2
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +2 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +4 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +8 -4
- package/dist/visit.js +4 -0
- package/package.json +3 -3
- package/run-tests.sh +1 -1
- package/src/add/add-addon-bans.ts +1 -0
- package/src/add/add-ai-agent.test.ts +87 -0
- package/src/add/add-ai-agent.ts +122 -0
- package/src/add/add-credential.ts +8 -0
- package/src/add/add-functions.ts +10 -12
- package/src/add/add-gateway.ts +11 -0
- package/src/add/add-http-route.ts +2 -8
- package/src/add/add-keyed-wiring.ts +2 -0
- package/src/add/add-mcp-prompt.ts +0 -1
- package/src/add/add-mcp-resource.ts +0 -1
- package/src/add/add-permission.ts +4 -242
- package/src/add/add-queue-worker.ts +11 -0
- package/src/add/add-schedule.ts +11 -0
- package/src/add/add-scope.test.ts +346 -0
- package/src/add/add-scope.ts +225 -0
- package/src/add/add-trigger.ts +11 -0
- package/src/add/add-wire-remote-addon.ts +77 -0
- package/src/add/add-workflow-edge-cases.test.ts +201 -0
- package/src/add/add-workflow-fanout.test.ts +60 -0
- package/src/add/add-workflow-graph-input.test.ts +94 -0
- package/src/add/add-workflow-graph-notes.test.ts +74 -0
- package/src/add/add-workflow-graph.ts +35 -3
- package/src/add/add-workflow-on-error.test.ts +118 -0
- package/src/add/add-workflow-return-switch.test.ts +121 -0
- package/src/add/add-workflow-silent-drops.test.ts +163 -0
- package/src/add/add-workflow-sleep-expression.test.ts +97 -0
- package/src/add/add-workflow.ts +9 -4
- package/src/add/config-value-metadata.test.ts +169 -0
- package/src/add/inline-wiring-function.test.ts +104 -0
- package/src/add/validate-workflow-graph-addons.test.ts +102 -0
- package/src/error-codes.ts +5 -0
- package/src/inspector.ts +14 -4
- package/src/types.ts +16 -17
- package/src/utils/custom-types-generator.test.ts +26 -1
- package/src/utils/custom-types-generator.ts +2 -1
- package/src/utils/ensure-function-metadata.ts +42 -0
- package/src/utils/filter-inspector-state.test.ts +0 -2
- package/src/utils/filter-inspector-state.ts +9 -0
- package/src/utils/get-property-value.test.ts +141 -0
- package/src/utils/get-property-value.ts +62 -1
- package/src/utils/load-addon-functions-meta.test.ts +157 -0
- package/src/utils/load-addon-functions-meta.ts +94 -6
- package/src/utils/load-addon-scopes.test.ts +138 -0
- package/src/utils/permissions.test.ts +7 -232
- package/src/utils/permissions.ts +20 -160
- package/src/utils/post-process.test.ts +269 -4
- package/src/utils/post-process.ts +216 -95
- package/src/utils/schema-generator.test.ts +164 -0
- package/src/utils/schema-generator.ts +26 -3
- package/src/utils/serialize-inspector-state.ts +22 -25
- package/src/utils/serialize-permissions-groups-meta.ts +0 -28
- package/src/utils/workflow/derive-workflow-plan.test.ts +41 -3
- package/src/utils/workflow/derive-workflow-plan.ts +11 -4
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +480 -119
- package/src/utils/workflow/dsl/deserialize-fanout-body.test.ts +71 -0
- package/src/utils/workflow/dsl/deserialize-fanout-fidelity.test.ts +99 -0
- package/src/utils/workflow/dsl/deserialize-graph-next.test.ts +161 -0
- package/src/utils/workflow/dsl/deserialize-roundtrip-bindings.test.ts +121 -0
- package/src/utils/workflow/dsl/deserialize-roundtrip-values.test.ts +116 -0
- package/src/utils/workflow/dsl/deserialize-roundtrip.test.ts +145 -0
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +311 -105
- package/src/utils/workflow/dsl/patterns.ts +18 -0
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +82 -10
- package/src/utils/workflow/graph/fanout-node-identity.test.ts +105 -0
- package/src/utils/workflow/graph/finalize-workflows.test.ts +50 -0
- package/src/utils/workflow/graph/finalize-workflows.ts +13 -2
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +6 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +11 -2
- package/src/visit.ts +4 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/inspector.js
CHANGED
|
@@ -6,7 +6,7 @@ import { TypesMap } from './types-map.js';
|
|
|
6
6
|
import { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
7
7
|
import { findCommonAncestor } from './utils/find-root-dir.js';
|
|
8
8
|
import { createNestedProjectFilter } from './utils/nested-project-filter.js';
|
|
9
|
-
import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, validateSchemaWiringSeparation, } from './utils/post-process.js';
|
|
9
|
+
import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, validateRemoteAddonDependencies, validateRemoteAddonAuth, validateScopeReferences, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, validateSchemaWiringSeparation, validateWorkflowGraphAddons, } from './utils/post-process.js';
|
|
10
10
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
|
|
11
11
|
import { pikkuState } from '@pikku/core/internal';
|
|
12
12
|
import { resolveLatestVersions } from './utils/resolve-versions.js';
|
|
@@ -59,7 +59,6 @@ export function getInitialInspectorState(rootDir) {
|
|
|
59
59
|
},
|
|
60
60
|
files: new Set(),
|
|
61
61
|
routeMiddleware: new Map(),
|
|
62
|
-
routePermissions: new Map(),
|
|
63
62
|
},
|
|
64
63
|
channels: {
|
|
65
64
|
files: new Set(),
|
|
@@ -94,6 +93,8 @@ export function getInitialInspectorState(rootDir) {
|
|
|
94
93
|
internalFiles: new Map(),
|
|
95
94
|
exposedMeta: {},
|
|
96
95
|
exposedFiles: new Map(),
|
|
96
|
+
remoteMeta: {},
|
|
97
|
+
remoteFiles: new Map(),
|
|
97
98
|
invokedFunctions: new Set(),
|
|
98
99
|
invokedFunctionsByFile: new Map(),
|
|
99
100
|
usedAddons: new Set(),
|
|
@@ -135,6 +136,10 @@ export function getInitialInspectorState(rootDir) {
|
|
|
135
136
|
definitions: [],
|
|
136
137
|
files: new Set(),
|
|
137
138
|
},
|
|
139
|
+
scopes: {
|
|
140
|
+
definitions: [],
|
|
141
|
+
files: new Set(),
|
|
142
|
+
},
|
|
138
143
|
variables: {
|
|
139
144
|
definitions: [],
|
|
140
145
|
files: new Set(),
|
|
@@ -160,7 +165,6 @@ export function getInitialInspectorState(rootDir) {
|
|
|
160
165
|
permissions: {
|
|
161
166
|
definitions: {},
|
|
162
167
|
instances: {},
|
|
163
|
-
tagPermissions: new Map(),
|
|
164
168
|
},
|
|
165
169
|
serviceAggregation: {
|
|
166
170
|
requiredServices: new Set(),
|
|
@@ -184,8 +188,6 @@ export function getInitialInspectorState(rootDir) {
|
|
|
184
188
|
},
|
|
185
189
|
permissionsGroupsMeta: {
|
|
186
190
|
definitions: {},
|
|
187
|
-
httpGroups: {},
|
|
188
|
-
tagGroups: {},
|
|
189
191
|
},
|
|
190
192
|
requiredSchemas: new Set(),
|
|
191
193
|
openAPISpec: null,
|
|
@@ -307,6 +309,7 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
307
309
|
computePermissionsGroupsMeta(state);
|
|
308
310
|
computeDiagnostics(state);
|
|
309
311
|
validateSchemaWiringSeparation(logger, state);
|
|
312
|
+
validateWorkflowGraphAddons(logger, state);
|
|
310
313
|
if (options.openAPI) {
|
|
311
314
|
state.openAPISpec = await generateOpenAPISpec(logger, state.functions.meta, state.http.meta, state.schemas, options.openAPI.additionalInfo, pikkuState(null, 'misc', 'errors'));
|
|
312
315
|
}
|
|
@@ -314,6 +317,9 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
314
317
|
validateSecretOverrides(logger, state);
|
|
315
318
|
validateVariableOverrides(logger, state);
|
|
316
319
|
validateCredentialOverrides(logger, state);
|
|
320
|
+
validateRemoteAddonDependencies(logger, state);
|
|
321
|
+
validateRemoteAddonAuth(logger, state);
|
|
322
|
+
validateScopeReferences(logger, state);
|
|
317
323
|
}
|
|
318
324
|
state.program = program;
|
|
319
325
|
return state;
|
package/dist/types.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { CLICommandMeta } from '@pikku/core/cli';
|
|
|
13
13
|
import type { NodesMeta } from '@pikku/core/node';
|
|
14
14
|
import type { SecretDefinitions } from '@pikku/core/secret';
|
|
15
15
|
import type { CredentialDefinitions } from '@pikku/core/credential';
|
|
16
|
+
import type { ScopeDefinitions } from '@pikku/core/scope';
|
|
16
17
|
import type { VariableDefinitions } from '@pikku/core/variable';
|
|
17
18
|
import type { TypesMap } from './types-map.js';
|
|
18
19
|
import type { FunctionsMeta, FunctionServicesMeta, FunctionWiresMeta, JSONValue } from '@pikku/core';
|
|
@@ -39,21 +40,11 @@ export interface MiddlewareGroupMeta {
|
|
|
39
40
|
instanceIds: string[];
|
|
40
41
|
isFactory: boolean;
|
|
41
42
|
}
|
|
42
|
-
export interface PermissionGroupMeta {
|
|
43
|
-
exportName: string | null;
|
|
44
|
-
sourceFile: string;
|
|
45
|
-
position: number;
|
|
46
|
-
services: FunctionServicesMeta;
|
|
47
|
-
count: number;
|
|
48
|
-
instanceIds: string[];
|
|
49
|
-
isFactory: boolean;
|
|
50
|
-
}
|
|
51
43
|
export interface InspectorHTTPState {
|
|
52
44
|
metaInputTypes: MetaInputTypes;
|
|
53
45
|
meta: HTTPWiringsMeta;
|
|
54
46
|
files: Set<string>;
|
|
55
47
|
routeMiddleware: Map<string, MiddlewareGroupMeta>;
|
|
56
|
-
routePermissions: Map<string, PermissionGroupMeta>;
|
|
57
48
|
}
|
|
58
49
|
/**
|
|
59
50
|
* Schema vendor types for Standard Schema compliant validators
|
|
@@ -184,7 +175,6 @@ export interface InspectorPermissionInstance {
|
|
|
184
175
|
export interface InspectorPermissionState {
|
|
185
176
|
definitions: Record<string, InspectorPermissionDefinition>;
|
|
186
177
|
instances: Record<string, InspectorPermissionInstance>;
|
|
187
|
-
tagPermissions: Map<string, PermissionGroupMeta>;
|
|
188
178
|
}
|
|
189
179
|
export type InspectorFilters = {
|
|
190
180
|
names?: string[];
|
|
@@ -418,6 +408,12 @@ export interface InspectorState {
|
|
|
418
408
|
path: string;
|
|
419
409
|
exportedName: string;
|
|
420
410
|
}>;
|
|
411
|
+
/** Functions marked `remote: true` — the surface a wireRemoteAddon consumer imports */
|
|
412
|
+
remoteMeta: Record<string, string>;
|
|
413
|
+
remoteFiles: Map<string, {
|
|
414
|
+
path: string;
|
|
415
|
+
exportedName: string;
|
|
416
|
+
}>;
|
|
421
417
|
invokedFunctions: Set<string>;
|
|
422
418
|
invokedFunctionsByFile: Map<string, Set<string>>;
|
|
423
419
|
usedAddons: Set<string>;
|
|
@@ -425,6 +421,14 @@ export interface InspectorState {
|
|
|
425
421
|
package: string;
|
|
426
422
|
rpcEndpoint?: string;
|
|
427
423
|
mcp?: boolean;
|
|
424
|
+
/** True when declared via `wireRemoteAddon` — import the addon's `.remote.gen` map, not `.internal.gen` */
|
|
425
|
+
remote?: boolean;
|
|
426
|
+
/** wireRemoteAddon: whether an `auth` binding was supplied (vs a public surface) */
|
|
427
|
+
hasAuth?: boolean;
|
|
428
|
+
/** wireRemoteAddon: statically-known `auth.credentialId`, for verify */
|
|
429
|
+
authCredentialId?: string;
|
|
430
|
+
/** wireRemoteAddon: statically-known `auth.secretId`, for verify */
|
|
431
|
+
authSecretId?: string;
|
|
428
432
|
secretOverrides?: Record<string, string>;
|
|
429
433
|
variableOverrides?: Record<string, string>;
|
|
430
434
|
credentialOverrides?: Record<string, string>;
|
|
@@ -480,6 +484,10 @@ export interface InspectorState {
|
|
|
480
484
|
definitions: CredentialDefinitions;
|
|
481
485
|
files: Set<string>;
|
|
482
486
|
};
|
|
487
|
+
scopes: {
|
|
488
|
+
definitions: ScopeDefinitions;
|
|
489
|
+
files: Set<string>;
|
|
490
|
+
};
|
|
483
491
|
variables: {
|
|
484
492
|
definitions: VariableDefinitions;
|
|
485
493
|
files: Set<string>;
|
|
@@ -518,8 +526,6 @@ export interface InspectorState {
|
|
|
518
526
|
};
|
|
519
527
|
permissionsGroupsMeta: {
|
|
520
528
|
definitions: Record<string, InspectorPermissionDefinition>;
|
|
521
|
-
httpGroups: Record<string, PermissionGroupMeta>;
|
|
522
|
-
tagGroups: Record<string, PermissionGroupMeta>;
|
|
523
529
|
};
|
|
524
530
|
requiredSchemas: Set<string>;
|
|
525
531
|
openAPISpec: Record<string, any> | null;
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* ts-json-schema-generator can discover inline/custom types from typesMap.
|
|
6
6
|
*/
|
|
7
7
|
export function sanitizeTypeName(name) {
|
|
8
|
-
|
|
8
|
+
const sanitized = name.replace(/[^a-zA-Z0-9_$]/g, '_');
|
|
9
|
+
return /^[0-9]/.test(sanitized) ? `_${sanitized}` : sanitized;
|
|
9
10
|
}
|
|
10
11
|
const CLASSIFICATION_WRAPPERS = new Set(['Private', 'Pii', 'Secret']);
|
|
11
12
|
function findMatchingAngleBracket(type, startIndex) {
|
|
@@ -7,3 +7,18 @@ import type { InspectorState } from '../types.js';
|
|
|
7
7
|
* extracts tags/middleware/permissions from the pikkuFunc() config.
|
|
8
8
|
*/
|
|
9
9
|
export declare function ensureFunctionMetadata(state: InspectorState, pikkuFuncId: string, fallbackName?: string, funcInitializer?: ts.Node, checker?: ts.TypeChecker, isHelper?: boolean): void;
|
|
10
|
+
/**
|
|
11
|
+
* Register metadata for a `func:` inlined into a wiring (queue/scheduler/
|
|
12
|
+
* trigger/gateway/…).
|
|
13
|
+
*
|
|
14
|
+
* An inline function has no exported name, so `addFunctions` skips it (it bails
|
|
15
|
+
* on `__temp_` ids) and only the wiring's context-based id exists. Without this
|
|
16
|
+
* the wiring points at a function that was never registered and the worker
|
|
17
|
+
* resolves to nothing at runtime ("Missing generated metadata for ...").
|
|
18
|
+
*
|
|
19
|
+
* The stub also carries no session info, so the runtime would treat it as
|
|
20
|
+
* session-required and every invocation would fail with "Authentication
|
|
21
|
+
* required". Derive `sessionless` from the helper the function was built with —
|
|
22
|
+
* the same rule `addFunctions` applies to named ones (`!== 'pikkuFunc'`).
|
|
23
|
+
*/
|
|
24
|
+
export declare function ensureInlineWiringFunction(state: InspectorState, pikkuFuncId: string, fallbackName: string | undefined, funcInitializer: ts.Node, checker: ts.TypeChecker, isHelper?: boolean): void;
|
|
@@ -207,6 +207,30 @@ export function ensureFunctionMetadata(state, pikkuFuncId, fallbackName, funcIni
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Register metadata for a `func:` inlined into a wiring (queue/scheduler/
|
|
212
|
+
* trigger/gateway/…).
|
|
213
|
+
*
|
|
214
|
+
* An inline function has no exported name, so `addFunctions` skips it (it bails
|
|
215
|
+
* on `__temp_` ids) and only the wiring's context-based id exists. Without this
|
|
216
|
+
* the wiring points at a function that was never registered and the worker
|
|
217
|
+
* resolves to nothing at runtime ("Missing generated metadata for ...").
|
|
218
|
+
*
|
|
219
|
+
* The stub also carries no session info, so the runtime would treat it as
|
|
220
|
+
* session-required and every invocation would fail with "Authentication
|
|
221
|
+
* required". Derive `sessionless` from the helper the function was built with —
|
|
222
|
+
* the same rule `addFunctions` applies to named ones (`!== 'pikkuFunc'`).
|
|
223
|
+
*/
|
|
224
|
+
export function ensureInlineWiringFunction(state, pikkuFuncId, fallbackName, funcInitializer, checker, isHelper) {
|
|
225
|
+
ensureFunctionMetadata(state, pikkuFuncId, fallbackName, funcInitializer, checker, isHelper);
|
|
226
|
+
const meta = state.functions.meta[pikkuFuncId];
|
|
227
|
+
if (meta &&
|
|
228
|
+
meta.sessionless === undefined &&
|
|
229
|
+
ts.isCallExpression(funcInitializer) &&
|
|
230
|
+
ts.isIdentifier(funcInitializer.expression)) {
|
|
231
|
+
meta.sessionless = funcInitializer.expression.text !== 'pikkuFunc';
|
|
232
|
+
}
|
|
233
|
+
}
|
|
210
234
|
function populateTypesLookup(state, pikkuFuncId, funcInitializer, checker) {
|
|
211
235
|
const typeArgs = funcInitializer.typeArguments;
|
|
212
236
|
if (typeArgs && typeArgs.length >= 1) {
|
|
@@ -334,6 +334,8 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
334
334
|
internalFiles: new Map(state.rpc.internalFiles),
|
|
335
335
|
exposedMeta: { ...state.rpc.exposedMeta },
|
|
336
336
|
exposedFiles: new Map(state.rpc.exposedFiles),
|
|
337
|
+
remoteMeta: { ...state.rpc.remoteMeta },
|
|
338
|
+
remoteFiles: new Map(state.rpc.remoteFiles),
|
|
337
339
|
invokedFunctions: new Set(state.rpc.invokedFunctions),
|
|
338
340
|
},
|
|
339
341
|
cli: {
|
|
@@ -746,6 +748,13 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
746
748
|
filteredState.rpc.exposedFiles.delete(key);
|
|
747
749
|
}
|
|
748
750
|
}
|
|
751
|
+
for (const key of Object.keys(filteredState.rpc.remoteMeta)) {
|
|
752
|
+
const targetFuncId = filteredState.rpc.remoteMeta[key];
|
|
753
|
+
if (!survivingFuncIds.has(targetFuncId) && !survivingFuncIds.has(key)) {
|
|
754
|
+
delete filteredState.rpc.remoteMeta[key];
|
|
755
|
+
filteredState.rpc.remoteFiles.delete(key);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
749
758
|
// Prune invokedFunctions to match surviving functions
|
|
750
759
|
for (const funcId of filteredState.rpc.invokedFunctions) {
|
|
751
760
|
if (!survivingFuncIds.has(funcId)) {
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import { ErrorCode } from '../error-codes.js';
|
|
3
|
+
export declare const getArrayPropertyValue: (obj: ts.ObjectLiteralExpression, propertyName: string) => string[] | null;
|
|
3
4
|
/**
|
|
4
|
-
* Extracts
|
|
5
|
+
* Extracts a string->string record from an object property.
|
|
6
|
+
*
|
|
7
|
+
* `getPropertyValue` falls through to `getText()` for an object literal, which
|
|
8
|
+
* returns the raw source text rather than the record — so callers that need the
|
|
9
|
+
* entries (e.g. oauth2 `additionalParams`) must use this instead.
|
|
5
10
|
*/
|
|
6
|
-
export declare const
|
|
11
|
+
export declare const getRecordPropertyValue: (obj: ts.ObjectLiteralExpression, propertyName: string) => Record<string, string> | null;
|
|
7
12
|
/**
|
|
8
13
|
* Wiring identity fields (`name`, `secretId`, `variableId`, …) are read
|
|
9
14
|
* STATICALLY from source — a const or variable reference is keyed by its
|
|
@@ -4,12 +4,21 @@ import { extractStringLiteral } from './extract-node-value.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Extracts an array of strings from an object property.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* Unwraps `x as const` / `x as any` / `x satisfies T` down to the underlying
|
|
9
|
+
* expression. Without this a cast makes the value invisible to the inspector,
|
|
10
|
+
* so it is silently dropped from meta rather than validated — and `as const` on
|
|
11
|
+
* an array is idiomatic enough that this is easy to hit by accident.
|
|
12
|
+
*/
|
|
13
|
+
const unwrapAs = (node) => ts.isAsExpression(node) || ts.isSatisfiesExpression(node)
|
|
14
|
+
? unwrapAs(node.expression)
|
|
15
|
+
: node;
|
|
7
16
|
export const getArrayPropertyValue = (obj, propertyName) => {
|
|
8
17
|
const property = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
9
18
|
ts.isIdentifier(p.name) &&
|
|
10
19
|
p.name.text === propertyName);
|
|
11
20
|
if (property && ts.isPropertyAssignment(property)) {
|
|
12
|
-
const initializer = property.initializer;
|
|
21
|
+
const initializer = unwrapAs(property.initializer);
|
|
13
22
|
if (ts.isArrayLiteralExpression(initializer)) {
|
|
14
23
|
return initializer.elements
|
|
15
24
|
.filter(ts.isStringLiteral)
|
|
@@ -18,6 +27,44 @@ export const getArrayPropertyValue = (obj, propertyName) => {
|
|
|
18
27
|
}
|
|
19
28
|
return null;
|
|
20
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* Extracts a string->string record from an object property.
|
|
32
|
+
*
|
|
33
|
+
* `getPropertyValue` falls through to `getText()` for an object literal, which
|
|
34
|
+
* returns the raw source text rather than the record — so callers that need the
|
|
35
|
+
* entries (e.g. oauth2 `additionalParams`) must use this instead.
|
|
36
|
+
*/
|
|
37
|
+
export const getRecordPropertyValue = (obj, propertyName) => {
|
|
38
|
+
const property = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
39
|
+
ts.isIdentifier(p.name) &&
|
|
40
|
+
p.name.text === propertyName);
|
|
41
|
+
if (!property || !ts.isPropertyAssignment(property)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const initializer = property.initializer;
|
|
45
|
+
if (!ts.isObjectLiteralExpression(initializer)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const record = {};
|
|
49
|
+
for (const prop of initializer.properties) {
|
|
50
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const key = ts.isIdentifier(prop.name)
|
|
54
|
+
? prop.name.text
|
|
55
|
+
: ts.isStringLiteral(prop.name)
|
|
56
|
+
? prop.name.text
|
|
57
|
+
: null;
|
|
58
|
+
if (key === null) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (ts.isStringLiteral(prop.initializer) ||
|
|
62
|
+
ts.isNoSubstitutionTemplateLiteral(prop.initializer)) {
|
|
63
|
+
record[key] = prop.initializer.text;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return Object.keys(record).length > 0 ? record : null;
|
|
67
|
+
};
|
|
21
68
|
/**
|
|
22
69
|
* Wiring identity fields (`name`, `secretId`, `variableId`, …) are read
|
|
23
70
|
* STATICALLY from source — a const or variable reference is keyed by its
|
|
@@ -80,6 +80,11 @@ export async function loadAddonFunctionsMeta(logger, state) {
|
|
|
80
80
|
return;
|
|
81
81
|
const require = createRequire(join(state.rootDir, 'package.json'));
|
|
82
82
|
for (const [namespace, decl] of wireAddonDeclarations) {
|
|
83
|
+
// Remote addons (wireRemoteAddon) ship as a devDependency: types only.
|
|
84
|
+
// Their functions, secrets, variables, schemas and services live on the
|
|
85
|
+
// HOST that runs them — never load or require any of that here.
|
|
86
|
+
if (decl.remote)
|
|
87
|
+
continue;
|
|
83
88
|
try {
|
|
84
89
|
const metaPath = require.resolve(`${decl.package}/.pikku/function/pikku-functions-meta.gen.json`);
|
|
85
90
|
const raw = await readFile(metaPath, 'utf-8');
|
|
@@ -108,32 +113,99 @@ export async function loadAddonFunctionsMeta(logger, state) {
|
|
|
108
113
|
const secretsRaw = await readFile(secretsMetaPath, 'utf-8');
|
|
109
114
|
const secretsMeta = JSON.parse(secretsRaw);
|
|
110
115
|
for (const [key, def] of Object.entries(secretsMeta)) {
|
|
111
|
-
|
|
116
|
+
// secretOverrides key on the SECRET ID (the string the addon passes to
|
|
117
|
+
// getSecret — its typed map is keyed by secretId, e.g.
|
|
118
|
+
// `getSecret('MAILGUN_CREDENTIALS')`), NOT the logical meta key, so the
|
|
119
|
+
// runtime aliaser (which also keys on secretId) and this merge agree.
|
|
120
|
+
// The resolved id is the real project secret; the addon's secretId is
|
|
121
|
+
// the default when no override is given. Two instances with different
|
|
122
|
+
// overrides therefore surface two distinct project secrets. `key` is
|
|
123
|
+
// the fallback for older meta that predates the secretId field.
|
|
124
|
+
const secretId = def.secretId ?? key;
|
|
125
|
+
const resolvedSecretId = decl.secretOverrides?.[secretId] ?? secretId;
|
|
126
|
+
const existing = state.secrets.definitions.find((d) => (d.secretId ?? d.name) === resolvedSecretId);
|
|
112
127
|
if (!existing) {
|
|
113
|
-
state.secrets.definitions.push(
|
|
114
|
-
|
|
128
|
+
state.secrets.definitions.push({
|
|
129
|
+
...def,
|
|
130
|
+
secretId: resolvedSecretId,
|
|
131
|
+
});
|
|
132
|
+
logger.debug(`Loaded addon secret '${resolvedSecretId}' from ${decl.package}`);
|
|
115
133
|
}
|
|
116
134
|
}
|
|
117
135
|
}
|
|
118
136
|
catch {
|
|
119
137
|
// No secrets meta — that's fine
|
|
120
138
|
}
|
|
139
|
+
// Load addon scopes meta. Without this an addon's own scopes never reach
|
|
140
|
+
// the host's ScopeId union or its declared set, so the pikku_scopes FK
|
|
141
|
+
// would refuse to grant one.
|
|
142
|
+
try {
|
|
143
|
+
const scopesMetaPath = require.resolve(`${decl.package}/.pikku/scopes/pikku-scopes-meta.gen.json`);
|
|
144
|
+
const scopesRaw = await readFile(scopesMetaPath, 'utf-8');
|
|
145
|
+
const scopesMeta = JSON.parse(scopesRaw);
|
|
146
|
+
for (const [key, def] of Object.entries(scopesMeta)) {
|
|
147
|
+
const existing = state.scopes.definitions.find((d) => d.name === key);
|
|
148
|
+
if (!existing) {
|
|
149
|
+
state.scopes.definitions.push(def);
|
|
150
|
+
logger.debug(`Loaded addon scope '${key}' from ${decl.package}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// No scopes meta — that's fine
|
|
156
|
+
}
|
|
121
157
|
// Load addon variables meta
|
|
122
158
|
try {
|
|
123
159
|
const variablesMetaPath = require.resolve(`${decl.package}/.pikku/variables/pikku-variables-meta.gen.json`);
|
|
124
160
|
const variablesRaw = await readFile(variablesMetaPath, 'utf-8');
|
|
125
161
|
const variablesMeta = JSON.parse(variablesRaw);
|
|
126
162
|
for (const [key, def] of Object.entries(variablesMeta)) {
|
|
127
|
-
|
|
163
|
+
// variableOverrides key on the VARIABLE ID (the string the addon reads
|
|
164
|
+
// via its typed map, keyed by variableId), same as secretOverrides key
|
|
165
|
+
// on secretId — so the runtime aliaser and this merge agree. `key` is
|
|
166
|
+
// the fallback for older meta without a variableId field.
|
|
167
|
+
const variableId = def.variableId ?? key;
|
|
168
|
+
const resolvedVariableId = decl.variableOverrides?.[variableId] ?? variableId;
|
|
169
|
+
const existing = state.variables.definitions.find((d) => (d.variableId ?? d.name) === resolvedVariableId);
|
|
128
170
|
if (!existing) {
|
|
129
|
-
state.variables.definitions.push(
|
|
130
|
-
|
|
171
|
+
state.variables.definitions.push({
|
|
172
|
+
...def,
|
|
173
|
+
variableId: resolvedVariableId,
|
|
174
|
+
});
|
|
175
|
+
logger.debug(`Loaded addon variable '${resolvedVariableId}' from ${decl.package}`);
|
|
131
176
|
}
|
|
132
177
|
}
|
|
133
178
|
}
|
|
134
179
|
catch {
|
|
135
180
|
// No variables meta — that's fine
|
|
136
181
|
}
|
|
182
|
+
// Load addon credentials meta. Without this an addon's OAuth2/wire
|
|
183
|
+
// credentials never reach the consuming app's CREDENTIAL_OAUTH2_CONFIGS,
|
|
184
|
+
// so the credential-oauth provider and BetterAuthCredentialService can't
|
|
185
|
+
// resolve them — the addon's Connect flow and status silently no-op.
|
|
186
|
+
try {
|
|
187
|
+
const credentialsMetaPath = require.resolve(`${decl.package}/.pikku/credentials/pikku-credentials-meta.gen.json`);
|
|
188
|
+
const credentialsRaw = await readFile(credentialsMetaPath, 'utf-8');
|
|
189
|
+
const credentialsMeta = JSON.parse(credentialsRaw);
|
|
190
|
+
for (const [key, def] of Object.entries(credentialsMeta)) {
|
|
191
|
+
// Each instance's credentialOverrides remap the addon's logical
|
|
192
|
+
// credential name to a project credential — same mechanic as secrets
|
|
193
|
+
// and variables above. The credential name doubles as the better-auth
|
|
194
|
+
// providerId (accounts keyed by providerId+userId), so two instances
|
|
195
|
+
// with different overrides surface two distinct OAuth providers rather
|
|
196
|
+
// than sharing one account pool. The logical name is the default when
|
|
197
|
+
// no override is given.
|
|
198
|
+
const resolvedName = decl.credentialOverrides?.[key] ?? key;
|
|
199
|
+
const existing = state.credentials.definitions.find((d) => d.name === resolvedName);
|
|
200
|
+
if (!existing) {
|
|
201
|
+
state.credentials.definitions.push({ ...def, name: resolvedName });
|
|
202
|
+
logger.debug(`Loaded addon credential '${resolvedName}' from ${decl.package}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// No credentials meta — that's fine
|
|
208
|
+
}
|
|
137
209
|
// Load addon serverlessIncompatible service names from pikku-addon-meta.gen.json
|
|
138
210
|
let loadedParentServices = false;
|
|
139
211
|
try {
|
|
@@ -215,6 +287,9 @@ export async function loadAddonSchemas(logger, state) {
|
|
|
215
287
|
return;
|
|
216
288
|
const require = createRequire(join(state.rootDir, 'package.json'));
|
|
217
289
|
for (const [namespace, decl] of wireAddonDeclarations) {
|
|
290
|
+
// Remote addons carry no local schemas — their funcs run on the host.
|
|
291
|
+
if (decl.remote)
|
|
292
|
+
continue;
|
|
218
293
|
try {
|
|
219
294
|
const metaPath = require.resolve(`${decl.package}/.pikku/function/pikku-functions-meta.gen.json`);
|
|
220
295
|
const schemasDir = join(dirname(metaPath), '..', 'schemas', 'schemas');
|
|
@@ -16,28 +16,8 @@ export declare function extractPermissionPikkuNames(node: ts.Expression, checker
|
|
|
16
16
|
* Returns the initializer node for the 'permissions' property if it exists
|
|
17
17
|
*/
|
|
18
18
|
export declare function getPermissionsNode(obj: ts.ObjectLiteralExpression): ts.Expression | undefined;
|
|
19
|
-
/**
|
|
20
|
-
* Check if a route matches a pattern with wildcards
|
|
21
|
-
* Pattern can be exact match or use * as wildcard
|
|
22
|
-
* e.g., '/api/*' matches '/api/users', '/api/posts/123', etc.
|
|
23
|
-
*/
|
|
24
|
-
export declare function routeMatchesPattern(route: string, pattern: string): boolean;
|
|
25
|
-
/**
|
|
26
|
-
* Resolve permissions for an HTTP wiring based on:
|
|
27
|
-
* 1. Global HTTP permissions (addHTTPPermission('*', [...]))
|
|
28
|
-
* 2. Route-specific HTTP permissions (addHTTPPermission('/pattern', [...]))
|
|
29
|
-
* 3. Tag-based permissions (addPermission('tag', [...]))
|
|
30
|
-
* 4. Explicit wiring permissions (wireHTTP({ permissions: [...] }))
|
|
31
|
-
* Returns undefined if no permissions are found, otherwise returns array with at least one item
|
|
32
|
-
*/
|
|
33
|
-
export declare function resolveHTTPPermissions(state: InspectorState, route: string, tags: string[] | undefined, explicitPermissionsNode: ts.Expression | undefined, checker: ts.TypeChecker): PermissionMetadata[] | undefined;
|
|
34
19
|
/**
|
|
35
20
|
* Convenience wrapper: Extract permissions node from object and resolve
|
|
36
21
|
* Use this in add-* files for cleaner code
|
|
37
22
|
*/
|
|
38
|
-
export declare function resolvePermissions(state: InspectorState, obj: ts.ObjectLiteralExpression,
|
|
39
|
-
/**
|
|
40
|
-
* Convenience wrapper for HTTP: Extract permissions and resolve with HTTP-specific logic
|
|
41
|
-
* Use this in add-http-route.ts for cleaner code
|
|
42
|
-
*/
|
|
43
|
-
export declare function resolveHTTPPermissionsFromObject(state: InspectorState, route: string, obj: ts.ObjectLiteralExpression, tags: string[] | undefined, checker: ts.TypeChecker): PermissionMetadata[] | undefined;
|
|
23
|
+
export declare function resolvePermissions(state: InspectorState, obj: ts.ObjectLiteralExpression, _tags: string[] | undefined, checker: ts.TypeChecker): PermissionMetadata[] | undefined;
|
|
@@ -57,122 +57,31 @@ export function getPermissionsNode(obj) {
|
|
|
57
57
|
return permissionsProp?.initializer;
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
60
|
+
* Resolve the explicit permission references declared on a function (or agent).
|
|
61
|
+
* Permissions are function-scoped only: a `permissions: { group: [fn] }` object
|
|
62
|
+
* is flattened to `{ type: 'wire', name }` references used at filter time.
|
|
63
|
+
* Returns undefined if none are declared, otherwise an array with ≥1 item.
|
|
63
64
|
*/
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
return
|
|
67
|
-
// Convert pattern to regex: replace * with .*
|
|
68
|
-
const regexPattern = pattern
|
|
69
|
-
.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape regex special chars except *
|
|
70
|
-
.replace(/\*/g, '.*'); // Replace * with .*
|
|
71
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
72
|
-
return regex.test(route);
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Resolve permissions for an HTTP wiring based on:
|
|
76
|
-
* 1. Global HTTP permissions (addHTTPPermission('*', [...]))
|
|
77
|
-
* 2. Route-specific HTTP permissions (addHTTPPermission('/pattern', [...]))
|
|
78
|
-
* 3. Tag-based permissions (addPermission('tag', [...]))
|
|
79
|
-
* 4. Explicit wiring permissions (wireHTTP({ permissions: [...] }))
|
|
80
|
-
* Returns undefined if no permissions are found, otherwise returns array with at least one item
|
|
81
|
-
*/
|
|
82
|
-
export function resolveHTTPPermissions(state, route, tags, explicitPermissionsNode, checker) {
|
|
83
|
-
const resolved = [];
|
|
84
|
-
// 1. HTTP route permission groups (includes '*' for global)
|
|
85
|
-
for (const [pattern, _groupMeta] of state.http.routePermissions.entries()) {
|
|
86
|
-
if (routeMatchesPattern(route, pattern)) {
|
|
87
|
-
// Just reference the group by route pattern
|
|
88
|
-
resolved.push({
|
|
89
|
-
type: 'http',
|
|
90
|
-
route: pattern,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
// 2. Tag-based permission groups
|
|
95
|
-
if (tags && tags.length > 0) {
|
|
96
|
-
for (const tag of tags) {
|
|
97
|
-
if (state.permissions.tagPermissions.has(tag)) {
|
|
98
|
-
// Just reference the group by tag
|
|
99
|
-
resolved.push({
|
|
100
|
-
type: 'tag',
|
|
101
|
-
tag,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// 3. Explicit wire permissions (inline is OK here)
|
|
107
|
-
if (explicitPermissionsNode) {
|
|
108
|
-
const permissionNames = extractPermissionPikkuNames(explicitPermissionsNode, checker, state.rootDir);
|
|
109
|
-
for (const name of permissionNames) {
|
|
110
|
-
const def = state.permissions.definitions[name];
|
|
111
|
-
resolved.push({
|
|
112
|
-
type: 'wire',
|
|
113
|
-
name,
|
|
114
|
-
inline: def?.exportedName === null,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
65
|
+
function resolveExplicitPermissions(state, explicitPermissionsNode, checker) {
|
|
66
|
+
if (!explicitPermissionsNode) {
|
|
67
|
+
return undefined;
|
|
117
68
|
}
|
|
118
|
-
return resolved.length > 0 ? resolved : undefined;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Resolve tag-based and explicit permissions (common logic for wires and functions)
|
|
122
|
-
* 1. Tag-based permissions (addPermission('tag', [...]))
|
|
123
|
-
* 2. Explicit permissions (wireHTTP/pikkuFunc({ permissions: [...] }))
|
|
124
|
-
*/
|
|
125
|
-
function resolveTagAndExplicitPermissions(state, tags, explicitPermissionsNode, checker) {
|
|
126
69
|
const resolved = [];
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// 2. Explicit permissions (inline is OK here - used directly in wire/function)
|
|
140
|
-
if (explicitPermissionsNode) {
|
|
141
|
-
const permissionNames = extractPermissionPikkuNames(explicitPermissionsNode, checker, state.rootDir);
|
|
142
|
-
for (const name of permissionNames) {
|
|
143
|
-
const def = state.permissions.definitions[name];
|
|
144
|
-
resolved.push({
|
|
145
|
-
type: 'wire',
|
|
146
|
-
name,
|
|
147
|
-
inline: def?.exportedName === null,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
70
|
+
const permissionNames = extractPermissionPikkuNames(explicitPermissionsNode, checker, state.rootDir);
|
|
71
|
+
for (const name of permissionNames) {
|
|
72
|
+
const def = state.permissions.definitions[name];
|
|
73
|
+
resolved.push({
|
|
74
|
+
type: 'wire',
|
|
75
|
+
name,
|
|
76
|
+
inline: def?.exportedName === null,
|
|
77
|
+
});
|
|
150
78
|
}
|
|
151
|
-
return resolved;
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Resolve permissions for a function based on:
|
|
155
|
-
* 1. Tag-based permissions (addPermission('tag', [...]))
|
|
156
|
-
* 2. Explicit function permissions (pikkuFunc({ permissions: [...] }))
|
|
157
|
-
* Returns undefined if no permissions are found, otherwise returns array with at least one item
|
|
158
|
-
*/
|
|
159
|
-
function resolveFunctionPermissionsInternal(state, tags, explicitPermissionsNode, checker) {
|
|
160
|
-
const resolved = resolveTagAndExplicitPermissions(state, tags, explicitPermissionsNode, checker);
|
|
161
79
|
return resolved.length > 0 ? resolved : undefined;
|
|
162
80
|
}
|
|
163
81
|
/**
|
|
164
82
|
* Convenience wrapper: Extract permissions node from object and resolve
|
|
165
83
|
* Use this in add-* files for cleaner code
|
|
166
84
|
*/
|
|
167
|
-
export function resolvePermissions(state, obj,
|
|
168
|
-
|
|
169
|
-
return resolveFunctionPermissionsInternal(state, tags, explicitPermissionsNode, checker);
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Convenience wrapper for HTTP: Extract permissions and resolve with HTTP-specific logic
|
|
173
|
-
* Use this in add-http-route.ts for cleaner code
|
|
174
|
-
*/
|
|
175
|
-
export function resolveHTTPPermissionsFromObject(state, route, obj, tags, checker) {
|
|
176
|
-
const explicitPermissionsNode = getPermissionsNode(obj);
|
|
177
|
-
return resolveHTTPPermissions(state, route, tags, explicitPermissionsNode, checker);
|
|
85
|
+
export function resolvePermissions(state, obj, _tags, checker) {
|
|
86
|
+
return resolveExplicitPermissions(state, getPermissionsNode(obj), checker);
|
|
178
87
|
}
|