@pikku/cli 0.12.124 → 0.12.126
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 +245 -0
- package/console-app/assets/{abnfDiagram-N423BO3Z-BP7zxMq6.js → abnfDiagram-N423BO3Z-B2suyn6i.js} +1 -1
- package/console-app/assets/architecture-TIHT7OUA-DmG0bXDT.js +1 -0
- package/console-app/assets/{architectureDiagram-T3A2C74G-mvqdkIfF.js → architectureDiagram-T3A2C74G-BQ5Dfvpo.js} +1 -1
- package/console-app/assets/{blockDiagram-VBNYF7ZC-daD8h3VT.js → blockDiagram-VBNYF7ZC-D4pVRDiF.js} +1 -1
- package/console-app/assets/{c4Diagram-5PPSVZJV-Bd0_wKJj.js → c4Diagram-5PPSVZJV-Da04jGpq.js} +1 -1
- package/console-app/assets/channel-BgCT5gon.js +1 -0
- package/console-app/assets/{chunk-2GRJ4B5K-BFRr_H2V.js → chunk-2GRJ4B5K-CwODIPP4.js} +1 -1
- package/console-app/assets/{chunk-4I5QYGJK-BXWo5Xyx.js → chunk-4I5QYGJK-Bep4akjR.js} +1 -1
- package/console-app/assets/{chunk-5RXB4S5H-BFnXfzzz.js → chunk-5RXB4S5H-CGyCRU9L.js} +1 -1
- package/console-app/assets/{chunk-6Q2QTUOP-Bswg4T3s.js → chunk-6Q2QTUOP-D0IK0SMT.js} +1 -1
- package/console-app/assets/{chunk-7Z6QIM7H-StNGH40U.js → chunk-7Z6QIM7H-D12vS1jX.js} +1 -1
- package/console-app/assets/{chunk-GF5L2VYU-QwXKtQxa.js → chunk-GF5L2VYU-kOLwTzIA.js} +1 -1
- package/console-app/assets/{chunk-I66GZJ75-DDQhhfA0.js → chunk-I66GZJ75-Cb2nDoa3.js} +1 -1
- package/console-app/assets/{chunk-JQJVKLGR-wFyF2bUv.js → chunk-JQJVKLGR-B3a_ctfQ.js} +1 -1
- package/console-app/assets/{chunk-KBJHAD2P-CQPQVDkD.js → chunk-KBJHAD2P-DeP2Lqwp.js} +1 -1
- package/console-app/assets/{chunk-NSK5VX7P-DZhjvnHm.js → chunk-NSK5VX7P-BO8vlGrO.js} +1 -1
- package/console-app/assets/{chunk-QR6OTTB3-D1-TbcCP.js → chunk-QR6OTTB3-sX5V0Lok.js} +1 -1
- package/console-app/assets/{chunk-RYQCIY6F-82OYF6U8.js → chunk-RYQCIY6F-DoCObEhk.js} +1 -1
- package/console-app/assets/{chunk-UBXNYLIW-D85x6Zh2.js → chunk-UBXNYLIW-BndMXU5r.js} +1 -1
- package/console-app/assets/{chunk-W5SLKNZC-B1Ps46_n.js → chunk-W5SLKNZC--VrZN8EC.js} +1 -1
- package/console-app/assets/{chunk-WRU74C26-Ct8XrToU.js → chunk-WRU74C26-DdS0mzXO.js} +1 -1
- package/console-app/assets/chunk-XXDRQBXY-EP2C9tTO.js +1 -0
- package/console-app/assets/classDiagram-JCYQIIEL-D9wGqB4J.js +1 -0
- package/console-app/assets/classDiagram-v2-OCEON4UE-D9wGqB4J.js +1 -0
- package/console-app/assets/{cose-bilkent-JH36ORCC-DdevwV5Y.js → cose-bilkent-JH36ORCC-C5EnHkFn.js} +1 -1
- package/console-app/assets/{cynefin-VYW2F7L2-Dj55csoo.js → cynefin-VYW2F7L2-9irdkTt4.js} +1 -1
- package/console-app/assets/{cynefinDiagram-MW4NZA55-XWSzdajo.js → cynefinDiagram-MW4NZA55-DY19660c.js} +1 -1
- package/console-app/assets/{dagre-VZM6K2ZE-BV2l3zSF.js → dagre-VZM6K2ZE-BYFZSaSb.js} +1 -1
- package/console-app/assets/{dagre-CLoXIMX5.js → dagre-nXgDYqvf.js} +1 -1
- package/console-app/assets/{diagram-7IWD3JNH-DC9NK-nv.js → diagram-7IWD3JNH-oDftiTp1.js} +1 -1
- package/console-app/assets/{diagram-B4RE2ZJO-Cirfb0aN.js → diagram-B4RE2ZJO-Cbj-pPbH.js} +1 -1
- package/console-app/assets/{diagram-LBJQPF4R-BuRRy5X5.js → diagram-LBJQPF4R-Ch53oSKw.js} +1 -1
- package/console-app/assets/{diagram-Q27KOJAE-CAOKQ2xs.js → diagram-Q27KOJAE-DA7K814v.js} +1 -1
- package/console-app/assets/{diagram-UB23O5K3-Bto5kBJo.js → diagram-UB23O5K3-B-_C-ctS.js} +1 -1
- package/console-app/assets/{ebnfDiagram-BXEA7PRR-C5OndYGC.js → ebnfDiagram-BXEA7PRR-DsOdsKXy.js} +1 -1
- package/console-app/assets/{erDiagram-JOGREHBK-DqU4xV4C.js → erDiagram-JOGREHBK-XB1mP5_A.js} +1 -1
- package/console-app/assets/eventmodeling-45OFAUF4-NKFCZ8Fj.js +1 -0
- package/console-app/assets/flowDiagram-UKHOOZJN-BpdPkmum.js +1 -0
- package/console-app/assets/{ganttDiagram-PKOTCBZU-D5_DE9R0.js → ganttDiagram-PKOTCBZU-knk0XSHi.js} +1 -1
- package/console-app/assets/{gitGraph-TEB2WS4Q-nuqfz7Fd.js → gitGraph-TEB2WS4Q-COJnK2tM.js} +1 -1
- package/console-app/assets/{gitGraphDiagram-DS77QQ5N-BtJwp2Y3.js → gitGraphDiagram-DS77QQ5N-sF0v605q.js} +1 -1
- package/console-app/assets/{index-DXcH2K9z.css → index-C8MC-uMB.css} +1 -1
- package/console-app/assets/{index-DRfts2XF.js → index-DMnbsgzs.js} +3 -3
- package/console-app/assets/{info-DKCQHKI2-CIfPVqHr.js → info-DKCQHKI2-Cbatr0w6.js} +1 -1
- package/console-app/assets/{infoDiagram-6WML65LV-B-gTHQad.js → infoDiagram-6WML65LV-93TFPCu2.js} +1 -1
- package/console-app/assets/{ishikawaDiagram-WSZJBQD7-CCwAWb1g.js → ishikawaDiagram-WSZJBQD7-BBR8_Qms.js} +1 -1
- package/console-app/assets/{journeyDiagram-NVQOT4AX-DhqNj_ci.js → journeyDiagram-NVQOT4AX-zG55yDoc.js} +1 -1
- package/console-app/assets/{kanban-definition-27J2QSJJ-DspZ1b3Y.js → kanban-definition-27J2QSJJ-DonGcEAY.js} +1 -1
- package/console-app/assets/{line-D7ZCpNkR.js → line-D1WdVm1S.js} +1 -1
- package/console-app/assets/{linear-B5jMy_q9.js → linear-D2GQL4Cm.js} +1 -1
- package/console-app/assets/{mermaid-parser.core-CMBFGJ0U.js → mermaid-parser.core-b4yKyO56.js} +3 -3
- package/console-app/assets/{mermaid.core-DYoDrSEJ.js → mermaid.core-Cwfec1gr.js} +4 -4
- package/console-app/assets/{mindmap-definition-FAOFIHXS-DsHwd2K9.js → mindmap-definition-FAOFIHXS-D0G4yKgw.js} +1 -1
- package/console-app/assets/{packet-7NZHBO7P-BSRRjf4H.js → packet-7NZHBO7P-DDa4HL2T.js} +1 -1
- package/console-app/assets/{pegDiagram-VL7TDLO6-Bi0OlzgS.js → pegDiagram-VL7TDLO6-fPdN5VZs.js} +1 -1
- package/console-app/assets/{pie-RZYD4A2V-D-8tgqYW.js → pie-RZYD4A2V-DRHURUqo.js} +1 -1
- package/console-app/assets/{pieDiagram-7S7Q4E2Y-CsYbVqaR.js → pieDiagram-7S7Q4E2Y-DMYn8ga_.js} +1 -1
- package/console-app/assets/{quadrantDiagram-CIZ2JOQS-LkLWrWKK.js → quadrantDiagram-CIZ2JOQS-C190OamC.js} +1 -1
- package/console-app/assets/{radar-I7S5WNFK-Bst62XZP.js → radar-I7S5WNFK-Djvqzz9A.js} +1 -1
- package/console-app/assets/{railroad-3IZDKUUU-zJswMQYF.js → railroad-3IZDKUUU-C9YvFeFq.js} +1 -1
- package/console-app/assets/railroad-abnf-AHOZXSZD-B3CYVW4p.js +1 -0
- package/console-app/assets/railroad-ebnf-EBAXGLYW-DrZ2yF62.js +1 -0
- package/console-app/assets/railroad-peg-LSFZ7HO6-CWAmMyxd.js +1 -0
- package/console-app/assets/{railroadDiagram-AXF67PYL-C0QZkg7m.js → railroadDiagram-AXF67PYL-IRFg1VrU.js} +1 -1
- package/console-app/assets/{requirementDiagram-LRYGKXZP-BQg0AbLI.js → requirementDiagram-LRYGKXZP-L-7ubbcT.js} +1 -1
- package/console-app/assets/{sankeyDiagram-W5VNT64P-CQb0BMMi.js → sankeyDiagram-W5VNT64P-DBttV6Gt.js} +1 -1
- package/console-app/assets/{sequenceDiagram-SI44F4Z6-BzgBi0a5.js → sequenceDiagram-SI44F4Z6-Bg15oKAB.js} +1 -1
- package/console-app/assets/{src-Dg-KiSsV.js → src-DUC7tLWb.js} +1 -1
- package/console-app/assets/{stateDiagram-OKZ733FA-BiqXn68g.js → stateDiagram-OKZ733FA-PWl0XGs5.js} +1 -1
- package/console-app/assets/stateDiagram-v2-UEYNNEHI-Celbgz4u.js +1 -0
- package/console-app/assets/{swimlanes-SLNWSIFB-Dy7wDP1q.js → swimlanes-SLNWSIFB-CyXEtGBV.js} +1 -1
- package/console-app/assets/swimlanesDiagram-ULZ7WXOC-zxpNaB4U.js +8 -0
- package/console-app/assets/{timeline-definition-Z64GVDOM-DSWXli8X.js → timeline-definition-Z64GVDOM-BLA-DhBt.js} +1 -1
- package/console-app/assets/{treeView-QDETBFTQ-CeWwcs9r.js → treeView-QDETBFTQ-Bfnmn2UY.js} +1 -1
- package/console-app/assets/{treemap-6X3UGDF4-BfrumiJh.js → treemap-6X3UGDF4-DTA0wveO.js} +1 -1
- package/console-app/assets/{vennDiagram-T6HMQDX7-BsuldZ1n.js → vennDiagram-T6HMQDX7-CLXYZln0.js} +1 -1
- package/console-app/assets/{wardley-OPB4EBWU-oZldxS8z.js → wardley-OPB4EBWU-zwxqisG0.js} +1 -1
- package/console-app/assets/{wardleyDiagram-T6FBY63Y-C5LXyHMj.js → wardleyDiagram-T6FBY63Y-hKtKvNlv.js} +1 -1
- package/console-app/assets/{xychartDiagram-ELKLHX3M-CwhqhNkO.js → xychartDiagram-ELKLHX3M-BGf8p4bo.js} +1 -1
- package/console-app/index.html +2 -2
- package/dist/.pikku/addon/index.d.ts +1 -1
- package/dist/.pikku/addon/index.js +1 -1
- package/dist/.pikku/addon/pikku-addon-types.gen.d.ts +1 -1
- package/dist/.pikku/addon/pikku-addon-types.gen.js +1 -1
- package/dist/.pikku/agent/index.d.ts +1 -1
- package/dist/.pikku/agent/index.js +1 -1
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/agent/pikku-model-aliases.gen.js +1 -1
- package/dist/.pikku/auth/index.d.ts +1 -1
- package/dist/.pikku/auth/index.js +1 -1
- package/dist/.pikku/auth/pikku-auth-types.gen.d.ts +1 -1
- package/dist/.pikku/auth/pikku-auth-types.gen.js +1 -1
- package/dist/.pikku/channel/index.d.ts +1 -1
- package/dist/.pikku/channel/index.js +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/index.d.ts +1 -1
- package/dist/.pikku/cli/index.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +11 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +7 -0
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +27 -0
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/error/index.d.ts +1 -1
- package/dist/.pikku/error/index.js +1 -1
- package/dist/.pikku/error/pikku-error-types.gen.d.ts +1 -1
- package/dist/.pikku/error/pikku-error-types.gen.js +1 -1
- package/dist/.pikku/function/index.d.ts +1 -1
- package/dist/.pikku/function/index.js +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +59 -23
- package/dist/.pikku/function/pikku-functions.gen.js +1 -1
- package/dist/.pikku/gateway/index.d.ts +1 -1
- package/dist/.pikku/gateway/index.js +1 -1
- package/dist/.pikku/gateway/pikku-gateway-types.gen.d.ts +1 -1
- package/dist/.pikku/gateway/pikku-gateway-types.gen.js +1 -1
- package/dist/.pikku/http/index.d.ts +1 -1
- package/dist/.pikku/http/index.js +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/mcp/index.d.ts +1 -1
- package/dist/.pikku/mcp/index.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/middleware/index.d.ts +1 -1
- package/dist/.pikku/middleware/index.js +1 -1
- package/dist/.pikku/middleware/pikku-middleware-types.gen.d.ts +1 -1
- package/dist/.pikku/middleware/pikku-middleware-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +1 -1
- package/dist/.pikku/queue/index.d.ts +1 -1
- package/dist/.pikku/queue/index.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +2 -0
- package/dist/.pikku/scenarios/index.d.ts +1 -1
- package/dist/.pikku/scenarios/index.js +1 -1
- package/dist/.pikku/scenarios/pikku-personas.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-types.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.js +1 -1
- package/dist/.pikku/scheduler/index.d.ts +1 -1
- package/dist/.pikku/scheduler/index.js +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +7 -1
- package/dist/.pikku/schemas/schemas/DeployApplyInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/FabricProjectsListInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricProjectsListOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricSecretsListInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/FabricSecretsRotateInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/FabricSecretsSetInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/FabricVariablesGetInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/FabricVariablesSetInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PersonaSecretInput.schema.json +1 -0
- package/dist/.pikku/scopes/index.d.ts +1 -1
- package/dist/.pikku/scopes/index.js +1 -1
- package/dist/.pikku/scopes/pikku-personas.gen.js +1 -1
- package/dist/.pikku/scopes/pikku-roles.gen.d.ts +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
- package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
- package/dist/.pikku/secrets/index.d.ts +1 -1
- package/dist/.pikku/secrets/index.js +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/services/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/services/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/setup/index.d.ts +1 -1
- package/dist/.pikku/setup/index.js +1 -1
- package/dist/.pikku/setup/pikku-setup-types.gen.d.ts +1 -1
- package/dist/.pikku/setup/pikku-setup-types.gen.js +1 -1
- package/dist/.pikku/trigger/index.d.ts +1 -1
- package/dist/.pikku/trigger/index.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/index.d.ts +1 -1
- package/dist/.pikku/variables/index.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/index.d.ts +1 -1
- package/dist/.pikku/workflow/index.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/src/cli.wiring.js +13 -1
- package/dist/src/deploy/build-pipeline.d.ts +10 -0
- package/dist/src/deploy/build-pipeline.js +16 -1
- package/dist/src/deploy/bundler/bun-bundler.js +12 -5
- package/dist/src/deploy/frontend-assets.d.ts +50 -0
- package/dist/src/deploy/frontend-assets.js +82 -0
- package/dist/src/fabric/fabric-commands.d.ts +55 -15
- package/dist/src/fabric/fabric-commands.js +6 -0
- package/dist/src/fabric/functions/deploy.function.js +12 -2
- package/dist/src/fabric/functions/projects-list.function.d.ts +72 -0
- package/dist/src/fabric/functions/projects-list.function.js +63 -0
- package/dist/src/fabric/functions/secrets-list.function.d.ts +4 -4
- package/dist/src/fabric/functions/secrets-list.function.js +4 -4
- package/dist/src/fabric/functions/secrets-rotate.function.d.ts +4 -4
- package/dist/src/fabric/functions/secrets-rotate.function.js +8 -5
- package/dist/src/fabric/functions/secrets-set.function.d.ts +4 -4
- package/dist/src/fabric/functions/secrets-set.function.js +4 -4
- package/dist/src/fabric/functions/validate.function.js +49 -24
- package/dist/src/fabric/functions/variables-get.function.d.ts +4 -4
- package/dist/src/fabric/functions/variables-get.function.js +4 -4
- package/dist/src/fabric/functions/variables-set.function.d.ts +4 -4
- package/dist/src/fabric/functions/variables-set.function.js +4 -4
- package/dist/src/fabric/lib/blank-comments.d.ts +21 -0
- package/dist/src/fabric/lib/blank-comments.js +71 -0
- package/dist/src/fabric/lib/blank-scenario-meta.d.ts +1 -0
- package/dist/src/fabric/lib/blank-scenario-meta.js +110 -0
- package/dist/src/fabric/lib/deployment.d.ts +26 -0
- package/dist/src/fabric/lib/deployment.js +35 -0
- package/dist/src/fabric/lib/stage.d.ts +19 -3
- package/dist/src/fabric/lib/stage.js +29 -6
- package/dist/src/functions/commands/db-reset.js +6 -2
- package/dist/src/functions/commands/deploy-apply.d.ts +10 -0
- package/dist/src/functions/commands/deploy-apply.js +8 -0
- package/dist/src/functions/commands/deploy-plan.js +1 -0
- package/dist/src/functions/commands/dev.js +18 -3
- package/dist/src/functions/commands/environment.d.ts +10 -0
- package/dist/src/functions/commands/environment.js +24 -0
- package/dist/src/functions/commands/persona.d.ts +18 -0
- package/dist/src/functions/commands/persona.js +41 -2
- package/dist/src/functions/commands/scenario-browser.d.ts +13 -5
- package/dist/src/functions/commands/serve-frontend.d.ts +11 -0
- package/dist/src/functions/commands/serve-frontend.js +17 -0
- package/dist/src/functions/commands/serve.js +16 -3
- package/dist/src/functions/db/annotation-parser.d.ts +8 -0
- package/dist/src/functions/db/annotation-parser.js +2 -0
- package/dist/src/functions/db/check-enums.d.ts +38 -0
- package/dist/src/functions/db/check-enums.js +103 -0
- package/dist/src/functions/db/db-codegen.js +35 -1
- package/dist/src/functions/db/postgres/postgres-introspector.d.ts +14 -0
- package/dist/src/functions/db/postgres/postgres-introspector.js +48 -0
- package/dist/src/functions/db/sqlite/sqlite-introspector.d.ts +3 -4
- package/dist/src/functions/db/sqlite/sqlite-introspector.js +5 -17
- package/dist/src/functions/wirings/virtual-user/serialize-virtual-user-functions.d.ts +9 -6
- package/dist/src/functions/wirings/virtual-user/serialize-virtual-user-functions.js +57 -30
- package/dist/src/server/actor-sign-in.d.ts +24 -1
- package/dist/src/server/actor-sign-in.js +45 -2
- package/dist/src/server/dev-server-runner.interface.d.ts +9 -0
- package/dist/src/server/node-server-runner.js +3 -0
- package/dist/src/server/server-ready.d.ts +6 -9
- package/dist/src/server/server-ready.js +6 -9
- package/dist/src/utils/frontend.d.ts +9 -0
- package/dist/src/utils/frontend.js +18 -0
- package/dist/src/utils/persona-credentials.d.ts +23 -2
- package/dist/src/utils/persona-credentials.js +55 -2
- package/dist/src/utils/pikku-cli-config.d.ts +14 -5
- package/dist/src/utils/pikku-cli-config.js +57 -10
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/snippets.json +2 -2
- package/surface.json +101 -51
- package/console-app/assets/architecture-TIHT7OUA-D4Hj6824.js +0 -1
- package/console-app/assets/channel-09aKDzM0.js +0 -1
- package/console-app/assets/chunk-XXDRQBXY-C1KaR4pt.js +0 -1
- package/console-app/assets/classDiagram-JCYQIIEL-23nagJMi.js +0 -1
- package/console-app/assets/classDiagram-v2-OCEON4UE-23nagJMi.js +0 -1
- package/console-app/assets/eventmodeling-45OFAUF4-RyG54ZHy.js +0 -1
- package/console-app/assets/flowDiagram-UKHOOZJN-DNdfrnRg.js +0 -1
- package/console-app/assets/railroad-abnf-AHOZXSZD-DX4FqVY6.js +0 -1
- package/console-app/assets/railroad-ebnf-EBAXGLYW-DdyOtX1l.js +0 -1
- package/console-app/assets/railroad-peg-LSFZ7HO6-CclC1sXF.js +0 -1
- package/console-app/assets/stateDiagram-v2-UEYNNEHI-I34jrzP0.js +0 -1
- package/console-app/assets/swimlanesDiagram-ULZ7WXOC-C1PrPB0G.js +0 -8
|
@@ -43,13 +43,17 @@ export const dbReset = pikkuSessionlessFunc({
|
|
|
43
43
|
logger.info(zod.written
|
|
44
44
|
? `db reset: regenerated ${zod.outFile} (${zod.tables.length} tables)`
|
|
45
45
|
: `db reset: ${zod.outFile} unchanged`);
|
|
46
|
+
// Both lines report on the SEED STEP, never on the database. A migration
|
|
47
|
+
// is free to carry rows — data a deployed stage needs is migration data —
|
|
48
|
+
// so "database is empty" was a verdict this step has no standing to reach,
|
|
49
|
+
// and it read as a failed reset every time it was wrong.
|
|
46
50
|
if (noSeed) {
|
|
47
|
-
logger.info('db reset: --no-seed,
|
|
51
|
+
logger.info('db reset: --no-seed, skipping the dev seed');
|
|
48
52
|
return;
|
|
49
53
|
}
|
|
50
54
|
const devSeedResult = await devSeed(resolved);
|
|
51
55
|
logger.info(devSeedResult.applied
|
|
52
56
|
? `db reset: seeded ${resolved.devSeedFile} (${devSeedResult.bytes} bytes)`
|
|
53
|
-
: `db reset: no ${resolved.devSeedFile} found
|
|
57
|
+
: `db reset: no dev seed applied (${resolved.devSeedFile} not found)`);
|
|
54
58
|
},
|
|
55
59
|
});
|
|
@@ -8,23 +8,33 @@ export declare function resolveProvider(config?: {
|
|
|
8
8
|
};
|
|
9
9
|
}, providerName?: string, options?: {
|
|
10
10
|
runtime?: string;
|
|
11
|
+
desktop?: boolean;
|
|
12
|
+
projectDir?: string;
|
|
13
|
+
desktopIdentifier?: string;
|
|
14
|
+
desktopUrl?: string;
|
|
11
15
|
}): Promise<ProviderAdapter>;
|
|
12
16
|
export declare const deployApply: import("#pikku/function").PikkuFunctionConfig<{
|
|
13
17
|
fromPlan?: boolean;
|
|
14
18
|
provider?: string;
|
|
15
19
|
runtime?: string;
|
|
20
|
+
desktop?: boolean;
|
|
21
|
+
desktopUrl?: string;
|
|
16
22
|
resultFile?: string;
|
|
17
23
|
debugArtifacts?: boolean;
|
|
18
24
|
}, void, "rpc" | "session", import("#pikku/function").PikkuFunctionSessionless<{
|
|
19
25
|
fromPlan?: boolean;
|
|
20
26
|
provider?: string;
|
|
21
27
|
runtime?: string;
|
|
28
|
+
desktop?: boolean;
|
|
29
|
+
desktopUrl?: string;
|
|
22
30
|
resultFile?: string;
|
|
23
31
|
debugArtifacts?: boolean;
|
|
24
32
|
}, void, "rpc" | "session", import("#pikku/function").WiredServices, unknown> | import("#pikku/function").PikkuFunction<{
|
|
25
33
|
fromPlan?: boolean;
|
|
26
34
|
provider?: string;
|
|
27
35
|
runtime?: string;
|
|
36
|
+
desktop?: boolean;
|
|
37
|
+
desktopUrl?: string;
|
|
28
38
|
resultFile?: string;
|
|
29
39
|
debugArtifacts?: boolean;
|
|
30
40
|
}, void, "rpc" | "session", import("#pikku/function").WiredServices>, undefined, undefined>;
|
|
@@ -178,8 +178,15 @@ async function runDeploy(provider, providerDir, logger, resultFile) {
|
|
|
178
178
|
export const deployApply = pikkuSessionlessFunc({
|
|
179
179
|
func: async ({ logger, config, getInspectorState, bundler }, data) => {
|
|
180
180
|
const projectDir = config.rootDir;
|
|
181
|
+
// A url on its own is a request for a shell — asking for both flags would
|
|
182
|
+
// only leave `--desktop-url` alone as a silent no-op.
|
|
183
|
+
const desktopUrl = data?.desktopUrl ?? config.deploy?.desktop?.url;
|
|
181
184
|
const provider = await resolveProvider(config, data?.provider, {
|
|
182
185
|
runtime: data?.runtime,
|
|
186
|
+
desktop: data?.desktop || Boolean(desktopUrl),
|
|
187
|
+
projectDir,
|
|
188
|
+
desktopIdentifier: config.deploy?.desktop?.identifier,
|
|
189
|
+
desktopUrl,
|
|
183
190
|
});
|
|
184
191
|
const fromPlan = data?.fromPlan ?? false;
|
|
185
192
|
const resultFile = data?.resultFile;
|
|
@@ -212,6 +219,7 @@ export const deployApply = pikkuSessionlessFunc({
|
|
|
212
219
|
defaultTarget: config.deploy?.defaultTarget,
|
|
213
220
|
globalHTTPPrefix: config.globalHTTPPrefix,
|
|
214
221
|
getEntryContext,
|
|
222
|
+
frontend: config.frontend,
|
|
215
223
|
outDir: config.outDir,
|
|
216
224
|
debugArtifacts: data?.debugArtifacts ?? false,
|
|
217
225
|
logger,
|
|
@@ -59,6 +59,7 @@ export const deployPlan = pikkuSessionlessFunc({
|
|
|
59
59
|
defaultTarget: config.deploy?.defaultTarget,
|
|
60
60
|
globalHTTPPrefix: config.globalHTTPPrefix,
|
|
61
61
|
getEntryContext,
|
|
62
|
+
frontend: config.frontend,
|
|
62
63
|
outDir: config.outDir,
|
|
63
64
|
debugArtifacts: data?.debugArtifacts ?? false,
|
|
64
65
|
logger,
|
|
@@ -18,17 +18,23 @@ import { resolveDb, createKysely, parseDatabaseUrl, } from '../db/local-db.js';
|
|
|
18
18
|
import { loadUserBootstrap, loadUserModule } from './load-user-project.js';
|
|
19
19
|
import { registerScenarioInstrumentation } from '../wirings/scenarios/register-scenario-instrumentation.js';
|
|
20
20
|
import { startCoverageService } from './start-coverage.js';
|
|
21
|
+
import { resolveDevEnvironmentName } from './environment.js';
|
|
21
22
|
import { createDevAgentRunner } from './dev-agent-runner.js';
|
|
22
23
|
import { resolveConsoleMount } from './serve-console.js';
|
|
23
24
|
import { serverReadyLine } from '../../server/server-ready.js';
|
|
24
25
|
import { createEphemeralContentSigningJWT } from '../../server/content-signing-jwt.js';
|
|
25
26
|
import { enableDevActorSignIn } from '../../server/actor-sign-in.js';
|
|
27
|
+
import { resolvePersonas } from '../../utils/resolve-personas.js';
|
|
26
28
|
import { applyModelAliasOverride } from '../../utils/model-alias-override.js';
|
|
27
29
|
export const dev = pikkuSessionlessFunc({
|
|
28
30
|
remote: true,
|
|
29
31
|
func: async ({ logger, config, getInspectorState, invalidateInspectorState, variables, devServerRunner, }, { port, watch, hmr, coverage, test, model }, { rpc }) => {
|
|
30
32
|
process.env.PIKKU_DEV_QUICK_LOGIN ??= 'true';
|
|
31
|
-
|
|
33
|
+
process.env.PIKKU_ENV ??= resolveDevEnvironmentName(config.environments ?? {});
|
|
34
|
+
await enableDevActorSignIn(logger, async () => {
|
|
35
|
+
const state = await getInspectorState(true, false, false, true);
|
|
36
|
+
return Object.values(resolvePersonas(state.personas?.definitions ?? [], config.scenarios?.emailDomain));
|
|
37
|
+
});
|
|
32
38
|
applyModelAliasOverride(logger, model, config.models);
|
|
33
39
|
if (test) {
|
|
34
40
|
process.env.PIKKU_TEST_RUN = 'true';
|
|
@@ -296,10 +302,19 @@ export const dev = pikkuSessionlessFunc({
|
|
|
296
302
|
await lifecycle?.beforeStart?.(resolvedServices);
|
|
297
303
|
await pikkuServer.start();
|
|
298
304
|
await lifecycle?.afterStart?.(resolvedServices);
|
|
305
|
+
// Not `resolvedPort`: `--port 0` asks the OS for a free port, and every URL
|
|
306
|
+
// announced from here has to name the one it actually handed out.
|
|
307
|
+
const boundPort = pikkuServer.port;
|
|
299
308
|
if (consoleMount) {
|
|
300
|
-
logger.info(`Pikku Console available at http://${hostname}:${
|
|
309
|
+
logger.info(`Pikku Console available at http://${hostname}:${boundPort}${consoleMount.urlPrefix}`);
|
|
310
|
+
}
|
|
311
|
+
// Serving the built frontend here would hand you whatever the last build
|
|
312
|
+
// produced, with no HMR and no warning that it is stale. The frontend's own
|
|
313
|
+
// dev server owns dev; it proxies its API calls back to this port.
|
|
314
|
+
if (config.frontend) {
|
|
315
|
+
logger.info(`Frontend not served in dev — run your frontend dev server and proxy its API calls to http://${hostname}:${boundPort}`);
|
|
301
316
|
}
|
|
302
|
-
logger.info(serverReadyLine(hostname,
|
|
317
|
+
logger.info(serverReadyLine(hostname, boundPort));
|
|
303
318
|
let watcher;
|
|
304
319
|
process.once('SIGINT', async () => {
|
|
305
320
|
logger.info('Stopping dev server...');
|
|
@@ -60,3 +60,13 @@ export declare const parseAppUrls: (value: string) => {
|
|
|
60
60
|
base?: string;
|
|
61
61
|
byApp: Record<string, string>;
|
|
62
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* The environment name `pikku dev` is running as, for anything that gates on
|
|
65
|
+
* `PIKKU_ENV` — persona provisioning above all, which fails closed and so
|
|
66
|
+
* provisions nothing at all when nobody says where the process is.
|
|
67
|
+
*
|
|
68
|
+
* Local means a configured, non-production environment whose `apiUrl` is a
|
|
69
|
+
* loopback host; `local` wins when several qualify. A project that configures
|
|
70
|
+
* none gets `undefined` rather than a guess.
|
|
71
|
+
*/
|
|
72
|
+
export declare const resolveDevEnvironmentName: (environments: Record<string, PikkuEnvironment>) => string | undefined;
|
|
@@ -94,3 +94,27 @@ export const parseAppUrls = (value) => {
|
|
|
94
94
|
}
|
|
95
95
|
return { base, byApp };
|
|
96
96
|
};
|
|
97
|
+
/**
|
|
98
|
+
* The environment name `pikku dev` is running as, for anything that gates on
|
|
99
|
+
* `PIKKU_ENV` — persona provisioning above all, which fails closed and so
|
|
100
|
+
* provisions nothing at all when nobody says where the process is.
|
|
101
|
+
*
|
|
102
|
+
* Local means a configured, non-production environment whose `apiUrl` is a
|
|
103
|
+
* loopback host; `local` wins when several qualify. A project that configures
|
|
104
|
+
* none gets `undefined` rather than a guess.
|
|
105
|
+
*/
|
|
106
|
+
export const resolveDevEnvironmentName = (environments) => {
|
|
107
|
+
const candidates = Object.entries(environments).filter(([, environment]) => {
|
|
108
|
+
if (environment.production) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
return LOCAL_HOSTNAMES.has(new URL(environment.apiUrl).hostname);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const preferred = candidates.find(([name]) => name === 'local');
|
|
119
|
+
return (preferred ?? candidates[0])?.[0];
|
|
120
|
+
};
|
|
@@ -55,4 +55,22 @@ export declare const personaRun: import("#pikku/function").PikkuFunctionConfig<{
|
|
|
55
55
|
out?: string;
|
|
56
56
|
}, void, "rpc" | "session", import("#pikku/function").WiredServices>, undefined, undefined>;
|
|
57
57
|
/** Who this app is for, and what each of them wants, without running anything. */
|
|
58
|
+
/**
|
|
59
|
+
* Mint the actor credential for one persona, or for every declared persona.
|
|
60
|
+
*
|
|
61
|
+
* The root secret is entitled to all of them, so nothing that only needs to be
|
|
62
|
+
* one person should ever hold it. What comes out is bound to that persona's
|
|
63
|
+
* address by the target — presenting it for anyone else is refused — so a CI
|
|
64
|
+
* job, a container or a virtual user can be handed exactly the people it is
|
|
65
|
+
* meant to be and no more.
|
|
66
|
+
*
|
|
67
|
+
* Printed as `id=secret` lines, which is the shape PIKKU_PERSONA_SECRETS reads.
|
|
68
|
+
*/
|
|
69
|
+
export declare const personaSecret: import("#pikku/function").PikkuFunctionConfig<{
|
|
70
|
+
personas?: string[];
|
|
71
|
+
}, void, "rpc" | "session", import("#pikku/function").PikkuFunctionSessionless<{
|
|
72
|
+
personas?: string[];
|
|
73
|
+
}, void, "rpc" | "session", import("#pikku/function").WiredServices, unknown> | import("#pikku/function").PikkuFunction<{
|
|
74
|
+
personas?: string[];
|
|
75
|
+
}, void, "rpc" | "session", import("#pikku/function").WiredServices>, undefined, undefined>;
|
|
58
76
|
export declare const personaList: import("#pikku/function").PikkuFunctionConfig<void, void, "rpc" | "session", import("#pikku/function").PikkuFunctionSessionless<void, void, "rpc" | "session", import("#pikku/function").WiredServices, unknown> | import("#pikku/function").PikkuFunction<void, void, "rpc" | "session", import("#pikku/function").WiredServices>, undefined, undefined>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { resolve, dirname } from 'node:path';
|
|
2
2
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { pikkuSessionlessFunc } from '#pikku/function';
|
|
4
|
-
import { createHttpPersonas, personaEnvironmentRefusal, roleMismatchMessage, verifyPersonaRoles, } from '@pikku/core/persona';
|
|
4
|
+
import { createHttpPersonas, deriveActorSecret, personaEnvironmentRefusal, roleMismatchMessage, verifyPersonaRoles, } from '@pikku/core/persona';
|
|
5
5
|
import { personaVirtualUserTarget, runVirtualUser, catalogueClassification, DISPOSITIONS, prepareVirtualUserRun, } from '@pikku/core/virtual-user';
|
|
6
6
|
import { getSingletonServices, setSingletonServices } from '@pikku/core/state';
|
|
7
7
|
import { resolvePersonas } from '../../utils/resolve-personas.js';
|
|
8
|
-
import { resolvePersonaCredentials } from '../../utils/persona-credentials.js';
|
|
8
|
+
import { ACTOR_SECRET_VARIABLE, PERSONA_SECRETS_VARIABLE, resolvePersonaCredentials, } from '../../utils/persona-credentials.js';
|
|
9
9
|
import { resolveEnvironment } from './environment.js';
|
|
10
10
|
import { createDevAgentRunner } from './dev-agent-runner.js';
|
|
11
11
|
import { formatVirtualUserReport } from './virtual-user-formatter.js';
|
|
@@ -191,6 +191,45 @@ export const personaRun = pikkuSessionlessFunc({
|
|
|
191
191
|
},
|
|
192
192
|
});
|
|
193
193
|
/** Who this app is for, and what each of them wants, without running anything. */
|
|
194
|
+
/**
|
|
195
|
+
* Mint the actor credential for one persona, or for every declared persona.
|
|
196
|
+
*
|
|
197
|
+
* The root secret is entitled to all of them, so nothing that only needs to be
|
|
198
|
+
* one person should ever hold it. What comes out is bound to that persona's
|
|
199
|
+
* address by the target — presenting it for anyone else is refused — so a CI
|
|
200
|
+
* job, a container or a virtual user can be handed exactly the people it is
|
|
201
|
+
* meant to be and no more.
|
|
202
|
+
*
|
|
203
|
+
* Printed as `id=secret` lines, which is the shape PIKKU_PERSONA_SECRETS reads.
|
|
204
|
+
*/
|
|
205
|
+
export const personaSecret = pikkuSessionlessFunc({
|
|
206
|
+
func: async ({ logger, config, variables, getInspectorState }, input) => {
|
|
207
|
+
const root = await variables.get(ACTOR_SECRET_VARIABLE);
|
|
208
|
+
if (!root) {
|
|
209
|
+
throw new Error(`${ACTOR_SECRET_VARIABLE} is not set — there is no root to derive from. ` +
|
|
210
|
+
`It lives in the environment of whoever mints credentials, and never in pikku.config.json.`);
|
|
211
|
+
}
|
|
212
|
+
const state = await getInspectorState(true, false, false, true);
|
|
213
|
+
const declared = resolvePersonas(state.personas?.definitions ?? [], config.scenarios?.emailDomain);
|
|
214
|
+
const wanted = input.personas?.length
|
|
215
|
+
? input.personas
|
|
216
|
+
: Object.keys(declared);
|
|
217
|
+
const unknown = wanted.filter((id) => !declared[id]);
|
|
218
|
+
if (unknown.length > 0) {
|
|
219
|
+
throw new Error(`No persona ${unknown.map((id) => `'${id}'`).join(', ')} is declared. ` +
|
|
220
|
+
`Declared: ${Object.keys(declared).sort().join(', ') || 'none'}.`);
|
|
221
|
+
}
|
|
222
|
+
const minted = [];
|
|
223
|
+
for (const id of wanted) {
|
|
224
|
+
minted.push(`${id}=${await deriveActorSecret(String(root), declared[id].email)}`);
|
|
225
|
+
}
|
|
226
|
+
for (const line of minted) {
|
|
227
|
+
logger.info(line);
|
|
228
|
+
}
|
|
229
|
+
logger.info('');
|
|
230
|
+
logger.info(`Hand these to a run as ${PERSONA_SECRETS_VARIABLE}='${minted.join(',')}'`);
|
|
231
|
+
},
|
|
232
|
+
});
|
|
194
233
|
export const personaList = pikkuSessionlessFunc({
|
|
195
234
|
func: async ({ logger, config, getInspectorState }) => {
|
|
196
235
|
const state = await getInspectorState(true, false, false, true);
|
|
@@ -9,12 +9,16 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { ScenarioArtifact, ScenarioBrowserFailure, ScenarioBrowserProvider } from '@pikku/core/scenario';
|
|
11
11
|
import type { ResolvedPersona } from '@pikku/core/services';
|
|
12
|
-
import type { OperatorSignInOptions } from '@pikku/core/persona';
|
|
12
|
+
import type { ActorSecretResolver, OperatorSignInOptions } from '@pikku/core/persona';
|
|
13
13
|
/** The default driver, used when a project names no other. */
|
|
14
14
|
export declare const DEFAULT_BROWSER_DRIVER = "@pikku/playwright";
|
|
15
15
|
export interface ScenarioBrowserDriverOptions {
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* The root actor secret, for a local `pikku dev` target — each persona's
|
|
18
|
+
* credential is derived from it. A resolver instead when this run was handed
|
|
19
|
+
* credentials for only some personas.
|
|
20
|
+
*/
|
|
21
|
+
secret?: string | ActorSecretResolver;
|
|
18
22
|
/** Fabric operator credentials, for a deployed target. */
|
|
19
23
|
operator?: OperatorSignInOptions;
|
|
20
24
|
actors: Record<string, ResolvedPersona>;
|
|
@@ -72,8 +76,12 @@ export interface ResolveScenarioBrowserProviderOptions {
|
|
|
72
76
|
appUrl?: string;
|
|
73
77
|
/** Base url per app, for a product whose personas sign into more than one. */
|
|
74
78
|
appUrls?: Record<string, string>;
|
|
75
|
-
/**
|
|
76
|
-
|
|
79
|
+
/**
|
|
80
|
+
* The root actor secret, for a local `pikku dev` target — each persona's
|
|
81
|
+
* credential is derived from it. A resolver instead when this run was handed
|
|
82
|
+
* credentials for only some personas.
|
|
83
|
+
*/
|
|
84
|
+
secret?: string | ActorSecretResolver;
|
|
77
85
|
/** Fabric operator credentials, for a deployed target. */
|
|
78
86
|
operator?: OperatorSignInOptions;
|
|
79
87
|
actors: Record<string, ResolvedPersona>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { StaticMount } from '@pikku/node-http-server';
|
|
2
|
+
import type { PikkuCLIConfig } from '../../../types/config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turn a resolved `frontend` config into the mount that serves it.
|
|
5
|
+
*
|
|
6
|
+
* The directory is checked rather than trusted because pikku only ever reads a
|
|
7
|
+
* frontend's output: an unbuilt `dir` means the project's own build has not run,
|
|
8
|
+
* and saying so here is far cheaper than a server that boots fine and answers
|
|
9
|
+
* every page with a 404.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveFrontendMount(frontend: NonNullable<PikkuCLIConfig['frontend']>): Promise<StaticMount>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { assertFrontendBuilt } from '../../utils/frontend.js';
|
|
2
|
+
/**
|
|
3
|
+
* Turn a resolved `frontend` config into the mount that serves it.
|
|
4
|
+
*
|
|
5
|
+
* The directory is checked rather than trusted because pikku only ever reads a
|
|
6
|
+
* frontend's output: an unbuilt `dir` means the project's own build has not run,
|
|
7
|
+
* and saying so here is far cheaper than a server that boots fine and answers
|
|
8
|
+
* every page with a 404.
|
|
9
|
+
*/
|
|
10
|
+
export async function resolveFrontendMount(frontend) {
|
|
11
|
+
await assertFrontendBuilt(frontend.dir);
|
|
12
|
+
return {
|
|
13
|
+
urlPrefix: frontend.urlPrefix,
|
|
14
|
+
directory: frontend.dir,
|
|
15
|
+
spaFallback: frontend.spaFallback,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -16,6 +16,7 @@ import { loadUserBootstrap, loadUserModule } from './load-user-project.js';
|
|
|
16
16
|
import { registerScenarioInstrumentation } from '../wirings/scenarios/register-scenario-instrumentation.js';
|
|
17
17
|
import { createDevAgentRunner } from './dev-agent-runner.js';
|
|
18
18
|
import { resolveConsoleMount } from './serve-console.js';
|
|
19
|
+
import { resolveFrontendMount } from './serve-frontend.js';
|
|
19
20
|
import { serverReadyLine } from '../../server/server-ready.js';
|
|
20
21
|
import { createEphemeralContentSigningJWT } from '../../server/content-signing-jwt.js';
|
|
21
22
|
import { disableDevActorSignIn } from '../../server/actor-sign-in.js';
|
|
@@ -144,22 +145,34 @@ export const serve = pikkuSessionlessFunc({
|
|
|
144
145
|
if (serveConsole && !consoleMount) {
|
|
145
146
|
logger.warn('Console app not found. Please rebuild @pikku/cli with the console app bundled.');
|
|
146
147
|
}
|
|
148
|
+
const frontendMount = config.frontend
|
|
149
|
+
? await resolveFrontendMount(config.frontend)
|
|
150
|
+
: undefined;
|
|
151
|
+
// The console goes first so a frontend mounted at `/` cannot claim
|
|
152
|
+
// `/console` before the console's own mount is offered the request.
|
|
153
|
+
const staticMounts = [consoleMount, frontendMount].filter((mount) => Boolean(mount));
|
|
147
154
|
const pikkuServer = devServerRunner.createServer({
|
|
148
155
|
...userConfig,
|
|
149
156
|
hostname: bindHostname,
|
|
150
157
|
port: resolvedPort,
|
|
151
158
|
content: localContentConfig,
|
|
152
|
-
...(
|
|
159
|
+
...(staticMounts.length ? { staticMounts } : {}),
|
|
153
160
|
}, logger, { contentSigningJWT });
|
|
154
161
|
const lifecycle = await loadLifecycle();
|
|
155
162
|
await pikkuServer.init();
|
|
156
163
|
await lifecycle?.beforeStart?.(resolvedServices);
|
|
157
164
|
await pikkuServer.start();
|
|
158
165
|
await lifecycle?.afterStart?.(resolvedServices);
|
|
166
|
+
// Not `resolvedPort`: `--port 0` asks the OS for a free port, and every URL
|
|
167
|
+
// announced from here has to name the one it actually handed out.
|
|
168
|
+
const boundPort = pikkuServer.port;
|
|
159
169
|
if (consoleMount) {
|
|
160
|
-
logger.info(`Pikku Console available at http://${hostname}:${
|
|
170
|
+
logger.info(`Pikku Console available at http://${hostname}:${boundPort}${consoleMount.urlPrefix}`);
|
|
171
|
+
}
|
|
172
|
+
if (frontendMount) {
|
|
173
|
+
logger.info(`Frontend available at http://${hostname}:${boundPort}${frontendMount.urlPrefix}`);
|
|
161
174
|
}
|
|
162
|
-
logger.info(serverReadyLine(hostname,
|
|
175
|
+
logger.info(serverReadyLine(hostname, boundPort));
|
|
163
176
|
process.once('SIGINT', async () => {
|
|
164
177
|
logger.info('Stopping server...');
|
|
165
178
|
try {
|
|
@@ -30,6 +30,14 @@ export interface ColAnnotation {
|
|
|
30
30
|
* insert/update brand on the generated column type. Absent means `plain`.
|
|
31
31
|
*/
|
|
32
32
|
form?: ColumnForm;
|
|
33
|
+
/**
|
|
34
|
+
* Which key protects this column, for an encrypted `form`. Absent means the
|
|
35
|
+
* deployment's default key.
|
|
36
|
+
*
|
|
37
|
+
* Reported as authored rather than defaulted here, so the manifest emitter
|
|
38
|
+
* stays the single place the default is applied.
|
|
39
|
+
*/
|
|
40
|
+
keyId?: string;
|
|
33
41
|
}
|
|
34
42
|
/** Per-table, per-column annotation map sourced from `db/annotations.ts`. */
|
|
35
43
|
export type AnnotationMap = Record<string, Record<string, ColAnnotation>>;
|
|
@@ -117,6 +117,8 @@ export function loadAnnotations(rootDir) {
|
|
|
117
117
|
COLUMN_FORMS.includes(ann.form)) {
|
|
118
118
|
entry.form = ann.form;
|
|
119
119
|
}
|
|
120
|
+
if (ann.keyId)
|
|
121
|
+
entry.keyId = ann.keyId;
|
|
120
122
|
const strategy = parseStrategy(ann.classification);
|
|
121
123
|
if (strategy !== null)
|
|
122
124
|
entry.anonymize = strategy;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `CHECK (col IN ('a','b',…))` constraint is an enum by another name, and
|
|
3
|
+
* both introspectors read one back as SQL text: SQLite stores the `CREATE
|
|
4
|
+
* TABLE` verbatim, Postgres hands back a normalised `pg_get_constraintdef`.
|
|
5
|
+
* The parsing they need differs, but the quoting rules do not, so both go
|
|
6
|
+
* through here.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Blank out `--` line comments and `/* … *\/` block comments, leaving a string
|
|
10
|
+
* of the same shape so offsets and line breaks still line up.
|
|
11
|
+
*
|
|
12
|
+
* Quoted text is skipped rather than scanned: `'a--b'` is a value, not a
|
|
13
|
+
* comment. The reverse — reading a comment as SQL — is what makes this
|
|
14
|
+
* necessary at all: an apostrophe in `-- the clinician's copy` opens a string
|
|
15
|
+
* that runs to the next quote in the real list, and a bracket in `-- (see
|
|
16
|
+
* below)` closes the list early. Either way the CHECK survives as a union that
|
|
17
|
+
* is wrong, which is worse than no union.
|
|
18
|
+
*/
|
|
19
|
+
export declare function stripSqlComments(sql: string): string;
|
|
20
|
+
/** The single-quoted literals in `text`, with doubled quotes unescaped. */
|
|
21
|
+
export declare function quotedValues(text: string): string[];
|
|
22
|
+
/**
|
|
23
|
+
* Map each column constrained by a `CHECK (col IN (…))` in a `CREATE TABLE`
|
|
24
|
+
* statement to its allowed values. Only the positive `col IN (…)` form is
|
|
25
|
+
* recognised (the convention); `NOT IN`, ranges and boolean expressions are
|
|
26
|
+
* left for the column to stay a plain string.
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseCheckEnumsFromDdl(ddl: string): Map<string, string[]>;
|
|
29
|
+
/**
|
|
30
|
+
* The allowed values in a Postgres check constraint, or `undefined` when the
|
|
31
|
+
* constraint is not an enumeration.
|
|
32
|
+
*
|
|
33
|
+
* Postgres does not store the `IN (…)` you wrote — `pg_get_constraintdef`
|
|
34
|
+
* rewrites it as `= ANY (ARRAY[…])`, with each element cast to the column's
|
|
35
|
+
* type. Matching that shape is deliberate: `NOT IN` normalises to `<> ALL
|
|
36
|
+
* (ARRAY[…])` and a range to `>`/`<`, and neither is an enum.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseCheckEnumValues(constraintDef: string): string[] | undefined;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `CHECK (col IN ('a','b',…))` constraint is an enum by another name, and
|
|
3
|
+
* both introspectors read one back as SQL text: SQLite stores the `CREATE
|
|
4
|
+
* TABLE` verbatim, Postgres hands back a normalised `pg_get_constraintdef`.
|
|
5
|
+
* The parsing they need differs, but the quoting rules do not, so both go
|
|
6
|
+
* through here.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Blank out `--` line comments and `/* … *\/` block comments, leaving a string
|
|
10
|
+
* of the same shape so offsets and line breaks still line up.
|
|
11
|
+
*
|
|
12
|
+
* Quoted text is skipped rather than scanned: `'a--b'` is a value, not a
|
|
13
|
+
* comment. The reverse — reading a comment as SQL — is what makes this
|
|
14
|
+
* necessary at all: an apostrophe in `-- the clinician's copy` opens a string
|
|
15
|
+
* that runs to the next quote in the real list, and a bracket in `-- (see
|
|
16
|
+
* below)` closes the list early. Either way the CHECK survives as a union that
|
|
17
|
+
* is wrong, which is worse than no union.
|
|
18
|
+
*/
|
|
19
|
+
export function stripSqlComments(sql) {
|
|
20
|
+
let out = '';
|
|
21
|
+
let i = 0;
|
|
22
|
+
while (i < sql.length) {
|
|
23
|
+
const c = sql[i];
|
|
24
|
+
if (c === "'" || c === '"') {
|
|
25
|
+
const end = closingQuote(sql, i);
|
|
26
|
+
out += sql.slice(i, end);
|
|
27
|
+
i = end;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (c === '-' && sql[i + 1] === '-') {
|
|
31
|
+
while (i < sql.length && sql[i] !== '\n') {
|
|
32
|
+
out += ' ';
|
|
33
|
+
i++;
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (c === '/' && sql[i + 1] === '*') {
|
|
38
|
+
const close = sql.indexOf('*/', i + 2);
|
|
39
|
+
const end = close === -1 ? sql.length : close + 2;
|
|
40
|
+
for (let k = i; k < end; k++)
|
|
41
|
+
out += sql[k] === '\n' ? '\n' : ' ';
|
|
42
|
+
i = end;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
out += c;
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** Index just past the quoted run starting at `start`, doubled quotes included. */
|
|
51
|
+
function closingQuote(sql, start) {
|
|
52
|
+
const quote = sql[start];
|
|
53
|
+
let i = start + 1;
|
|
54
|
+
while (i < sql.length) {
|
|
55
|
+
if (sql[i] === quote) {
|
|
56
|
+
if (sql[i + 1] === quote) {
|
|
57
|
+
i += 2;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
return i + 1;
|
|
61
|
+
}
|
|
62
|
+
i++;
|
|
63
|
+
}
|
|
64
|
+
return sql.length;
|
|
65
|
+
}
|
|
66
|
+
/** The single-quoted literals in `text`, with doubled quotes unescaped. */
|
|
67
|
+
export function quotedValues(text) {
|
|
68
|
+
return [...text.matchAll(/'((?:[^']|'')*)'/g)].map((m) => m[1].replace(/''/g, "'"));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Map each column constrained by a `CHECK (col IN (…))` in a `CREATE TABLE`
|
|
72
|
+
* statement to its allowed values. Only the positive `col IN (…)` form is
|
|
73
|
+
* recognised (the convention); `NOT IN`, ranges and boolean expressions are
|
|
74
|
+
* left for the column to stay a plain string.
|
|
75
|
+
*/
|
|
76
|
+
export function parseCheckEnumsFromDdl(ddl) {
|
|
77
|
+
const out = new Map();
|
|
78
|
+
const sql = stripSqlComments(ddl);
|
|
79
|
+
const checkIn = /CHECK\s*\(\s*"?(\w+)"?\s+IN\s*\(([^)]*)\)/gi;
|
|
80
|
+
let m;
|
|
81
|
+
while ((m = checkIn.exec(sql))) {
|
|
82
|
+
const values = quotedValues(m[2]);
|
|
83
|
+
if (values.length > 0)
|
|
84
|
+
out.set(m[1], values);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The allowed values in a Postgres check constraint, or `undefined` when the
|
|
90
|
+
* constraint is not an enumeration.
|
|
91
|
+
*
|
|
92
|
+
* Postgres does not store the `IN (…)` you wrote — `pg_get_constraintdef`
|
|
93
|
+
* rewrites it as `= ANY (ARRAY[…])`, with each element cast to the column's
|
|
94
|
+
* type. Matching that shape is deliberate: `NOT IN` normalises to `<> ALL
|
|
95
|
+
* (ARRAY[…])` and a range to `>`/`<`, and neither is an enum.
|
|
96
|
+
*/
|
|
97
|
+
export function parseCheckEnumValues(constraintDef) {
|
|
98
|
+
const any = /=\s*ANY\s*\(\s*\(?\s*ARRAY\s*\[([^\]]*)\]/i.exec(constraintDef);
|
|
99
|
+
if (!any)
|
|
100
|
+
return undefined;
|
|
101
|
+
const values = quotedValues(any[1]);
|
|
102
|
+
return values.length > 0 ? values : undefined;
|
|
103
|
+
}
|
|
@@ -340,6 +340,14 @@ function emitInterface(table, camelCase, explicitAnnotations, dialect, enumByNam
|
|
|
340
340
|
return `export interface ${ifaceName} {\n${fields}\n}`;
|
|
341
341
|
}
|
|
342
342
|
// ─── Manifest emitter ────────────────────────────────────────────────────────
|
|
343
|
+
/**
|
|
344
|
+
* The key an encrypted column is protected by when it names none.
|
|
345
|
+
*
|
|
346
|
+
* Spelled out in every entry rather than left implicit, so the set of keys a
|
|
347
|
+
* deployment has to resolve can be read off the manifest without knowing the
|
|
348
|
+
* rule that produced it.
|
|
349
|
+
*/
|
|
350
|
+
const DEFAULT_COLUMN_KEY_ID = 'default';
|
|
343
351
|
function emitManifest(tables, explicitAnnotations) {
|
|
344
352
|
const tableEntries = tables
|
|
345
353
|
.map((table) => {
|
|
@@ -354,8 +362,15 @@ function emitManifest(tables, explicitAnnotations) {
|
|
|
354
362
|
// tell "held in the clear, stated" from "nobody has said yet", and a
|
|
355
363
|
// defaulted 'plain' would erase exactly that difference.
|
|
356
364
|
const formLiteral = ann?.form ? `, form: '${ann.form}'` : '';
|
|
365
|
+
// Only a column that is actually encrypted names a key. Emitting one
|
|
366
|
+
// on a plain or hashed column would claim a protection it has not
|
|
367
|
+
// got — and a hash has no key to be opened with in the first place.
|
|
368
|
+
const encrypted = ann?.form === 'wrapped' || ann?.form === 'sealed';
|
|
369
|
+
const keyIdLiteral = encrypted
|
|
370
|
+
? `, keyId: '${ann?.keyId ?? DEFAULT_COLUMN_KEY_ID}'`
|
|
371
|
+
: '';
|
|
357
372
|
return (` ${JSON.stringify(col.name)}: ` +
|
|
358
|
-
`{ classification: '${classification}', anonymize_strategy: ${strategyLiteral}${formLiteral} }`);
|
|
373
|
+
`{ classification: '${classification}', anonymize_strategy: ${strategyLiteral}${formLiteral}${keyIdLiteral} }`);
|
|
359
374
|
})
|
|
360
375
|
.join(',\n');
|
|
361
376
|
return ` ${JSON.stringify(table.name)}: {\n${colEntries}\n }`;
|
|
@@ -391,6 +406,10 @@ function emitClassificationMap(tables) {
|
|
|
391
406
|
` * real encrypt/seal/hash result can be written to it. Defaults to 'plain',`,
|
|
392
407
|
` * which on a \`secret\` column is warned about (PKU483) until stated here. */`,
|
|
393
408
|
` form?: 'plain' | 'hashed' | 'wrapped' | 'sealed'`,
|
|
409
|
+
` /** Which key protects this column, for a 'wrapped' or 'sealed' form. It is a`,
|
|
410
|
+
` * purpose, not a tenant: naming one here says these columns open together and`,
|
|
411
|
+
` * separately from the rest. Defaults to 'default'. */`,
|
|
412
|
+
` keyId?: string`,
|
|
394
413
|
` /** Anonymize strategy used by \`pikku db anonymize\`. */`,
|
|
395
414
|
` classification?: 'fake:email' | 'fake:name' | 'hash' | 'keep'`,
|
|
396
415
|
` /** Column kind override for codegen coercion + typing. */`,
|
|
@@ -622,6 +641,21 @@ export async function generateSchemaTypes(introspector, options) {
|
|
|
622
641
|
const coercionBody = [
|
|
623
642
|
`// Generated by @pikku/cli — do not edit by hand.`,
|
|
624
643
|
`// Run \`pikku db migrate\` to refresh.`,
|
|
644
|
+
`//`,
|
|
645
|
+
`// Nothing applies this for you. Attach it where the kysely singleton is`,
|
|
646
|
+
`// created, in createSingletonServices:`,
|
|
647
|
+
`//`,
|
|
648
|
+
`// import { createCoercionPlugin } from '@pikku/kysely'`,
|
|
649
|
+
`// import { coercionMap } from '#pikku/db/coercion.gen.js'`,
|
|
650
|
+
`//`,
|
|
651
|
+
`// kysely: existingServices.kysely.withPlugin(`,
|
|
652
|
+
`// createCoercionPlugin({ map: coercionMap }),`,
|
|
653
|
+
`// )`,
|
|
654
|
+
`//`,
|
|
655
|
+
`// Unwired, this file is invisible locally and fatal deployed: the dev`,
|
|
656
|
+
`// driver hydrates a TEXT date into a Date by itself, a deployed one`,
|
|
657
|
+
`// returns the raw string, and the generated schema types the column`,
|
|
658
|
+
`// \`Date\` either way — so tsc flags nothing and no local test can fail.`,
|
|
625
659
|
``,
|
|
626
660
|
`export const coercionMap = {`,
|
|
627
661
|
coercionEntries,
|
|
@@ -12,6 +12,20 @@ export declare class PostgresIntrospector implements DbIntrospector {
|
|
|
12
12
|
connect(): Promise<void>;
|
|
13
13
|
listTables(): Promise<string[]>;
|
|
14
14
|
getColumns(table: string): Promise<ColumnInfo[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Columns constrained to a value list by a `CHECK (col IN (…))`, keyed like
|
|
17
|
+
* {@link getAllColumns} then by column name. Postgres has native enums, but
|
|
18
|
+
* a CHECK is the other half of how a schema says "one of these" — and the
|
|
19
|
+
* one an app written against SQLite as well ends up using, since SQLite has
|
|
20
|
+
* no enums at all. Left as plain `string`, every caller narrows it by hand.
|
|
21
|
+
*
|
|
22
|
+
* Only single-column constraints are read: `conkey` naming two columns means
|
|
23
|
+
* neither is restricted to the list on its own.
|
|
24
|
+
*
|
|
25
|
+
* `table` limits the sweep to one table; omitted, it covers the schema. Both
|
|
26
|
+
* are one round-trip — see {@link getAllColumns}.
|
|
27
|
+
*/
|
|
28
|
+
private getCheckEnums;
|
|
15
29
|
getForeignKeys(table: string): Promise<ForeignKeyInfo[]>;
|
|
16
30
|
getAllColumns(): Promise<Map<string, ColumnInfo[]>>;
|
|
17
31
|
getAllForeignKeys(): Promise<Map<string, ForeignKeyInfo[]>>;
|