@pikku/cli 0.12.117 → 0.12.119
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 +216 -0
- package/console-app/assets/{abnfDiagram-N423BO3Z-WNIAg_Oa.js → abnfDiagram-N423BO3Z-CiK5F2ml.js} +1 -1
- package/console-app/assets/architecture-TIHT7OUA-FMO1RByN.js +1 -0
- package/console-app/assets/{architectureDiagram-T3A2C74G-DM698mgf.js → architectureDiagram-T3A2C74G-g4Elcs_0.js} +1 -1
- package/console-app/assets/{blockDiagram-VBNYF7ZC-DPfy9K5S.js → blockDiagram-VBNYF7ZC-CI6muc3M.js} +1 -1
- package/console-app/assets/{c4Diagram-5PPSVZJV-DhLbbBbX.js → c4Diagram-5PPSVZJV-CjzL9yGp.js} +1 -1
- package/console-app/assets/channel-Dp8A5NJg.js +1 -0
- package/console-app/assets/{chunk-2GRJ4B5K-C1RQ1gvs.js → chunk-2GRJ4B5K-C68TKOLb.js} +1 -1
- package/console-app/assets/{chunk-4I5QYGJK-BkzSdNc_.js → chunk-4I5QYGJK-D_DINIPO.js} +1 -1
- package/console-app/assets/{chunk-5RXB4S5H-DZpBpdcU.js → chunk-5RXB4S5H-D8mEuiea.js} +1 -1
- package/console-app/assets/{chunk-6Q2QTUOP-mZObxdld.js → chunk-6Q2QTUOP-CVNqLnb8.js} +1 -1
- package/console-app/assets/{chunk-7Z6QIM7H-C0HbxZEa.js → chunk-7Z6QIM7H-Bnih6HyS.js} +1 -1
- package/console-app/assets/{chunk-GF5L2VYU-DXPVwWqW.js → chunk-GF5L2VYU-C5efrbFt.js} +1 -1
- package/console-app/assets/{chunk-I66GZJ75-pVpq9ckD.js → chunk-I66GZJ75-dckv50K3.js} +1 -1
- package/console-app/assets/{chunk-JQJVKLGR-DJQ8K4wK.js → chunk-JQJVKLGR-HirBo6LQ.js} +1 -1
- package/console-app/assets/{chunk-KBJHAD2P-B8XuxRCO.js → chunk-KBJHAD2P-lknhmPj0.js} +1 -1
- package/console-app/assets/{chunk-NSK5VX7P-cYzQU4bD.js → chunk-NSK5VX7P-tGAl7zxF.js} +1 -1
- package/console-app/assets/{chunk-QR6OTTB3-CNoTFsqn.js → chunk-QR6OTTB3-BlSKCrEq.js} +1 -1
- package/console-app/assets/{chunk-RYQCIY6F-DoKRUO_N.js → chunk-RYQCIY6F-CwQmwEV-.js} +1 -1
- package/console-app/assets/{chunk-UBXNYLIW-BqcIoUpU.js → chunk-UBXNYLIW-D1Dn6ZK0.js} +1 -1
- package/console-app/assets/{chunk-W5SLKNZC-p0Qalbjj.js → chunk-W5SLKNZC-DOkZHhST.js} +1 -1
- package/console-app/assets/{chunk-WRU74C26-DUMn96Dr.js → chunk-WRU74C26-CB4orIKm.js} +1 -1
- package/console-app/assets/chunk-XXDRQBXY-BMybY6E-.js +1 -0
- package/console-app/assets/classDiagram-JCYQIIEL-C-K-UAUR.js +1 -0
- package/console-app/assets/classDiagram-v2-OCEON4UE-C-K-UAUR.js +1 -0
- package/console-app/assets/{cose-bilkent-JH36ORCC-BCh3BlPI.js → cose-bilkent-JH36ORCC-stBhaqpU.js} +1 -1
- package/console-app/assets/{cynefin-VYW2F7L2-ZfghUZsD.js → cynefin-VYW2F7L2-Bb1nmKWZ.js} +1 -1
- package/console-app/assets/{cynefinDiagram-MW4NZA55-DcV3QXCe.js → cynefinDiagram-MW4NZA55-Bp8fMJi-.js} +1 -1
- package/console-app/assets/{dagre-BPFWXLBN.js → dagre-DshmzimC.js} +1 -1
- package/console-app/assets/{dagre-VZM6K2ZE-DETshBO-.js → dagre-VZM6K2ZE-DIl1klf5.js} +1 -1
- package/console-app/assets/{diagram-7IWD3JNH-CsLX3P2o.js → diagram-7IWD3JNH-DdkSOxKT.js} +1 -1
- package/console-app/assets/{diagram-B4RE2ZJO-o2zcxm5S.js → diagram-B4RE2ZJO-9Dj9O4L8.js} +1 -1
- package/console-app/assets/{diagram-LBJQPF4R-BD2zKU21.js → diagram-LBJQPF4R-CxBb73tU.js} +1 -1
- package/console-app/assets/{diagram-Q27KOJAE-DHaacyna.js → diagram-Q27KOJAE-CPpUmnvb.js} +1 -1
- package/console-app/assets/{diagram-UB23O5K3-pSLCMUyV.js → diagram-UB23O5K3-7wV_Db6W.js} +1 -1
- package/console-app/assets/{ebnfDiagram-BXEA7PRR-BDzawX3O.js → ebnfDiagram-BXEA7PRR-Cxzbf8pk.js} +1 -1
- package/console-app/assets/{erDiagram-JOGREHBK-CyYnPmbN.js → erDiagram-JOGREHBK-BBPLq83P.js} +1 -1
- package/console-app/assets/eventmodeling-45OFAUF4-CLUnygIv.js +1 -0
- package/console-app/assets/flowDiagram-UKHOOZJN-D3mDcWWx.js +1 -0
- package/console-app/assets/{ganttDiagram-PKOTCBZU-3fhCBbPA.js → ganttDiagram-PKOTCBZU-Erh7WK4H.js} +1 -1
- package/console-app/assets/{gitGraph-TEB2WS4Q-CdLRkVux.js → gitGraph-TEB2WS4Q-o5FLRF57.js} +1 -1
- package/console-app/assets/{gitGraphDiagram-DS77QQ5N-CC1epdKw.js → gitGraphDiagram-DS77QQ5N-BFbsaenX.js} +1 -1
- package/console-app/assets/{index-GruDnAhJ.css → index-Mq-YbqNO.css} +1 -1
- package/console-app/assets/{index-BRmokG9t.js → index-w2ZoJr8X.js} +178 -178
- package/console-app/assets/{info-DKCQHKI2-BJs8WkzC.js → info-DKCQHKI2-Dkcrdr1w.js} +1 -1
- package/console-app/assets/{infoDiagram-6WML65LV-uoOwoIeI.js → infoDiagram-6WML65LV-iZVC5Wzy.js} +1 -1
- package/console-app/assets/{ishikawaDiagram-WSZJBQD7-vCUJhwTi.js → ishikawaDiagram-WSZJBQD7-BjV8LLeA.js} +1 -1
- package/console-app/assets/{journeyDiagram-NVQOT4AX-D2QcIlLG.js → journeyDiagram-NVQOT4AX-D3rpLjYi.js} +1 -1
- package/console-app/assets/{kanban-definition-27J2QSJJ-JwwKowmR.js → kanban-definition-27J2QSJJ-Dds5TFyv.js} +1 -1
- package/console-app/assets/{line-h2oRreYF.js → line-D1rUAT18.js} +1 -1
- package/console-app/assets/{linear-Dz002X_a.js → linear-fQ_6Sc79.js} +1 -1
- package/console-app/assets/{mermaid-parser.core-DTgmT0AA.js → mermaid-parser.core-B9Q-4IXF.js} +3 -3
- package/console-app/assets/{mermaid.core-1jSEDr-j.js → mermaid.core-bPhADShw.js} +4 -4
- package/console-app/assets/{mindmap-definition-FAOFIHXS-C3Txxhj4.js → mindmap-definition-FAOFIHXS-CULlU6Fy.js} +1 -1
- package/console-app/assets/{packet-7NZHBO7P-BGG7zIK2.js → packet-7NZHBO7P-DpwzoQpc.js} +1 -1
- package/console-app/assets/{pegDiagram-VL7TDLO6--g1r_rov.js → pegDiagram-VL7TDLO6-B9fKKc4F.js} +1 -1
- package/console-app/assets/{pie-RZYD4A2V-C5OepMH7.js → pie-RZYD4A2V-DkXpqWAI.js} +1 -1
- package/console-app/assets/{pieDiagram-7S7Q4E2Y-DGD_LzGm.js → pieDiagram-7S7Q4E2Y-Bcpn2rUM.js} +1 -1
- package/console-app/assets/{quadrantDiagram-CIZ2JOQS-CyBQQlD2.js → quadrantDiagram-CIZ2JOQS-Bd5wWFj_.js} +1 -1
- package/console-app/assets/{radar-I7S5WNFK-CaBOAO95.js → radar-I7S5WNFK-ylw-u3HI.js} +1 -1
- package/console-app/assets/{railroad-3IZDKUUU-2aTWN4Pw.js → railroad-3IZDKUUU-D4LMucL-.js} +1 -1
- package/console-app/assets/railroad-abnf-AHOZXSZD-CRTVzK1g.js +1 -0
- package/console-app/assets/railroad-ebnf-EBAXGLYW-CB13ugAF.js +1 -0
- package/console-app/assets/railroad-peg-LSFZ7HO6-AN8zyQYp.js +1 -0
- package/console-app/assets/{railroadDiagram-AXF67PYL-3Nr0rlTp.js → railroadDiagram-AXF67PYL-CZukQql9.js} +1 -1
- package/console-app/assets/{requirementDiagram-LRYGKXZP-BN7sEwSa.js → requirementDiagram-LRYGKXZP-Ou4nRENK.js} +1 -1
- package/console-app/assets/{sankeyDiagram-W5VNT64P-CXZR46wO.js → sankeyDiagram-W5VNT64P-BKdG-c_o.js} +1 -1
- package/console-app/assets/{sequenceDiagram-SI44F4Z6-DNtmdJ4U.js → sequenceDiagram-SI44F4Z6-Dc7d5obe.js} +1 -1
- package/console-app/assets/{src-x1E3pJJ-.js → src-BFb2oeKx.js} +1 -1
- package/console-app/assets/{stateDiagram-OKZ733FA-B_GyYdl_.js → stateDiagram-OKZ733FA-BA9fuN7b.js} +1 -1
- package/console-app/assets/stateDiagram-v2-UEYNNEHI-D13a6mUd.js +1 -0
- package/console-app/assets/{swimlanes-SLNWSIFB-D4N7VdF7.js → swimlanes-SLNWSIFB-l-ETgM83.js} +1 -1
- package/console-app/assets/swimlanesDiagram-ULZ7WXOC-BsC-B2Z9.js +8 -0
- package/console-app/assets/{timeline-definition-Z64GVDOM-DU5rsOWQ.js → timeline-definition-Z64GVDOM-Uj_ByeVr.js} +1 -1
- package/console-app/assets/{treeView-QDETBFTQ-DaKM_9sB.js → treeView-QDETBFTQ-BvFRkVGY.js} +1 -1
- package/console-app/assets/{treemap-6X3UGDF4-BabsKtZY.js → treemap-6X3UGDF4-BV5T7Dw9.js} +1 -1
- package/console-app/assets/{vennDiagram-T6HMQDX7-Dniftpq0.js → vennDiagram-T6HMQDX7-DAFU8gAo.js} +1 -1
- package/console-app/assets/{wardley-OPB4EBWU-BWffQNys.js → wardley-OPB4EBWU-MLNuhuiA.js} +1 -1
- package/console-app/assets/{wardleyDiagram-T6FBY63Y-CQ74fVdn.js → wardleyDiagram-T6FBY63Y-B1lCTfi1.js} +1 -1
- package/console-app/assets/{xychartDiagram-ELKLHX3M-D-XxbGIL.js → xychartDiagram-ELKLHX3M-Bw3zhuWc.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 +6 -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-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 +14 -9
- 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 +26 -7
- package/dist/.pikku/function/pikku-functions.gen.js +1 -3
- 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 +1 -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.d.ts +11 -0
- package/dist/.pikku/scenarios/pikku-personas.gen.js +12 -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 +3 -1
- package/dist/.pikku/schemas/schemas/PersonaSyncInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuMetaApplyInput.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 +7 -10
- package/dist/src/fabric/functions/smoke.function.js +2 -2
- package/dist/src/fabric/functions/validate.function.js +2 -2
- package/dist/src/fabric/lib/git.js +31 -1
- package/dist/src/functions/commands/dev.js +2 -0
- package/dist/src/functions/commands/meta.d.ts +7 -0
- package/dist/src/functions/commands/meta.js +65 -0
- package/dist/src/functions/commands/persona-sync.d.ts +14 -22
- package/dist/src/functions/commands/persona-sync.js +29 -107
- package/dist/src/functions/commands/serve.js +2 -0
- package/dist/src/functions/validate/auth-plugin-checks.d.ts +16 -0
- package/dist/src/functions/validate/auth-plugin-checks.js +165 -0
- package/dist/src/functions/validate/persona-checks.js +1 -1
- package/dist/src/functions/validate/shared-checks.js +12 -0
- package/dist/src/functions/validate/websocket-deps-checks.d.ts +30 -0
- package/dist/src/functions/validate/websocket-deps-checks.js +42 -0
- package/dist/src/functions/wirings/personas/serialize-personas-secrets.d.ts +1 -1
- package/dist/src/functions/wirings/personas/serialize-personas-secrets.js +1 -1
- package/dist/src/functions/wirings/scopes/pikku-command-scopes.d.ts +10 -0
- package/dist/src/functions/wirings/scopes/pikku-command-scopes.js +20 -3
- package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +1 -1
- package/dist/src/functions/wirings/workflow/serialize-personas.d.ts +2 -1
- package/dist/src/functions/wirings/workflow/serialize-personas.js +20 -1
- package/dist/src/server/actor-sign-in.d.ts +5 -0
- package/dist/src/server/actor-sign-in.js +27 -0
- package/dist/src/server/node-server-runner.d.ts +11 -0
- package/dist/src/server/node-server-runner.js +33 -8
- package/dist/src/services.js +3 -1
- package/dist/src/utils/resolve-from-project.d.ts +29 -0
- package/dist/src/utils/resolve-from-project.js +45 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -11
- package/snippets.json +1 -1
- package/surface.json +2 -2
- package/console-app/assets/architecture-TIHT7OUA-CIT-Nug2.js +0 -1
- package/console-app/assets/channel-DQC6cL1c.js +0 -1
- package/console-app/assets/chunk-XXDRQBXY-Dgz5VbWO.js +0 -1
- package/console-app/assets/classDiagram-JCYQIIEL-1ASFocNV.js +0 -1
- package/console-app/assets/classDiagram-v2-OCEON4UE-1ASFocNV.js +0 -1
- package/console-app/assets/eventmodeling-45OFAUF4-BYJ0A5k-.js +0 -1
- package/console-app/assets/flowDiagram-UKHOOZJN-CVn87uiP.js +0 -1
- package/console-app/assets/railroad-abnf-AHOZXSZD-TU7k5gB_.js +0 -1
- package/console-app/assets/railroad-ebnf-EBAXGLYW-DbArPBkM.js +0 -1
- package/console-app/assets/railroad-peg-LSFZ7HO6-TzB93z4K.js +0 -1
- package/console-app/assets/stateDiagram-v2-UEYNNEHI-D7V_HITc.js +0 -1
- package/console-app/assets/swimlanesDiagram-ULZ7WXOC-C4XcIHKT.js +0 -8
|
@@ -1,51 +1,26 @@
|
|
|
1
1
|
import { pikkuSessionlessFunc } from '#pikku/function';
|
|
2
|
-
import { createHttpPersonas } from '@pikku/core/persona';
|
|
3
2
|
import { personaEnvironmentRefusal } from '@pikku/core/persona';
|
|
4
3
|
import { resolvePersonas } from '../../utils/resolve-personas.js';
|
|
5
|
-
import { resolvePersonaCredentials } from '../../utils/persona-credentials.js';
|
|
6
|
-
import { resolveEnvironment } from './environment.js';
|
|
7
|
-
import { loadDeclaredRoles, openScopeServiceForRoles } from './roles-shared.js';
|
|
8
4
|
/**
|
|
9
|
-
*
|
|
5
|
+
* Report which personas an environment will provision, and which it will not.
|
|
10
6
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
const userIdByEmail = async (db, email) => {
|
|
19
|
-
const row = await db
|
|
20
|
-
.selectFrom('user')
|
|
21
|
-
.select('id')
|
|
22
|
-
.where('email', '=', email)
|
|
23
|
-
.executeTakeFirst();
|
|
24
|
-
return row ? String(row.id) : null;
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Provision the declared personas into an environment: their accounts, and the
|
|
28
|
-
* roles they declare.
|
|
29
|
-
*
|
|
30
|
-
* This is deployment, not seeding. `pikku db reset` applies a project's test
|
|
31
|
-
* fixtures and does not run in production; an accountable persona doing a real
|
|
32
|
-
* job in production still needs an account and its grants, and this is where
|
|
33
|
-
* they come from.
|
|
34
|
-
*
|
|
35
|
-
* Additive in the same way `syncSystemRoles` is: it creates and grants, and
|
|
36
|
-
* never deletes or revokes. Removing a persona from the code leaves their
|
|
37
|
-
* account and their grants standing until somebody decides otherwise — which is
|
|
38
|
-
* the point, because the alternative is a rolling deploy quietly locking out the
|
|
39
|
-
* teammate that the older replica is still running.
|
|
7
|
+
* It reports rather than writes, because the writing happens somewhere this
|
|
8
|
+
* command cannot reach. Provisioning a persona means creating a user row and
|
|
9
|
+
* granting it roles, and both of those live in the deployment's database — one
|
|
10
|
+
* the CLI has no connection to for any environment that is not the developer's
|
|
11
|
+
* own. This command used to open a Kysely connection from the local project
|
|
12
|
+
* config and write through it, which provisioned a local stage correctly and
|
|
13
|
+
* silently provisioned the wrong database for every other environment.
|
|
40
14
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* database
|
|
15
|
+
* So the deployment provisions itself: an app calls `provisionPersonas` from
|
|
16
|
+
* `@pikku/better-auth` in its server lifecycle, where the database connection
|
|
17
|
+
* and better-auth's own adapter are both already open, and a deploy carries its
|
|
18
|
+
* personas with it. What is left here is the half that needs no database — the
|
|
19
|
+
* declaration, and the environment rule applied to it — which is what tells you
|
|
20
|
+
* before a deploy whether the accounts you expect will appear.
|
|
45
21
|
*/
|
|
46
22
|
export const personaSync = pikkuSessionlessFunc({
|
|
47
|
-
|
|
48
|
-
func: async ({ logger, config, getInspectorState, variables }, { environment, apiUrl, dryRun }) => {
|
|
23
|
+
func: async ({ logger, config, getInspectorState }, { environment }) => {
|
|
49
24
|
const state = await getInspectorState(true, false, false, true);
|
|
50
25
|
const personas = resolvePersonas(state.personas?.definitions ?? [], config.scenarios?.emailDomain);
|
|
51
26
|
if (Object.keys(personas).length === 0) {
|
|
@@ -53,9 +28,11 @@ export const personaSync = pikkuSessionlessFunc({
|
|
|
53
28
|
return;
|
|
54
29
|
}
|
|
55
30
|
const environments = config.environments ?? {};
|
|
56
|
-
|
|
31
|
+
if (!environments[environment]) {
|
|
32
|
+
throw new Error(`Unknown environment '${environment}'. Configured environments: ${Object.keys(environments).join(', ') || 'none'}.`);
|
|
33
|
+
}
|
|
57
34
|
// The same rule that decides who may *run* against an environment decides
|
|
58
|
-
// who
|
|
35
|
+
// who is provisioned into it. Two rules would drift, and the one that
|
|
59
36
|
// drifted would leave an account standing in production for a persona the
|
|
60
37
|
// engine then refuses to sign in.
|
|
61
38
|
//
|
|
@@ -73,76 +50,21 @@ export const personaSync = pikkuSessionlessFunc({
|
|
|
73
50
|
}
|
|
74
51
|
if (eligible.length === 0) {
|
|
75
52
|
logger.info(`persona sync: no persona may act in '${environment}' — ${skipped.length} declared persona(s) were all skipped.`);
|
|
76
|
-
logger.info(' Run with --dry-run for the reasons, or give the persona this environment and disposition `accountable` if it is production.');
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
if (dryRun) {
|
|
80
|
-
logger.info(`persona sync (dry run): ${eligible.length} persona(s) would be provisioned into '${environment}' at ${env.apiUrl}:`);
|
|
81
|
-
for (const [id, persona] of eligible) {
|
|
82
|
-
logger.info(` ${id.padEnd(20)} ${persona.email}${persona.roles.length
|
|
83
|
-
? ` -> ${persona.roles.join(' + ')}`
|
|
84
|
-
: ' (no roles)'}`);
|
|
85
|
-
}
|
|
86
53
|
for (const refusal of skipped) {
|
|
87
|
-
logger.info(`
|
|
54
|
+
logger.info(` ${refusal}`);
|
|
88
55
|
}
|
|
89
|
-
logger.info('
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
const credentials = await resolvePersonaCredentials(variables, 'a persona account');
|
|
93
|
-
const declaredRoles = await loadDeclaredRoles(config.rolesMetaJsonFile, logger);
|
|
94
|
-
if (!declaredRoles) {
|
|
95
|
-
throw new Error('role metadata not found');
|
|
96
|
-
}
|
|
97
|
-
const signedIn = createHttpPersonas({
|
|
98
|
-
apiUrl: env.apiUrl,
|
|
99
|
-
...credentials,
|
|
100
|
-
personas: Object.fromEntries(eligible),
|
|
101
|
-
signInPath: env.signInPath,
|
|
102
|
-
rpcPath: env.rpcPath,
|
|
103
|
-
});
|
|
104
|
-
// Roles are synced on the way in, so a grant can name a role this deploy
|
|
105
|
-
// introduced. Both that sync and everything below are additive.
|
|
106
|
-
const opened = await openScopeServiceForRoles({ config, logger }, declaredRoles, 'pikku persona sync');
|
|
107
|
-
if (!opened) {
|
|
56
|
+
logger.info(' Give the persona this environment, and disposition `accountable` if it is production.');
|
|
108
57
|
return;
|
|
109
58
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// Signing in is what creates the account: the actor plugin upserts an
|
|
116
|
-
// `actor: true` user row on first sign-in, and nothing else does.
|
|
117
|
-
// `sessionRoles()` is the public call that forces it — its answer is a
|
|
118
|
-
// by-product here, not the reason for the call.
|
|
119
|
-
await signedIn[id].sessionRoles();
|
|
120
|
-
accounts++;
|
|
121
|
-
if (persona.roles.length === 0) {
|
|
122
|
-
logger.info(` ${id.padEnd(20)} ${persona.email} (no roles)`);
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
const userId = await userIdByEmail(opened.db, persona.email);
|
|
126
|
-
if (!userId) {
|
|
127
|
-
throw new Error(`persona sync: signed '${id}' in at ${env.apiUrl}, but no user row for ${persona.email} exists in the database this command opened. ` +
|
|
128
|
-
`That database is not the one '${environment}' runs on, so its grants would land nowhere.`);
|
|
129
|
-
}
|
|
130
|
-
const already = new Set(await opened.service.listUserRoles(userId));
|
|
131
|
-
const missing = persona.roles.filter((role) => !already.has(role));
|
|
132
|
-
for (const role of missing) {
|
|
133
|
-
await opened.service.addUserToRole(userId, role);
|
|
134
|
-
}
|
|
135
|
-
granted += missing.length;
|
|
136
|
-
held += persona.roles.length - missing.length;
|
|
137
|
-
logger.info(` ${id.padEnd(20)} ${persona.email} -> ${persona.roles.join(' + ')}${missing.length === 0 ? ' (already held)' : ''}`);
|
|
138
|
-
}
|
|
139
|
-
logger.info(`persona sync: ${accounts} account(s) in '${environment}', ${granted} role grant(s) applied, ${held} already held`);
|
|
140
|
-
if (skipped.length > 0) {
|
|
141
|
-
logger.info(` ${skipped.length} persona(s) skipped — they do not act in '${environment}'. Run with --dry-run for the reasons.`);
|
|
142
|
-
}
|
|
59
|
+
logger.info(`persona sync: ${eligible.length} persona(s) will be provisioned when a deployment of '${environment}' starts:`);
|
|
60
|
+
for (const [id, persona] of eligible) {
|
|
61
|
+
logger.info(` ${id.padEnd(20)} ${persona.email}${persona.roles.length
|
|
62
|
+
? ` -> ${persona.roles.join(' + ')}`
|
|
63
|
+
: ' (no roles)'}`);
|
|
143
64
|
}
|
|
144
|
-
|
|
145
|
-
|
|
65
|
+
for (const refusal of skipped) {
|
|
66
|
+
logger.info(` skipped: ${refusal}`);
|
|
146
67
|
}
|
|
68
|
+
logger.info(" Provisioning runs in the deployment: call `provisionPersonas` from '@pikku/better-auth' in your server lifecycle, passing `personaConfigs` and `personaEnvironments` from the generated personas file.");
|
|
147
69
|
},
|
|
148
70
|
});
|
|
@@ -18,11 +18,13 @@ import { createDevAgentRunner } from './dev-agent-runner.js';
|
|
|
18
18
|
import { resolveConsoleMount } from './serve-console.js';
|
|
19
19
|
import { serverReadyLine } from '../../server/server-ready.js';
|
|
20
20
|
import { createEphemeralContentSigningJWT } from '../../server/content-signing-jwt.js';
|
|
21
|
+
import { disableDevActorSignIn } from '../../server/actor-sign-in.js';
|
|
21
22
|
import { applyModelAliasOverride } from '../../utils/model-alias-override.js';
|
|
22
23
|
export const serve = pikkuSessionlessFunc({
|
|
23
24
|
remote: true,
|
|
24
25
|
func: async ({ logger, config, getInspectorState, variables, devServerRunner }, { port, console: serveConsole, model }) => {
|
|
25
26
|
process.env.PIKKU_DEV_QUICK_LOGIN ??= 'true';
|
|
27
|
+
disableDevActorSignIn(logger);
|
|
26
28
|
applyModelAliasOverride(logger, model, config.models);
|
|
27
29
|
const resolvedPort = parseInt(port || '3000', 10);
|
|
28
30
|
const hostname = 'localhost';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ValidateFinding } from './persona-checks.js';
|
|
2
|
+
export type AuthPluginCheckConfig = {
|
|
3
|
+
srcDirectories?: unknown;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Whether better-auth is configured the way pikku supports.
|
|
7
|
+
*
|
|
8
|
+
* Pikku authorizes on scopes, so better-auth's `admin()` — which authorizes its
|
|
9
|
+
* endpoints against a `user.role` column — is unsupported: wiring it means
|
|
10
|
+
* running two authorization models and projecting one onto the other. The
|
|
11
|
+
* inspector refuses it outright at prebuild; this reports the same thing
|
|
12
|
+
* without one, and additionally catches the quieter half of the migration, an
|
|
13
|
+
* app that dropped `admin()` and never wired `ban()`, which keeps its ban
|
|
14
|
+
* columns and its ban UI while silently enforcing nothing.
|
|
15
|
+
*/
|
|
16
|
+
export declare const runAuthPluginChecks: (root: string, config: AuthPluginCheckConfig | null) => Promise<ValidateFinding[]>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const lines = (...parts) => parts.join('\n');
|
|
5
|
+
/** Local rather than imported from shared-checks, which imports this module. */
|
|
6
|
+
const readTextSafe = async (path) => {
|
|
7
|
+
if (!existsSync(path))
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
return await readFile(path, 'utf8');
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const stringArray = (value) => Array.isArray(value)
|
|
17
|
+
? value.filter((v) => typeof v === 'string')
|
|
18
|
+
: [];
|
|
19
|
+
/**
|
|
20
|
+
* Source files under one directory, generated files included — better-auth is
|
|
21
|
+
* routinely configured in a file the auth scaffold wrote.
|
|
22
|
+
*/
|
|
23
|
+
const listSourceFiles = async (dir) => {
|
|
24
|
+
if (!existsSync(dir))
|
|
25
|
+
return [];
|
|
26
|
+
try {
|
|
27
|
+
return (await readdir(dir, { recursive: true }))
|
|
28
|
+
.filter((f) => typeof f === 'string' &&
|
|
29
|
+
(f.endsWith('.ts') || f.endsWith('.tsx')) &&
|
|
30
|
+
!f.includes('node_modules'))
|
|
31
|
+
.map((f) => join(dir, f));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The text inside every `plugins: [...]` array in the file.
|
|
39
|
+
*
|
|
40
|
+
* Bracket-counted rather than parsed: `pikku validate` runs without a TypeScript
|
|
41
|
+
* program, and a `]` inside a string literal in a plugin's options is the only
|
|
42
|
+
* way this misreads an array — at which point the check under-reports rather
|
|
43
|
+
* than inventing a finding.
|
|
44
|
+
*/
|
|
45
|
+
const pluginArrays = (text) => {
|
|
46
|
+
const arrays = [];
|
|
47
|
+
const opener = /plugins\s*:\s*\[/g;
|
|
48
|
+
let match;
|
|
49
|
+
while ((match = opener.exec(text))) {
|
|
50
|
+
const start = match.index + match[0].length;
|
|
51
|
+
let depth = 1;
|
|
52
|
+
let i = start;
|
|
53
|
+
while (i < text.length && depth > 0) {
|
|
54
|
+
if (text[i] === '[')
|
|
55
|
+
depth++;
|
|
56
|
+
else if (text[i] === ']')
|
|
57
|
+
depth--;
|
|
58
|
+
i++;
|
|
59
|
+
}
|
|
60
|
+
if (depth === 0)
|
|
61
|
+
arrays.push(text.slice(start, i - 1));
|
|
62
|
+
}
|
|
63
|
+
return arrays;
|
|
64
|
+
};
|
|
65
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
66
|
+
/**
|
|
67
|
+
* How `exported` from `module` can be called in this file: the local name a
|
|
68
|
+
* named import bound it to, or `<namespace>.<exported>` for a namespace import.
|
|
69
|
+
* Empty when the file never imports it, so a local helper of the same name is
|
|
70
|
+
* not mistaken for the package's.
|
|
71
|
+
*/
|
|
72
|
+
const importedCallees = (text, module, exported) => {
|
|
73
|
+
const callees = [];
|
|
74
|
+
const imports = /import\s+([\s\S]*?)\s+from\s*['"]([^'"]+)['"]/g;
|
|
75
|
+
let match;
|
|
76
|
+
while ((match = imports.exec(text))) {
|
|
77
|
+
if (match[2] !== module)
|
|
78
|
+
continue;
|
|
79
|
+
const clause = match[1];
|
|
80
|
+
const namespace = clause.match(/\*\s+as\s+([A-Za-z_$][\w$]*)/);
|
|
81
|
+
if (namespace) {
|
|
82
|
+
callees.push(`${namespace[1]}.${exported}`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const named = clause.match(/\{([\s\S]*?)\}/);
|
|
86
|
+
if (!named)
|
|
87
|
+
continue;
|
|
88
|
+
for (const entry of named[1].split(',')) {
|
|
89
|
+
const [imported, alias] = entry.split(/\s+as\s+/).map((v) => v.trim());
|
|
90
|
+
if (imported === exported)
|
|
91
|
+
callees.push(alias || imported);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return callees;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Whether `exported` from `module` is wired as a plugin — imported from that
|
|
98
|
+
* package *and* called inside a `plugins: [...]` array. An import left behind
|
|
99
|
+
* after the call was removed configures nothing, and a call somewhere else in
|
|
100
|
+
* the file is not a plugin.
|
|
101
|
+
*/
|
|
102
|
+
const configuresPlugin = (text, module, exported) => {
|
|
103
|
+
const callees = importedCallees(text, module, exported);
|
|
104
|
+
if (callees.length === 0)
|
|
105
|
+
return false;
|
|
106
|
+
return pluginArrays(text).some((array) => callees.some((callee) => new RegExp(`(^|[^\\w$.])${escapeRegExp(callee)}\\s*\\(`).test(array)));
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Whether better-auth is configured the way pikku supports.
|
|
110
|
+
*
|
|
111
|
+
* Pikku authorizes on scopes, so better-auth's `admin()` — which authorizes its
|
|
112
|
+
* endpoints against a `user.role` column — is unsupported: wiring it means
|
|
113
|
+
* running two authorization models and projecting one onto the other. The
|
|
114
|
+
* inspector refuses it outright at prebuild; this reports the same thing
|
|
115
|
+
* without one, and additionally catches the quieter half of the migration, an
|
|
116
|
+
* app that dropped `admin()` and never wired `ban()`, which keeps its ban
|
|
117
|
+
* columns and its ban UI while silently enforcing nothing.
|
|
118
|
+
*/
|
|
119
|
+
export const runAuthPluginChecks = async (root, config) => {
|
|
120
|
+
const findings = [];
|
|
121
|
+
const srcDirectories = stringArray(config?.srcDirectories);
|
|
122
|
+
const searchDirs = srcDirectories.length
|
|
123
|
+
? srcDirectories
|
|
124
|
+
: [join('packages', 'functions', 'src')];
|
|
125
|
+
const sourceFiles = (await Promise.all(searchDirs.map((dir) => listSourceFiles(join(root, dir))))).flat();
|
|
126
|
+
let bansAnywhere = false;
|
|
127
|
+
const adminFiles = [];
|
|
128
|
+
for (const file of sourceFiles) {
|
|
129
|
+
const text = await readTextSafe(file);
|
|
130
|
+
if (!text)
|
|
131
|
+
continue;
|
|
132
|
+
if (configuresPlugin(text, 'better-auth/plugins', 'admin'))
|
|
133
|
+
adminFiles.push(file);
|
|
134
|
+
if (configuresPlugin(text, '@pikku/better-auth', 'ban'))
|
|
135
|
+
bansAnywhere = true;
|
|
136
|
+
}
|
|
137
|
+
for (const file of adminFiles) {
|
|
138
|
+
findings.push({
|
|
139
|
+
id: 'better-auth-admin-plugin',
|
|
140
|
+
severity: 'error',
|
|
141
|
+
message: "better-auth's admin() plugin is not supported — it authorizes against a user.role column while pikku authorizes on scopes, so one has to be projected onto the other",
|
|
142
|
+
path: file,
|
|
143
|
+
fixHint: lines('Replace it with ban(), which keeps the one capability admin() had that', 'pikku cannot supply from outside better-auth — refusing a banned user a', 'session:', " import { ban } from '@pikku/better-auth'", ' betterAuth({ plugins: [ban()] })', 'User management is already scoped RPCs in @pikku/addon-admin: list,', 'create, ban, remove, revoke sessions and set password.'),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (!bansAnywhere && adminFiles.length === 0) {
|
|
147
|
+
const configuresAuth = sourceFiles.length > 0;
|
|
148
|
+
if (configuresAuth) {
|
|
149
|
+
for (const file of sourceFiles) {
|
|
150
|
+
const text = await readTextSafe(file);
|
|
151
|
+
if (!text || !/\bpikkuBetterAuth\s*\(/.test(text))
|
|
152
|
+
continue;
|
|
153
|
+
findings.push({
|
|
154
|
+
id: 'better-auth-no-ban-plugin',
|
|
155
|
+
severity: 'warn',
|
|
156
|
+
message: 'better-auth is configured without ban() — the banned/banReason/banExpires columns do not exist, so banning a user through @pikku/addon-admin has nothing to write and nothing refuses their next sign-in',
|
|
157
|
+
path: file,
|
|
158
|
+
fixHint: lines(" import { ban } from '@pikku/better-auth'", ' betterAuth({ plugins: [ban()] })', 'Wire it wherever the rest of your plugins are, then run', '`pikku db migrate` to add the columns.'),
|
|
159
|
+
});
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return findings;
|
|
165
|
+
};
|
|
@@ -123,7 +123,7 @@ export const runPersonaChecks = async (root, config) => {
|
|
|
123
123
|
return findings;
|
|
124
124
|
}
|
|
125
125
|
if (!(await hasActorSignIn(sourceFiles))) {
|
|
126
|
-
w('personas-no-actor-sign-in', `${personas} persona${personas === 1 ? ' is' : 's are'} declared, but nothing wires actor sign-in — a scenario run signs in over POST /auth/sign-in/actor, so every persona fails to acquire a session`, pikkuConfigPath, lines('Wire the actor plugin where better-auth is configured:', " import { actor } from '@pikku/better-auth'", ' plugins: [actor({ secret: SCENARIO_ACTOR_SECRET })]', '
|
|
126
|
+
w('personas-no-actor-sign-in', `${personas} persona${personas === 1 ? ' is' : 's are'} declared, but nothing wires actor sign-in — a scenario run signs in over POST /auth/sign-in/actor, so every persona fails to acquire a session`, pikkuConfigPath, lines('Wire the actor plugin where better-auth is configured:', " import { actor } from '@pikku/better-auth'", ' plugins: [actor({ secret: SCENARIO_ACTOR_SECRET })]', '`pikku dev` turns the endpoint on and mints the secret itself, so there', 'is nothing to configure locally. A deployed stage that must run', 'scenarios opts in with', 'PIKKU_ALLOW_ACTOR_SIGN_IN=passwordless-actor-sign-in; everywhere else', 'the endpoint stays off whatever the environment happens to contain.'));
|
|
127
127
|
}
|
|
128
128
|
const environments = config?.environments;
|
|
129
129
|
const hasEnvironment = typeof environments === 'object' &&
|
|
@@ -4,7 +4,10 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { runKnowledgeValidate } from '@pikku/knowledge';
|
|
5
5
|
import { runBootstrapChecks } from './bootstrap-checks.js';
|
|
6
6
|
import { runPersonaChecks } from './persona-checks.js';
|
|
7
|
+
import { runAuthPluginChecks } from './auth-plugin-checks.js';
|
|
7
8
|
import { runScenarioFileChecks } from './scenario-file-checks.js';
|
|
9
|
+
import { projectWiresChannels, runWebsocketDepsChecks, } from './websocket-deps-checks.js';
|
|
10
|
+
import { resolveFromProject } from '../../utils/resolve-from-project.js';
|
|
8
11
|
/**
|
|
9
12
|
* A malformed config is named, never reported as absent.
|
|
10
13
|
*
|
|
@@ -311,8 +314,17 @@ export async function runSharedProjectChecks(root) {
|
|
|
311
314
|
}
|
|
312
315
|
// ── server bootstrap ───────────────────────────────────────────────────
|
|
313
316
|
findings.push(...(await runBootstrapChecks(root, rootPkg, pikkuConfig)));
|
|
317
|
+
findings.push(...runWebsocketDepsChecks({
|
|
318
|
+
root,
|
|
319
|
+
runtime: typeof globalThis.Bun !== 'undefined'
|
|
320
|
+
? 'bun'
|
|
321
|
+
: 'node',
|
|
322
|
+
hasChannels: await projectWiresChannels(root, typeof pikkuConfig?.outDir === 'string' ? pikkuConfig.outDir : '.pikku'),
|
|
323
|
+
resolve: (specifier) => resolveFromProject(root, specifier),
|
|
324
|
+
}));
|
|
314
325
|
// ── personas, scenario files and the knowledge base ────────────────────
|
|
315
326
|
findings.push(...(await runPersonaChecks(root, pikkuConfig)));
|
|
327
|
+
findings.push(...(await runAuthPluginChecks(root, pikkuConfig)));
|
|
316
328
|
const srcDirectories = Array.isArray(pikkuConfig?.srcDirectories)
|
|
317
329
|
? pikkuConfig.srcDirectories.filter((d) => typeof d === 'string')
|
|
318
330
|
: [];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ValidateFinding } from './persona-checks.js';
|
|
2
|
+
/**
|
|
3
|
+
* The packages the Node dev/serve path needs to speak WebSocket. Both are
|
|
4
|
+
* optional peers of `@pikku/cli`, because Bun serves WebSockets natively
|
|
5
|
+
* through `@pikku/bun-server` and a Bun project has no use for either.
|
|
6
|
+
*/
|
|
7
|
+
export declare const NODE_WEBSOCKET_PACKAGES: readonly ["@pikku/ws", "ws"];
|
|
8
|
+
export type WebsocketDepsInput = {
|
|
9
|
+
root: string;
|
|
10
|
+
/** The runtime the CLI is running on. Bun needs neither package. */
|
|
11
|
+
runtime: 'node' | 'bun';
|
|
12
|
+
/** Whether the project wires any channels. Nothing is reported when not. */
|
|
13
|
+
hasChannels: boolean;
|
|
14
|
+
/** Resolves a specifier from the project. `undefined` when not installed. */
|
|
15
|
+
resolve: (specifier: string) => string | undefined;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Reports the optional WebSocket peers when a Node-hosted project that wires
|
|
19
|
+
* channels is missing them.
|
|
20
|
+
*
|
|
21
|
+
* `pikku dev` / `pikku serve` under Node start over plain HTTP when the
|
|
22
|
+
* packages are absent, rather than failing: a project with no channels does not
|
|
23
|
+
* need them, and making them hard dependencies of the CLI is what put a second
|
|
24
|
+
* `@pikku/core` in the tree. That leaves a project that *does* wire channels
|
|
25
|
+
* with sockets that never connect and nothing pointing at the cause — which is
|
|
26
|
+
* this check.
|
|
27
|
+
*/
|
|
28
|
+
export declare function runWebsocketDepsChecks(input: WebsocketDepsInput): ValidateFinding[];
|
|
29
|
+
/** True when codegen produced at least one channel for this project. */
|
|
30
|
+
export declare function projectWiresChannels(root: string, outDir: string): Promise<boolean>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { readJsonSafe } from './shared-checks.js';
|
|
3
|
+
/**
|
|
4
|
+
* The packages the Node dev/serve path needs to speak WebSocket. Both are
|
|
5
|
+
* optional peers of `@pikku/cli`, because Bun serves WebSockets natively
|
|
6
|
+
* through `@pikku/bun-server` and a Bun project has no use for either.
|
|
7
|
+
*/
|
|
8
|
+
export const NODE_WEBSOCKET_PACKAGES = ['@pikku/ws', 'ws'];
|
|
9
|
+
/**
|
|
10
|
+
* Reports the optional WebSocket peers when a Node-hosted project that wires
|
|
11
|
+
* channels is missing them.
|
|
12
|
+
*
|
|
13
|
+
* `pikku dev` / `pikku serve` under Node start over plain HTTP when the
|
|
14
|
+
* packages are absent, rather than failing: a project with no channels does not
|
|
15
|
+
* need them, and making them hard dependencies of the CLI is what put a second
|
|
16
|
+
* `@pikku/core` in the tree. That leaves a project that *does* wire channels
|
|
17
|
+
* with sockets that never connect and nothing pointing at the cause — which is
|
|
18
|
+
* this check.
|
|
19
|
+
*/
|
|
20
|
+
export function runWebsocketDepsChecks(input) {
|
|
21
|
+
const { root, runtime, hasChannels, resolve } = input;
|
|
22
|
+
if (runtime === 'bun' || !hasChannels)
|
|
23
|
+
return [];
|
|
24
|
+
const missing = NODE_WEBSOCKET_PACKAGES.filter((name) => !resolve(name));
|
|
25
|
+
if (missing.length === 0)
|
|
26
|
+
return [];
|
|
27
|
+
const named = missing.join(' and ');
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
id: 'websocket-deps-missing',
|
|
31
|
+
severity: 'error',
|
|
32
|
+
message: `${named} ${missing.length > 1 ? 'are' : 'is'} not installed, but this project wires channels — \`pikku dev\` and \`pikku serve\` will start without WebSocket support and every channel connection will fail`,
|
|
33
|
+
path: join(root, 'package.json'),
|
|
34
|
+
fixHint: `Install ${NODE_WEBSOCKET_PACKAGES.join(' ')} in this project. They are optional peer dependencies of @pikku/cli so that Bun projects, which serve WebSockets natively, do not pull them in`,
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
/** True when codegen produced at least one channel for this project. */
|
|
39
|
+
export async function projectWiresChannels(root, outDir) {
|
|
40
|
+
const meta = await readJsonSafe(join(root, outDir, 'channel', 'pikku-channels-meta.gen.json'));
|
|
41
|
+
return !!meta && Object.keys(meta).length > 0;
|
|
42
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `SCENARIO_ACTOR_SECRET` is the platform's, not the application's: nothing in
|
|
5
5
|
* app code reads it. It is consumed by the scenario service when it signs an
|
|
6
|
-
* actor in, by `pikku scenario` / `pikku persona
|
|
6
|
+
* actor in, by `pikku scenario` / `pikku persona`, and by
|
|
7
7
|
* the Playwright provider. Declaring it here — rather than leaving every project
|
|
8
8
|
* to hand-write the same `defineSecret` — is the same reasoning that puts
|
|
9
9
|
* `BETTER_AUTH_SECRET` in the auth scaffold: a value the framework requires
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `SCENARIO_ACTOR_SECRET` is the platform's, not the application's: nothing in
|
|
5
5
|
* app code reads it. It is consumed by the scenario service when it signs an
|
|
6
|
-
* actor in, by `pikku scenario` / `pikku persona
|
|
6
|
+
* actor in, by `pikku scenario` / `pikku persona`, and by
|
|
7
7
|
* the Playwright provider. Declaring it here — rather than leaving every project
|
|
8
8
|
* to hand-write the same `defineSecret` — is the same reasoning that puts
|
|
9
9
|
* `BETTER_AUTH_SECRET` in the auth scaffold: a value the framework requires
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import type { PersonaDefinitions } from '@pikku/core/persona';
|
|
2
|
+
import type { ScopeDefinitions } from '@pikku/core/scope';
|
|
3
|
+
/**
|
|
4
|
+
* The declared scopes, plus the `app` tree derived from the personas.
|
|
5
|
+
*
|
|
6
|
+
* Appended here rather than in the inspector because it is not a declaration
|
|
7
|
+
* the AST can find: it is the personas' `app` field read as a scope, and this
|
|
8
|
+
* is the one place the two states are both in hand.
|
|
9
|
+
*/
|
|
10
|
+
export declare const withAppScopes: (scopes: ScopeDefinitions, personas: PersonaDefinitions) => ScopeDefinitions;
|
|
1
11
|
export declare const pikkuScopes: import("#pikku/function").PikkuFunctionConfig<{
|
|
2
12
|
bootstrap?: boolean;
|
|
3
13
|
}, void, "rpc" | "session", import("#pikku/function").PikkuFunctionSessionless<{
|
|
@@ -4,6 +4,22 @@ import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-
|
|
|
4
4
|
import { serializeScopesTypes } from './serialize-scopes-types.js';
|
|
5
5
|
import { serializeScopesClient } from './serialize-scopes-client.js';
|
|
6
6
|
import { validateAndBuildScopeDefinitionsMeta } from '@pikku/core/scope';
|
|
7
|
+
import { APP_SCOPE_ROOT, buildAppScopeDefinition } from '@pikku/core/persona';
|
|
8
|
+
/**
|
|
9
|
+
* The declared scopes, plus the `app` tree derived from the personas.
|
|
10
|
+
*
|
|
11
|
+
* Appended here rather than in the inspector because it is not a declaration
|
|
12
|
+
* the AST can find: it is the personas' `app` field read as a scope, and this
|
|
13
|
+
* is the one place the two states are both in hand.
|
|
14
|
+
*/
|
|
15
|
+
export const withAppScopes = (scopes, personas) => {
|
|
16
|
+
const declared = scopes.find((scope) => scope.name === APP_SCOPE_ROOT);
|
|
17
|
+
if (declared) {
|
|
18
|
+
throw new Error(`'${APP_SCOPE_ROOT}' is reserved: it is derived from the apps your personas name, and ${declared.sourceFile ?? 'a defineScope call'} declares it too. Rename that scope root — two trees answering to one name make a grant ambiguous.`);
|
|
19
|
+
}
|
|
20
|
+
const appScopes = buildAppScopeDefinition(personas);
|
|
21
|
+
return appScopes ? [...scopes, appScopes] : scopes;
|
|
22
|
+
};
|
|
7
23
|
export const pikkuScopes = pikkuSessionlessFunc({
|
|
8
24
|
func: async ({ logger, config, getInspectorState }, data) => {
|
|
9
25
|
const { scopesFile, scopesMetaJsonFile } = config;
|
|
@@ -19,15 +35,16 @@ export const pikkuScopes = pikkuSessionlessFunc({
|
|
|
19
35
|
// Scopes step regenerates it with the declarations once setup has run.
|
|
20
36
|
const bootstrap = data?.bootstrap ?? false;
|
|
21
37
|
const state = await getInspectorState(false, bootstrap, bootstrap);
|
|
38
|
+
const definitions = withAppScopes(state.scopes.definitions, state.personas.definitions);
|
|
22
39
|
const content = serializeScopesTypes({
|
|
23
|
-
definitions
|
|
40
|
+
definitions,
|
|
24
41
|
});
|
|
25
42
|
await writeFileInDir(logger, scopesFile, content);
|
|
26
43
|
if (scopesClientFile) {
|
|
27
|
-
await writeFileInDir(logger, scopesClientFile, serializeScopesClient({ definitions
|
|
44
|
+
await writeFileInDir(logger, scopesClientFile, serializeScopesClient({ definitions }));
|
|
28
45
|
}
|
|
29
46
|
if (scopesMetaJsonFile) {
|
|
30
|
-
const meta = validateAndBuildScopeDefinitionsMeta(
|
|
47
|
+
const meta = validateAndBuildScopeDefinitionsMeta(definitions);
|
|
31
48
|
await writeFileInDir(logger, scopesMetaJsonFile, JSON.stringify(meta, null, 2));
|
|
32
49
|
}
|
|
33
50
|
},
|
|
@@ -103,7 +103,7 @@ export const pikkuWorkflow = pikkuSessionlessFunc({
|
|
|
103
103
|
{
|
|
104
104
|
const agentMapImportPath = getFileImportRelativePath(config.personasWiringFile, config.agentMapDeclarationFile, packageMappings);
|
|
105
105
|
const exposedRpcMapImportPath = getFileImportRelativePath(config.personasWiringFile, config.rpcMapDeclarationFile, packageMappings);
|
|
106
|
-
await writeFileInDir(logger, config.personasWiringFile, serializePersonas(personas, agentMapImportPath, exposedRpcMapImportPath));
|
|
106
|
+
await writeFileInDir(logger, config.personasWiringFile, serializePersonas(personas, agentMapImportPath, exposedRpcMapImportPath, config.environments ?? {}));
|
|
107
107
|
// Same reason as the scenario meta above: a project generated before this
|
|
108
108
|
// moved to `scenarios/` still has a copy under `workflow/`, and tsc
|
|
109
109
|
// compiles every file in the output tree whether anything imports it.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PersonaEnvironment } from '@pikku/core/persona';
|
|
1
2
|
import type { ResolvedPersona } from '@pikku/core/services';
|
|
2
3
|
/**
|
|
3
4
|
* The runtime half of `definePersonas()`.
|
|
@@ -7,4 +8,4 @@ import type { ResolvedPersona } from '@pikku/core/services';
|
|
|
7
8
|
* to arrive at the same one. Baking them here is what makes that a single fact
|
|
8
9
|
* rather than three implementations of the same rule.
|
|
9
10
|
*/
|
|
10
|
-
export declare const serializePersonas: (personas: Record<string, ResolvedPersona>, agentMapImportPath: string, rpcMapImportPath: string) => string;
|
|
11
|
+
export declare const serializePersonas: (personas: Record<string, ResolvedPersona>, agentMapImportPath: string, rpcMapImportPath: string, environments?: Readonly<Record<string, PersonaEnvironment>>) => string;
|
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
* to arrive at the same one. Baking them here is what makes that a single fact
|
|
7
7
|
* rather than three implementations of the same rule.
|
|
8
8
|
*/
|
|
9
|
-
export const serializePersonas = (personas, agentMapImportPath, rpcMapImportPath) => {
|
|
9
|
+
export const serializePersonas = (personas, agentMapImportPath, rpcMapImportPath, environments = {}) => {
|
|
10
|
+
// Only the flag the rule reads. The rest of an environment entry is its
|
|
11
|
+
// apiUrl and paths, which belong to the machine running `pikku scenario` and
|
|
12
|
+
// have no business inside the deployed bundle.
|
|
13
|
+
const projected = Object.fromEntries(Object.entries(environments).map(([name, environment]) => [
|
|
14
|
+
name,
|
|
15
|
+
environment.production ? { production: true } : {},
|
|
16
|
+
]));
|
|
10
17
|
return `/** Personas declared with definePersonas() */
|
|
11
18
|
import { createHttpPersonas } from '@pikku/core/persona'
|
|
12
19
|
import type { HttpPersonasConfig } from '@pikku/core/persona'
|
|
@@ -27,6 +34,18 @@ export type PersonaName = keyof typeof personaConfigs
|
|
|
27
34
|
*/
|
|
28
35
|
export const personaList: ResolvedPersona[] = Object.values(personaConfigs)
|
|
29
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The \`environments\` block of pikku.config.json, carried into the runtime.
|
|
39
|
+
*
|
|
40
|
+
* A persona's environments are checked twice: at build time against the config,
|
|
41
|
+
* and again wherever a persona is signed in or provisioned, against the
|
|
42
|
+
* environment the running process says it is (\`PIKKU_ENV\`). The second check
|
|
43
|
+
* is not the first one repeated — the build check trusts the file, and the run
|
|
44
|
+
* check does not trust which artifact got deployed — so the rule needs the
|
|
45
|
+
* config on both sides of the build.
|
|
46
|
+
*/
|
|
47
|
+
export const personaEnvironments = ${JSON.stringify(projected, null, 2)} as const satisfies Record<string, { production?: boolean }>
|
|
48
|
+
|
|
30
49
|
export type AgentName = keyof AgentMap & string
|
|
31
50
|
/**
|
|
32
51
|
* The personas this project declares, keyed by name — the \`actors\` a scenario
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Logger } from '@pikku/core/services';
|
|
2
|
+
export declare const ACTOR_SECRET_ENV = "SCENARIO_ACTOR_SECRET";
|
|
3
|
+
export declare const VITE_ACTOR_SECRET_ENV = "VITE_SCENARIO_ACTOR_SECRET";
|
|
4
|
+
export declare const enableDevActorSignIn: (logger: Logger) => void;
|
|
5
|
+
export declare const disableDevActorSignIn: (logger: Logger) => void;
|