@pikku/inspector 0.12.75 → 0.12.81

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 (219) hide show
  1. package/CHANGELOG.md +330 -0
  2. package/dist/add/add-analytics.d.ts +9 -0
  3. package/dist/add/add-analytics.js +178 -0
  4. package/dist/add/add-auth.js +0 -54
  5. package/dist/add/add-feature-flag.d.ts +2 -0
  6. package/dist/add/add-feature-flag.js +162 -0
  7. package/dist/add/add-wire-addon.js +1 -0
  8. package/dist/add/add-wire-remote-addon.js +1 -0
  9. package/dist/index.d.ts +3 -1
  10. package/dist/index.js +3 -1
  11. package/dist/inspector.js +4 -0
  12. package/dist/surface/collect-surface.d.ts +58 -0
  13. package/dist/surface/collect-surface.js +613 -0
  14. package/dist/types.d.ts +20 -11
  15. package/dist/utils/filter-inspector-state.js +14 -0
  16. package/dist/utils/post-process.js +15 -0
  17. package/dist/utils/read-module-specifiers.d.ts +8 -0
  18. package/dist/utils/read-module-specifiers.js +23 -0
  19. package/dist/utils/read-tsconfig-out-dir.d.ts +5 -0
  20. package/dist/utils/read-tsconfig-out-dir.js +12 -0
  21. package/dist/utils/resolve-deploy-target.d.ts +7 -0
  22. package/dist/utils/resolve-deploy-target.js +15 -7
  23. package/dist/utils/serialize-inspector-state.d.ts +5 -0
  24. package/dist/utils/serialize-inspector-state.js +8 -0
  25. package/dist/visit.js +4 -0
  26. package/package.json +16 -6
  27. package/run-tests.sh +21 -6
  28. package/src/add/add-addon-bans.ts +0 -85
  29. package/src/add/add-agent-scorer.test.ts +0 -197
  30. package/src/add/add-agent-scorer.ts +0 -83
  31. package/src/add/add-agent.test.ts +0 -87
  32. package/src/add/add-agent.ts +0 -638
  33. package/src/add/add-approval-description.ts +0 -76
  34. package/src/add/add-auth.test.ts +0 -791
  35. package/src/add/add-auth.ts +0 -424
  36. package/src/add/add-channel.ts +0 -778
  37. package/src/add/add-cli-renderers.test.ts +0 -74
  38. package/src/add/add-cli.ts +0 -1006
  39. package/src/add/add-credential.ts +0 -186
  40. package/src/add/add-feature.test.ts +0 -306
  41. package/src/add/add-feature.ts +0 -253
  42. package/src/add/add-file-extends-core-type.ts +0 -66
  43. package/src/add/add-file-with-config.ts +0 -45
  44. package/src/add/add-file-with-factory.test.ts +0 -134
  45. package/src/add/add-file-with-factory.ts +0 -288
  46. package/src/add/add-functions.test.ts +0 -743
  47. package/src/add/add-functions.ts +0 -1548
  48. package/src/add/add-gateway.ts +0 -143
  49. package/src/add/add-http-route-ref.test.ts +0 -149
  50. package/src/add/add-http-route.ts +0 -530
  51. package/src/add/add-http-routes.ts +0 -471
  52. package/src/add/add-keyed-wiring.ts +0 -180
  53. package/src/add/add-mcp-prompt.ts +0 -157
  54. package/src/add/add-mcp-resource.ts +0 -175
  55. package/src/add/add-middleware.test.ts +0 -196
  56. package/src/add/add-middleware.ts +0 -879
  57. package/src/add/add-permission-auth.test.ts +0 -59
  58. package/src/add/add-permission.ts +0 -311
  59. package/src/add/add-personas.test.ts +0 -406
  60. package/src/add/add-personas.ts +0 -287
  61. package/src/add/add-queue-worker.ts +0 -117
  62. package/src/add/add-rpc-invocations.test.ts +0 -287
  63. package/src/add/add-rpc-invocations.ts +0 -221
  64. package/src/add/add-scenario-step.test.ts +0 -509
  65. package/src/add/add-schedule.ts +0 -120
  66. package/src/add/add-scope.test.ts +0 -419
  67. package/src/add/add-scope.ts +0 -221
  68. package/src/add/add-secret.ts +0 -10
  69. package/src/add/add-subject-scenario-steps.test.ts +0 -161
  70. package/src/add/add-system-role.test.ts +0 -297
  71. package/src/add/add-system-role.ts +0 -162
  72. package/src/add/add-trigger.ts +0 -165
  73. package/src/add/add-variable.ts +0 -10
  74. package/src/add/add-wire-addon.test.ts +0 -185
  75. package/src/add/add-wire-addon.ts +0 -146
  76. package/src/add/add-wire-remote-addon.ts +0 -80
  77. package/src/add/add-workflow-edge-cases.test.ts +0 -201
  78. package/src/add/add-workflow-fanout.test.ts +0 -166
  79. package/src/add/add-workflow-graph-foreach.test.ts +0 -174
  80. package/src/add/add-workflow-graph-input.test.ts +0 -94
  81. package/src/add/add-workflow-graph-notes.test.ts +0 -74
  82. package/src/add/add-workflow-graph.ts +0 -644
  83. package/src/add/add-workflow-on-error.test.ts +0 -118
  84. package/src/add/add-workflow-return-switch.test.ts +0 -121
  85. package/src/add/add-workflow-silent-drops.test.ts +0 -261
  86. package/src/add/add-workflow-sleep-expression.test.ts +0 -96
  87. package/src/add/add-workflow.test.ts +0 -155
  88. package/src/add/add-workflow.ts +0 -482
  89. package/src/add/addon-bans.test.ts +0 -121
  90. package/src/add/addon-contracts.test.ts +0 -221
  91. package/src/add/allow-escape-hatches.test.ts +0 -144
  92. package/src/add/audit-meta.test.ts +0 -75
  93. package/src/add/classified-sink-check.test.ts +0 -347
  94. package/src/add/config-value-metadata.test.ts +0 -263
  95. package/src/add/dynamic-import-check.test.ts +0 -119
  96. package/src/add/expect-eventually-scenario-only.test.ts +0 -108
  97. package/src/add/function-body-span.test.ts +0 -96
  98. package/src/add/inline-wiring-function.test.ts +0 -104
  99. package/src/add/pii-check.test.ts +0 -258
  100. package/src/add/rpc-type-cast.test.ts +0 -123
  101. package/src/add/scenario-instrumentation.test.ts +0 -89
  102. package/src/add/scenario-instrumentation.ts +0 -42
  103. package/src/add/scenario-marker.test.ts +0 -89
  104. package/src/add/scenario-not-extractable.test.ts +0 -159
  105. package/src/add/scenario-services.test.ts +0 -103
  106. package/src/add/scenario-skip.test.ts +0 -83
  107. package/src/add/validate-workflow-graph-addons.test.ts +0 -102
  108. package/src/add/wire-name-literal.test.ts +0 -117
  109. package/src/error-codes-emitted.test.ts +0 -76
  110. package/src/error-codes.ts +0 -145
  111. package/src/index.ts +0 -66
  112. package/src/inspector.ts +0 -575
  113. package/src/schema-generator.ts +0 -1
  114. package/src/source-file-cache.ts +0 -73
  115. package/src/tsconfig-paths.test.ts +0 -90
  116. package/src/types-map.ts +0 -141
  117. package/src/types.ts +0 -775
  118. package/src/utils/annotate-http-route-auth.test.ts +0 -161
  119. package/src/utils/annotate-http-route-auth.ts +0 -53
  120. package/src/utils/check-classified-sinks.ts +0 -215
  121. package/src/utils/check-pii-output.ts +0 -120
  122. package/src/utils/compute-diagnostics.test.ts +0 -106
  123. package/src/utils/compute-required-schemas.ts +0 -48
  124. package/src/utils/contract-hashes.test.ts +0 -553
  125. package/src/utils/contract-hashes.ts +0 -387
  126. package/src/utils/custom-types-generator.test.ts +0 -124
  127. package/src/utils/custom-types-generator.ts +0 -150
  128. package/src/utils/detect-schema-vendor.test.ts +0 -123
  129. package/src/utils/detect-schema-vendor.ts +0 -120
  130. package/src/utils/does-type-extend-core-type.ts +0 -53
  131. package/src/utils/ensure-function-metadata.ts +0 -385
  132. package/src/utils/extract-function-name.ts +0 -612
  133. package/src/utils/extract-function-node.ts +0 -70
  134. package/src/utils/extract-node-value.test.ts +0 -117
  135. package/src/utils/extract-node-value.ts +0 -172
  136. package/src/utils/extract-secret-usage.test.ts +0 -81
  137. package/src/utils/extract-secret-usage.ts +0 -73
  138. package/src/utils/extract-services.ts +0 -70
  139. package/src/utils/extract-surface-usage.test.ts +0 -147
  140. package/src/utils/extract-surface-usage.ts +0 -121
  141. package/src/utils/filter-inspector-state.test.ts +0 -2232
  142. package/src/utils/filter-inspector-state.ts +0 -1298
  143. package/src/utils/filter-utils.test.ts +0 -843
  144. package/src/utils/filter-utils.ts +0 -161
  145. package/src/utils/find-root-dir.ts +0 -68
  146. package/src/utils/find-zod-transform.test.ts +0 -70
  147. package/src/utils/find-zod-transform.ts +0 -103
  148. package/src/utils/get-exported-variable-name.ts +0 -48
  149. package/src/utils/get-files-and-methods.ts +0 -157
  150. package/src/utils/get-property-value.test.ts +0 -141
  151. package/src/utils/get-property-value.ts +0 -331
  152. package/src/utils/hash.ts +0 -26
  153. package/src/utils/literal-properties.ts +0 -97
  154. package/src/utils/load-addon-functions-meta.test.ts +0 -157
  155. package/src/utils/load-addon-functions-meta.ts +0 -508
  156. package/src/utils/load-addon-scopes.test.ts +0 -138
  157. package/src/utils/middleware.test.ts +0 -204
  158. package/src/utils/middleware.ts +0 -303
  159. package/src/utils/nested-project-filter.test.ts +0 -53
  160. package/src/utils/nested-project-filter.ts +0 -28
  161. package/src/utils/permissions-shorthand.test.ts +0 -79
  162. package/src/utils/permissions.test.ts +0 -125
  163. package/src/utils/permissions.ts +0 -129
  164. package/src/utils/post-process.test.ts +0 -1094
  165. package/src/utils/post-process.ts +0 -1604
  166. package/src/utils/resolve-addon-package.ts +0 -106
  167. package/src/utils/resolve-core-type.test.ts +0 -142
  168. package/src/utils/resolve-core-type.ts +0 -35
  169. package/src/utils/resolve-deploy-target.test.ts +0 -135
  170. package/src/utils/resolve-deploy-target.ts +0 -65
  171. package/src/utils/resolve-function-meta.ts +0 -25
  172. package/src/utils/resolve-function-types.ts +0 -42
  173. package/src/utils/resolve-identifier.ts +0 -46
  174. package/src/utils/resolve-ref-contract.ts +0 -71
  175. package/src/utils/resolve-versions.test.ts +0 -499
  176. package/src/utils/resolve-versions.ts +0 -228
  177. package/src/utils/schema-cache.test.ts +0 -124
  178. package/src/utils/schema-generator.test.ts +0 -272
  179. package/src/utils/schema-generator.ts +0 -780
  180. package/src/utils/secret-alias-services.test.ts +0 -146
  181. package/src/utils/secret-alias-services.ts +0 -58
  182. package/src/utils/serialize-inspector-state.ts +0 -742
  183. package/src/utils/serialize-mcp-json.ts +0 -150
  184. package/src/utils/serialize-middleware-groups-meta.ts +0 -36
  185. package/src/utils/serialize-openapi-json.ts +0 -277
  186. package/src/utils/serialize-permissions-groups-meta.ts +0 -7
  187. package/src/utils/single-declaration.ts +0 -57
  188. package/src/utils/type-utils.ts +0 -145
  189. package/src/utils/validate-auth-sessionless.ts +0 -30
  190. package/src/utils/validate-exposed-functions-gated.test.ts +0 -230
  191. package/src/utils/validate-exposed-functions-gated.ts +0 -103
  192. package/src/utils/validate-tags-resolve-to-middleware.test.ts +0 -108
  193. package/src/utils/validate-tags-resolve-to-middleware.ts +0 -48
  194. package/src/utils/workflow/derive-workflow-plan.test.ts +0 -178
  195. package/src/utils/workflow/derive-workflow-plan.ts +0 -117
  196. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +0 -1465
  197. package/src/utils/workflow/dsl/deserialize-fanout-body.test.ts +0 -71
  198. package/src/utils/workflow/dsl/deserialize-fanout-fidelity.test.ts +0 -99
  199. package/src/utils/workflow/dsl/deserialize-graph-next.test.ts +0 -161
  200. package/src/utils/workflow/dsl/deserialize-roundtrip-bindings.test.ts +0 -121
  201. package/src/utils/workflow/dsl/deserialize-roundtrip-values.test.ts +0 -116
  202. package/src/utils/workflow/dsl/deserialize-roundtrip.test.ts +0 -145
  203. package/src/utils/workflow/dsl/extract-approval-policy.test.ts +0 -71
  204. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +0 -2254
  205. package/src/utils/workflow/dsl/index.ts +0 -11
  206. package/src/utils/workflow/dsl/patterns.ts +0 -440
  207. package/src/utils/workflow/dsl/validation.ts +0 -203
  208. package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +0 -35
  209. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +0 -543
  210. package/src/utils/workflow/graph/fanout-node-identity.test.ts +0 -105
  211. package/src/utils/workflow/graph/finalize-workflow-wires.ts +0 -310
  212. package/src/utils/workflow/graph/finalize-workflows.test.ts +0 -50
  213. package/src/utils/workflow/graph/finalize-workflows.ts +0 -130
  214. package/src/utils/workflow/graph/index.ts +0 -28
  215. package/src/utils/workflow/graph/serialize-workflow-graph.ts +0 -240
  216. package/src/utils/workflow/graph/workflow-graph.types.ts +0 -281
  217. package/src/visit.ts +0 -181
  218. package/tsconfig.json +0 -19
  219. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,613 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join, relative, resolve } from 'node:path';
4
+ import ts from 'typescript';
5
+ const SOURCE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts'];
6
+ const MODULE_EXTENSIONS = [...SOURCE_EXTENSIONS, '.js', '.jsx', '.mjs', '.cjs'];
7
+ const wildcardStem = (fileName) => {
8
+ if (fileName.endsWith('.map'))
9
+ return null;
10
+ if (/\.d\.(ts|mts|cts)$/.test(fileName))
11
+ return null;
12
+ const extension = MODULE_EXTENSIONS.find((each) => fileName.endsWith(each));
13
+ return extension ? fileName.slice(0, -extension.length) : null;
14
+ };
15
+ const subpathMap = (exports) => {
16
+ if (typeof exports === 'string')
17
+ return { '.': exports };
18
+ return Object.keys(exports).some((key) => key.startsWith('.'))
19
+ ? exports
20
+ : { '.': exports };
21
+ };
22
+ const publishesCode = (target) => !/\.(css|scss|json|svg|png|woff2?|txt|md)$/.test(target);
23
+ const sourceForTarget = (packageDir, target, outDir) => {
24
+ const withoutPrefix = target.replace(/^\.\//, '');
25
+ const isDeclaration = /\.d\.(ts|mts|cts)$/.test(withoutPrefix);
26
+ if (!isDeclaration &&
27
+ SOURCE_EXTENSIONS.some((extension) => withoutPrefix.endsWith(extension))) {
28
+ return existsSync(join(packageDir, withoutPrefix)) ? withoutPrefix : null;
29
+ }
30
+ const stripped = withoutPrefix.startsWith(`${outDir}/`)
31
+ ? withoutPrefix.slice(outDir.length + 1)
32
+ : withoutPrefix;
33
+ const base = stripped
34
+ .replace(/\.d\.(ts|mts|cts)$/, '')
35
+ .replace(/\.(js|jsx|mjs|cjs)$/, '');
36
+ const withoutSrc = base.startsWith('src/') ? base.slice(4) : base;
37
+ const candidates = [];
38
+ for (const extension of SOURCE_EXTENSIONS) {
39
+ candidates.push(join('src', `${base}${extension}`), `${base}${extension}`, join('src', `${withoutSrc}${extension}`), join('src', base, `index${extension}`), join(base, `index${extension}`));
40
+ }
41
+ for (const candidate of candidates) {
42
+ if (existsSync(join(packageDir, candidate)))
43
+ return candidate;
44
+ }
45
+ return null;
46
+ };
47
+ const expandWildcard = (packageDir, subpath, target, outDir) => {
48
+ const targetDir = target.replace(/^\.\//, '').split('*')[0] ?? '';
49
+ const suffix = target.slice(target.indexOf('*') + 1);
50
+ const suffixStem = wildcardStem(suffix) ?? suffix;
51
+ // A package that points its subpaths at the build output has nothing to walk
52
+ // until it is built, so the sources the output would be compiled from stand
53
+ // in for it. `sourceForTarget` strips the same prefix, so the target stays
54
+ // the declared one and only the directory being read changes.
55
+ const scanDir = existsSync(join(packageDir, targetDir)) ||
56
+ !targetDir.startsWith(`${outDir}/`)
57
+ ? targetDir
58
+ : targetDir.slice(outDir.length + 1);
59
+ const absolute = join(packageDir, scanDir);
60
+ if (!existsSync(absolute))
61
+ return [];
62
+ const expanded = [];
63
+ const walk = (directory, prefix) => {
64
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
65
+ if (entry.isDirectory()) {
66
+ walk(join(directory, entry.name), `${prefix}${entry.name}/`);
67
+ continue;
68
+ }
69
+ if (!entry.isFile())
70
+ continue;
71
+ if (wildcardStem(entry.name) === null)
72
+ continue;
73
+ // The suffix is matched against the path the wildcard stands in for, not
74
+ // the file name, so `#pikku/*` → `./.pikku/*/index.ts` captures the leaf
75
+ // directory rather than failing to match `index.ts` against `/index.ts`.
76
+ const relativePath = `${prefix}${entry.name}`;
77
+ // Matched without extensions, so a `*/index.js` target still finds the
78
+ // `index.ts` it would have been compiled from.
79
+ const pathStem = `${prefix}${wildcardStem(entry.name)}`;
80
+ if (suffixStem && !pathStem.endsWith(suffixStem))
81
+ continue;
82
+ const captured = suffixStem
83
+ ? pathStem.slice(0, -suffixStem.length)
84
+ : pathStem;
85
+ if (!captured)
86
+ continue;
87
+ expanded.push({
88
+ subpath: subpath.replace('*', captured),
89
+ target: `${scanDir}${relativePath}`,
90
+ });
91
+ }
92
+ };
93
+ walk(absolute, '');
94
+ return expanded;
95
+ };
96
+ const targetOf = (value) => {
97
+ if (typeof value === 'string')
98
+ return value;
99
+ // A fallback array declares the same specifier several ways; the first entry
100
+ // is the one a resolver reaches for, and the rest only matter when it misses.
101
+ if (Array.isArray(value)) {
102
+ for (const candidate of value) {
103
+ const resolved = targetOf(candidate);
104
+ if (resolved)
105
+ return resolved;
106
+ }
107
+ return null;
108
+ }
109
+ if (!value || typeof value !== 'object')
110
+ return null;
111
+ const conditions = value;
112
+ for (const key of ['types', 'import', 'default', 'require']) {
113
+ const resolved = targetOf(conditions[key]);
114
+ if (resolved)
115
+ return resolved;
116
+ }
117
+ return null;
118
+ };
119
+ const readJson = async (path) => {
120
+ try {
121
+ return JSON.parse(await readFile(path, 'utf8'));
122
+ }
123
+ catch {
124
+ return null;
125
+ }
126
+ };
127
+ const readCompilerOptions = async (packageDir) => {
128
+ const configPath = join(packageDir, 'tsconfig.json');
129
+ if (!existsSync(configPath))
130
+ return { outDir: 'dist', paths: {} };
131
+ const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
132
+ if (configFile.error)
133
+ return { outDir: 'dist', paths: {} };
134
+ const { options } = ts.parseJsonConfigFileContent(configFile.config, ts.sys, packageDir);
135
+ const { baseUrl, paths, pathsBasePath } = options;
136
+ return {
137
+ outDir: options.outDir ? relative(packageDir, options.outDir) : 'dist',
138
+ paths: {
139
+ ...(baseUrl !== undefined ? { baseUrl } : {}),
140
+ ...(paths !== undefined ? { paths } : {}),
141
+ ...(pathsBasePath !== undefined ? { pathsBasePath } : {}),
142
+ },
143
+ };
144
+ };
145
+ const KIND_BY_FLAG = [
146
+ [ts.SymbolFlags.Class, 'class'],
147
+ [ts.SymbolFlags.Interface, 'interface'],
148
+ [ts.SymbolFlags.RegularEnum | ts.SymbolFlags.ConstEnum, 'enum'],
149
+ [ts.SymbolFlags.Function, 'function'],
150
+ [ts.SymbolFlags.TypeAlias, 'type'],
151
+ [ts.SymbolFlags.Namespace, 'namespace'],
152
+ ];
153
+ /**
154
+ * A leaf re-exports through `export *`, so the symbol reached here is an alias
155
+ * that carries no kind, no documentation, no tags and no type of its own.
156
+ */
157
+ const aliasTargetOf = (symbol, checker) => symbol.flags & ts.SymbolFlags.Alias
158
+ ? checker.getAliasedSymbol(symbol)
159
+ : symbol;
160
+ const kindOf = (symbol, checker) => {
161
+ const target = aliasTargetOf(symbol, checker);
162
+ for (const [flag, kind] of KIND_BY_FLAG) {
163
+ if (target.flags & flag)
164
+ return kind;
165
+ }
166
+ if (target.flags & ts.SymbolFlags.Variable) {
167
+ const declaration = target.declarations?.[0];
168
+ if (declaration) {
169
+ const type = checker.getTypeOfSymbolAtLocation(target, declaration);
170
+ if (type.getCallSignatures().length > 0)
171
+ return 'function';
172
+ }
173
+ return 'const';
174
+ }
175
+ return 'const';
176
+ };
177
+ const jsDocTagsOf = (symbol, checker) => {
178
+ const own = symbol.getJsDocTags(checker);
179
+ if (own.length > 0)
180
+ return own;
181
+ return aliasTargetOf(symbol, checker).getJsDocTags(checker);
182
+ };
183
+ const isDeprecated = (tags) => tags.some((tag) => tag.name === 'deprecated');
184
+ const deprecationReason = (tags) => {
185
+ const tag = tags.find((each) => each.name === 'deprecated');
186
+ if (!tag)
187
+ return undefined;
188
+ const text = ts.displayPartsToString(tag.text).trim();
189
+ return text.length > 0 ? text : undefined;
190
+ };
191
+ export class UnknownSnippetError extends Error {
192
+ constructor(name, symbol, known) {
193
+ super(`The example on ${symbol} names the snippet "${name}", which no template declares. Wrap the code you want shown in "// @snippet start ${name}" / "// @snippet end ${name}". Declared: ${known.join(', ')}.`);
194
+ this.name = 'UnknownSnippetError';
195
+ }
196
+ }
197
+ const SNIPPET_REFERENCE = /^snippet:\s*(\S+)$/;
198
+ /**
199
+ * The `@example` blocks. `getDocumentationComment` returns the description only,
200
+ * so an example an author wrote is dropped unless the tags are read separately.
201
+ * An `@example snippet: name` names a region of real template source instead of
202
+ * restating it, and is resolved here so the shipped doc holds code that compiles.
203
+ */
204
+ const examplesOf = (tags, symbolName, snippets) => tags
205
+ .filter((tag) => tag.name === 'example')
206
+ .map((tag) => ts.displayPartsToString(tag.text).trim())
207
+ .filter((text) => text.length > 0)
208
+ .map((text) => {
209
+ const reference = text.match(SNIPPET_REFERENCE);
210
+ if (!reference?.[1] || !snippets)
211
+ return text;
212
+ const snippet = snippets.get(reference[1]);
213
+ if (snippet === undefined) {
214
+ throw new UnknownSnippetError(reference[1], symbolName, [
215
+ ...snippets.keys(),
216
+ ]);
217
+ }
218
+ return snippet;
219
+ });
220
+ /**
221
+ * The doc comment as written. A leaf re-exports through `export *`, so the
222
+ * symbol reached here is an alias carrying no documentation of its own, and an
223
+ * overloaded function documents whichever overload the author chose to explain
224
+ * — hence the walk through the aliased symbol and then every declaration.
225
+ */
226
+ const documentationOf = (symbol, checker) => {
227
+ const targets = [symbol];
228
+ if (symbol.flags & ts.SymbolFlags.Alias) {
229
+ targets.push(checker.getAliasedSymbol(symbol));
230
+ }
231
+ for (const target of targets) {
232
+ const documentation = ts
233
+ .displayPartsToString(target.getDocumentationComment(checker))
234
+ .trim();
235
+ if (documentation.length > 0)
236
+ return documentation;
237
+ }
238
+ return exportDeclarationDocumentationOf(symbol);
239
+ };
240
+ /**
241
+ * A re-export the generator documents (`export type { Services }`) carries its
242
+ * JSDoc on the export statement, which TypeScript attaches to no symbol at all
243
+ * — so it is read off the node.
244
+ */
245
+ const exportDeclarationDocumentationOf = (symbol) => {
246
+ for (const declaration of symbol.declarations ?? []) {
247
+ if (!ts.isExportSpecifier(declaration))
248
+ continue;
249
+ const statement = declaration.parent.parent;
250
+ const [comment] = ts.getJSDocCommentsAndTags(statement);
251
+ if (!comment || !ts.isJSDoc(comment))
252
+ continue;
253
+ const documentation = ts.getTextOfJSDocComment(comment.comment)?.trim();
254
+ if (documentation && documentation.length > 0)
255
+ return documentation;
256
+ }
257
+ return undefined;
258
+ };
259
+ /**
260
+ * The first paragraph of the doc comment. A symbol's JSDoc regularly runs to
261
+ * several paragraphs of examples, and a list row shows one line.
262
+ */
263
+ const summaryOf = (documentation) => {
264
+ if (!documentation)
265
+ return undefined;
266
+ return documentation
267
+ .split(/\n\s*\n/)[0]
268
+ .replace(/\s+/g, ' ')
269
+ .trim();
270
+ };
271
+ const declarationFileOf = (symbol, checker) => {
272
+ const target = aliasTargetOf(symbol, checker);
273
+ const declaration = target.declarations?.[0] ?? symbol.declarations?.[0];
274
+ return declaration?.getSourceFile().fileName ?? null;
275
+ };
276
+ /**
277
+ * `NoTruncation` is the load-bearing one: the default cuts a type off at ~160
278
+ * characters, which lands in the middle of the generics of every wiring helper.
279
+ * `UseAliasDefinedOutsideCurrentScope` keeps a named alias named instead of
280
+ * inlining its definition, which is what keeps the result readable.
281
+ */
282
+ const SIGNATURE_FLAGS = ts.TypeFormatFlags.NoTruncation |
283
+ ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope |
284
+ ts.TypeFormatFlags.WriteTypeArgumentsOfSignature;
285
+ const DECLARED_TYPE_KINDS = new Set(['interface', 'type']);
286
+ const typeOfSymbol = (symbol, kind, checker) => {
287
+ if (DECLARED_TYPE_KINDS.has(kind))
288
+ return checker.getDeclaredTypeOfSymbol(symbol);
289
+ const declaration = symbol.declarations?.[0];
290
+ return declaration
291
+ ? checker.getTypeOfSymbolAtLocation(symbol, declaration)
292
+ : undefined;
293
+ };
294
+ const printType = (type, location, checker) => checker.typeToString(type, location, SIGNATURE_FLAGS).replace(/\s+/g, ' ');
295
+ /**
296
+ * The widest an expanded type may print before its name is the better answer.
297
+ * A method verb or a content type says everything inline; a wiring object does
298
+ * not, and expanding it buries the keys that matter in the ones that do not.
299
+ */
300
+ const INLINE_LIMIT = 72;
301
+ const EXPANDED_FLAGS = SIGNATURE_FLAGS & ~ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
302
+ const NOT_TERMINAL = /[{}]|=>/;
303
+ const collapse = (constituents) => {
304
+ const collapsed = constituents.includes('true') && constituents.includes('false')
305
+ ? [
306
+ 'boolean',
307
+ ...constituents.filter((c) => c !== 'true' && c !== 'false'),
308
+ ]
309
+ : constituents;
310
+ const isAbsent = (c) => c === 'undefined' || c === 'null';
311
+ return [
312
+ ...collapsed.filter((c) => !isAbsent(c)),
313
+ ...collapsed.filter(isAbsent),
314
+ ];
315
+ };
316
+ /**
317
+ * A named type whose definition is a short union or an index signature costs a
318
+ * reader a second lookup to learn something that fits on the line they are
319
+ * already reading, so it is printed as its values rather than its name.
320
+ */
321
+ const printResolved = (type, location, checker, optional = false) => {
322
+ const constraint = type.flags & ts.TypeFlags.TypeParameter
323
+ ? checker.getBaseConstraintOfType(type)
324
+ : undefined;
325
+ if (constraint) {
326
+ return printResolved(constraint, location, checker, optional);
327
+ }
328
+ const named = printType(type, location, checker);
329
+ const expanded = type.isUnion()
330
+ ? collapse(type.types.map((constituent) => checker.typeToString(constituent, location, EXPANDED_FLAGS))).join(' | ')
331
+ : checker.typeToString(type, location, EXPANDED_FLAGS).replace(/\s+/g, ' ');
332
+ const shed = optional ? / \| undefined$/ : /(?!)/;
333
+ if (expanded === named || expanded.length > INLINE_LIMIT) {
334
+ return named.replace(shed, '');
335
+ }
336
+ return (NOT_TERMINAL.test(expanded) ? named : expanded).replace(shed, '');
337
+ };
338
+ /**
339
+ * A class prints as `typeof Foo`, which says nothing. Its construct signatures
340
+ * are what a caller needs, so they stand in for it.
341
+ */
342
+ const constructorSignature = (name, type, checker) => {
343
+ const signatures = type.getConstructSignatures();
344
+ if (signatures.length === 0)
345
+ return undefined;
346
+ return signatures
347
+ .map((signature) => `new ${name}(${signature
348
+ .getParameters()
349
+ .map((parameter) => {
350
+ const declaration = parameter.valueDeclaration;
351
+ const parameterType = declaration
352
+ ? checker.getTypeOfSymbolAtLocation(parameter, declaration)
353
+ : checker.getTypeOfSymbol(parameter);
354
+ const optional = declaration &&
355
+ ts.isParameter(declaration) &&
356
+ declaration.questionToken
357
+ ? '?'
358
+ : '';
359
+ return `${parameter.getName()}${optional}: ${printResolved(parameterType, declaration, checker, optional === '?')}`;
360
+ })
361
+ .join(', ')})`)
362
+ .join(' | ');
363
+ };
364
+ const signatureOf = (name, type, kind, location, checker) => {
365
+ const printed = kind === 'class'
366
+ ? constructorSignature(name, type, checker)
367
+ : printType(type, location, checker);
368
+ if (!printed || printed === 'any' || printed === 'error' || printed === name)
369
+ return undefined;
370
+ return collapseRuns(printed);
371
+ };
372
+ /**
373
+ * A union parameterised by the project — every function name, every route —
374
+ * is unbounded and belongs to `pikku meta`, not to a framework doc that is the
375
+ * same for every project. Printed in full it buried one export under 4,000
376
+ * tokens of another project's identifiers.
377
+ */
378
+ const LITERAL_RUN = /("[^"]*"(?: \| "[^"]*"){7,})/g;
379
+ const collapseRuns = (line) => line.replace(LITERAL_RUN, (run) => `${run.split(' | ').length} names (pikku meta)`);
380
+ /**
381
+ * A key declared by the project rather than by the framework — the services in
382
+ * its own `SingletonServices`, a type it wrote — is not part of the surface
383
+ * every project shares. Left in, the doc built from the sample project told
384
+ * every reader their singleton services hold a `todoStore`.
385
+ */
386
+ const isProjectOwned = (declaration, root) => {
387
+ const file = declaration.getSourceFile().fileName;
388
+ return file.startsWith(root) && !file.includes('node_modules');
389
+ };
390
+ const memberLine = (property, checker, program, root) => {
391
+ if (property.getName().startsWith('__'))
392
+ return undefined;
393
+ const declaration = property.valueDeclaration ?? property.declarations?.[0];
394
+ if (declaration &&
395
+ (program.isSourceFileDefaultLibrary(declaration.getSourceFile()) ||
396
+ isProjectOwned(declaration, root))) {
397
+ return undefined;
398
+ }
399
+ const type = declaration
400
+ ? checker.getTypeOfSymbolAtLocation(property, declaration)
401
+ : checker.getTypeOfSymbol(property);
402
+ const optional = property.flags & ts.SymbolFlags.Optional ? '?' : '';
403
+ const doc = ts
404
+ .displayPartsToString(property.getDocumentationComment(checker))
405
+ .replace(/\s+/g, ' ')
406
+ .trim();
407
+ return {
408
+ line: collapseRuns(`${property.getName()}${optional}: ${printResolved(type, declaration, checker, optional === '?')}`),
409
+ ...(doc ? { doc } : {}),
410
+ };
411
+ };
412
+ /**
413
+ * Every wiring and function helper takes one options object, so its signature
414
+ * can only name that object's type and the keys someone has to write sit one
415
+ * level below it. Anything else describes itself.
416
+ */
417
+ const shapeOf = (type, kind, checker) => {
418
+ if (kind === 'class') {
419
+ return type.getConstructSignatures()[0]?.getReturnType();
420
+ }
421
+ if (kind !== 'function' && kind !== 'const')
422
+ return type;
423
+ const signature = type.getCallSignatures()[0];
424
+ if (!signature)
425
+ return type;
426
+ for (const parameter of signature.getParameters()) {
427
+ const declaration = parameter.valueDeclaration;
428
+ const parameterType = declaration
429
+ ? checker.getTypeOfSymbolAtLocation(parameter, declaration)
430
+ : checker.getTypeOfSymbol(parameter);
431
+ if (checker.getPropertiesOfType(parameterType).length > 0)
432
+ return parameterType;
433
+ }
434
+ return undefined;
435
+ };
436
+ const membersOf = (type, kind, checker, program, root) => {
437
+ const shape = shapeOf(type, kind, checker);
438
+ if (!shape)
439
+ return [];
440
+ return checker
441
+ .getPropertiesOfType(shape)
442
+ .map((property) => memberLine(property, checker, program, root))
443
+ .filter((member) => member !== undefined)
444
+ .sort((a, b) => a.line.localeCompare(b.line));
445
+ };
446
+ /**
447
+ * `addError(SomeError, { status })` is a runtime registration, so nothing about
448
+ * a class says what it maps to. The calls are top-level statements next to the
449
+ * classes they register, which makes the mapping readable by parsing rather
450
+ * than by importing and booting the runtime.
451
+ *
452
+ * A surveyed project consumes pikku as `.d.ts`, and a declaration file carries
453
+ * no statements — the registrations survive only in the `.js` beside it, which
454
+ * is why the emitted sibling is parsed too.
455
+ */
456
+ const collectErrorStatuses = (program) => {
457
+ const statuses = new Map();
458
+ const record = (name, details) => {
459
+ if (!ts.isIdentifier(name) || !ts.isObjectLiteralExpression(details))
460
+ return;
461
+ for (const property of details.properties) {
462
+ if (!ts.isPropertyAssignment(property))
463
+ continue;
464
+ if (property.name.getText() !== 'status')
465
+ continue;
466
+ const status = Number(property.initializer.getText());
467
+ if (Number.isFinite(status))
468
+ statuses.set(name.text, status);
469
+ }
470
+ };
471
+ const scan = (sourceFile) => {
472
+ for (const statement of sourceFile.statements) {
473
+ if (!ts.isExpressionStatement(statement))
474
+ continue;
475
+ const call = statement.expression;
476
+ if (!ts.isCallExpression(call) || !ts.isIdentifier(call.expression))
477
+ continue;
478
+ if (call.expression.text === 'addError') {
479
+ const [name, details] = call.arguments;
480
+ if (name && details)
481
+ record(name, details);
482
+ continue;
483
+ }
484
+ if (call.expression.text !== 'addErrors')
485
+ continue;
486
+ const [list] = call.arguments;
487
+ if (!list || !ts.isArrayLiteralExpression(list))
488
+ continue;
489
+ for (const entry of list.elements) {
490
+ if (!ts.isArrayLiteralExpression(entry))
491
+ continue;
492
+ const [name, details] = entry.elements;
493
+ if (name && details)
494
+ record(name, details);
495
+ }
496
+ }
497
+ };
498
+ for (const sourceFile of program.getSourceFiles()) {
499
+ if (!sourceFile.isDeclarationFile) {
500
+ scan(sourceFile);
501
+ continue;
502
+ }
503
+ if (!sourceFile.text.includes('PikkuError'))
504
+ continue;
505
+ const emitted = sourceFile.fileName.replace(/\.d\.([cm]?ts)$/, '.$1');
506
+ if (emitted === sourceFile.fileName)
507
+ continue;
508
+ const source = ts.sys.readFile(emitted.replace(/\.ts$/, '.js'));
509
+ if (source === undefined)
510
+ continue;
511
+ scan(ts.createSourceFile(emitted, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS));
512
+ }
513
+ return statuses;
514
+ };
515
+ export const collectSurface = async (packageDir, { importsSubpath, subpaths, snippets } = {}) => {
516
+ const root = resolve(packageDir);
517
+ const packageJson = await readJson(join(root, 'package.json'));
518
+ if (!packageJson)
519
+ return [];
520
+ const declaredMap = importsSubpath
521
+ ? packageJson.imports?.[importsSubpath] !== undefined
522
+ ? { [importsSubpath]: packageJson.imports[importsSubpath] }
523
+ : {}
524
+ : packageJson.exports
525
+ ? subpathMap(packageJson.exports)
526
+ : {};
527
+ if (Object.keys(declaredMap).length === 0)
528
+ return [];
529
+ const { outDir, paths } = await readCompilerOptions(root);
530
+ const packageName = packageJson.name ?? '';
531
+ const entries = [];
532
+ const seenEntryFiles = new Set();
533
+ for (const [subpath, value] of Object.entries(declaredMap)) {
534
+ if (!importsSubpath && !subpath.startsWith('.'))
535
+ continue;
536
+ const target = targetOf(value);
537
+ if (!target || !publishesCode(target))
538
+ continue;
539
+ const declared = target.includes('*')
540
+ ? expandWildcard(root, subpath, target, outDir)
541
+ : [{ subpath, target }];
542
+ for (const each of declared) {
543
+ if (subpaths && !subpaths.includes(each.subpath))
544
+ continue;
545
+ const entryFile = sourceForTarget(root, each.target, outDir);
546
+ if (!entryFile || seenEntryFiles.has(entryFile))
547
+ continue;
548
+ seenEntryFiles.add(entryFile);
549
+ entries.push({
550
+ subpath: each.subpath,
551
+ specifier: importsSubpath
552
+ ? each.subpath
553
+ : each.subpath === '.'
554
+ ? packageName
555
+ : `${packageName}${each.subpath.slice(1)}`,
556
+ entryFile,
557
+ });
558
+ }
559
+ }
560
+ if (entries.length === 0)
561
+ return [];
562
+ const program = ts.createProgram(entries.map((entry) => join(root, entry.entryFile)), {
563
+ ...paths,
564
+ target: ts.ScriptTarget.ESNext,
565
+ module: ts.ModuleKind.Node16,
566
+ moduleResolution: ts.ModuleResolutionKind.Node16,
567
+ skipLibCheck: true,
568
+ skipDefaultLibCheck: true,
569
+ types: [],
570
+ allowJs: false,
571
+ checkJs: false,
572
+ });
573
+ const checker = program.getTypeChecker();
574
+ const errorStatuses = collectErrorStatuses(program);
575
+ return entries.map((entry) => {
576
+ const sourceFile = program.getSourceFile(join(root, entry.entryFile));
577
+ const moduleSymbol = sourceFile
578
+ ? checker.getSymbolAtLocation(sourceFile)
579
+ : undefined;
580
+ const symbols = [];
581
+ for (const symbol of moduleSymbol
582
+ ? checker.getExportsOfModule(moduleSymbol)
583
+ : []) {
584
+ const file = declarationFileOf(symbol, checker);
585
+ const kind = kindOf(symbol, checker);
586
+ const docs = documentationOf(symbol, checker);
587
+ const tags = jsDocTagsOf(symbol, checker);
588
+ const name = symbol.getName();
589
+ const target = aliasTargetOf(symbol, checker);
590
+ const type = typeOfSymbol(target, kind, checker);
591
+ const location = target.declarations?.[0];
592
+ const examples = examplesOf(tags, name, snippets);
593
+ const members = type ? membersOf(type, kind, checker, program, root) : [];
594
+ symbols.push({
595
+ name,
596
+ kind,
597
+ declaredAt: file ? relative(root, file) : entry.entryFile,
598
+ declaredIn: file,
599
+ deprecated: isDeprecated(tags),
600
+ deprecatedReason: deprecationReason(tags),
601
+ summary: summaryOf(docs),
602
+ docs,
603
+ signature: type
604
+ ? signatureOf(name, type, kind, location, checker)
605
+ : undefined,
606
+ members: members.length > 0 ? members : undefined,
607
+ examples: examples.length > 0 ? examples : undefined,
608
+ status: errorStatuses.get(name),
609
+ });
610
+ }
611
+ return { ...entry, symbols };
612
+ });
613
+ };
package/dist/types.d.ts CHANGED
@@ -18,6 +18,7 @@ import type { SecretDefinitions } from '@pikku/core/secret';
18
18
  import type { CredentialDefinitions } from '@pikku/core/credential';
19
19
  import type { ScopeDefinitions } from '@pikku/core/scope';
20
20
  import type { SystemRoleDefinitions } from '@pikku/core/role';
21
+ import type { FeatureFlagDefinitions } from '@pikku/core/flag';
21
22
  import type { PersonaDefinitions } from '@pikku/core/persona';
22
23
  import type { VariableDefinitions } from '@pikku/core/variable';
23
24
  import type { TypesMap } from './types-map.js';
@@ -470,6 +471,19 @@ export interface InspectorState {
470
471
  addonRequiredParentServices: string[];
471
472
  addonCreatedServices: string[];
472
473
  addonServicesFactorySeen: boolean;
474
+ /**
475
+ * The project's `defineAnalyticsEvents` declarations, in visit order. Absent
476
+ * when nothing declares any.
477
+ */
478
+ analytics?: Array<{
479
+ file: string;
480
+ variable: string;
481
+ events: string[];
482
+ /** Each event's props as declared, keyed by event name then prop name, with
483
+ * the schema's own source text as the value. Absent for a shape the
484
+ * inspector cannot read off the declaration. */
485
+ props?: Record<string, Record<string, string>>;
486
+ }>;
473
487
  addonServerlessIncompatible: Map<string, string[]>;
474
488
  configFactories: PathToNameAndType;
475
489
  serverLifecycleFactories: PathToNameAndType;
@@ -548,6 +562,8 @@ export interface InspectorState {
548
562
  usedAddons: Set<string>;
549
563
  wireAddonDeclarations: Map<string, {
550
564
  package: string;
565
+ /** The app source file whose `wireAddon` call declared this instance. */
566
+ file?: string;
551
567
  rpcEndpoint?: string;
552
568
  mcp?: boolean;
553
569
  /**
@@ -633,17 +649,6 @@ export interface InspectorState {
633
649
  * codebase, if any. The CLI generates the `/auth/*` HTTP wiring from it.
634
650
  * More than one `pikkuBetterAuth` is a critical error. */
635
651
  definition: AuthDefinition | null;
636
- /** True when a user (non-generated) file already registers
637
- * `betterAuthStatelessSession(...)`. The CLI then skips auto-generating its
638
- * own default-map stateless middleware, which would otherwise pre-empt the
639
- * user's custom mapSession (pikkujs/pikku#754). */
640
- userStatelessSession?: boolean;
641
- /** True when a user (non-generated) file already registers a global
642
- * `betterAuthSession(...)`. The CLI then skips auto-generating its own
643
- * default stateful middleware, which would otherwise run first and pre-empt
644
- * the user's config (mapSession/impersonation/apiKey). Stateful analogue of
645
- * `userStatelessSession`. */
646
- hasUserSessionMiddleware?: boolean;
647
652
  };
648
653
  secrets: {
649
654
  definitions: SecretDefinitions;
@@ -665,6 +670,10 @@ export interface InspectorState {
665
670
  definitions: SystemRoleDefinitions;
666
671
  files: Set<string>;
667
672
  };
673
+ featureFlags: {
674
+ definitions: FeatureFlagDefinitions;
675
+ files: Set<string>;
676
+ };
668
677
  personas: {
669
678
  definitions: PersonaDefinitions;
670
679
  files: Set<string>;