@podosoft/podokit 1.4.2 → 1.6.0
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/README.md +2 -1
- package/dist/app-proxy-timeouts.d.ts +27 -0
- package/dist/app-proxy-timeouts.js +180 -0
- package/dist/assemble.d.ts +5 -2
- package/dist/assemble.js +14 -4
- package/dist/deploy-checks.d.ts +44 -0
- package/dist/deploy-checks.js +267 -0
- package/dist/deploy-compose-profile.d.ts +6 -2
- package/dist/deploy-compose-profile.js +15 -6
- package/dist/deploy-compose-render.d.ts +12 -4
- package/dist/deploy-compose-render.js +47 -17
- package/dist/deploy-compose.d.ts +2 -5
- package/dist/deploy-compose.js +7 -2
- package/dist/deploy-profile.d.ts +6 -2
- package/dist/deploy-profile.js +11 -7
- package/dist/deploy-render.d.ts +4 -1
- package/dist/deploy-render.js +28 -20
- package/dist/deploy-schema.d.ts +61 -0
- package/dist/deploy-schema.js +157 -1
- package/dist/deploy.d.ts +2 -5
- package/dist/deploy.js +5 -2
- package/dist/dev.d.ts +21 -0
- package/dist/dev.js +78 -3
- package/dist/eject.js +2 -0
- package/dist/index.js +85 -5
- package/dist/lib.d.ts +2 -0
- package/dist/lib.js +5 -2
- package/dist/lockfile.d.ts +24 -0
- package/dist/lockfile.js +77 -5
- package/dist/provider.d.ts +1 -0
- package/dist/provider.js +12 -4
- package/dist/remove.js +1 -1
- package/dist/template-files.d.ts +42 -0
- package/dist/template-files.js +102 -0
- package/dist/templates/fullstack/AGENTS.md +44 -7
- package/dist/templates/fullstack/README.md +8 -7
- package/dist/templates/fullstack/apps/api/package.json +4 -4
- package/dist/templates/fullstack/apps/api/scripts/build.extensions.mjs +6 -0
- package/dist/templates/fullstack/apps/api/scripts/build.mjs +65 -16
- package/dist/templates/fullstack/apps/api/scripts/run-migrations.mjs +7 -3
- package/dist/templates/fullstack/apps/api/scripts/verify-api-contract.ts +5 -1
- package/dist/templates/fullstack/apps/api/src/app.extensions.ts +7 -4
- package/dist/templates/fullstack/apps/api/src/app.spec.ts +22 -0
- package/dist/templates/fullstack/apps/api/src/app.ts +47 -28
- package/dist/templates/fullstack/apps/api/src/build.spec.ts +118 -0
- package/dist/templates/fullstack/apps/api/src/config/env.extensions.ts +20 -0
- package/dist/templates/fullstack/apps/api/src/config/env.validation.spec.ts +76 -0
- package/dist/templates/fullstack/apps/api/src/config/env.validation.ts +46 -3
- package/dist/templates/fullstack/apps/api/src/config/provider-selection.spec.ts +53 -0
- package/dist/templates/fullstack/apps/api/src/config/provider-selection.ts +79 -0
- package/dist/templates/fullstack/apps/api/src/config/providers.ts +8 -2
- package/dist/templates/fullstack/apps/api/src/config/server-options.spec.ts +17 -6
- package/dist/templates/fullstack/apps/api/src/config/server-options.ts +5 -8
- package/dist/templates/fullstack/apps/api/src/core/api-contract.extensions.ts +6 -0
- package/dist/templates/fullstack/apps/api/src/core/api-contract.spec.ts +29 -0
- package/dist/templates/fullstack/apps/api/src/core/api-contract.ts +39 -2
- package/dist/templates/fullstack/apps/api/src/core/client-address.spec.ts +82 -0
- package/dist/templates/fullstack/apps/api/src/core/client-address.ts +68 -0
- package/dist/templates/fullstack/apps/api/src/core/services.ts +43 -0
- package/dist/templates/fullstack/apps/api/src/database/data-source.spec.ts +27 -0
- package/dist/templates/fullstack/apps/api/src/database/data-source.ts +7 -10
- package/dist/templates/fullstack/apps/api/src/database/database.spec.ts +12 -0
- package/dist/templates/fullstack/apps/api/src/database/database.ts +2 -3
- package/dist/templates/fullstack/apps/api/src/database/entities.ts +12 -0
- package/dist/templates/fullstack/apps/api/src/database/migrate.ts +12 -2
- package/dist/templates/fullstack/apps/api/src/database/migration-files.spec.ts +116 -0
- package/dist/templates/fullstack/apps/api/src/database/migration-files.ts +134 -0
- package/dist/templates/fullstack/apps/api/src/database/migration-readiness.spec.ts +36 -0
- package/dist/templates/fullstack/apps/api/src/database/migration-readiness.ts +25 -0
- package/dist/templates/fullstack/apps/api/src/database/run-migrations.spec.ts +12 -1
- package/dist/templates/fullstack/apps/api/src/database/sqlite-migrator.ts +2 -36
- package/dist/templates/fullstack/apps/api/src/main.ts +2 -2
- package/dist/templates/fullstack/apps/api/tsconfig.json +2 -0
- package/dist/templates/fullstack/apps/web/Dockerfile +4 -0
- package/dist/templates/fullstack/apps/web/package.json +1 -1
- package/dist/templates/fullstack/apps/web/src/app.css +3 -0
- package/dist/templates/fullstack/apps/web/src/app.extensions.css +11 -0
- package/dist/templates/fullstack/apps/web/src/lib/components/site-runtime.svelte +3 -1
- package/dist/templates/fullstack/apps/web/src/lib/server/api.test.ts +19 -0
- package/dist/templates/fullstack/apps/web/src/lib/server/api.ts +4 -1
- package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.test.ts +370 -36
- package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.ts +250 -41
- package/dist/templates/fullstack/apps/web/src/lib/server/body-limits.ts +5 -2
- package/dist/templates/fullstack/apps/web/src/lib/server/proxy-timeouts.ts +23 -0
- package/dist/templates/fullstack/apps/web/tsconfig.json +1 -1
- package/dist/templates/fullstack/apps/web/vite.config.test.ts +33 -0
- package/dist/templates/fullstack/apps/web/vite.config.ts +55 -13
- package/dist/templates/fullstack/apps/web/vite.extensions.ts +23 -0
- package/dist/templates/fullstack/compose.dev.yaml +7 -4
- package/dist/templates/fullstack/dot-agents/skills/podokit-deploy/SKILL.md +2 -2
- package/dist/templates/fullstack/dot-agents/skills/podokit-deploy/references/profile.md +12 -3
- package/dist/templates/fullstack/dot-claude/skills/podokit-update/SKILL.md +5 -2
- package/dist/templates/fullstack/dot-env.docker.example +7 -2
- package/dist/templates/fullstack/dot-env.example +21 -1
- package/dist/templates/fullstack/dot-podokit/dev.json +1 -1
- package/dist/templates/fullstack/infra/k3s/configmap.yaml +4 -0
- package/dist/templates/fullstack/tests/README.md +40 -1
- package/dist/templates/fullstack/tests/helpers/app-paths.ts +18 -0
- package/dist/templates/fullstack/tests/helpers/paths.ts +33 -0
- package/dist/templates/fullstack/tests/package.json +2 -2
- package/dist/templates/fullstack/tests/playwright.config.ts +7 -3
- package/dist/templates/fullstack/tests/playwright.extensions.ts +88 -1
- package/dist/templates/fullstack/tests/ui/smoke.ui.spec.ts +5 -4
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/account-sessions/account-sessions.routes.spec.ts +152 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/account-sessions/account-sessions.routes.ts +112 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.revoker.ts +24 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.service.spec.ts +18 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.service.ts +36 -13
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin.module.ts +13 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.test.ts +37 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +15 -6
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/account-sessions.test.ts +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/account-sessions.ts +34 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-flag-settings.test.ts +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-flag-settings.ts +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-flags.ts +12 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-nav.ts +11 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/nav.test.ts +39 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/nav.ts +35 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/registry.svelte.ts +15 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/account-page.svelte +73 -39
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/admin-sidebar.svelte +2 -23
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.svelte +7 -2
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.test.ts +30 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/impersonation-banner.svelte +3 -2
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/settings/app-flags-settings.svelte +65 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +32 -23
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.test.ts +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/site-footer.svelte +4 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/site-runtime.svelte +3 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-toolbar.svelte +3 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/en.json +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/ko.json +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/app-hooks.ts +11 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/non-indexable-paths.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/search-indexing.test.ts +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/search-indexing.ts +5 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(admin)/admin/settings/+page.svelte +1 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/account/+page.server.ts +2 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/account/+page.svelte +1 -5
- package/dist/templates/modules/admin-dashboard/files/dot-claude/skills/podokit-data-table/SKILL.md +2 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/account-sessions.api.spec.ts +55 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +1 -1
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +159 -170
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +72 -81
- package/dist/templates/modules/admin-dashboard/files/tests/api/email.api.spec.ts +1 -6
- package/dist/templates/modules/admin-dashboard/files/tests/api/profile-image.api.spec.ts +1 -1
- package/dist/templates/modules/admin-dashboard/files/tests/api/site-settings.api.spec.ts +43 -53
- package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +29 -1
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +24 -6
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/app-accounts.ts +24 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/devices.ts +55 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/disposable-users.ts +5 -3
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/mailpit.ts +3 -9
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/sms.ts +2 -4
- package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +11 -5
- package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +3 -10
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account-flows.ui.spec.ts +4 -5
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account-sessions.ui.spec.ts +46 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +62 -59
- package/dist/templates/modules/admin-dashboard/files/tests/ui/app-flags.ui.spec.ts +63 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/appearance.ui.spec.ts +12 -16
- package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +3 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +32 -35
- package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +4 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email-admin.ui.spec.ts +1 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +1 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/general-effects.ui.spec.ts +82 -91
- package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +6 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +16 -20
- package/dist/templates/modules/admin-dashboard/files/tests/ui/impersonation.ui.spec.ts +6 -3
- package/dist/templates/modules/admin-dashboard/files/tests/ui/login-methods.ui.spec.ts +2 -3
- package/dist/templates/modules/admin-dashboard/files/tests/ui/oidc.ui.spec.ts +2 -4
- package/dist/templates/modules/admin-dashboard/files/tests/ui/organizations.ui.spec.ts +5 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/passkey.ui.spec.ts +7 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/same-origin-requests.ui.spec.ts +89 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +5 -4
- package/dist/templates/modules/admin-dashboard/files/tests/ui/settings-dialogs.ui.spec.ts +67 -73
- package/dist/templates/modules/admin-dashboard/files/tests/ui/settings.ui.spec.ts +84 -93
- package/dist/templates/modules/admin-dashboard/files/tests/ui/table-toolbar.ui.spec.ts +33 -7
- package/dist/templates/modules/admin-dashboard/files/tests/ui/theme-mode.ui.spec.ts +29 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/two-factor.ui.spec.ts +51 -54
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +10 -9
- package/dist/templates/modules/admin-dashboard/module.manifest.json +7 -2
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +2 -1
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +5 -1
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.spec.ts +33 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +20 -1
- package/dist/templates/modules/audit-log/module.manifest.json +15 -3
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-app-name.spec.ts +21 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-config-store.ts +13 -1
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-environment.ts +9 -2
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.ts +19 -3
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.module.ts +6 -1
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.runtime.spec.ts +10 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.service.spec.ts +54 -6
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.service.ts +26 -4
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +15 -8
- package/dist/templates/modules/auth/files/apps/api/src/auth/revoke-other-sessions.spec.ts +56 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/revoke-other-sessions.ts +45 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/schema-recheck.spec.ts +86 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/schema-recheck.ts +44 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.service.ts +2 -1
- package/dist/templates/modules/auth/files/apps/api/src/migrate.ts +3 -1
- package/dist/templates/modules/auth/files/apps/api/src/settings/app-flags.ts +9 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.spec.ts +43 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.ts +30 -5
- package/dist/templates/modules/auth/module.manifest.json +8 -2
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/bullmq-job.queue.spec.ts +57 -1
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/bullmq-job.queue.ts +93 -9
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.spec.ts +40 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +52 -5
- package/dist/templates/modules/bullmq/files/apps/api/src/main-worker.ts +9 -7
- package/dist/templates/modules/bullmq/files/apps/api/src/worker.extensions.ts +13 -0
- package/dist/templates/modules/bullmq/module.manifest.json +5 -1
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.worker.spec.ts +2 -0
- package/dist/templates/modules/jobs-local/files/apps/api/src/jobs-local/local-job.queue.spec.ts +87 -1
- package/dist/templates/modules/jobs-local/files/apps/api/src/jobs-local/local-job.queue.ts +93 -3
- package/dist/templates/modules/jobs-local/module.manifest.json +3 -0
- package/dist/templates/modules/logging/files/apps/api/src/logging/logging.module.spec.ts +16 -0
- package/dist/templates/modules/logging/files/apps/api/src/logging/logging.module.ts +15 -10
- package/dist/templates/modules/mailer/module.manifest.json +1 -1
- package/dist/templates/modules/object-storage-local/files/tests/api/storage-local.api.spec.ts +1 -1
- package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +5 -4
- package/dist/templates/modules/object-storage-s3/files/tests/api/storage.api.spec.ts +1 -1
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.config.spec.ts +2 -59
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.config.ts +0 -65
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.identity.spec.ts +82 -21
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.identity.ts +54 -15
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.module.spec.ts +43 -5
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.module.ts +10 -2
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.routes.spec.ts +61 -3
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.routes.ts +48 -15
- package/dist/templates/modules/rate-limit/files/tests/api/rate-limit.api.spec.ts +63 -27
- package/dist/templates/modules/rate-limit/module.manifest.json +6 -9
- package/dist/templates/modules/sse/files/tests/api/events.api.spec.ts +1 -1
- package/dist/templates/todo/AGENTS.md +46 -6
- package/dist/templates/todo/README.md +6 -4
- package/dist/text-diff.d.ts +12 -0
- package/dist/text-diff.js +111 -0
- package/dist/update.d.ts +61 -3
- package/dist/update.js +152 -27
- package/package.json +2 -2
- package/dist/templates/modules/admin-dashboard/files/tests/api/exclusive.api.spec.ts +0 -75
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/exclusive.ts +0 -80
|
@@ -170,6 +170,7 @@ function parseComposeProfile(value) {
|
|
|
170
170
|
"port",
|
|
171
171
|
"webSocketPaths",
|
|
172
172
|
"apiPaths",
|
|
173
|
+
"edgeProxies",
|
|
173
174
|
"trustedProxyCidrs",
|
|
174
175
|
"gatewayImage",
|
|
175
176
|
], "exposure");
|
|
@@ -201,6 +202,8 @@ function parseComposeProfile(value) {
|
|
|
201
202
|
throw new Error("Deployment exposure.port must be a TCP port.");
|
|
202
203
|
const webSocketPaths = (0, websocket_paths_1.parseExactWebSocketPaths)(exposure.webSocketPaths ?? [], "exposure.webSocketPaths");
|
|
203
204
|
const apiPaths = (0, api_paths_1.parseApiPaths)(exposure.apiPaths ?? [], "exposure.apiPaths", webSocketPaths);
|
|
205
|
+
// A reverse proxy on the host is the expected front door, as bindAddress assumes.
|
|
206
|
+
const edgeProxies = (0, deploy_schema_1.parseEdgeProxies)(exposure, 1);
|
|
204
207
|
const trustedProxyCidrs = parseTrustedProxyCidrs(exposure.trustedProxyCidrs ?? [], "exposure.trustedProxyCidrs");
|
|
205
208
|
if (trustedProxyCidrs.length > 0 && webSocketPaths.length === 0 && apiPaths.length === 0) {
|
|
206
209
|
throw new Error("Deployment exposure.trustedProxyCidrs requires at least one exposure.webSocketPaths or exposure.apiPaths entry.");
|
|
@@ -231,6 +234,12 @@ function parseComposeProfile(value) {
|
|
|
231
234
|
if (typeof secrets.registryLogin !== "boolean") {
|
|
232
235
|
throw new Error("Deployment secrets.registryLogin must be a boolean.");
|
|
233
236
|
}
|
|
237
|
+
const runtimeConfig = (0, deploy_schema_1.parseRuntimeConfig)((0, deploy_schema_1.requiredRecord)(value, "runtimeConfig"));
|
|
238
|
+
const postgresRecord = (0, deploy_schema_1.requiredRecord)(dependencies, "postgres");
|
|
239
|
+
const postgres = {
|
|
240
|
+
...parseComposeDependency(postgresRecord, "dependencies.postgres", ["maxConnections", "appPools"]),
|
|
241
|
+
...(0, deploy_schema_1.parsePostgresBudget)(postgresRecord, "dependencies.postgres", runtimeConfig),
|
|
242
|
+
};
|
|
234
243
|
const profile = {
|
|
235
244
|
schemaVersion: 1,
|
|
236
245
|
driver: "docker-compose",
|
|
@@ -243,6 +252,7 @@ function parseComposeProfile(value) {
|
|
|
243
252
|
port,
|
|
244
253
|
webSocketPaths,
|
|
245
254
|
apiPaths,
|
|
255
|
+
edgeProxies,
|
|
246
256
|
trustedProxyCidrs,
|
|
247
257
|
gatewayImage,
|
|
248
258
|
},
|
|
@@ -254,7 +264,7 @@ function parseComposeProfile(value) {
|
|
|
254
264
|
: parseComposeWorkload((0, deploy_schema_1.requiredRecord)(workloads, "worker"), "workloads.worker"),
|
|
255
265
|
},
|
|
256
266
|
dependencies: {
|
|
257
|
-
postgres
|
|
267
|
+
postgres,
|
|
258
268
|
redis: parseComposeDependency((0, deploy_schema_1.requiredRecord)(dependencies, "redis"), "dependencies.redis"),
|
|
259
269
|
objectStorage,
|
|
260
270
|
},
|
|
@@ -267,7 +277,7 @@ function parseComposeProfile(value) {
|
|
|
267
277
|
? { migration: { command: (0, deploy_schema_1.parseCommand)(migration.command, "migration.command") } }
|
|
268
278
|
: {}),
|
|
269
279
|
...(sync ? { sync: parseSyncProfile(sync, "sync") } : {}),
|
|
270
|
-
runtimeConfig
|
|
280
|
+
runtimeConfig,
|
|
271
281
|
verification: (0, deploy_schema_1.parseVerification)((0, deploy_schema_1.requiredRecord)(value, "verification")),
|
|
272
282
|
};
|
|
273
283
|
return profile;
|
|
@@ -308,6 +318,7 @@ function buildDefaultComposeProfile(projectName, options) {
|
|
|
308
318
|
port: 8080,
|
|
309
319
|
webSocketPaths: [],
|
|
310
320
|
apiPaths: [],
|
|
321
|
+
edgeProxies: 1,
|
|
311
322
|
trustedProxyCidrs: [],
|
|
312
323
|
gatewayImage: "caddy:2.10-alpine",
|
|
313
324
|
},
|
|
@@ -355,8 +366,6 @@ function buildDefaultComposeProfile(projectName, options) {
|
|
|
355
366
|
runtimeConfig: {
|
|
356
367
|
NODE_ENV: "production",
|
|
357
368
|
CORS_ORIGIN: `https://${host}`,
|
|
358
|
-
ADDRESS_HEADER: "x-forwarded-for",
|
|
359
|
-
XFF_DEPTH: "1",
|
|
360
369
|
},
|
|
361
370
|
verification: {
|
|
362
371
|
baseUrl: `https://${host}`,
|
|
@@ -410,6 +419,8 @@ function initializeComposeProfile(projectRoot, name, options) {
|
|
|
410
419
|
draft.runtimeConfig = {
|
|
411
420
|
...draft.runtimeConfig,
|
|
412
421
|
...(usesAuth ? { BETTER_AUTH_URL: `https://${host}` } : {}),
|
|
422
|
+
// The API's fallback until an administrator saves the setting.
|
|
423
|
+
...(modules.has("audit-log") ? { AUDIT_LOG_ENABLED: "true" } : {}),
|
|
413
424
|
...(modules.has("rate-limit")
|
|
414
425
|
? {
|
|
415
426
|
RATE_LIMIT_TTL: "60",
|
|
@@ -418,8 +429,6 @@ function initializeComposeProfile(projectRoot, name, options) {
|
|
|
418
429
|
RATE_LIMIT_AUTH_TTL: "60",
|
|
419
430
|
RATE_LIMIT_AUTH_MAX: "20",
|
|
420
431
|
RATE_LIMIT_RUNTIME_MAX: "1000",
|
|
421
|
-
RATE_LIMIT_TRUSTED_PROXY_HOPS: "1",
|
|
422
|
-
RATE_LIMIT_PROXY_HEADER: "x-forwarded-for",
|
|
423
432
|
RATE_LIMIT_STORAGE_TIMEOUT_MS: "1000",
|
|
424
433
|
RATE_LIMIT_UNAVAILABLE_RETRY_AFTER: "1",
|
|
425
434
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DockerComposeProfileV1 } from "./deploy-compose-profile";
|
|
2
|
+
import { type AppProxyTimeouts } from "./app-proxy-timeouts";
|
|
2
3
|
/**
|
|
3
4
|
* Compose project rendering.
|
|
4
5
|
*
|
|
@@ -29,7 +30,14 @@ export declare function serviceName(profile: DockerComposeProfileV1, role: strin
|
|
|
29
30
|
* Configuration PodoKit derives rather than accepts, so a profile cannot point the
|
|
30
31
|
* app at the wrong service or port.
|
|
31
32
|
*/
|
|
32
|
-
export declare function derivedRuntimeConfig(profile: DockerComposeProfileV1): Record<string, string>;
|
|
33
|
+
export declare function derivedRuntimeConfig(profile: DockerComposeProfileV1, proxyTimeouts: AppProxyTimeouts): Record<string, string>;
|
|
34
|
+
/**
|
|
35
|
+
* The gateway replaces X-Forwarded-For with the one address it resolved. It keeps
|
|
36
|
+
* a trusted proxy's X-Forwarded-Proto and X-Forwarded-Host and otherwise writes its
|
|
37
|
+
* own, which name its plain-HTTP listener: behind an edge proxy it does not trust,
|
|
38
|
+
* the web server keeps its default origin (https and the Host header) instead.
|
|
39
|
+
*/
|
|
40
|
+
export declare function composeProxyTrust(profile: DockerComposeProfileV1): Record<string, string>;
|
|
33
41
|
/**
|
|
34
42
|
* Identity of everything the containers read but Compose cannot see a change in.
|
|
35
43
|
*
|
|
@@ -38,12 +46,12 @@ export declare function derivedRuntimeConfig(profile: DockerComposeProfileV1): R
|
|
|
38
46
|
* secret or a changed runtime config a definition change, so `up` actually rolls
|
|
39
47
|
* the containers instead of leaving stale values running.
|
|
40
48
|
*/
|
|
41
|
-
export declare function rolloutStateDigest(profile: DockerComposeProfileV1, secretIdentities: Record<string, string
|
|
42
|
-
export declare function renderComposeDocument(profile: DockerComposeProfileV1, release: string, images: ComposeImages, stateDigest: string, workerCommand?: string[]): string;
|
|
49
|
+
export declare function rolloutStateDigest(profile: DockerComposeProfileV1, secretIdentities: Record<string, string>, proxyTimeouts: AppProxyTimeouts): string;
|
|
50
|
+
export declare function renderComposeDocument(profile: DockerComposeProfileV1, release: string, images: ComposeImages, stateDigest: string, proxyTimeouts: AppProxyTimeouts, workerCommand?: string[]): string;
|
|
43
51
|
/**
|
|
44
52
|
* The migration runs as its own Compose project so it can be started, waited on,
|
|
45
53
|
* and removed without touching the running application definition.
|
|
46
54
|
*/
|
|
47
|
-
export declare function renderMigrationDocument(profile: DockerComposeProfileV1, release: string, images: ComposeImages, defaultCommand?: string[]): string;
|
|
55
|
+
export declare function renderMigrationDocument(profile: DockerComposeProfileV1, release: string, images: ComposeImages, proxyTimeouts: AppProxyTimeouts, defaultCommand?: string[]): string;
|
|
48
56
|
export declare function defaultComposeImages(profile: DockerComposeProfileV1, release: string): ComposeImages;
|
|
49
57
|
export declare function renderComposeDeployment(projectRoot: string, profileName: string, profile: DockerComposeProfileV1, release: string, resolvedImages?: ComposeImages, stateDigest?: string): ComposeRuntime;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.serviceName = serviceName;
|
|
4
4
|
exports.derivedRuntimeConfig = derivedRuntimeConfig;
|
|
5
|
+
exports.composeProxyTrust = composeProxyTrust;
|
|
5
6
|
exports.rolloutStateDigest = rolloutStateDigest;
|
|
6
7
|
exports.renderComposeDocument = renderComposeDocument;
|
|
7
8
|
exports.renderMigrationDocument = renderMigrationDocument;
|
|
@@ -12,6 +13,7 @@ const node_fs_1 = require("node:fs");
|
|
|
12
13
|
const node_path_1 = require("node:path");
|
|
13
14
|
const api_paths_1 = require("./api-paths");
|
|
14
15
|
const deploy_compose_profile_1 = require("./deploy-compose-profile");
|
|
16
|
+
const app_proxy_timeouts_1 = require("./app-proxy-timeouts");
|
|
15
17
|
const deploy_schema_1 = require("./deploy-schema");
|
|
16
18
|
const lockfile_1 = require("./lockfile");
|
|
17
19
|
const toolchain_1 = require("./toolchain");
|
|
@@ -33,10 +35,12 @@ function serviceName(profile, role) {
|
|
|
33
35
|
* Configuration PodoKit derives rather than accepts, so a profile cannot point the
|
|
34
36
|
* app at the wrong service or port.
|
|
35
37
|
*/
|
|
36
|
-
function derivedRuntimeConfig(profile) {
|
|
38
|
+
function derivedRuntimeConfig(profile, proxyTimeouts) {
|
|
37
39
|
const result = {
|
|
38
40
|
PORT: String(CONTAINER_PORT),
|
|
39
41
|
BACKEND_INTERNAL_URL: `http://${serviceName(profile, "api")}:${CONTAINER_PORT}`,
|
|
42
|
+
...(0, deploy_schema_1.idleTimeoutConfig)(profile.runtimeConfig, proxyTimeouts),
|
|
43
|
+
...composeProxyTrust(profile),
|
|
40
44
|
...profile.runtimeConfig,
|
|
41
45
|
};
|
|
42
46
|
if (profile.dependencies.postgres.mode === "managed") {
|
|
@@ -54,6 +58,18 @@ function derivedRuntimeConfig(profile) {
|
|
|
54
58
|
}
|
|
55
59
|
return result;
|
|
56
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* The gateway replaces X-Forwarded-For with the one address it resolved. It keeps
|
|
63
|
+
* a trusted proxy's X-Forwarded-Proto and X-Forwarded-Host and otherwise writes its
|
|
64
|
+
* own, which name its plain-HTTP listener: behind an edge proxy it does not trust,
|
|
65
|
+
* the web server keeps its default origin (https and the Host header) instead.
|
|
66
|
+
*/
|
|
67
|
+
function composeProxyTrust(profile) {
|
|
68
|
+
const { edgeProxies, trustedProxyCidrs } = profile.exposure;
|
|
69
|
+
return (0, deploy_compose_profile_1.composeHasGateway)(profile)
|
|
70
|
+
? (0, deploy_schema_1.proxyTrustConfig)(1, edgeProxies === 0 || trustedProxyCidrs.length > 0)
|
|
71
|
+
: (0, deploy_schema_1.proxyTrustConfig)(edgeProxies);
|
|
72
|
+
}
|
|
57
73
|
function sortedEnvironment(config) {
|
|
58
74
|
return Object.entries(config).sort(([left], [right]) => left.localeCompare(right));
|
|
59
75
|
}
|
|
@@ -73,10 +89,10 @@ function environmentBlock(config, indent) {
|
|
|
73
89
|
* secret or a changed runtime config a definition change, so `up` actually rolls
|
|
74
90
|
* the containers instead of leaving stale values running.
|
|
75
91
|
*/
|
|
76
|
-
function rolloutStateDigest(profile, secretIdentities) {
|
|
92
|
+
function rolloutStateDigest(profile, secretIdentities, proxyTimeouts) {
|
|
77
93
|
return digest([
|
|
78
94
|
"compose-rollout-state",
|
|
79
|
-
JSON.stringify(sortedEnvironment(derivedRuntimeConfig(profile))),
|
|
95
|
+
JSON.stringify(sortedEnvironment(derivedRuntimeConfig(profile, proxyTimeouts))),
|
|
80
96
|
JSON.stringify(Object.entries(secretIdentities).sort(([l], [r]) => l.localeCompare(r))),
|
|
81
97
|
]);
|
|
82
98
|
}
|
|
@@ -120,8 +136,13 @@ function dependencyService(profile, role, dependency, image, extra) {
|
|
|
120
136
|
extra.healthcheck);
|
|
121
137
|
}
|
|
122
138
|
function postgresService(profile, image) {
|
|
139
|
+
const { maxConnections } = profile.dependencies.postgres;
|
|
123
140
|
return dependencyService(profile, "postgres", profile.dependencies.postgres, image, {
|
|
124
141
|
dataPath: "/var/lib/postgresql/data",
|
|
142
|
+
// The image's entry point runs postgres with arguments that start with "-".
|
|
143
|
+
...(maxConnections === undefined
|
|
144
|
+
? {}
|
|
145
|
+
: { command: `["-c", ${quote(`max_connections=${maxConnections}`)}]` }),
|
|
125
146
|
healthcheck: ` healthcheck:\n` +
|
|
126
147
|
` test: ["CMD-SHELL", "pg_isready -U \\"$$POSTGRES_USER\\" -d \\"$$POSTGRES_DB\\""]\n` +
|
|
127
148
|
` interval: 10s\n timeout: 5s\n retries: 10\n start_period: 20s\n`,
|
|
@@ -195,7 +216,7 @@ function objectStorageInitService(profile, clientImage) {
|
|
|
195
216
|
` entrypoint: ["sh", "-c", ${quote(script)}]\n` +
|
|
196
217
|
` networks: [${profile.target.project}]\n`);
|
|
197
218
|
}
|
|
198
|
-
function applicationService(profile, role, image, release, stateDigest, options) {
|
|
219
|
+
function applicationService(profile, role, image, release, stateDigest, environment, options) {
|
|
199
220
|
const workload = role === "worker" ? profile.workloads.worker : profile.workloads[role];
|
|
200
221
|
if (!workload)
|
|
201
222
|
return "";
|
|
@@ -221,7 +242,7 @@ function applicationService(profile, role, image, release, stateDigest, options)
|
|
|
221
242
|
` restart: unless-stopped\n` +
|
|
222
243
|
(options.command ? ` command: ${options.command}\n` : "") +
|
|
223
244
|
(envFile ? ` env_file:\n - ${quote(envFile.path)}\n` : "") +
|
|
224
|
-
environmentBlock(
|
|
245
|
+
environmentBlock(environment, " ") +
|
|
225
246
|
(dependsOn.length
|
|
226
247
|
? ` depends_on:\n${dependsOn
|
|
227
248
|
.map(([name, condition]) => ` ${name}:\n condition: ${condition}\n`)
|
|
@@ -252,10 +273,17 @@ function gatewayService(profile, image, release, stateDigest) {
|
|
|
252
273
|
`
|
|
253
274
|
: "";
|
|
254
275
|
const api = `${serviceName(profile, "api")}:${CONTAINER_PORT}`;
|
|
276
|
+
// Caddy appends the peer to X-Forwarded-For, so behind a trusted proxy web and
|
|
277
|
+
// API would get "client, proxy" and record the proxy at one trusted hop. Send
|
|
278
|
+
// the client address Caddy resolved instead: the client behind a trusted proxy,
|
|
279
|
+
// the peer otherwise.
|
|
280
|
+
const reverseProxy = (upstream) => ` reverse_proxy ${upstream} {\n` +
|
|
281
|
+
` header_up X-Forwarded-For {client_ip}\n` +
|
|
282
|
+
` }\n`;
|
|
255
283
|
const webSocketRoutes = profile.exposure.webSocketPaths.length
|
|
256
284
|
? ` @api_websockets path ${profile.exposure.webSocketPaths.join(" ")}\n` +
|
|
257
285
|
` handle @api_websockets {\n` +
|
|
258
|
-
|
|
286
|
+
reverseProxy(api) +
|
|
259
287
|
` }\n`
|
|
260
288
|
: "";
|
|
261
289
|
const apiPathRoutes = profile.exposure.apiPaths.length
|
|
@@ -264,7 +292,7 @@ function gatewayService(profile, image, release, stateDigest) {
|
|
|
264
292
|
.join(" ")}\n` +
|
|
265
293
|
` handle @api_paths {\n` +
|
|
266
294
|
` uri strip_prefix ${api_paths_1.API_PATH_PREFIX}\n` +
|
|
267
|
-
|
|
295
|
+
reverseProxy(api) +
|
|
268
296
|
` }\n`
|
|
269
297
|
: "";
|
|
270
298
|
const caddyfile = globalOptions +
|
|
@@ -272,7 +300,7 @@ function gatewayService(profile, image, release, stateDigest) {
|
|
|
272
300
|
webSocketRoutes +
|
|
273
301
|
apiPathRoutes +
|
|
274
302
|
` handle {\n` +
|
|
275
|
-
|
|
303
|
+
reverseProxy(`${serviceName(profile, "web")}:${CONTAINER_PORT}`) +
|
|
276
304
|
` }\n` +
|
|
277
305
|
`}\n`;
|
|
278
306
|
const start = "printf '%s' \"$${CADDYFILE_CONTENT}\" > /tmp/Caddyfile\n" +
|
|
@@ -292,7 +320,8 @@ function gatewayService(profile, image, release, stateDigest) {
|
|
|
292
320
|
` interval: 10s\n timeout: 5s\n retries: 6\n start_period: 10s\n` +
|
|
293
321
|
labelsBlock(profile, release, stateDigest, " "));
|
|
294
322
|
}
|
|
295
|
-
function renderComposeDocument(profile, release, images, stateDigest, workerCommand = ["bun", "dist/main-worker.js"]) {
|
|
323
|
+
function renderComposeDocument(profile, release, images, stateDigest, proxyTimeouts, workerCommand = ["bun", "dist/main-worker.js"]) {
|
|
324
|
+
const environment = derivedRuntimeConfig(profile, proxyTimeouts);
|
|
296
325
|
const services = [];
|
|
297
326
|
const hasGateway = (0, deploy_compose_profile_1.composeHasGateway)(profile);
|
|
298
327
|
if (profile.dependencies.postgres.mode === "managed" && images.postgres) {
|
|
@@ -307,10 +336,10 @@ function renderComposeDocument(profile, release, images, stateDigest, workerComm
|
|
|
307
336
|
services.push(objectStorageInitService(profile, images.objectStorageClient));
|
|
308
337
|
}
|
|
309
338
|
}
|
|
310
|
-
services.push(applicationService(profile, "api", images.api, release, stateDigest, {
|
|
339
|
+
services.push(applicationService(profile, "api", images.api, release, stateDigest, environment, {
|
|
311
340
|
healthcheckPath: "/health/ready",
|
|
312
341
|
}));
|
|
313
|
-
services.push(applicationService(profile, "web", images.web, release, stateDigest, {
|
|
342
|
+
services.push(applicationService(profile, "web", images.web, release, stateDigest, environment, {
|
|
314
343
|
// The web server's own route: an API outage must not mark it unhealthy.
|
|
315
344
|
healthcheckPath: "/healthz",
|
|
316
345
|
publish: !hasGateway,
|
|
@@ -324,7 +353,7 @@ function renderComposeDocument(profile, release, images, stateDigest, workerComm
|
|
|
324
353
|
if (profile.workloads.worker) {
|
|
325
354
|
// The worker runs the API image with a different entry point, exactly as the
|
|
326
355
|
// Kubernetes driver does.
|
|
327
|
-
services.push(applicationService(profile, "worker", images.api, release, stateDigest, {
|
|
356
|
+
services.push(applicationService(profile, "worker", images.api, release, stateDigest, environment, {
|
|
328
357
|
command: `[${workerCommand.map((part) => quote(part)).join(", ")}]`,
|
|
329
358
|
}));
|
|
330
359
|
}
|
|
@@ -349,7 +378,7 @@ function renderComposeDocument(profile, release, images, stateDigest, workerComm
|
|
|
349
378
|
* The migration runs as its own Compose project so it can be started, waited on,
|
|
350
379
|
* and removed without touching the running application definition.
|
|
351
380
|
*/
|
|
352
|
-
function renderMigrationDocument(profile, release, images, defaultCommand = ["bun", "run", "migrate:all"]) {
|
|
381
|
+
function renderMigrationDocument(profile, release, images, proxyTimeouts, defaultCommand = ["bun", "run", "migrate:all"]) {
|
|
353
382
|
const command = profile.migration?.command ?? defaultCommand;
|
|
354
383
|
return (`# Generated by PodoKit. One-shot migration for ${release}.\n` +
|
|
355
384
|
`name: ${profile.target.project}-migrate\n` +
|
|
@@ -359,7 +388,7 @@ function renderMigrationDocument(profile, release, images, defaultCommand = ["bu
|
|
|
359
388
|
` restart: "no"\n` +
|
|
360
389
|
` command: [${command.map((part) => quote(part)).join(", ")}]\n` +
|
|
361
390
|
` env_file:\n - ${quote(profile.secrets.api.path)}\n` +
|
|
362
|
-
environmentBlock(derivedRuntimeConfig(profile), " ") +
|
|
391
|
+
environmentBlock(derivedRuntimeConfig(profile, proxyTimeouts), " ") +
|
|
363
392
|
` networks: [${profile.target.project}]\n` +
|
|
364
393
|
`networks:\n ${profile.target.project}:\n name: ${profile.target.project}\n external: true\n`);
|
|
365
394
|
}
|
|
@@ -383,13 +412,14 @@ function defaultComposeImages(profile, release) {
|
|
|
383
412
|
function renderComposeDeployment(projectRoot, profileName, profile, release, resolvedImages, stateDigest) {
|
|
384
413
|
(0, deploy_schema_1.profilePath)(projectRoot, profileName);
|
|
385
414
|
const images = resolvedImages ?? defaultComposeImages(profile, release);
|
|
386
|
-
const
|
|
415
|
+
const proxyTimeouts = (0, app_proxy_timeouts_1.readAppProxyTimeouts)(projectRoot);
|
|
416
|
+
const effectiveStateDigest = stateDigest ?? rolloutStateDigest(profile, {}, proxyTimeouts);
|
|
387
417
|
const root = (0, node_path_1.join)((0, node_path_1.resolve)(projectRoot), ".podokit", "runtime", "deploy", profileName);
|
|
388
418
|
(0, node_fs_1.rmSync)(root, { recursive: true, force: true });
|
|
389
419
|
(0, node_fs_1.mkdirSync)(root, { recursive: true });
|
|
390
420
|
const toolchain = (0, lockfile_1.readManifest)(projectRoot)?.toolchain ?? (0, toolchain_1.resolveToolchain)();
|
|
391
|
-
const composeDocument = renderComposeDocument(profile, release, images, effectiveStateDigest, (0, toolchain_1.toolchainWorkerCommand)(toolchain));
|
|
392
|
-
const migrationDocument = renderMigrationDocument(profile, release, images, (0, toolchain_1.toolchainMigrationCommand)(toolchain));
|
|
421
|
+
const composeDocument = renderComposeDocument(profile, release, images, effectiveStateDigest, proxyTimeouts, (0, toolchain_1.toolchainWorkerCommand)(toolchain));
|
|
422
|
+
const migrationDocument = renderMigrationDocument(profile, release, images, proxyTimeouts, (0, toolchain_1.toolchainMigrationCommand)(toolchain));
|
|
393
423
|
const composeFile = (0, node_path_1.join)(root, "compose.yaml");
|
|
394
424
|
const migrationFile = (0, node_path_1.join)(root, "compose.migrate.yaml");
|
|
395
425
|
(0, node_fs_1.writeFileSync)(composeFile, composeDocument);
|
package/dist/deploy-compose.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DeployFinding } from "./deploy-checks";
|
|
1
2
|
import type { CommandRunner } from "./dev";
|
|
2
3
|
import { type ComposeSyncDrift } from "./deploy-compose-sync";
|
|
3
4
|
import { type ComposeImages } from "./deploy-compose-render";
|
|
@@ -10,11 +11,7 @@ import { type ComposeImages } from "./deploy-compose-render";
|
|
|
10
11
|
* lock file in a state volume replaces the Kubernetes Lease, and a release ledger in
|
|
11
12
|
* that same volume replaces Helm revisions.
|
|
12
13
|
*/
|
|
13
|
-
export
|
|
14
|
-
code: string;
|
|
15
|
-
ok: boolean;
|
|
16
|
-
message: string;
|
|
17
|
-
}
|
|
14
|
+
export type ComposeDoctorFinding = DeployFinding;
|
|
18
15
|
export interface ComposePlanAction {
|
|
19
16
|
order: number;
|
|
20
17
|
kind: "dependencies" | "migration" | "application" | "verification" | "rollback";
|
package/dist/deploy-compose.js
CHANGED
|
@@ -12,6 +12,8 @@ exports.composeRuntimeRoot = composeRuntimeRoot;
|
|
|
12
12
|
const node_crypto_1 = require("node:crypto");
|
|
13
13
|
const node_path_1 = require("node:path");
|
|
14
14
|
const api_paths_1 = require("./api-paths");
|
|
15
|
+
const app_proxy_timeouts_1 = require("./app-proxy-timeouts");
|
|
16
|
+
const deploy_checks_1 = require("./deploy-checks");
|
|
15
17
|
const deploy_driver_1 = require("./deploy-driver");
|
|
16
18
|
const deploy_compose_exec_1 = require("./deploy-compose-exec");
|
|
17
19
|
const deploy_compose_sync_1 = require("./deploy-compose-sync");
|
|
@@ -171,7 +173,7 @@ function managedDependencyServices(profile) {
|
|
|
171
173
|
}
|
|
172
174
|
function doctorComposeDeployment(projectRoot, profileName, runner = deploy_compose_exec_1.defaultRunner, release) {
|
|
173
175
|
const profile = (0, deploy_driver_1.loadComposeProfile)(projectRoot, profileName);
|
|
174
|
-
const findings =
|
|
176
|
+
const findings = (0, deploy_checks_1.profileFindings)(projectRoot, profile);
|
|
175
177
|
const record = (code, ok, message) => {
|
|
176
178
|
findings.push({ code, ok, message });
|
|
177
179
|
};
|
|
@@ -351,7 +353,7 @@ function planComposeDeployment(projectRoot, profileName, release, runner = deplo
|
|
|
351
353
|
]));
|
|
352
354
|
secretIdentities[label] = output.trim();
|
|
353
355
|
}
|
|
354
|
-
const stateDigest = (0, deploy_compose_render_1.rolloutStateDigest)(profile, secretIdentities);
|
|
356
|
+
const stateDigest = (0, deploy_compose_render_1.rolloutStateDigest)(profile, secretIdentities, (0, app_proxy_timeouts_1.readAppProxyTimeouts)(projectRoot));
|
|
355
357
|
const runtime = (0, deploy_compose_render_1.renderComposeDeployment)(projectRoot, profileName, profile, release, images, stateDigest);
|
|
356
358
|
const composeDocumentDigest = digest(["compose-document", runtime.composeDocument]);
|
|
357
359
|
const psOutput = composePs(profile, runner);
|
|
@@ -386,6 +388,9 @@ function planComposeDeployment(projectRoot, profileName, release, runner = deplo
|
|
|
386
388
|
warnings.push(`Release ${release} is already the current revision; apply will recreate containers whose definition changed.`);
|
|
387
389
|
}
|
|
388
390
|
warnings.push("The migration runs before the new containers start, so it must be compatible with the release currently serving traffic.");
|
|
391
|
+
const budgetWarning = (0, deploy_checks_1.connectionBudgetWarning)(profile, (0, deploy_checks_1.installedModules)(projectRoot));
|
|
392
|
+
if (budgetWarning)
|
|
393
|
+
warnings.push(budgetWarning);
|
|
389
394
|
const profileDigest = (0, deploy_compose_profile_1.composeProfileDigest)(profile);
|
|
390
395
|
const planHash = digest([
|
|
391
396
|
"compose-plan",
|
package/dist/deploy-profile.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MigrationProfile, VerificationCheckProfile } from "./deploy-schema";
|
|
1
|
+
import { MigrationProfile, PostgresBudgetProfile, VerificationCheckProfile } from "./deploy-schema";
|
|
2
2
|
export { profileDirectory, profilePath } from "./deploy-schema";
|
|
3
3
|
export type { MigrationProfile, VerificationCheckProfile } from "./deploy-schema";
|
|
4
4
|
export type DependencyMode = "inCluster" | "external" | "disabled";
|
|
@@ -20,6 +20,8 @@ export interface StatefulDependencyProfile {
|
|
|
20
20
|
storageSize: string;
|
|
21
21
|
}
|
|
22
22
|
export type RedisDependencyProfile = StatefulDependencyProfile;
|
|
23
|
+
export interface PostgresDependencyProfile extends StatefulDependencyProfile, PostgresBudgetProfile {
|
|
24
|
+
}
|
|
23
25
|
export interface ObjectStorageDependencyProfile extends StatefulDependencyProfile {
|
|
24
26
|
bucket: string;
|
|
25
27
|
clientImage: string;
|
|
@@ -52,6 +54,8 @@ export interface DeployProfileV1 {
|
|
|
52
54
|
webSocketPaths: string[];
|
|
53
55
|
/** Path prefixes under /api/ that the Ingress sends to the API with /api removed. */
|
|
54
56
|
apiPaths: string[];
|
|
57
|
+
/** Reverse proxies in front of the Ingress controller or NodePort that append to X-Forwarded-For. */
|
|
58
|
+
edgeProxies: number;
|
|
55
59
|
};
|
|
56
60
|
workloads: {
|
|
57
61
|
api: WorkloadProfile;
|
|
@@ -59,7 +63,7 @@ export interface DeployProfileV1 {
|
|
|
59
63
|
worker: WorkloadProfile | null;
|
|
60
64
|
};
|
|
61
65
|
dependencies: {
|
|
62
|
-
postgres:
|
|
66
|
+
postgres: PostgresDependencyProfile;
|
|
63
67
|
redis: RedisDependencyProfile;
|
|
64
68
|
objectStorage: ObjectStorageDependencyProfile;
|
|
65
69
|
};
|
package/dist/deploy-profile.js
CHANGED
|
@@ -119,7 +119,7 @@ function parseDeploymentProfile(value) {
|
|
|
119
119
|
const runtimeConfig = (0, deploy_schema_1.requiredRecord)(value, "runtimeConfig");
|
|
120
120
|
(0, deploy_schema_1.assertOnlyKeys)(target, ["context", "clusterFingerprint", "namespace", "release", "createNamespace"], "target");
|
|
121
121
|
(0, deploy_schema_1.assertOnlyKeys)(release, ["strategy", "tagPattern", "apiRepository", "webRepository"], "release");
|
|
122
|
-
(0, deploy_schema_1.assertOnlyKeys)(exposure, ["mode", "host", "ingressClassName", "tlsSecretName", "webSocketPaths", "apiPaths"], "exposure");
|
|
122
|
+
(0, deploy_schema_1.assertOnlyKeys)(exposure, ["mode", "host", "ingressClassName", "tlsSecretName", "webSocketPaths", "apiPaths", "edgeProxies"], "exposure");
|
|
123
123
|
(0, deploy_schema_1.assertOnlyKeys)(workloads, ["api", "web", "worker"], "workloads");
|
|
124
124
|
(0, deploy_schema_1.assertOnlyKeys)(dependencies, ["postgres", "redis", "objectStorage"], "dependencies");
|
|
125
125
|
(0, deploy_schema_1.assertOnlyKeys)(secrets, ["api", "web", "imagePull"], "secrets");
|
|
@@ -173,7 +173,12 @@ function parseDeploymentProfile(value) {
|
|
|
173
173
|
if (exposureMode === "nodePort" && apiPaths.length > 0) {
|
|
174
174
|
throw new Error("Deployment exposure.apiPaths requires exposure.mode ingress.");
|
|
175
175
|
}
|
|
176
|
-
const
|
|
176
|
+
const parsedConfig = (0, deploy_schema_1.parseRuntimeConfig)(runtimeConfig);
|
|
177
|
+
const postgresRecord = (0, deploy_schema_1.requiredRecord)(dependencies, "postgres");
|
|
178
|
+
const postgres = {
|
|
179
|
+
...parseStatefulDependency(postgresRecord, "dependencies.postgres", ["maxConnections", "appPools"]),
|
|
180
|
+
...(0, deploy_schema_1.parsePostgresBudget)(postgresRecord, "dependencies.postgres", parsedConfig),
|
|
181
|
+
};
|
|
177
182
|
const redis = parseRedisDependency((0, deploy_schema_1.requiredRecord)(dependencies, "redis"), "dependencies.redis");
|
|
178
183
|
const objectStorageRecord = (0, deploy_schema_1.requiredRecord)(dependencies, "objectStorage");
|
|
179
184
|
(0, deploy_schema_1.assertOnlyKeys)(objectStorageRecord, [
|
|
@@ -192,7 +197,6 @@ function parseDeploymentProfile(value) {
|
|
|
192
197
|
};
|
|
193
198
|
(0, deploy_schema_1.validateDnsLabel)(objectStorage.bucket, "dependencies.objectStorage.bucket");
|
|
194
199
|
(0, deploy_schema_1.validateImage)(objectStorage.clientImage, "dependencies.objectStorage.clientImage");
|
|
195
|
-
const parsedConfig = (0, deploy_schema_1.parseRuntimeConfig)(runtimeConfig);
|
|
196
200
|
const { baseUrl, checks } = (0, deploy_schema_1.parseVerification)(verification);
|
|
197
201
|
const profile = {
|
|
198
202
|
schemaVersion: 1,
|
|
@@ -217,6 +221,7 @@ function parseDeploymentProfile(value) {
|
|
|
217
221
|
tlsSecretName,
|
|
218
222
|
webSocketPaths,
|
|
219
223
|
apiPaths,
|
|
224
|
+
edgeProxies: (0, deploy_schema_1.parseEdgeProxies)(exposure, 0),
|
|
220
225
|
},
|
|
221
226
|
workloads: {
|
|
222
227
|
api: parseWorkload((0, deploy_schema_1.requiredRecord)(workloads, "api"), "workloads.api"),
|
|
@@ -327,6 +332,7 @@ function initializeDeploymentProfile(projectRoot, name, options) {
|
|
|
327
332
|
tlsSecretName: null,
|
|
328
333
|
webSocketPaths: [],
|
|
329
334
|
apiPaths: (0, api_paths_1.installedApiPaths)(manifest.modules),
|
|
335
|
+
edgeProxies: 0,
|
|
330
336
|
},
|
|
331
337
|
workloads: {
|
|
332
338
|
api: {
|
|
@@ -395,9 +401,9 @@ function initializeDeploymentProfile(projectRoot, name, options) {
|
|
|
395
401
|
runtimeConfig: {
|
|
396
402
|
NODE_ENV: "production",
|
|
397
403
|
CORS_ORIGIN: `https://${host}`,
|
|
398
|
-
ADDRESS_HEADER: "x-forwarded-for",
|
|
399
|
-
XFF_DEPTH: "1",
|
|
400
404
|
...(usesAuth ? { BETTER_AUTH_URL: `https://${host}` } : {}),
|
|
405
|
+
// The API's fallback until an administrator saves the setting.
|
|
406
|
+
...(modules.has("audit-log") ? { AUDIT_LOG_ENABLED: "true" } : {}),
|
|
401
407
|
...(modules.has("rate-limit")
|
|
402
408
|
? {
|
|
403
409
|
RATE_LIMIT_TTL: "60",
|
|
@@ -406,8 +412,6 @@ function initializeDeploymentProfile(projectRoot, name, options) {
|
|
|
406
412
|
RATE_LIMIT_AUTH_TTL: "60",
|
|
407
413
|
RATE_LIMIT_AUTH_MAX: "20",
|
|
408
414
|
RATE_LIMIT_RUNTIME_MAX: "1000",
|
|
409
|
-
RATE_LIMIT_TRUSTED_PROXY_HOPS: "1",
|
|
410
|
-
RATE_LIMIT_PROXY_HEADER: "x-forwarded-for",
|
|
411
415
|
RATE_LIMIT_STORAGE_TIMEOUT_MS: "1000",
|
|
412
416
|
RATE_LIMIT_UNAVAILABLE_RETRY_AFTER: "1",
|
|
413
417
|
}
|
package/dist/deploy-render.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DeployProfileV1 } from "./deploy-profile";
|
|
2
|
+
import { type AppProxyTimeouts } from "./app-proxy-timeouts";
|
|
2
3
|
export interface DeploymentRuntime {
|
|
3
4
|
root: string;
|
|
4
5
|
dependencyChart: string;
|
|
@@ -15,4 +16,6 @@ export interface DeploymentImages {
|
|
|
15
16
|
objectStorage: string | null;
|
|
16
17
|
objectStorageClient: string | null;
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
+
/** The Ingress controller appends its peer: the client, or the last edge proxy. */
|
|
20
|
+
export declare function kubernetesProxyTrust(profile: DeployProfileV1): Record<string, string>;
|
|
21
|
+
export declare function renderDeployment(projectRoot: string, profileName: string, profile: DeployProfileV1, release: string, resolvedImages?: DeploymentImages, rolloutStateDigest?: string, proxyTimeouts?: AppProxyTimeouts): DeploymentRuntime;
|
package/dist/deploy-render.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.kubernetesProxyTrust = kubernetesProxyTrust;
|
|
3
4
|
exports.renderDeployment = renderDeployment;
|
|
4
5
|
const node_fs_1 = require("node:fs");
|
|
5
6
|
const node_crypto_1 = require("node:crypto");
|
|
6
7
|
const node_path_1 = require("node:path");
|
|
7
8
|
const deploy_profile_1 = require("./deploy-profile");
|
|
8
9
|
const api_paths_1 = require("./api-paths");
|
|
10
|
+
const app_proxy_timeouts_1 = require("./app-proxy-timeouts");
|
|
11
|
+
const deploy_schema_1 = require("./deploy-schema");
|
|
9
12
|
const lockfile_1 = require("./lockfile");
|
|
10
13
|
const toolchain_1 = require("./toolchain");
|
|
11
14
|
function quote(value) {
|
|
@@ -111,7 +114,7 @@ ${labels(name, 8)} spec:
|
|
|
111
114
|
- name: postgres
|
|
112
115
|
image: ${quote(image ?? dependency.image)}
|
|
113
116
|
imagePullPolicy: IfNotPresent
|
|
114
|
-
env:
|
|
117
|
+
${dependency.maxConnections === undefined ? "" : ` args: ["-c", ${quote(`max_connections=${dependency.maxConnections}`)}]\n`} env:
|
|
115
118
|
- name: POSTGRES_USER
|
|
116
119
|
valueFrom:
|
|
117
120
|
secretKeyRef: { name: ${dependency.secretName}, key: POSTGRES_USER }
|
|
@@ -350,12 +353,16 @@ function dependencyManifest(profile, images, rolloutStateDigest) {
|
|
|
350
353
|
.filter(Boolean)
|
|
351
354
|
.join("\n---\n");
|
|
352
355
|
}
|
|
353
|
-
|
|
356
|
+
/** The Ingress controller appends its peer: the client, or the last edge proxy. */
|
|
357
|
+
function kubernetesProxyTrust(profile) {
|
|
358
|
+
return (0, deploy_schema_1.proxyTrustConfig)(profile.exposure.edgeProxies + (profile.exposure.mode === "ingress" ? 1 : 0));
|
|
359
|
+
}
|
|
360
|
+
function derivedRuntimeConfig(profile, proxyTimeouts) {
|
|
354
361
|
const result = {
|
|
355
362
|
PORT: "3000",
|
|
356
363
|
BACKEND_INTERNAL_URL: `http://${profile.target.release}-api:3000`,
|
|
357
|
-
|
|
358
|
-
|
|
364
|
+
...(0, deploy_schema_1.idleTimeoutConfig)(profile.runtimeConfig, proxyTimeouts),
|
|
365
|
+
...kubernetesProxyTrust(profile),
|
|
359
366
|
...profile.runtimeConfig,
|
|
360
367
|
};
|
|
361
368
|
if (profile.dependencies.postgres.mode === "inCluster") {
|
|
@@ -373,12 +380,11 @@ function derivedRuntimeConfig(profile) {
|
|
|
373
380
|
}
|
|
374
381
|
return result;
|
|
375
382
|
}
|
|
376
|
-
function
|
|
377
|
-
|
|
378
|
-
return digest(["runtime-config", JSON.stringify(entries)]);
|
|
383
|
+
function sortedRuntimeConfig(runtimeConfig) {
|
|
384
|
+
return Object.entries(runtimeConfig).sort(([left], [right]) => left.localeCompare(right));
|
|
379
385
|
}
|
|
380
|
-
function configMap(profile) {
|
|
381
|
-
const entries =
|
|
386
|
+
function configMap(profile, runtimeConfig) {
|
|
387
|
+
const entries = sortedRuntimeConfig(runtimeConfig);
|
|
382
388
|
return `apiVersion: v1
|
|
383
389
|
kind: ConfigMap
|
|
384
390
|
metadata:
|
|
@@ -626,10 +632,10 @@ ${webSocketPaths} - path: /
|
|
|
626
632
|
port: { number: 3000 }
|
|
627
633
|
${apiPathIngress(profile, tls)}`;
|
|
628
634
|
}
|
|
629
|
-
function applicationManifest(profile, images, rolloutStateDigest, workerCommand) {
|
|
630
|
-
const runtimeConfigDigest =
|
|
635
|
+
function applicationManifest(profile, images, rolloutStateDigest, workerCommand, runtimeConfig) {
|
|
636
|
+
const runtimeConfigDigest = digest(["runtime-config", JSON.stringify(sortedRuntimeConfig(runtimeConfig))]);
|
|
631
637
|
return [
|
|
632
|
-
configMap(profile),
|
|
638
|
+
configMap(profile, runtimeConfig),
|
|
633
639
|
deployment(profile, "api", profile.workloads.api, images.api, rolloutStateDigest, runtimeConfigDigest),
|
|
634
640
|
deployment(profile, "web", profile.workloads.web, images.web, rolloutStateDigest, runtimeConfigDigest),
|
|
635
641
|
workerDeployment(profile, images.api, rolloutStateDigest, runtimeConfigDigest, workerCommand),
|
|
@@ -641,14 +647,13 @@ function applicationManifest(profile, images, rolloutStateDigest, workerCommand)
|
|
|
641
647
|
.filter(Boolean)
|
|
642
648
|
.join("\n---\n");
|
|
643
649
|
}
|
|
644
|
-
function migrationRuntimeEnv(
|
|
645
|
-
return
|
|
646
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
650
|
+
function migrationRuntimeEnv(runtimeConfig) {
|
|
651
|
+
return sortedRuntimeConfig(runtimeConfig)
|
|
647
652
|
.map(([key, value]) => ` - name: ${key}
|
|
648
653
|
value: ${quote(value)}`)
|
|
649
654
|
.join("\n");
|
|
650
655
|
}
|
|
651
|
-
function migrationManifest(profile, release, images, defaultCommand) {
|
|
656
|
+
function migrationManifest(profile, release, images, defaultCommand, runtimeConfig) {
|
|
652
657
|
const releaseSlug = release.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
653
658
|
const command = profile.migration?.command ?? defaultCommand;
|
|
654
659
|
return `apiVersion: batch/v1
|
|
@@ -678,7 +683,7 @@ ${labels(`${profile.target.release}-migration`, 8)} spec:
|
|
|
678
683
|
imagePullPolicy: IfNotPresent
|
|
679
684
|
command: ${yamlStringSequence(command)}
|
|
680
685
|
env:
|
|
681
|
-
${migrationRuntimeEnv(
|
|
686
|
+
${migrationRuntimeEnv(runtimeConfig)}
|
|
682
687
|
envFrom:
|
|
683
688
|
- secretRef: { name: ${profile.secrets.api.name} }
|
|
684
689
|
securityContext:
|
|
@@ -692,7 +697,9 @@ function chart(directory, name, manifest) {
|
|
|
692
697
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(directory, "resources.yaml"), manifest || "# No resources\n");
|
|
693
698
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(directory, "templates", "resources.yaml"), '{{ .Files.Get "resources.yaml" }}\n');
|
|
694
699
|
}
|
|
695
|
-
function renderDeployment(projectRoot, profileName, profile, release, resolvedImages, rolloutStateDigest
|
|
700
|
+
function renderDeployment(projectRoot, profileName, profile, release, resolvedImages, rolloutStateDigest,
|
|
701
|
+
// Planning renders into a scratch directory, so it reads these from the project.
|
|
702
|
+
proxyTimeouts = (0, app_proxy_timeouts_1.readAppProxyTimeouts)(projectRoot)) {
|
|
696
703
|
(0, deploy_profile_1.profilePath)(projectRoot, profileName);
|
|
697
704
|
const images = resolvedImages ?? {
|
|
698
705
|
api: `${profile.release.apiRepository}:${release}`,
|
|
@@ -717,8 +724,9 @@ function renderDeployment(projectRoot, profileName, profile, release, resolvedIm
|
|
|
717
724
|
const effectiveRolloutStateDigest = rolloutStateDigest ?? offlineRolloutStateDigest(profile);
|
|
718
725
|
const dependencies = dependencyManifest(profile, images, effectiveRolloutStateDigest);
|
|
719
726
|
const toolchain = (0, lockfile_1.readManifest)(projectRoot)?.toolchain ?? (0, toolchain_1.resolveToolchain)();
|
|
720
|
-
const
|
|
721
|
-
const
|
|
727
|
+
const runtimeConfig = derivedRuntimeConfig(profile, proxyTimeouts);
|
|
728
|
+
const application = applicationManifest(profile, images, effectiveRolloutStateDigest, (0, toolchain_1.toolchainWorkerCommand)(toolchain), runtimeConfig);
|
|
729
|
+
const migration = migrationManifest(profile, release, images, (0, toolchain_1.toolchainMigrationCommand)(toolchain), runtimeConfig);
|
|
722
730
|
chart(dependencyChart, "podokit-dependencies", dependencies);
|
|
723
731
|
chart(applicationChart, "podokit-application", application);
|
|
724
732
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, "migration.yaml"), migration);
|