@podosoft/podokit 1.5.0 → 1.6.1

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.
Files changed (215) hide show
  1. package/README.md +2 -1
  2. package/dist/app-proxy-timeouts.d.ts +27 -0
  3. package/dist/app-proxy-timeouts.js +180 -0
  4. package/dist/assemble.d.ts +5 -2
  5. package/dist/assemble.js +14 -4
  6. package/dist/deploy-checks.d.ts +44 -0
  7. package/dist/deploy-checks.js +267 -0
  8. package/dist/deploy-compose-profile.d.ts +6 -2
  9. package/dist/deploy-compose-profile.js +15 -6
  10. package/dist/deploy-compose-render.d.ts +12 -4
  11. package/dist/deploy-compose-render.js +47 -18
  12. package/dist/deploy-compose.d.ts +2 -5
  13. package/dist/deploy-compose.js +7 -2
  14. package/dist/deploy-profile.d.ts +6 -2
  15. package/dist/deploy-profile.js +11 -7
  16. package/dist/deploy-render.d.ts +4 -1
  17. package/dist/deploy-render.js +27 -21
  18. package/dist/deploy-schema.d.ts +60 -2
  19. package/dist/deploy-schema.js +141 -14
  20. package/dist/deploy.d.ts +2 -5
  21. package/dist/deploy.js +5 -2
  22. package/dist/dev.d.ts +21 -0
  23. package/dist/dev.js +78 -3
  24. package/dist/eject.js +2 -0
  25. package/dist/index.js +85 -5
  26. package/dist/lib.d.ts +2 -0
  27. package/dist/lib.js +5 -2
  28. package/dist/lockfile.d.ts +24 -0
  29. package/dist/lockfile.js +74 -4
  30. package/dist/provider.js +10 -5
  31. package/dist/remove.js +1 -1
  32. package/dist/template-files.d.ts +42 -0
  33. package/dist/template-files.js +102 -0
  34. package/dist/templates/fullstack/AGENTS.md +30 -4
  35. package/dist/templates/fullstack/README.md +8 -7
  36. package/dist/templates/fullstack/apps/api/package.json +4 -4
  37. package/dist/templates/fullstack/apps/api/scripts/build.extensions.mjs +6 -0
  38. package/dist/templates/fullstack/apps/api/scripts/build.mjs +65 -16
  39. package/dist/templates/fullstack/apps/api/scripts/run-migrations.mjs +7 -3
  40. package/dist/templates/fullstack/apps/api/scripts/verify-api-contract.ts +7 -2
  41. package/dist/templates/fullstack/apps/api/src/app.spec.ts +22 -0
  42. package/dist/templates/fullstack/apps/api/src/app.ts +41 -25
  43. package/dist/templates/fullstack/apps/api/src/build.spec.ts +121 -0
  44. package/dist/templates/fullstack/apps/api/src/config/env.extensions.ts +20 -0
  45. package/dist/templates/fullstack/apps/api/src/config/env.validation.spec.ts +76 -0
  46. package/dist/templates/fullstack/apps/api/src/config/env.validation.ts +18 -3
  47. package/dist/templates/fullstack/apps/api/src/config/provider-selection.spec.ts +53 -0
  48. package/dist/templates/fullstack/apps/api/src/config/provider-selection.ts +79 -0
  49. package/dist/templates/fullstack/apps/api/src/config/providers.ts +7 -3
  50. package/dist/templates/fullstack/apps/api/src/core/api-contract.extensions.ts +6 -0
  51. package/dist/templates/fullstack/apps/api/src/core/api-contract.spec.ts +72 -4
  52. package/dist/templates/fullstack/apps/api/src/core/api-contract.ts +78 -6
  53. package/dist/templates/fullstack/apps/api/src/core/services.ts +13 -0
  54. package/dist/templates/fullstack/apps/api/src/database/data-source.spec.ts +27 -0
  55. package/dist/templates/fullstack/apps/api/src/database/data-source.ts +7 -10
  56. package/dist/templates/fullstack/apps/api/src/database/database.spec.ts +12 -0
  57. package/dist/templates/fullstack/apps/api/src/database/database.ts +2 -3
  58. package/dist/templates/fullstack/apps/api/src/database/entities.ts +12 -0
  59. package/dist/templates/fullstack/apps/api/src/database/migrate.ts +12 -2
  60. package/dist/templates/fullstack/apps/api/src/database/migration-files.spec.ts +116 -0
  61. package/dist/templates/fullstack/apps/api/src/database/migration-files.ts +134 -0
  62. package/dist/templates/fullstack/apps/api/src/database/migration-readiness.spec.ts +36 -0
  63. package/dist/templates/fullstack/apps/api/src/database/migration-readiness.ts +25 -0
  64. package/dist/templates/fullstack/apps/api/src/database/run-migrations.spec.ts +12 -1
  65. package/dist/templates/fullstack/apps/api/src/database/sqlite-migrator.ts +2 -36
  66. package/dist/templates/fullstack/apps/api/src/main.ts +2 -2
  67. package/dist/templates/fullstack/apps/api/tsconfig.json +2 -0
  68. package/dist/templates/fullstack/apps/web/package.json +1 -1
  69. package/dist/templates/fullstack/apps/web/src/app.css +3 -0
  70. package/dist/templates/fullstack/apps/web/src/app.extensions.css +11 -0
  71. package/dist/templates/fullstack/apps/web/src/lib/components/site-runtime.svelte +3 -1
  72. package/dist/templates/fullstack/apps/web/src/lib/server/api.test.ts +19 -0
  73. package/dist/templates/fullstack/apps/web/src/lib/server/api.ts +4 -1
  74. package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.test.ts +79 -0
  75. package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.ts +55 -1
  76. package/dist/templates/fullstack/apps/web/src/lib/server/proxy-timeouts.ts +2 -1
  77. package/dist/templates/fullstack/apps/web/tsconfig.json +1 -1
  78. package/dist/templates/fullstack/apps/web/vite.config.test.ts +33 -0
  79. package/dist/templates/fullstack/apps/web/vite.config.ts +55 -13
  80. package/dist/templates/fullstack/apps/web/vite.extensions.ts +23 -0
  81. package/dist/templates/fullstack/compose.dev.yaml +7 -4
  82. package/dist/templates/fullstack/dot-agents/skills/podokit-deploy/SKILL.md +2 -2
  83. package/dist/templates/fullstack/dot-agents/skills/podokit-deploy/references/profile.md +13 -3
  84. package/dist/templates/fullstack/dot-claude/skills/podokit-update/SKILL.md +5 -2
  85. package/dist/templates/fullstack/dot-env.docker.example +5 -2
  86. package/dist/templates/fullstack/dot-env.example +4 -0
  87. package/dist/templates/fullstack/dot-podokit/dev.json +1 -1
  88. package/dist/templates/fullstack/infra/k3s/configmap.yaml +1 -0
  89. package/dist/templates/fullstack/tests/README.md +50 -1
  90. package/dist/templates/fullstack/tests/helpers/app-paths.ts +18 -0
  91. package/dist/templates/fullstack/tests/helpers/paths.ts +33 -0
  92. package/dist/templates/fullstack/tests/helpers/seeded-admin.ts +15 -0
  93. package/dist/templates/fullstack/tests/package.json +3 -3
  94. package/dist/templates/fullstack/tests/playwright.config.ts +10 -4
  95. package/dist/templates/fullstack/tests/playwright.extensions.ts +149 -1
  96. package/dist/templates/fullstack/tests/run-e2e.ts +28 -0
  97. package/dist/templates/fullstack/tests/ui/smoke.ui.spec.ts +5 -4
  98. package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.test.ts +37 -1
  99. package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +15 -6
  100. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-flag-settings.test.ts +31 -0
  101. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-flag-settings.ts +31 -0
  102. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-flags.ts +12 -0
  103. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-nav.ts +11 -0
  104. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/nav.test.ts +53 -0
  105. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/nav.ts +35 -0
  106. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/registry.svelte.ts +15 -0
  107. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/account-page.svelte +11 -1
  108. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/admin-sidebar.svelte +2 -23
  109. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.svelte +7 -2
  110. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.test.ts +30 -0
  111. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/impersonation-banner.svelte +3 -2
  112. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/settings/app-flags-settings.svelte +65 -0
  113. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +32 -23
  114. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.test.ts +27 -0
  115. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/site-footer.svelte +4 -1
  116. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/site-runtime.svelte +3 -1
  117. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/theme-toggle.svelte +1 -1
  118. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/en.json +6 -0
  119. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/ko.json +6 -0
  120. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/app-hooks.ts +11 -0
  121. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/non-indexable-paths.ts +8 -0
  122. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/search-indexing.test.ts +31 -0
  123. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/search-indexing.ts +5 -1
  124. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(admin)/admin/settings/+page.svelte +1 -1
  125. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/account/+page.server.ts +2 -1
  126. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/account/+page.svelte +1 -5
  127. package/dist/templates/modules/admin-dashboard/files/dot-claude/skills/podokit-data-table/SKILL.md +2 -0
  128. package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +1 -1
  129. package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +159 -170
  130. package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +72 -81
  131. package/dist/templates/modules/admin-dashboard/files/tests/api/email.api.spec.ts +1 -6
  132. package/dist/templates/modules/admin-dashboard/files/tests/api/profile-image.api.spec.ts +1 -1
  133. package/dist/templates/modules/admin-dashboard/files/tests/api/site-settings.api.spec.ts +43 -53
  134. package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +3 -1
  135. package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +24 -6
  136. package/dist/templates/modules/admin-dashboard/files/tests/helpers/app-accounts.ts +24 -0
  137. package/dist/templates/modules/admin-dashboard/files/tests/helpers/devices.ts +41 -11
  138. package/dist/templates/modules/admin-dashboard/files/tests/helpers/disposable-users.ts +5 -3
  139. package/dist/templates/modules/admin-dashboard/files/tests/helpers/hydration.ts +13 -4
  140. package/dist/templates/modules/admin-dashboard/files/tests/helpers/mailpit.ts +3 -9
  141. package/dist/templates/modules/admin-dashboard/files/tests/helpers/sms.ts +2 -4
  142. package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +14 -6
  143. package/dist/templates/modules/admin-dashboard/files/tests/ui/account-flows.ui.spec.ts +4 -5
  144. package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +55 -55
  145. package/dist/templates/modules/admin-dashboard/files/tests/ui/app-flags.ui.spec.ts +63 -0
  146. package/dist/templates/modules/admin-dashboard/files/tests/ui/appearance.ui.spec.ts +12 -16
  147. package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +3 -2
  148. package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +32 -35
  149. package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +4 -2
  150. package/dist/templates/modules/admin-dashboard/files/tests/ui/email-admin.ui.spec.ts +1 -2
  151. package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +1 -2
  152. package/dist/templates/modules/admin-dashboard/files/tests/ui/general-effects.ui.spec.ts +82 -91
  153. package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +6 -2
  154. package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +16 -20
  155. package/dist/templates/modules/admin-dashboard/files/tests/ui/impersonation.ui.spec.ts +6 -3
  156. package/dist/templates/modules/admin-dashboard/files/tests/ui/login-methods.ui.spec.ts +2 -3
  157. package/dist/templates/modules/admin-dashboard/files/tests/ui/oidc.ui.spec.ts +2 -4
  158. package/dist/templates/modules/admin-dashboard/files/tests/ui/organizations.ui.spec.ts +5 -2
  159. package/dist/templates/modules/admin-dashboard/files/tests/ui/passkey.ui.spec.ts +7 -2
  160. package/dist/templates/modules/admin-dashboard/files/tests/ui/same-origin-requests.ui.spec.ts +89 -0
  161. package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +5 -4
  162. package/dist/templates/modules/admin-dashboard/files/tests/ui/settings-dialogs.ui.spec.ts +67 -73
  163. package/dist/templates/modules/admin-dashboard/files/tests/ui/settings.ui.spec.ts +84 -93
  164. package/dist/templates/modules/admin-dashboard/files/tests/ui/table-toolbar.ui.spec.ts +8 -7
  165. package/dist/templates/modules/admin-dashboard/files/tests/ui/theme-mode.ui.spec.ts +35 -0
  166. package/dist/templates/modules/admin-dashboard/files/tests/ui/two-factor.ui.spec.ts +51 -54
  167. package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +10 -9
  168. package/dist/templates/modules/admin-dashboard/module.manifest.json +7 -2
  169. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +5 -1
  170. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.spec.ts +17 -0
  171. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +18 -0
  172. package/dist/templates/modules/audit-log/module.manifest.json +15 -3
  173. package/dist/templates/modules/auth/files/apps/api/src/auth/auth-app-name.spec.ts +21 -0
  174. package/dist/templates/modules/auth/files/apps/api/src/auth/auth-config-store.ts +13 -1
  175. package/dist/templates/modules/auth/files/apps/api/src/auth/auth-environment.ts +9 -2
  176. package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.ts +19 -3
  177. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.module.ts +2 -0
  178. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.runtime.spec.ts +10 -0
  179. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +7 -5
  180. package/dist/templates/modules/auth/files/apps/api/src/auth/schema-recheck.spec.ts +86 -0
  181. package/dist/templates/modules/auth/files/apps/api/src/auth/schema-recheck.ts +44 -0
  182. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.service.ts +2 -1
  183. package/dist/templates/modules/auth/files/apps/api/src/migrate.ts +3 -1
  184. package/dist/templates/modules/auth/files/apps/api/src/settings/app-flags.ts +9 -0
  185. package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.spec.ts +43 -0
  186. package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.ts +30 -5
  187. package/dist/templates/modules/auth/module.manifest.json +8 -2
  188. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/bullmq-job.queue.spec.ts +107 -1
  189. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/bullmq-job.queue.ts +113 -9
  190. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.spec.ts +40 -0
  191. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +52 -5
  192. package/dist/templates/modules/bullmq/files/apps/api/src/main-worker.ts +9 -7
  193. package/dist/templates/modules/bullmq/files/apps/api/src/worker.extensions.ts +13 -0
  194. package/dist/templates/modules/bullmq/module.manifest.json +5 -1
  195. package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.worker.spec.ts +2 -0
  196. package/dist/templates/modules/jobs-local/files/apps/api/src/jobs-local/local-job.queue.spec.ts +174 -1
  197. package/dist/templates/modules/jobs-local/files/apps/api/src/jobs-local/local-job.queue.ts +150 -3
  198. package/dist/templates/modules/jobs-local/module.manifest.json +3 -0
  199. package/dist/templates/modules/logging/files/apps/api/src/logging/logging.module.spec.ts +16 -0
  200. package/dist/templates/modules/logging/files/apps/api/src/logging/logging.module.ts +15 -10
  201. package/dist/templates/modules/mailer/module.manifest.json +1 -1
  202. package/dist/templates/modules/object-storage-local/files/tests/api/storage-local.api.spec.ts +3 -6
  203. package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +5 -4
  204. package/dist/templates/modules/object-storage-s3/files/tests/api/storage.api.spec.ts +3 -6
  205. package/dist/templates/modules/rate-limit/files/tests/api/rate-limit.api.spec.ts +45 -20
  206. package/dist/templates/modules/sse/files/tests/api/events.api.spec.ts +3 -6
  207. package/dist/templates/todo/AGENTS.md +5 -1
  208. package/dist/templates/todo/README.md +6 -4
  209. package/dist/text-diff.d.ts +12 -0
  210. package/dist/text-diff.js +111 -0
  211. package/dist/update.d.ts +61 -3
  212. package/dist/update.js +152 -27
  213. package/package.json +2 -2
  214. package/dist/templates/modules/admin-dashboard/files/tests/api/exclusive.api.spec.ts +0 -75
  215. package/dist/templates/modules/admin-dashboard/files/tests/helpers/exclusive.ts +0 -80
@@ -1,3 +1,4 @@
1
+ import type { AppProxyTimeout, AppProxyTimeouts } from "./app-proxy-timeouts";
1
2
  /**
2
3
  * Validation primitives shared by every deployment driver.
3
4
  *
@@ -30,6 +31,23 @@ export declare const SENSITIVE_KEY: RegExp;
30
31
  export declare const STABLE_SEMVER_TAG_PATTERN = "^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$";
31
32
  /** Keys PodoKit derives itself, so a profile may not set them. */
32
33
  export declare const MANAGED_RUNTIME_CONFIG_KEYS: string[];
34
+ /**
35
+ * Keys that decide which forwarded address web and API record. They follow from
36
+ * the proxies in front of the deployment, so a profile states that one number and
37
+ * PodoKit renders the four values that agree with it.
38
+ */
39
+ export declare const CLIENT_ADDRESS_KEYS: string[];
40
+ export declare function parseEdgeProxies(exposure: Record<string, unknown>, fallback: number): number;
41
+ /**
42
+ * What the web server and the API trust, given how many proxies each request to
43
+ * the web server has passed. Behind at least one, the web server takes its origin
44
+ * from X-Forwarded-Proto and X-Forwarded-Host and the client from the
45
+ * X-Forwarded-For entry the outermost trusted proxy wrote (XFF_DEPTH); with none
46
+ * it trusts no forwarded header. It forwards that one address to the API, so the
47
+ * API always trusts one hop: the web proxy, the Compose gateway, or an Ingress
48
+ * that trusts no proxy in front of it.
49
+ */
50
+ export declare function proxyTrustConfig(webProxyHops: number, trustForwardedOrigin?: boolean): Record<string, string>;
33
51
  export declare function containsUrlCredentials(value: string): boolean;
34
52
  export declare function isRecord(value: unknown): value is Record<string, unknown>;
35
53
  export declare function assertOnlyKeys(value: Record<string, unknown>, allowed: string[], field: string): void;
@@ -50,13 +68,53 @@ export declare function validateImage(value: string, field: string): void;
50
68
  export declare function validateImageRepository(value: string, field: string): void;
51
69
  export declare function parseReleaseProfile(release: Record<string, unknown>): ReleaseProfile;
52
70
  export declare function parseRuntimeConfig(runtimeConfig: Record<string, unknown>): Record<string, string>;
71
+ /**
72
+ * An application-owned PostgreSQL pool each API or worker process opens beside
73
+ * PodoKit's own. Either `sizeKey` names the runtimeConfig key that caps it, or
74
+ * `size` is the cap the application's code sets, such as a driver's default.
75
+ * `extraConnections` counts connections it opens beyond that cap, such as one to
76
+ * check a server that stopped answering.
77
+ */
78
+ export type AppConnectionPool = {
79
+ sizeKey: string;
80
+ extraConnections: number;
81
+ } | {
82
+ size: number;
83
+ extraConnections: number;
84
+ };
85
+ /** What the connection budget needs to know about the database, beyond its mode. */
86
+ export interface PostgresBudgetProfile {
87
+ /** The server's max_connections; PodoKit sets it on a database it runs. */
88
+ maxConnections?: number;
89
+ appPools?: AppConnectionPool[];
90
+ }
91
+ export declare function parsePostgresBudget(value: Record<string, unknown>, field: string, runtimeConfig: Record<string, string>): PostgresBudgetProfile;
92
+ /** Bun closes silent connections on a 4-second tick, up to 4 seconds before their idle timeout. */
93
+ export declare const IDLE_TICK_SECONDS = 4;
94
+ export declare const MAX_IDLE_TIMEOUT_SECONDS = 255;
95
+ export declare const IDLE_TIMEOUT_KEYS: readonly ["CONNECTION_IDLE_TIMEOUT", "API_IDLE_TIMEOUT"];
96
+ /** The longest the /api proxy waits for response headers, and what sets it. */
97
+ export interface LongestProxyWait {
98
+ /** Milliseconds, or null for a route without a deadline. */
99
+ timeoutMs: number | null;
100
+ source: string;
101
+ }
102
+ /**
103
+ * BACKEND_PROXY_TIMEOUT_MS, or the application's longest APP_PROXY_TIMEOUTS
104
+ * entry when that waits longer; an entry without a deadline wins outright.
105
+ */
106
+ export declare function longestProxyWait(runtimeConfig: Record<string, string>, entries: AppProxyTimeout[]): LongestProxyWait;
107
+ export declare function describeProxyWait(wait: LongestProxyWait): string;
108
+ /** Whether an idle timeout keeps a silent connection open for the whole wait. */
109
+ export declare function idleTimeoutCovers(seconds: number, wait: LongestProxyWait): boolean;
53
110
  /**
54
111
  * The idle timeouts of the web server (CONNECTION_IDLE_TIMEOUT) and the API
55
112
  * (API_IDLE_TIMEOUT), both run by Bun. Each is the profile's own value, or else
56
113
  * the fewest seconds that keep a silent connection open past the /api proxy's
57
- * BACKEND_PROXY_TIMEOUT_MS deadline: 35 for the default 30000 ms.
114
+ * longest wait: 35 for the default 30000 ms, and 0 (off) when an application
115
+ * route has no deadline.
58
116
  */
59
- export declare function idleTimeoutConfig(runtimeConfig: Record<string, string>): Record<string, string>;
117
+ export declare function idleTimeoutConfig(runtimeConfig: Record<string, string>, proxyTimeouts: AppProxyTimeouts): Record<string, string>;
60
118
  export declare function validateHttpsOrigin(value: string): void;
61
119
  export declare function parseVerification(verification: Record<string, unknown>): VerificationProfile;
62
120
  export declare function profileDirectory(projectRoot: string): string;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MANAGED_RUNTIME_CONFIG_KEYS = exports.STABLE_SEMVER_TAG_PATTERN = exports.SENSITIVE_KEY = exports.DNS_LABEL = exports.PROFILE_NAME = void 0;
3
+ exports.IDLE_TIMEOUT_KEYS = exports.MAX_IDLE_TIMEOUT_SECONDS = exports.IDLE_TICK_SECONDS = exports.CLIENT_ADDRESS_KEYS = exports.MANAGED_RUNTIME_CONFIG_KEYS = exports.STABLE_SEMVER_TAG_PATTERN = exports.SENSITIVE_KEY = exports.DNS_LABEL = exports.PROFILE_NAME = void 0;
4
+ exports.parseEdgeProxies = parseEdgeProxies;
5
+ exports.proxyTrustConfig = proxyTrustConfig;
4
6
  exports.containsUrlCredentials = containsUrlCredentials;
5
7
  exports.isRecord = isRecord;
6
8
  exports.assertOnlyKeys = assertOnlyKeys;
@@ -17,6 +19,10 @@ exports.validateImage = validateImage;
17
19
  exports.validateImageRepository = validateImageRepository;
18
20
  exports.parseReleaseProfile = parseReleaseProfile;
19
21
  exports.parseRuntimeConfig = parseRuntimeConfig;
22
+ exports.parsePostgresBudget = parsePostgresBudget;
23
+ exports.longestProxyWait = longestProxyWait;
24
+ exports.describeProxyWait = describeProxyWait;
25
+ exports.idleTimeoutCovers = idleTimeoutCovers;
20
26
  exports.idleTimeoutConfig = idleTimeoutConfig;
21
27
  exports.validateHttpsOrigin = validateHttpsOrigin;
22
28
  exports.parseVerification = parseVerification;
@@ -36,6 +42,38 @@ exports.MANAGED_RUNTIME_CONFIG_KEYS = [
36
42
  "PROTOCOL_HEADER",
37
43
  "HOST_HEADER",
38
44
  ];
45
+ /**
46
+ * Keys that decide which forwarded address web and API record. They follow from
47
+ * the proxies in front of the deployment, so a profile states that one number and
48
+ * PodoKit renders the four values that agree with it.
49
+ */
50
+ exports.CLIENT_ADDRESS_KEYS = ["ADDRESS_HEADER", "XFF_DEPTH", "TRUSTED_PROXY_HOPS", "TRUSTED_PROXY_HEADER"];
51
+ /** Edge proxies are counted, not configured; more than this is a typo. */
52
+ const MAX_EDGE_PROXIES = 8;
53
+ function parseEdgeProxies(exposure, fallback) {
54
+ const value = exposure.edgeProxies ?? fallback;
55
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > MAX_EDGE_PROXIES) {
56
+ throw new Error(`Deployment profile field "exposure.edgeProxies" must be an integer from 0 to ${MAX_EDGE_PROXIES}.`);
57
+ }
58
+ return value;
59
+ }
60
+ /**
61
+ * What the web server and the API trust, given how many proxies each request to
62
+ * the web server has passed. Behind at least one, the web server takes its origin
63
+ * from X-Forwarded-Proto and X-Forwarded-Host and the client from the
64
+ * X-Forwarded-For entry the outermost trusted proxy wrote (XFF_DEPTH); with none
65
+ * it trusts no forwarded header. It forwards that one address to the API, so the
66
+ * API always trusts one hop: the web proxy, the Compose gateway, or an Ingress
67
+ * that trusts no proxy in front of it.
68
+ */
69
+ function proxyTrustConfig(webProxyHops, trustForwardedOrigin = webProxyHops > 0) {
70
+ return {
71
+ ...(trustForwardedOrigin ? { PROTOCOL_HEADER: "x-forwarded-proto", HOST_HEADER: "x-forwarded-host" } : {}),
72
+ ...(webProxyHops > 0 ? { ADDRESS_HEADER: "x-forwarded-for", XFF_DEPTH: String(webProxyHops) } : {}),
73
+ TRUSTED_PROXY_HOPS: "1",
74
+ TRUSTED_PROXY_HEADER: "x-forwarded-for",
75
+ };
76
+ }
39
77
  /** Keys the generated API no longer reads; it refuses to start while they are set. */
40
78
  const RENAMED_RUNTIME_CONFIG_KEYS = {
41
79
  RATE_LIMIT_TRUSTED_PROXY_HOPS: "TRUSTED_PROXY_HOPS",
@@ -189,6 +227,10 @@ function parseRuntimeConfig(runtimeConfig) {
189
227
  if (exports.MANAGED_RUNTIME_CONFIG_KEYS.includes(key)) {
190
228
  throw new Error(`Deployment runtimeConfig key "${key}" is managed by PodoKit.`);
191
229
  }
230
+ if (exports.CLIENT_ADDRESS_KEYS.includes(key)) {
231
+ throw new Error(`Deployment runtimeConfig key "${key}" is derived from exposure.edgeProxies, the number of reverse ` +
232
+ "proxies in front of the deployment; remove it and set exposure.edgeProxies instead.");
233
+ }
192
234
  const renamed = RENAMED_RUNTIME_CONFIG_KEYS[key];
193
235
  if (renamed) {
194
236
  throw new Error(`Deployment runtimeConfig key "${key}" was renamed to "${renamed}".`);
@@ -203,33 +245,118 @@ function parseRuntimeConfig(runtimeConfig) {
203
245
  }
204
246
  return parsed;
205
247
  }
248
+ /** PostgreSQL's own ceiling for max_connections. */
249
+ const MAX_POSTGRES_CONNECTIONS = 262_143;
250
+ /** Pools whose sizes the connection budget already reads. */
251
+ const PODOKIT_POOL_KEYS = ["DB_POOL_MAX", "DB_AUTH_POOL_MAX"];
252
+ function parsePostgresBudget(value, field, runtimeConfig) {
253
+ const parsed = {};
254
+ if (value.maxConnections !== undefined) {
255
+ const maxConnections = value.maxConnections;
256
+ if (typeof maxConnections !== "number" ||
257
+ !Number.isInteger(maxConnections) ||
258
+ maxConnections < 4 ||
259
+ maxConnections > MAX_POSTGRES_CONNECTIONS) {
260
+ throw new Error(`Deployment profile field "${field}.maxConnections" must be an integer from 4 to ${MAX_POSTGRES_CONNECTIONS}.`);
261
+ }
262
+ parsed.maxConnections = maxConnections;
263
+ }
264
+ if (value.appPools !== undefined) {
265
+ if (!Array.isArray(value.appPools)) {
266
+ throw new Error(`Deployment profile field "${field}.appPools" must be an array.`);
267
+ }
268
+ const seen = new Set();
269
+ parsed.appPools = value.appPools.map((entry, index) => {
270
+ const entryField = `${field}.appPools[${index}]`;
271
+ if (!isRecord(entry))
272
+ throw new Error(`Deployment profile field "${entryField}" must be an object.`);
273
+ assertOnlyKeys(entry, ["sizeKey", "size", "extraConnections"], entryField);
274
+ if ((entry.sizeKey === undefined) === (entry.size === undefined)) {
275
+ throw new Error(`Deployment profile field "${entryField}" must set either sizeKey, the runtimeConfig key that caps the pool, ` +
276
+ "or size, the cap the application sets itself.");
277
+ }
278
+ const extraConnections = entry.extraConnections ?? 0;
279
+ if (typeof extraConnections !== "number" || !Number.isInteger(extraConnections) || extraConnections < 0) {
280
+ throw new Error(`Deployment profile field "${entryField}.extraConnections" must be a non-negative integer.`);
281
+ }
282
+ if (entry.size !== undefined) {
283
+ const size = entry.size;
284
+ if (typeof size !== "number" || !Number.isInteger(size) || size < 1 || size > MAX_POSTGRES_CONNECTIONS) {
285
+ throw new Error(`Deployment profile field "${entryField}.size" must be an integer from 1 to ${MAX_POSTGRES_CONNECTIONS}.`);
286
+ }
287
+ return { size, extraConnections };
288
+ }
289
+ const sizeKey = requiredString(entry, "sizeKey");
290
+ if (!/^[A-Z][A-Z0-9_]*$/.test(sizeKey) || PODOKIT_POOL_KEYS.includes(sizeKey) || seen.has(sizeKey)) {
291
+ throw new Error(`Deployment profile field "${entryField}.sizeKey" must name another pool's runtimeConfig key, once; ` +
292
+ `${PODOKIT_POOL_KEYS.join(" and ")} are counted already.`);
293
+ }
294
+ seen.add(sizeKey);
295
+ if (!/^[1-9][0-9]*$/.test(runtimeConfig[sizeKey] ?? "")) {
296
+ throw new Error(`Deployment profile field "${entryField}.sizeKey" names ${sizeKey}, which runtimeConfig must set to a positive integer.`);
297
+ }
298
+ return { sizeKey, extraConnections };
299
+ });
300
+ }
301
+ return parsed;
302
+ }
206
303
  /** Bun closes silent connections on a 4-second tick, up to 4 seconds before their idle timeout. */
207
- const IDLE_TICK_SECONDS = 4;
208
- const MAX_IDLE_TIMEOUT_SECONDS = 255;
304
+ exports.IDLE_TICK_SECONDS = 4;
305
+ exports.MAX_IDLE_TIMEOUT_SECONDS = 255;
209
306
  const DEFAULT_BACKEND_PROXY_TIMEOUT_MS = 30_000;
307
+ exports.IDLE_TIMEOUT_KEYS = ["CONNECTION_IDLE_TIMEOUT", "API_IDLE_TIMEOUT"];
308
+ /**
309
+ * BACKEND_PROXY_TIMEOUT_MS, or the application's longest APP_PROXY_TIMEOUTS
310
+ * entry when that waits longer; an entry without a deadline wins outright.
311
+ */
312
+ function longestProxyWait(runtimeConfig, entries) {
313
+ const proxyTimeout = runtimeConfig.BACKEND_PROXY_TIMEOUT_MS?.trim();
314
+ let longest = {
315
+ timeoutMs: proxyTimeout && /^\d+$/.test(proxyTimeout) ? Number(proxyTimeout) : DEFAULT_BACKEND_PROXY_TIMEOUT_MS,
316
+ source: "BACKEND_PROXY_TIMEOUT_MS",
317
+ };
318
+ for (const entry of entries) {
319
+ if (longest.timeoutMs !== null && (entry.timeoutMs === null || entry.timeoutMs > longest.timeoutMs)) {
320
+ longest = { timeoutMs: entry.timeoutMs, source: `APP_PROXY_TIMEOUTS entry ${entry.pathPrefix}` };
321
+ }
322
+ }
323
+ return longest;
324
+ }
325
+ function describeProxyWait(wait) {
326
+ return `${wait.timeoutMs === null ? "no deadline" : `${wait.timeoutMs} ms`}, ${wait.source}`;
327
+ }
328
+ /** Whether an idle timeout keeps a silent connection open for the whole wait. */
329
+ function idleTimeoutCovers(seconds, wait) {
330
+ return seconds === 0 || (wait.timeoutMs !== null && (seconds - exports.IDLE_TICK_SECONDS) * 1000 > wait.timeoutMs);
331
+ }
210
332
  /**
211
333
  * The idle timeouts of the web server (CONNECTION_IDLE_TIMEOUT) and the API
212
334
  * (API_IDLE_TIMEOUT), both run by Bun. Each is the profile's own value, or else
213
335
  * the fewest seconds that keep a silent connection open past the /api proxy's
214
- * BACKEND_PROXY_TIMEOUT_MS deadline: 35 for the default 30000 ms.
336
+ * longest wait: 35 for the default 30000 ms, and 0 (off) when an application
337
+ * route has no deadline.
215
338
  */
216
- function idleTimeoutConfig(runtimeConfig) {
217
- const proxyTimeout = runtimeConfig.BACKEND_PROXY_TIMEOUT_MS?.trim();
218
- const proxyMs = proxyTimeout && /^\d+$/.test(proxyTimeout) ? Number(proxyTimeout) : DEFAULT_BACKEND_PROXY_TIMEOUT_MS;
219
- const derived = Math.floor(proxyMs / 1000) + IDLE_TICK_SECONDS + 1;
339
+ function idleTimeoutConfig(runtimeConfig, proxyTimeouts) {
220
340
  const config = {};
221
- for (const name of ["CONNECTION_IDLE_TIMEOUT", "API_IDLE_TIMEOUT"]) {
341
+ for (const name of exports.IDLE_TIMEOUT_KEYS) {
222
342
  const configured = runtimeConfig[name];
223
343
  if (configured !== undefined) {
224
344
  config[name] = configured;
345
+ continue;
225
346
  }
226
- else if (derived > MAX_IDLE_TIMEOUT_SECONDS) {
227
- throw new Error(`Deployment runtimeConfig BACKEND_PROXY_TIMEOUT_MS ${proxyMs} outlasts the longest idle timeout ` +
228
- `(${MAX_IDLE_TIMEOUT_SECONDS} seconds); set ${name} to "0" to turn it off.`);
347
+ if ("error" in proxyTimeouts)
348
+ throw new Error(proxyTimeouts.error);
349
+ const wait = longestProxyWait(runtimeConfig, proxyTimeouts.entries);
350
+ if (wait.timeoutMs === null) {
351
+ config[name] = "0";
352
+ continue;
229
353
  }
230
- else {
231
- config[name] = String(derived);
354
+ const derived = Math.floor(wait.timeoutMs / 1000) + exports.IDLE_TICK_SECONDS + 1;
355
+ if (derived > exports.MAX_IDLE_TIMEOUT_SECONDS) {
356
+ throw new Error(`The /api proxy waits up to ${wait.timeoutMs} ms (${wait.source}), longer than the longest idle timeout ` +
357
+ `(${exports.MAX_IDLE_TIMEOUT_SECONDS} seconds); set ${name} to "0" to turn it off.`);
232
358
  }
359
+ config[name] = String(derived);
233
360
  }
234
361
  return config;
235
362
  }
package/dist/deploy.d.ts CHANGED
@@ -1,10 +1,7 @@
1
+ import { type DeployFinding } from "./deploy-checks";
1
2
  import type { CommandRunner } from "./dev";
2
3
  import { type DeploymentImages } from "./deploy-render";
3
- export interface DeploymentDoctorFinding {
4
- code: string;
5
- ok: boolean;
6
- message: string;
7
- }
4
+ export type DeploymentDoctorFinding = DeployFinding;
8
5
  export interface DeploymentPlanAction {
9
6
  order: number;
10
7
  kind: "dependencies" | "migration" | "application" | "verification" | "rollback";
package/dist/deploy.js CHANGED
@@ -14,6 +14,8 @@ const node_fs_1 = require("node:fs");
14
14
  const node_os_1 = require("node:os");
15
15
  const node_path_1 = require("node:path");
16
16
  const api_paths_1 = require("./api-paths");
17
+ const app_proxy_timeouts_1 = require("./app-proxy-timeouts");
18
+ const deploy_checks_1 = require("./deploy-checks");
17
19
  const deploy_profile_1 = require("./deploy-profile");
18
20
  const deploy_render_1 = require("./deploy-render");
19
21
  function defaultRunner(command, args, options) {
@@ -143,7 +145,7 @@ function apiSecretRequirements(profile) {
143
145
  }
144
146
  function doctorDeployment(projectRoot, profileName, runner = defaultRunner) {
145
147
  const profile = (0, deploy_profile_1.loadDeploymentProfile)(projectRoot, profileName);
146
- const findings = [];
148
+ const findings = (0, deploy_checks_1.profileFindings)(projectRoot, profile);
147
149
  const fingerprint = inspectClusterFingerprint(profile.target.context, runner);
148
150
  findings.push({
149
151
  code: "cluster-fingerprint",
@@ -496,11 +498,12 @@ function planDeployment(projectRoot, profileName, release, runner = defaultRunne
496
498
  warnings: [
497
499
  "Application rollback does not reverse database migrations.",
498
500
  "PersistentVolumeClaims and Kubernetes Secrets are never deleted by apply or rollback.",
501
+ ...[(0, deploy_checks_1.connectionBudgetWarning)(profile, (0, deploy_checks_1.installedModules)(projectRoot))].filter((warning) => warning !== null),
499
502
  ],
500
503
  };
501
504
  const temporaryRoot = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "podokit-deploy-plan-"));
502
505
  try {
503
- const runtime = (0, deploy_render_1.renderDeployment)(temporaryRoot, profileName, profile, release, images, state.rolloutStateDigest);
506
+ const runtime = (0, deploy_render_1.renderDeployment)(temporaryRoot, profileName, profile, release, images, state.rolloutStateDigest, (0, app_proxy_timeouts_1.readAppProxyTimeouts)(projectRoot));
504
507
  return { ...base, planHash: planDigest(base, runtime) };
505
508
  }
506
509
  finally {
package/dist/dev.d.ts CHANGED
@@ -9,6 +9,10 @@ export interface DevConfig {
9
9
  export interface DevRuntime {
10
10
  alias: string;
11
11
  config: DevConfig;
12
+ /** Browser origins of the development stack, for CORS_ORIGIN. */
13
+ origins: string[];
14
+ /** BETTER_AUTH_URL for the containers. */
15
+ authUrl: string;
12
16
  projectRoot: string;
13
17
  routeId: string;
14
18
  runtimeComposePath: string;
@@ -22,8 +26,25 @@ export type CommandRunner = (command: string, args: string[], options: {
22
26
  capture: boolean;
23
27
  }) => CommandResult;
24
28
  export declare function readDevConfig(projectRoot: string): DevConfig;
29
+ /**
30
+ * The origins a browser reaches the development stack on: the gateway
31
+ * hostname, the HTTPS tunnel origin when one is declared, and plain localhost
32
+ * for the compose file's own proxy.
33
+ */
34
+ export declare function devOrigins(config: Pick<DevConfig, "hostname" | "publicUrl">): string[];
35
+ /**
36
+ * Template variables for files that name the development host. They follow
37
+ * `.podokit/dev.json` when it exists and is valid, so a renamed host renders
38
+ * the same way it is routed; otherwise the project name's default.
39
+ */
40
+ export declare function devTemplateVars(projectName: string, projectRoot?: string): Record<string, string>;
25
41
  export declare function resolveDevRuntime(projectRoot: string): DevRuntime;
26
42
  export declare function installedDevProfiles(projectRoot: string): string[];
43
+ /**
44
+ * The compose overlay `podo dev` adds. Besides the gateway network it sets
45
+ * CORS_ORIGIN and BETTER_AUTH_URL for web and api from `.podokit/dev.json`, so
46
+ * the browser origins follow the routed hostname whatever `.env.docker` says.
47
+ */
27
48
  export declare function renderRuntimeCompose(runtime: DevRuntime): string;
28
49
  export declare function renderRoute(runtime: DevRuntime): string;
29
50
  /**
package/dist/dev.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.readDevConfig = readDevConfig;
4
+ exports.devOrigins = devOrigins;
5
+ exports.devTemplateVars = devTemplateVars;
4
6
  exports.resolveDevRuntime = resolveDevRuntime;
5
7
  exports.installedDevProfiles = installedDevProfiles;
6
8
  exports.renderRuntimeCompose = renderRuntimeCompose;
@@ -106,6 +108,68 @@ function readDevConfig(projectRoot) {
106
108
  apiPaths: (0, api_paths_1.parseApiPaths)(config.apiPaths ?? [], `${DEV_CONFIG}.apiPaths`, webSocketPaths),
107
109
  };
108
110
  }
111
+ /**
112
+ * The origins a browser reaches the development stack on: the gateway
113
+ * hostname, the HTTPS tunnel origin when one is declared, and plain localhost
114
+ * for the compose file's own proxy.
115
+ */
116
+ function devOrigins(config) {
117
+ return [
118
+ ...new Set([
119
+ `http://${config.hostname}`,
120
+ ...(config.publicUrl ? [new URL(config.publicUrl).origin] : []),
121
+ "http://localhost",
122
+ ]),
123
+ ];
124
+ }
125
+ /**
126
+ * Template variables for files that name the development host. They follow
127
+ * `.podokit/dev.json` when it exists and is valid, so a renamed host renders
128
+ * the same way it is routed; otherwise the project name's default.
129
+ */
130
+ function devTemplateVars(projectName, projectRoot) {
131
+ let config = { hostname: `${projectName}.localhost` };
132
+ const path = projectRoot ? (0, node_path_1.join)(projectRoot, DEV_CONFIG) : undefined;
133
+ if (path && (0, node_fs_1.existsSync)(path)) {
134
+ try {
135
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
136
+ if (typeof parsed.hostname === "string") {
137
+ validateHostname(parsed.hostname);
138
+ if (parsed.publicUrl)
139
+ validatePublicUrl(parsed.publicUrl);
140
+ config = { hostname: parsed.hostname, publicUrl: parsed.publicUrl };
141
+ }
142
+ }
143
+ catch {
144
+ // podo dev reports an unreadable dev.json; rendering keeps the default.
145
+ }
146
+ }
147
+ return {
148
+ devHostname: config.hostname,
149
+ devCorsOrigin: devOrigins(config).join(","),
150
+ devAuthUrl: `http://${config.hostname}`,
151
+ };
152
+ }
153
+ /**
154
+ * BETTER_AUTH_URL for the containers: the gateway origin, or the origin named
155
+ * by PODOKIT_DEV_AUTH_URL for an OAuth round trip through `publicUrl`.
156
+ */
157
+ function devAuthUrl(config, origins) {
158
+ const requested = process.env.PODOKIT_DEV_AUTH_URL?.trim();
159
+ if (!requested)
160
+ return `http://${config.hostname}`;
161
+ let origin;
162
+ try {
163
+ origin = new URL(requested).origin;
164
+ }
165
+ catch {
166
+ origin = "";
167
+ }
168
+ if (!origins.includes(origin) || origin === "http://localhost") {
169
+ throw new Error(`PODOKIT_DEV_AUTH_URL must be http://${config.hostname}${config.publicUrl ? ` or ${new URL(config.publicUrl).origin}` : ""} (from ${DEV_CONFIG}).`);
170
+ }
171
+ return origin;
172
+ }
109
173
  function stableId(projectRoot) {
110
174
  return (0, node_crypto_1.createHash)("sha256").update(projectRoot).digest("hex").slice(0, 10);
111
175
  }
@@ -118,9 +182,12 @@ function resolveDevRuntime(projectRoot) {
118
182
  const config = readDevConfig(root);
119
183
  const id = stableId(root);
120
184
  const projectSlug = slug(config.hostname.replace(/\.localhost$/, ""));
185
+ const origins = devOrigins(config);
121
186
  return {
122
187
  alias: `podokit-${projectSlug}-${id}`,
123
188
  config,
189
+ origins,
190
+ authUrl: devAuthUrl(config, origins),
124
191
  projectRoot: root,
125
192
  routeId: `route-${projectSlug}-${id}`,
126
193
  runtimeComposePath: (0, node_path_1.join)(root, RUNTIME_COMPOSE),
@@ -143,11 +210,19 @@ function routesToApi(config) {
143
210
  function anyOf(matchers) {
144
211
  return matchers.length > 1 ? `(${matchers.join(" || ")})` : (matchers[0] ?? "");
145
212
  }
213
+ /**
214
+ * The compose overlay `podo dev` adds. Besides the gateway network it sets
215
+ * CORS_ORIGIN and BETTER_AUTH_URL for web and api from `.podokit/dev.json`, so
216
+ * the browser origins follow the routed hostname whatever `.env.docker` says.
217
+ */
146
218
  function renderRuntimeCompose(runtime) {
147
- const api = routesToApi(runtime.config)
148
- ? ` api:\n networks:\n ${GATEWAY_NETWORK}:\n aliases: [${runtime.alias}-api]\n`
219
+ const environment = ` environment:\n` +
220
+ ` CORS_ORIGIN: ${JSON.stringify(runtime.origins.join(","))}\n` +
221
+ ` BETTER_AUTH_URL: ${JSON.stringify(runtime.authUrl)}\n`;
222
+ const apiNetworks = routesToApi(runtime.config)
223
+ ? ` networks:\n ${GATEWAY_NETWORK}:\n aliases: [${runtime.alias}-api]\n`
149
224
  : "";
150
- return `# Generated by podo dev. Do not edit.\nservices:\n traefik:\n profiles: [podokit-legacy-proxy]\n web:\n networks:\n ${GATEWAY_NETWORK}:\n aliases: [${runtime.alias}]\n${api}networks:\n ${GATEWAY_NETWORK}:\n external: true\n name: ${GATEWAY_NETWORK}\n`;
225
+ return `# Generated by podo dev. Do not edit.\nservices:\n traefik:\n profiles: [podokit-legacy-proxy]\n web:\n${environment} networks:\n ${GATEWAY_NETWORK}:\n aliases: [${runtime.alias}]\n api:\n${environment}${apiNetworks}networks:\n ${GATEWAY_NETWORK}:\n external: true\n name: ${GATEWAY_NETWORK}\n`;
151
226
  }
152
227
  function renderRoute(runtime) {
153
228
  const { alias, config, routeId } = runtime;
package/dist/eject.js CHANGED
@@ -17,6 +17,8 @@ function eject(projectRoot, paths) {
17
17
  continue;
18
18
  }
19
19
  if (entry.tier !== "owned") {
20
+ // A managed file's baseline is what the template generated.
21
+ entry.templateHash = entry.outHash;
20
22
  entry.tier = "owned";
21
23
  result.ejected.push(path);
22
24
  }