@supatype/cli 0.1.6 → 0.1.9
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +152 -102
- package/.turbo/turbo-typecheck.log +1 -1
- package/LICENSE +202 -0
- package/deno.d.ts +52 -0
- package/dist/app/framework.js +1 -1
- package/dist/app/framework.js.map +1 -1
- package/dist/augmentation-generator.d.ts +22 -0
- package/dist/augmentation-generator.d.ts.map +1 -1
- package/dist/augmentation-generator.js +19 -5
- package/dist/augmentation-generator.js.map +1 -1
- package/dist/binary-cache.d.ts +42 -6
- package/dist/binary-cache.d.ts.map +1 -1
- package/dist/binary-cache.js +76 -44
- package/dist/binary-cache.js.map +1 -1
- package/dist/cache-clean.d.ts +15 -0
- package/dist/cache-clean.d.ts.map +1 -0
- package/dist/cache-clean.js +76 -0
- package/dist/cache-clean.js.map +1 -0
- package/dist/cache-pins.d.ts +51 -0
- package/dist/cache-pins.d.ts.map +1 -0
- package/dist/cache-pins.js +184 -0
- package/dist/cache-pins.js.map +1 -0
- package/dist/cli-artifacts.d.ts +32 -0
- package/dist/cli-artifacts.d.ts.map +1 -0
- package/dist/cli-artifacts.js +49 -0
- package/dist/cli-artifacts.js.map +1 -0
- package/dist/cli-install-method.d.ts +27 -0
- package/dist/cli-install-method.d.ts.map +1 -0
- package/dist/cli-install-method.js +78 -0
- package/dist/cli-install-method.js.map +1 -0
- package/dist/cli-package-version.d.ts +1 -1
- package/dist/cli-package-version.d.ts.map +1 -1
- package/dist/cli-package-version.js +6 -1
- package/dist/cli-package-version.js.map +1 -1
- package/dist/cli-standalone.d.ts +13 -4
- package/dist/cli-standalone.d.ts.map +1 -1
- package/dist/cli-standalone.js +86 -31
- package/dist/cli-standalone.js.map +1 -1
- package/dist/cli-version-embedded.d.ts +10 -0
- package/dist/cli-version-embedded.d.ts.map +1 -0
- package/dist/cli-version-embedded.js +13 -0
- package/dist/cli-version-embedded.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -1
- package/dist/cli.js.map +1 -1
- package/dist/cloud-credentials.d.ts +34 -0
- package/dist/cloud-credentials.d.ts.map +1 -0
- package/dist/cloud-credentials.js +139 -0
- package/dist/cloud-credentials.js.map +1 -0
- package/dist/commands/add.js +1 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/admin.d.ts +11 -0
- package/dist/commands/admin.d.ts.map +1 -1
- package/dist/commands/admin.js +123 -21
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/adopt.js +1 -1
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/app.js +2 -2
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/cache.d.ts +1 -1
- package/dist/commands/cache.d.ts.map +1 -1
- package/dist/commands/cache.js +69 -73
- package/dist/commands/cache.js.map +1 -1
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +32 -8
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/db.d.ts +3 -2
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +149 -3
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts +9 -9
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +3 -3
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +63 -23
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +28 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +60 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/engine.d.ts +1 -1
- package/dist/commands/engine.js +1 -1
- package/dist/commands/functions.d.ts +7 -0
- package/dist/commands/functions.d.ts.map +1 -1
- package/dist/commands/functions.js +45 -11
- package/dist/commands/functions.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +5 -1
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/hooks.d.ts +12 -0
- package/dist/commands/hooks.d.ts.map +1 -0
- package/dist/commands/hooks.js +118 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +64 -30
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/internal.d.ts +6 -0
- package/dist/commands/internal.d.ts.map +1 -0
- package/dist/commands/internal.js +22 -0
- package/dist/commands/internal.js.map +1 -0
- package/dist/commands/link-helpers.d.ts.map +1 -1
- package/dist/commands/link-helpers.js +8 -2
- package/dist/commands/link-helpers.js.map +1 -1
- package/dist/commands/login.d.ts +7 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +110 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logs.d.ts +1 -1
- package/dist/commands/migrate.js +5 -5
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/pg.d.ts +1 -1
- package/dist/commands/pg.js +1 -1
- package/dist/commands/plugins.js +1 -1
- package/dist/commands/plugins.js.map +1 -1
- package/dist/commands/pull.js +2 -2
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +110 -7
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/self-host.d.ts +1 -1
- package/dist/commands/self-host.js +1 -1
- package/dist/commands/self-update.d.ts +3 -2
- package/dist/commands/self-update.d.ts.map +1 -1
- package/dist/commands/self-update.js +31 -16
- package/dist/commands/self-update.js.map +1 -1
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/update.d.ts +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +19 -4
- package/dist/config.js.map +1 -1
- package/dist/db-preflight.d.ts +78 -0
- package/dist/db-preflight.d.ts.map +1 -0
- package/dist/db-preflight.js +452 -0
- package/dist/db-preflight.js.map +1 -0
- package/dist/dev-compose.d.ts +4 -4
- package/dist/dev-compose.d.ts.map +1 -1
- package/dist/dev-compose.js +143 -88
- package/dist/dev-compose.js.map +1 -1
- package/dist/dev-log-filter.d.ts +1 -1
- package/dist/dev-log-filter.d.ts.map +1 -1
- package/dist/dev-log-filter.js +1 -1
- package/dist/dev-log-filter.js.map +1 -1
- package/dist/dev-ports.d.ts +2 -2
- package/dist/dev-ports.d.ts.map +1 -1
- package/dist/dev-ports.js +2 -2
- package/dist/dev-ports.js.map +1 -1
- package/dist/dev-session.d.ts +5 -5
- package/dist/dev-session.d.ts.map +1 -1
- package/dist/dev-session.js +6 -6
- package/dist/dev-session.js.map +1 -1
- package/dist/dev-shutdown.d.ts +5 -5
- package/dist/dev-shutdown.d.ts.map +1 -1
- package/dist/dev-shutdown.js +7 -7
- package/dist/dev-shutdown.js.map +1 -1
- package/dist/dev-task-colors.d.ts +1 -1
- package/dist/dev-task-colors.d.ts.map +1 -1
- package/dist/dev-task-colors.js +1 -1
- package/dist/dev-task-colors.js.map +1 -1
- package/dist/docker-postgres.d.ts +5 -5
- package/dist/docker-postgres.d.ts.map +1 -1
- package/dist/docker-postgres.js +5 -5
- package/dist/docker-postgres.js.map +1 -1
- package/dist/docker-runtime.d.ts +2 -2
- package/dist/docker-runtime.d.ts.map +1 -1
- package/dist/docker-runtime.js +5 -5
- package/dist/docker-runtime.js.map +1 -1
- package/dist/engine-client.d.ts +1 -1
- package/dist/engine-client.js +3 -3
- package/dist/engine-client.js.map +1 -1
- package/dist/engine-push-output.js +1 -1
- package/dist/engine-push-output.js.map +1 -1
- package/dist/ensure-binary.d.ts.map +1 -1
- package/dist/ensure-binary.js +17 -0
- package/dist/ensure-binary.js.map +1 -1
- package/dist/ensure-component-binaries.js +1 -1
- package/dist/ensure-component-binaries.js.map +1 -1
- package/dist/env-file.d.ts +40 -2
- package/dist/env-file.d.ts.map +1 -1
- package/dist/env-file.js +132 -11
- package/dist/env-file.js.map +1 -1
- package/dist/field-masking-tier.d.ts +50 -0
- package/dist/field-masking-tier.d.ts.map +1 -0
- package/dist/field-masking-tier.js +91 -0
- package/dist/field-masking-tier.js.map +1 -0
- package/dist/functions-deno-types.d.ts +24 -0
- package/dist/functions-deno-types.d.ts.map +1 -0
- package/dist/functions-deno-types.js +155 -0
- package/dist/functions-deno-types.js.map +1 -0
- package/dist/functions-router-gen.js +1 -1
- package/dist/gitignore.d.ts +2 -2
- package/dist/gitignore.d.ts.map +1 -1
- package/dist/gitignore.js +3 -3
- package/dist/gitignore.js.map +1 -1
- package/dist/hook-upload.d.ts +30 -0
- package/dist/hook-upload.d.ts.map +1 -0
- package/dist/hook-upload.js +86 -0
- package/dist/hook-upload.js.map +1 -0
- package/dist/hooks-generator.d.ts +8 -0
- package/dist/hooks-generator.d.ts.map +1 -0
- package/dist/hooks-generator.js +332 -0
- package/dist/hooks-generator.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/init-project-detect.d.ts +1 -1
- package/dist/init-project-detect.d.ts.map +1 -1
- package/dist/init-project-detect.js +1 -1
- package/dist/init-project-detect.js.map +1 -1
- package/dist/link.d.ts +4 -0
- package/dist/link.d.ts.map +1 -1
- package/dist/link.js +2 -0
- package/dist/link.js.map +1 -1
- package/dist/local-secrets.d.ts +71 -0
- package/dist/local-secrets.d.ts.map +1 -0
- package/dist/local-secrets.js +100 -0
- package/dist/local-secrets.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +20 -0
- package/dist/main.js.map +1 -0
- package/dist/model-hooks.d.ts +73 -0
- package/dist/model-hooks.d.ts.map +1 -0
- package/dist/model-hooks.js +211 -0
- package/dist/model-hooks.js.map +1 -0
- package/dist/pgbouncer-userlist.d.ts +1 -1
- package/dist/pgbouncer-userlist.d.ts.map +1 -1
- package/dist/pgbouncer-userlist.js +2 -2
- package/dist/pgbouncer-userlist.js.map +1 -1
- package/dist/postgres-ctl.d.ts +12 -1
- package/dist/postgres-ctl.d.ts.map +1 -1
- package/dist/postgres-ctl.js +28 -5
- package/dist/postgres-ctl.js.map +1 -1
- package/dist/process-manager.d.ts +2 -2
- package/dist/process-manager.d.ts.map +1 -1
- package/dist/process-manager.js +4 -4
- package/dist/process-manager.js.map +1 -1
- package/dist/project-config.d.ts +122 -2
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +150 -1
- package/dist/project-config.js.map +1 -1
- package/dist/pull-utils.d.ts +2 -2
- package/dist/pull-utils.d.ts.map +1 -1
- package/dist/pull-utils.js +4 -4
- package/dist/pull-utils.js.map +1 -1
- package/dist/realtime-launch.js +1 -1
- package/dist/realtime-launch.js.map +1 -1
- package/dist/release-pins.d.ts +1 -1
- package/dist/release-pins.js +2 -2
- package/dist/release-pins.js.map +1 -1
- package/dist/resolve-api-url.js +1 -1
- package/dist/resolve-api-url.js.map +1 -1
- package/dist/resolve-target.d.ts +4 -0
- package/dist/resolve-target.d.ts.map +1 -1
- package/dist/resolve-target.js +69 -73
- package/dist/resolve-target.js.map +1 -1
- package/dist/rest-cache-admin.js +2 -2
- package/dist/rest-cache-admin.js.map +1 -1
- package/dist/runtime-routes.d.ts +1 -1
- package/dist/runtime-routes.js +1 -1
- package/dist/schema-ast-v2.d.ts +5 -4
- package/dist/schema-ast-v2.d.ts.map +1 -1
- package/dist/schema-ast-v2.js +5 -3
- package/dist/schema-ast-v2.js.map +1 -1
- package/dist/schema-push-lock.d.ts.map +1 -1
- package/dist/schema-push-lock.js +43 -16
- package/dist/schema-push-lock.js.map +1 -1
- package/dist/scripts/postinstall.d.ts +1 -1
- package/dist/scripts/postinstall.js +1 -1
- package/dist/self-host-compose.d.ts +26 -2
- package/dist/self-host-compose.d.ts.map +1 -1
- package/dist/self-host-compose.js +305 -73
- package/dist/self-host-compose.js.map +1 -1
- package/dist/service-role-check.d.ts +21 -0
- package/dist/service-role-check.d.ts.map +1 -0
- package/dist/service-role-check.js +85 -0
- package/dist/service-role-check.js.map +1 -0
- package/dist/storage-provision.d.ts +1 -1
- package/dist/storage-provision.js +3 -3
- package/dist/storage-provision.js.map +1 -1
- package/dist/studio-dev-server.d.ts +1 -1
- package/dist/studio-dev-server.d.ts.map +1 -1
- package/dist/systemd.d.ts +1 -1
- package/dist/systemd.js +1 -1
- package/dist/target-client.d.ts +17 -0
- package/dist/target-client.d.ts.map +1 -1
- package/dist/target-client.js +69 -17
- package/dist/target-client.js.map +1 -1
- package/dist/tsx-runner.js +1 -1
- package/dist/tsx-runner.js.map +1 -1
- package/dist/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor.js +738 -34
- package/dist/type-extractor.js.map +1 -1
- package/dist/type-resolver.js +1 -1
- package/dist/type-resolver.js.map +1 -1
- package/dist/ui/brand.d.ts +1 -1
- package/dist/ui/brand.js +1 -1
- package/dist/ui/components/LogoWordmark.d.ts +1 -1
- package/dist/ui/components/LogoWordmark.d.ts.map +1 -1
- package/dist/ui/components/LogoWordmark.js +1 -1
- package/dist/ui/components/LogoWordmark.js.map +1 -1
- package/dist/ui/components/SelectField.js +1 -1
- package/dist/ui/components/SelectField.js.map +1 -1
- package/dist/ui/confirm.d.ts +1 -1
- package/dist/ui/confirm.js +1 -1
- package/dist/ui/fatal.d.ts +1 -1
- package/dist/ui/fatal.d.ts.map +1 -1
- package/dist/ui/fatal.js +1 -1
- package/dist/ui/fatal.js.map +1 -1
- package/dist/ui/flows/prompt-fields.js +1 -1
- package/dist/ui/flows/prompt-fields.js.map +1 -1
- package/dist/ui/interactive.d.ts +1 -1
- package/dist/ui/interactive.d.ts.map +1 -1
- package/dist/ui/interactive.js +1 -1
- package/dist/ui/interactive.js.map +1 -1
- package/dist/ui/progress.d.ts +1 -1
- package/dist/ui/progress.js +1 -1
- package/dist/ui/runtime/command-chrome.js +2 -2
- package/dist/ui/runtime/command-chrome.js.map +1 -1
- package/dist/ui/runtime/run-clack-flow.js +1 -1
- package/dist/ui/runtime/run-clack-flow.js.map +1 -1
- package/dist/ui/runtime/stdin-after-ink.d.ts.map +1 -1
- package/dist/ui/runtime/stdin-after-ink.js +4 -2
- package/dist/ui/runtime/stdin-after-ink.js.map +1 -1
- package/dist/ui/theme.d.ts +1 -1
- package/dist/ui/theme.js +1 -1
- package/dist/valkey-sidecar.d.ts +1 -1
- package/dist/valkey-sidecar.d.ts.map +1 -1
- package/dist/valkey-sidecar.js +2 -2
- package/dist/valkey-sidecar.js.map +1 -1
- package/package.json +6 -2
- package/scripts/build-release-artifacts.sh +112 -0
- package/scripts/generate-homebrew-formula.mjs +123 -0
- package/scripts/mirror-deno-release.sh +1 -1
- package/src/TYPE-RESOLUTION.md +31 -31
- package/src/app/framework.ts +1 -1
- package/src/augmentation-generator.ts +19 -6
- package/src/binary-cache.ts +94 -47
- package/src/cache-clean.ts +81 -0
- package/src/cache-pins.ts +243 -0
- package/src/cli-artifacts.ts +65 -0
- package/src/cli-install-method.ts +87 -0
- package/src/cli-package-version.ts +5 -1
- package/src/cli-standalone.ts +116 -34
- package/src/cli-version-embedded.ts +12 -0
- package/src/cli.ts +7 -1
- package/src/cloud-credentials.ts +183 -0
- package/src/commands/add.ts +1 -1
- package/src/commands/admin.ts +142 -27
- package/src/commands/adopt.ts +1 -1
- package/src/commands/app.ts +2 -2
- package/src/commands/cache.ts +92 -77
- package/src/commands/cloud.ts +32 -8
- package/src/commands/db.ts +164 -3
- package/src/commands/deploy.ts +12 -12
- package/src/commands/dev.ts +72 -23
- package/src/commands/doctor.ts +69 -3
- package/src/commands/engine.ts +1 -1
- package/src/commands/functions.ts +47 -10
- package/src/commands/generate.ts +5 -1
- package/src/commands/hooks.ts +138 -0
- package/src/commands/init.ts +65 -31
- package/src/commands/internal.ts +23 -0
- package/src/commands/link-helpers.ts +9 -2
- package/src/commands/login.ts +127 -0
- package/src/commands/logs.ts +1 -1
- package/src/commands/migrate.ts +5 -5
- package/src/commands/pg.ts +1 -1
- package/src/commands/plugins.ts +1 -1
- package/src/commands/pull.ts +2 -2
- package/src/commands/push.ts +124 -7
- package/src/commands/self-host.ts +1 -1
- package/src/commands/self-update.ts +41 -19
- package/src/commands/status.ts +1 -1
- package/src/commands/update.ts +1 -1
- package/src/config.ts +23 -4
- package/src/db-preflight.ts +575 -0
- package/src/dev-compose.ts +158 -89
- package/src/dev-log-filter.ts +1 -1
- package/src/dev-ports.ts +3 -3
- package/src/dev-session.ts +6 -6
- package/src/dev-shutdown.ts +8 -8
- package/src/dev-task-colors.ts +1 -1
- package/src/docker-postgres.ts +6 -6
- package/src/docker-runtime.ts +5 -5
- package/src/engine-client.ts +3 -3
- package/src/engine-push-output.ts +1 -1
- package/src/ensure-binary.ts +20 -0
- package/src/ensure-component-binaries.ts +1 -1
- package/src/env-file.ts +157 -11
- package/src/field-masking-tier.ts +107 -0
- package/src/functions-deno-types.ts +188 -0
- package/src/functions-router-gen.ts +1 -1
- package/src/gitignore.ts +3 -3
- package/src/hook-upload.ts +107 -0
- package/src/hooks-generator.ts +352 -0
- package/src/index.ts +1 -1
- package/src/init-project-detect.ts +1 -1
- package/src/link.ts +7 -0
- package/src/local-secrets.ts +104 -0
- package/src/main.ts +20 -0
- package/src/model-hooks.ts +260 -0
- package/src/pgbouncer-userlist.ts +2 -2
- package/src/postgres-ctl.ts +32 -5
- package/src/process-manager.ts +4 -4
- package/src/project-config.ts +237 -3
- package/src/pull-utils.ts +4 -4
- package/src/realtime-launch.ts +1 -1
- package/src/release-pins.ts +2 -2
- package/src/resolve-api-url.ts +1 -1
- package/src/resolve-target.ts +113 -68
- package/src/rest-cache-admin.ts +2 -2
- package/src/runtime-routes.ts +2 -2
- package/src/schema-ast-v2.ts +8 -5
- package/src/schema-push-lock.ts +56 -17
- package/src/scripts/postinstall.ts +1 -1
- package/src/self-host-compose.ts +348 -72
- package/src/service-role-check.ts +111 -0
- package/src/storage-provision.ts +3 -3
- package/src/studio-dev-server.ts +1 -1
- package/src/systemd.ts +1 -1
- package/src/target-client.ts +105 -18
- package/src/tsx-runner.ts +1 -1
- package/src/type-extractor.ts +885 -30
- package/src/type-resolver.ts +1 -1
- package/src/ui/README.md +1 -1
- package/src/ui/brand.ts +1 -1
- package/src/ui/components/LogoWordmark.tsx +1 -1
- package/src/ui/components/SelectField.tsx +1 -1
- package/src/ui/confirm.ts +1 -1
- package/src/ui/fatal.ts +1 -1
- package/src/ui/flows/prompt-fields.tsx +1 -1
- package/src/ui/interactive.ts +1 -1
- package/src/ui/progress.ts +1 -1
- package/src/ui/runtime/command-chrome.ts +2 -2
- package/src/ui/runtime/run-clack-flow.tsx +1 -1
- package/src/ui/runtime/stdin-after-ink.ts +4 -2
- package/src/ui/theme.ts +1 -1
- package/src/valkey-sidecar.ts +2 -2
- package/tests/access-composition.test.ts +667 -0
- package/tests/admin-studio-membership.test.ts +121 -0
- package/tests/api-schemas.test.ts +83 -0
- package/tests/archive-name-alias.test.ts +42 -0
- package/tests/augmentation-generator.test.ts +28 -0
- package/tests/cache-clean.test.ts +206 -0
- package/tests/cli-help.test.ts +11 -2
- package/tests/cli-install-method.test.ts +66 -0
- package/tests/command-chrome-coverage.test.ts +31 -26
- package/tests/command-chrome-wrap.test.ts +1 -0
- package/tests/db-preflight.test.ts +72 -0
- package/tests/doctor-render.test.ts +62 -0
- package/tests/doctor-service-role.test.ts +55 -0
- package/tests/engine-push-output.test.ts +1 -1
- package/tests/env-file.test.ts +121 -0
- package/tests/external-database-compose.test.ts +184 -0
- package/tests/external-database-config.test.ts +154 -0
- package/tests/field-masking-tier.test.ts +184 -0
- package/tests/functions-deno-types.test.ts +197 -0
- package/tests/homebrew-formula.test.ts +100 -0
- package/tests/hook-upload.test.ts +125 -0
- package/tests/hooks-generator.test.ts +212 -0
- package/tests/init.test.ts +5 -2
- package/tests/keys.test.ts +8 -2
- package/tests/local-secrets.test.ts +92 -0
- package/tests/model-hooks.test.ts +322 -0
- package/tests/runtime-contract.test.ts +113 -1
- package/tests/service-role-check.test.ts +109 -0
- package/tests/service-role-routes.test.ts +51 -0
- package/tests/storage-provision.test.ts +1 -1
- package/tests/target-client-refresh.test.ts +94 -0
- package/tests/type-extractor.test.ts +187 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +7 -1
- package/scripts/build-release-bundle.mjs +0 -27
- package/tests/engine-distribution.test.ts +0 -418
package/src/commands/dev.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* supatype dev
|
|
2
|
+
* supatype dev: start local Postgres, apply schema, run supatype-server.
|
|
3
3
|
*
|
|
4
4
|
* Runtime provider (top-level `provider` or legacy `database.provider`):
|
|
5
|
-
* native
|
|
6
|
-
* docker
|
|
5
|
+
* native: host Postgres + host server + host engine (default)
|
|
6
|
+
* docker: full self-host Compose stack (Kong :18473); see dev-compose.ts
|
|
7
7
|
*
|
|
8
8
|
* Edge functions (when a functions/ dir exists): Deno is resolved from the CDN cache
|
|
9
9
|
* (auto-download on miss). Self-host/cloud Docker stacks use supatype-server in-container;
|
|
@@ -18,13 +18,21 @@ import { isAbsolute, join, relative, resolve } from "node:path"
|
|
|
18
18
|
import { loadConfig } from "../config.js"
|
|
19
19
|
import type { ExtractedSchemaAstV2 } from "../schema-ast-v2.js"
|
|
20
20
|
import {
|
|
21
|
+
apiSchemaList,
|
|
21
22
|
functionsPathCandidatesFromProject,
|
|
22
23
|
resolveRuntimeProvider,
|
|
23
24
|
schemaPathFromProject,
|
|
24
25
|
type SupatypeProjectConfig,
|
|
25
26
|
} from "../project-config.js"
|
|
26
27
|
import { discoverTsFunctionsInDir, writeDevFunctionsRouter } from "../functions-router-gen.js"
|
|
28
|
+
import { fieldMaskingTierFromProject } from "../field-masking-tier.js"
|
|
27
29
|
import { signJwt } from "../jwt.js"
|
|
30
|
+
import {
|
|
31
|
+
devAuthenticatorPassword,
|
|
32
|
+
devJwtSecret,
|
|
33
|
+
devPostgresPassword,
|
|
34
|
+
secretFingerprint,
|
|
35
|
+
} from "../local-secrets.js"
|
|
28
36
|
import {
|
|
29
37
|
normalisePlatformPath,
|
|
30
38
|
cachePath,
|
|
@@ -50,6 +58,7 @@ import { writeAppViteEnv } from "../app-vite-env.js"
|
|
|
50
58
|
import { ensureValkeySidecar, stopValkeySidecar } from "../valkey-sidecar.js"
|
|
51
59
|
import {
|
|
52
60
|
initdb,
|
|
61
|
+
nativeMaskLibraryPresent,
|
|
53
62
|
start as pgStart,
|
|
54
63
|
stop as pgStop,
|
|
55
64
|
waitReady as pgWaitReady,
|
|
@@ -118,7 +127,7 @@ export function registerDev(program: Command): void {
|
|
|
118
127
|
.action(async (opts: { watch: boolean; stream?: boolean; port?: string }) => {
|
|
119
128
|
const cwd = process.cwd()
|
|
120
129
|
|
|
121
|
-
// ── 1. Load project config (before TUI
|
|
130
|
+
// ── 1. Load project config (before TUI, fatal errors must hit real stderr) ──
|
|
122
131
|
const config = loadConfig(cwd)
|
|
123
132
|
const provider = resolveRuntimeProvider(config)
|
|
124
133
|
|
|
@@ -172,15 +181,21 @@ export function registerDev(program: Command): void {
|
|
|
172
181
|
|
|
173
182
|
// ── 5–7. Start Postgres ───────────────────────────────────────────────
|
|
174
183
|
let dbURL: string
|
|
184
|
+
/** What PostgREST connects as, deliberately not `dbURL`, which is a superuser. */
|
|
185
|
+
let postgrestDbURL: string
|
|
175
186
|
let stopPostgres: () => void | Promise<void>
|
|
176
187
|
const pgPort = NATIVE_PG_PORT
|
|
177
|
-
const pgPassword =
|
|
188
|
+
const pgPassword = devPostgresPassword(cwd)
|
|
189
|
+
// Distinct from pgPassword even locally, so the split the other paths enforce is the
|
|
190
|
+
// one developers see. Read from .env when present so it matches a stack the user has
|
|
191
|
+
// already provisioned.
|
|
192
|
+
const authenticatorPassword = devAuthenticatorPassword(cwd)
|
|
178
193
|
// pgBinDir is set on the native path and used to add DLL search path for
|
|
179
194
|
// PostgREST on Windows (PostgREST links against libpq + SSL from MinGW).
|
|
180
195
|
let pgBinDir: string | null = null
|
|
181
196
|
|
|
182
197
|
{
|
|
183
|
-
// native
|
|
198
|
+
// native: resolve pg bin dir and manage with pg_ctl
|
|
184
199
|
pgBinDir = await resolvePgBinDir(config)
|
|
185
200
|
const dataDir = config.database.data_dir ?? join(stateRoot, "data")
|
|
186
201
|
mkdirSync(dataDir, { recursive: true })
|
|
@@ -192,7 +207,8 @@ export function registerDev(program: Command): void {
|
|
|
192
207
|
pgStart(pgOpts)
|
|
193
208
|
await pgWaitReady(pgOpts, 15_000)
|
|
194
209
|
console.log("[supatype] Postgres is ready.")
|
|
195
|
-
dbURL = `postgres://postgres
|
|
210
|
+
dbURL = `postgres://postgres:${pgPassword}@127.0.0.1:${pgPort}/${projectName}?sslmode=disable`
|
|
211
|
+
postgrestDbURL = `postgres://authenticator:${authenticatorPassword}@127.0.0.1:${pgPort}/${projectName}?sslmode=disable`
|
|
196
212
|
stopPostgres = () => pgStop(pgOpts)
|
|
197
213
|
|
|
198
214
|
// Create project database if it doesn't exist (native only).
|
|
@@ -219,8 +235,25 @@ export function registerDev(program: Command): void {
|
|
|
219
235
|
// anon – unauthenticated requests (RLS enforced)
|
|
220
236
|
// authenticated – signed-in user requests (RLS enforced)
|
|
221
237
|
// service_role – developer/admin bypass (BYPASSRLS)
|
|
238
|
+
// authenticator – the role PostgREST *connects* as, and nothing else
|
|
239
|
+
//
|
|
240
|
+
// `authenticator` is NOINHERIT and holds no privileges of its own: it can only
|
|
241
|
+
// SET ROLE to the three above. That containment is the point. Connecting as
|
|
242
|
+
// `postgres`: a superuser: meant a request whose JWT named *any* role in the
|
|
243
|
+
// cluster got it, because a superuser may SET ROLE to anything. Verified: as
|
|
244
|
+
// postgres, `SET ROLE supatype_replication_admin` succeeds; as authenticator the
|
|
245
|
+
// same statement is refused. Mirrors the image, which has had this role all along.
|
|
246
|
+
// Created here rather than during the push, because the engine chooses how to enforce field
|
|
247
|
+
// rules from the extensions present *when it diffs*. Creating it later would leave the tier
|
|
248
|
+
// decided against a database that no longer looks like that.
|
|
249
|
+
//
|
|
250
|
+
// Conditional on the library being bundled: an archive downloaded before it was would fail
|
|
251
|
+
// the CREATE, and there is nothing the developer could do about it from here.
|
|
252
|
+
const maskSql = nativeMaskLibraryPresent(pgBinDir)
|
|
253
|
+
? "CREATE EXTENSION IF NOT EXISTS supatype_mask;\n"
|
|
254
|
+
: ""
|
|
222
255
|
const rolesSql = `
|
|
223
|
-
CREATE SCHEMA IF NOT EXISTS auth;
|
|
256
|
+
${maskSql}CREATE SCHEMA IF NOT EXISTS auth;
|
|
224
257
|
DO $$ BEGIN
|
|
225
258
|
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'anon')
|
|
226
259
|
THEN CREATE ROLE anon NOLOGIN; END IF;
|
|
@@ -228,8 +261,11 @@ DO $$ BEGIN
|
|
|
228
261
|
THEN CREATE ROLE authenticated NOLOGIN; END IF;
|
|
229
262
|
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'service_role')
|
|
230
263
|
THEN CREATE ROLE service_role NOLOGIN BYPASSRLS; END IF;
|
|
264
|
+
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authenticator')
|
|
265
|
+
THEN CREATE ROLE authenticator LOGIN NOINHERIT PASSWORD '${authenticatorPassword}'; END IF;
|
|
231
266
|
END $$;
|
|
232
267
|
GRANT anon, authenticated, service_role TO postgres;
|
|
268
|
+
GRANT anon, authenticated, service_role TO authenticator;
|
|
233
269
|
GRANT USAGE ON SCHEMA public TO anon, authenticated, service_role;
|
|
234
270
|
-- Table-level privileges (RLS restricts rows; roles still need table access)
|
|
235
271
|
GRANT SELECT ON ALL TABLES IN SCHEMA public TO anon;
|
|
@@ -246,7 +282,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
|
|
|
246
282
|
{ stdio: "pipe", encoding: "utf8", env: pgEnv })
|
|
247
283
|
}
|
|
248
284
|
|
|
249
|
-
const LOCAL_JWT_SECRET =
|
|
285
|
+
const LOCAL_JWT_SECRET = devJwtSecret(cwd)
|
|
250
286
|
const authDbURL = dbURL.includes("?")
|
|
251
287
|
? `${dbURL}&search_path=auth`
|
|
252
288
|
: `${dbURL}?search_path=auth`
|
|
@@ -264,7 +300,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
|
|
|
264
300
|
mkdirSync(supatypeDir, { recursive: true })
|
|
265
301
|
|
|
266
302
|
const localStoragePath = config.storage?.provider !== "s3" ? join(stateRoot, "storage") : undefined
|
|
267
|
-
// Native Postgres builds don't include PostGIS
|
|
303
|
+
// Native Postgres builds don't include PostGIS, skip geo fields rather than failing.
|
|
268
304
|
const skipFieldKinds: ReadonlySet<string> = new Set(["geo", "vector"])
|
|
269
305
|
|
|
270
306
|
await runSchemaPush(cwd, engineBin, schemaPath, dbURL, manifestPath, adminConfigPath, localStoragePath, skipFieldKinds, config).catch(
|
|
@@ -305,7 +341,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
|
|
|
305
341
|
}
|
|
306
342
|
} catch (err) {
|
|
307
343
|
console.warn(
|
|
308
|
-
`[supatype] ⚠ Found ${functionsDir} but could not provision Deno
|
|
344
|
+
`[supatype] ⚠ Found ${functionsDir} but could not provision Deno, edge functions will not run.\n` +
|
|
309
345
|
` ${(err as Error).message}\n` +
|
|
310
346
|
" (Functions still appear in Studio; invocations need Deno.)",
|
|
311
347
|
)
|
|
@@ -340,14 +376,14 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
|
|
|
340
376
|
JWT_SECRET: LOCAL_JWT_SECRET,
|
|
341
377
|
PORT: REALTIME_PORT,
|
|
342
378
|
SLOT_NAME: "supatype_realtime",
|
|
343
|
-
PUBLICATION_NAME: "
|
|
379
|
+
PUBLICATION_NAME: "supatype_realtime",
|
|
344
380
|
},
|
|
345
381
|
})
|
|
346
382
|
realtimeProc.start()
|
|
347
383
|
console.log(`[supatype] Realtime service: ${realtimeUrl}`)
|
|
348
384
|
} catch (err) {
|
|
349
385
|
console.warn(
|
|
350
|
-
`[supatype] ⚠ Realtime unavailable
|
|
386
|
+
`[supatype] ⚠ Realtime unavailable, WebSocket subscriptions will not work.\n` +
|
|
351
387
|
` ${(err as Error).message}`,
|
|
352
388
|
)
|
|
353
389
|
}
|
|
@@ -472,8 +508,21 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
|
|
|
472
508
|
}
|
|
473
509
|
|
|
474
510
|
const postgrestEnv: Record<string, string> = {
|
|
475
|
-
PGRST_DB_URI:
|
|
476
|
-
|
|
511
|
+
PGRST_DB_URI: postgrestDbURL,
|
|
512
|
+
// Derived from schema.pg_schema (or schema.api_schemas), a hardcoded "public" here meant
|
|
513
|
+
// a non-default pg_schema pushed correctly and then answered PGRST106 on every request.
|
|
514
|
+
// Native dev has no masking extension either, so a schema with field rules is served from
|
|
515
|
+
// `api` here too: the case an "is the database external" rule would have got wrong.
|
|
516
|
+
PGRST_DB_SCHEMA: apiSchemaList(
|
|
517
|
+
config,
|
|
518
|
+
// The installed archive decides: one downloaded before the masking library was bundled
|
|
519
|
+
// has no extension, so its field rules are enforced by views instead.
|
|
520
|
+
fieldMaskingTierFromProject(cwd, config, nativeMaskLibraryPresent(pgBinDir)),
|
|
521
|
+
),
|
|
522
|
+
// Parity with self-host, which has always set this. Unqualified names in column defaults
|
|
523
|
+
// (`uuid_generate_v4()`) resolve here, and with a non-public pg_schema the request schema
|
|
524
|
+
// alone does not reach them.
|
|
525
|
+
PGRST_DB_EXTRA_SEARCH_PATH: "public,extensions",
|
|
477
526
|
PGRST_DB_ANON_ROLE: "anon",
|
|
478
527
|
PGRST_SERVER_PORT: postgrestPort,
|
|
479
528
|
PGRST_SERVER_HOST: "127.0.0.1",
|
|
@@ -547,7 +596,7 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
|
|
|
547
596
|
links,
|
|
548
597
|
anonKey,
|
|
549
598
|
serviceRoleKey,
|
|
550
|
-
hints: [`Postgres ${dbURL}`, `JWT secret:
|
|
599
|
+
hints: [`Postgres ${dbURL}`, `JWT secret: #${secretFingerprint(LOCAL_JWT_SECRET)} (in .env)`],
|
|
551
600
|
})
|
|
552
601
|
|
|
553
602
|
// ── Shutdown handler ──────────────────────────────────────────────────
|
|
@@ -595,9 +644,9 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticate
|
|
|
595
644
|
// Schema push (engine subprocess)
|
|
596
645
|
// ---------------------------------------------------------------------------
|
|
597
646
|
|
|
598
|
-
// Last successfully-pushed AST JSON
|
|
647
|
+
// Last successfully-pushed AST JSON, used to skip no-op re-fires.
|
|
599
648
|
let _lastPushedAst: string | null = null
|
|
600
|
-
// AST that failed on its last attempt
|
|
649
|
+
// AST that failed on its last attempt, always retried even if content is unchanged.
|
|
601
650
|
let _lastFailedAst: string | null = null
|
|
602
651
|
|
|
603
652
|
async function runSchemaPush(
|
|
@@ -621,7 +670,7 @@ async function runSchemaPush(
|
|
|
621
670
|
ast = filtered
|
|
622
671
|
if (adapted.length > 0) {
|
|
623
672
|
console.warn(
|
|
624
|
-
`[supatype] ⚠ ${adapted.length} field(s) replaced with JSONB
|
|
673
|
+
`[supatype] ⚠ ${adapted.length} field(s) replaced with JSONB, required extensions not available:\n` +
|
|
625
674
|
adapted.map((s: string) => ` ${s}`).join("\n"),
|
|
626
675
|
)
|
|
627
676
|
}
|
|
@@ -695,7 +744,7 @@ async function runSchemaPush(
|
|
|
695
744
|
{ cwd, stdio: "pipe", encoding: "utf8" },
|
|
696
745
|
)
|
|
697
746
|
if (genResult.status !== 0) {
|
|
698
|
-
console.warn("[supatype] Manifest generation failed
|
|
747
|
+
console.warn("[supatype] Manifest generation failed, server routing may be stale.")
|
|
699
748
|
}
|
|
700
749
|
|
|
701
750
|
// Generate admin config (for Studio). Engine writes to stdout.
|
|
@@ -840,7 +889,7 @@ async function extractPostgresArchive(
|
|
|
840
889
|
}
|
|
841
890
|
|
|
842
891
|
// ---------------------------------------------------------------------------
|
|
843
|
-
// PostgREST resolver
|
|
892
|
+
// PostgREST resolver: downloads from GitHub releases if not cached
|
|
844
893
|
// ---------------------------------------------------------------------------
|
|
845
894
|
|
|
846
895
|
const POSTGREST_DEFAULT_VERSION = "12.2.3"
|
|
@@ -981,12 +1030,12 @@ function repairWindowsPostgrestRuntime(cacheDir: string, archivePath: string, bi
|
|
|
981
1030
|
}
|
|
982
1031
|
|
|
983
1032
|
// ---------------------------------------------------------------------------
|
|
984
|
-
// Local-dev JWT generator (no external dep
|
|
1033
|
+
// Local-dev JWT generator (no external dep, pure crypto)
|
|
985
1034
|
// ---------------------------------------------------------------------------
|
|
986
1035
|
|
|
987
1036
|
|
|
988
1037
|
// ---------------------------------------------------------------------------
|
|
989
|
-
// AST adaptation
|
|
1038
|
+
// AST adaptation: replace extension-dependent fields with JSONB fallbacks
|
|
990
1039
|
// ---------------------------------------------------------------------------
|
|
991
1040
|
|
|
992
1041
|
interface AstField { kind: string; required?: boolean; [k: string]: unknown }
|
package/src/commands/doctor.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Command } from "commander"
|
|
2
2
|
import { loadConfig, loadSchemaAst } from "../config.js"
|
|
3
3
|
import { info, plain } from "../ui/messages.js"
|
|
4
|
-
import { schemaPathFromProject } from "../project-config.js"
|
|
4
|
+
import { hooksPathFromProject, schemaPathFromProject, serviceRoleRoutes } from "../project-config.js"
|
|
5
5
|
import { resolveTarget, targetSchemaDoctor, schemaPgSchema } from "../resolve-target.js"
|
|
6
6
|
import { loadProjectLink } from "../link.js"
|
|
7
7
|
import { resolveHostEngineDatabaseUrl } from "../dev-compose.js"
|
|
8
|
+
import { hooksReport, type HooksReport } from "../model-hooks.js"
|
|
9
|
+
import { checkServiceRoleRoutes, type ServiceRoleProblems } from "../service-role-check.js"
|
|
8
10
|
|
|
9
11
|
interface DoctorItem {
|
|
10
12
|
kind: string
|
|
@@ -20,12 +22,15 @@ interface DoctorReport {
|
|
|
20
22
|
unmanagedDrift: DoctorItem[]
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
/** Exported for tests: the label form is easy to get subtly wrong per item kind. */
|
|
26
|
+
export function printSection(title: string, items: DoctorItem[]): void {
|
|
24
27
|
if (items.length === 0) return
|
|
25
28
|
plain(`\n${title} (${items.length}):\n`)
|
|
26
29
|
for (const item of items) {
|
|
27
30
|
const fields = item.fields.length > 0 ? ` (${item.fields.join(", ")})` : ""
|
|
28
|
-
|
|
31
|
+
// A table's `name` *is* its table, so the usual `table.name` form renders "widget.widget".
|
|
32
|
+
const label = item.table === item.name ? item.name : `${item.table}.${item.name}`
|
|
33
|
+
plain(` • ${label}${fields}`)
|
|
29
34
|
plain(` ${item.message}`)
|
|
30
35
|
}
|
|
31
36
|
}
|
|
@@ -82,6 +87,9 @@ export function registerDoctor(program: Command): void {
|
|
|
82
87
|
})) as DoctorReport
|
|
83
88
|
}
|
|
84
89
|
|
|
90
|
+
printHooks(hooksReport(cwd, hooksPathFromProject(config, cwd), ast))
|
|
91
|
+
printServiceRoleGrants(checkServiceRoleRoutes(config, cwd), serviceRoleRoutes(config))
|
|
92
|
+
|
|
85
93
|
printSection("Missing (in AST, not in DB)", report.missing ?? [])
|
|
86
94
|
printSection("Stale managed (stamped, not in AST)", report.staleManaged ?? [])
|
|
87
95
|
printSection("Unmanaged drift (manual decision)", report.unmanagedDrift ?? [])
|
|
@@ -101,3 +109,61 @@ export function registerDoctor(program: Command): void {
|
|
|
101
109
|
}
|
|
102
110
|
})
|
|
103
111
|
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Whether declared hooks can actually run.
|
|
115
|
+
*
|
|
116
|
+
* Worth its own section because every failure here is silent: a hook whose function is missing, or a
|
|
117
|
+
* stack with functions switched off, produces no error anywhere, the write just succeeds
|
|
118
|
+
* unvalidated. Drift you cannot see is the thing doctor exists for.
|
|
119
|
+
*/
|
|
120
|
+
export function printHooks(report: HooksReport): void {
|
|
121
|
+
if (report.declared.length === 0) return
|
|
122
|
+
|
|
123
|
+
plain(`\nHooks (${report.declared.length}):\n`)
|
|
124
|
+
for (const hook of report.declared) {
|
|
125
|
+
const broken = report.missing.some(
|
|
126
|
+
(m) => m.model === hook.model && m.event === hook.event,
|
|
127
|
+
)
|
|
128
|
+
plain(` ${broken ? "✗" : "•"} ${hook.model}.${hook.event} → ${hook.function}`)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (report.missing.length > 0) {
|
|
132
|
+
plain("\n Those marked ✗ name a function that does not exist, so they never fire.")
|
|
133
|
+
plain(" Create it with: supatype hooks new <name>")
|
|
134
|
+
}
|
|
135
|
+
if (report.functionsDisabled) {
|
|
136
|
+
plain("\n functions_enabled is false in .supatype/manifest.json, every hook is inert.")
|
|
137
|
+
plain(" Regenerate the stack config with: supatype self-host compose")
|
|
138
|
+
}
|
|
139
|
+
if (report.mapMissing) {
|
|
140
|
+
plain("\n .supatype/manifest.json carries no hook map, so the server has nothing to call.")
|
|
141
|
+
plain(" Run: supatype push")
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Report which functions may see the service-role key, and which grants do nothing.
|
|
147
|
+
*
|
|
148
|
+
* Worth printing even when everything resolves: this is the list of functions that can read and write
|
|
149
|
+
* past every access rule in the schema, and "which ones are those again?" should be answerable without
|
|
150
|
+
* opening the config.
|
|
151
|
+
*/
|
|
152
|
+
export function printServiceRoleGrants(
|
|
153
|
+
problems: ServiceRoleProblems,
|
|
154
|
+
declared: readonly string[],
|
|
155
|
+
): void {
|
|
156
|
+
if (declared.length === 0) return
|
|
157
|
+
|
|
158
|
+
plain(`\nService-role grants (${declared.length}):\n`)
|
|
159
|
+
const broken = new Set(problems.missing)
|
|
160
|
+
for (const name of declared) {
|
|
161
|
+
plain(` ${broken.has(name) ? "✗" : "•"} ${name}`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (broken.size > 0) {
|
|
165
|
+
plain("\n Those marked ✗ match no function, so they grant nothing, the function reads no key.")
|
|
166
|
+
}
|
|
167
|
+
for (const warning of problems.warnings) plain(warning)
|
|
168
|
+
plain("\n These functions bypass every access rule in the schema. Anything not listed cannot.")
|
|
169
|
+
}
|
package/src/commands/engine.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { ensureBinary } from "../ensure-binary.js"
|
|
|
16
16
|
import {
|
|
17
17
|
functionsPathCandidatesFromProject,
|
|
18
18
|
preferredFunctionsPathFromProject,
|
|
19
|
+
serviceRoleRoutes,
|
|
19
20
|
} from "../project-config.js"
|
|
20
21
|
import {
|
|
21
22
|
discoverTsFunctionsInDir,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
import { loadProjectLink } from "../link.js"
|
|
25
26
|
import { resolveTarget } from "../resolve-target.js"
|
|
26
27
|
import { targetFetch } from "../target-client.js"
|
|
28
|
+
import { ensureFunctionsDenoTypes } from "../functions-deno-types.js"
|
|
27
29
|
import { error, info, plain } from "../ui/messages.js"
|
|
28
30
|
import { nextSteps } from "../ui/next-steps.js"
|
|
29
31
|
|
|
@@ -136,22 +138,23 @@ function scaffoldFunction(cwd: string, name: string): void {
|
|
|
136
138
|
|
|
137
139
|
mkdirSync(fnDir, { recursive: true })
|
|
138
140
|
|
|
139
|
-
const indexContent = `// ${name}
|
|
141
|
+
const indexContent = `// ${name}, Supatype Edge Function
|
|
140
142
|
// Docs: https://supatype.com/docs/edge-functions
|
|
141
143
|
|
|
142
144
|
export default async function handler(req: Request): Promise<Response> {
|
|
143
|
-
const { method
|
|
145
|
+
const { method } = req
|
|
146
|
+
const url = Deno.env.get("SUPATYPE_URL")
|
|
144
147
|
|
|
145
148
|
// Example: read request body for POST requests
|
|
146
149
|
if (method === "POST") {
|
|
147
150
|
const body = await req.json()
|
|
148
|
-
return new Response(JSON.stringify({ message: "Hello from ${name}!", received: body }), {
|
|
151
|
+
return new Response(JSON.stringify({ message: "Hello from ${name}!", received: body, url }), {
|
|
149
152
|
status: 200,
|
|
150
153
|
headers: { "Content-Type": "application/json" },
|
|
151
154
|
})
|
|
152
155
|
}
|
|
153
156
|
|
|
154
|
-
return new Response(JSON.stringify({ message: "Hello from ${name}!" }), {
|
|
157
|
+
return new Response(JSON.stringify({ message: "Hello from ${name}!", url }), {
|
|
155
158
|
status: 200,
|
|
156
159
|
headers: { "Content-Type": "application/json" },
|
|
157
160
|
})
|
|
@@ -181,13 +184,27 @@ export default async function handler(req: Request): Promise<Response> {
|
|
|
181
184
|
)
|
|
182
185
|
}
|
|
183
186
|
|
|
187
|
+
const denoTypes = ensureFunctionsDenoTypes(cwd, functionsDir)
|
|
188
|
+
|
|
184
189
|
const functionsDirLabel = relativeFunctionsDir(cwd, functionsDir)
|
|
185
190
|
info(`Created function: ${functionsDirLabel}/${name}/index.ts`)
|
|
186
|
-
|
|
191
|
+
if (denoTypes.wroteDenoDts) {
|
|
192
|
+
info(`Added Deno IDE types: ${functionsDirLabel}/deno.d.ts`)
|
|
193
|
+
}
|
|
194
|
+
if (denoTypes.wroteTsconfig) {
|
|
195
|
+
info(`Added functions tsconfig: ${functionsDirLabel}/tsconfig.json`)
|
|
196
|
+
}
|
|
197
|
+
if (denoTypes.rootExclude === "updated") {
|
|
198
|
+
info('Updated root tsconfig.json to exclude "functions"')
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const steps = [
|
|
187
202
|
"Local dev: npx supatype functions serve",
|
|
188
203
|
`Invoke: npx supatype functions invoke ${name}`,
|
|
189
204
|
"Deploy: npx supatype functions deploy",
|
|
190
|
-
|
|
205
|
+
...denoTypes.hints,
|
|
206
|
+
]
|
|
207
|
+
nextSteps("Next steps:", steps)
|
|
191
208
|
}
|
|
192
209
|
|
|
193
210
|
// ─── Discover functions ──────────────────────────────────────────────────────
|
|
@@ -240,7 +257,7 @@ function discoverFunctions(cwd: string): DiscoveredFunction[] {
|
|
|
240
257
|
const stat = statSync(fullPath)
|
|
241
258
|
|
|
242
259
|
if (stat.isDirectory()) {
|
|
243
|
-
// Directory function
|
|
260
|
+
// Directory function: look for index.ts
|
|
244
261
|
const indexPath = join(fullPath, "index.ts")
|
|
245
262
|
if (existsSync(indexPath)) {
|
|
246
263
|
fns.push({ name: entry, entrypoint: indexPath, absPath: fullPath })
|
|
@@ -352,7 +369,7 @@ async function deploy(cwd: string, opts: { only?: string; env?: string; dryRun?:
|
|
|
352
369
|
}
|
|
353
370
|
|
|
354
371
|
if (opts.dryRun) {
|
|
355
|
-
plain("Dry run
|
|
372
|
+
plain("Dry run: the following functions would be deployed:\n")
|
|
356
373
|
for (const fn of fns) {
|
|
357
374
|
plain(` ${fn.name} → ${relative(cwd, fn.entrypoint)}`)
|
|
358
375
|
}
|
|
@@ -422,6 +439,9 @@ async function deployViaTarget(
|
|
|
422
439
|
source,
|
|
423
440
|
entrypoint: `${fn.name}/index.ts`,
|
|
424
441
|
}],
|
|
442
|
+
// Sent every deploy, and replaced wholesale by the platform: the config file is the whole
|
|
443
|
+
// truth, so a name removed from it must stop being entitled to the service-role key.
|
|
444
|
+
serviceRole: serviceRoleRoutesFor(cwd),
|
|
425
445
|
},
|
|
426
446
|
token: target.token!,
|
|
427
447
|
orgId: target.orgId,
|
|
@@ -479,6 +499,9 @@ async function deployCloud(cwd: string, fns: DiscoveredFunction[], env?: string)
|
|
|
479
499
|
source,
|
|
480
500
|
entrypoint: `${fn.name}/index.ts`,
|
|
481
501
|
}],
|
|
502
|
+
// Same grants as the target path: both reach the same endpoint, and a project deployed one
|
|
503
|
+
// way must not end up with a different credential surface than the other.
|
|
504
|
+
serviceRole: serviceRoleRoutesFor(cwd),
|
|
482
505
|
}),
|
|
483
506
|
signal: AbortSignal.timeout(60_000),
|
|
484
507
|
})
|
|
@@ -500,13 +523,27 @@ async function deployCloud(cwd: string, fns: DiscoveredFunction[], env?: string)
|
|
|
500
523
|
info(`Invoke: https://${linked.ref}.supatype.dev/functions/v1/<name>`)
|
|
501
524
|
}
|
|
502
525
|
|
|
526
|
+
/**
|
|
527
|
+
* The project's `functions.serviceRole` list, or undefined when the config cannot be read.
|
|
528
|
+
*
|
|
529
|
+
* Undefined rather than `[]`: the platform replaces the stored list wholesale when the field is present,
|
|
530
|
+
* so sending an empty array because a config failed to load would revoke every grant the project has.
|
|
531
|
+
*/
|
|
532
|
+
export function serviceRoleRoutesFor(cwd: string): string[] | undefined {
|
|
533
|
+
try {
|
|
534
|
+
return serviceRoleRoutes(loadConfig(cwd))
|
|
535
|
+
} catch {
|
|
536
|
+
return undefined
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
503
540
|
function readFunctionSource(fn: DiscoveredFunction): string {
|
|
504
541
|
const stat = statSync(fn.absPath)
|
|
505
542
|
if (stat.isFile()) {
|
|
506
543
|
return readFileSync(fn.absPath, "utf8")
|
|
507
544
|
}
|
|
508
545
|
|
|
509
|
-
// Directory function
|
|
546
|
+
// Directory function: read all .ts files
|
|
510
547
|
const files: Record<string, string> = {}
|
|
511
548
|
const entries = readdirSync(fn.absPath, { recursive: true }) as string[]
|
|
512
549
|
for (const entry of entries) {
|
|
@@ -570,7 +607,7 @@ async function listFunctions(cwd: string): Promise<void> {
|
|
|
570
607
|
plain(` ${"─".repeat(28)} ${"─".repeat(24)} ${"─".repeat(20)} ${"─".repeat(12)}`)
|
|
571
608
|
|
|
572
609
|
for (const fn of data) {
|
|
573
|
-
const deployed = fn.deployedAt ? new Date(fn.deployedAt).toLocaleString() : "
|
|
610
|
+
const deployed = fn.deployedAt ? new Date(fn.deployedAt).toLocaleString() : "-"
|
|
574
611
|
plain(
|
|
575
612
|
` ${fn.name.padEnd(28)} ${deployed.padEnd(24)} ${String(fn.invocations24h ?? 0).padEnd(20)} ${fn.avgDurationMs ?? 0}ms`,
|
|
576
613
|
)
|
package/src/commands/generate.ts
CHANGED
|
@@ -2,9 +2,10 @@ import type { Command } from "commander"
|
|
|
2
2
|
import { mkdirSync, writeFileSync } from "node:fs"
|
|
3
3
|
import { resolve, dirname } from "node:path"
|
|
4
4
|
import { loadConfig, loadSchemaAst } from "../config.js"
|
|
5
|
-
import { schemaPathFromProject } from "../project-config.js"
|
|
5
|
+
import { hooksPathFromProject, schemaPathFromProject } from "../project-config.js"
|
|
6
6
|
import { ensureEngine, engineRequest } from "../engine-client.js"
|
|
7
7
|
import { generateClientAugmentation } from "../augmentation-generator.js"
|
|
8
|
+
import { writeHooksModule } from "../model-hooks.js"
|
|
8
9
|
import { error, info } from "../ui/messages.js"
|
|
9
10
|
|
|
10
11
|
export function registerGenerate(program: Command): void {
|
|
@@ -41,5 +42,8 @@ export function registerGenerate(program: Command): void {
|
|
|
41
42
|
mkdirSync(dirname(augmentationOutPath), { recursive: true })
|
|
42
43
|
writeFileSync(augmentationOutPath, augmentationCode, "utf8")
|
|
43
44
|
info(`Client augmentation written to ${outputClientPath}`)
|
|
45
|
+
|
|
46
|
+
const hooksPath = writeHooksModule(cwd, hooksPathFromProject(config, cwd), ast)
|
|
47
|
+
if (hooksPath !== null) info(`Hook handler types written to ${hooksPath}`)
|
|
44
48
|
})
|
|
45
49
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `supatype hooks`: scaffold and inspect model hooks.
|
|
3
|
+
*
|
|
4
|
+
* Separate from `supatype functions` because the two are different things wearing the same runtime.
|
|
5
|
+
* A function is a **public endpoint**: anyone holding the anon key can invoke it. A hook is
|
|
6
|
+
* **procedural**: only the API server calls it, around a write, and the gateway refuses the route
|
|
7
|
+
* from outside. Keeping them in separate directories is what makes that boundary structural instead
|
|
8
|
+
* of a list somebody has to maintain.
|
|
9
|
+
*/
|
|
10
|
+
import type { Command } from "commander"
|
|
11
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs"
|
|
12
|
+
import { join, relative } from "node:path"
|
|
13
|
+
import { loadConfig, loadSchemaAst } from "../config.js"
|
|
14
|
+
import { hooksPathFromProject, schemaPathFromProject } from "../project-config.js"
|
|
15
|
+
import { declaredHooks, hooksReport } from "../model-hooks.js"
|
|
16
|
+
import { error, info, plain } from "../ui/messages.js"
|
|
17
|
+
|
|
18
|
+
export function registerHooks(program: Command): void {
|
|
19
|
+
const hooksCmd = program
|
|
20
|
+
.command("hooks")
|
|
21
|
+
.description("Manage model hooks (procedural handlers the API runs around a write)")
|
|
22
|
+
|
|
23
|
+
hooksCmd
|
|
24
|
+
.command("new <name>")
|
|
25
|
+
.description("Scaffold a hook handler")
|
|
26
|
+
.action((name: string) => {
|
|
27
|
+
scaffoldHook(process.cwd(), name)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
hooksCmd
|
|
31
|
+
.command("list")
|
|
32
|
+
.description("Show the hooks the schema declares and whether each one can run")
|
|
33
|
+
.action(() => {
|
|
34
|
+
const cwd = process.cwd()
|
|
35
|
+
const config = loadConfig(cwd)
|
|
36
|
+
const ast = loadSchemaAst(schemaPathFromProject(config, cwd), cwd)
|
|
37
|
+
const report = hooksReport(cwd, hooksPathFromProject(config, cwd), ast)
|
|
38
|
+
|
|
39
|
+
if (report.declared.length === 0) {
|
|
40
|
+
info("No model declares a hook.")
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
for (const hook of report.declared) {
|
|
44
|
+
const broken = report.missing.some((m) => m.model === hook.model && m.event === hook.event)
|
|
45
|
+
plain(` ${broken ? "✗" : "•"} ${hook.model}.${hook.event} → ${hook.function}`)
|
|
46
|
+
}
|
|
47
|
+
if (report.missing.length > 0) {
|
|
48
|
+
plain("\n ✗ names a handler that does not exist, so that hook never fires.")
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function scaffoldHook(cwd: string, name: string): void {
|
|
54
|
+
const config = loadConfig(cwd)
|
|
55
|
+
const hooksDir = hooksPathFromProject(config, cwd)
|
|
56
|
+
const dir = join(hooksDir, name)
|
|
57
|
+
|
|
58
|
+
if (existsSync(dir)) {
|
|
59
|
+
error(`Hook "${name}" already exists at ${relative(cwd, dir)}`)
|
|
60
|
+
process.exit(1)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const ast = loadSchemaAst(schemaPathFromProject(config, cwd), cwd)
|
|
64
|
+
const declared = declaredHooks(ast).filter((hook) => hook.function === name)
|
|
65
|
+
|
|
66
|
+
mkdirSync(dir, { recursive: true })
|
|
67
|
+
writeFileSync(join(dir, "index.ts"), handlerTemplate(name, declared), "utf8")
|
|
68
|
+
|
|
69
|
+
info(`Created hook: ${relative(cwd, join(dir, "index.ts"))}`)
|
|
70
|
+
if (declared.length === 0) {
|
|
71
|
+
// Scaffolding a handler nothing calls is easy to do and impossible to notice, since a hook that
|
|
72
|
+
// is never referenced simply never runs.
|
|
73
|
+
plain("")
|
|
74
|
+
plain(" No model references it yet. Add it to a model's meta:")
|
|
75
|
+
plain(` hooks: { beforeChange: "${name}" }`)
|
|
76
|
+
plain(" then run `supatype push` to generate its types and register it.")
|
|
77
|
+
} else {
|
|
78
|
+
plain("")
|
|
79
|
+
for (const hook of declared) {
|
|
80
|
+
plain(` Wired to ${hook.model}.${hook.event}`)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The scaffold deliberately imports from `../_supatype/hooks.ts`, which `push` generates.
|
|
87
|
+
*
|
|
88
|
+
* Before the first push that file does not exist, so the handler will not compile, which is the
|
|
89
|
+
* honest state: the types come from the schema, and a handler typed against a model that has not been
|
|
90
|
+
* pushed is a handler typed against a guess.
|
|
91
|
+
*/
|
|
92
|
+
function handlerTemplate(name: string, declared: { model: string; event: string }[]): string {
|
|
93
|
+
const event = declared[0]?.event ?? "beforeChange"
|
|
94
|
+
const model = declared[0]?.model ?? "YourModel"
|
|
95
|
+
|
|
96
|
+
if (event.startsWith("after")) {
|
|
97
|
+
return `// ${name}: model hook (${event})
|
|
98
|
+
//
|
|
99
|
+
// Runs after the write has succeeded. It cannot change or undo it: a rejection here would be a
|
|
100
|
+
// rejection of something that already happened. Failures are logged, not surfaced to the caller.
|
|
101
|
+
|
|
102
|
+
import { hook, type AfterChange } from "../_supatype/hooks.ts"
|
|
103
|
+
|
|
104
|
+
const handler: AfterChange<"your_table"> = async (ctx) => {
|
|
105
|
+
console.log(\`\${ctx.table} \${ctx.operation} by \${ctx.user?.sub ?? "anon"}\`)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default hook(handler)
|
|
109
|
+
`
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return `// ${name}: model hook (${event}) for ${model}
|
|
113
|
+
//
|
|
114
|
+
// Runs before the write reaches Postgres, so it can reject it or rewrite the body.
|
|
115
|
+
//
|
|
116
|
+
// Not a security boundary: hooks fire for writes through the API, and direct SQL, seeds and anything
|
|
117
|
+
// holding the service role bypass them. Invariants belong in the model's access rules or a CHECK.
|
|
118
|
+
|
|
119
|
+
import { hook, type BeforeChange } from "../_supatype/hooks.ts"
|
|
120
|
+
|
|
121
|
+
const handler: BeforeChange<"your_table"> = async (ctx) => {
|
|
122
|
+
if (ctx.operation === "insert") {
|
|
123
|
+
// \`ctx.rows\` is the submitted rows, typed from the model.
|
|
124
|
+
return { rows: ctx.rows }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// \`ctx.patch\` is the submitted partial; \`ctx.previous()\` reads the rows about to change.
|
|
128
|
+
// Those come back as stored, unmasked, so do not echo them into a rejection message.
|
|
129
|
+
return {}
|
|
130
|
+
|
|
131
|
+
// Reject with a message the caller sees, and a status of your choosing:
|
|
132
|
+
// return { reject: "A post needs a title" }
|
|
133
|
+
// return { reject: { message: "Already exists", status: 409, code: "duplicate" } }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default hook(handler)
|
|
137
|
+
`
|
|
138
|
+
}
|