@pikku/inspector 0.12.91 → 0.12.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,36 @@
1
+ ## 0.12.94
2
+
3
+ ### Patch Changes
4
+
5
+ - 4323bcf: Inline wiring functions now record their source file, so `pikku release` leaves scaffold-injected ones out of the API surface.
6
+
7
+ ## 0.12.93
8
+
9
+ ### Patch Changes
10
+
11
+ - 658f047: A function whose services are all singletons no longer builds wire services, so an addon's credential-bound wire factory does not run for, or fail, a webhook `receive` step that needs no connection. The inspector decides this from the `Services` type and records it as `singletonServicesOnly` on the function's runtime meta; the runner only reads the flag.
12
+ - 658f047: Webhook signing secrets live in the credential store. `WebhookSigningSecret.fromCredential(provider, credentialService, name)` reads the secret per delivery via `load()`, so a `setup` step (or a handshake) that stores a new one with `credentialService.set` takes effect without a deploy.
13
+
14
+ Breaking: `setup` no longer returns `secret`, `wireTriggerWebhookSource` no longer takes `secret`, lifecycle outcomes drop `secretName`/`secret`, and `pikku webhooks setup` drops `--secretsOut`.
15
+
16
+ - 658f047: A webhook source's `method` may be a list, mounting one route per method, for providers that verify the URL with a GET and deliver events with a POST (WhatsApp, Strava, Onfleet, Mailchimp), or a HEAD (Trello, Mandrill, SurveyMonkey).
17
+ - Updated dependencies [658f047]
18
+ - Updated dependencies [de63ab2]
19
+ - Updated dependencies [658f047]
20
+ - Updated dependencies [658f047]
21
+ - Updated dependencies [658f047]
22
+ - Updated dependencies [658f047]
23
+ - @pikku/core@0.12.128
24
+
25
+ ## 0.12.92
26
+
27
+ ### Patch Changes
28
+
29
+ - f84e173: An addon wired from a workspace package now resolves from that package. The installed-addon check (PKU340), the remote addon `devDependencies` check and `pikku db generate`'s addon schemas all looked only at the project root, so an addon declared where it is wired — the only place bun links it — failed codegen unless it was also declared at the root.
30
+ - Updated dependencies [f02585e]
31
+ - Updated dependencies [5586749]
32
+ - @pikku/core@0.12.127
33
+
1
34
  ## 0.12.91
2
35
 
3
36
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
2
+ import { getArrayPropertyValue, getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
3
3
  import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
4
4
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
5
5
  import { ensureInlineWiringFunction } from '../utils/ensure-function-metadata.js';
@@ -114,9 +114,10 @@ const addWireTriggerWebhookSource = (logger, node, checker, state, firstArg) =>
114
114
  logger.critical(ErrorCode.MISSING_NAME, `wireTriggerWebhookSource in ${node.getSourceFile().fileName} needs 'name' as a string literal.`);
115
115
  return;
116
116
  }
117
- const method = getPropertyValue(obj, 'method') ?? 'post';
117
+ const method = getArrayPropertyValue(obj, 'method') ??
118
+ getPropertyValue(obj, 'method') ??
119
+ 'post';
118
120
  const route = getPropertyValue(obj, 'route') ?? `/webhooks/${name}`;
119
- let secret = getPropertyValue(obj, 'secret');
120
121
  const eventsInitializer = getPropertyAssignmentInitializer(obj, 'events', false, checker);
121
122
  const events = eventsInitializer
122
123
  ? checker
@@ -151,23 +152,11 @@ const addWireTriggerWebhookSource = (logger, node, checker, state, firstArg) =>
151
152
  steps[step] = pikkuFuncId;
152
153
  state.serviceAggregation.usedFunctions.add(pikkuFuncId);
153
154
  }
154
- // An addon's receive reads the secret by its own name; the app may have
155
- // renamed it for this instance.
156
- const namespace = steps.receive?.includes(':')
157
- ? steps.receive.slice(0, steps.receive.indexOf(':'))
158
- : null;
159
- const addon = namespace
160
- ? state.rpc.wireAddonDeclarations.get(namespace)
161
- : undefined;
162
- if (secret && addon?.secretOverrides?.[secret]) {
163
- secret = addon.secretOverrides[secret];
164
- }
165
155
  const meta = {
166
156
  name,
167
157
  method: method,
168
158
  route,
169
159
  events,
170
- ...(secret ? { secret } : {}),
171
160
  ...steps,
172
161
  };
173
162
  state.triggers.webhookSourceMeta[name] = meta;
package/dist/index.d.ts CHANGED
@@ -26,3 +26,4 @@ export type { SerializedWorkflowGraph, SerializedWorkflowGraphs, } from './utils
26
26
  export { resolveCoreType } from './utils/resolve-core-type.js';
27
27
  export { readModuleSpecifiers } from './utils/read-module-specifiers.js';
28
28
  export { readTsconfigOutDir } from './utils/read-tsconfig-out-dir.js';
29
+ export { addonResolutionDirs, createAddonResolver, } from './utils/addon-resolution.js';
package/dist/index.js CHANGED
@@ -15,3 +15,4 @@ export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
15
15
  export { resolveCoreType } from './utils/resolve-core-type.js';
16
16
  export { readModuleSpecifiers } from './utils/read-module-specifiers.js';
17
17
  export { readTsconfigOutDir } from './utils/read-tsconfig-out-dir.js';
18
+ export { addonResolutionDirs, createAddonResolver, } from './utils/addon-resolution.js';
@@ -0,0 +1,10 @@
1
+ export type AddonResolver = {
2
+ resolve: (id: string) => string;
3
+ };
4
+ /**
5
+ * An addon is installed wherever the package that calls `wireAddon` lists it,
6
+ * which in a workspace is usually not the repo root. Resolve from the declaring
7
+ * package first, as its code does at runtime, then from the root.
8
+ */
9
+ export declare const addonResolutionDirs: (rootDir: string, declFile?: string) => string[];
10
+ export declare const createAddonResolver: (dirs: string[]) => AddonResolver;
@@ -0,0 +1,43 @@
1
+ import { existsSync } from 'fs';
2
+ import { createRequire } from 'module';
3
+ import { join, dirname, parse, resolve } from 'path';
4
+ const nearestPackageDir = (file) => {
5
+ const root = parse(file).root;
6
+ let dir = dirname(file);
7
+ while (dir && dir !== root) {
8
+ if (existsSync(join(dir, 'package.json')))
9
+ return dir;
10
+ const parent = dirname(dir);
11
+ if (parent === dir)
12
+ break;
13
+ dir = parent;
14
+ }
15
+ return null;
16
+ };
17
+ /**
18
+ * An addon is installed wherever the package that calls `wireAddon` lists it,
19
+ * which in a workspace is usually not the repo root. Resolve from the declaring
20
+ * package first, as its code does at runtime, then from the root.
21
+ */
22
+ export const addonResolutionDirs = (rootDir, declFile) => {
23
+ const root = resolve(rootDir);
24
+ const declDir = declFile ? nearestPackageDir(resolve(declFile)) : null;
25
+ return declDir && declDir !== root ? [declDir, root] : [root];
26
+ };
27
+ export const createAddonResolver = (dirs) => {
28
+ const requires = dirs.map((dir) => createRequire(join(dir, 'package.json')));
29
+ return {
30
+ resolve: (id) => {
31
+ let lastError;
32
+ for (const req of requires) {
33
+ try {
34
+ return req.resolve(id);
35
+ }
36
+ catch (e) {
37
+ lastError = e;
38
+ }
39
+ }
40
+ throw lastError;
41
+ },
42
+ };
43
+ };
@@ -176,6 +176,9 @@ export function ensureFunctionMetadata(state, pikkuFuncId, fallbackName, funcIni
176
176
  };
177
177
  }
178
178
  const meta = state.functions.meta[pikkuFuncId];
179
+ if (!meta.sourceFile && funcInitializer) {
180
+ meta.sourceFile = funcInitializer.getSourceFile().fileName;
181
+ }
179
182
  if (funcInitializer && checker) {
180
183
  let pikkuFuncCall = null;
181
184
  if (ts.isCallExpression(funcInitializer)) {
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'fs';
2
2
  import { readFile, readdir } from 'fs/promises';
3
- import { createRequire } from 'module';
4
3
  import { join, dirname, parse, relative } from 'path';
4
+ import { addonResolutionDirs, createAddonResolver, } from './addon-resolution.js';
5
5
  import { ErrorCode } from '../error-codes.js';
6
6
  const isHTTPRouteConfig = (value) => typeof value === 'object' &&
7
7
  value !== null &&
@@ -90,45 +90,6 @@ const resolveAddonMeta = (require, packageName, baseName) => {
90
90
  }
91
91
  return null;
92
92
  };
93
- const nearestPackageDir = (file) => {
94
- const root = parse(file).root;
95
- let dir = dirname(file);
96
- while (dir && dir !== root) {
97
- if (existsSync(join(dir, 'package.json')))
98
- return dir;
99
- const parent = dirname(dir);
100
- if (parent === dir)
101
- break;
102
- dir = parent;
103
- }
104
- return null;
105
- };
106
- /**
107
- * An addon is installed wherever the package that calls `wireAddon` lists it,
108
- * which in a workspace is usually not the repo root. Resolve from the declaring
109
- * package first, as its code does at runtime, then from the root.
110
- */
111
- const addonResolutionDirs = (rootDir, declFile) => {
112
- const declDir = declFile ? nearestPackageDir(declFile) : null;
113
- return declDir && declDir !== rootDir ? [declDir, rootDir] : [rootDir];
114
- };
115
- const createAddonResolver = (dirs) => {
116
- const requires = dirs.map((dir) => createRequire(join(dir, 'package.json')));
117
- return {
118
- resolve: (id) => {
119
- let lastError;
120
- for (const req of requires) {
121
- try {
122
- return req.resolve(id);
123
- }
124
- catch (e) {
125
- lastError = e;
126
- }
127
- }
128
- throw lastError;
129
- },
130
- };
131
- };
132
93
  const findInstalledPackageDir = (dirs, packageName) => {
133
94
  for (const start of dirs) {
134
95
  const root = parse(start).root;
@@ -10,6 +10,7 @@ import { relative } from 'node:path';
10
10
  import { AUTH_HANDLER_FUNC_ID } from '../add/add-auth.js';
11
11
  import { flattenScopeDefinitions } from '@pikku/core/scope';
12
12
  import { DYNAMIC_SCENARIO_STEP_TARGET } from './workflow/dsl/patterns.js';
13
+ import { addonResolutionDirs } from './addon-resolution.js';
13
14
  /**
14
15
  * Stamp the inspected authorize/callbacks service set onto the generated auth
15
16
  * handler's function meta.
@@ -93,6 +94,24 @@ function extractAllServices(state) {
93
94
  state.serviceAggregation.allWireServices = extractTypeKeys(servicesType)
94
95
  .filter((name) => !singletonSet.has(name))
95
96
  .sort();
97
+ markSingletonServicesOnly(state, new Set(state.serviceAggregation.allWireServices));
98
+ }
99
+ }
100
+ /**
101
+ * Marks the functions that never read a wire service, so the runner can skip
102
+ * `createWireServices` for them. Only called once the `Services` type has
103
+ * resolved: an unresolved type yields no wire services, which would otherwise
104
+ * mark every function.
105
+ */
106
+ function markSingletonServicesOnly(state, wireServices) {
107
+ for (const meta of Object.values(state.functions.meta)) {
108
+ if (meta.services?.optimized &&
109
+ !meta.services.services.some((name) => wireServices.has(name))) {
110
+ meta.singletonServicesOnly = true;
111
+ }
112
+ else {
113
+ delete meta.singletonServicesOnly;
114
+ }
96
115
  }
97
116
  }
98
117
  /**
@@ -460,32 +479,34 @@ export function validateVariableOverrides(logger, state) {
460
479
  }
461
480
  }
462
481
  /**
463
- * Can `pkg` be resolved from `rootDir`, by any means the loader would use?
482
+ * Can `pkg` be resolved from any of `dirs`, by any means the loader would use?
464
483
  *
465
484
  * Resolution is attempted against `<pkg>/package.json` first and the bare
466
485
  * specifier second, because neither alone is conclusive. A package with a
467
486
  * restrictive `exports` map refuses the first with
468
487
  * `ERR_PACKAGE_PATH_NOT_EXPORTED` — which is not a failure at all: reaching the
469
- * exports map means the package was found. Only a module-not-found from both
470
- * attempts means it is genuinely absent.
488
+ * exports map means the package was found. Only a module-not-found from every
489
+ * attempt means it is genuinely absent.
471
490
  */
472
- const isPackageResolvable = (pkg, rootDir) => {
491
+ const isPackageResolvable = (pkg, dirs) => {
473
492
  const NOT_FOUND = new Set(['MODULE_NOT_FOUND', 'ERR_MODULE_NOT_FOUND']);
474
- let req;
475
- try {
476
- req = createRequire(join(rootDir, 'package.json'));
477
- }
478
- catch {
479
- return true; // cannot ask; do not accuse
480
- }
481
- for (const specifier of [`${pkg}/package.json`, pkg]) {
493
+ for (const dir of dirs) {
494
+ let req;
482
495
  try {
483
- req.resolve(specifier);
484
- return true;
496
+ req = createRequire(join(dir, 'package.json'));
485
497
  }
486
- catch (e) {
487
- if (!NOT_FOUND.has(e?.code))
498
+ catch {
499
+ return true; // cannot ask; do not accuse
500
+ }
501
+ for (const specifier of [`${pkg}/package.json`, pkg]) {
502
+ try {
503
+ req.resolve(specifier);
488
504
  return true;
505
+ }
506
+ catch (e) {
507
+ if (!NOT_FOUND.has(e?.code))
508
+ return true;
509
+ }
489
510
  }
490
511
  }
491
512
  return false;
@@ -523,35 +544,45 @@ export function validateRemoteAddonDependencies(logger, state) {
523
544
  for (const [namespace, decl] of wireAddonDeclarations.entries()) {
524
545
  if (decl.remote)
525
546
  continue;
526
- if (isPackageResolvable(decl.package, state.rootDir))
547
+ const dirs = addonResolutionDirs(state.rootDir, decl.file);
548
+ if (isPackageResolvable(decl.package, dirs))
527
549
  continue;
528
- logger.critical(ErrorCode.ADDON_NOT_INSTALLED, `Addon '${namespace}' ('${decl.package}') is wired with wireAddon but cannot be resolved — every ref('${namespace}:…') will resolve to nothing and the surface will be dead at runtime. Install it, or remove the wireAddon call.`);
529
- }
530
- if (!Array.from(wireAddonDeclarations.values()).some((d) => d.remote))
531
- return;
532
- const pkgJsonPath = join(state.rootDir, 'package.json');
533
- if (!existsSync(pkgJsonPath))
534
- return; // no manifest to check (e.g. some tests)
535
- let pkgJson;
536
- try {
537
- pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
538
- }
539
- catch (e) {
540
- logger.warn(`Could not read ${pkgJsonPath} to verify remote addon dependencies: ${e?.message ?? e}`);
541
- return;
542
- }
543
- const prodDeps = pkgJson.dependencies ?? {};
544
- const devDeps = pkgJson.devDependencies ?? {};
550
+ logger.critical(ErrorCode.ADDON_NOT_INSTALLED, `Addon '${namespace}' ('${decl.package}') is wired with wireAddon but cannot be resolved from ${dirs.join(' or ')} — every ref('${namespace}:…') will resolve to nothing and the surface will be dead at runtime. Add it to the dependencies of ${join(dirs[0], 'package.json')} and install, or remove the wireAddon call.`);
551
+ }
552
+ // A remote addon belongs in the devDependencies of the package that wires
553
+ // it, which in a workspace is not the root.
554
+ const manifests = new Map();
555
+ const readManifest = (path) => {
556
+ if (manifests.has(path))
557
+ return manifests.get(path);
558
+ let manifest = null;
559
+ if (existsSync(path)) {
560
+ try {
561
+ manifest = JSON.parse(readFileSync(path, 'utf-8'));
562
+ }
563
+ catch (e) {
564
+ logger.warn(`Could not read ${path} to verify remote addon dependencies: ${e?.message ?? e}`);
565
+ }
566
+ }
567
+ manifests.set(path, manifest);
568
+ return manifest;
569
+ };
545
570
  for (const [namespace, decl] of wireAddonDeclarations.entries()) {
546
571
  if (!decl.remote)
547
572
  continue;
573
+ const pkgJsonPath = join(addonResolutionDirs(state.rootDir, decl.file)[0], 'package.json');
574
+ const pkgJson = readManifest(pkgJsonPath);
575
+ if (!pkgJson)
576
+ continue; // no manifest to check (e.g. some tests)
577
+ const prodDeps = pkgJson.dependencies ?? {};
578
+ const devDeps = pkgJson.devDependencies ?? {};
548
579
  if (decl.package in devDeps)
549
580
  continue; // correct
550
581
  if (decl.package in prodDeps) {
551
- logger.critical(ErrorCode.REMOTE_ADDON_NOT_DEV_DEPENDENCY, `Remote addon '${namespace}' ('${decl.package}') is a production dependency, but wireRemoteAddon consumes it for types only — its handlers run on the host. Move '${decl.package}' from "dependencies" to "devDependencies".`);
582
+ logger.critical(ErrorCode.REMOTE_ADDON_NOT_DEV_DEPENDENCY, `Remote addon '${namespace}' ('${decl.package}') is a production dependency in ${pkgJsonPath}, but wireRemoteAddon consumes it for types only — its handlers run on the host. Move '${decl.package}' from "dependencies" to "devDependencies".`);
552
583
  }
553
584
  else {
554
- logger.critical(ErrorCode.REMOTE_ADDON_NOT_DEV_DEPENDENCY, `Remote addon '${namespace}' ('${decl.package}') is wired with wireRemoteAddon but is not in "devDependencies". Add '${decl.package}' to "devDependencies" (types only).`);
585
+ logger.critical(ErrorCode.REMOTE_ADDON_NOT_DEV_DEPENDENCY, `Remote addon '${namespace}' ('${decl.package}') is wired with wireRemoteAddon but is not in the "devDependencies" of ${pkgJsonPath}. Add '${decl.package}' to "devDependencies" (types only).`);
555
586
  }
556
587
  }
557
588
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/inspector",
3
- "version": "0.12.91",
3
+ "version": "0.12.94",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/pikkujs/pikku.git",
@@ -54,10 +54,10 @@
54
54
  "zod": "^4.4.3"
55
55
  },
56
56
  "peerDependencies": {
57
- "@pikku/core": "^0.12.125"
57
+ "@pikku/core": "^0.12.128"
58
58
  },
59
59
  "devDependencies": {
60
- "@pikku/core": "^0.12.125",
60
+ "@pikku/core": "^0.12.128",
61
61
  "@types/node": "^24.13.3"
62
62
  },
63
63
  "engines": {