@podosoft/podokit 1.5.0 → 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 -18
- 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 +27 -21
- package/dist/deploy-schema.d.ts +56 -2
- package/dist/deploy-schema.js +130 -14
- 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 +74 -4
- package/dist/provider.js +10 -5
- 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 +28 -4
- 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.spec.ts +22 -0
- package/dist/templates/fullstack/apps/api/src/app.ts +41 -25
- 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 +18 -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 +7 -3
- 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/services.ts +13 -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/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 +79 -0
- package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.ts +55 -1
- package/dist/templates/fullstack/apps/web/src/lib/server/proxy-timeouts.ts +2 -1
- 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 +5 -2
- package/dist/templates/fullstack/dot-env.example +4 -0
- package/dist/templates/fullstack/dot-podokit/dev.json +1 -1
- package/dist/templates/fullstack/infra/k3s/configmap.yaml +1 -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/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/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 +11 -1
- 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/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/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 +3 -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 +41 -11
- 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/ui/account-flows.ui.spec.ts +4 -5
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +55 -55
- 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 +8 -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.module.ts +5 -1
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.spec.ts +17 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +18 -0
- 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 +2 -0
- 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.ts +7 -5
- 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/tests/api/rate-limit.api.spec.ts +45 -20
- package/dist/templates/modules/sse/files/tests/api/events.api.spec.ts +1 -1
- package/dist/templates/todo/AGENTS.md +5 -1
- 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,10 +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
|
-
TRUSTED_PROXY_HOPS: "1",
|
|
361
|
-
TRUSTED_PROXY_HEADER: "x-forwarded-for",
|
|
362
369
|
},
|
|
363
370
|
verification: {
|
|
364
371
|
baseUrl: `https://${host}`,
|
|
@@ -412,6 +419,8 @@ function initializeComposeProfile(projectRoot, name, options) {
|
|
|
412
419
|
draft.runtimeConfig = {
|
|
413
420
|
...draft.runtimeConfig,
|
|
414
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" } : {}),
|
|
415
424
|
...(modules.has("rate-limit")
|
|
416
425
|
? {
|
|
417
426
|
RATE_LIMIT_TTL: "60",
|
|
@@ -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,11 +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}`,
|
|
40
|
-
...(0, deploy_schema_1.idleTimeoutConfig)(profile.runtimeConfig),
|
|
42
|
+
...(0, deploy_schema_1.idleTimeoutConfig)(profile.runtimeConfig, proxyTimeouts),
|
|
43
|
+
...composeProxyTrust(profile),
|
|
41
44
|
...profile.runtimeConfig,
|
|
42
45
|
};
|
|
43
46
|
if (profile.dependencies.postgres.mode === "managed") {
|
|
@@ -55,6 +58,18 @@ function derivedRuntimeConfig(profile) {
|
|
|
55
58
|
}
|
|
56
59
|
return result;
|
|
57
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
|
+
}
|
|
58
73
|
function sortedEnvironment(config) {
|
|
59
74
|
return Object.entries(config).sort(([left], [right]) => left.localeCompare(right));
|
|
60
75
|
}
|
|
@@ -74,10 +89,10 @@ function environmentBlock(config, indent) {
|
|
|
74
89
|
* secret or a changed runtime config a definition change, so `up` actually rolls
|
|
75
90
|
* the containers instead of leaving stale values running.
|
|
76
91
|
*/
|
|
77
|
-
function rolloutStateDigest(profile, secretIdentities) {
|
|
92
|
+
function rolloutStateDigest(profile, secretIdentities, proxyTimeouts) {
|
|
78
93
|
return digest([
|
|
79
94
|
"compose-rollout-state",
|
|
80
|
-
JSON.stringify(sortedEnvironment(derivedRuntimeConfig(profile))),
|
|
95
|
+
JSON.stringify(sortedEnvironment(derivedRuntimeConfig(profile, proxyTimeouts))),
|
|
81
96
|
JSON.stringify(Object.entries(secretIdentities).sort(([l], [r]) => l.localeCompare(r))),
|
|
82
97
|
]);
|
|
83
98
|
}
|
|
@@ -121,8 +136,13 @@ function dependencyService(profile, role, dependency, image, extra) {
|
|
|
121
136
|
extra.healthcheck);
|
|
122
137
|
}
|
|
123
138
|
function postgresService(profile, image) {
|
|
139
|
+
const { maxConnections } = profile.dependencies.postgres;
|
|
124
140
|
return dependencyService(profile, "postgres", profile.dependencies.postgres, image, {
|
|
125
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}`)}]` }),
|
|
126
146
|
healthcheck: ` healthcheck:\n` +
|
|
127
147
|
` test: ["CMD-SHELL", "pg_isready -U \\"$$POSTGRES_USER\\" -d \\"$$POSTGRES_DB\\""]\n` +
|
|
128
148
|
` interval: 10s\n timeout: 5s\n retries: 10\n start_period: 20s\n`,
|
|
@@ -196,7 +216,7 @@ function objectStorageInitService(profile, clientImage) {
|
|
|
196
216
|
` entrypoint: ["sh", "-c", ${quote(script)}]\n` +
|
|
197
217
|
` networks: [${profile.target.project}]\n`);
|
|
198
218
|
}
|
|
199
|
-
function applicationService(profile, role, image, release, stateDigest, options) {
|
|
219
|
+
function applicationService(profile, role, image, release, stateDigest, environment, options) {
|
|
200
220
|
const workload = role === "worker" ? profile.workloads.worker : profile.workloads[role];
|
|
201
221
|
if (!workload)
|
|
202
222
|
return "";
|
|
@@ -222,7 +242,7 @@ function applicationService(profile, role, image, release, stateDigest, options)
|
|
|
222
242
|
` restart: unless-stopped\n` +
|
|
223
243
|
(options.command ? ` command: ${options.command}\n` : "") +
|
|
224
244
|
(envFile ? ` env_file:\n - ${quote(envFile.path)}\n` : "") +
|
|
225
|
-
environmentBlock(
|
|
245
|
+
environmentBlock(environment, " ") +
|
|
226
246
|
(dependsOn.length
|
|
227
247
|
? ` depends_on:\n${dependsOn
|
|
228
248
|
.map(([name, condition]) => ` ${name}:\n condition: ${condition}\n`)
|
|
@@ -253,10 +273,17 @@ function gatewayService(profile, image, release, stateDigest) {
|
|
|
253
273
|
`
|
|
254
274
|
: "";
|
|
255
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`;
|
|
256
283
|
const webSocketRoutes = profile.exposure.webSocketPaths.length
|
|
257
284
|
? ` @api_websockets path ${profile.exposure.webSocketPaths.join(" ")}\n` +
|
|
258
285
|
` handle @api_websockets {\n` +
|
|
259
|
-
|
|
286
|
+
reverseProxy(api) +
|
|
260
287
|
` }\n`
|
|
261
288
|
: "";
|
|
262
289
|
const apiPathRoutes = profile.exposure.apiPaths.length
|
|
@@ -265,7 +292,7 @@ function gatewayService(profile, image, release, stateDigest) {
|
|
|
265
292
|
.join(" ")}\n` +
|
|
266
293
|
` handle @api_paths {\n` +
|
|
267
294
|
` uri strip_prefix ${api_paths_1.API_PATH_PREFIX}\n` +
|
|
268
|
-
|
|
295
|
+
reverseProxy(api) +
|
|
269
296
|
` }\n`
|
|
270
297
|
: "";
|
|
271
298
|
const caddyfile = globalOptions +
|
|
@@ -273,7 +300,7 @@ function gatewayService(profile, image, release, stateDigest) {
|
|
|
273
300
|
webSocketRoutes +
|
|
274
301
|
apiPathRoutes +
|
|
275
302
|
` handle {\n` +
|
|
276
|
-
|
|
303
|
+
reverseProxy(`${serviceName(profile, "web")}:${CONTAINER_PORT}`) +
|
|
277
304
|
` }\n` +
|
|
278
305
|
`}\n`;
|
|
279
306
|
const start = "printf '%s' \"$${CADDYFILE_CONTENT}\" > /tmp/Caddyfile\n" +
|
|
@@ -293,7 +320,8 @@ function gatewayService(profile, image, release, stateDigest) {
|
|
|
293
320
|
` interval: 10s\n timeout: 5s\n retries: 6\n start_period: 10s\n` +
|
|
294
321
|
labelsBlock(profile, release, stateDigest, " "));
|
|
295
322
|
}
|
|
296
|
-
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);
|
|
297
325
|
const services = [];
|
|
298
326
|
const hasGateway = (0, deploy_compose_profile_1.composeHasGateway)(profile);
|
|
299
327
|
if (profile.dependencies.postgres.mode === "managed" && images.postgres) {
|
|
@@ -308,10 +336,10 @@ function renderComposeDocument(profile, release, images, stateDigest, workerComm
|
|
|
308
336
|
services.push(objectStorageInitService(profile, images.objectStorageClient));
|
|
309
337
|
}
|
|
310
338
|
}
|
|
311
|
-
services.push(applicationService(profile, "api", images.api, release, stateDigest, {
|
|
339
|
+
services.push(applicationService(profile, "api", images.api, release, stateDigest, environment, {
|
|
312
340
|
healthcheckPath: "/health/ready",
|
|
313
341
|
}));
|
|
314
|
-
services.push(applicationService(profile, "web", images.web, release, stateDigest, {
|
|
342
|
+
services.push(applicationService(profile, "web", images.web, release, stateDigest, environment, {
|
|
315
343
|
// The web server's own route: an API outage must not mark it unhealthy.
|
|
316
344
|
healthcheckPath: "/healthz",
|
|
317
345
|
publish: !hasGateway,
|
|
@@ -325,7 +353,7 @@ function renderComposeDocument(profile, release, images, stateDigest, workerComm
|
|
|
325
353
|
if (profile.workloads.worker) {
|
|
326
354
|
// The worker runs the API image with a different entry point, exactly as the
|
|
327
355
|
// Kubernetes driver does.
|
|
328
|
-
services.push(applicationService(profile, "worker", images.api, release, stateDigest, {
|
|
356
|
+
services.push(applicationService(profile, "worker", images.api, release, stateDigest, environment, {
|
|
329
357
|
command: `[${workerCommand.map((part) => quote(part)).join(", ")}]`,
|
|
330
358
|
}));
|
|
331
359
|
}
|
|
@@ -350,7 +378,7 @@ function renderComposeDocument(profile, release, images, stateDigest, workerComm
|
|
|
350
378
|
* The migration runs as its own Compose project so it can be started, waited on,
|
|
351
379
|
* and removed without touching the running application definition.
|
|
352
380
|
*/
|
|
353
|
-
function renderMigrationDocument(profile, release, images, defaultCommand = ["bun", "run", "migrate:all"]) {
|
|
381
|
+
function renderMigrationDocument(profile, release, images, proxyTimeouts, defaultCommand = ["bun", "run", "migrate:all"]) {
|
|
354
382
|
const command = profile.migration?.command ?? defaultCommand;
|
|
355
383
|
return (`# Generated by PodoKit. One-shot migration for ${release}.\n` +
|
|
356
384
|
`name: ${profile.target.project}-migrate\n` +
|
|
@@ -360,7 +388,7 @@ function renderMigrationDocument(profile, release, images, defaultCommand = ["bu
|
|
|
360
388
|
` restart: "no"\n` +
|
|
361
389
|
` command: [${command.map((part) => quote(part)).join(", ")}]\n` +
|
|
362
390
|
` env_file:\n - ${quote(profile.secrets.api.path)}\n` +
|
|
363
|
-
environmentBlock(derivedRuntimeConfig(profile), " ") +
|
|
391
|
+
environmentBlock(derivedRuntimeConfig(profile, proxyTimeouts), " ") +
|
|
364
392
|
` networks: [${profile.target.project}]\n` +
|
|
365
393
|
`networks:\n ${profile.target.project}:\n name: ${profile.target.project}\n external: true\n`);
|
|
366
394
|
}
|
|
@@ -384,13 +412,14 @@ function defaultComposeImages(profile, release) {
|
|
|
384
412
|
function renderComposeDeployment(projectRoot, profileName, profile, release, resolvedImages, stateDigest) {
|
|
385
413
|
(0, deploy_schema_1.profilePath)(projectRoot, profileName);
|
|
386
414
|
const images = resolvedImages ?? defaultComposeImages(profile, release);
|
|
387
|
-
const
|
|
415
|
+
const proxyTimeouts = (0, app_proxy_timeouts_1.readAppProxyTimeouts)(projectRoot);
|
|
416
|
+
const effectiveStateDigest = stateDigest ?? rolloutStateDigest(profile, {}, proxyTimeouts);
|
|
388
417
|
const root = (0, node_path_1.join)((0, node_path_1.resolve)(projectRoot), ".podokit", "runtime", "deploy", profileName);
|
|
389
418
|
(0, node_fs_1.rmSync)(root, { recursive: true, force: true });
|
|
390
419
|
(0, node_fs_1.mkdirSync)(root, { recursive: true });
|
|
391
420
|
const toolchain = (0, lockfile_1.readManifest)(projectRoot)?.toolchain ?? (0, toolchain_1.resolveToolchain)();
|
|
392
|
-
const composeDocument = renderComposeDocument(profile, release, images, effectiveStateDigest, (0, toolchain_1.toolchainWorkerCommand)(toolchain));
|
|
393
|
-
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));
|
|
394
423
|
const composeFile = (0, node_path_1.join)(root, "compose.yaml");
|
|
395
424
|
const migrationFile = (0, node_path_1.join)(root, "compose.migrate.yaml");
|
|
396
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,11 +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
|
-
TRUSTED_PROXY_HOPS: "1",
|
|
401
|
-
TRUSTED_PROXY_HEADER: "x-forwarded-for",
|
|
402
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" } : {}),
|
|
403
407
|
...(modules.has("rate-limit")
|
|
404
408
|
? {
|
|
405
409
|
RATE_LIMIT_TTL: "60",
|
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,13 @@
|
|
|
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");
|
|
9
11
|
const deploy_schema_1 = require("./deploy-schema");
|
|
10
12
|
const lockfile_1 = require("./lockfile");
|
|
11
13
|
const toolchain_1 = require("./toolchain");
|
|
@@ -112,7 +114,7 @@ ${labels(name, 8)} spec:
|
|
|
112
114
|
- name: postgres
|
|
113
115
|
image: ${quote(image ?? dependency.image)}
|
|
114
116
|
imagePullPolicy: IfNotPresent
|
|
115
|
-
env:
|
|
117
|
+
${dependency.maxConnections === undefined ? "" : ` args: ["-c", ${quote(`max_connections=${dependency.maxConnections}`)}]\n`} env:
|
|
116
118
|
- name: POSTGRES_USER
|
|
117
119
|
valueFrom:
|
|
118
120
|
secretKeyRef: { name: ${dependency.secretName}, key: POSTGRES_USER }
|
|
@@ -351,13 +353,16 @@ function dependencyManifest(profile, images, rolloutStateDigest) {
|
|
|
351
353
|
.filter(Boolean)
|
|
352
354
|
.join("\n---\n");
|
|
353
355
|
}
|
|
354
|
-
|
|
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) {
|
|
355
361
|
const result = {
|
|
356
362
|
PORT: "3000",
|
|
357
363
|
BACKEND_INTERNAL_URL: `http://${profile.target.release}-api:3000`,
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
...(0, deploy_schema_1.idleTimeoutConfig)(profile.runtimeConfig),
|
|
364
|
+
...(0, deploy_schema_1.idleTimeoutConfig)(profile.runtimeConfig, proxyTimeouts),
|
|
365
|
+
...kubernetesProxyTrust(profile),
|
|
361
366
|
...profile.runtimeConfig,
|
|
362
367
|
};
|
|
363
368
|
if (profile.dependencies.postgres.mode === "inCluster") {
|
|
@@ -375,12 +380,11 @@ function derivedRuntimeConfig(profile) {
|
|
|
375
380
|
}
|
|
376
381
|
return result;
|
|
377
382
|
}
|
|
378
|
-
function
|
|
379
|
-
|
|
380
|
-
return digest(["runtime-config", JSON.stringify(entries)]);
|
|
383
|
+
function sortedRuntimeConfig(runtimeConfig) {
|
|
384
|
+
return Object.entries(runtimeConfig).sort(([left], [right]) => left.localeCompare(right));
|
|
381
385
|
}
|
|
382
|
-
function configMap(profile) {
|
|
383
|
-
const entries =
|
|
386
|
+
function configMap(profile, runtimeConfig) {
|
|
387
|
+
const entries = sortedRuntimeConfig(runtimeConfig);
|
|
384
388
|
return `apiVersion: v1
|
|
385
389
|
kind: ConfigMap
|
|
386
390
|
metadata:
|
|
@@ -628,10 +632,10 @@ ${webSocketPaths} - path: /
|
|
|
628
632
|
port: { number: 3000 }
|
|
629
633
|
${apiPathIngress(profile, tls)}`;
|
|
630
634
|
}
|
|
631
|
-
function applicationManifest(profile, images, rolloutStateDigest, workerCommand) {
|
|
632
|
-
const runtimeConfigDigest =
|
|
635
|
+
function applicationManifest(profile, images, rolloutStateDigest, workerCommand, runtimeConfig) {
|
|
636
|
+
const runtimeConfigDigest = digest(["runtime-config", JSON.stringify(sortedRuntimeConfig(runtimeConfig))]);
|
|
633
637
|
return [
|
|
634
|
-
configMap(profile),
|
|
638
|
+
configMap(profile, runtimeConfig),
|
|
635
639
|
deployment(profile, "api", profile.workloads.api, images.api, rolloutStateDigest, runtimeConfigDigest),
|
|
636
640
|
deployment(profile, "web", profile.workloads.web, images.web, rolloutStateDigest, runtimeConfigDigest),
|
|
637
641
|
workerDeployment(profile, images.api, rolloutStateDigest, runtimeConfigDigest, workerCommand),
|
|
@@ -643,14 +647,13 @@ function applicationManifest(profile, images, rolloutStateDigest, workerCommand)
|
|
|
643
647
|
.filter(Boolean)
|
|
644
648
|
.join("\n---\n");
|
|
645
649
|
}
|
|
646
|
-
function migrationRuntimeEnv(
|
|
647
|
-
return
|
|
648
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
650
|
+
function migrationRuntimeEnv(runtimeConfig) {
|
|
651
|
+
return sortedRuntimeConfig(runtimeConfig)
|
|
649
652
|
.map(([key, value]) => ` - name: ${key}
|
|
650
653
|
value: ${quote(value)}`)
|
|
651
654
|
.join("\n");
|
|
652
655
|
}
|
|
653
|
-
function migrationManifest(profile, release, images, defaultCommand) {
|
|
656
|
+
function migrationManifest(profile, release, images, defaultCommand, runtimeConfig) {
|
|
654
657
|
const releaseSlug = release.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
655
658
|
const command = profile.migration?.command ?? defaultCommand;
|
|
656
659
|
return `apiVersion: batch/v1
|
|
@@ -680,7 +683,7 @@ ${labels(`${profile.target.release}-migration`, 8)} spec:
|
|
|
680
683
|
imagePullPolicy: IfNotPresent
|
|
681
684
|
command: ${yamlStringSequence(command)}
|
|
682
685
|
env:
|
|
683
|
-
${migrationRuntimeEnv(
|
|
686
|
+
${migrationRuntimeEnv(runtimeConfig)}
|
|
684
687
|
envFrom:
|
|
685
688
|
- secretRef: { name: ${profile.secrets.api.name} }
|
|
686
689
|
securityContext:
|
|
@@ -694,7 +697,9 @@ function chart(directory, name, manifest) {
|
|
|
694
697
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(directory, "resources.yaml"), manifest || "# No resources\n");
|
|
695
698
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(directory, "templates", "resources.yaml"), '{{ .Files.Get "resources.yaml" }}\n');
|
|
696
699
|
}
|
|
697
|
-
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)) {
|
|
698
703
|
(0, deploy_profile_1.profilePath)(projectRoot, profileName);
|
|
699
704
|
const images = resolvedImages ?? {
|
|
700
705
|
api: `${profile.release.apiRepository}:${release}`,
|
|
@@ -719,8 +724,9 @@ function renderDeployment(projectRoot, profileName, profile, release, resolvedIm
|
|
|
719
724
|
const effectiveRolloutStateDigest = rolloutStateDigest ?? offlineRolloutStateDigest(profile);
|
|
720
725
|
const dependencies = dependencyManifest(profile, images, effectiveRolloutStateDigest);
|
|
721
726
|
const toolchain = (0, lockfile_1.readManifest)(projectRoot)?.toolchain ?? (0, toolchain_1.resolveToolchain)();
|
|
722
|
-
const
|
|
723
|
-
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);
|
|
724
730
|
chart(dependencyChart, "podokit-dependencies", dependencies);
|
|
725
731
|
chart(applicationChart, "podokit-application", application);
|
|
726
732
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, "migration.yaml"), migration);
|