@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, readdirSync, } from 'node:fs';
|
|
2
2
|
import { resolve, isAbsolute, relative, dirname, join } from 'node:path';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
4
5
|
import { runInNewContext } from 'node:vm';
|
|
5
6
|
import { transformSync } from 'esbuild';
|
|
6
7
|
import { CamelCasePlugin, Kysely, PostgresDialect } from 'kysely';
|
|
@@ -15,7 +16,7 @@ import { SqliteMigrationExecutor } from './sqlite/sqlite-migrator.js';
|
|
|
15
16
|
import { SqliteIntrospector } from './sqlite/sqlite-introspector.js';
|
|
16
17
|
import { createSqliteKysely } from './sqlite/sqlite-kysely.js';
|
|
17
18
|
import { loadSqliteRuntime } from './sqlite/sqlite-runtime.js';
|
|
18
|
-
import {
|
|
19
|
+
import { devSeed as runDevSeed } from './sqlite/dev-seed.js';
|
|
19
20
|
import { PostgresMigrationExecutor } from './postgres/postgres-migrator.js';
|
|
20
21
|
import { createPGliteKysely } from './postgres/pglite-kysely.js';
|
|
21
22
|
import { PostgresIntrospector } from './postgres/postgres-introspector.js';
|
|
@@ -37,7 +38,11 @@ export function parseDatabaseUrl(url) {
|
|
|
37
38
|
* Resolve a UserConfigShape into an absolute-path descriptor.
|
|
38
39
|
* Returns null when neither sqliteDb nor postgresUrl is configured.
|
|
39
40
|
*/
|
|
40
|
-
export function resolveDb(userConfig, rootDir, outDir, runtimeDir) {
|
|
41
|
+
export function resolveDb(userConfig, rootDir, outDir, runtimeDir, dbConfig) {
|
|
42
|
+
// The parameter took a bare schema string before `defaultSchema` existed;
|
|
43
|
+
// both forms are accepted so callers outside this repo keep working.
|
|
44
|
+
const schema = typeof dbConfig === 'string' ? dbConfig : dbConfig?.schema;
|
|
45
|
+
const defaultSchema = typeof dbConfig === 'string' ? undefined : dbConfig?.defaultSchema;
|
|
41
46
|
const resolvedRuntimeDir = runtimeDir
|
|
42
47
|
? resolveAgainst(rootDir, runtimeDir)
|
|
43
48
|
: join(rootDir, '.pikku-runtime');
|
|
@@ -58,17 +63,21 @@ export function resolveDb(userConfig, rootDir, outDir, runtimeDir) {
|
|
|
58
63
|
classificationsGenJsonFile: join(rootDir, 'db', 'annotations.gen.json'),
|
|
59
64
|
zodFile: join(outDir, 'db', 'zod.gen.ts'),
|
|
60
65
|
camelCase: true,
|
|
66
|
+
defaultSchema,
|
|
61
67
|
});
|
|
62
68
|
if (userConfig.postgresUrl && userConfig.sqliteDb) {
|
|
63
69
|
throw new Error('Both postgresUrl and sqliteDb are set. Configure exactly one database dialect.');
|
|
64
70
|
}
|
|
71
|
+
const pgliteExtensions = userConfig.pgliteExtensions ?? [];
|
|
65
72
|
if (userConfig.postgresUrl) {
|
|
66
73
|
return {
|
|
67
74
|
dialect: 'postgres',
|
|
68
75
|
mode: 'url',
|
|
69
76
|
connectionString: userConfig.postgresUrl,
|
|
70
77
|
runtimeDir: resolvedRuntimeDir,
|
|
71
|
-
|
|
78
|
+
devSeedFile: resolveAgainst(rootDir, 'db/postgres-dev-seed.sql'),
|
|
79
|
+
pgliteExtensions,
|
|
80
|
+
schema,
|
|
72
81
|
...base('db/postgres'),
|
|
73
82
|
};
|
|
74
83
|
}
|
|
@@ -77,11 +86,17 @@ export function resolveDb(userConfig, rootDir, outDir, runtimeDir) {
|
|
|
77
86
|
? '.pikku-runtime/dev.db'
|
|
78
87
|
: undefined);
|
|
79
88
|
if (sqliteDb) {
|
|
89
|
+
if (schema) {
|
|
90
|
+
throw new Error(`db.schema is set to '${schema}', but the resolved database is sqlite. ` +
|
|
91
|
+
'sqlite has one schema to be in, and its REFERENCES clause takes a bare ' +
|
|
92
|
+
'table name, so qualified DDL does not compile there. Remove db.schema, ' +
|
|
93
|
+
'or configure postgres.');
|
|
94
|
+
}
|
|
80
95
|
return {
|
|
81
96
|
dialect: 'sqlite',
|
|
82
97
|
dbFile: resolveAgainst(rootDir, sqliteDb),
|
|
83
98
|
runtimeDir: resolvedRuntimeDir,
|
|
84
|
-
|
|
99
|
+
devSeedFile: resolveAgainst(rootDir, 'db/sqlite-dev-seed.sql'),
|
|
85
100
|
...base('db/sqlite'),
|
|
86
101
|
};
|
|
87
102
|
}
|
|
@@ -91,7 +106,9 @@ export function resolveDb(userConfig, rootDir, outDir, runtimeDir) {
|
|
|
91
106
|
mode: 'pglite',
|
|
92
107
|
pgliteDir: join(resolvedRuntimeDir, 'dev-postgres'),
|
|
93
108
|
runtimeDir: resolvedRuntimeDir,
|
|
94
|
-
|
|
109
|
+
devSeedFile: resolveAgainst(rootDir, 'db/postgres-dev-seed.sql'),
|
|
110
|
+
pgliteExtensions,
|
|
111
|
+
schema,
|
|
95
112
|
...base('db/postgres'),
|
|
96
113
|
};
|
|
97
114
|
}
|
|
@@ -120,7 +137,7 @@ async function createPostgresClient(resolved) {
|
|
|
120
137
|
throw new Error('PGlite Postgres resolution is missing pgliteDir.');
|
|
121
138
|
}
|
|
122
139
|
mkdirSync(dirname(resolved.pgliteDir), { recursive: true });
|
|
123
|
-
const db = await createEmbeddedPostgres(resolved.pgliteDir);
|
|
140
|
+
const db = await createEmbeddedPostgres(resolved, resolved.pgliteDir);
|
|
124
141
|
return pgliteAsClient(db);
|
|
125
142
|
}
|
|
126
143
|
let embeddedPostgresWasm;
|
|
@@ -157,21 +174,107 @@ async function loadEmbeddedPostgresWasm() {
|
|
|
157
174
|
return undefined;
|
|
158
175
|
}
|
|
159
176
|
}
|
|
160
|
-
|
|
177
|
+
const isPGliteExtension = (value) => typeof value === 'object' &&
|
|
178
|
+
value !== null &&
|
|
179
|
+
typeof value.setup === 'function';
|
|
180
|
+
/**
|
|
181
|
+
* Turn a declared extension specifier into the module that publishes it.
|
|
182
|
+
*
|
|
183
|
+
* A bare name is one of PGlite's own contrib extensions (`hstore`, `citext`,
|
|
184
|
+
* `uuid_ossp`), which ship with the CLI's copy of PGlite and need nothing
|
|
185
|
+
* installed. Anything else is a package the project depends on — pgvector, say,
|
|
186
|
+
* as `@electric-sql/pglite-pgvector` — so it is resolved from the project first
|
|
187
|
+
* and only then from the CLI, since the project is where a user installs it.
|
|
188
|
+
*/
|
|
189
|
+
async function importPGliteExtensionModule(spec, rootDir) {
|
|
190
|
+
const id = spec.includes('/') || spec.startsWith('@')
|
|
191
|
+
? spec
|
|
192
|
+
: `@electric-sql/pglite/contrib/${spec}`;
|
|
193
|
+
for (const from of [join(rootDir, 'package.json'), import.meta.url]) {
|
|
194
|
+
let resolvedPath;
|
|
195
|
+
try {
|
|
196
|
+
resolvedPath = createRequire(from).resolve(id);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
return (await import(pathToFileURL(resolvedPath).href));
|
|
202
|
+
}
|
|
203
|
+
throw new Error(`The PGlite extension '${spec}' could not be resolved as '${id}', ` +
|
|
204
|
+
`from either the project (${rootDir}) or the pikku CLI. ` +
|
|
205
|
+
`Install it in your project, or name one of PGlite's bundled contrib ` +
|
|
206
|
+
`extensions (hstore, citext, uuid_ossp, …), which need no install.`);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Load every extension the project declares, keyed the way PGlite wants them.
|
|
210
|
+
*
|
|
211
|
+
* The key is the export name (`vector`, `hstore`), which is also the name the
|
|
212
|
+
* SQL uses, so `CREATE EXTENSION vector` finds what a declaration of
|
|
213
|
+
* `@electric-sql/pglite-pgvector` loaded. A module exporting several is fine —
|
|
214
|
+
* all of them are taken — and a `default` export is used only when it is the
|
|
215
|
+
* only extension there, so a package that default-exports the same object it
|
|
216
|
+
* also names is not registered twice.
|
|
217
|
+
*/
|
|
218
|
+
export async function loadPGliteExtensions(rootDir, specs) {
|
|
219
|
+
const extensions = {};
|
|
220
|
+
for (const spec of specs) {
|
|
221
|
+
const module = await importPGliteExtensionModule(spec, rootDir);
|
|
222
|
+
const found = Object.entries(module).filter(([, value]) => isPGliteExtension(value));
|
|
223
|
+
const named = found.filter(([name]) => name !== 'default');
|
|
224
|
+
const chosen = named.length > 0 ? named : found;
|
|
225
|
+
if (chosen.length === 0) {
|
|
226
|
+
throw new Error(`'${spec}' exports no PGlite extension. A PGlite extension is an ` +
|
|
227
|
+
`object with a 'setup' function — check that the package is one, ` +
|
|
228
|
+
`and that its version matches the PGlite the CLI runs.`);
|
|
229
|
+
}
|
|
230
|
+
for (const [name, extension] of chosen) {
|
|
231
|
+
extensions[name === 'default' ? (extension.name ?? spec) : name] =
|
|
232
|
+
extension;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return extensions;
|
|
236
|
+
}
|
|
237
|
+
async function createEmbeddedPostgres(context, dataDir) {
|
|
161
238
|
embeddedPostgresWasm ??= loadEmbeddedPostgresWasm();
|
|
162
|
-
const [{ PGlite }, { pgcrypto }, wasm] = await Promise.all([
|
|
239
|
+
const [{ PGlite }, { pgcrypto }, wasm, declared] = await Promise.all([
|
|
163
240
|
import('@electric-sql/pglite'),
|
|
164
241
|
import('@electric-sql/pglite/contrib/pgcrypto'),
|
|
165
242
|
embeddedPostgresWasm,
|
|
243
|
+
loadPGliteExtensions(context.rootDir, context.pgliteExtensions),
|
|
166
244
|
]);
|
|
167
245
|
return new PGlite({
|
|
168
246
|
...(dataDir ? { dataDir } : {}),
|
|
169
247
|
...wasm,
|
|
170
248
|
extensions: {
|
|
171
249
|
pgcrypto,
|
|
250
|
+
...declared,
|
|
172
251
|
},
|
|
173
252
|
});
|
|
174
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* PGlite reports an extension it was never given as a plain
|
|
256
|
+
* `extension "x" is not available`, which reads as though the SQL is at fault.
|
|
257
|
+
* It is not: the database is real Postgres, the migration is fine, and the one
|
|
258
|
+
* thing missing is the declaration that would have loaded the extension into
|
|
259
|
+
* this particular instance. Say so, since nothing else in the message points
|
|
260
|
+
* anywhere near the fix.
|
|
261
|
+
*/
|
|
262
|
+
function explainMissingExtension(error, declared) {
|
|
263
|
+
const message = error instanceof Error ? error.message : '';
|
|
264
|
+
const match = /extension "([^"]+)" is not available/.exec(message);
|
|
265
|
+
if (!match)
|
|
266
|
+
return error;
|
|
267
|
+
const name = match[1];
|
|
268
|
+
return new Error(`The embedded PGlite database has no '${name}' extension. ` +
|
|
269
|
+
`The CLI migrates a PGlite shadow database to type and diff your schema, ` +
|
|
270
|
+
`so every extension your migrations use has to be declared as ` +
|
|
271
|
+
`pgliteExtensions in createConfig — e.g. pgliteExtensions: ['${name}'] ` +
|
|
272
|
+
`for a PGlite contrib extension, or the package that publishes it ` +
|
|
273
|
+
`('@electric-sql/pglite-pgvector' for pgvector).` +
|
|
274
|
+
(declared.length > 0
|
|
275
|
+
? ` Currently declared: ${declared.join(', ')}.`
|
|
276
|
+
: ''), { cause: error });
|
|
277
|
+
}
|
|
175
278
|
function pgliteAsClient(db) {
|
|
176
279
|
return {
|
|
177
280
|
query: (sql, params) => db.query(sql, params),
|
|
@@ -189,6 +292,14 @@ async function withPostgresClient(resolved, run) {
|
|
|
189
292
|
try {
|
|
190
293
|
return await run(client);
|
|
191
294
|
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
// Only the embedded database's missing extensions are the declaration's
|
|
297
|
+
// doing. On a real server it is the server that lacks the extension, and
|
|
298
|
+
// pointing at the CLI's config would send the reader the wrong way.
|
|
299
|
+
throw resolved.mode === 'pglite'
|
|
300
|
+
? explainMissingExtension(error, resolved.pgliteExtensions)
|
|
301
|
+
: error;
|
|
302
|
+
}
|
|
192
303
|
finally {
|
|
193
304
|
await client.end();
|
|
194
305
|
}
|
|
@@ -226,6 +337,7 @@ export async function migrateAndCodegen(resolved, options = {}) {
|
|
|
226
337
|
schemaJsonFile: resolved.schemaJsonFile,
|
|
227
338
|
enumsFile: resolved.enumsFile,
|
|
228
339
|
camelCase: resolved.camelCase,
|
|
340
|
+
defaultSchema: resolved.defaultSchema,
|
|
229
341
|
rootDir: resolved.rootDir,
|
|
230
342
|
dialect: 'sqlite',
|
|
231
343
|
migrationsDir: resolved.migrationsDir,
|
|
@@ -237,7 +349,7 @@ export async function migrateAndCodegen(resolved, options = {}) {
|
|
|
237
349
|
}
|
|
238
350
|
else {
|
|
239
351
|
const withClient = options.scratch
|
|
240
|
-
? (
|
|
352
|
+
? (r, run) => withScratchPostgresDatabase(r, run)
|
|
241
353
|
: withPostgresClient;
|
|
242
354
|
await withClient(resolved, async (client) => {
|
|
243
355
|
const introspector = new PostgresIntrospector(client);
|
|
@@ -253,6 +365,7 @@ export async function migrateAndCodegen(resolved, options = {}) {
|
|
|
253
365
|
schemaJsonFile: resolved.schemaJsonFile,
|
|
254
366
|
enumsFile: resolved.enumsFile,
|
|
255
367
|
camelCase: resolved.camelCase,
|
|
368
|
+
defaultSchema: resolved.defaultSchema,
|
|
256
369
|
rootDir: resolved.rootDir,
|
|
257
370
|
dialect: 'postgres',
|
|
258
371
|
migrationsDir: resolved.migrationsDir,
|
|
@@ -282,21 +395,32 @@ export async function migrateAndCodegen(resolved, options = {}) {
|
|
|
282
395
|
};
|
|
283
396
|
}
|
|
284
397
|
// ─── SQLite-only operations ───────────────────────────────────────────────────
|
|
285
|
-
|
|
398
|
+
/**
|
|
399
|
+
* Apply the dev seed file. Only {@link reset} calls this, immediately after
|
|
400
|
+
* migrating a database it has just wiped, which is what lets a seed file be
|
|
401
|
+
* plain `INSERT`s rather than idempotent ones.
|
|
402
|
+
*
|
|
403
|
+
* `reset` refuses production before it gets here, so this guard is unreachable
|
|
404
|
+
* through the CLI. It stays because the export is reachable without it.
|
|
405
|
+
*/
|
|
406
|
+
export async function devSeed(resolved) {
|
|
407
|
+
if (process.env.NODE_ENV === 'production') {
|
|
408
|
+
throw new Error(`pikku dev seed refused: NODE_ENV=production. Seed data is test data — it only runs in dev.`);
|
|
409
|
+
}
|
|
286
410
|
if (resolved.dialect === 'sqlite') {
|
|
287
411
|
const runtime = await loadSqliteRuntime();
|
|
288
412
|
const db = runtime.open(resolved.dbFile);
|
|
289
413
|
try {
|
|
290
|
-
return
|
|
414
|
+
return runDevSeed(db, resolved.devSeedFile);
|
|
291
415
|
}
|
|
292
416
|
finally {
|
|
293
417
|
db.close();
|
|
294
418
|
}
|
|
295
419
|
}
|
|
296
|
-
if (!existsSync(resolved.
|
|
420
|
+
if (!existsSync(resolved.devSeedFile)) {
|
|
297
421
|
return { applied: false, bytes: 0 };
|
|
298
422
|
}
|
|
299
|
-
const sql = readFileSync(resolved.
|
|
423
|
+
const sql = readFileSync(resolved.devSeedFile, 'utf8');
|
|
300
424
|
if (sql.trim().length === 0) {
|
|
301
425
|
return { applied: false, bytes: 0 };
|
|
302
426
|
}
|
|
@@ -346,8 +470,7 @@ export async function reset(resolved, rootDir) {
|
|
|
346
470
|
AND schema_name NOT LIKE 'pg_temp_%'
|
|
347
471
|
`);
|
|
348
472
|
for (const { schema_name: schemaName } of result.rows) {
|
|
349
|
-
|
|
350
|
-
await client.query(`DROP SCHEMA IF EXISTS ${quoted} CASCADE`);
|
|
473
|
+
await client.query(`DROP SCHEMA IF EXISTS ${quoteIdentifier(schemaName)} CASCADE`);
|
|
351
474
|
}
|
|
352
475
|
await client.query('CREATE SCHEMA IF NOT EXISTS public');
|
|
353
476
|
});
|
|
@@ -474,7 +597,7 @@ export async function createKysely(resolved) {
|
|
|
474
597
|
}
|
|
475
598
|
mkdirSync(dirname(resolved.pgliteDir), { recursive: true });
|
|
476
599
|
return createPGliteKysely({
|
|
477
|
-
db: await createEmbeddedPostgres(resolved.pgliteDir),
|
|
600
|
+
db: await createEmbeddedPostgres(resolved, resolved.pgliteDir),
|
|
478
601
|
camelCase: resolved.camelCase,
|
|
479
602
|
plugins,
|
|
480
603
|
});
|
|
@@ -486,11 +609,22 @@ async function introspectorToMap(intro) {
|
|
|
486
609
|
}
|
|
487
610
|
return map;
|
|
488
611
|
}
|
|
489
|
-
|
|
612
|
+
/**
|
|
613
|
+
* The tables and columns `desired` has that `actual` does not.
|
|
614
|
+
*
|
|
615
|
+
* `schema` is the one the desired bare names belong in, when the caller knows.
|
|
616
|
+
* Given it, only that schema's copy of a table counts: a `workflow_step` in
|
|
617
|
+
* `public` is a different table from the `app.workflow_step` a source with
|
|
618
|
+
* `db.schema: "app"` wants, and letting it match would report the source
|
|
619
|
+
* satisfied by a table its SQL never creates.
|
|
620
|
+
*/
|
|
621
|
+
function diffSchemas(desired, actual, schema) {
|
|
490
622
|
const missingTables = [];
|
|
491
623
|
const missingColumns = [];
|
|
492
624
|
for (const [table, cols] of desired) {
|
|
493
|
-
const actualCols =
|
|
625
|
+
const actualCols = schema
|
|
626
|
+
? actual.get(qualifiedTableKey(schema, table))
|
|
627
|
+
: (actual.get(table) ?? schemaQualifiedMatch(actual, table));
|
|
494
628
|
if (!actualCols) {
|
|
495
629
|
missingTables.push(table);
|
|
496
630
|
continue;
|
|
@@ -501,6 +635,19 @@ function diffSchemas(desired, actual) {
|
|
|
501
635
|
}
|
|
502
636
|
return { missingTables, missingColumns };
|
|
503
637
|
}
|
|
638
|
+
/**
|
|
639
|
+
* How an introspected schema map keys a table in a given schema.
|
|
640
|
+
*
|
|
641
|
+
* Introspection elides `public`, so the key for a table there is the bare name.
|
|
642
|
+
* Anything else carries its qualifier.
|
|
643
|
+
*/
|
|
644
|
+
function qualifiedTableKey(schema, table) {
|
|
645
|
+
return schema === 'public' ? table : `${schema}.${table}`;
|
|
646
|
+
}
|
|
647
|
+
/** A PostgreSQL identifier, quoted so its casing survives the parser. */
|
|
648
|
+
function quoteIdentifier(name) {
|
|
649
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
650
|
+
}
|
|
504
651
|
/**
|
|
505
652
|
* The one table in `actual` whose name matches `table` once its schema is
|
|
506
653
|
* dropped, if there is exactly one.
|
|
@@ -530,11 +677,14 @@ function isPostgresAuthDatabase(options) {
|
|
|
530
677
|
// target server. Creating a real database via `CREATE DATABASE` would require
|
|
531
678
|
// elevated privileges that application roles (correctly) don't have, which made
|
|
532
679
|
// `pikku db migrate` fail against managed/locked-down Postgres (error 42501).
|
|
533
|
-
async function withScratchPostgresDatabase(run) {
|
|
534
|
-
const scratchDb = await createEmbeddedPostgres();
|
|
680
|
+
async function withScratchPostgresDatabase(context, run) {
|
|
681
|
+
const scratchDb = await createEmbeddedPostgres(context);
|
|
535
682
|
try {
|
|
536
683
|
return await run(pgliteAsClient(scratchDb));
|
|
537
684
|
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
throw explainMissingExtension(error, context.pgliteExtensions);
|
|
687
|
+
}
|
|
538
688
|
finally {
|
|
539
689
|
if (!scratchDb.closed) {
|
|
540
690
|
await scratchDb.close();
|
|
@@ -551,8 +701,8 @@ async function postgresDatabaseToMap(client) {
|
|
|
551
701
|
await intro.close();
|
|
552
702
|
}
|
|
553
703
|
}
|
|
554
|
-
async function desiredPostgresAuthSchema(rootDir, srcDirectories, logger) {
|
|
555
|
-
return withScratchPostgresDatabase(async (scratchDb) => {
|
|
704
|
+
async function desiredPostgresAuthSchema(context, rootDir, srcDirectories, logger) {
|
|
705
|
+
return withScratchPostgresDatabase(context, async (scratchDb) => {
|
|
556
706
|
// The scratch DB is always an embedded PGlite instance (see
|
|
557
707
|
// withScratchPostgresDatabase), so drive Better Auth's migration codegen
|
|
558
708
|
// through the PGlite-backed Kysely regardless of how the app DB is
|
|
@@ -598,7 +748,7 @@ export async function desiredAuthSchema(resolved, rootDir, srcDirectories, logge
|
|
|
598
748
|
if (resolved.dialect !== 'postgres') {
|
|
599
749
|
throw new Error('Better Auth database.type is postgres, but the resolved app database is not postgres.');
|
|
600
750
|
}
|
|
601
|
-
return desiredPostgresAuthSchema(rootDir, srcDirectories, logger);
|
|
751
|
+
return desiredPostgresAuthSchema(resolved, rootDir, srcDirectories, logger);
|
|
602
752
|
}
|
|
603
753
|
const { runMigrations, compileMigrations } = await getAuthMigrations(options);
|
|
604
754
|
await runMigrations();
|
|
@@ -685,7 +835,7 @@ export async function desiredRuntimeSchema(resolved, rootDir, srcDirectories, lo
|
|
|
685
835
|
db.close();
|
|
686
836
|
}
|
|
687
837
|
}
|
|
688
|
-
return withScratchPostgresDatabase(async (db) => {
|
|
838
|
+
return withScratchPostgresDatabase(resolved, async (db) => {
|
|
689
839
|
const kysely = createPGliteKysely({
|
|
690
840
|
db: db.__pglite,
|
|
691
841
|
camelCase: true,
|
|
@@ -694,7 +844,7 @@ export async function desiredRuntimeSchema(resolved, rootDir, srcDirectories, lo
|
|
|
694
844
|
const { tables, schemas, types } = await collect(kysely, () => postgresDatabaseToMap(db));
|
|
695
845
|
return {
|
|
696
846
|
tables,
|
|
697
|
-
sql: compilePikkuSchemas(kysely, schemas, types),
|
|
847
|
+
sql: compilePikkuSchemas(kysely, schemas, types, resolved.schema),
|
|
698
848
|
skipped,
|
|
699
849
|
};
|
|
700
850
|
}
|
|
@@ -736,8 +886,8 @@ async function coveredSqliteSchema(migrationsDir) {
|
|
|
736
886
|
db.close();
|
|
737
887
|
}
|
|
738
888
|
}
|
|
739
|
-
async function coveredPostgresSchema(migrationsDir) {
|
|
740
|
-
return withScratchPostgresDatabase(async (client) => {
|
|
889
|
+
async function coveredPostgresSchema(migrationsDir, context) {
|
|
890
|
+
return withScratchPostgresDatabase(context, async (client) => {
|
|
741
891
|
await migrate(new PostgresMigrationExecutor(client), migrationsDir);
|
|
742
892
|
return postgresDatabaseToMap(client);
|
|
743
893
|
});
|
|
@@ -764,7 +914,7 @@ async function coveredPostgresSchema(migrationsDir) {
|
|
|
764
914
|
export async function computeSchemaDrift(resolved, rootDir, srcDirectories, logger) {
|
|
765
915
|
const covered = resolved.dialect === 'sqlite'
|
|
766
916
|
? await coveredSqliteSchema(resolved.migrationsDir)
|
|
767
|
-
: await coveredPostgresSchema(resolved.migrationsDir);
|
|
917
|
+
: await coveredPostgresSchema(resolved.migrationsDir, resolved);
|
|
768
918
|
const actual = await introspectSchema(resolved);
|
|
769
919
|
const { missingTables, missingColumns } = diffSchemas(covered, actual);
|
|
770
920
|
// Compare on the full name. A migration that names no schema lands in
|
|
@@ -889,7 +1039,7 @@ const concatMigrations = (migrationsDir) => readdirSync(migrationsDir)
|
|
|
889
1039
|
* happens to be configured against — an addon is published once and consumed by
|
|
890
1040
|
* projects on either engine.
|
|
891
1041
|
*/
|
|
892
|
-
export async function exportSchema(rootDir) {
|
|
1042
|
+
export async function exportSchema(rootDir, pgliteExtensions = []) {
|
|
893
1043
|
const artifact = {};
|
|
894
1044
|
const sqliteDir = join(rootDir, 'db', 'sqlite');
|
|
895
1045
|
if (existsSync(sqliteDir)) {
|
|
@@ -902,7 +1052,7 @@ export async function exportSchema(rootDir) {
|
|
|
902
1052
|
if (existsSync(postgresDir)) {
|
|
903
1053
|
artifact.postgres = {
|
|
904
1054
|
sql: concatMigrations(postgresDir),
|
|
905
|
-
tables: serializeSchemaMap(await coveredPostgresSchema(postgresDir)),
|
|
1055
|
+
tables: serializeSchemaMap(await coveredPostgresSchema(postgresDir, { rootDir, pgliteExtensions })),
|
|
906
1056
|
};
|
|
907
1057
|
}
|
|
908
1058
|
return artifact;
|
|
@@ -975,6 +1125,7 @@ export async function schemaSources(resolved, rootDir, srcDirectories, logger, a
|
|
|
975
1125
|
name: 'pikku-runtime',
|
|
976
1126
|
desired: runtime,
|
|
977
1127
|
origin: "@pikku/kysely's runtime services",
|
|
1128
|
+
schema: resolved.dialect === 'postgres' ? resolved.schema : undefined,
|
|
978
1129
|
});
|
|
979
1130
|
}
|
|
980
1131
|
sources.push(...(await addonSchemaSources(rootDir, resolved.dialect, addons, logger)));
|
|
@@ -1042,15 +1193,31 @@ export async function generateMigrations(resolved, rootDir, srcDirectories, logg
|
|
|
1042
1193
|
// is part of what the next one is compared against.
|
|
1043
1194
|
const covered = resolved.dialect === 'sqlite'
|
|
1044
1195
|
? await coveredSqliteSchema(resolved.migrationsDir)
|
|
1045
|
-
: await coveredPostgresSchema(resolved.migrationsDir);
|
|
1046
|
-
const { missingTables, missingColumns } = diffSchemas(source.desired.tables, covered);
|
|
1196
|
+
: await coveredPostgresSchema(resolved.migrationsDir, resolved);
|
|
1197
|
+
const { missingTables, missingColumns } = diffSchemas(source.desired.tables, covered, source.schema);
|
|
1047
1198
|
if (missingTables.length === 0 && missingColumns.length === 0) {
|
|
1048
1199
|
result.upToDate.push(source.name);
|
|
1049
1200
|
continue;
|
|
1050
1201
|
}
|
|
1051
|
-
|
|
1202
|
+
// Matched the same way `diffSchemas` matches, qualifier and all. A project
|
|
1203
|
+
// that keeps its tables in a schema has them covered as `app.workflow_step`
|
|
1204
|
+
// while the declaration names them bare, so a strict `covered.has(t)` reads
|
|
1205
|
+
// "nothing is covered" and re-emits the whole schema over tables that are
|
|
1206
|
+
// already there — and a copy in some other schema is not that table, so it
|
|
1207
|
+
// must not count either.
|
|
1208
|
+
const partial = [...source.desired.tables.keys()].some((t) => source.schema
|
|
1209
|
+
? covered.has(qualifiedTableKey(source.schema, t))
|
|
1210
|
+
: covered.has(t) || schemaQualifiedMatch(covered, t) !== undefined);
|
|
1052
1211
|
let body;
|
|
1053
1212
|
let needsBackfill = [];
|
|
1213
|
+
// The source's own SQL is already qualified when it can be; the delta below
|
|
1214
|
+
// is written here from bare introspected names, so it has to be qualified
|
|
1215
|
+
// to match, or it alters a table in whichever schema `search_path` finds.
|
|
1216
|
+
// Quoted, because the compiled SQL this delta has to line up with goes
|
|
1217
|
+
// through the schema builder and is quoted there. `db.schema: "App"` left
|
|
1218
|
+
// raw folds to `app`, so the delta would alter a table in a schema the
|
|
1219
|
+
// runtime never uses.
|
|
1220
|
+
const qualify = (table) => source.schema ? `${quoteIdentifier(source.schema)}.${table}` : table;
|
|
1054
1221
|
if (!partial) {
|
|
1055
1222
|
body = source.desired.sql;
|
|
1056
1223
|
}
|
|
@@ -1071,7 +1238,7 @@ export async function generateMigrations(resolved, rootDir, srcDirectories, logg
|
|
|
1071
1238
|
statements.push(`-- REVIEW: ${table} is new, and its CREATE TABLE could not be found in the\n` +
|
|
1072
1239
|
`-- source's own SQL. The column list below carries no indexes, constraints\n` +
|
|
1073
1240
|
`-- or foreign keys — copy the real statement over it before applying.\n` +
|
|
1074
|
-
`CREATE TABLE ${table} (\n` +
|
|
1241
|
+
`CREATE TABLE ${qualify(table)} (\n` +
|
|
1075
1242
|
[...(columns?.values() ?? [])]
|
|
1076
1243
|
.map((c) => ` ${c.name} ${c.type}${c.notNull ? ' NOT NULL' : ''}`)
|
|
1077
1244
|
.join(',\n') +
|
|
@@ -1081,7 +1248,7 @@ export async function generateMigrations(resolved, rootDir, srcDirectories, logg
|
|
|
1081
1248
|
const infos = columns
|
|
1082
1249
|
.map((name) => source.desired.tables.get(table)?.get(name))
|
|
1083
1250
|
.filter((c) => c !== undefined);
|
|
1084
|
-
const added = addColumnStatements(table, infos);
|
|
1251
|
+
const added = addColumnStatements(qualify(table), infos);
|
|
1085
1252
|
statements.push(...added.sql);
|
|
1086
1253
|
needsBackfill.push(...added.needsBackfill);
|
|
1087
1254
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SyncSqliteDatabase } from './sqlite-runtime.js';
|
|
2
|
+
export interface DevSeedResult {
|
|
3
|
+
applied: boolean;
|
|
4
|
+
bytes: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Apply db/<engine>-dev-seed.sql to the open db. Only `pikku db reset` reaches
|
|
8
|
+
* here, against a database it has just wiped and migrated, so the file may be
|
|
9
|
+
* plain `INSERT`s — it is never applied twice. Returns `applied: false` if the
|
|
10
|
+
* file doesn't exist; throws on SQL errors.
|
|
11
|
+
*/
|
|
12
|
+
export declare function devSeed(db: SyncSqliteDatabase, devSeedFile: string): DevSeedResult;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* Apply db/<engine>-dev-seed.sql to the open db. Only `pikku db reset` reaches
|
|
4
|
+
* here, against a database it has just wiped and migrated, so the file may be
|
|
5
|
+
* plain `INSERT`s — it is never applied twice. Returns `applied: false` if the
|
|
6
|
+
* file doesn't exist; throws on SQL errors.
|
|
7
|
+
*/
|
|
8
|
+
export function devSeed(db, devSeedFile) {
|
|
9
|
+
if (!existsSync(devSeedFile)) {
|
|
10
|
+
return { applied: false, bytes: 0 };
|
|
11
|
+
}
|
|
12
|
+
const raw = readFileSync(devSeedFile);
|
|
13
|
+
db.exec('BEGIN');
|
|
14
|
+
try {
|
|
15
|
+
db.exec(raw.toString('utf8'));
|
|
16
|
+
db.exec('COMMIT');
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
db.exec('ROLLBACK');
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
return { applied: true, bytes: raw.length };
|
|
23
|
+
}
|
|
@@ -30,7 +30,7 @@ class BunSqliteDatabase {
|
|
|
30
30
|
}
|
|
31
31
|
exec(sql) {
|
|
32
32
|
// bun:sqlite throws "no valid SQL statement" on comment-only/empty input
|
|
33
|
-
// (e.g. a placeholder seed.sql); node:sqlite silently no-ops. Match node's
|
|
33
|
+
// (e.g. a placeholder dev-seed.sql); node:sqlite silently no-ops. Match node's
|
|
34
34
|
// tolerance by skipping when nothing executable remains after stripping
|
|
35
35
|
// comments. The original `sql` is still exec'd verbatim when non-empty.
|
|
36
36
|
const executable = sql
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ValidateFinding } from './persona-checks.js';
|
|
2
|
+
type PackageManifest = {
|
|
3
|
+
scripts?: Record<string, string>;
|
|
4
|
+
dependencies?: Record<string, string>;
|
|
5
|
+
devDependencies?: Record<string, string>;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Reports a root `start`/`dev` script that boots a server Pikku does not own.
|
|
9
|
+
*
|
|
10
|
+
* Services built in a hand-rolled entrypoint never reach the
|
|
11
|
+
* `pikkuServerLifecycle` hooks, so startup and shutdown work silently does not
|
|
12
|
+
* run. A project depending on a runtime adapter has opted into owning its own
|
|
13
|
+
* server, and a script that delegates to another runner cannot be followed, so
|
|
14
|
+
* neither is reported.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runBootstrapChecks(root: string, rootPkg: PackageManifest | null, pikkuConfig: {
|
|
17
|
+
lint?: unknown;
|
|
18
|
+
} | null): Promise<ValidateFinding[]>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { readJsonSafe } from './shared-checks.js';
|
|
3
|
+
/**
|
|
4
|
+
* `pikku dev` / `pikku serve` own the server lifecycle, so a project that boots
|
|
5
|
+
* through them can hang setup off `pikkuServerLifecycle` instead of constructing
|
|
6
|
+
* its own services. These patterns cover the runner prefixes people put in front
|
|
7
|
+
* of the binary (npx/yarn/pnpm/bun), leading env assignments, and `&&` chains.
|
|
8
|
+
*/
|
|
9
|
+
const PIKKU_SERVER_SCRIPT = /(?:^|[\s;&|])pikku(?:\.js)?\s+(?:-[^\s]*\s+)*(?:dev|serve)(?:\s|$)/;
|
|
10
|
+
/**
|
|
11
|
+
* Scripts that hand off to another script rather than starting a process
|
|
12
|
+
* themselves. We cannot follow the indirection, so they are treated as unknown
|
|
13
|
+
* rather than reported.
|
|
14
|
+
*/
|
|
15
|
+
const DELEGATING_SCRIPT = /(?:^|[\s;&|])(?:turbo|nx|lerna|npm-run-all|run-p|run-s)(?:\s|$)|(?:^|[\s;&|])(?:npm|yarn|pnpm|bun)\s+(?:run|workspace|workspaces|--filter)(?:\s|$)/;
|
|
16
|
+
/**
|
|
17
|
+
* Runtime adapters that embed Pikku in a server the project owns. Depending on
|
|
18
|
+
* one is a deliberate choice to bootstrap by hand — `pikku dev` / `pikku serve`
|
|
19
|
+
* cannot host these, so their entrypoints are not reported.
|
|
20
|
+
*/
|
|
21
|
+
const RUNTIME_ADAPTER_PACKAGES = new Set([
|
|
22
|
+
'@pikku/azure-functions',
|
|
23
|
+
'@pikku/bun-server',
|
|
24
|
+
'@pikku/cloudflare',
|
|
25
|
+
'@pikku/express',
|
|
26
|
+
'@pikku/express-middleware',
|
|
27
|
+
'@pikku/fastify',
|
|
28
|
+
'@pikku/fastify-plugin',
|
|
29
|
+
'@pikku/lambda',
|
|
30
|
+
'@pikku/modelcontextprotocol',
|
|
31
|
+
'@pikku/next',
|
|
32
|
+
'@pikku/node-http-server',
|
|
33
|
+
'@pikku/tanstack-start',
|
|
34
|
+
'@pikku/uws',
|
|
35
|
+
'@pikku/uws-handler',
|
|
36
|
+
'@pikku/ws',
|
|
37
|
+
]);
|
|
38
|
+
async function hasRuntimeAdapter(root, rootPkg) {
|
|
39
|
+
const fnPkg = await readJsonSafe(join(root, 'packages', 'functions', 'package.json'));
|
|
40
|
+
const names = [
|
|
41
|
+
...Object.keys(rootPkg.dependencies ?? {}),
|
|
42
|
+
...Object.keys(rootPkg.devDependencies ?? {}),
|
|
43
|
+
...Object.keys(fnPkg?.dependencies ?? {}),
|
|
44
|
+
...Object.keys(fnPkg?.devDependencies ?? {}),
|
|
45
|
+
];
|
|
46
|
+
return names.some((name) => RUNTIME_ADAPTER_PACKAGES.has(name));
|
|
47
|
+
}
|
|
48
|
+
function bootstrapLintSeverity(pikkuConfig) {
|
|
49
|
+
const lint = pikkuConfig?.lint;
|
|
50
|
+
const configured = lint?.customServerBootstrap;
|
|
51
|
+
return configured === 'off' || configured === 'error' || configured === 'warn'
|
|
52
|
+
? configured
|
|
53
|
+
: 'warn';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Reports a root `start`/`dev` script that boots a server Pikku does not own.
|
|
57
|
+
*
|
|
58
|
+
* Services built in a hand-rolled entrypoint never reach the
|
|
59
|
+
* `pikkuServerLifecycle` hooks, so startup and shutdown work silently does not
|
|
60
|
+
* run. A project depending on a runtime adapter has opted into owning its own
|
|
61
|
+
* server, and a script that delegates to another runner cannot be followed, so
|
|
62
|
+
* neither is reported.
|
|
63
|
+
*/
|
|
64
|
+
export async function runBootstrapChecks(root, rootPkg, pikkuConfig) {
|
|
65
|
+
if (!rootPkg)
|
|
66
|
+
return [];
|
|
67
|
+
const severity = bootstrapLintSeverity(pikkuConfig);
|
|
68
|
+
if (severity === 'off')
|
|
69
|
+
return [];
|
|
70
|
+
if (await hasRuntimeAdapter(root, rootPkg))
|
|
71
|
+
return [];
|
|
72
|
+
const customScripts = ['start', 'dev'].filter((name) => {
|
|
73
|
+
const script = rootPkg.scripts?.[name];
|
|
74
|
+
if (!script)
|
|
75
|
+
return false;
|
|
76
|
+
return !PIKKU_SERVER_SCRIPT.test(script) && !DELEGATING_SCRIPT.test(script);
|
|
77
|
+
});
|
|
78
|
+
if (customScripts.length === 0)
|
|
79
|
+
return [];
|
|
80
|
+
const named = customScripts.map((n) => `"${n}"`).join(', ');
|
|
81
|
+
return [
|
|
82
|
+
{
|
|
83
|
+
id: 'custom-server-bootstrap',
|
|
84
|
+
severity,
|
|
85
|
+
message: `package.json ${named} ${customScripts.length > 1 ? 'scripts start a server' : 'script starts a server'} without \`pikku dev\` or \`pikku serve\`, and no Pikku runtime adapter is installed — services created in a hand-rolled entrypoint bypass the server lifecycle hooks`,
|
|
86
|
+
path: join(root, 'package.json'),
|
|
87
|
+
fixHint: 'Start the server with `pikku serve` (or `pikku dev` locally) and move startup and shutdown work into a `pikkuServerLifecycle({ beforeStart, afterStart, beforeStop, afterStop })` export, which receives the already-created singleton services. To keep a custom entrypoint, set "lint": { "customServerBootstrap": "off" } in pikku.config.json',
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type ValidateSeverity = 'error' | 'warn' | 'info';
|
|
2
|
+
export type ValidateFinding = {
|
|
3
|
+
id: string;
|
|
4
|
+
severity: ValidateSeverity;
|
|
5
|
+
message: string;
|
|
6
|
+
path: string;
|
|
7
|
+
fixHint: string;
|
|
8
|
+
};
|
|
9
|
+
export type PersonaCheckConfig = {
|
|
10
|
+
srcDirectories?: unknown;
|
|
11
|
+
outDir?: unknown;
|
|
12
|
+
personasMetaJsonFile?: unknown;
|
|
13
|
+
environments?: unknown;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The people this project can run as, and whether it can run as them.
|
|
17
|
+
*
|
|
18
|
+
* Shared by `pikku workspace validate` and `pikku fabric validate` — the two
|
|
19
|
+
* validators are separate implementations, and a check that only one of them
|
|
20
|
+
* makes is a check half the projects never see.
|
|
21
|
+
*
|
|
22
|
+
* knowledge: decisions/internals/validate-checks-personas-through-a-shared-module.md
|
|
23
|
+
*/
|
|
24
|
+
export declare const runPersonaChecks: (root: string, config: PersonaCheckConfig | null) => Promise<ValidateFinding[]>;
|