@pikku/inspector 0.12.109 → 0.12.111

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.
Files changed (46) hide show
  1. package/dist/add/add-channel.js +8 -0
  2. package/dist/add/add-file-with-factory.d.ts +7 -1
  3. package/dist/add/add-file-with-factory.js +63 -2
  4. package/dist/add/add-functions.js +32 -1
  5. package/dist/add/add-pikku-studio.d.ts +15 -0
  6. package/dist/add/add-pikku-studio.js +307 -0
  7. package/dist/add/add-rpc-invocations.d.ts +1 -1
  8. package/dist/add/add-rpc-invocations.js +14 -2
  9. package/dist/add/add-wire-remote-addon.js +25 -4
  10. package/dist/add/check-disabled-references.d.ts +27 -0
  11. package/dist/add/check-disabled-references.js +226 -0
  12. package/dist/error-codes.d.ts +14 -0
  13. package/dist/error-codes.js +14 -0
  14. package/dist/finalize.js +3 -1
  15. package/dist/index.d.ts +6 -1
  16. package/dist/index.js +3 -0
  17. package/dist/initial-state.js +1 -0
  18. package/dist/inspector.js +4 -0
  19. package/dist/lite.d.ts +7 -1
  20. package/dist/lite.js +5 -1
  21. package/dist/types.d.ts +65 -13
  22. package/dist/utils/addon-dependencies.d.ts +43 -0
  23. package/dist/utils/addon-dependencies.js +91 -0
  24. package/dist/utils/contract-hashes.js +2 -2
  25. package/dist/utils/find-unused-functions.d.ts +20 -0
  26. package/dist/utils/find-unused-functions.js +132 -0
  27. package/dist/utils/find-wire-remote-addon-specs.d.ts +17 -0
  28. package/dist/utils/find-wire-remote-addon-specs.js +53 -0
  29. package/dist/utils/get-property-value.js +4 -1
  30. package/dist/utils/load-addon-functions-meta.js +69 -46
  31. package/dist/utils/post-process.js +58 -8
  32. package/dist/utils/remote-addon-sdk.d.ts +57 -0
  33. package/dist/utils/remote-addon-sdk.js +111 -0
  34. package/dist/utils/resolve-addon-package.d.ts +1 -0
  35. package/dist/utils/resolve-addon-package.js +1 -1
  36. package/dist/utils/resolve-versions.js +18 -0
  37. package/dist/utils/serialize-inspector-state.d.ts +5 -2
  38. package/dist/utils/serialize-inspector-state.js +12 -4
  39. package/dist/utils/validate-addon-zod-contract.d.ts +23 -0
  40. package/dist/utils/validate-addon-zod-contract.js +70 -0
  41. package/dist/utils/validate-exposed-functions-gated.d.ts +3 -1
  42. package/dist/utils/validate-exposed-functions-gated.js +15 -8
  43. package/dist/visit.js +6 -3
  44. package/package.json +3 -3
  45. package/dist/add/add-define-screens.d.ts +0 -8
  46. package/dist/add/add-define-screens.js +0 -140
@@ -0,0 +1,226 @@
1
+ import * as ts from 'typescript';
2
+ import { relative } from 'node:path';
3
+ import { ErrorCode } from '../error-codes.js';
4
+ /**
5
+ * A function's `disabled`, read from its config: `true`, or a non-empty string
6
+ * saying why. Anything else the build cannot read statically is refused rather
7
+ * than taken as enabled.
8
+ */
9
+ export const readFunctionDisabled = (obj, name, logger) => {
10
+ for (const prop of obj.properties) {
11
+ if (!ts.isPropertyAssignment(prop) ||
12
+ !ts.isIdentifier(prop.name) ||
13
+ prop.name.text !== 'disabled') {
14
+ continue;
15
+ }
16
+ const init = prop.initializer;
17
+ if (init.kind === ts.SyntaxKind.TrueKeyword)
18
+ return {};
19
+ if (init.kind === ts.SyntaxKind.FalseKeyword)
20
+ return null;
21
+ if ((ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) &&
22
+ init.text !== '') {
23
+ return { reason: init.text };
24
+ }
25
+ logger.critical(ErrorCode.INVALID_VALUE, `'${name}' has a \`disabled\` the build cannot read. Use \`true\` or a non-empty string literal.`);
26
+ return {};
27
+ }
28
+ return null;
29
+ };
30
+ const describe = (state, meta) => `'${meta.name}' is disabled${meta.reason ? ` (${meta.reason})` : ''} in ${relative(state.rootDir, meta.sourceFile)}`;
31
+ const position = (state, node) => {
32
+ const sourceFile = node.getSourceFile();
33
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
34
+ return `${relative(state.rootDir, sourceFile.fileName)}:${line + 1}`;
35
+ };
36
+ /** The call a reference sits in, by its callee: `wireHTTP`, `rpc.invoke`. */
37
+ const enclosingCall = (node) => {
38
+ for (let current = node.parent; current; current = current.parent) {
39
+ if (ts.isCallExpression(current) &&
40
+ (ts.isIdentifier(current.expression) ||
41
+ ts.isPropertyAccessExpression(current.expression))) {
42
+ return current.expression.getText();
43
+ }
44
+ }
45
+ return undefined;
46
+ };
47
+ /**
48
+ * Records a function declared with `disabled`, in place of everything
49
+ * addFunctions would otherwise record. A function defined inline in what
50
+ * uses it is referenced by that, so it is refused instead.
51
+ */
52
+ export const recordDisabledFunction = (logger, state, node, pikkuFuncId, meta) => {
53
+ const parent = node.parent;
54
+ if (ts.isVariableDeclaration(parent) && parent.initializer === node) {
55
+ state.functions.disabled[pikkuFuncId] = meta;
56
+ return;
57
+ }
58
+ const call = enclosingCall(node);
59
+ logger.critical(ErrorCode.DISABLED_FUNCTION_REFERENCED, `A function declared \`disabled\` is defined inline at ${position(state, node)}` +
60
+ (call ? ` inside ${call}(...)` : '') +
61
+ `, which still uses it. Remove what uses it, or remove \`disabled\`.`);
62
+ };
63
+ const byLocalName = new WeakMap();
64
+ /** Disabled functions by the name code reaches them by: the export, else the name. */
65
+ const disabledByLocalName = (state) => {
66
+ let index = byLocalName.get(state);
67
+ if (index)
68
+ return index;
69
+ index = new Map();
70
+ for (const meta of Object.values(state.functions.disabled)) {
71
+ const local = meta.exportedName ?? meta.name;
72
+ index.set(local, [...(index.get(local) ?? []), meta]);
73
+ }
74
+ byLocalName.set(state, index);
75
+ return index;
76
+ };
77
+ /** Whether a node is inside the definition of a disabled function, whose body is dead too. */
78
+ const isInsideDisabled = (state, node) => {
79
+ const fileName = node.getSourceFile().fileName;
80
+ const index = disabledByLocalName(state);
81
+ for (let current = node.parent; current; current = current.parent) {
82
+ if (ts.isVariableDeclaration(current) &&
83
+ ts.isIdentifier(current.name) &&
84
+ index.get(current.name.text)?.some((meta) => meta.sourceFile === fileName)) {
85
+ return true;
86
+ }
87
+ }
88
+ return false;
89
+ };
90
+ const isNotAReference = (node) => {
91
+ const parent = node.parent;
92
+ if (ts.isImportSpecifier(parent) ||
93
+ ts.isExportSpecifier(parent) ||
94
+ ts.isImportClause(parent) ||
95
+ ts.isNamespaceImport(parent) ||
96
+ ts.isTypeQueryNode(parent) ||
97
+ ts.isQualifiedName(parent)) {
98
+ return true;
99
+ }
100
+ if (ts.isShorthandPropertyAssignment(parent))
101
+ return false;
102
+ return 'name' in parent && parent.name === node;
103
+ };
104
+ const resolveDeclaration = (node, checker) => {
105
+ let symbol = ts.isShorthandPropertyAssignment(node.parent)
106
+ ? checker.getShorthandAssignmentValueSymbol(node.parent)
107
+ : checker.getSymbolAtLocation(node);
108
+ if (symbol && symbol.flags & ts.SymbolFlags.Alias) {
109
+ symbol = checker.getAliasedSymbol(symbol);
110
+ }
111
+ return symbol?.valueDeclaration ?? symbol?.declarations?.[0];
112
+ };
113
+ /** `rpc.invoke('x')` / `rpc.mesh('x')`, seen through `!` and parentheses. */
114
+ const invokedRpcName = (node) => {
115
+ const callee = node.expression;
116
+ if (!ts.isPropertyAccessExpression(callee) ||
117
+ (callee.name.text !== 'invoke' && callee.name.text !== 'mesh')) {
118
+ return undefined;
119
+ }
120
+ let receiver = callee.expression;
121
+ while (ts.isNonNullExpression(receiver) ||
122
+ ts.isParenthesizedExpression(receiver)) {
123
+ receiver = receiver.expression;
124
+ }
125
+ const isRpc = ts.isIdentifier(receiver)
126
+ ? receiver.text === 'rpc'
127
+ : ts.isPropertyAccessExpression(receiver) && receiver.name.text === 'rpc';
128
+ const [first] = node.arguments;
129
+ if (!isRpc ||
130
+ !first ||
131
+ !(ts.isStringLiteral(first) || ts.isNoSubstitutionTemplateLiteral(first))) {
132
+ return undefined;
133
+ }
134
+ return first.text;
135
+ };
136
+ /**
137
+ * Whether a node sits in a generated file. This run rewrites those from its own
138
+ * state, which leaves disabled functions out, so a reference there is the last
139
+ * run's and is about to go.
140
+ */
141
+ const isGenerated = (node) => {
142
+ const fileName = node.getSourceFile().fileName;
143
+ return fileName.endsWith('.gen.ts') || fileName.endsWith('.gen.js');
144
+ };
145
+ /**
146
+ * PKU562: anything that still references a disabled function — a wiring, an
147
+ * agent's tools, an `rpc.invoke` by name, any other code. Runs in the routes
148
+ * sweep, after every function is registered, so it sees the whole set.
149
+ */
150
+ export const checkDisabledReferences = (logger, node, checker, state) => {
151
+ if (Object.keys(state.functions.disabled).length === 0)
152
+ return;
153
+ if (isGenerated(node))
154
+ return;
155
+ if (ts.isCallExpression(node)) {
156
+ const invoked = invokedRpcName(node);
157
+ if (invoked === undefined)
158
+ return;
159
+ const meta = Object.values(state.functions.disabled).find((m) => m.name === invoked);
160
+ if (!meta || isInsideDisabled(state, node))
161
+ return;
162
+ logger.critical(ErrorCode.DISABLED_FUNCTION_REFERENCED, `${describe(state, meta)}, but ${node.expression.getText()}('${invoked}') calls it at ${position(state, node)}. ` +
163
+ `Remove the call, or remove \`disabled\`.`);
164
+ return;
165
+ }
166
+ if (!ts.isIdentifier(node))
167
+ return;
168
+ const candidates = disabledByLocalName(state).get(node.text);
169
+ if (!candidates || isNotAReference(node))
170
+ return;
171
+ const decl = resolveDeclaration(node, checker);
172
+ if (!decl || !ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name)) {
173
+ return;
174
+ }
175
+ const declFile = decl.getSourceFile().fileName;
176
+ const declName = decl.name.text;
177
+ const meta = candidates.find((m) => m.sourceFile === declFile && (m.exportedName ?? m.name) === declName);
178
+ if (!meta || isInsideDisabled(state, node))
179
+ return;
180
+ const call = enclosingCall(node);
181
+ logger.critical(ErrorCode.DISABLED_FUNCTION_REFERENCED, `${describe(state, meta)}, but ` +
182
+ (call ? `${call}(...) references it` : 'it is referenced') +
183
+ ` at ${position(state, node)}. Remove the reference, or remove \`disabled\`.`);
184
+ };
185
+ /** Every `rpcName` in a workflow's meta, wherever in its steps it sits. */
186
+ const collectRpcNames = (value, into) => {
187
+ if (Array.isArray(value)) {
188
+ for (const item of value)
189
+ collectRpcNames(item, into);
190
+ return;
191
+ }
192
+ if (!value || typeof value !== 'object')
193
+ return;
194
+ for (const [key, child] of Object.entries(value)) {
195
+ if (key === 'rpcName' && typeof child === 'string')
196
+ into.add(child);
197
+ else
198
+ collectRpcNames(child, into);
199
+ }
200
+ };
201
+ /**
202
+ * PKU562 for workflow steps, which name the function they call as a string
203
+ * the workflow meta already holds. Runs once, after the routes sweep.
204
+ */
205
+ export const checkDisabledWorkflowSteps = (logger, state) => {
206
+ const disabled = Object.values(state.functions.disabled);
207
+ if (disabled.length === 0)
208
+ return;
209
+ const sections = [
210
+ ['workflow', state.workflows.meta],
211
+ ['workflow graph', state.workflows.graphMeta],
212
+ ];
213
+ for (const [kind, section] of sections) {
214
+ for (const [workflowName, meta] of Object.entries(section)) {
215
+ const names = new Set();
216
+ collectRpcNames(meta, names);
217
+ for (const rpcName of [...names].sort()) {
218
+ const target = disabled.find((m) => m.name === rpcName);
219
+ if (!target)
220
+ continue;
221
+ logger.critical(ErrorCode.DISABLED_FUNCTION_REFERENCED, `${describe(state, target)}, but ${kind} '${workflowName}' calls it as a step. ` +
222
+ `Remove the step, or remove \`disabled\`.`);
223
+ }
224
+ }
225
+ }
226
+ };
@@ -42,6 +42,16 @@ export declare enum ErrorCode {
42
42
  ADDON_USES_NOT_WIRED = "PKU347",
43
43
  ADDON_NEEDS_NOT_MAPPED = "PKU348",
44
44
  ADDON_CONTENT_GRANT_INVALID = "PKU349",
45
+ REMOTE_ADDON_PACKAGE_NOT_STATIC = "PKU350",
46
+ REMOTE_ADDON_SDK_CORE_MISMATCH = "PKU351",
47
+ REMOTE_ADDON_SDK_INTEGRITY_MISMATCH = "PKU352",
48
+ REMOTE_ADDON_PACKAGE_INVALID = "PKU353",
49
+ ADDON_DEPENDENCY_VERSION_MISMATCH = "PKU354",
50
+ ADDON_USES_NOT_DECLARED = "PKU355",
51
+ ADDON_DEPENDENCY_INVALID = "PKU356",
52
+ STUDIO_SCREEN_PATH_DUPLICATE = "PKU357",
53
+ STUDIO_MESSAGE_KEY_MISSING = "PKU358",
54
+ STUDIO_BADGE_FUNCTION_NOT_FOUND = "PKU359",
45
55
  DB_COLUMN_NAME_TYPE_CONTRADICTION = "PKU480",
46
56
  DB_JSON_COLUMN_UNTYPED = "PKU481",
47
57
  DB_FORMAT_ON_NON_STRING = "PKU482",
@@ -52,6 +62,9 @@ export declare enum ErrorCode {
52
62
  SCHEMA_REFERENCE_UNRESOLVED = "PKU463",
53
63
  INLINE_SCHEMA = "PKU489",
54
64
  FUNCTION_METADATA_NOT_FOUND = "PKU559",
65
+ DISABLED_FUNCTION_REFERENCED = "PKU562",
66
+ UNUSED_FUNCTION = "PKU563",
67
+ FACTORY_NOT_A_DIRECT_CALL = "PKU564",
55
68
  DUPLICATE_AUTH_DEFINITION = "PKU581",
56
69
  AUTH_NOT_EXPORTED = "PKU582",
57
70
  DUPLICATE_VIRTUAL_USERS_DEFINITION = "PKU585",
@@ -89,6 +102,7 @@ export declare enum ErrorCode {
89
102
  PII_IN_OUTPUT = "PKU910",
90
103
  ADDON_WIRING_NOT_ALLOWED = "PKU920",
91
104
  ADDON_CONTRACT_HANDLERS_NOT_ALLOWED = "PKU921",
105
+ ADDON_REMOTE_CONTRACT_NOT_ZOD = "PKU922",
92
106
  RPC_INVOCATION_TYPE_CAST = "PKU940",
93
107
  KNOWLEDGE_NOTE_MISSING = "PKU960",
94
108
  SECRET_SERVICE_ALIASED = "PKU950",
@@ -45,6 +45,16 @@ export var ErrorCode;
45
45
  ErrorCode["ADDON_USES_NOT_WIRED"] = "PKU347";
46
46
  ErrorCode["ADDON_NEEDS_NOT_MAPPED"] = "PKU348";
47
47
  ErrorCode["ADDON_CONTENT_GRANT_INVALID"] = "PKU349";
48
+ ErrorCode["REMOTE_ADDON_PACKAGE_NOT_STATIC"] = "PKU350";
49
+ ErrorCode["REMOTE_ADDON_SDK_CORE_MISMATCH"] = "PKU351";
50
+ ErrorCode["REMOTE_ADDON_SDK_INTEGRITY_MISMATCH"] = "PKU352";
51
+ ErrorCode["REMOTE_ADDON_PACKAGE_INVALID"] = "PKU353";
52
+ ErrorCode["ADDON_DEPENDENCY_VERSION_MISMATCH"] = "PKU354";
53
+ ErrorCode["ADDON_USES_NOT_DECLARED"] = "PKU355";
54
+ ErrorCode["ADDON_DEPENDENCY_INVALID"] = "PKU356";
55
+ ErrorCode["STUDIO_SCREEN_PATH_DUPLICATE"] = "PKU357";
56
+ ErrorCode["STUDIO_MESSAGE_KEY_MISSING"] = "PKU358";
57
+ ErrorCode["STUDIO_BADGE_FUNCTION_NOT_FOUND"] = "PKU359";
48
58
  // Database schema codegen warnings
49
59
  ErrorCode["DB_COLUMN_NAME_TYPE_CONTRADICTION"] = "PKU480";
50
60
  ErrorCode["DB_JSON_COLUMN_UNTYPED"] = "PKU481";
@@ -58,6 +68,9 @@ export var ErrorCode;
58
68
  ErrorCode["INLINE_SCHEMA"] = "PKU489";
59
69
  // Function errors
60
70
  ErrorCode["FUNCTION_METADATA_NOT_FOUND"] = "PKU559";
71
+ ErrorCode["DISABLED_FUNCTION_REFERENCED"] = "PKU562";
72
+ ErrorCode["UNUSED_FUNCTION"] = "PKU563";
73
+ ErrorCode["FACTORY_NOT_A_DIRECT_CALL"] = "PKU564";
61
74
  // Auth errors
62
75
  ErrorCode["DUPLICATE_AUTH_DEFINITION"] = "PKU581";
63
76
  ErrorCode["AUTH_NOT_EXPORTED"] = "PKU582";
@@ -114,6 +127,7 @@ export var ErrorCode;
114
127
  // Addon authoring errors
115
128
  ErrorCode["ADDON_WIRING_NOT_ALLOWED"] = "PKU920";
116
129
  ErrorCode["ADDON_CONTRACT_HANDLERS_NOT_ALLOWED"] = "PKU921";
130
+ ErrorCode["ADDON_REMOTE_CONTRACT_NOT_ZOD"] = "PKU922";
117
131
  ErrorCode["RPC_INVOCATION_TYPE_CAST"] = "PKU940";
118
132
  // Knowledge base errors
119
133
  ErrorCode["KNOWLEDGE_NOTE_MISSING"] = "PKU960";
package/dist/finalize.js CHANGED
@@ -77,7 +77,9 @@ export const finalizeInspectorState = async (logger, analyzed, options = {}) =>
77
77
  annotateHttpRouteAuth(state);
78
78
  // Last of the validators: they read function meta and every wireAddon
79
79
  // declaration together, so they need the whole program inspected.
80
- validateExposedFunctionsGated(logger, state);
80
+ validateExposedFunctionsGated(logger, state, {
81
+ isAddon: options.isAddon === true,
82
+ });
81
83
  validateTagsResolveToMiddleware(logger, state);
82
84
  if (options.openAPI) {
83
85
  state.openAPISpec = await generateOpenAPISpec(logger, state.functions.meta, state.http.meta, state.schemas, options.openAPI.additionalInfo, pikkuState(null, 'misc', 'errors'));
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { inspect, analyzeProgram } from './inspector.js';
2
2
  export { getInitialInspectorState } from './initial-state.js';
3
3
  export { finalizeInspectorState } from './finalize.js';
4
4
  export type { TypesMap } from './types-map.js';
5
- export type { AddonConfig, AuthDefinition, CredentialOverrideMeta, ScreensManifestMeta, AddonScreenMeta, AddonContributorRef, AddonContributorsMeta, InspectorDiagnostic, InspectorFilters, InspectorHTTPState, InspectorLogger, InspectorMiddlewareState, InspectorOptions, InspectorPermissionState, InspectorServiceTypes, InspectorState, MetaInputTypes, MiddlewareGroupMeta, MiddlewareRegistration, SchemaRef, } from './types.js';
5
+ export type { AddonConfig, AuthDefinition, CredentialOverrideMeta, StudioMeta, StudioScreenMeta, StudioNavMeta, AddonContributorRef, AddonContributorsMeta, InspectorDiagnostic, InspectorFilters, InspectorHTTPState, InspectorLogger, InspectorMiddlewareState, InspectorOptions, InspectorPermissionState, InspectorServiceTypes, InspectorState, MetaInputTypes, MiddlewareGroupMeta, MiddlewareRegistration, SchemaRef, } from './types.js';
6
6
  export type { FilesAndMethodsErrors } from './utils/get-files-and-methods.js';
7
7
  export { ErrorCode } from './error-codes.js';
8
8
  export type { DiagnosticSeverity, CodedDiagnostic } from './error-codes.js';
@@ -16,6 +16,7 @@ export { incompatibleServicesFor, resolveDeployTarget, } from './utils/resolve-d
16
16
  export { generateCustomTypes, customTypeNames, sanitizeTypeName, } from './utils/custom-types-generator.js';
17
17
  export type { GenerateCustomTypesOptions } from './utils/custom-types-generator.js';
18
18
  export { unresolvedSchemaReferences } from './utils/post-process.js';
19
+ export { findUnusedFunctions, type UnusedFunction, } from './utils/find-unused-functions.js';
19
20
  export type { SurfaceUsageCounts } from './utils/extract-surface-usage.js';
20
21
  export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
21
22
  export type { ContractEntry, VersionHashEntry, VersionValidateError, VersionManifest, VersionManifestEntry, } from './utils/contract-hashes.js';
@@ -30,3 +31,7 @@ export { readModuleSpecifiers } from './utils/read-module-specifiers.js';
30
31
  export { readJsxLiteralText } from './utils/read-jsx-literal-text.js';
31
32
  export { readTsconfigOutDir } from './utils/read-tsconfig-out-dir.js';
32
33
  export { addonResolutionDirs, createAddonResolver, } from './utils/addon-resolution.js';
34
+ export { ADDONS_LOCK_FILE, REMOTE_SDK_DIR, SDK_INTEGRITY_MARKER, parseRemoteAddonPackage, emptyAddonsLock, readAddonsLock, serializeAddonsLock, remoteSdkDir, readSdkManifest, checkLockedSdk, } from './utils/remote-addon-sdk.js';
35
+ export { findWireRemoteAddonSpecs } from './utils/find-wire-remote-addon-specs.js';
36
+ export type { AddonsLock, AddonsLockEntry, RemoteAddonSdkManifest, RemoteAddonPackageSpec, LockedSdk, } from './utils/remote-addon-sdk.js';
37
+ export type { WireRemoteAddonSpec } from './utils/find-wire-remote-addon-specs.js';
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ export { filterInspectorState } from './utils/filter-inspector-state.js';
9
9
  export { incompatibleServicesFor, resolveDeployTarget, } from './utils/resolve-deploy-target.js';
10
10
  export { generateCustomTypes, customTypeNames, sanitizeTypeName, } from './utils/custom-types-generator.js';
11
11
  export { unresolvedSchemaReferences } from './utils/post-process.js';
12
+ export { findUnusedFunctions, } from './utils/find-unused-functions.js';
12
13
  export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
13
14
  export { serializeMCPJson } from './utils/serialize-mcp-json.js';
14
15
  export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/deserialize-dsl-workflow.js';
@@ -18,3 +19,5 @@ export { readModuleSpecifiers } from './utils/read-module-specifiers.js';
18
19
  export { readJsxLiteralText } from './utils/read-jsx-literal-text.js';
19
20
  export { readTsconfigOutDir } from './utils/read-tsconfig-out-dir.js';
20
21
  export { addonResolutionDirs, createAddonResolver, } from './utils/addon-resolution.js';
22
+ export { ADDONS_LOCK_FILE, REMOTE_SDK_DIR, SDK_INTEGRITY_MARKER, parseRemoteAddonPackage, emptyAddonsLock, readAddonsLock, serializeAddonsLock, remoteSdkDir, readSdkManifest, checkLockedSdk, } from './utils/remote-addon-sdk.js';
23
+ export { findWireRemoteAddonSpecs } from './utils/find-wire-remote-addon-specs.js';
@@ -31,6 +31,7 @@ export function getInitialInspectorState(rootDir) {
31
31
  files: new Map(),
32
32
  approvalDescriptions: {},
33
33
  dynamicImportIds: new Set(),
34
+ disabled: {},
34
35
  },
35
36
  http: {
36
37
  metaInputTypes: new Map(),
package/dist/inspector.js CHANGED
@@ -8,6 +8,7 @@ import { createNestedProjectFilter } from './utils/nested-project-filter.js';
8
8
  import { toProgramPath } from './utils/program-path.js';
9
9
  import { resolveLatestVersions } from './utils/resolve-versions.js';
10
10
  import { validateWebhookSourceTriggers } from './add/add-trigger.js';
11
+ import { validatePikkuStudio } from './add/add-pikku-studio.js';
11
12
  import { generateAllSchemas } from './utils/schema-generator.js';
12
13
  import { extractSecretUsage } from './utils/extract-secret-usage.js';
13
14
  import { accumulateSurfaceUsage, surfaceUsageArea, } from './utils/extract-surface-usage.js';
@@ -15,6 +16,7 @@ import { loadAddonFunctionsMeta } from './utils/load-addon-functions-meta.js';
15
16
  import { resolveServiceTypes } from './utils/resolve-service-types.js';
16
17
  import { finalizeInspectorState } from './finalize.js';
17
18
  import { getInitialInspectorState } from './initial-state.js';
19
+ import { checkDisabledWorkflowSteps } from './add/check-disabled-references.js';
18
20
  export { getInitialInspectorState };
19
21
  /**
20
22
  * The path mappings the inspector's program has to share with the project.
@@ -140,7 +142,9 @@ export const analyzeProgram = async (logger, routeFiles, options = {}) => {
140
142
  ts.forEachChild(sourceFile, (child) => visitRoutes(logger, checker, child, state, sourceOptions));
141
143
  }
142
144
  logger.debug(`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(0)}ms`);
145
+ checkDisabledWorkflowSteps(logger, state);
143
146
  validateWebhookSourceTriggers(logger, state);
147
+ validatePikkuStudio(logger, state);
144
148
  resolveLatestVersions(state, logger);
145
149
  if (options.schemaConfig) {
146
150
  const startSchemas = performance.now();
package/dist/lite.d.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  * reachable from this file imports `typescript`.
10
10
  */
11
11
  export { getInitialInspectorState } from './initial-state.js';
12
+ export { TypesMap } from './types-map.js';
12
13
  export { ErrorCode } from './error-codes.js';
13
14
  export type { DiagnosticSeverity, CodedDiagnostic } from './error-codes.js';
14
15
  export { AUTH_HANDLER_FUNC_ID } from './utils/inspector-constants.js';
@@ -18,9 +19,14 @@ export { filterInspectorState } from './utils/filter-inspector-state.js';
18
19
  export { incompatibleServicesFor, resolveDeployTarget, } from './utils/resolve-deploy-target.js';
19
20
  export { customTypeNames, generateCustomTypes, sanitizeTypeName, } from './utils/custom-types-generator.js';
20
21
  export { unresolvedSchemaReferences } from './utils/post-process.js';
21
- export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
22
+ export { findUnusedFunctions, type UnusedFunction, } from './utils/find-unused-functions.js';
23
+ export { computeContractHash, createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
22
24
  export { serializeMCPJson } from './utils/serialize-mcp-json.js';
23
25
  export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/deserialize-dsl-workflow.js';
24
26
  export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
25
27
  export { resolveCoreType } from './utils/resolve-core-type.js';
26
28
  export { addonResolutionDirs, createAddonResolver, } from './utils/addon-resolution.js';
29
+ export { ADDONS_LOCK_FILE, REMOTE_SDK_DIR, SDK_INTEGRITY_MARKER, checkLockedSdk, emptyAddonsLock, parseRemoteAddonPackage, readAddonsLock, readSdkManifest, remoteSdkDir, serializeAddonsLock, } from './utils/remote-addon-sdk.js';
30
+ export type { AddonsLock, AddonsLockEntry, RemoteAddonSdkManifest, RemoteAddonPackageSpec, LockedSdk, } from './utils/remote-addon-sdk.js';
31
+ export { declaredAddonNames, findInstalledPackageDir, parseAddonDependencies, readAddonDependencies, } from './utils/addon-dependencies.js';
32
+ export type { AddonDependency, AddonDependencies, } from './utils/addon-dependencies.js';
package/dist/lite.js CHANGED
@@ -9,6 +9,7 @@
9
9
  * reachable from this file imports `typescript`.
10
10
  */
11
11
  export { getInitialInspectorState } from './initial-state.js';
12
+ export { TypesMap } from './types-map.js';
12
13
  export { ErrorCode } from './error-codes.js';
13
14
  export { AUTH_HANDLER_FUNC_ID } from './utils/inspector-constants.js';
14
15
  export { SCENARIO_INSTRUMENTATION_FUNCTIONS, isScenarioInstrumentationFunction, } from './add/scenario-instrumentation.js';
@@ -17,9 +18,12 @@ export { filterInspectorState } from './utils/filter-inspector-state.js';
17
18
  export { incompatibleServicesFor, resolveDeployTarget, } from './utils/resolve-deploy-target.js';
18
19
  export { customTypeNames, generateCustomTypes, sanitizeTypeName, } from './utils/custom-types-generator.js';
19
20
  export { unresolvedSchemaReferences } from './utils/post-process.js';
20
- export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
21
+ export { findUnusedFunctions, } from './utils/find-unused-functions.js';
22
+ export { computeContractHash, createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
21
23
  export { serializeMCPJson } from './utils/serialize-mcp-json.js';
22
24
  export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/deserialize-dsl-workflow.js';
23
25
  export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
24
26
  export { resolveCoreType } from './utils/resolve-core-type.js';
25
27
  export { addonResolutionDirs, createAddonResolver, } from './utils/addon-resolution.js';
28
+ export { ADDONS_LOCK_FILE, REMOTE_SDK_DIR, SDK_INTEGRITY_MARKER, checkLockedSdk, emptyAddonsLock, parseRemoteAddonPackage, readAddonsLock, readSdkManifest, remoteSdkDir, serializeAddonsLock, } from './utils/remote-addon-sdk.js';
29
+ export { declaredAddonNames, findInstalledPackageDir, parseAddonDependencies, readAddonDependencies, } from './utils/addon-dependencies.js';
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type * as ts from 'typescript';
2
2
  import type { SecretUsage } from './utils/extract-secret-usage.js';
3
+ import type { AddonDependency } from './utils/addon-dependencies.js';
3
4
  import type { SurfaceUsageCounts } from './utils/extract-surface-usage.js';
4
5
  import type { ChannelMessageMeta, ChannelsMeta } from '@pikku/core/channel';
5
6
  import type { GatewaysMeta } from '@pikku/core/gateway';
@@ -23,6 +24,14 @@ import type { VirtualUserDefinitions } from '@pikku/core/virtual-user';
23
24
  import type { VariableDefinitions } from '@pikku/core/variable';
24
25
  import type { TypesMap } from './types-map.js';
25
26
  import type { FunctionsMeta } from '@pikku/core/services';
27
+ /**
28
+ * A remote addon SDK's function meta: each contract function, with the
29
+ * versions of its name the SDK can call and the one a call without `@vN` goes to.
30
+ */
31
+ export type RemoteAddonFunctionsMeta = Record<string, FunctionsMeta[string] & {
32
+ versions?: number[];
33
+ latestVersion?: number;
34
+ }>;
26
35
  import type { FunctionServicesMeta, FunctionWiresMeta } from '@pikku/core/function';
27
36
  import type { JSONValue } from '@pikku/core/utils';
28
37
  import type { OpenAPISpecInfo } from './utils/serialize-openapi-json.js';
@@ -97,6 +106,19 @@ export interface InspectorFunctionState {
97
106
  * inspection pass — deliberately not part of the serialized function meta.
98
107
  */
99
108
  dynamicImportIds: Set<string>;
109
+ /**
110
+ * Functions declared with `disabled`, keyed by pikkuFuncId. They are in no
111
+ * other part of the state: nothing is generated for them, and a reference
112
+ * to one is PKU562.
113
+ */
114
+ disabled: Record<string, DisabledFunctionMeta>;
115
+ }
116
+ export interface DisabledFunctionMeta {
117
+ name: string;
118
+ sourceFile: string;
119
+ exportedName?: string;
120
+ /** The string `disabled` was given, when it was not just `true`. */
121
+ reason?: string;
100
122
  }
101
123
  export interface InspectorChannelState {
102
124
  meta: ChannelsMeta;
@@ -509,11 +531,22 @@ export type CredentialOverrideMeta = string | {
509
531
  name?: string;
510
532
  mode?: 'singleton' | 'wire';
511
533
  };
512
- /** A screen of an addon as `defineScreens` declares it, read statically. */
513
- export type AddonScreenMeta = {
534
+ /** A screen's nav entry as `pikkuStudio` declares it, read statically. */
535
+ export type StudioNavMeta = {
536
+ area: 'rail' | 'team' | 'project' | 'account';
537
+ icon: string;
538
+ order: number;
539
+ badge?: string;
540
+ };
541
+ /** A screen of an addon as `pikkuStudio` declares it, read statically. */
542
+ export type StudioScreenMeta = {
514
543
  path: string;
515
544
  title: string;
516
- nav?: boolean;
545
+ scope: 'studio' | 'team' | 'project';
546
+ nav?: StudioNavMeta;
547
+ replaces?: boolean;
548
+ needsApp?: boolean;
549
+ layout?: 'bare';
517
550
  scopes?: string[];
518
551
  } & ({
519
552
  component: string;
@@ -522,15 +555,14 @@ export type AddonScreenMeta = {
522
555
  app: string;
523
556
  component?: undefined;
524
557
  });
525
- export type ScreensManifestMeta = {
558
+ export type StudioMeta = {
526
559
  title: string;
527
- icon?: string;
528
- /** The source file whose `defineScreens` call declared this manifest. */
560
+ /** The source file whose `pikkuStudio` call declared this manifest. */
529
561
  file: string;
530
- screens: AddonScreenMeta[];
562
+ screens: StudioScreenMeta[];
531
563
  /**
532
564
  * The addon's role: every scope its own functions require, derived by
533
- * `pikku all` and never declared. Absent from the raw `defineScreens` read.
565
+ * `pikku all` and never declared. Absent from the raw `pikkuStudio` read.
534
566
  * On a wired instance it also carries the host's `wireAddon` scopes, which
535
567
  * the runner demands of every function in the package.
536
568
  */
@@ -554,10 +586,10 @@ export interface AddonContributorsMeta {
554
586
  contributors: AddonContributorRef[];
555
587
  }
556
588
  export interface InspectorState {
557
- /** Set when this package calls `defineScreens`: the screens it ships. */
558
- screensManifest?: ScreensManifestMeta;
559
- /** The screens of the addons this project wires with `ui: true`, by namespace. */
560
- addonScreens?: Record<string, ScreensManifestMeta>;
589
+ /** Set when this package calls `pikkuStudio`: the screens it ships. */
590
+ studio?: StudioMeta;
591
+ /** The `pikkuStudio` metadata of the addons this project wires with `ui: true`, by namespace. */
592
+ addonStudio?: Record<string, StudioMeta>;
561
593
  /**
562
594
  * Platform service contributors each wired, non-remote addon declares in its
563
595
  * `package.json` under `pikku.contributors`, by namespace. `module` resolves
@@ -680,8 +712,20 @@ export interface InspectorState {
680
712
  /** Declared with `ui: true`: the package must ship screens, and the host mounts them. */
681
713
  ui?: boolean;
682
714
  rpcEndpoint?: string;
683
- /** The other addons this instance may call: package -> the name it is wired under. */
715
+ /**
716
+ * The other addons this instance may call: the name the addon calls
717
+ * (its declared alias, or the package name) -> the name the target is
718
+ * wired under in this app.
719
+ */
684
720
  uses?: Record<string, string>;
721
+ /**
722
+ * The names the addon's package.json declares under `pikku.uses`, in
723
+ * either form. Read when its meta loads; absent when it declares
724
+ * nothing, which leaves `uses` unchecked against a declaration.
725
+ */
726
+ declaredUses?: string[];
727
+ /** The `{ alias: 'package@range' }` form of `pikku.uses`, per alias. */
728
+ dependencies?: AddonDependency[];
685
729
  /**
686
730
  * `true` offers every function the addon declared `mcp: true`; a list
687
731
  * names the functions to offer, whatever the addon declared. Absent
@@ -713,6 +757,12 @@ export interface InspectorState {
713
757
  scopes?: string[];
714
758
  /** True when declared via `wireRemoteAddon` — import the addon's `.remote.gen` map, not `.internal.gen` */
715
759
  remote?: boolean;
760
+ /**
761
+ * wireRemoteAddon: the semver range written after the package name
762
+ * (`@scope/name@^1.4.0`). Present means the contract comes from the
763
+ * SDK `pikku all` downloads, not from an installed package.
764
+ */
765
+ range?: string;
716
766
  /** wireRemoteAddon: whether an `auth` binding was supplied (vs a public surface) */
717
767
  hasAuth?: boolean;
718
768
  /** wireRemoteAddon: statically-known `auth.credentialId`, for verify */
@@ -878,6 +928,8 @@ export interface InspectorState {
878
928
  openAPISpec: Record<string, any> | null;
879
929
  diagnostics: InspectorDiagnostic[];
880
930
  addonFunctions: Record<string, FunctionsMeta>;
931
+ /** The function metadata of each versioned `wireRemoteAddon`'s SDK, by namespace. Kept apart from `addonFunctions`: none of it runs here. */
932
+ remoteAddonFunctions?: Record<string, RemoteAddonFunctionsMeta>;
881
933
  exportedContracts: InspectorExportedContractsState;
882
934
  program: ts.Program | null;
883
935
  }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * One addon an addon calls: the alias its code calls it by
3
+ * (`rpc.invoke('<alias>:<fn>')`) and the package and semver range it was
4
+ * written against.
5
+ */
6
+ export type AddonDependency = {
7
+ alias: string;
8
+ package: string;
9
+ range: string;
10
+ };
11
+ /**
12
+ * What an addon's package.json declares under `pikku.uses`.
13
+ *
14
+ * - `none`: nothing. The wiring's `uses` is not checked against a declaration.
15
+ * - `list`: the older form, a list of names the addon calls. The name is also
16
+ * the package the wiring must map it to.
17
+ * - `map`: `{ "<alias>": "<package>@<range>" }`. `invalid` holds a message for
18
+ * each entry that could not be read; those entries are not in `dependencies`.
19
+ *
20
+ * knowledge: decisions/an-addon-declares-the-addons-it-calls.md
21
+ */
22
+ export type AddonDependencies = {
23
+ form: 'none';
24
+ } | {
25
+ form: 'list';
26
+ names: string[];
27
+ } | {
28
+ form: 'map';
29
+ dependencies: AddonDependency[];
30
+ invalid: string[];
31
+ };
32
+ /** The names a wiring's `uses` may map: the declared aliases, or the listed names. */
33
+ export declare const declaredAddonNames: (declared: AddonDependencies) => string[];
34
+ /** Reads the `pikku.uses` value of an addon's package.json. */
35
+ export declare const parseAddonDependencies: (uses: unknown) => AddonDependencies;
36
+ /** The `pikku.uses` an addon's package.json declares. `none` when there is no readable package.json. */
37
+ export declare const readAddonDependencies: (packageDir: string) => AddonDependencies;
38
+ /**
39
+ * The directory `packageName` is installed in, looking in `node_modules` from
40
+ * each of `dirs` upwards, as Node's resolution would. Null when it is not
41
+ * installed.
42
+ */
43
+ export declare const findInstalledPackageDir: (dirs: string[], packageName: string) => string | null;