@pikku/cli 0.12.91 → 0.12.92

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 (222) hide show
  1. package/LICENSE +106 -0
  2. package/console-app/assets/{index-CpkvmAGb.js → index-DDpIMCpy.js} +149 -149
  3. package/console-app/index.html +1 -1
  4. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  5. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  7. package/dist/.pikku/cli/pikku-cli-channel.js +6 -1
  8. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  9. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  10. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
  11. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
  12. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +15 -0
  13. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  14. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  15. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  16. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +21 -0
  17. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  18. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  19. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  20. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  21. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  22. package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
  23. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  24. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  25. package/dist/.pikku/function/pikku-functions-meta.gen.json +35 -40
  26. package/dist/.pikku/function/pikku-functions.gen.js +1 -3
  27. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  28. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  29. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  30. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  31. package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +2 -1
  32. package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +2 -1
  33. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  34. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  35. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  36. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  37. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  38. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  39. package/dist/.pikku/pikku-types.gen.js +1 -1
  40. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  41. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  42. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  43. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  44. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  45. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  46. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +1 -1
  47. package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
  48. package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
  49. package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
  50. package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
  51. package/dist/.pikku/scenarios/schemas/register.gen.d.ts +4 -0
  52. package/dist/.pikku/scenarios/schemas/register.gen.js +4 -0
  53. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  54. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  55. package/dist/.pikku/schemas/register.gen.js +5 -3
  56. package/dist/.pikku/schemas/schemas/FabricSecretsListOutput.schema.json +1 -1
  57. package/dist/.pikku/schemas/schemas/FabricSecretsRotateInput.schema.json +1 -0
  58. package/dist/.pikku/schemas/schemas/FabricSecretsRotateOutput.schema.json +1 -0
  59. package/dist/.pikku/schemas/schemas/FabricSecretsSetOutput.schema.json +1 -1
  60. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  61. package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +1 -1
  62. package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
  63. package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
  64. package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
  65. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  66. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  67. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  68. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  69. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  70. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  71. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  72. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  73. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  74. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  75. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +2 -8
  76. package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +1 -1
  77. package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +1 -1
  78. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  79. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  80. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  81. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  82. package/dist/bin/pikku-bin.mjs +2 -2
  83. package/dist/src/cli.wiring.js +6 -0
  84. package/dist/src/deploy/analyzer/analyzer.js +22 -5
  85. package/dist/src/deploy/build-pipeline.js +5 -1
  86. package/dist/src/fabric/fabric-commands.d.ts +37 -9
  87. package/dist/src/fabric/fabric-commands.js +12 -0
  88. package/dist/src/fabric/functions/domains-add.function.d.ts +4 -4
  89. package/dist/src/fabric/functions/secrets-list.function.d.ts +16 -4
  90. package/dist/src/fabric/functions/secrets-list.function.js +12 -10
  91. package/dist/src/fabric/functions/secrets-rotate.function.d.ts +24 -0
  92. package/dist/src/fabric/functions/secrets-rotate.function.js +36 -0
  93. package/dist/src/fabric/functions/secrets-set.function.d.ts +8 -4
  94. package/dist/src/fabric/functions/secrets-set.function.js +18 -7
  95. package/dist/src/fabric/lib/http.d.ts +7 -1
  96. package/dist/src/fabric/lib/sealed-box.d.ts +16 -0
  97. package/dist/src/fabric/lib/sealed-box.js +72 -0
  98. package/dist/src/functions/commands/dev.js +9 -0
  99. package/dist/src/functions/commands/pikku-command-bootstrap.js +1 -0
  100. package/dist/src/functions/commands/scenario-browser.d.ts +17 -2
  101. package/dist/src/functions/commands/scenario-browser.js +16 -8
  102. package/dist/src/functions/commands/scenario-environment.d.ts +32 -0
  103. package/dist/src/functions/commands/scenario-environment.js +61 -0
  104. package/dist/src/functions/commands/scenario.d.ts +6 -0
  105. package/dist/src/functions/commands/scenario.js +14 -9
  106. package/dist/src/functions/commands/skills.js +27 -61
  107. package/dist/src/functions/db/db-codegen.d.ts +6 -0
  108. package/dist/src/functions/db/db-codegen.js +9 -0
  109. package/dist/src/functions/db/db-migrator.js +14 -0
  110. package/dist/src/functions/db/local-db.d.ts +8 -0
  111. package/dist/src/functions/db/local-db.js +24 -2
  112. package/dist/src/functions/db/migration-identifiers.d.ts +58 -0
  113. package/dist/src/functions/db/migration-identifiers.js +262 -0
  114. package/dist/src/functions/db/migration-provenance.d.ts +35 -0
  115. package/dist/src/functions/db/migration-provenance.js +80 -0
  116. package/dist/src/functions/db/schema-sql.d.ts +43 -0
  117. package/dist/src/functions/db/schema-sql.js +135 -0
  118. package/dist/src/functions/db/sqlite/sqlite-kysely.js +22 -2
  119. package/dist/src/functions/wirings/functions/schemas.js +13 -1
  120. package/dist/src/functions/wirings/rpc/pikku-command-rpc.js +6 -1
  121. package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.d.ts +10 -0
  122. package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.js +102 -0
  123. package/dist/src/functions/wirings/scenarios/scenario-partition.d.ts +13 -0
  124. package/dist/src/functions/wirings/scenarios/scenario-partition.js +15 -2
  125. package/dist/src/functions/wirings/scenarios/scenario-schema-partition.d.ts +33 -0
  126. package/dist/src/functions/wirings/scenarios/scenario-schema-partition.js +49 -0
  127. package/dist/src/functions/workflows/all.workflow.js +2 -7
  128. package/dist/src/utils/file-writer.js +14 -2
  129. package/dist/src/utils/pikku-cli-config.d.ts +14 -0
  130. package/dist/src/utils/pikku-cli-config.js +34 -0
  131. package/dist/src/utils/remove-legacy-scaffold-file.d.ts +14 -0
  132. package/dist/src/utils/remove-legacy-scaffold-file.js +23 -0
  133. package/dist/src/utils/serialize-schemas.d.ts +3 -1
  134. package/dist/src/utils/serialize-schemas.js +39 -4
  135. package/dist/tsconfig.tsbuildinfo +1 -1
  136. package/package.json +9 -9
  137. package/dist/.pikku/schemas/schemas/PikkuScenarioFunctionsOutput.schema.json +0 -1
  138. package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.d.ts +0 -1
  139. package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.js +0 -31
  140. package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.d.ts +0 -10
  141. package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.js +0 -104
  142. package/skills/pikku-addon/SKILL.md +0 -243
  143. package/skills/pikku-addon/references/addon-package-manifest.md +0 -63
  144. package/skills/pikku-ai-agent/SKILL.md +0 -231
  145. package/skills/pikku-ai-vercel/SKILL.md +0 -82
  146. package/skills/pikku-ai-voice/SKILL.md +0 -88
  147. package/skills/pikku-audit/SKILL.md +0 -175
  148. package/skills/pikku-aws/SKILL.md +0 -111
  149. package/skills/pikku-backblaze/SKILL.md +0 -71
  150. package/skills/pikku-better-auth/SKILL.md +0 -298
  151. package/skills/pikku-cli/SKILL.md +0 -198
  152. package/skills/pikku-cli/references/complete-example.md +0 -82
  153. package/skills/pikku-concepts/SKILL.md +0 -250
  154. package/skills/pikku-concepts/references/concept-mapping.md +0 -556
  155. package/skills/pikku-concepts/references/packages.md +0 -29
  156. package/skills/pikku-config/SKILL.md +0 -212
  157. package/skills/pikku-cron/SKILL.md +0 -214
  158. package/skills/pikku-deploy-azure/SKILL.md +0 -71
  159. package/skills/pikku-deploy-cloudflare/SKILL.md +0 -82
  160. package/skills/pikku-deploy-express/SKILL.md +0 -86
  161. package/skills/pikku-deploy-fastify/SKILL.md +0 -73
  162. package/skills/pikku-deploy-lambda/SKILL.md +0 -110
  163. package/skills/pikku-deploy-nextjs/SKILL.md +0 -78
  164. package/skills/pikku-deploy-uws/SKILL.md +0 -88
  165. package/skills/pikku-deps/SKILL.md +0 -98
  166. package/skills/pikku-emails/SKILL.md +0 -163
  167. package/skills/pikku-fabric/SKILL.md +0 -318
  168. package/skills/pikku-fabric-debug/SKILL.md +0 -112
  169. package/skills/pikku-feature/SKILL.md +0 -258
  170. package/skills/pikku-gateway-slack/SKILL.md +0 -115
  171. package/skills/pikku-http/SKILL.md +0 -220
  172. package/skills/pikku-http/references/http-options.md +0 -55
  173. package/skills/pikku-i18n/SKILL.md +0 -137
  174. package/skills/pikku-info/SKILL.md +0 -100
  175. package/skills/pikku-jose/SKILL.md +0 -105
  176. package/skills/pikku-kysely/SKILL.md +0 -219
  177. package/skills/pikku-machine-auth/SKILL.md +0 -183
  178. package/skills/pikku-mcp/SKILL.md +0 -241
  179. package/skills/pikku-middleware/SKILL.md +0 -231
  180. package/skills/pikku-middleware/references/middleware-patterns.md +0 -61
  181. package/skills/pikku-mongodb/SKILL.md +0 -105
  182. package/skills/pikku-n8n-import/SKILL.md +0 -109
  183. package/skills/pikku-n8n-import/SPEC.md +0 -84
  184. package/skills/pikku-n8n-import/references/addon-mapping.md +0 -121
  185. package/skills/pikku-n8n-import/references/code-translation.md +0 -121
  186. package/skills/pikku-n8n-import/references/loops-and-control.md +0 -87
  187. package/skills/pikku-paraglide/SKILL.md +0 -117
  188. package/skills/pikku-permissions/SKILL.md +0 -192
  189. package/skills/pikku-pino/SKILL.md +0 -79
  190. package/skills/pikku-product-second-opinion/README.md +0 -43
  191. package/skills/pikku-product-second-opinion/SKILL.md +0 -158
  192. package/skills/pikku-product-second-opinion/example/sample-report.md +0 -90
  193. package/skills/pikku-product-second-opinion/references/report-template.md +0 -73
  194. package/skills/pikku-queue/SKILL.md +0 -240
  195. package/skills/pikku-react/SKILL.md +0 -212
  196. package/skills/pikku-react-query/SKILL.md +0 -242
  197. package/skills/pikku-realtime/SKILL.md +0 -236
  198. package/skills/pikku-realtime/references/other-routes.md +0 -23
  199. package/skills/pikku-redis/SKILL.md +0 -90
  200. package/skills/pikku-rpc/SKILL.md +0 -171
  201. package/skills/pikku-rtl/SKILL.md +0 -219
  202. package/skills/pikku-scenario/SKILL.md +0 -382
  203. package/skills/pikku-schedule/SKILL.md +0 -57
  204. package/skills/pikku-schema-ajv/SKILL.md +0 -62
  205. package/skills/pikku-schema-cfworker/SKILL.md +0 -63
  206. package/skills/pikku-security/SKILL.md +0 -108
  207. package/skills/pikku-services/SKILL.md +0 -248
  208. package/skills/pikku-services/references/audit-wire-service.md +0 -34
  209. package/skills/pikku-software-archaeology/README.md +0 -70
  210. package/skills/pikku-software-archaeology/SKILL.md +0 -186
  211. package/skills/pikku-software-archaeology/references/blueprint.schema.json +0 -625
  212. package/skills/pikku-software-archaeology/references/pikku-mapping.md +0 -49
  213. package/skills/pikku-software-archaeology/scripts/validate.mjs +0 -173
  214. package/skills/pikku-tag-middleware/SKILL.md +0 -13
  215. package/skills/pikku-template-clone/SKILL.md +0 -40
  216. package/skills/pikku-trigger/SKILL.md +0 -181
  217. package/skills/pikku-versioning/SKILL.md +0 -173
  218. package/skills/pikku-websocket/SKILL.md +0 -243
  219. package/skills/pikku-workflow/SKILL.md +0 -172
  220. package/skills/pikku-workflow/references/workflow-reference.md +0 -63
  221. package/skills/pikku-workflows-client/SKILL.md +0 -150
  222. package/skills/pikku-ws/SKILL.md +0 -47
@@ -451,37 +451,65 @@ export declare const fabricCommands: {
451
451
  value?: string | undefined;
452
452
  force?: boolean | undefined;
453
453
  }, {
454
- ok: boolean;
454
+ name: string;
455
+ keyId: string;
455
456
  }, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
456
457
  name: string;
457
458
  branch: string;
458
459
  value?: string | undefined;
459
460
  force?: boolean | undefined;
460
461
  }, {
461
- ok: boolean;
462
+ name: string;
463
+ keyId: string;
462
464
  }, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices> | import("../../.pikku/pikku-types.gen.js").PikkuFunction<{
463
465
  name: string;
464
466
  branch: string;
465
467
  value?: string | undefined;
466
468
  force?: boolean | undefined;
467
469
  }, {
468
- ok: boolean;
470
+ name: string;
471
+ keyId: string;
469
472
  }, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>, import("../../.pikku/pikku-types.gen.js").PikkuMiddleware, (services: import("../../types/application-types.js").SingletonServices, data: any, session?: import("../../types/application-types.js").UserSession | undefined) => void | Promise<void>, string>;
470
473
  list: import("@pikku/core/cli").CoreCLICommandConfig<import("../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
471
474
  branch: string;
472
475
  json?: boolean | undefined;
473
476
  }, {
474
- names: string[];
477
+ secrets: {
478
+ name: string;
479
+ updatedAt: string;
480
+ }[];
475
481
  }, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
476
482
  branch: string;
477
483
  json?: boolean | undefined;
478
484
  }, {
479
- names: string[];
485
+ secrets: {
486
+ name: string;
487
+ updatedAt: string;
488
+ }[];
480
489
  }, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices> | import("../../.pikku/pikku-types.gen.js").PikkuFunction<{
481
490
  branch: string;
482
491
  json?: boolean | undefined;
483
492
  }, {
484
- names: string[];
493
+ secrets: {
494
+ name: string;
495
+ updatedAt: string;
496
+ }[];
497
+ }, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>, import("../../.pikku/pikku-types.gen.js").PikkuMiddleware, (services: import("../../types/application-types.js").SingletonServices, data: any, session?: import("../../types/application-types.js").UserSession | undefined) => void | Promise<void>, string>;
498
+ rotate: import("@pikku/core/cli").CoreCLICommandConfig<import("../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
499
+ branch: string;
500
+ force?: boolean | undefined;
501
+ }, {
502
+ retiredKeyId: string;
503
+ }, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
504
+ branch: string;
505
+ force?: boolean | undefined;
506
+ }, {
507
+ retiredKeyId: string;
508
+ }, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices> | import("../../.pikku/pikku-types.gen.js").PikkuFunction<{
509
+ branch: string;
510
+ force?: boolean | undefined;
511
+ }, {
512
+ retiredKeyId: string;
485
513
  }, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>, import("../../.pikku/pikku-types.gen.js").PikkuMiddleware, (services: import("../../types/application-types.js").SingletonServices, data: any, session?: import("../../types/application-types.js").UserSession | undefined) => void | Promise<void>, string>;
486
514
  };
487
515
  };
@@ -622,21 +650,21 @@ export declare const fabricCommands: {
622
650
  }, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>, import("../../.pikku/pikku-types.gen.js").PikkuMiddleware, (services: import("../../types/application-types.js").SingletonServices, data: any, session?: import("../../types/application-types.js").UserSession | undefined) => void | Promise<void>, string>;
623
651
  add: import("@pikku/core/cli").CoreCLICommandConfig<import("../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
624
652
  hostname: string;
625
- target?: "api" | "app" | undefined;
653
+ target?: "app" | "api" | undefined;
626
654
  apiUrl?: string | undefined;
627
655
  }, {
628
656
  hostname: string;
629
657
  cnameTarget: string;
630
658
  }, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
631
659
  hostname: string;
632
- target?: "api" | "app" | undefined;
660
+ target?: "app" | "api" | undefined;
633
661
  apiUrl?: string | undefined;
634
662
  }, {
635
663
  hostname: string;
636
664
  cnameTarget: string;
637
665
  }, "rpc" | "session", import("../../.pikku/pikku-types.gen.js").WiredServices> | import("../../.pikku/pikku-types.gen.js").PikkuFunction<{
638
666
  hostname: string;
639
- target?: "api" | "app" | undefined;
667
+ target?: "app" | "api" | undefined;
640
668
  apiUrl?: string | undefined;
641
669
  }, {
642
670
  hostname: string;
@@ -31,6 +31,7 @@ import { FabricDbSchema, renderDbSchema, } from './functions/db-schema.function.
31
31
  import { FabricRollback } from './functions/rollback.function.js';
32
32
  import { FabricSecretsSet } from './functions/secrets-set.function.js';
33
33
  import { FabricSecretsList } from './functions/secrets-list.function.js';
34
+ import { FabricSecretsRotate } from './functions/secrets-rotate.function.js';
34
35
  import { FabricLogs } from './functions/logs.function.js';
35
36
  import { FabricMetrics } from './functions/metrics.function.js';
36
37
  import { FabricTrace } from './functions/trace.function.js';
@@ -241,6 +242,17 @@ export const fabricCommands = defineCLICommands({
241
242
  json: { description: 'Machine-readable output', default: false },
242
243
  },
243
244
  }),
245
+ rotate: pikkuCLICommand({
246
+ func: FabricSecretsRotate,
247
+ description: "Retire a stage's sealing key (secrets must be set again)",
248
+ options: {
249
+ branch: { description: 'Target branch', short: 'b' },
250
+ force: {
251
+ description: 'Confirm that existing secrets become unreadable',
252
+ default: false,
253
+ },
254
+ },
255
+ }),
244
256
  },
245
257
  },
246
258
  logs: pikkuCLICommand({
@@ -2,8 +2,8 @@ import { z } from 'zod';
2
2
  export declare const FabricDomainsAddInput: z.ZodObject<{
3
3
  hostname: z.ZodString;
4
4
  target: z.ZodOptional<z.ZodEnum<{
5
- api: "api";
6
5
  app: "app";
6
+ api: "api";
7
7
  }>>;
8
8
  apiUrl: z.ZodOptional<z.ZodString>;
9
9
  }, z.core.$strip>;
@@ -13,21 +13,21 @@ export declare const FabricDomainsAddOutput: z.ZodObject<{
13
13
  }, z.core.$strip>;
14
14
  export declare const FabricDomainsAdd: import("../../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
15
15
  hostname: string;
16
- target?: "api" | "app" | undefined;
16
+ target?: "app" | "api" | undefined;
17
17
  apiUrl?: string | undefined;
18
18
  }, {
19
19
  hostname: string;
20
20
  cnameTarget: string;
21
21
  }, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
22
22
  hostname: string;
23
- target?: "api" | "app" | undefined;
23
+ target?: "app" | "api" | undefined;
24
24
  apiUrl?: string | undefined;
25
25
  }, {
26
26
  hostname: string;
27
27
  cnameTarget: string;
28
28
  }, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices> | import("../../../.pikku/pikku-types.gen.js").PikkuFunction<{
29
29
  hostname: string;
30
- target?: "api" | "app" | undefined;
30
+ target?: "app" | "api" | undefined;
31
31
  apiUrl?: string | undefined;
32
32
  }, {
33
33
  hostname: string;
@@ -4,21 +4,33 @@ export declare const FabricSecretsListInput: z.ZodObject<{
4
4
  json: z.ZodOptional<z.ZodBoolean>;
5
5
  }, z.core.$strip>;
6
6
  export declare const FabricSecretsListOutput: z.ZodObject<{
7
- names: z.ZodArray<z.ZodString>;
7
+ secrets: z.ZodArray<z.ZodObject<{
8
+ name: z.ZodString;
9
+ updatedAt: z.ZodString;
10
+ }, z.core.$strip>>;
8
11
  }, z.core.$strip>;
9
12
  export declare const FabricSecretsList: import("../../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
10
13
  branch: string;
11
14
  json?: boolean | undefined;
12
15
  }, {
13
- names: string[];
16
+ secrets: {
17
+ name: string;
18
+ updatedAt: string;
19
+ }[];
14
20
  }, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
15
21
  branch: string;
16
22
  json?: boolean | undefined;
17
23
  }, {
18
- names: string[];
24
+ secrets: {
25
+ name: string;
26
+ updatedAt: string;
27
+ }[];
19
28
  }, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices> | import("../../../.pikku/pikku-types.gen.js").PikkuFunction<{
20
29
  branch: string;
21
30
  json?: boolean | undefined;
22
31
  }, {
23
- names: string[];
32
+ secrets: {
33
+ name: string;
34
+ updatedAt: string;
35
+ }[];
24
36
  }, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>;
@@ -2,13 +2,16 @@ import { z } from 'zod';
2
2
  import { pikkuSessionlessFunc } from '../../../.pikku/pikku-types.gen.js';
3
3
  import { resolveApiContext } from '../lib/config.js';
4
4
  import { getFabricRPC } from '../lib/http.js';
5
+ import { resolveStageId } from '../lib/stage.js';
5
6
  export const FabricSecretsListInput = z.object({
6
7
  branch: z.string(),
7
8
  json: z.boolean().optional(),
8
9
  });
9
- export const FabricSecretsListOutput = z.object({ names: z.array(z.string()) });
10
+ export const FabricSecretsListOutput = z.object({
11
+ secrets: z.array(z.object({ name: z.string(), updatedAt: z.string() })),
12
+ });
10
13
  export const FabricSecretsList = pikkuSessionlessFunc({
11
- description: 'List secret names visible to a stage (values never leave the server).',
14
+ description: 'List the secrets set on a stage, by name. Values are sealed to the stage and never leave it.',
12
15
  input: FabricSecretsListInput,
13
16
  output: FabricSecretsListOutput,
14
17
  func: async (_services, { branch, json }) => {
@@ -18,19 +21,18 @@ export const FabricSecretsList = pikkuSessionlessFunc({
18
21
  if (!ctx.projectId)
19
22
  throw new Error('No fabric project linked. Run `pikku fabric link` first.');
20
23
  const rpc = getFabricRPC({ apiUrl: ctx.apiUrl, token: ctx.token });
21
- const result = await rpc.invoke('listStageSecrets', {
22
- projectId: ctx.projectId,
23
- branch,
24
- });
24
+ const stageId = await resolveStageId(rpc, ctx.projectId, branch);
25
+ const result = await rpc.invoke('listStageSecretNames', { stageId });
25
26
  if (json) {
26
- console.log(JSON.stringify({ branch, names: result.names }, null, 2));
27
+ console.log(JSON.stringify({ branch, secrets: result.secrets }, null, 2));
27
28
  }
28
- else if (result.names.length === 0) {
29
+ else if (result.secrets.length === 0) {
29
30
  console.log(`[fabric] no secrets set on ${branch}`);
30
31
  }
31
32
  else {
32
- for (const name of result.names)
33
- console.log(name);
33
+ for (const { name, updatedAt } of result.secrets) {
34
+ console.log(`${name}\t${updatedAt}`);
35
+ }
34
36
  }
35
37
  return result;
36
38
  },
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ export declare const FabricSecretsRotateInput: z.ZodObject<{
3
+ branch: z.ZodString;
4
+ force: z.ZodOptional<z.ZodBoolean>;
5
+ }, z.core.$strip>;
6
+ export declare const FabricSecretsRotateOutput: z.ZodObject<{
7
+ retiredKeyId: z.ZodString;
8
+ }, z.core.$strip>;
9
+ export declare const FabricSecretsRotate: import("../../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
10
+ branch: string;
11
+ force?: boolean | undefined;
12
+ }, {
13
+ retiredKeyId: string;
14
+ }, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
15
+ branch: string;
16
+ force?: boolean | undefined;
17
+ }, {
18
+ retiredKeyId: string;
19
+ }, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices> | import("../../../.pikku/pikku-types.gen.js").PikkuFunction<{
20
+ branch: string;
21
+ force?: boolean | undefined;
22
+ }, {
23
+ retiredKeyId: string;
24
+ }, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>;
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+ import { pikkuSessionlessFunc } from '../../../.pikku/pikku-types.gen.js';
3
+ import { resolveApiContext } from '../lib/config.js';
4
+ import { getFabricRPC } from '../lib/http.js';
5
+ import { resolveStageId } from '../lib/stage.js';
6
+ export const FabricSecretsRotateInput = z.object({
7
+ branch: z.string(),
8
+ force: z.boolean().optional(),
9
+ });
10
+ export const FabricSecretsRotateOutput = z.object({
11
+ retiredKeyId: z.string(),
12
+ });
13
+ export const FabricSecretsRotate = pikkuSessionlessFunc({
14
+ description: "Retire a stage's sealing key so the next deploy issues a new one. Secrets sealed to the old key must be set again.",
15
+ input: FabricSecretsRotateInput,
16
+ output: FabricSecretsRotateOutput,
17
+ func: async (_services, { branch, force }) => {
18
+ const ctx = await resolveApiContext();
19
+ if (!ctx.token)
20
+ throw new Error('Not logged in. Run `pikku fabric login` first.');
21
+ if (!ctx.projectId)
22
+ throw new Error('No fabric project linked. Run `pikku fabric link` first.');
23
+ if (!force) {
24
+ // Not a confirmation for politeness — this is the one operation here
25
+ // that destroys access to data. Fabric cannot read the sealed values, so
26
+ // it cannot carry them over, and nobody can undo it afterwards.
27
+ throw new Error(`Rotating the sealing key on ${branch} makes every secret already set on it unreadable — fabric cannot re-seal values it cannot open, so each one must be set again after the next deploy. Re-run with --force if that is what you want.`);
28
+ }
29
+ const rpc = getFabricRPC({ apiUrl: ctx.apiUrl, token: ctx.token });
30
+ const stageId = await resolveStageId(rpc, ctx.projectId, branch);
31
+ const result = await rpc.invoke('rotateStageSealingKey', { stageId });
32
+ console.log(`[fabric] retired sealing key ${result.retiredKeyId} on ${branch}`);
33
+ console.log('[fabric] deploy the stage to issue a new key, then set its secrets again');
34
+ return result;
35
+ },
36
+ });
@@ -6,7 +6,8 @@ export declare const FabricSecretsSetInput: z.ZodObject<{
6
6
  force: z.ZodOptional<z.ZodBoolean>;
7
7
  }, z.core.$strip>;
8
8
  export declare const FabricSecretsSetOutput: z.ZodObject<{
9
- ok: z.ZodBoolean;
9
+ name: z.ZodString;
10
+ keyId: z.ZodString;
10
11
  }, z.core.$strip>;
11
12
  export declare const FabricSecretsSet: import("../../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<{
12
13
  name: string;
@@ -14,19 +15,22 @@ export declare const FabricSecretsSet: import("../../../.pikku/pikku-types.gen.j
14
15
  value?: string | undefined;
15
16
  force?: boolean | undefined;
16
17
  }, {
17
- ok: boolean;
18
+ name: string;
19
+ keyId: string;
18
20
  }, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").PikkuFunctionSessionless<{
19
21
  name: string;
20
22
  branch: string;
21
23
  value?: string | undefined;
22
24
  force?: boolean | undefined;
23
25
  }, {
24
- ok: boolean;
26
+ name: string;
27
+ keyId: string;
25
28
  }, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices> | import("../../../.pikku/pikku-types.gen.js").PikkuFunction<{
26
29
  name: string;
27
30
  branch: string;
28
31
  value?: string | undefined;
29
32
  force?: boolean | undefined;
30
33
  }, {
31
- ok: boolean;
34
+ name: string;
35
+ keyId: string;
32
36
  }, "rpc" | "session", import("../../../.pikku/pikku-types.gen.js").WiredServices>, undefined, undefined>;
@@ -3,15 +3,20 @@ import { pikkuSessionlessFunc } from '../../../.pikku/pikku-types.gen.js';
3
3
  import { resolveApiContext } from '../lib/config.js';
4
4
  import { getFabricRPC } from '../lib/http.js';
5
5
  import { promptSecret } from '../lib/prompt.js';
6
+ import { resolveStageId } from '../lib/stage.js';
7
+ import { seal, serializeSealedValue } from '../lib/sealed-box.js';
6
8
  export const FabricSecretsSetInput = z.object({
7
9
  name: z.string(),
8
10
  branch: z.string(),
9
11
  value: z.string().optional(),
10
12
  force: z.boolean().optional(),
11
13
  });
12
- export const FabricSecretsSetOutput = z.object({ ok: z.boolean() });
14
+ export const FabricSecretsSetOutput = z.object({
15
+ name: z.string(),
16
+ keyId: z.string(),
17
+ });
13
18
  export const FabricSecretsSet = pikkuSessionlessFunc({
14
- description: 'Set a stage-scoped secret. Encrypted with the stage KEK and stored in the project D1.',
19
+ description: 'Set a stage-scoped secret. Sealed here to the stage public key the plaintext never leaves this machine.',
15
20
  input: FabricSecretsSetInput,
16
21
  output: FabricSecretsSetOutput,
17
22
  func: async (_services, { name, branch, value }) => {
@@ -24,13 +29,19 @@ export const FabricSecretsSet = pikkuSessionlessFunc({
24
29
  if (!plaintext)
25
30
  throw new Error('Empty secret value — aborting.');
26
31
  const rpc = getFabricRPC({ apiUrl: ctx.apiUrl, token: ctx.token });
27
- const result = await rpc.invoke('setStageSecret', {
28
- projectId: ctx.projectId,
29
- branch,
32
+ const stageId = await resolveStageId(rpc, ctx.projectId, branch);
33
+ // Fabric holds only the public half of this keypair. The private half went
34
+ // to the stage's worker at deploy time and was dropped there, so the value
35
+ // sealed below can be opened by that worker and by nothing else — including
36
+ // by the fabric process that stores it.
37
+ const key = await rpc.invoke('getStageSealingKey', { stageId });
38
+ const sealed = seal(key.publicKey, key.keyId, plaintext);
39
+ const result = await rpc.invoke('setStageSealedSecret', {
40
+ stageId,
30
41
  name,
31
- value: plaintext,
42
+ sealedValue: serializeSealedValue(sealed),
32
43
  });
33
- console.log(`[fabric] ${name} set on ${branch}.`);
44
+ console.log(`[fabric] ${name} sealed to ${result.keyId} and set on ${branch}.`);
34
45
  return result;
35
46
  },
36
47
  });
@@ -1,4 +1,10 @@
1
+ /**
2
+ * Type-safe RPC client backed by the snapshot SDK in `../sdk/`. The map is
3
+ * a copy of fabric's generated rpc-map at the time of the addon's last
4
+ * release — versioning bumps are taken by republishing this package.
5
+ */
6
+ import { PikkuRPC } from '../sdk/pikku-rpc.gen.js';
1
7
  export declare function getFabricRPC(opts: {
2
8
  apiUrl: string;
3
9
  token: string | null;
4
- }): any;
10
+ }): PikkuRPC;
@@ -0,0 +1,16 @@
1
+ /** Algorithm tag carried by every sealed value. Must match fabric's. */
2
+ export declare const SEALED_BOX_ALGORITHM = "sealed-x25519-v1";
3
+ export interface SealedValue {
4
+ alg: typeof SEALED_BOX_ALGORITHM;
5
+ /** The `encryption_key` row whose public key sealed this. */
6
+ kid: string;
7
+ /** Ephemeral public key, SPKI DER as base64. */
8
+ epk: string;
9
+ iv: string;
10
+ ct: string;
11
+ tag: string;
12
+ }
13
+ /** Seal a value to a stage's public key. */
14
+ export declare function seal(publicKey: string, keyId: string, plaintext: string): SealedValue;
15
+ /** Wire form. Fabric stores exactly this string. */
16
+ export declare function serializeSealedValue(sealed: SealedValue): string;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Sealing a value to a stage's public key, here on the client.
3
+ *
4
+ * `pikku fabric secrets set` seals before it sends, so the plaintext never
5
+ * reaches fabric — not the ingress in front of it, not a request log, not the
6
+ * process handling the write. Fabric stores the sealed blob and hands it to the
7
+ * worker, which holds the only private key that opens it.
8
+ *
9
+ * This is the seal half of fabric's `sealed-x25519-v1`, and the two must stay
10
+ * byte-compatible: an ephemeral X25519 keypair, ECDH against the recipient,
11
+ * HKDF-SHA256 to a content key, AES-256-GCM to seal. Everything below is
12
+ * `node:crypto` on purpose — a sealing primitive that pulled in a dependency
13
+ * would be a supply-chain question sitting directly on the plaintext.
14
+ *
15
+ * There is deliberately no `unseal` here. The CLI has no private key and no
16
+ * business having one; opening a value happens in the worker.
17
+ */
18
+ import { createCipheriv, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync, randomBytes, } from 'node:crypto';
19
+ /** Algorithm tag carried by every sealed value. Must match fabric's. */
20
+ export const SEALED_BOX_ALGORITHM = 'sealed-x25519-v1';
21
+ /** HKDF domain separator. Must match fabric's byte for byte. */
22
+ const SEAL_SALT = 'pikkufabric/sealed-box/v1';
23
+ /** Seal a value to a stage's public key. */
24
+ export function seal(publicKey, keyId, plaintext) {
25
+ const recipient = importPublicKey(publicKey);
26
+ const ephemeral = generateKeyPairSync('x25519');
27
+ const epk = ephemeral.publicKey.export({ type: 'spki', format: 'der' });
28
+ const contentKey = deriveContentKey(diffieHellman({ privateKey: ephemeral.privateKey, publicKey: recipient }), epk, recipient.export({ type: 'spki', format: 'der' }));
29
+ const iv = randomBytes(12);
30
+ const cipher = createCipheriv('aes-256-gcm', contentKey, iv);
31
+ const ct = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
32
+ return {
33
+ alg: SEALED_BOX_ALGORITHM,
34
+ kid: keyId,
35
+ epk: epk.toString('base64'),
36
+ iv: iv.toString('base64'),
37
+ ct: ct.toString('base64'),
38
+ tag: cipher.getAuthTag().toString('base64'),
39
+ };
40
+ }
41
+ /** Wire form. Fabric stores exactly this string. */
42
+ export function serializeSealedValue(sealed) {
43
+ return JSON.stringify(sealed);
44
+ }
45
+ /**
46
+ * Both public keys go into the HKDF `info`, which is what stops a sealed value
47
+ * being redirected: change either the ephemeral key or the recipient and the
48
+ * derived content key changes with it.
49
+ */
50
+ function deriveContentKey(shared, epk, recipientPublic) {
51
+ return Buffer.from(hkdfSync('sha256', shared, Buffer.from(SEAL_SALT), Buffer.concat([epk, recipientPublic]), 32));
52
+ }
53
+ function importPublicKey(publicKey) {
54
+ let key;
55
+ try {
56
+ key = createPublicKey({
57
+ key: Buffer.from(publicKey, 'base64'),
58
+ type: 'spki',
59
+ format: 'der',
60
+ });
61
+ }
62
+ catch (error) {
63
+ throw new Error(`Not a valid X25519 public key (${error instanceof Error ? error.message : String(error)})`);
64
+ }
65
+ // A public key of the wrong curve would still ECDH, just against a key the
66
+ // stage's worker cannot match — the failure would surface as an unopenable
67
+ // secret much later, so refuse it at the point the type is still knowable.
68
+ if (key.asymmetricKeyType !== 'x25519') {
69
+ throw new Error(`Stage sealing key is ${key.asymmetricKeyType}, expected x25519 — this CLI is too old for it, run \`npm i -g @pikku/cli\``);
70
+ }
71
+ return key;
72
+ }
@@ -11,6 +11,7 @@ import { LocalContent, } from '@pikku/core/services/local-content';
11
11
  import { InMemorySchedulerService } from '@pikku/schedule';
12
12
  import { resolveDb, createKysely, parseDatabaseUrl, } from '../db/local-db.js';
13
13
  import { loadUserBootstrap, loadUserModule } from './load-user-project.js';
14
+ import { registerScenarioInstrumentation } from '../wirings/scenarios/register-scenario-instrumentation.js';
14
15
  import { startCoverageService } from './start-coverage.js';
15
16
  import { createDevAIAgentRunner } from './dev-ai-runner.js';
16
17
  import { resolveConsoleMount } from './serve-console.js';
@@ -115,6 +116,14 @@ export const dev = pikkuSessionlessFunc({
115
116
  return;
116
117
  }
117
118
  await loadUserBootstrap(pikkuDir);
119
+ // Scenario instrumentation exists only on a development server: the runner
120
+ // calls it over RPC to reset and snapshot coverage and stub calls. It is
121
+ // registered here, after the app bootstrap, rather than generated into the
122
+ // project — an app bootstrap is what a deployed bundle imports, and coverage
123
+ // endpoints have no business in one.
124
+ if (config.scaffold?.scenarios) {
125
+ registerScenarioInstrumentation(config.scaffold.scenarios === 'auth');
126
+ }
118
127
  const configModule = await loadUserModule(pikkuConfigFactory.file);
119
128
  const servicesModule = await loadUserModule(singletonServicesFactory.file);
120
129
  const userCreateConfig = configModule[pikkuConfigFactory.variable];
@@ -70,6 +70,7 @@ export const pikkuBootstrap = pikkuSessionlessFunc({
70
70
  // in place however this module is reached.
71
71
  const scenarioBootstrapImports = [
72
72
  config.bootstrapFile,
73
+ join(config.scenarioSchemaDirectory, 'register.gen.ts'),
73
74
  config.scenarioStepsMetaFile,
74
75
  config.scenarioStepsFile,
75
76
  config.scenarioWiringsMetaFile,
@@ -29,9 +29,17 @@ export interface ScenarioBrowserDriver {
29
29
  createScenarioBrowserProvider?: (options: ScenarioBrowserDriverOptions) => ScenarioBrowserProvider;
30
30
  /** The class form the bundled driver exports. */
31
31
  PlaywrightScenarioBrowserProvider?: new (options: ScenarioBrowserDriverOptions) => ScenarioBrowserProvider;
32
- /** Driver-specific config (headed, slowMo, timeouts) resolved from the env. */
32
+ /**
33
+ * Driver-specific config (headed, slowMo, timeouts) resolved from the env.
34
+ *
35
+ * A driver may resolve `appUrl` itself when the caller passes none — that is
36
+ * how a target known only at run time (a sandbox hostname in the env) is
37
+ * reached without a config file per deployment. A driver that answers with a
38
+ * placeholder rather than a real target must say so with
39
+ * `appUrlSource: 'default'`, which the runner reports as a missing appUrl.
40
+ */
33
41
  browserConfigFromEnv?: (overrides: {
34
- appUrl: string;
42
+ appUrl?: string;
35
43
  apiUrl: string;
36
44
  }) => unknown;
37
45
  }
@@ -55,6 +63,13 @@ export interface ResolveScenarioBrowserProviderOptions {
55
63
  * Both failure modes here are discovered before the first scenario starts. A
56
64
  * missing `appUrl` or an uninstalled driver found mid-run costs a whole run to
57
65
  * learn about.
66
+ *
67
+ * `appUrl` is checked after the driver has resolved its config rather than
68
+ * before, so a driver that knows the target from its own environment (a
69
+ * sandbox hostname) is allowed to supply it. The check is not dropped: a driver
70
+ * reporting `appUrlSource: 'default'` resolved nothing and fails the same way,
71
+ * because a run against a placeholder URL is worse than one that refuses to
72
+ * start.
58
73
  */
59
74
  export declare const resolveScenarioBrowserProvider: ({ environment, apiUrl, appUrl, secret, actors, signInPath, failureDir, browserScenarios, driver, importDriver, }: ResolveScenarioBrowserProviderOptions) => Promise<ScenarioBrowserProvider>;
60
75
  /**
@@ -4,12 +4,15 @@ export const DEFAULT_BROWSER_DRIVER = '@pikku/playwright';
4
4
  * Both failure modes here are discovered before the first scenario starts. A
5
5
  * missing `appUrl` or an uninstalled driver found mid-run costs a whole run to
6
6
  * learn about.
7
+ *
8
+ * `appUrl` is checked after the driver has resolved its config rather than
9
+ * before, so a driver that knows the target from its own environment (a
10
+ * sandbox hostname) is allowed to supply it. The check is not dropped: a driver
11
+ * reporting `appUrlSource: 'default'` resolved nothing and fails the same way,
12
+ * because a run against a placeholder URL is worse than one that refuses to
13
+ * start.
7
14
  */
8
15
  export const resolveScenarioBrowserProvider = async ({ environment, apiUrl, appUrl, secret, actors, signInPath, failureDir, browserScenarios, driver = DEFAULT_BROWSER_DRIVER, importDriver = (specifier) => import(specifier), }) => {
9
- if (!appUrl) {
10
- throw new Error(`Scenario environment '${environment}' has browser steps but no 'appUrl'. ` +
11
- `Add it to scenarios.environments.${environment} in pikku.config.json, or run with --no-browser to skip them.`);
12
- }
13
16
  const module = await importDriver(driver).catch(() => {
14
17
  const install = driver === DEFAULT_BROWSER_DRIVER
15
18
  ? `Run 'yarn add -D ${driver} @playwright/test'`
@@ -17,15 +20,20 @@ export const resolveScenarioBrowserProvider = async ({ environment, apiUrl, appU
17
20
  throw new Error(`Scenarios ${browserScenarios.join(', ')} declare browser steps but '${driver}' could not be loaded. ` +
18
21
  `${install}, or run with --no-browser to skip them.`);
19
22
  });
23
+ const config = (module.browserConfigFromEnv?.({ appUrl, apiUrl }) ?? {
24
+ appUrl,
25
+ apiUrl,
26
+ });
27
+ if (!config.appUrl || config.appUrlSource === 'default') {
28
+ throw new Error(`Scenario environment '${environment}' has browser steps but no 'appUrl', and '${driver}' resolved none from the environment. ` +
29
+ `Add it to scenarios.environments.${environment} in pikku.config.json, pass --app-url for a target that only exists at run time, or run with --no-browser to skip them.`);
30
+ }
20
31
  const options = {
21
32
  secret,
22
33
  actors,
23
34
  signInPath,
24
35
  failureDir,
25
- config: module.browserConfigFromEnv?.({ appUrl, apiUrl }) ?? {
26
- appUrl,
27
- apiUrl,
28
- },
36
+ config,
29
37
  };
30
38
  if (module.createScenarioBrowserProvider) {
31
39
  return module.createScenarioBrowserProvider(options);
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Resolving `pikku scenario run <environment>` to the URLs the run targets.
3
+ *
4
+ * The configured environment is the base; `--api-url`/`--app-url` override it
5
+ * for one invocation, which is what makes a suite runnable against a target
6
+ * that only exists at run time (a freshly provisioned sandbox) without writing
7
+ * a pikku.config.json per deployment.
8
+ */
9
+ /** One entry of `scenarios.environments` in pikku.config.json. */
10
+ export interface ScenarioEnvironment {
11
+ apiUrl: string;
12
+ signInPath?: string;
13
+ rpcPath?: string;
14
+ appUrl?: string;
15
+ }
16
+ export interface ResolveScenarioEnvironmentOptions {
17
+ /** The name given on the command line. Must exist in config. */
18
+ environment: string;
19
+ environments: Record<string, ScenarioEnvironment>;
20
+ /** `--api-url`, overriding the configured `apiUrl`. */
21
+ apiUrl?: string;
22
+ /** `--app-url`, overriding the configured `appUrl`. */
23
+ appUrl?: string;
24
+ /** `--spawn`, which is only coherent against a local `apiUrl`. */
25
+ spawn?: boolean;
26
+ }
27
+ /**
28
+ * Applied once, so actors, step env, the browser driver and the spawned server
29
+ * all see the same target. The environment is still looked up by name: the
30
+ * flags override fields on a configured environment, they do not invent one.
31
+ */
32
+ export declare const resolveScenarioEnvironment: ({ environment, environments, apiUrl, appUrl, spawn, }: ResolveScenarioEnvironmentOptions) => ScenarioEnvironment;