@pikku/inspector 0.12.110 → 0.12.112

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 (35) hide show
  1. package/dist/add/add-channel.js +8 -0
  2. package/dist/add/add-define-contributors.d.ts +10 -0
  3. package/dist/add/add-define-contributors.js +98 -0
  4. package/dist/add/add-file-with-factory.d.ts +7 -1
  5. package/dist/add/add-file-with-factory.js +63 -2
  6. package/dist/add/add-functions.js +18 -1
  7. package/dist/add/add-pikku-studio.d.ts +15 -0
  8. package/dist/add/add-pikku-studio.js +307 -0
  9. package/dist/add/add-rpc-invocations.js +12 -0
  10. package/dist/add/add-wire-addon.js +11 -2
  11. package/dist/add/check-disabled-references.d.ts +27 -0
  12. package/dist/add/check-disabled-references.js +226 -0
  13. package/dist/error-codes.d.ts +14 -0
  14. package/dist/error-codes.js +14 -0
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.js +1 -0
  17. package/dist/initial-state.js +1 -0
  18. package/dist/inspector.js +4 -0
  19. package/dist/lite.d.ts +3 -0
  20. package/dist/lite.js +2 -0
  21. package/dist/types.d.ts +78 -28
  22. package/dist/utils/addon-dependencies.d.ts +43 -0
  23. package/dist/utils/addon-dependencies.js +91 -0
  24. package/dist/utils/find-unused-functions.d.ts +20 -0
  25. package/dist/utils/find-unused-functions.js +132 -0
  26. package/dist/utils/get-property-value.js +4 -1
  27. package/dist/utils/load-addon-functions-meta.d.ts +1 -8
  28. package/dist/utils/load-addon-functions-meta.js +87 -76
  29. package/dist/utils/post-process.js +31 -7
  30. package/dist/utils/serialize-inspector-state.d.ts +6 -3
  31. package/dist/utils/serialize-inspector-state.js +12 -6
  32. package/dist/visit.js +8 -3
  33. package/package.json +3 -3
  34. package/dist/add/add-define-screens.d.ts +0 -8
  35. 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
+ };
@@ -46,6 +46,17 @@ export declare enum ErrorCode {
46
46
  REMOTE_ADDON_SDK_CORE_MISMATCH = "PKU351",
47
47
  REMOTE_ADDON_SDK_INTEGRITY_MISMATCH = "PKU352",
48
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",
55
+ CONTRIBUTORS_NOT_STATIC = "PKU360",
56
+ CONTRIBUTOR_NAME_DUPLICATED = "PKU361",
57
+ ADDON_CONTRIBUTORS_NOT_STATIC = "PKU362",
58
+ ADDON_CONTRIBUTOR_UNKNOWN = "PKU363",
59
+ ADDON_LEGACY_CONTRIBUTORS = "PKU364",
49
60
  DB_COLUMN_NAME_TYPE_CONTRADICTION = "PKU480",
50
61
  DB_JSON_COLUMN_UNTYPED = "PKU481",
51
62
  DB_FORMAT_ON_NON_STRING = "PKU482",
@@ -56,6 +67,9 @@ export declare enum ErrorCode {
56
67
  SCHEMA_REFERENCE_UNRESOLVED = "PKU463",
57
68
  INLINE_SCHEMA = "PKU489",
58
69
  FUNCTION_METADATA_NOT_FOUND = "PKU559",
70
+ DISABLED_FUNCTION_REFERENCED = "PKU562",
71
+ UNUSED_FUNCTION = "PKU563",
72
+ FACTORY_NOT_A_DIRECT_CALL = "PKU564",
59
73
  DUPLICATE_AUTH_DEFINITION = "PKU581",
60
74
  AUTH_NOT_EXPORTED = "PKU582",
61
75
  DUPLICATE_VIRTUAL_USERS_DEFINITION = "PKU585",
@@ -49,6 +49,17 @@ export var ErrorCode;
49
49
  ErrorCode["REMOTE_ADDON_SDK_CORE_MISMATCH"] = "PKU351";
50
50
  ErrorCode["REMOTE_ADDON_SDK_INTEGRITY_MISMATCH"] = "PKU352";
51
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";
58
+ ErrorCode["CONTRIBUTORS_NOT_STATIC"] = "PKU360";
59
+ ErrorCode["CONTRIBUTOR_NAME_DUPLICATED"] = "PKU361";
60
+ ErrorCode["ADDON_CONTRIBUTORS_NOT_STATIC"] = "PKU362";
61
+ ErrorCode["ADDON_CONTRIBUTOR_UNKNOWN"] = "PKU363";
62
+ ErrorCode["ADDON_LEGACY_CONTRIBUTORS"] = "PKU364";
52
63
  // Database schema codegen warnings
53
64
  ErrorCode["DB_COLUMN_NAME_TYPE_CONTRADICTION"] = "PKU480";
54
65
  ErrorCode["DB_JSON_COLUMN_UNTYPED"] = "PKU481";
@@ -62,6 +73,9 @@ export var ErrorCode;
62
73
  ErrorCode["INLINE_SCHEMA"] = "PKU489";
63
74
  // Function errors
64
75
  ErrorCode["FUNCTION_METADATA_NOT_FOUND"] = "PKU559";
76
+ ErrorCode["DISABLED_FUNCTION_REFERENCED"] = "PKU562";
77
+ ErrorCode["UNUSED_FUNCTION"] = "PKU563";
78
+ ErrorCode["FACTORY_NOT_A_DIRECT_CALL"] = "PKU564";
65
79
  // Auth errors
66
80
  ErrorCode["DUPLICATE_AUTH_DEFINITION"] = "PKU581";
67
81
  ErrorCode["AUTH_NOT_EXPORTED"] = "PKU582";
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, ContributorRef, ContributorVariantsMeta, ContributorsManifestMeta, OptedInContributorsMeta, 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';
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';
@@ -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
@@ -19,6 +19,7 @@ export { filterInspectorState } from './utils/filter-inspector-state.js';
19
19
  export { incompatibleServicesFor, resolveDeployTarget, } from './utils/resolve-deploy-target.js';
20
20
  export { customTypeNames, generateCustomTypes, sanitizeTypeName, } from './utils/custom-types-generator.js';
21
21
  export { unresolvedSchemaReferences } from './utils/post-process.js';
22
+ export { findUnusedFunctions, type UnusedFunction, } from './utils/find-unused-functions.js';
22
23
  export { computeContractHash, createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
23
24
  export { serializeMCPJson } from './utils/serialize-mcp-json.js';
24
25
  export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/deserialize-dsl-workflow.js';
@@ -27,3 +28,5 @@ export { resolveCoreType } from './utils/resolve-core-type.js';
27
28
  export { addonResolutionDirs, createAddonResolver, } from './utils/addon-resolution.js';
28
29
  export { ADDONS_LOCK_FILE, REMOTE_SDK_DIR, SDK_INTEGRITY_MARKER, checkLockedSdk, emptyAddonsLock, parseRemoteAddonPackage, readAddonsLock, readSdkManifest, remoteSdkDir, serializeAddonsLock, } from './utils/remote-addon-sdk.js';
29
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
@@ -18,6 +18,7 @@ export { filterInspectorState } from './utils/filter-inspector-state.js';
18
18
  export { incompatibleServicesFor, resolveDeployTarget, } from './utils/resolve-deploy-target.js';
19
19
  export { customTypeNames, generateCustomTypes, sanitizeTypeName, } from './utils/custom-types-generator.js';
20
20
  export { unresolvedSchemaReferences } from './utils/post-process.js';
21
+ export { findUnusedFunctions, } from './utils/find-unused-functions.js';
21
22
  export { computeContractHash, createEmptyManifest, serializeManifest, } from './utils/contract-hashes.js';
22
23
  export { serializeMCPJson } from './utils/serialize-mcp-json.js';
23
24
  export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/deserialize-dsl-workflow.js';
@@ -25,3 +26,4 @@ export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
25
26
  export { resolveCoreType } from './utils/resolve-core-type.js';
26
27
  export { addonResolutionDirs, createAddonResolver, } from './utils/addon-resolution.js';
27
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';
@@ -105,6 +106,19 @@ export interface InspectorFunctionState {
105
106
  * inspection pass — deliberately not part of the serialized function meta.
106
107
  */
107
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;
108
122
  }
109
123
  export interface InspectorChannelState {
110
124
  meta: ChannelsMeta;
@@ -517,11 +531,22 @@ export type CredentialOverrideMeta = string | {
517
531
  name?: string;
518
532
  mode?: 'singleton' | 'wire';
519
533
  };
520
- /** A screen of an addon as `defineScreens` declares it, read statically. */
521
- export type AddonScreenMeta = {
534
+ /** A screen's nav entry as `definePlugin` 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 `definePlugin` declares it, read statically. */
542
+ export type StudioScreenMeta = {
522
543
  path: string;
523
544
  title: string;
524
- nav?: boolean;
545
+ scope: 'studio' | 'team' | 'project';
546
+ nav?: StudioNavMeta;
547
+ replaces?: boolean;
548
+ needsApp?: boolean;
549
+ layout?: 'bare';
525
550
  scopes?: string[];
526
551
  } & ({
527
552
  component: string;
@@ -530,20 +555,42 @@ export type AddonScreenMeta = {
530
555
  app: string;
531
556
  component?: undefined;
532
557
  });
533
- export type ScreensManifestMeta = {
558
+ export type StudioMeta = {
534
559
  title: string;
535
- icon?: string;
536
- /** The source file whose `defineScreens` call declared this manifest. */
560
+ /** The source file whose `definePlugin` call declared this manifest. */
537
561
  file: string;
538
- screens: AddonScreenMeta[];
562
+ screens: StudioScreenMeta[];
539
563
  /**
540
564
  * The addon's role: every scope its own functions require, derived by
541
- * `pikku all` and never declared. Absent from the raw `defineScreens` read.
565
+ * `pikku all` and never declared. Absent from the raw `definePlugin` read.
542
566
  * On a wired instance it also carries the host's `wireAddon` scopes, which
543
567
  * the runner demands of every function in the package.
544
568
  */
545
569
  scopes?: string[];
546
570
  };
571
+ /**
572
+ * Where a contributor variant lives, so a host can import only that variant.
573
+ * `module` is the source file that declares it (absolute while it is in
574
+ * inspector state, relative to the addon package in the published meta);
575
+ * `export` is the name that module exports it under.
576
+ */
577
+ export type ContributorRef = {
578
+ module: string;
579
+ export: string;
580
+ };
581
+ /** One `defineContributors` entry: each variant optional, each its own import. */
582
+ export type ContributorVariantsMeta = {
583
+ dev?: ContributorRef;
584
+ deploy?: ContributorRef;
585
+ };
586
+ /** The contributors an addon registers, keyed by name. */
587
+ export type ContributorsManifestMeta = Record<string, ContributorVariantsMeta>;
588
+ /** The contributors a project opted in to for one wired addon. */
589
+ export type OptedInContributorsMeta = {
590
+ packageName: string;
591
+ /** Each opted-in name with the variants the addon recorded; `module` is relative to the addon package. */
592
+ contributors: ContributorsManifestMeta;
593
+ };
547
594
  export interface InspectorServiceTypes {
548
595
  /** Property names of the project's SingletonServices type, sorted. */
549
596
  singletonServices: string[] | null;
@@ -552,26 +599,15 @@ export interface InspectorServiceTypes {
552
599
  /** Singleton services that are a `SecretService` under another name. */
553
600
  secretAliases: string[];
554
601
  }
555
- export interface AddonContributorRef {
556
- module: string;
557
- export?: string;
558
- }
559
- export interface AddonContributorsMeta {
560
- packageName: string;
561
- packageDir: string;
562
- contributors: AddonContributorRef[];
563
- }
564
602
  export interface InspectorState {
565
- /** Set when this package calls `defineScreens`: the screens it ships. */
566
- screensManifest?: ScreensManifestMeta;
567
- /** The screens of the addons this project wires with `ui: true`, by namespace. */
568
- addonScreens?: Record<string, ScreensManifestMeta>;
569
- /**
570
- * Platform service contributors each wired, non-remote addon declares in its
571
- * `package.json` under `pikku.contributors`, by namespace. `module` resolves
572
- * from `packageDir`.
573
- */
574
- addonContributors?: Record<string, AddonContributorsMeta>;
603
+ /** Set when this package calls `definePlugin`: the screens it ships. */
604
+ studio?: StudioMeta;
605
+ /** The `definePlugin` metadata of the addons this project wires with `ui: true`, by namespace. */
606
+ addonStudio?: Record<string, StudioMeta>;
607
+ /** Set when this package calls `defineContributors`: the contributors it registers, by name. */
608
+ contributorsManifest?: ContributorsManifestMeta;
609
+ /** Per wired addon namespace, the contributors `wireAddon({ contributors })` opted in to. */
610
+ optedInContributors?: Record<string, OptedInContributorsMeta>;
575
611
  rootDir: string;
576
612
  singletonServicesTypeImportMap: PathToNameAndType;
577
613
  wireServicesTypeImportMap: PathToNameAndType;
@@ -688,8 +724,20 @@ export interface InspectorState {
688
724
  /** Declared with `ui: true`: the package must ship screens, and the host mounts them. */
689
725
  ui?: boolean;
690
726
  rpcEndpoint?: string;
691
- /** The other addons this instance may call: package -> the name it is wired under. */
727
+ /**
728
+ * The other addons this instance may call: the name the addon calls
729
+ * (its declared alias, or the package name) -> the name the target is
730
+ * wired under in this app.
731
+ */
692
732
  uses?: Record<string, string>;
733
+ /**
734
+ * The names the addon's package.json declares under `pikku.uses`, in
735
+ * either form. Read when its meta loads; absent when it declares
736
+ * nothing, which leaves `uses` unchecked against a declaration.
737
+ */
738
+ declaredUses?: string[];
739
+ /** The `{ alias: 'package@range' }` form of `pikku.uses`, per alias. */
740
+ dependencies?: AddonDependency[];
693
741
  /**
694
742
  * `true` offers every function the addon declared `mcp: true`; a list
695
743
  * names the functions to offer, whatever the addon declared. Absent
@@ -757,6 +805,8 @@ export interface InspectorState {
757
805
  * `ContentService` instead of one scoped to its own folder.
758
806
  */
759
807
  globalContent?: string;
808
+ /** The addon contributor names this project opted in to, as `wireAddon({ contributors })` listed them. */
809
+ contributors?: string[];
760
810
  }>;
761
811
  wireAddonFiles: Set<string>;
762
812
  };
@@ -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;