@pikku/cli 0.12.96 → 0.12.99
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/console-app/assets/abnfDiagram-VRR7QNED-CytV-CG1.js +1 -0
- package/console-app/assets/arc-C5PvmnaA.js +1 -0
- package/console-app/assets/architecture-TIHT7OUA-C1_ScvPX.js +1 -0
- package/console-app/assets/architectureDiagram-ZJ3FMSHR-CJ2xb7C-.js +36 -0
- package/console-app/assets/array-BifhSqXX.js +1 -0
- package/console-app/assets/blockDiagram-677ZJIJ3-CCecrBrk.js +132 -0
- package/console-app/assets/c4Diagram-LMCZKHZV-CBnpukvQ.js +10 -0
- package/console-app/assets/channel-CwDOkXGI.js +1 -0
- package/console-app/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/console-app/assets/chunk-32BRIVSS-D8qWWZRQ.js +1 -0
- package/console-app/assets/chunk-52WLFC77-BEpXHMsp.js +10 -0
- package/console-app/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/console-app/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
- package/console-app/assets/chunk-C7G6YPKG-DsHUMkOp.js +1 -0
- package/console-app/assets/chunk-EX3LRPZG-mM7TjVx9.js +231 -0
- package/console-app/assets/chunk-FWX5IMBZ-CndRAn18.js +2 -0
- package/console-app/assets/chunk-HOUHSVGY-D1kT8umn.js +1 -0
- package/console-app/assets/chunk-ICXQ74PX-CeSaGyro.js +2 -0
- package/console-app/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/console-app/assets/chunk-KEIR6QF5-DNzq6p3w.js +161 -0
- package/console-app/assets/chunk-MOJQB5TN-CaZOCHYr.js +88 -0
- package/console-app/assets/chunk-OGEWGWER-CnB1CsPt.js +1 -0
- package/console-app/assets/chunk-PUDLZKDR-BbGulHJm.js +156 -0
- package/console-app/assets/chunk-Q4XR5HBZ-BywwzeeR.js +70 -0
- package/console-app/assets/chunk-RYQCIY6F-CV0R1Adv.js +1 -0
- package/console-app/assets/chunk-V7JOEXUC-DGFaAWzB.js +206 -0
- package/console-app/assets/chunk-VAUOI2AC-DxPUd8Yb.js +1 -0
- package/console-app/assets/chunk-VR4S4FIN-D4-UaW_J.js +1 -0
- package/console-app/assets/chunk-WYO6CB5R-D_TFN0d9.js +127 -0
- package/console-app/assets/chunk-XXDRQBXY-BzVuweA_.js +1 -0
- package/console-app/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/console-app/assets/chunk-ZGVPDNZ5-C6hdkGQb.js +62 -0
- package/console-app/assets/chunk-ZIRB5QZD-H-kepumB.js +32 -0
- package/console-app/assets/classDiagram-OUVF2IWQ-CTYJkTT0.js +1 -0
- package/console-app/assets/classDiagram-v2-EOCWNBFH-CTYJkTT0.js +1 -0
- package/console-app/assets/cose-bilkent-JH36ORCC-B6ZPxN6v.js +1 -0
- package/console-app/assets/cynefin-VYW2F7L2-D_nmxNz_.js +1 -0
- package/console-app/assets/cynefinDiagram-TSTJHNR4-CHy5QN0S.js +62 -0
- package/console-app/assets/cytoscape.esm-h6BdjjI9.js +321 -0
- package/console-app/assets/dagre-CXRCoUWR.js +1 -0
- package/console-app/assets/dagre-VKFMJZFB-DYfukM34.js +4 -0
- package/console-app/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/console-app/assets/diagram-FQU43EPY-Dgb9TPSB.js +3 -0
- package/console-app/assets/diagram-G47NLZAW-CnFNZs9Z.js +24 -0
- package/console-app/assets/diagram-NH7WQ7WH-DN7h2DU-.js +24 -0
- package/console-app/assets/diagram-OA4YK3LP-RLaQaNtK.js +30 -0
- package/console-app/assets/diagram-WEI45ONY-DxHWhVGd.js +41 -0
- package/console-app/assets/dist-BaJNIo4I.js +1 -0
- package/console-app/assets/ebnfDiagram-CCIWWBDH-UN3Z81-l.js +1 -0
- package/console-app/assets/erDiagram-Q63AITRT-C9lGAY3i.js +85 -0
- package/console-app/assets/eventmodeling-45OFAUF4-Dc5wNSSK.js +1 -0
- package/console-app/assets/flowDiagram-23GEKE2U-zPyGHMi4.js +1 -0
- package/console-app/assets/ganttDiagram-NO4QXBWP-CvBjBcdK.js +292 -0
- package/console-app/assets/gitGraph-TEB2WS4Q-Cs5Z9jMo.js +1 -0
- package/console-app/assets/gitGraphDiagram-IHSO6WYX-B0i_IJ5r.js +106 -0
- package/console-app/assets/graphlib-B8gBHxth.js +1 -0
- package/console-app/assets/{index-CeQodYfg.js → index-71U8EEj7.js} +155 -153
- package/console-app/assets/{index-CyYHTGWZ.css → index-BXMjqLQ5.css} +1 -1
- package/console-app/assets/info-DKCQHKI2-DHL0afNl.js +1 -0
- package/console-app/assets/infoDiagram-FWYZ7A6U-CBWykodX.js +2 -0
- package/console-app/assets/init-D6jRqBbL.js +1 -0
- package/console-app/assets/ishikawaDiagram-FXEZZL3T-DM377eW_.js +70 -0
- package/console-app/assets/journeyDiagram-5HDEW3XC-BpX_-Cns.js +139 -0
- package/console-app/assets/kanban-definition-HUTT4EX6-D3xFsQkO.js +89 -0
- package/console-app/assets/katex-Vhh-h91d.js +257 -0
- package/console-app/assets/line-CTSKHTE0.js +1 -0
- package/console-app/assets/linear-R5GlBvXT.js +1 -0
- package/console-app/assets/map-DsCK-0Cs.js +1 -0
- package/console-app/assets/mermaid-parser.core-CkHk4BPi.js +7 -0
- package/console-app/assets/mermaid.core-CcjGXuV6.js +11 -0
- package/console-app/assets/mindmap-definition-LN4V7U3C-CtolazqQ.js +96 -0
- package/console-app/assets/ordinal-hYBb2elL.js +1 -0
- package/console-app/assets/packet-7NZHBO7P-BxSOj_Ny.js +1 -0
- package/console-app/assets/path-BWPyau1x.js +1 -0
- package/console-app/assets/pegDiagram-2B236MQR-BNsMtxsM.js +1 -0
- package/console-app/assets/pie-RZYD4A2V-BbkKQBA5.js +1 -0
- package/console-app/assets/pieDiagram-ENE6RG2P-BAZLIz2f.js +39 -0
- package/console-app/assets/quadrantDiagram-ABIIQ3AL-Dpvn-1Fs.js +7 -0
- package/console-app/assets/radar-I7S5WNFK-B-uKXIAH.js +1 -0
- package/console-app/assets/railroad-3IZDKUUU-BHXXK04t.js +1 -0
- package/console-app/assets/railroad-abnf-AHOZXSZD-tXSQaBvY.js +1 -0
- package/console-app/assets/railroad-ebnf-EBAXGLYW-DG3aohR0.js +1 -0
- package/console-app/assets/railroad-peg-LSFZ7HO6-DY_gBA-x.js +1 -0
- package/console-app/assets/railroadDiagram-RFXS5EU6-DfUApEQj.js +1 -0
- package/console-app/assets/requirementDiagram-TGXJPOKE-B2zX0jqo.js +84 -0
- package/console-app/assets/rough.esm-CSKSodPl.js +1 -0
- package/console-app/assets/sankeyDiagram-HTMAVEWB-CbQ2zc7z.js +40 -0
- package/console-app/assets/sequenceDiagram-DBY2YBRQ-BLT8PXI_.js +162 -0
- package/console-app/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
- package/console-app/assets/src-BLuPUFWW.js +1 -0
- package/console-app/assets/stateDiagram-2N3HPSRC-BvEyVp85.js +1 -0
- package/console-app/assets/stateDiagram-v2-6OUMAXLB-RGKS9iU4.js +1 -0
- package/console-app/assets/swimlanes-5IMT3BWC-1AZ459s6.js +2 -0
- package/console-app/assets/swimlanesDiagram-G3AALYLV-Ck_0-rfN.js +8 -0
- package/console-app/assets/timeline-definition-FHXFAJF6-dLuvH4s2.js +120 -0
- package/console-app/assets/treeView-QDETBFTQ-xL2msjo2.js +1 -0
- package/console-app/assets/treemap-6X3UGDF4-C98ichVP.js +1 -0
- package/console-app/assets/vennDiagram-L72KCM5P-HbUmidhv.js +34 -0
- package/console-app/assets/wardley-OPB4EBWU-C4gJS-m2.js +1 -0
- package/console-app/assets/wardleyDiagram-EHGQE667-B7b-LB02.js +78 -0
- package/console-app/assets/xychartDiagram-FW5EYKEG-Cm03zUGy.js +7 -0
- package/console-app/index.html +2 -2
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/auth/auth.types.d.ts +1 -1
- package/dist/.pikku/auth/auth.types.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/pikku-cli-channel.js +1 -6
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +36 -9
- 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 +7 -8
- 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 +18 -2
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +12 -6
- 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 +39 -30
- package/dist/.pikku/function/pikku-functions.gen.js +3 -3
- 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/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-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-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +2 -1
- package/dist/.pikku/pikku-services.gen.js +1 -0
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.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 -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-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/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 -3
- package/dist/.pikku/schemas/schemas/DbResetInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/{DbSeedInput.schema.json → PikkuVirtualUserFunctionsOutput.schema.json} +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/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/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.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/meta/allWorkflow.gen.json +20 -14
- package/dist/.pikku/workflow/pikku-personas.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +6 -6
- 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 +8 -7
- package/dist/src/fabric/functions/validate.function.js +76 -176
- package/dist/src/functions/commands/all.d.ts +1 -1
- package/dist/src/functions/commands/bootstrap.d.ts +1 -1
- package/dist/src/functions/commands/db-audit.js +1 -1
- package/dist/src/functions/commands/db-baseline.js +1 -1
- package/dist/src/functions/commands/db-check.js +6 -5
- package/dist/src/functions/commands/db-codegen.js +1 -1
- package/dist/src/functions/commands/db-generate.js +1 -1
- package/dist/src/functions/commands/db-migrate.js +1 -1
- package/dist/src/functions/commands/db-reset.d.ts +15 -1
- package/dist/src/functions/commands/db-reset.js +18 -6
- package/dist/src/functions/commands/db-shared.d.ts +14 -0
- package/dist/src/functions/commands/deploy-info.d.ts +1 -1
- package/dist/src/functions/commands/dev.js +1 -1
- package/dist/src/functions/commands/enable.d.ts +8 -8
- package/dist/src/functions/commands/new-addon.js +2 -2
- package/dist/src/functions/commands/persona-sync.d.ts +1 -1
- package/dist/src/functions/commands/persona-sync.js +1 -1
- package/dist/src/functions/commands/persona.js +16 -18
- package/dist/src/functions/commands/scenario-ladder.js +1 -1
- package/dist/src/functions/commands/scenario.js +4 -2
- package/dist/src/functions/commands/scopes-shared.d.ts +4 -0
- package/dist/src/functions/commands/scopes-shared.js +1 -1
- package/dist/src/functions/commands/serve.js +1 -1
- package/dist/src/functions/commands/versions-check.js +3 -3
- package/dist/src/functions/db/annotation-parser.d.ts +14 -0
- package/dist/src/functions/db/annotation-parser.js +50 -2
- package/dist/src/functions/db/better-auth-schema.js +2 -1
- package/dist/src/functions/db/db-codegen.d.ts +12 -0
- package/dist/src/functions/db/db-codegen.js +149 -16
- package/dist/src/functions/db/local-db.d.ts +87 -6
- package/dist/src/functions/db/local-db.js +202 -35
- package/dist/src/functions/db/sqlite/dev-seed.d.ts +12 -0
- package/dist/src/functions/db/sqlite/dev-seed.js +23 -0
- package/dist/src/functions/db/sqlite/sqlite-runtime-bun.js +1 -1
- package/dist/src/functions/validate/bootstrap-checks.d.ts +19 -0
- package/dist/src/functions/validate/bootstrap-checks.js +90 -0
- package/dist/src/functions/validate/persona-checks.d.ts +24 -0
- package/dist/src/functions/validate/persona-checks.js +136 -0
- package/dist/src/functions/validate/scenario-file-checks.d.ts +17 -0
- package/dist/src/functions/validate/scenario-file-checks.js +123 -0
- package/dist/src/functions/validate/shared-checks.d.ts +59 -0
- package/dist/src/functions/validate/shared-checks.js +311 -0
- package/dist/src/functions/validate/workspace-validate.d.ts +1 -2
- package/dist/src/functions/validate/workspace-validate.js +4 -196
- package/dist/src/functions/wirings/auth/serialize-auth-gen.js +16 -1
- package/dist/src/functions/wirings/auth/serialize-auth-types.d.ts +3 -1
- package/dist/src/functions/wirings/auth/serialize-auth-types.js +3 -1
- package/dist/src/functions/wirings/channels/pikku-channels.d.ts +1 -1
- package/dist/src/functions/wirings/channels/serialize-typed-channel-map.js +10 -2
- package/dist/src/functions/wirings/cli/pikku-command-cli-entry.js +9 -4
- package/dist/src/functions/wirings/cli/serialize-channel-cli-client.js +32 -10
- package/dist/src/functions/wirings/cli/serialize-cli-entrypoint-guard.d.ts +12 -0
- package/dist/src/functions/wirings/cli/serialize-cli-entrypoint-guard.js +28 -0
- package/dist/src/functions/wirings/cli/serialize-local-cli-bootstrap.js +4 -1
- package/dist/src/functions/wirings/console/serialize-console-functions.js +1 -1
- package/dist/src/functions/wirings/functions/serialize-addon-types.js +1 -1
- package/dist/src/functions/wirings/functions/serialize-function-types.js +19 -5
- package/dist/src/functions/wirings/package/pikku-command-package-types.d.ts +3 -3
- package/dist/src/functions/wirings/personas/personas-secrets-file.d.ts +8 -0
- package/dist/src/functions/wirings/personas/personas-secrets-file.js +9 -0
- package/dist/src/functions/wirings/personas/pikku-command-personas.js +14 -0
- package/dist/src/functions/wirings/personas/serialize-personas-secrets.d.ts +16 -0
- package/dist/src/functions/wirings/personas/serialize-personas-secrets.js +34 -0
- package/dist/src/functions/wirings/queue/serialize-queue-map.js +2 -1
- package/dist/src/functions/wirings/queue/serialize-queue-wrapper.js +2 -1
- package/dist/src/functions/wirings/virtual-user/pikku-command-virtual-user-functions.d.ts +1 -0
- package/dist/src/functions/wirings/virtual-user/pikku-command-virtual-user-functions.js +42 -0
- package/dist/src/functions/wirings/virtual-user/serialize-virtual-user-functions.d.ts +28 -0
- package/dist/src/functions/wirings/virtual-user/serialize-virtual-user-functions.js +439 -0
- package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.js +2 -2
- package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +5 -11
- package/dist/src/functions/workflows/all.workflow.js +49 -16
- package/dist/src/server/bun-server-runner.d.ts +2 -2
- package/dist/src/server/bun-server-runner.js +5 -1
- package/dist/src/services/cli-logger.service.d.ts +28 -16
- package/dist/src/services/cli-logger.service.js +25 -16
- package/dist/src/services.js +8 -0
- package/dist/src/utils/file-import-path.js +9 -0
- package/dist/src/utils/pikku-cli-config.js +7 -0
- package/dist/src/utils/resolve-personas.d.ts +4 -9
- package/dist/src/utils/resolve-personas.js +4 -15
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -11
- package/dist/.pikku/schemas/schemas/BootstrapInput.schema.json +0 -1
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +0 -1
- package/dist/src/functions/commands/db-seed.d.ts +0 -1
- package/dist/src/functions/commands/db-seed.js +0 -24
- package/dist/src/functions/db/sqlite/seed.d.ts +0 -11
- package/dist/src/functions/db/sqlite/seed.js +0 -22
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
const stringArray = (value) => Array.isArray(value)
|
|
6
|
+
? value.filter((v) => typeof v === 'string')
|
|
7
|
+
: [];
|
|
8
|
+
/**
|
|
9
|
+
* Source files under one directory, flat list, generated files included.
|
|
10
|
+
*
|
|
11
|
+
* A persona declaration is `definePersonas({ … })` written by hand, but the
|
|
12
|
+
* actor plugin is routinely wired in a file the auth scaffold generated, so
|
|
13
|
+
* `.gen.ts` cannot be skipped the way the undeclared-import walk skips it.
|
|
14
|
+
*/
|
|
15
|
+
const listSourceFiles = async (dir) => {
|
|
16
|
+
if (!existsSync(dir))
|
|
17
|
+
return [];
|
|
18
|
+
try {
|
|
19
|
+
return (await readdir(dir, { recursive: true }))
|
|
20
|
+
.filter((f) => typeof f === 'string' &&
|
|
21
|
+
(f.endsWith('.ts') || f.endsWith('.tsx')) &&
|
|
22
|
+
!f.includes('node_modules'))
|
|
23
|
+
.map((f) => join(dir, f));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const readTextSafe = async (path) => {
|
|
30
|
+
if (!existsSync(path))
|
|
31
|
+
return null;
|
|
32
|
+
try {
|
|
33
|
+
return await readFile(path, 'utf8');
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Where the persona meta lands when the project has not overridden it, mirroring
|
|
41
|
+
* the default `pikku-cli-config` resolves — validate reads the raw config file
|
|
42
|
+
* rather than the resolved one, so the default has to be repeated here.
|
|
43
|
+
*/
|
|
44
|
+
const personaMetaPath = (root, config) => {
|
|
45
|
+
if (typeof config.personasMetaJsonFile === 'string') {
|
|
46
|
+
return join(root, config.personasMetaJsonFile);
|
|
47
|
+
}
|
|
48
|
+
const outDir = typeof config.outDir === 'string' ? config.outDir : '.pikku';
|
|
49
|
+
return join(root, outDir, 'scopes', 'pikku-personas-meta.gen.json');
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Whether this project declares anyone to run as.
|
|
53
|
+
*
|
|
54
|
+
* Two sources, because either one alone is wrong at a predictable moment: the
|
|
55
|
+
* generated meta is empty on a checkout where codegen has not run yet, and a
|
|
56
|
+
* source scan cannot see personas a dependency contributed. Declared by either
|
|
57
|
+
* counts as declared — a validator that cries about a missing persona on a
|
|
58
|
+
* fresh clone gets muted, and then it never says anything worth hearing.
|
|
59
|
+
*/
|
|
60
|
+
const countPersonas = async (root, config, sourceFiles) => {
|
|
61
|
+
const metaText = await readTextSafe(personaMetaPath(root, config));
|
|
62
|
+
if (metaText) {
|
|
63
|
+
try {
|
|
64
|
+
const meta = JSON.parse(metaText);
|
|
65
|
+
const declared = Object.keys(meta).length;
|
|
66
|
+
if (declared > 0)
|
|
67
|
+
return declared;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// A corrupt meta file is the scan's problem to answer, not a reason to
|
|
71
|
+
// claim there are no personas.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const file of sourceFiles) {
|
|
75
|
+
const text = await readTextSafe(file);
|
|
76
|
+
if (text && /\bdefinePersonas\s*\(/.test(text))
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
return 0;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Whether a persona can actually sign in.
|
|
83
|
+
*
|
|
84
|
+
* Two shapes count. `actor()` from `@pikku/better-auth` is the one the scaffold
|
|
85
|
+
* wires, and a hand-rolled `/sign-in/actor` route is the one a project that does
|
|
86
|
+
* not use better-auth ends up with. Both terminate at the same endpoint, so
|
|
87
|
+
* either satisfies the check.
|
|
88
|
+
*/
|
|
89
|
+
const hasActorSignIn = async (sourceFiles) => {
|
|
90
|
+
for (const file of sourceFiles) {
|
|
91
|
+
const text = await readTextSafe(file);
|
|
92
|
+
if (!text)
|
|
93
|
+
continue;
|
|
94
|
+
const wiresPlugin = /@pikku\/better-auth/.test(text) && /\bactor\s*\(/.test(text);
|
|
95
|
+
if (wiresPlugin || /sign-in\/actor/.test(text))
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* The people this project can run as, and whether it can run as them.
|
|
102
|
+
*
|
|
103
|
+
* Shared by `pikku workspace validate` and `pikku fabric validate` — the two
|
|
104
|
+
* validators are separate implementations, and a check that only one of them
|
|
105
|
+
* makes is a check half the projects never see.
|
|
106
|
+
*
|
|
107
|
+
* knowledge: decisions/internals/validate-checks-personas-through-a-shared-module.md
|
|
108
|
+
*/
|
|
109
|
+
export const runPersonaChecks = async (root, config) => {
|
|
110
|
+
const findings = [];
|
|
111
|
+
const w = (id, message, path, fixHint) => {
|
|
112
|
+
findings.push({ id, severity: 'warn', message, path, fixHint });
|
|
113
|
+
};
|
|
114
|
+
const srcDirectories = stringArray(config?.srcDirectories);
|
|
115
|
+
const searchDirs = srcDirectories.length
|
|
116
|
+
? srcDirectories
|
|
117
|
+
: [join('packages', 'functions', 'src')];
|
|
118
|
+
const sourceFiles = (await Promise.all(searchDirs.map((dir) => listSourceFiles(join(root, dir))))).flat();
|
|
119
|
+
const personas = await countPersonas(root, config ?? {}, sourceFiles);
|
|
120
|
+
const pikkuConfigPath = join(root, 'pikku.config.json');
|
|
121
|
+
if (personas === 0) {
|
|
122
|
+
w('no-personas', 'No personas are declared — `pikku scenario run` and `pikku persona run` have nobody to run as, so nothing exercises this app the way a user would', pikkuConfigPath, lines('Declare the people who use this app with definePersonas(), e.g. in', 'packages/functions/src/scenarios/personas.virtual-user.ts:', " import { definePersonas } from '#pikku/scopes/pikku-personas.gen.js'", ' definePersonas({', " founder: { name: 'Anna Müller', jobTitle: 'Managing director' },", ' })', 'Their email addresses are computed from the key and scenarios.emailDomain.'));
|
|
123
|
+
return findings;
|
|
124
|
+
}
|
|
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 })]', 'The plugin refuses every sign-in while the secret is unset, so a stage', 'that must not run scenarios simply leaves SCENARIO_ACTOR_SECRET out.'));
|
|
127
|
+
}
|
|
128
|
+
const environments = config?.environments;
|
|
129
|
+
const hasEnvironment = typeof environments === 'object' &&
|
|
130
|
+
environments !== null &&
|
|
131
|
+
Object.keys(environments).length > 0;
|
|
132
|
+
if (!hasEnvironment) {
|
|
133
|
+
w('personas-no-environments', `${personas} persona${personas === 1 ? ' is' : 's are'} declared, but pikku.config.json configures no environments — \`pikku scenario run <environment>\` has no target to resolve and refuses every name`, pikkuConfigPath, lines('Add the targets a run may point at:', ' "environments": {', ' "local": { "apiUrl": "http://localhost:4002", "appUrl": "http://localhost:3000" }', ' }'));
|
|
134
|
+
}
|
|
135
|
+
return findings;
|
|
136
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ValidateFinding } from './persona-checks.js';
|
|
2
|
+
/**
|
|
3
|
+
* Scenarios, features and steps live in files named for what they are.
|
|
4
|
+
*
|
|
5
|
+
* A `pikkuScenario` declared next to the functions it exercises reads as more
|
|
6
|
+
* of the same file — the wiring, the function and the test of the function all
|
|
7
|
+
* in one scroll — and the scenario is the part that gets lost. Keeping them in
|
|
8
|
+
* `*.scenario.ts` / `*.scenarios.ts` / `*.steps.ts` means a file's name tells
|
|
9
|
+
* you whether it ships or whether it tests, before you open it.
|
|
10
|
+
*
|
|
11
|
+
* An error rather than a warning: the mixing is only cheap to undo while it is
|
|
12
|
+
* one file, and every project that let it slide ended up with scenarios spread
|
|
13
|
+
* across dozens.
|
|
14
|
+
*
|
|
15
|
+
* knowledge: decisions/internals/scenarios-live-in-files-named-for-them.md
|
|
16
|
+
*/
|
|
17
|
+
export declare const runScenarioFileChecks: (root: string, srcDirectories: string[]) => Promise<ValidateFinding[]>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
3
|
+
import { join, relative } from 'node:path';
|
|
4
|
+
import { readTextSafe } from './shared-checks.js';
|
|
5
|
+
/**
|
|
6
|
+
* The declarations that must live in a dedicated scenario file.
|
|
7
|
+
*
|
|
8
|
+
* Matched on the call, not the import, so a re-export or an aliased import does
|
|
9
|
+
* not slip past — and a file that only mentions the name in a comment or a
|
|
10
|
+
* string does not get flagged.
|
|
11
|
+
*/
|
|
12
|
+
const SCENARIO_DECLARATIONS = [
|
|
13
|
+
'pikkuScenario',
|
|
14
|
+
'pikkuFeature',
|
|
15
|
+
'pikkuScenarioStep',
|
|
16
|
+
'pikkuPlatformScenarioStep',
|
|
17
|
+
'pikkuAddonScenarioStep',
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* A file allowed to declare them.
|
|
21
|
+
*
|
|
22
|
+
* Three suffixes rather than one because the split that matters is scenarios
|
|
23
|
+
* apart from application code, not a particular spelling: `.scenario.ts` and
|
|
24
|
+
* `.scenarios.ts` hold the scenarios, `.steps.ts` holds the steps they call.
|
|
25
|
+
*/
|
|
26
|
+
const SCENARIO_FILE = /\.(scenario|scenarios|steps)\.tsx?$/;
|
|
27
|
+
/**
|
|
28
|
+
* The declarations that make up a virtual user, and where they live.
|
|
29
|
+
*
|
|
30
|
+
* There is no `defineVirtualUsers` to look for — that name was retired in
|
|
31
|
+
* favour of `definePersonas`, because a virtual user is derived rather than
|
|
32
|
+
* authored: the function meta becomes its catalogue, the scenario meta becomes
|
|
33
|
+
* its intents, and the declared personas become its identities. So the thing a
|
|
34
|
+
* project actually writes is the persona list, and that is what has to be
|
|
35
|
+
* findable by filename.
|
|
36
|
+
*/
|
|
37
|
+
const VIRTUAL_USER_DECLARATIONS = ['definePersonas', 'runVirtualUser'];
|
|
38
|
+
const VIRTUAL_USER_FILE = /\.(virtual-user|vu)\.tsx?$/;
|
|
39
|
+
const isGenerated = (path) => path.includes('.gen.') || path.includes(`${'/'}.pikku${'/'}`);
|
|
40
|
+
const listSourceFiles = async (dir) => {
|
|
41
|
+
if (!existsSync(dir))
|
|
42
|
+
return [];
|
|
43
|
+
try {
|
|
44
|
+
return (await readdir(dir, { recursive: true }))
|
|
45
|
+
.filter((f) => typeof f === 'string' &&
|
|
46
|
+
(f.endsWith('.ts') || f.endsWith('.tsx')) &&
|
|
47
|
+
!f.includes('node_modules'))
|
|
48
|
+
.map((f) => join(dir, f));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Scenarios, features and steps live in files named for what they are.
|
|
56
|
+
*
|
|
57
|
+
* A `pikkuScenario` declared next to the functions it exercises reads as more
|
|
58
|
+
* of the same file — the wiring, the function and the test of the function all
|
|
59
|
+
* in one scroll — and the scenario is the part that gets lost. Keeping them in
|
|
60
|
+
* `*.scenario.ts` / `*.scenarios.ts` / `*.steps.ts` means a file's name tells
|
|
61
|
+
* you whether it ships or whether it tests, before you open it.
|
|
62
|
+
*
|
|
63
|
+
* An error rather than a warning: the mixing is only cheap to undo while it is
|
|
64
|
+
* one file, and every project that let it slide ended up with scenarios spread
|
|
65
|
+
* across dozens.
|
|
66
|
+
*
|
|
67
|
+
* knowledge: decisions/internals/scenarios-live-in-files-named-for-them.md
|
|
68
|
+
*/
|
|
69
|
+
export const runScenarioFileChecks = async (root, srcDirectories) => {
|
|
70
|
+
const findings = [];
|
|
71
|
+
const dirs = srcDirectories.length
|
|
72
|
+
? srcDirectories
|
|
73
|
+
: [join('packages', 'functions', 'src')];
|
|
74
|
+
const files = (await Promise.all(dirs.map((dir) => listSourceFiles(join(root, dir))))).flat();
|
|
75
|
+
for (const file of files) {
|
|
76
|
+
if (SCENARIO_FILE.test(file) || isGenerated(file))
|
|
77
|
+
continue;
|
|
78
|
+
const text = await readTextSafe(file);
|
|
79
|
+
if (!text)
|
|
80
|
+
continue;
|
|
81
|
+
const used = SCENARIO_DECLARATIONS.filter((name) => new RegExp(`\\b${name}\\s*\\(`).test(text));
|
|
82
|
+
if (used.length === 0)
|
|
83
|
+
continue;
|
|
84
|
+
const rel = relative(root, file);
|
|
85
|
+
const suggested = rel.replace(/\.tsx?$/, (ext) => `.scenarios${ext}`);
|
|
86
|
+
findings.push({
|
|
87
|
+
id: 'scenario-declared-outside-scenario-file',
|
|
88
|
+
severity: 'error',
|
|
89
|
+
message: `${rel} declares ${used.join(', ')} — scenarios, features and steps must live in a *.scenario.ts, *.scenarios.ts or *.steps.ts file, not mixed into application code`,
|
|
90
|
+
path: file,
|
|
91
|
+
fixHint: [
|
|
92
|
+
`Move the ${used.length === 1 ? 'declaration' : 'declarations'} into a dedicated file, e.g.:`,
|
|
93
|
+
` ${suggested}`,
|
|
94
|
+
'Steps belong in *.steps.ts; the scenarios that call them in *.scenarios.ts.',
|
|
95
|
+
'Keeping them apart is what lets a file name say whether it ships or tests.',
|
|
96
|
+
].join('\n'),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
for (const file of files) {
|
|
100
|
+
if (VIRTUAL_USER_FILE.test(file) || isGenerated(file))
|
|
101
|
+
continue;
|
|
102
|
+
const text = await readTextSafe(file);
|
|
103
|
+
if (!text)
|
|
104
|
+
continue;
|
|
105
|
+
const used = VIRTUAL_USER_DECLARATIONS.filter((name) => new RegExp(`\\b${name}\\s*\\(`).test(text));
|
|
106
|
+
if (used.length === 0)
|
|
107
|
+
continue;
|
|
108
|
+
const rel = relative(root, file);
|
|
109
|
+
findings.push({
|
|
110
|
+
id: 'virtual-user-declared-outside-virtual-user-file',
|
|
111
|
+
severity: 'error',
|
|
112
|
+
message: `${rel} declares ${used.join(', ')} — the personas a virtual user plays must live in a *.virtual-user.ts (or *.vu.ts) file, not mixed into application code`,
|
|
113
|
+
path: file,
|
|
114
|
+
fixHint: [
|
|
115
|
+
'Move the declaration into a dedicated file, e.g.:',
|
|
116
|
+
` ${rel.replace(/[^/\\]+$/, 'personas.virtual-user.ts')}`,
|
|
117
|
+
'A virtual user is derived from what the project already generates —',
|
|
118
|
+
'the personas are the only part it authors, so they get their own file.',
|
|
119
|
+
].join('\n'),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return findings;
|
|
123
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type ValidateFinding } from './persona-checks.js';
|
|
2
|
+
export type Finding = ValidateFinding;
|
|
3
|
+
export type SharedPikkuConfig = {
|
|
4
|
+
srcDirectories?: unknown;
|
|
5
|
+
outDir?: unknown;
|
|
6
|
+
environments?: unknown;
|
|
7
|
+
clientFiles?: unknown;
|
|
8
|
+
scaffold?: {
|
|
9
|
+
console?: unknown;
|
|
10
|
+
rpc?: unknown;
|
|
11
|
+
agent?: unknown;
|
|
12
|
+
workflow?: unknown;
|
|
13
|
+
events?: unknown;
|
|
14
|
+
remoteRpc?: unknown;
|
|
15
|
+
};
|
|
16
|
+
db?: {
|
|
17
|
+
engine?: 'sqlite' | 'postgres';
|
|
18
|
+
};
|
|
19
|
+
lint?: {
|
|
20
|
+
customServerBootstrap?: 'off' | 'warn' | 'error';
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export type SharedFnPkg = {
|
|
24
|
+
type?: string;
|
|
25
|
+
imports?: Record<string, unknown>;
|
|
26
|
+
dependencies?: Record<string, string>;
|
|
27
|
+
devDependencies?: Record<string, string>;
|
|
28
|
+
peerDependencies?: Record<string, string>;
|
|
29
|
+
};
|
|
30
|
+
export type SharedCheckResult = {
|
|
31
|
+
findings: Finding[];
|
|
32
|
+
/** Read once here and handed back, so a caller never parses these twice. */
|
|
33
|
+
pikkuConfig: SharedPikkuConfig | null;
|
|
34
|
+
fnPkg: SharedFnPkg | null;
|
|
35
|
+
fnDir: string;
|
|
36
|
+
dbEngine: 'sqlite' | 'postgres';
|
|
37
|
+
migrationsDir: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A malformed config is named, never reported as absent.
|
|
41
|
+
*
|
|
42
|
+
* `pikku fabric validate` used to swallow the parse error and report
|
|
43
|
+
* `pikku-config-missing` for a file that is right there — the one message
|
|
44
|
+
* guaranteed to send you looking in the wrong place.
|
|
45
|
+
*/
|
|
46
|
+
export declare function readJsonSafe<T>(path: string): Promise<T | null>;
|
|
47
|
+
export declare function readTextSafe(path: string): Promise<string | null>;
|
|
48
|
+
/**
|
|
49
|
+
* Every check that is true of any pikku project, regardless of where it deploys.
|
|
50
|
+
*
|
|
51
|
+
* `pikku workspace validate` is exactly this; `pikku fabric validate` is this
|
|
52
|
+
* plus the deploy-shaped checks (themes, frontends, the Cloudflare adapter, the
|
|
53
|
+
* .gitignore contract). Before this module the two were separate walks over the
|
|
54
|
+
* same project that duplicated sixteen findings verbatim, and each carried
|
|
55
|
+
* checks the other lacked for no reason anyone could name.
|
|
56
|
+
*
|
|
57
|
+
* knowledge: decisions/internals/one-project-shape-check-two-validators.md
|
|
58
|
+
*/
|
|
59
|
+
export declare function runSharedProjectChecks(root: string): Promise<SharedCheckResult>;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { runKnowledgeValidate } from '@pikku/knowledge';
|
|
5
|
+
import { runBootstrapChecks } from './bootstrap-checks.js';
|
|
6
|
+
import { runPersonaChecks } from './persona-checks.js';
|
|
7
|
+
import { runScenarioFileChecks } from './scenario-file-checks.js';
|
|
8
|
+
/**
|
|
9
|
+
* A malformed config is named, never reported as absent.
|
|
10
|
+
*
|
|
11
|
+
* `pikku fabric validate` used to swallow the parse error and report
|
|
12
|
+
* `pikku-config-missing` for a file that is right there — the one message
|
|
13
|
+
* guaranteed to send you looking in the wrong place.
|
|
14
|
+
*/
|
|
15
|
+
export async function readJsonSafe(path) {
|
|
16
|
+
if (!existsSync(path))
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(await readFile(path, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23
|
+
throw new Error(`Invalid JSON in ${path}: ${message}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function readTextSafe(path) {
|
|
27
|
+
if (!existsSync(path))
|
|
28
|
+
return null;
|
|
29
|
+
try {
|
|
30
|
+
return await readFile(path, 'utf8');
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Whether this app authenticates HTTP requests at all.
|
|
38
|
+
*
|
|
39
|
+
* Matches any `betterAuth*Session` definition rather than the literal
|
|
40
|
+
* `betterAuthSession`. The stateless variant is a separate definition id that
|
|
41
|
+
* the CLI wires whenever `session.cookieCache` is enabled, which is the
|
|
42
|
+
* configuration Fabric asks for — so an exact-match check saw no auth on
|
|
43
|
+
* precisely the apps most likely to have it, and silently skipped every auth
|
|
44
|
+
* schema check below.
|
|
45
|
+
*/
|
|
46
|
+
async function hasAuthSessionMiddleware(fnDir) {
|
|
47
|
+
const meta = await readJsonSafe(join(fnDir, '.pikku', 'middleware', 'pikku-middleware-groups-meta.gen.json'));
|
|
48
|
+
if (!meta?.instances)
|
|
49
|
+
return false;
|
|
50
|
+
return Object.values(meta.instances).some((instance) => /^betterAuth\w*Session$/.test(instance.definitionId ?? ''));
|
|
51
|
+
}
|
|
52
|
+
function migrationCreatesTable(sql, tableName) {
|
|
53
|
+
const escapedTable = tableName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
54
|
+
const re = new RegExp(`\\bcreate\\s+table\\s+(?:if\\s+not\\s+exists\\s+)?["'\`]?${escapedTable}["'\`]?\\b`, 'i');
|
|
55
|
+
return re.test(sql);
|
|
56
|
+
}
|
|
57
|
+
/** better-auth's core schema — the tables its adapter queries on every request. */
|
|
58
|
+
const BETTER_AUTH_TABLES = ['user', 'session', 'account', 'verification'];
|
|
59
|
+
/**
|
|
60
|
+
* Scaffold flags that gate a generated surface the pikku console calls.
|
|
61
|
+
*
|
|
62
|
+
* `console` gates app introspection, so nothing renders without it;
|
|
63
|
+
* `rpc`/`agent`/`workflow` gate endpoints the console hits directly; `events`
|
|
64
|
+
* gates the realtime channel, which no template ships yet — hence the warning.
|
|
65
|
+
*/
|
|
66
|
+
const REQUIRED_SCAFFOLD = [
|
|
67
|
+
{
|
|
68
|
+
key: 'console',
|
|
69
|
+
severity: 'error',
|
|
70
|
+
surface: 'app introspection (console:getFunctionsMeta and friends) — the console shows no functions',
|
|
71
|
+
},
|
|
72
|
+
{ key: 'rpc', severity: 'error', surface: 'the generic /rpc/:name endpoint' },
|
|
73
|
+
{
|
|
74
|
+
key: 'agent',
|
|
75
|
+
severity: 'error',
|
|
76
|
+
surface: 'the agent endpoints (/rpc/agent/:agentName) — the agent playground 404s',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
key: 'workflow',
|
|
80
|
+
severity: 'error',
|
|
81
|
+
surface: 'the workflow endpoints (/workflow/:workflowName/start) — triggering a workflow 404s',
|
|
82
|
+
},
|
|
83
|
+
{ key: 'events', severity: 'warn', surface: 'the realtime events channel' },
|
|
84
|
+
];
|
|
85
|
+
/**
|
|
86
|
+
* Every check that is true of any pikku project, regardless of where it deploys.
|
|
87
|
+
*
|
|
88
|
+
* `pikku workspace validate` is exactly this; `pikku fabric validate` is this
|
|
89
|
+
* plus the deploy-shaped checks (themes, frontends, the Cloudflare adapter, the
|
|
90
|
+
* .gitignore contract). Before this module the two were separate walks over the
|
|
91
|
+
* same project that duplicated sixteen findings verbatim, and each carried
|
|
92
|
+
* checks the other lacked for no reason anyone could name.
|
|
93
|
+
*
|
|
94
|
+
* knowledge: decisions/internals/one-project-shape-check-two-validators.md
|
|
95
|
+
*/
|
|
96
|
+
export async function runSharedProjectChecks(root) {
|
|
97
|
+
const findings = [];
|
|
98
|
+
const lines = (...parts) => parts.join('\n');
|
|
99
|
+
const e = (id, message, path, fixHint) => {
|
|
100
|
+
findings.push({ id, severity: 'error', message, path, fixHint });
|
|
101
|
+
};
|
|
102
|
+
const w = (id, message, path, fixHint) => {
|
|
103
|
+
findings.push({ id, severity: 'warn', message, path, fixHint });
|
|
104
|
+
};
|
|
105
|
+
const info = (id, message, path, fixHint) => {
|
|
106
|
+
findings.push({ id, severity: 'info', message, path, fixHint });
|
|
107
|
+
};
|
|
108
|
+
// ── pikku.config.json ──────────────────────────────────────────────────
|
|
109
|
+
const pikkuConfigPath = join(root, 'pikku.config.json');
|
|
110
|
+
const pikkuConfig = await readJsonSafe(pikkuConfigPath);
|
|
111
|
+
if (!pikkuConfig) {
|
|
112
|
+
e('pikku-config-missing', 'pikku.config.json not found at project root', pikkuConfigPath, lines('Create `pikku.config.json` at the repo root.', 'Minimum useful shape:', '{', ' "srcDirectories": ["packages/functions/src"],', ' "outDir": "packages/functions/.pikku",', ' "clientFiles": {', ' "rpcMapDeclarationFile": "packages/functions-sdk/src/pikku/rpc-map.gen.d.ts",', ' "reactQueryFile": "packages/functions-sdk/src/pikku/api.gen.ts"', ' }', '}'));
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
if (!pikkuConfig.srcDirectories) {
|
|
116
|
+
e('pikku-config-no-src-dirs', 'pikku.config.json missing "srcDirectories"', pikkuConfigPath, 'Add "srcDirectories": ["packages/functions/src"] to pikku.config.json');
|
|
117
|
+
}
|
|
118
|
+
if (!pikkuConfig.outDir) {
|
|
119
|
+
e('pikku-config-no-out-dir', 'pikku.config.json missing "outDir"', pikkuConfigPath, 'Add "outDir": "packages/functions/.pikku" to pikku.config.json');
|
|
120
|
+
}
|
|
121
|
+
if (!pikkuConfig.clientFiles) {
|
|
122
|
+
info('pikku-config-no-client-files', 'pikku.config.json missing "clientFiles" — generated RPC client files and React Query hooks will not be written', pikkuConfigPath, lines('Add a `clientFiles` block to `pikku.config.json`.', 'Recommended values:', '"clientFiles": {', ' "rpcMapDeclarationFile": "packages/functions-sdk/src/pikku/rpc-map.gen.d.ts",', ' "reactQueryFile": "packages/functions-sdk/src/pikku/api.gen.ts"', '}', 'Those files should live in `packages/functions-sdk/src/pikku/` and are generated by Pikku.'));
|
|
123
|
+
}
|
|
124
|
+
for (const s of REQUIRED_SCAFFOLD) {
|
|
125
|
+
if (pikkuConfig.scaffold?.[s.key])
|
|
126
|
+
continue;
|
|
127
|
+
const report = s.severity === 'error' ? e : w;
|
|
128
|
+
report(`pikku-config-no-scaffold-${s.key}`, `pikku.config.json scaffold is missing "${s.key}" — ${s.surface} is never generated, so the console 404s for it`, pikkuConfigPath, lines(`Add "${s.key}" to the scaffold block in pikku.config.json:`, '"scaffold": {', ' "pikkuDir": "packages/functions/src/scaffold",', ` "${s.key}": true`, '}', 'true means enabled and authenticated; write { "auth": false } to go public.', 'Then re-run codegen (`pikku all`) and restart the dev server.'));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const dbEngine = pikkuConfig?.db?.engine ?? 'sqlite';
|
|
132
|
+
const rootPkgPath = join(root, 'package.json');
|
|
133
|
+
const rootPkg = await readJsonSafe(rootPkgPath);
|
|
134
|
+
if (!rootPkg) {
|
|
135
|
+
e('root-package-missing', 'root package.json not found', rootPkgPath, 'Create a root package.json with workspaces: {"workspaces": ["packages/*", "apps/*"]}');
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
if (!rootPkg.workspaces) {
|
|
139
|
+
w('root-package-no-workspaces', 'root package.json missing "workspaces"', rootPkgPath, 'Add "workspaces": ["packages/*", "apps/*"] to enable yarn workspaces');
|
|
140
|
+
}
|
|
141
|
+
const allDeps = { ...rootPkg.dependencies, ...rootPkg.devDependencies };
|
|
142
|
+
if (!allDeps['@pikku/core']) {
|
|
143
|
+
e('missing-core', '@pikku/core not in dependencies', rootPkgPath, 'Add "@pikku/core" to dependencies');
|
|
144
|
+
}
|
|
145
|
+
// Vendor tgz presence — only file: deps that reference a vendor/ path.
|
|
146
|
+
for (const [pkg, spec] of Object.entries(allDeps)) {
|
|
147
|
+
if (typeof spec !== 'string' || !spec.startsWith('file:'))
|
|
148
|
+
continue;
|
|
149
|
+
const relPath = spec.slice(5);
|
|
150
|
+
if (!relPath.includes('vendor'))
|
|
151
|
+
continue;
|
|
152
|
+
const absPath = join(root, relPath);
|
|
153
|
+
if (!existsSync(absPath)) {
|
|
154
|
+
w(`vendor-missing-${pkg.replace(/[@/]/g, '-')}`, `Vendor file missing for ${pkg}: ${relPath}`, absPath, `Run \`pikku pack\` in the pikku source repo and copy the output to ${relPath}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// ── packages/functions/ ────────────────────────────────────────────────
|
|
159
|
+
const fnDir = join(root, 'packages', 'functions');
|
|
160
|
+
const fnPkgPath = join(fnDir, 'package.json');
|
|
161
|
+
let fnPkg = null;
|
|
162
|
+
// Migrations live at the repo root, which is where `pikku db migrate` reads
|
|
163
|
+
// them from (see local-db.ts). Workspace validate used to look inside
|
|
164
|
+
// packages/functions/db, so every auth schema check it made ran against a
|
|
165
|
+
// directory that does not exist in any project the CLI can migrate.
|
|
166
|
+
const migrationsDir = join(root, 'db', dbEngine);
|
|
167
|
+
if (!existsSync(fnDir)) {
|
|
168
|
+
e('functions-pkg-missing', 'packages/functions/ directory not found', fnDir, 'Create packages/functions/ as a workspace containing src/, tests/, config.ts, and any local db assets you use');
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
fnPkg = await readJsonSafe(fnPkgPath);
|
|
172
|
+
if (!fnPkg) {
|
|
173
|
+
e('functions-package-json-missing', 'packages/functions/package.json not found', fnPkgPath, 'Create packages/functions/package.json and declare the workspace package');
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
if (fnPkg.type !== 'module') {
|
|
177
|
+
w('functions-pkg-no-esm', 'packages/functions/package.json is missing "type": "module"', fnPkgPath, 'Add "type": "module" to packages/functions/package.json');
|
|
178
|
+
}
|
|
179
|
+
// zod v4 must be installed in the functions package: pikku's generated
|
|
180
|
+
// schemas and the auth scaffold (auth-secrets.gen.ts) both `import { z }
|
|
181
|
+
// from 'zod'`, so a missing/old zod fails codegen (PKU489) or type-checks.
|
|
182
|
+
const fnDeps = { ...fnPkg.dependencies, ...fnPkg.devDependencies };
|
|
183
|
+
const zodRange = fnDeps.zod;
|
|
184
|
+
const zodMajor = zodRange?.match(/(\d+)/)?.[1];
|
|
185
|
+
if (!zodRange) {
|
|
186
|
+
e('functions-missing-zod', 'packages/functions/package.json does not declare "zod" — pikku schemas and the generated auth scaffold import it', fnPkgPath, 'Add "zod": "^4" to packages/functions dependencies');
|
|
187
|
+
}
|
|
188
|
+
else if (zodMajor !== '4') {
|
|
189
|
+
e('functions-zod-not-v4', `packages/functions requires zod v4 but found "${zodRange}"`, fnPkgPath, 'Set "zod": "^4" in packages/functions dependencies');
|
|
190
|
+
}
|
|
191
|
+
// @pikku/playwright earns its keep by declaration merging: it is what
|
|
192
|
+
// puts `page`, `context` and `locate` on a step's `PikkuBrowserWire`. A
|
|
193
|
+
// merge only applies if the compiler loads the module, and a tsconfig
|
|
194
|
+
// with an explicit `types` array loads nothing it is not told to. A step
|
|
195
|
+
// that reaches for `wire.page` without importing the package anywhere in
|
|
196
|
+
// the program then fails to type-check with a message about a property
|
|
197
|
+
// that does not exist — which reads like the step is wrong, not the
|
|
198
|
+
// tsconfig. Listing the package in `types` loads it either way.
|
|
199
|
+
if (fnDeps['@pikku/playwright']) {
|
|
200
|
+
const tsconfigPath = join(fnDir, 'tsconfig.json');
|
|
201
|
+
const tsconfig = await readJsonSafe(tsconfigPath);
|
|
202
|
+
const types = tsconfig?.compilerOptions?.types;
|
|
203
|
+
if (types && !types.includes('@pikku/playwright')) {
|
|
204
|
+
w('playwright-types-not-loaded', 'packages/functions depends on @pikku/playwright but does not list it in "compilerOptions.types" — the browser bindings it adds to a step (page, context, locate) will not be typed', tsconfigPath, lines('Add it to the types array in packages/functions/tsconfig.json:', ` "types": ${JSON.stringify([...types, '@pikku/playwright'])}`, 'The package declaration-merges PikkuBrowserWire, and an explicit', '`types` array means the compiler only loads what it is told to.'));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const servicesPath = join(fnDir, 'src', 'services.ts');
|
|
209
|
+
if (!(await readTextSafe(servicesPath))) {
|
|
210
|
+
w('services-missing', 'packages/functions/src/services.ts not found', servicesPath, 'Create services.ts and export your service factory for the workspace');
|
|
211
|
+
}
|
|
212
|
+
// ── auth schema ──────────────────────────────────────────────────────
|
|
213
|
+
// The tables are better-auth's own (`user`, `session`, `account`,
|
|
214
|
+
// `verification`) because those are what the adapter queries. This check
|
|
215
|
+
// used to look for `app_user` and `auth_verification_token`, two names that
|
|
216
|
+
// appear nowhere else in pikku or in any template — it never fired against a
|
|
217
|
+
// real app, and would have been wrong for every one of them if it had.
|
|
218
|
+
const authEnabled = await hasAuthSessionMiddleware(fnDir);
|
|
219
|
+
const configText = await readTextSafe(join(fnDir, 'src', 'config.ts'));
|
|
220
|
+
const missingAuthTables = [];
|
|
221
|
+
if (existsSync(migrationsDir)) {
|
|
222
|
+
try {
|
|
223
|
+
const files = (await readdir(migrationsDir))
|
|
224
|
+
.filter((f) => f.endsWith('.sql'))
|
|
225
|
+
.sort();
|
|
226
|
+
const nums = [];
|
|
227
|
+
for (const f of files) {
|
|
228
|
+
const m = f.match(/^(\d+)/);
|
|
229
|
+
if (m)
|
|
230
|
+
nums.push(parseInt(m[1], 10));
|
|
231
|
+
}
|
|
232
|
+
for (let idx = 1; idx < nums.length; idx++) {
|
|
233
|
+
if (nums[idx] !== nums[idx - 1] + 1) {
|
|
234
|
+
const missing = `${nums[idx - 1] + 1}..${nums[idx] - 1}`;
|
|
235
|
+
e('migration-gap', `Migration numbering gap: IDs ${missing} are missing`, migrationsDir, 'Migrations must be consecutive. Add the missing .sql file or renumber if not yet applied.');
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (authEnabled) {
|
|
240
|
+
const allSql = (await Promise.all(files.map((f) => readTextSafe(join(migrationsDir, f)))))
|
|
241
|
+
.filter((sql) => Boolean(sql))
|
|
242
|
+
.join('\n');
|
|
243
|
+
for (const table of BETTER_AUTH_TABLES) {
|
|
244
|
+
if (!migrationCreatesTable(allSql, table)) {
|
|
245
|
+
missingAuthTables.push(table);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// readdir failure — skip
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else if (authEnabled) {
|
|
255
|
+
missingAuthTables.push(...BETTER_AUTH_TABLES);
|
|
256
|
+
}
|
|
257
|
+
// `pikku db migrate` falls back to .pikku-runtime/dev.db whenever db/sqlite
|
|
258
|
+
// exists (see db-shared.ts), so sqliteDb in createConfig is only required
|
|
259
|
+
// when the project has no conventional db assets at all. Demanding it
|
|
260
|
+
// unconditionally failed every project using the layout the CLI expects.
|
|
261
|
+
const hasConventionalDbAssets = existsSync(join(root, 'db', 'sqlite')) ||
|
|
262
|
+
existsSync(join(root, 'db', 'postgres'));
|
|
263
|
+
if (authEnabled &&
|
|
264
|
+
dbEngine !== 'postgres' &&
|
|
265
|
+
!/sqliteDb/.test(configText ?? '') &&
|
|
266
|
+
!hasConventionalDbAssets) {
|
|
267
|
+
e('auth-dev-db-missing', 'Auth middleware is registered, but there is no db/sqlite directory and createConfig does not set sqliteDb — `pikku db migrate` has no database to create', join(fnDir, 'src', 'config.ts'), 'Add db/sqlite/ with numbered migrations, or set sqliteDb in createConfig');
|
|
268
|
+
}
|
|
269
|
+
if (missingAuthTables.length > 0) {
|
|
270
|
+
e('auth-schema-missing-tables', `Auth middleware is registered, but no SQL migration creates: ${missingAuthTables.join(', ')} — better-auth queries these tables on every request`, migrationsDir, lines(`Add a migration under db/${dbEngine}/ that creates the better-auth core schema:`, ' user, session, account, verification', 'The pikku-better-auth skill generates one for the dialect you are on.'));
|
|
271
|
+
}
|
|
272
|
+
// ── generated types must stay generated ──────────────────────────────
|
|
273
|
+
const dbTypesPath = join(fnDir, 'src', 'types', 'db.types.ts');
|
|
274
|
+
const dbTypesText = await readTextSafe(dbTypesPath);
|
|
275
|
+
if (dbTypesText) {
|
|
276
|
+
const isReexport = dbTypesText.includes('.pikku/db/schema') ||
|
|
277
|
+
dbTypesText.includes('.pikku\\db\\schema');
|
|
278
|
+
const hasInlineTypes = /(?:^|\n)\s*(?:export\s+)?(?:interface\s+\w|type\s+\w+\s*=)/.test(dbTypesText);
|
|
279
|
+
if (hasInlineTypes && !isReexport) {
|
|
280
|
+
w('db-types-hand-edited', 'src/types/db.types.ts contains inline type definitions — it should only re-export from .pikku', dbTypesPath, "Replace the file with a single line: export type { DB } from '../../.pikku/db/schema.gen.js' then regenerate");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// ── layout ───────────────────────────────────────────────────────────
|
|
284
|
+
if (!existsSync(join(fnDir, 'src', 'functions'))) {
|
|
285
|
+
info('functions-dir-missing', 'packages/functions/src/functions/ not found', join(fnDir, 'src', 'functions'), 'Create src/functions/ to hold pikkuSessionlessFunc definitions (one function per file)');
|
|
286
|
+
}
|
|
287
|
+
if (!existsSync(join(fnDir, 'src', 'wirings'))) {
|
|
288
|
+
info('wirings-dir-missing', 'packages/functions/src/wirings/ not found', join(fnDir, 'src', 'wirings'), 'Create src/wirings/ for transport bindings such as *.http.ts or *.queue.ts');
|
|
289
|
+
}
|
|
290
|
+
if (!existsSync(join(fnDir, 'src', 'config.ts'))) {
|
|
291
|
+
info('config-missing', 'packages/functions/src/config.ts not found', join(fnDir, 'src', 'config.ts'), 'Create src/config.ts and export your workspace config factory');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const sdkDir = join(root, 'packages', 'functions-sdk');
|
|
295
|
+
if (!existsSync(sdkDir)) {
|
|
296
|
+
info('functions-sdk-missing', 'packages/functions-sdk/ not found — generated RPC client and React Query hooks will not be available', sdkDir, 'Create packages/functions-sdk/ as a workspace with src/pikku/ as the generated output root; point clientFiles.rpcMapDeclarationFile and clientFiles.reactQueryFile there');
|
|
297
|
+
}
|
|
298
|
+
// ── server bootstrap ───────────────────────────────────────────────────
|
|
299
|
+
findings.push(...(await runBootstrapChecks(root, rootPkg, pikkuConfig)));
|
|
300
|
+
// ── personas, scenario files and the knowledge base ────────────────────
|
|
301
|
+
findings.push(...(await runPersonaChecks(root, pikkuConfig)));
|
|
302
|
+
const srcDirectories = Array.isArray(pikkuConfig?.srcDirectories)
|
|
303
|
+
? pikkuConfig.srcDirectories.filter((d) => typeof d === 'string')
|
|
304
|
+
: [];
|
|
305
|
+
findings.push(...(await runScenarioFileChecks(root, srcDirectories)));
|
|
306
|
+
const knowledge = await runKnowledgeValidate(root, join(root, typeof pikkuConfig?.outDir === 'string' ? pikkuConfig.outDir : '.pikku'));
|
|
307
|
+
for (const finding of knowledge.findings) {
|
|
308
|
+
findings.push({ ...finding, path: join(root, finding.path) });
|
|
309
|
+
}
|
|
310
|
+
return { findings, pikkuConfig, fnPkg, fnDir, dbEngine, migrationsDir };
|
|
311
|
+
}
|