openship 0.4.8 → 0.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.
Files changed (112) hide show
  1. package/README.md +27 -1
  2. package/dist/bare-FSUAM5V5.js +23 -0
  3. package/dist/{bowser-O3AEA6AN.js → bowser-HNOTPGJO.js} +2 -2
  4. package/dist/{chunk-5MZ624VX.js → chunk-2GWYYHOR.js} +1 -1
  5. package/dist/chunk-3FYIXVX5.js +8051 -0
  6. package/dist/{chunk-ILFETZOM.js → chunk-3LDWWY7U.js} +21 -1
  7. package/dist/{chunk-W73X43R7.js → chunk-3NQQXMDR.js} +3 -3
  8. package/dist/{chunk-OWED2GRM.js → chunk-42WQABXM.js} +2 -2
  9. package/dist/{chunk-TYVME3XD.js → chunk-6FH4AXVY.js} +20 -3
  10. package/dist/{chunk-OXWDY22G.js → chunk-6ODUBKUB.js} +3 -3
  11. package/dist/{chunk-DYPH4HCI.js → chunk-AO252K7Z.js} +1 -1
  12. package/dist/{chunk-XPPZ7B2J.js → chunk-B3CE2OMR.js} +14 -12
  13. package/dist/{chunk-IXCHURVK.js → chunk-DOD2BXHQ.js} +4 -4
  14. package/dist/{chunk-VWPEKOH6.js → chunk-DRK3MEDS.js} +3 -3
  15. package/dist/{chunk-V6AN6XLZ.js → chunk-EMXI4UCU.js} +125 -47
  16. package/dist/{chunk-GKHUSZCD.js → chunk-EP2TNALB.js} +2 -2
  17. package/dist/{chunk-SLTYVHU7.js → chunk-GUQ3QCJ7.js} +942 -519
  18. package/dist/chunk-I3DPMTSF.js +29 -0
  19. package/dist/{chunk-L2VOGB2X.js → chunk-IW2UKBVF.js} +3 -3
  20. package/dist/{chunk-VDMQJHPI.js → chunk-IXM5245F.js} +228 -13
  21. package/dist/{chunk-MZ2WEHHY.js → chunk-JIMUYEKN.js} +4 -4
  22. package/dist/chunk-OGY63DGD.js +705 -0
  23. package/dist/{chunk-O6ZZF52V.js → chunk-ONKEYSMY.js} +2 -2
  24. package/dist/{chunk-6APULWSU.js → chunk-OTLSJYAC.js} +1744 -118
  25. package/dist/{chunk-EI575GHN.js → chunk-PDW4L3NV.js} +3 -3
  26. package/dist/{chunk-5NYUCONN.js → chunk-PSZSKEPA.js} +33 -51
  27. package/dist/{chunk-KI2EY3WB.js → chunk-PXKCZB2I.js} +1 -1
  28. package/dist/{chunk-FXTBHTWY.js → chunk-QEH4QUBX.js} +649 -85
  29. package/dist/{chunk-6APIPIJY.js → chunk-QWHBRY2K.js} +5 -5
  30. package/dist/{chunk-6B5YCFFV.js → chunk-RVER2EOO.js} +5 -5
  31. package/dist/{chunk-Q2CIRAOK.js → chunk-RXQBD4ZY.js} +1 -1
  32. package/dist/{chunk-TGIINPVM.js → chunk-TWJCLD4W.js} +60 -10
  33. package/dist/{chunk-4Z5QO7N6.js → chunk-VDDMADOF.js} +3 -3
  34. package/dist/{chunk-BBHX7T4R.js → chunk-VE2YOK4I.js} +991 -254
  35. package/dist/{chunk-2D3I5G6P.js → chunk-VZ2ZHPS2.js} +73 -43
  36. package/dist/{chunk-AVIB7CT7.js → chunk-WNWQBB7F.js} +2 -2
  37. package/dist/{chunk-FRNAR33O.js → chunk-XZYFZ6A5.js} +3 -3
  38. package/dist/{chunk-RPHRPFEH.js → chunk-ZCFVHA25.js} +1 -1
  39. package/dist/{chunk-FPRHYBY2.js → chunk-ZFJXPCQP.js} +1 -1
  40. package/dist/{chunk-JYGD5WSN.js → chunk-ZWLDLTJP.js} +1 -1
  41. package/dist/cloud-E5M6VMCG.js +31 -0
  42. package/dist/{cloud-7QYOGF7F.js → cloud-KRTZ2IL5.js} +3 -3
  43. package/dist/{detect-AZLOHUFX.js → detect-ALF4GXRX.js} +7 -8
  44. package/dist/{dist-IQJ6H6UZ.js → dist-BM6NPWIH.js} +3 -3
  45. package/dist/{dist-es-X3B5QYKB.js → dist-es-2YRDXMUU.js} +6 -6
  46. package/dist/{dist-es-OSNJBLXB.js → dist-es-CCVZEFRZ.js} +5 -5
  47. package/dist/{dist-es-VNKHQVIB.js → dist-es-IOH6G4GM.js} +4 -4
  48. package/dist/{dist-es-RYHIPOTN.js → dist-es-JGBPABJI.js} +14 -14
  49. package/dist/{dist-es-JSTDUS7Q.js → dist-es-KWSMDPIP.js} +6 -6
  50. package/dist/{dist-es-5DY7K32L.js → dist-es-UYDDOVZ3.js} +6 -6
  51. package/dist/{dist-es-WV4AMOII.js → dist-es-XUOR4NQN.js} +8 -8
  52. package/dist/docker-QECQJ5P4.js +32 -0
  53. package/dist/docker-edge-executor-62N64MGS.js +17 -0
  54. package/dist/edge-import-CQWHN2Q3.js +48 -0
  55. package/dist/ensure-container-edge-MJVXJQG5.js +32 -0
  56. package/dist/{event-streams-EHRAKL75.js → event-streams-TR4GAC53.js} +4 -4
  57. package/dist/{executor-XJ6RP4Q2.js → executor-JELARVKC.js} +8 -8
  58. package/dist/index.js +1209 -560
  59. package/dist/{loadSso-46FQRIZS.js → loadSso-UGF4JMRK.js} +10 -10
  60. package/dist/{local-executor-V7U5VOZW.js → local-executor-WOFZD72X.js} +4 -4
  61. package/dist/nginx-AXGNWRKZ.js +31 -0
  62. package/dist/node-entry.js +4 -0
  63. package/dist/{noop-FMMFG2EZ.js → noop-NJ72F46T.js} +3 -3
  64. package/dist/openresty-lua-IE4P5YQX.js +81 -0
  65. package/dist/server/index.js +116248 -94749
  66. package/dist/server/lua/geo_country.lua +31 -6
  67. package/dist/server/lua/maxminddb.lua +403 -0
  68. package/dist/server/lua/mgmt_api.lua +431 -68
  69. package/dist/server/lua/pipe_log.lua +10 -4
  70. package/dist/server/lua/site_logger.lua +220 -12
  71. package/dist/server/migrations/0073_project_volumes_object_storage.sql +3 -0
  72. package/dist/server/migrations/0074_project_compose_path.sql +1 -0
  73. package/dist/server/migrations/0075_service_command_argv.sql +1 -0
  74. package/dist/server/migrations/0076_rollback_retention.sql +17 -0
  75. package/dist/server/migrations/0077_domain_redirect.sql +13 -0
  76. package/dist/server/migrations/0078_grant_source_scope.sql +30 -0
  77. package/dist/server/migrations/0079_project_readiness.sql +18 -0
  78. package/dist/server/migrations/0080_deployment_error_classification.sql +26 -0
  79. package/dist/server/migrations/0081_project_readiness_repair.sql +34 -0
  80. package/dist/server/migrations/0082_edge_target_verification.sql +39 -0
  81. package/dist/server/migrations/0083_service_incident.sql +84 -0
  82. package/dist/server/migrations/0084_analytics_daily_rollup.sql +72 -0
  83. package/dist/server/migrations/0085_resource_usage.sql +68 -0
  84. package/dist/server/migrations/0086_remote_infra_updates.sql +41 -0
  85. package/dist/server/migrations/0087_server_container_version_columns.sql +17 -0
  86. package/dist/server/migrations/0088_audit_source_and_settings.sql +27 -0
  87. package/dist/server/migrations/0089_auto_scan_infra.sql +7 -0
  88. package/dist/server/migrations/0090_project_collect_paths.sql +18 -0
  89. package/dist/server/migrations/0091_project_server_id.sql +26 -0
  90. package/dist/server/migrations/0092_project_internal_alias.sql +13 -0
  91. package/dist/server/migrations/0093_service_incident_org_index.sql +13 -0
  92. package/dist/server/migrations/0094_backup_restore_meta.sql +15 -0
  93. package/dist/server/migrations/0095_backup_restore_cancel.sql +19 -0
  94. package/dist/server/migrations/0096_backup_policy_retention_defaults.sql +20 -0
  95. package/dist/server/migrations/0097_update_status_upstream_only.sql +35 -0
  96. package/dist/server/migrations/meta/_journal.json +175 -0
  97. package/dist/setup-FA4PW4NU.js +21 -0
  98. package/dist/{signin-ITOPKKUB.js → signin-3M3UPB6K.js} +10 -10
  99. package/dist/{sso-oidc-NON2PD7Y.js → sso-oidc-3VDIYOEJ.js} +10 -10
  100. package/dist/{sts-HHFFSAY2.js → sts-B3WIYLXR.js} +9 -9
  101. package/package.json +6 -3
  102. package/dist/bare-JY5Y5ELU.js +0 -23
  103. package/dist/chunk-SJIFLLX4.js +0 -2931
  104. package/dist/chunk-VFINNTUL.js +0 -362
  105. package/dist/cloud-KYWY3RP4.js +0 -30
  106. package/dist/docker-LODK5Y2S.js +0 -16
  107. package/dist/docker-edge-executor-VHW3JPJS.js +0 -13
  108. package/dist/edge-import-ABUDKKJI.js +0 -45
  109. package/dist/ensure-container-edge-SW6HENSG.js +0 -31
  110. package/dist/nginx-3L4SIBJA.js +0 -24
  111. package/dist/openresty-lua-4WLGDSER.js +0 -42
  112. package/dist/setup-NU6T2PDU.js +0 -20
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env sh
2
- ":" //# ; exec "$(command -v bun || command -v node)" "$0" "$@"
2
+ ":" //# ; exec "$(command -v node || command -v bun)" "$0" "$@"
3
3
  import { createRequire as __ospCreateRequire } from "node:module";
4
4
  const require = __ospCreateRequire(import.meta.url);
5
5
  import {
6
6
  __export
7
- } from "./chunk-KI2EY3WB.js";
7
+ } from "./chunk-PXKCZB2I.js";
8
8
 
9
9
  // ../../packages/core/src/stacks.ts
10
10
  var LANGUAGES = {
@@ -52,8 +52,15 @@ var LANGUAGES = {
52
52
  },
53
53
  php: {
54
54
  name: "PHP",
55
- buildImage: "php:8.3-cli",
56
- runtimeImage: "php:8.3-fpm",
55
+ buildImage: "php:8.4-cli",
56
+ // FrankenPHP, not php:*-fpm. An fpm image is a FastCGI backend, not a
57
+ // process host: it ships no web server, and pairing it with an apt nginx
58
+ // means two processes under a shell that swallows SIGTERM — a container stop
59
+ // then kills in-flight work instead of draining it. FrankenPHP is ONE
60
+ // process that owns both the HTTP server and PHP, so signals land where they
61
+ // should with no supervision tree, it runs fine as a non-root user, and its
62
+ // docroot convention (`/app/public`) is already the layout our recipes emit.
63
+ runtimeImage: "dunglas/frankenphp:1-php8.4-bookworm",
57
64
  packageManagers: ["composer"],
58
65
  requiredTools: ["php", "composer"]
59
66
  },
@@ -146,6 +153,34 @@ var STACKS = {
146
153
  deps: ["@remix-run/react", "@remix-run/node", "remix"]
147
154
  }
148
155
  },
156
+ "tanstack-start": {
157
+ name: "TanStack Start",
158
+ language: "typescript",
159
+ category: "fullstack",
160
+ outputDirectory: ".output",
161
+ defaultPort: 3e3,
162
+ defaultBuildCommand: "vite build",
163
+ defaultStartCommand: "node .output/server/index.mjs",
164
+ // Nitro/Vinxi server bundle is self-contained under .output — same shape as Nuxt.
165
+ productionPaths: [".output"],
166
+ detection: {
167
+ // TanStack Start is Vite/Rsbuild-based; app.config.* is the older
168
+ // framework-specific marker, while current apps commonly ship vite.config.*
169
+ // or rsbuild.config.* plus the start package dep.
170
+ rootMarkers: [
171
+ "app.config.ts",
172
+ "app.config.js",
173
+ "app.config.mjs",
174
+ "vite.config.ts",
175
+ "vite.config.js",
176
+ "vite.config.mjs",
177
+ "rsbuild.config.ts",
178
+ "rsbuild.config.js",
179
+ "rsbuild.config.mjs"
180
+ ],
181
+ deps: ["@tanstack/react-start", "@tanstack/start"]
182
+ }
183
+ },
149
184
  astro: {
150
185
  name: "Astro",
151
186
  language: "typescript",
@@ -515,18 +550,28 @@ var STACKS = {
515
550
  }
516
551
  },
517
552
  // ── PHP ────────────────────────────────────────────────────────────────────
518
- // PHP stacks run php-fpm behind nginx (docroot public/). The generated
519
- // Dockerfile (docker-build-plan.ts PHP branch) installs nginx + writes the
520
- // config template; this start command renders it for the injected $PORT and
521
- // launches both processes. Runtime image inherits php:8.3-fpm from the language.
553
+ // PHP stacks serve `public/` from FrankenPHP (see LANGUAGES.php). `exec` hands
554
+ // the container's PID to frankenphp so SIGTERM drains in-flight requests
555
+ // instead of being swallowed by the shell; SERVER_NAME carries the injected
556
+ // $PORT (a bare `:port` also keeps Caddy's automatic HTTPS out of the way —
557
+ // TLS terminates at the edge). No build command: `composer install` is the
558
+ // INSTALL step, and the build step is where a JS asset pipeline goes (the
559
+ // detector fills it in when the repo has one).
522
560
  laravel: {
523
561
  name: "Laravel",
524
562
  language: "php",
525
563
  category: "fullstack",
526
564
  outputDirectory: "public",
527
565
  defaultPort: 8e3,
528
- defaultBuildCommand: "composer install --no-dev --optimize-autoloader",
529
- defaultStartCommand: "envsubst '$PORT' < /etc/nginx/app.conf.template > /etc/nginx/conf.d/default.conf && php-fpm -D && nginx -g 'daemon off;'",
566
+ defaultBuildCommand: "",
567
+ defaultStartCommand: 'SERVER_NAME=":$PORT" exec frankenphp run --config /etc/frankenphp/Caddyfile --adapter caddyfile',
568
+ // Everything a stock Laravel app writes lives here: uploads
569
+ // (storage/app), sessions + cache when the drivers are `file`, and the
570
+ // framework's own scratch space. `database/` is deliberately NOT persisted —
571
+ // it holds migrations, so mounting over it would hide the ones a later
572
+ // release adds. A SQLite app should point DB_DATABASE at a persisted path or
573
+ // (better) use a database service.
574
+ persistentPaths: ["storage"],
530
575
  detection: {
531
576
  rootMarkers: ["artisan", "composer.json"],
532
577
  deps: ["laravel/framework"]
@@ -538,8 +583,10 @@ var STACKS = {
538
583
  category: "fullstack",
539
584
  outputDirectory: "public",
540
585
  defaultPort: 8e3,
541
- defaultBuildCommand: "composer install --no-dev --optimize-autoloader",
542
- defaultStartCommand: "envsubst '$PORT' < /etc/nginx/app.conf.template > /etc/nginx/conf.d/default.conf && php-fpm -D && nginx -g 'daemon off;'",
586
+ defaultBuildCommand: "",
587
+ defaultStartCommand: 'SERVER_NAME=":$PORT" exec frankenphp run --config /etc/frankenphp/Caddyfile --adapter caddyfile',
588
+ // No persistentPaths: Symfony's `var/` is cache + logs, both regenerated,
589
+ // and it has no convention for where user uploads land.
543
590
  detection: {
544
591
  rootMarkers: ["composer.json", "symfony.lock"],
545
592
  deps: ["symfony/framework-bundle"]
@@ -778,6 +825,9 @@ var STACK_ROOT_MARKERS = new Set(
778
825
  Object.values(STACKS).flatMap((stack) => stack.detection?.rootMarkers ?? []).map((marker) => marker.toLowerCase())
779
826
  );
780
827
  function packageManagerEnsureCommand(packageManager) {
828
+ if (packageManager === "bun") {
829
+ return `(command -v bun >/dev/null 2>&1 || npm i -g bun) >/dev/null 2>&1 || true`;
830
+ }
781
831
  if (packageManager !== "pnpm" && packageManager !== "yarn") return "";
782
832
  return `(corepack enable ${packageManager} || corepack enable || npm i -g ${packageManager}) >/dev/null 2>&1 || true`;
783
833
  }
@@ -788,6 +838,7 @@ var STACK_ICONS = {
788
838
  nuxt: `${DI}/nuxtjs/nuxtjs-original.svg`,
789
839
  sveltekit: `${DI}/svelte/svelte-original.svg`,
790
840
  remix: `${DI}/react/react-original.svg`,
841
+ "tanstack-start": `${DI}/react/react-original.svg`,
791
842
  astro: `${DI}/astro/astro-original.svg`,
792
843
  vite: `${DI}/vitejs/vitejs-original.svg`,
793
844
  angular: `${DI}/angular/angular-original.svg`,
@@ -841,6 +892,22 @@ var STACK_ICONS = {
841
892
  "docker-compose": `${DI}/docker/docker-original.svg`
842
893
  };
843
894
 
895
+ // ../../packages/core/src/volumes.ts
896
+ var APP_DIR = "/app";
897
+ var MODE_SUFFIX = /:(ro|rw|z|Z|nocopy)$/;
898
+ function appVolumeTargets(volumes, appDir = APP_DIR) {
899
+ const out = [];
900
+ for (const spec of volumes) {
901
+ const body = spec.replace(MODE_SUFFIX, "");
902
+ const idx = body.indexOf(":");
903
+ if (idx < 0) continue;
904
+ const target = body.slice(idx + 1);
905
+ if (!target.startsWith(`${appDir}/`)) continue;
906
+ out.push(target.slice(appDir.length + 1));
907
+ }
908
+ return out;
909
+ }
910
+
844
911
  // ../../packages/core/src/constants.ts
845
912
  var DEFAULT_IMAGE_REGISTRY = "ghcr.io/oblien";
846
913
  var PLANS = {
@@ -974,17 +1041,26 @@ var CREDIT_PACKS = [
974
1041
  }
975
1042
  ];
976
1043
 
977
- // ../../packages/core/src/edge-image-ref.ts
978
- function buildEdgeImageRef(opts) {
1044
+ // ../../packages/core/src/image-ref.ts
1045
+ function buildImageRef(opts, image) {
979
1046
  const explicit = opts?.explicit?.trim();
980
1047
  if (explicit) return explicit;
981
- const override = process.env.OPENSHIP_EDGE_IMAGE?.trim();
1048
+ const override = image.imageOverride?.trim();
982
1049
  if (override) return override;
983
1050
  const injected = opts?.injectedDefault?.trim();
984
1051
  if (injected) return injected;
985
1052
  const registry2 = process.env.OPENSHIP_IMAGE_REGISTRY?.trim() || DEFAULT_IMAGE_REGISTRY;
986
- const tag = process.env.OPENSHIP_EDGE_TAG?.trim() || process.env.OPENSHIP_VERSION?.trim() || opts?.fallbackTag?.trim() || "latest";
987
- return `${registry2}/openship-edge:${tag}`;
1053
+ const tag = image.tagOverride?.trim() || process.env.OPENSHIP_VERSION?.trim() || opts?.fallbackTag?.trim() || "latest";
1054
+ return `${registry2}/${image.name}:${tag}`;
1055
+ }
1056
+
1057
+ // ../../packages/core/src/edge-image-ref.ts
1058
+ function buildEdgeImageRef(opts) {
1059
+ return buildImageRef(opts, {
1060
+ name: "openship-edge",
1061
+ imageOverride: process.env.OPENSHIP_EDGE_IMAGE,
1062
+ tagOverride: process.env.OPENSHIP_EDGE_TAG
1063
+ });
988
1064
  }
989
1065
 
990
1066
  // ../../packages/core/src/system.ts
@@ -1017,7 +1093,35 @@ var SYSTEM = {
1017
1093
  /** Maximum length of the error message stored in DB */
1018
1094
  MAX_ERROR_MESSAGE_LENGTH: 512,
1019
1095
  /** Default restart policy for production containers */
1020
- DEFAULT_RESTART_POLICY: "always"
1096
+ DEFAULT_RESTART_POLICY: "always",
1097
+ /**
1098
+ * How long a just-started container is watched before a deploy may call
1099
+ * itself ready. Container-create succeeding proves nothing: a bad start
1100
+ * command exits within milliseconds and `restart: always` hides it behind a
1101
+ * bounce loop. Measured from each container's OWN start time, so a stack
1102
+ * whose earlier services have already been up this long waits only for the
1103
+ * last one.
1104
+ */
1105
+ STABILIZE_WINDOW_MS: 15e3,
1106
+ /** Inspect poll interval inside that window. */
1107
+ STABILIZE_POLL_MS: 1e3,
1108
+ /**
1109
+ * Restarts within the window that mean "crash loop" rather than "waited for
1110
+ * a dependency and recovered". Docker's restart backoff (100ms doubling)
1111
+ * takes an instantly-exiting process past this in ~2s, while a service that
1112
+ * times out waiting on a slow database manages one or two.
1113
+ */
1114
+ STABILIZE_CRASH_RESTARTS: 3,
1115
+ /** Log lines folded into the failure message, so diagnosing needs no SSH. */
1116
+ STABILIZE_LOG_TAIL_LINES: 20,
1117
+ /**
1118
+ * Readiness-probe budget when a project OPTS IN to the health check. Only
1119
+ * reached with `readiness.enabled` — an unconfigured deploy runs no probe
1120
+ * at all, so this never sits on the default critical path.
1121
+ */
1122
+ READINESS_TIMEOUT_MS: 45e3,
1123
+ /** Readiness-probe poll interval. */
1124
+ READINESS_INTERVAL_MS: 1e3
1021
1125
  },
1022
1126
  // ── SSE / Build Streaming ────────────────────────────────────────────
1023
1127
  SSE: {
@@ -1030,6 +1134,12 @@ var SYSTEM = {
1030
1134
  // 4 hours
1031
1135
  /** Keep-alive heartbeat interval (ms) - prevents proxy/CDN drops */
1032
1136
  HEARTBEAT_INTERVAL_MS: 25e3,
1137
+ /**
1138
+ * How long a finished handler waits for already-queued frames to flush
1139
+ * before the stream closes. Bounded because a client that has gone away
1140
+ * never drains, and that must not hold the response open.
1141
+ */
1142
+ TERMINAL_DRAIN_TIMEOUT_MS: 5e3,
1033
1143
  /** Maximum active sessions in the cache */
1034
1144
  MAX_SESSIONS: 500,
1035
1145
  /** Background sweep interval for stale sessions (ms) */
@@ -1071,11 +1181,12 @@ var SYSTEM = {
1071
1181
  /** Port range */
1072
1182
  MIN_PORT: 1,
1073
1183
  MAX_PORT: 65535,
1074
- /** Resource limits */
1075
- MIN_CPU_CORES: 0.25,
1076
- MAX_CPU_CORES: 4,
1077
- MIN_MEMORY_MB: 128,
1078
- MAX_MEMORY_MB: 8192,
1184
+ // Resource bounds deliberately do NOT live here. They belong to
1185
+ // ./resources.ts, which is the single source of truth: the floors
1186
+ // (MIN_CPU_CORES / MIN_MEMORY_MB) plus the rule that the UPPER bound is the
1187
+ // target machine's probed capacity, not a constant. The unread
1188
+ // MAX_CPU_CORES: 4 / MAX_MEMORY_MB: 8192 pair that used to sit here encoded
1189
+ // exactly the fixed ceiling that made a large self-hosted box unusable.
1079
1190
  /** Pagination */
1080
1191
  DEFAULT_PAGE: 1,
1081
1192
  DEFAULT_PER_PAGE: 20,
@@ -1083,6 +1194,41 @@ var SYSTEM = {
1083
1194
  }
1084
1195
  };
1085
1196
 
1197
+ // ../../packages/core/src/utils.ts
1198
+ var REDIRECT_STATUSES = [301, 302, 307, 308];
1199
+ var DEFAULT_REDIRECT_STATUS = 301;
1200
+ function resolveRedirectStatus(status) {
1201
+ return REDIRECT_STATUSES.includes(status) ? status : DEFAULT_REDIRECT_STATUS;
1202
+ }
1203
+ function isValidEmail(value) {
1204
+ const email3 = value?.trim() ?? "";
1205
+ if (!email3 || email3.length > 254) return false;
1206
+ return /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/.test(email3);
1207
+ }
1208
+ function formatBytes(bytes, decimals = 2) {
1209
+ if (bytes <= 0) return "0 B";
1210
+ const k = 1024;
1211
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
1212
+ const i = Math.min(Math.max(Math.floor(Math.log(bytes) / Math.log(k)), 0), sizes.length - 1);
1213
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
1214
+ }
1215
+ function withTimeout(promise2, ms, message) {
1216
+ if (!ms) return promise2;
1217
+ return new Promise((resolve, reject) => {
1218
+ const timer = setTimeout(() => reject(new Error(message)), ms);
1219
+ promise2.then(
1220
+ (v) => {
1221
+ clearTimeout(timer);
1222
+ resolve(v);
1223
+ },
1224
+ (e) => {
1225
+ clearTimeout(timer);
1226
+ reject(e);
1227
+ }
1228
+ );
1229
+ });
1230
+ }
1231
+
1086
1232
  // ../../packages/core/src/errors.ts
1087
1233
  var AppError = class extends Error {
1088
1234
  constructor(message, statusCode = 500, code) {
@@ -1106,6 +1252,12 @@ function safeErrorMessage(err) {
1106
1252
  return String(err).slice(0, 2e3);
1107
1253
  }
1108
1254
 
1255
+ // ../../packages/core/src/types.ts
1256
+ var OPENSHIP_READINESS_FAILURE_ACTIONS = [
1257
+ "warn",
1258
+ "fail"
1259
+ ];
1260
+
1109
1261
  // ../../packages/core/src/openship-config/schema.ts
1110
1262
  var OPENSHIP_RUNTIMES = ["bare", "docker"];
1111
1263
  var OPENSHIP_PRODUCTION_MODES = [
@@ -1121,6 +1273,7 @@ var OPENSHIP_RESTARTS = [
1121
1273
  "unless-stopped"
1122
1274
  ];
1123
1275
  var OPENSHIP_RESOURCE_TIERS = [
1276
+ "unlimited",
1124
1277
  "micro",
1125
1278
  "low",
1126
1279
  "medium",
@@ -1133,12 +1286,14 @@ var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
1133
1286
  "framework",
1134
1287
  "packageManager",
1135
1288
  "rootDirectory",
1289
+ "composePath",
1136
1290
  "installCommand",
1137
1291
  "buildCommand",
1138
1292
  "startCommand",
1139
1293
  "outputDirectory",
1140
1294
  "buildImage",
1141
1295
  "productionPaths",
1296
+ "volumes",
1142
1297
  "runtime",
1143
1298
  "productionMode",
1144
1299
  "port",
@@ -1146,6 +1301,7 @@ var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
1146
1301
  "domains",
1147
1302
  "routes",
1148
1303
  "resources",
1304
+ "readiness",
1149
1305
  "services",
1150
1306
  "monorepo"
1151
1307
  ]);
@@ -1320,12 +1476,28 @@ function parseResources(ctx, v, path) {
1320
1476
  }
1321
1477
  const r = {
1322
1478
  tier: ctx.enumOf(v.tier, `${path}.tier`, OPENSHIP_RESOURCE_TIERS),
1323
- cpuCores: ctx.int(v.cpuCores, `${path}.cpuCores`, 0.25, 4),
1324
- memoryMb: ctx.int(v.memoryMb, `${path}.memoryMb`, 128, 8192),
1325
- diskMb: ctx.int(v.diskMb, `${path}.diskMb`, 64, 204800)
1479
+ cpuCores: ctx.int(v.cpuCores, `${path}.cpuCores`, 0, 1024),
1480
+ memoryMb: ctx.int(v.memoryMb, `${path}.memoryMb`, 0, 4194304),
1481
+ diskMb: ctx.int(v.diskMb, `${path}.diskMb`, 0, 204800)
1326
1482
  };
1327
1483
  return r;
1328
1484
  }
1485
+ function parseReadiness(ctx, v, path) {
1486
+ if (v === void 0) return void 0;
1487
+ if (!ctx.isObj(v)) {
1488
+ ctx.err(path, "must be an object");
1489
+ return void 0;
1490
+ }
1491
+ return {
1492
+ enabled: ctx.bool(v.enabled, `${path}.enabled`),
1493
+ path: ctx.str(v.path, `${path}.path`),
1494
+ port: ctx.int(v.port, `${path}.port`, 1, 65535),
1495
+ timeoutSeconds: ctx.int(v.timeoutSeconds, `${path}.timeoutSeconds`, 1, 600),
1496
+ stabilization: ctx.bool(v.stabilization, `${path}.stabilization`),
1497
+ stabilizationSeconds: ctx.int(v.stabilizationSeconds, `${path}.stabilizationSeconds`, 1, 600),
1498
+ onFailure: ctx.enumOf(v.onFailure, `${path}.onFailure`, OPENSHIP_READINESS_FAILURE_ACTIONS)
1499
+ };
1500
+ }
1329
1501
  function parseHealthcheck(ctx, v, path) {
1330
1502
  if (v === void 0) return void 0;
1331
1503
  if (!ctx.isObj(v)) {
@@ -1374,7 +1546,9 @@ function parseServices(ctx, v, path) {
1374
1546
  exposed: ctx.bool(item.exposed, `${p}.exposed`),
1375
1547
  exposedPort: ctx.str(item.exposedPort, `${p}.exposedPort`),
1376
1548
  domain: ctx.str(item.domain, `${p}.domain`),
1377
- healthcheck: parseHealthcheck(ctx, item.healthcheck, `${p}.healthcheck`)
1549
+ healthcheck: parseHealthcheck(ctx, item.healthcheck, `${p}.healthcheck`),
1550
+ readiness: parseReadiness(ctx, item.readiness, `${p}.readiness`),
1551
+ resources: parseResources(ctx, item.resources, `${p}.resources`)
1378
1552
  });
1379
1553
  });
1380
1554
  return out;
@@ -1458,12 +1632,14 @@ function parseOpenshipConfig(raw) {
1458
1632
  framework: ctx.enumOf(raw.framework, "framework", STACK_IDS),
1459
1633
  packageManager: parsePackageManager(ctx, raw.packageManager, "packageManager"),
1460
1634
  rootDirectory: ctx.str(raw.rootDirectory, "rootDirectory"),
1635
+ composePath: ctx.str(raw.composePath, "composePath"),
1461
1636
  installCommand: ctx.str(raw.installCommand, "installCommand"),
1462
1637
  buildCommand: ctx.str(raw.buildCommand, "buildCommand"),
1463
1638
  startCommand: ctx.str(raw.startCommand, "startCommand"),
1464
1639
  outputDirectory: ctx.str(raw.outputDirectory, "outputDirectory"),
1465
1640
  buildImage: ctx.str(raw.buildImage, "buildImage"),
1466
1641
  productionPaths: ctx.strArray(raw.productionPaths, "productionPaths"),
1642
+ volumes: ctx.strArray(raw.volumes, "volumes"),
1467
1643
  runtime: ctx.enumOf(raw.runtime, "runtime", OPENSHIP_RUNTIMES),
1468
1644
  productionMode: ctx.enumOf(raw.productionMode, "productionMode", OPENSHIP_PRODUCTION_MODES),
1469
1645
  port: ctx.int(raw.port, "port", 1, 65535),
@@ -1471,6 +1647,7 @@ function parseOpenshipConfig(raw) {
1471
1647
  domains: parseDomains(ctx, raw.domains, "domains"),
1472
1648
  routes: parseRoutes(ctx, raw.routes, "routes"),
1473
1649
  resources: parseResources(ctx, raw.resources, "resources"),
1650
+ readiness: parseReadiness(ctx, raw.readiness, "readiness"),
1474
1651
  services: parseServices(ctx, raw.services, "services"),
1475
1652
  monorepo: parseMonorepo(ctx, raw.monorepo, "monorepo")
1476
1653
  };
@@ -1864,57 +2041,279 @@ var METADATA_FILES = new Set(
1864
2041
  );
1865
2042
 
1866
2043
  // ../../packages/core/src/proxy-settings.ts
1867
- var PROXY_SIZE_RE = /^[1-9][0-9]*(k|m|g)$/;
2044
+ var PROXY_SIZE_RE = /^(0|[1-9][0-9]*(k|m|g))$/;
1868
2045
  var PROXY_TIME_RE = /^[1-9][0-9]*(s|m|h)$/;
2046
+ var PROXY_BUFFERS_RE = /^[1-9][0-9]* [1-9][0-9]*(k|m|g)?$/;
1869
2047
  var PROXY_GZIP_TYPES = "text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml";
1870
- var PROXY_STRING_FIELD_RE = {
1871
- clientMaxBodySize: PROXY_SIZE_RE,
1872
- proxyReadTimeout: PROXY_TIME_RE,
1873
- proxySendTimeout: PROXY_TIME_RE,
1874
- clientBodyTimeout: PROXY_TIME_RE
1875
- };
2048
+ var PROXY_DIRECTIVES = [
2049
+ // ── Request body ────────────────────────────────────────────────────────
2050
+ { key: "clientMaxBodySize", directive: "client_max_body_size", kind: "size", group: "body" },
2051
+ { key: "clientBodyTimeout", directive: "client_body_timeout", kind: "time", group: "body" },
2052
+ {
2053
+ key: "clientBodyBufferSize",
2054
+ directive: "client_body_buffer_size",
2055
+ kind: "size",
2056
+ group: "body"
2057
+ },
2058
+ {
2059
+ key: "proxyRequestBuffering",
2060
+ directive: "proxy_request_buffering",
2061
+ kind: "bool",
2062
+ group: "body"
2063
+ },
2064
+ // ── Upstream timeouts ───────────────────────────────────────────────────
2065
+ { key: "proxyConnectTimeout", directive: "proxy_connect_timeout", kind: "time", group: "timeouts" },
2066
+ { key: "proxyReadTimeout", directive: "proxy_read_timeout", kind: "time", group: "timeouts" },
2067
+ { key: "proxySendTimeout", directive: "proxy_send_timeout", kind: "time", group: "timeouts" },
2068
+ { key: "sendTimeout", directive: "send_timeout", kind: "time", group: "timeouts" },
2069
+ { key: "keepaliveTimeout", directive: "keepalive_timeout", kind: "time", group: "timeouts" },
2070
+ // ── Response buffering ──────────────────────────────────────────────────
2071
+ { key: "proxyBuffering", directive: "proxy_buffering", kind: "bool", group: "buffering" },
2072
+ { key: "proxyBufferSize", directive: "proxy_buffer_size", kind: "size", group: "buffering" },
2073
+ { key: "proxyBuffers", directive: "proxy_buffers", kind: "buffers", group: "buffering" },
2074
+ {
2075
+ key: "proxyBusyBuffersSize",
2076
+ directive: "proxy_busy_buffers_size",
2077
+ kind: "size",
2078
+ group: "buffering"
2079
+ },
2080
+ {
2081
+ key: "proxyMaxTempFileSize",
2082
+ directive: "proxy_max_temp_file_size",
2083
+ kind: "size",
2084
+ group: "buffering"
2085
+ },
2086
+ // ── Compression ─────────────────────────────────────────────────────────
2087
+ {
2088
+ key: "gzip",
2089
+ directive: "gzip",
2090
+ kind: "bool",
2091
+ group: "compression",
2092
+ companionsWhenOn: [
2093
+ `gzip_types ${PROXY_GZIP_TYPES}`,
2094
+ "gzip_vary on",
2095
+ // nginx's own default is 20 bytes, small enough that the gzip header costs
2096
+ // more than the compression saves.
2097
+ "gzip_min_length 1024"
2098
+ ]
2099
+ },
2100
+ {
2101
+ key: "gzipCompLevel",
2102
+ directive: "gzip_comp_level",
2103
+ kind: "int",
2104
+ group: "compression",
2105
+ min: 1,
2106
+ max: 9
2107
+ },
2108
+ // Bytes, as an int — the natural unit here is "1024", and `size` would force "1k".
2109
+ {
2110
+ key: "gzipMinLength",
2111
+ directive: "gzip_min_length",
2112
+ kind: "int",
2113
+ group: "compression",
2114
+ min: 0,
2115
+ max: 10485760
2116
+ },
2117
+ // ── Headers & limits ────────────────────────────────────────────────────
2118
+ {
2119
+ key: "largeClientHeaderBuffers",
2120
+ directive: "large_client_header_buffers",
2121
+ kind: "buffers",
2122
+ group: "headers"
2123
+ },
2124
+ {
2125
+ key: "underscoresInHeaders",
2126
+ directive: "underscores_in_headers",
2127
+ kind: "bool",
2128
+ group: "headers"
2129
+ },
2130
+ { key: "serverTokens", directive: "server_tokens", kind: "bool", group: "headers" },
2131
+ {
2132
+ key: "http2",
2133
+ directive: "http2",
2134
+ kind: "bool",
2135
+ group: "headers",
2136
+ // `http2 on;` at server scope replaced `listen ... http2` in nginx 1.25.1.
2137
+ minNginx: [1, 25, 1],
2138
+ tlsOnly: true
2139
+ },
2140
+ // ── TLS to the upstream ─────────────────────────────────────────────────
2141
+ {
2142
+ key: "proxySslServerName",
2143
+ directive: "proxy_ssl_server_name",
2144
+ kind: "bool",
2145
+ group: "upstreamTls"
2146
+ },
2147
+ { key: "proxySslVerify", directive: "proxy_ssl_verify", kind: "bool", group: "upstreamTls" }
2148
+ ];
2149
+ var PROXY_DIRECTIVE_BY_KEY = new Map(
2150
+ PROXY_DIRECTIVES.map((d) => [d.key, d])
2151
+ );
2152
+ var PROXY_DIRECTIVE_BY_NAME = new Map(
2153
+ PROXY_DIRECTIVES.map((d) => [d.directive, d])
2154
+ );
2155
+ function proxyKindRegex(kind) {
2156
+ switch (kind) {
2157
+ case "size":
2158
+ return PROXY_SIZE_RE;
2159
+ case "time":
2160
+ return PROXY_TIME_RE;
2161
+ case "buffers":
2162
+ return PROXY_BUFFERS_RE;
2163
+ default:
2164
+ return void 0;
2165
+ }
2166
+ }
2167
+ var PROXY_STRING_FIELD_RE = Object.freeze(
2168
+ Object.fromEntries(
2169
+ PROXY_DIRECTIVES.flatMap((d) => {
2170
+ const re = proxyKindRegex(d.kind);
2171
+ return re ? [[d.key, re]] : [];
2172
+ })
2173
+ )
2174
+ );
2175
+ function coerceProxyValue(spec, value) {
2176
+ if (spec.kind === "bool") return typeof value === "boolean" ? value : void 0;
2177
+ if (spec.kind === "int") {
2178
+ if (typeof value !== "number" || !Number.isInteger(value)) return void 0;
2179
+ if (spec.min !== void 0 && value < spec.min) return void 0;
2180
+ if (spec.max !== void 0 && value > spec.max) return void 0;
2181
+ return value;
2182
+ }
2183
+ const re = proxyKindRegex(spec.kind);
2184
+ if (typeof value !== "string" || !re || !re.test(value)) return void 0;
2185
+ return value;
2186
+ }
1876
2187
  function sanitizeProxySettings(input) {
1877
2188
  if (!input || typeof input !== "object") return void 0;
1878
2189
  const src = input;
1879
2190
  const out = {};
1880
- for (const [key, re] of Object.entries(PROXY_STRING_FIELD_RE)) {
1881
- const v = src[key];
1882
- if (typeof v === "string" && re.test(v)) {
1883
- out[key] = v;
1884
- }
2191
+ for (const spec of PROXY_DIRECTIVES) {
2192
+ if (!(spec.key in src)) continue;
2193
+ const v = coerceProxyValue(spec, src[spec.key]);
2194
+ if (v !== void 0) out[spec.key] = v;
1885
2195
  }
1886
- if (typeof src.proxyBuffering === "boolean") out.proxyBuffering = src.proxyBuffering;
1887
- if (typeof src.gzip === "boolean") out.gzip = src.gzip;
1888
2196
  return Object.keys(out).length > 0 ? out : void 0;
1889
2197
  }
1890
-
1891
- // ../../packages/core/src/utils.ts
1892
- function isValidEmail(value) {
1893
- const email3 = value?.trim() ?? "";
1894
- if (!email3 || email3.length > 254) return false;
1895
- return /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/.test(email3);
2198
+ function renderProxyValue(spec, value) {
2199
+ if (spec.kind === "bool") return value ? "on" : "off";
2200
+ return String(value);
1896
2201
  }
1897
- function formatBytes(bytes, decimals = 2) {
1898
- if (bytes <= 0) return "0 B";
1899
- const k = 1024;
1900
- const sizes = ["B", "KB", "MB", "GB", "TB"];
1901
- const i = Math.min(Math.max(Math.floor(Math.log(bytes) / Math.log(k)), 0), sizes.length - 1);
1902
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
2202
+ function proxyDirectiveAllowed(spec, version2) {
2203
+ if (!spec.minNginx || !version2) return true;
2204
+ for (let i = 0; i < 3; i++) {
2205
+ const need = spec.minNginx[i] ?? 0;
2206
+ const have = version2[i] ?? 0;
2207
+ if (have > need) return true;
2208
+ if (have < need) return false;
2209
+ }
2210
+ return true;
1903
2211
  }
1904
- function withTimeout(promise2, ms, message) {
1905
- if (!ms) return promise2;
1906
- return new Promise((resolve, reject) => {
1907
- const timer = setTimeout(() => reject(new Error(message)), ms);
1908
- promise2.then(
1909
- (v) => {
1910
- clearTimeout(timer);
1911
- resolve(v);
1912
- },
1913
- (e) => {
1914
- clearTimeout(timer);
1915
- reject(e);
2212
+ function resolveProxyDirectives(settings, opts) {
2213
+ const p = sanitizeProxySettings(settings);
2214
+ if (!p) return [];
2215
+ const values = p;
2216
+ const explicit = new Set(
2217
+ PROXY_DIRECTIVES.filter((s) => values[s.key] !== void 0).map((s) => s.directive)
2218
+ );
2219
+ const out = [];
2220
+ for (const spec of PROXY_DIRECTIVES) {
2221
+ const wanted = opts.scope === "all" || opts.scope === "tls" === !!spec.tlsOnly;
2222
+ if (!wanted) continue;
2223
+ if (!proxyDirectiveAllowed(spec, opts.nginxVersion)) continue;
2224
+ const value = values[spec.key];
2225
+ if (value === void 0) continue;
2226
+ out.push({ directive: spec.directive, text: renderProxyValue(spec, value), key: spec.key, value });
2227
+ if (value !== true || !spec.companionsWhenOn) continue;
2228
+ for (const line of spec.companionsWhenOn) {
2229
+ const [name, ...rest] = line.split(/\s+/);
2230
+ if (!name || explicit.has(name)) continue;
2231
+ const text = rest.join(" ");
2232
+ const companion = PROXY_DIRECTIVE_BY_NAME.get(name);
2233
+ const parsed = companion ? parseProxyValue(companion, text) : void 0;
2234
+ out.push({
2235
+ directive: name,
2236
+ text,
2237
+ ...companion && parsed !== void 0 ? { key: companion.key, value: parsed } : {}
2238
+ });
2239
+ }
2240
+ }
2241
+ return out;
2242
+ }
2243
+ function parseProxyValue(spec, raw) {
2244
+ const text = raw.trim();
2245
+ if (spec.kind === "bool") {
2246
+ if (text === "on") return true;
2247
+ if (text === "off") return false;
2248
+ return void 0;
2249
+ }
2250
+ if (spec.kind === "int") {
2251
+ if (!/^[0-9]+$/.test(text)) return void 0;
2252
+ return coerceProxyValue(spec, Number(text));
2253
+ }
2254
+ const normalized = spec.kind === "buffers" ? text.replace(/\s+/g, " ") : text;
2255
+ return coerceProxyValue(spec, normalized.toLowerCase());
2256
+ }
2257
+
2258
+ // ../../packages/core/src/object-storage.ts
2259
+ function isAwsS3Endpoint(endpoint2) {
2260
+ return !endpoint2 || endpoint2.includes(".amazonaws.com");
2261
+ }
2262
+
2263
+ // ../../packages/core/src/shell-split.ts
2264
+ function shellSplitWords(value) {
2265
+ const words = [];
2266
+ let current = "";
2267
+ let quoted = false;
2268
+ let inSingle = false;
2269
+ let inDouble = false;
2270
+ let escaped = false;
2271
+ for (const char of value.trim()) {
2272
+ if (escaped) {
2273
+ current += char;
2274
+ escaped = false;
2275
+ continue;
2276
+ }
2277
+ if (char === "\\" && !inSingle) {
2278
+ escaped = true;
2279
+ continue;
2280
+ }
2281
+ if (char === "'" && !inDouble) {
2282
+ inSingle = !inSingle;
2283
+ quoted = true;
2284
+ continue;
2285
+ }
2286
+ if (char === '"' && !inSingle) {
2287
+ inDouble = !inDouble;
2288
+ quoted = true;
2289
+ continue;
2290
+ }
2291
+ if (/\s/.test(char) && !inSingle && !inDouble) {
2292
+ if (current || quoted) {
2293
+ words.push(current);
2294
+ current = "";
2295
+ quoted = false;
1916
2296
  }
1917
- );
2297
+ continue;
2298
+ }
2299
+ current += char;
2300
+ }
2301
+ if (escaped) current += "\\";
2302
+ if (current || quoted) words.push(current);
2303
+ return words;
2304
+ }
2305
+ function commandToArgv(command) {
2306
+ if (command == null) return null;
2307
+ if (Array.isArray(command)) return command.map((part) => String(part));
2308
+ return shellSplitWords(command);
2309
+ }
2310
+
2311
+ // ../../packages/core/src/mail-image-ref.ts
2312
+ function buildMailImageRef(opts) {
2313
+ return buildImageRef(opts, {
2314
+ name: "openship-mail",
2315
+ imageOverride: process.env.OPENSHIP_MAIL_IMAGE,
2316
+ tagOverride: process.env.OPENSHIP_MAIL_TAG
1918
2317
  });
1919
2318
  }
1920
2319
 
@@ -1977,6 +2376,36 @@ var dashboardRuntimeOrigins = Object.values(DASHBOARD_RUNTIME_TARGETS).flatMap(
1977
2376
  ({ dashboard, api }) => [dashboard, api]
1978
2377
  );
1979
2378
 
2379
+ // ../../packages/core/src/resources.ts
2380
+ var RESOURCE_TIER_ORDER = ["micro", "low", "medium", "high"];
2381
+ var ALL_RESOURCE_TIERS = [
2382
+ "unlimited",
2383
+ ...RESOURCE_TIER_ORDER,
2384
+ "custom"
2385
+ ];
2386
+ var CLOUD_RESOURCE_TIER_IDS = [
2387
+ ...RESOURCE_TIER_ORDER,
2388
+ "custom"
2389
+ ];
2390
+ function hasCpuLimit(r) {
2391
+ return typeof r?.cpuCores === "number" && r.cpuCores > 0;
2392
+ }
2393
+ function hasMemoryLimit(r) {
2394
+ return typeof r?.memoryMb === "number" && r.memoryMb > 0;
2395
+ }
2396
+ function formatMemoryMb(mb) {
2397
+ if (!mb || mb <= 0) return "no limit";
2398
+ if (mb >= 1024) return `${(mb / 1024).toFixed(mb % 1024 === 0 ? 0 : 1)} GB`;
2399
+ return `${mb} MB`;
2400
+ }
2401
+ function formatCpuCores(cores) {
2402
+ if (!cores || cores <= 0) return "no limit";
2403
+ return `${cores} vCPU`;
2404
+ }
2405
+
2406
+ // ../../packages/core/src/rollback-window.ts
2407
+ var ROLLBACK_DISK_BUDGET_CAP_BYTES = 20 * 1024 * 1024 * 1024;
2408
+
1980
2409
  // ../../packages/core/src/workspaces/toml-helpers.ts
1981
2410
  function extractStringArrayFromSection(content, sectionPath, key) {
1982
2411
  const sectionHeader = new RegExp(
@@ -2158,14 +2587,33 @@ var goWorkspaceDetector = {
2158
2587
  };
2159
2588
 
2160
2589
  // ../../packages/core/src/workspaces/gradle.ts
2590
+ function readIncludeArgs(content, start) {
2591
+ let i = start;
2592
+ while (content[i] === " " || content[i] === " ") i++;
2593
+ if (content[i] === "(") {
2594
+ let depth = 0;
2595
+ for (let j = i; j < content.length; j++) {
2596
+ if (content[j] === "(") depth++;
2597
+ else if (content[j] === ")" && --depth === 0) return content.slice(i + 1, j);
2598
+ }
2599
+ return content.slice(i + 1);
2600
+ }
2601
+ let args = "";
2602
+ for (; ; ) {
2603
+ const newline = content.indexOf("\n", i);
2604
+ const line = newline === -1 ? content.slice(i) : content.slice(i, newline);
2605
+ args += line;
2606
+ if (newline === -1 || !line.trimEnd().endsWith(",")) return args;
2607
+ i = newline + 1;
2608
+ }
2609
+ }
2161
2610
  function parseGradleSettings(content) {
2162
2611
  const cleaned = content.replace(/\/\*[\s\S]*?\*\//g, "").split("\n").map((line) => line.replace(/\/\/.*$/, "")).join("\n");
2163
2612
  const paths = [];
2164
- const includePattern = /include\s*(?:\(\s*)?((?:[^()\n]+))(?:\))?/g;
2613
+ const includePattern = /\binclude\b/g;
2165
2614
  let match;
2166
2615
  while ((match = includePattern.exec(cleaned)) !== null) {
2167
- const args = match[1];
2168
- if (/^Build\b/.test(args)) continue;
2616
+ const args = readIncludeArgs(cleaned, match.index + match[0].length);
2169
2617
  const stringPattern = /"([^"]+)"|'([^']+)'/g;
2170
2618
  let stringMatch;
2171
2619
  while ((stringMatch = stringPattern.exec(args)) !== null) {
@@ -2190,9 +2638,49 @@ var gradleWorkspaceDetector = {
2190
2638
  parseSubProjects: parseGradleSettings
2191
2639
  };
2192
2640
 
2641
+ // ../../packages/core/src/workspaces/comments.ts
2642
+ function stripJsonComments(content) {
2643
+ let out = "";
2644
+ let inString = false;
2645
+ for (let i = 0; i < content.length; i++) {
2646
+ const ch = content[i];
2647
+ if (inString) {
2648
+ out += ch;
2649
+ if (ch === "\\") {
2650
+ out += content[++i] ?? "";
2651
+ continue;
2652
+ }
2653
+ if (ch === '"') inString = false;
2654
+ continue;
2655
+ }
2656
+ if (ch === '"') {
2657
+ inString = true;
2658
+ out += ch;
2659
+ continue;
2660
+ }
2661
+ if (ch === "/" && content[i + 1] === "/") {
2662
+ const newline = content.indexOf("\n", i);
2663
+ if (newline === -1) break;
2664
+ i = newline - 1;
2665
+ continue;
2666
+ }
2667
+ if (ch === "/" && content[i + 1] === "*") {
2668
+ const end = content.indexOf("*/", i + 2);
2669
+ if (end === -1) break;
2670
+ i = end + 1;
2671
+ continue;
2672
+ }
2673
+ out += ch;
2674
+ }
2675
+ return out;
2676
+ }
2677
+ function stripXmlComments(content) {
2678
+ return content.replace(/<!--[\s\S]*?-->/g, "");
2679
+ }
2680
+
2193
2681
  // ../../packages/core/src/workspaces/maven.ts
2194
2682
  function parsePomXml(content) {
2195
- const modulesBlock = content.match(/<modules\b[^>]*>([\s\S]*?)<\/modules>/i);
2683
+ const modulesBlock = stripXmlComments(content).match(/<modules\b[^>]*>([\s\S]*?)<\/modules>/i);
2196
2684
  if (!modulesBlock) return [];
2197
2685
  const moduleEntries = modulesBlock[1].matchAll(/<module\b[^>]*>\s*([\s\S]*?)\s*<\/module>/gi);
2198
2686
  const paths = [];
@@ -2289,7 +2777,7 @@ var uvWorkspaceDetector = {
2289
2777
  function parseRushJson(content) {
2290
2778
  let parsed = null;
2291
2779
  try {
2292
- parsed = JSON.parse(content);
2780
+ parsed = JSON.parse(stripJsonComments(content));
2293
2781
  } catch {
2294
2782
  return [];
2295
2783
  }
@@ -2367,8 +2855,11 @@ var dockerLanguageDetector = {
2367
2855
  // ../../packages/core/src/languages/elixir.ts
2368
2856
  function parseMixExs2(content) {
2369
2857
  const deps = {};
2370
- for (const m of content.matchAll(/\{:([\w]+),/g)) {
2371
- deps[m[1]] = "*";
2858
+ for (const line of content.split("\n")) {
2859
+ if (line.trim().startsWith("#")) continue;
2860
+ for (const m of line.matchAll(/\{:([\w]+),/g)) {
2861
+ deps[m[1]] = "*";
2862
+ }
2372
2863
  }
2373
2864
  return deps;
2374
2865
  }
@@ -2416,6 +2907,34 @@ function clampPort(raw) {
2416
2907
  const port = parseInt(raw, 10);
2417
2908
  return port > 0 && port <= 65535 ? port : null;
2418
2909
  }
2910
+ var YAML_PORT_RE = /^port:\s*["']?(\d{2,5})/;
2911
+ function springYamlPort(yml) {
2912
+ const inline = yml.match(/^server:\s*\{[^}]*\bport:\s*["']?(\d{2,5})/m);
2913
+ if (inline) return inline[1];
2914
+ let inServer = false;
2915
+ let childIndent = null;
2916
+ for (const raw of yml.split("\n")) {
2917
+ const trimmed2 = raw.trim();
2918
+ if (!trimmed2 || trimmed2.startsWith("#")) continue;
2919
+ const indent = raw.length - raw.trimStart().length;
2920
+ if (inServer) {
2921
+ if (indent > 0) {
2922
+ if (childIndent === null) childIndent = indent;
2923
+ if (indent === childIndent) {
2924
+ const m = trimmed2.match(YAML_PORT_RE);
2925
+ if (m) return m[1];
2926
+ }
2927
+ continue;
2928
+ }
2929
+ inServer = false;
2930
+ }
2931
+ if (indent === 0 && /^server:$/.test(trimmed2)) {
2932
+ inServer = true;
2933
+ childIndent = null;
2934
+ }
2935
+ }
2936
+ return null;
2937
+ }
2419
2938
  function detectJavaPort(context) {
2420
2939
  const fc = context.fileContents;
2421
2940
  if (!fc) return null;
@@ -2427,8 +2946,8 @@ function detectJavaPort(context) {
2427
2946
  for (const name of ["application.yml", "application.yaml"]) {
2428
2947
  const yml = fc[name];
2429
2948
  if (!yml) continue;
2430
- const m = yml.match(/server:[\s\S]*?\bport:\s*["']?(\d{2,5})/) ?? yml.match(/^\s*port:\s*["']?(\d{2,5})/m);
2431
- if (m) return clampPort(m[1]);
2949
+ const port = springYamlPort(yml) ?? yml.match(/^port:\s*["']?(\d{2,5})/m)?.[1];
2950
+ if (port) return clampPort(port);
2432
2951
  }
2433
2952
  return null;
2434
2953
  }
@@ -2515,11 +3034,30 @@ function parseRequirementsTxt(content) {
2515
3034
  }
2516
3035
  return deps;
2517
3036
  }
3037
+ function sliceArrayValue(body, key) {
3038
+ const open = body.match(new RegExp(`(?:^|\\n)\\s*${key}\\s*=\\s*\\[`));
3039
+ if (!open || open.index === void 0) return null;
3040
+ const from = open.index + open[0].length;
3041
+ let depth = 1;
3042
+ let quote = null;
3043
+ for (let i = from; i < body.length; i++) {
3044
+ const char = body[i];
3045
+ if (quote) {
3046
+ if (char === quote) quote = null;
3047
+ continue;
3048
+ }
3049
+ if (char === '"' || char === "'") quote = char;
3050
+ else if (char === "[") depth++;
3051
+ else if (char === "]" && --depth === 0) return body.slice(from, i);
3052
+ }
3053
+ return null;
3054
+ }
2518
3055
  function parsePyprojectToml(content) {
2519
3056
  const deps = {};
2520
- const pep621 = content.match(/\[project\][^[]*?dependencies\s*=\s*\[([\s\S]*?)\]/);
2521
- if (pep621) {
2522
- const items = pep621[1].matchAll(/["']([^"']+)["']/g);
3057
+ const project = content.match(/\[project\]([\s\S]*?)(?=\n\[|$)/);
3058
+ const pep621 = project ? sliceArrayValue(project[1], "dependencies") : null;
3059
+ if (pep621 !== null) {
3060
+ const items = pep621.matchAll(/["']([^"']+)["']/g);
2523
3061
  for (const item of items) {
2524
3062
  const m = item[1].match(/^([A-Za-z0-9][A-Za-z0-9._-]*)/);
2525
3063
  if (m) deps[normalizeName(m[1])] = "*";
@@ -2576,8 +3114,11 @@ var pythonLanguageDetector = {
2576
3114
  // ../../packages/core/src/languages/ruby.ts
2577
3115
  function parseGemfile(content) {
2578
3116
  const deps = {};
2579
- for (const m of content.matchAll(/gem\s+['"]([^'"]+)['"]/g)) {
2580
- deps[m[1].toLowerCase()] = "*";
3117
+ for (const line of content.split("\n")) {
3118
+ if (line.trim().startsWith("#")) continue;
3119
+ for (const m of line.matchAll(/gem\s+['"]([^'"]+)['"]/g)) {
3120
+ deps[m[1].toLowerCase()] = "*";
3121
+ }
2581
3122
  }
2582
3123
  return deps;
2583
3124
  }
@@ -2674,9 +3215,6 @@ var catalog_default = {
2674
3215
  {
2675
3216
  name: "db",
2676
3217
  image: "supabase/postgres:17.6.1.136",
2677
- ports: [
2678
- "5432:5432"
2679
- ],
2680
3218
  environment: {
2681
3219
  POSTGRES_HOST: "/var/run/postgresql",
2682
3220
  PGPORT: "5432",
@@ -2687,7 +3225,8 @@ var catalog_default = {
2687
3225
  JWT_EXP: "3600"
2688
3226
  },
2689
3227
  volumes: [
2690
- "supabase_db_data:/var/lib/postgresql/data"
3228
+ "supabase_db_data:/var/lib/postgresql/data",
3229
+ "supabase_db_config:/etc/postgresql-custom"
2691
3230
  ],
2692
3231
  healthcheck: {
2693
3232
  test: [
@@ -2707,9 +3246,6 @@ var catalog_default = {
2707
3246
  {
2708
3247
  name: "kong",
2709
3248
  image: "kong:3.9.1",
2710
- ports: [
2711
- "8000:8000"
2712
- ],
2713
3249
  exposedPort: 8e3,
2714
3250
  exposed: true,
2715
3251
  routes: [
@@ -2863,7 +3399,7 @@ var catalog_default = {
2863
3399
  "--no-verbose",
2864
3400
  "--tries=1",
2865
3401
  "--spider",
2866
- "http://localhost:5000/status"
3402
+ "http://127.0.0.1:5000/status"
2867
3403
  ],
2868
3404
  interval: "5s",
2869
3405
  timeout: "5s",
@@ -3016,8 +3552,231 @@ var catalog_default = {
3016
3552
  },
3017
3553
  {
3018
3554
  service: "db",
3019
- path: "/docker-entrypoint-initdb.d/99-roles.sql",
3020
- content: "ALTER USER authenticator WITH PASSWORD '{{config:POSTGRES_PASSWORD}}';\nALTER USER pgbouncer WITH PASSWORD '{{config:POSTGRES_PASSWORD}}';\nALTER USER supabase_auth_admin WITH PASSWORD '{{config:POSTGRES_PASSWORD}}';\nALTER USER supabase_functions_admin WITH PASSWORD '{{config:POSTGRES_PASSWORD}}';\nALTER USER supabase_storage_admin WITH PASSWORD '{{config:POSTGRES_PASSWORD}}';\nALTER USER supabase_admin WITH PASSWORD '{{config:POSTGRES_PASSWORD}}';\n"
3555
+ path: "/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql",
3556
+ content: `BEGIN;
3557
+ -- Create pg_net extension
3558
+ CREATE EXTENSION IF NOT EXISTS pg_net SCHEMA extensions;
3559
+ -- Create supabase_functions schema
3560
+ CREATE SCHEMA supabase_functions AUTHORIZATION supabase_admin;
3561
+ GRANT USAGE ON SCHEMA supabase_functions TO postgres, anon, authenticated, service_role;
3562
+ ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role;
3563
+ ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON FUNCTIONS TO postgres, anon, authenticated, service_role;
3564
+ ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role;
3565
+ -- supabase_functions.migrations definition
3566
+ CREATE TABLE supabase_functions.migrations (
3567
+ version text PRIMARY KEY,
3568
+ inserted_at timestamptz NOT NULL DEFAULT NOW()
3569
+ );
3570
+ -- Initial supabase_functions migration
3571
+ INSERT INTO supabase_functions.migrations (version) VALUES ('initial');
3572
+ -- supabase_functions.hooks definition
3573
+ CREATE TABLE supabase_functions.hooks (
3574
+ id bigserial PRIMARY KEY,
3575
+ hook_table_id integer NOT NULL,
3576
+ hook_name text NOT NULL,
3577
+ created_at timestamptz NOT NULL DEFAULT NOW(),
3578
+ request_id bigint
3579
+ );
3580
+ CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id);
3581
+ CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name);
3582
+ COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.';
3583
+ CREATE FUNCTION supabase_functions.http_request()
3584
+ RETURNS trigger
3585
+ LANGUAGE plpgsql
3586
+ AS $function$
3587
+ DECLARE
3588
+ request_id bigint;
3589
+ payload jsonb;
3590
+ url text := TG_ARGV[0]::text;
3591
+ method text := TG_ARGV[1]::text;
3592
+ headers jsonb DEFAULT '{}'::jsonb;
3593
+ params jsonb DEFAULT '{}'::jsonb;
3594
+ timeout_ms integer DEFAULT 1000;
3595
+ BEGIN
3596
+ IF url IS NULL OR url = 'null' THEN
3597
+ RAISE EXCEPTION 'url argument is missing';
3598
+ END IF;
3599
+
3600
+ IF method IS NULL OR method = 'null' THEN
3601
+ RAISE EXCEPTION 'method argument is missing';
3602
+ END IF;
3603
+
3604
+ IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN
3605
+ headers = '{"Content-Type": "application/json"}'::jsonb;
3606
+ ELSE
3607
+ headers = TG_ARGV[2]::jsonb;
3608
+ END IF;
3609
+
3610
+ IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN
3611
+ params = '{}'::jsonb;
3612
+ ELSE
3613
+ params = TG_ARGV[3]::jsonb;
3614
+ END IF;
3615
+
3616
+ IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN
3617
+ timeout_ms = 1000;
3618
+ ELSE
3619
+ timeout_ms = TG_ARGV[4]::integer;
3620
+ END IF;
3621
+
3622
+ CASE
3623
+ WHEN method = 'GET' THEN
3624
+ SELECT http_get INTO request_id FROM net.http_get(
3625
+ url,
3626
+ params,
3627
+ headers,
3628
+ timeout_ms
3629
+ );
3630
+ WHEN method = 'POST' THEN
3631
+ payload = jsonb_build_object(
3632
+ 'old_record', OLD,
3633
+ 'record', NEW,
3634
+ 'type', TG_OP,
3635
+ 'table', TG_TABLE_NAME,
3636
+ 'schema', TG_TABLE_SCHEMA
3637
+ );
3638
+
3639
+ SELECT http_post INTO request_id FROM net.http_post(
3640
+ url,
3641
+ payload,
3642
+ params,
3643
+ headers,
3644
+ timeout_ms
3645
+ );
3646
+ ELSE
3647
+ RAISE EXCEPTION 'method argument % is invalid', method;
3648
+ END CASE;
3649
+
3650
+ INSERT INTO supabase_functions.hooks
3651
+ (hook_table_id, hook_name, request_id)
3652
+ VALUES
3653
+ (TG_RELID, TG_NAME, request_id);
3654
+
3655
+ RETURN NEW;
3656
+ END
3657
+ $function$;
3658
+ -- Supabase super admin
3659
+ DO
3660
+ $$
3661
+ BEGIN
3662
+ IF NOT EXISTS (
3663
+ SELECT 1
3664
+ FROM pg_roles
3665
+ WHERE rolname = 'supabase_functions_admin'
3666
+ )
3667
+ THEN
3668
+ CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION;
3669
+ END IF;
3670
+ END
3671
+ $$;
3672
+ GRANT ALL PRIVILEGES ON SCHEMA supabase_functions TO supabase_functions_admin;
3673
+ GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA supabase_functions TO supabase_functions_admin;
3674
+ GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA supabase_functions TO supabase_functions_admin;
3675
+ ALTER USER supabase_functions_admin SET search_path = "supabase_functions";
3676
+ ALTER table "supabase_functions".migrations OWNER TO supabase_functions_admin;
3677
+ ALTER table "supabase_functions".hooks OWNER TO supabase_functions_admin;
3678
+ ALTER function "supabase_functions".http_request() OWNER TO supabase_functions_admin;
3679
+ GRANT supabase_functions_admin TO postgres;
3680
+ -- Remove unused supabase_pg_net_admin role
3681
+ DO
3682
+ $$
3683
+ BEGIN
3684
+ IF EXISTS (
3685
+ SELECT 1
3686
+ FROM pg_roles
3687
+ WHERE rolname = 'supabase_pg_net_admin'
3688
+ )
3689
+ THEN
3690
+ REASSIGN OWNED BY supabase_pg_net_admin TO supabase_admin;
3691
+ DROP OWNED BY supabase_pg_net_admin;
3692
+ DROP ROLE supabase_pg_net_admin;
3693
+ END IF;
3694
+ END
3695
+ $$;
3696
+ -- pg_net grants when extension is already enabled
3697
+ DO
3698
+ $$
3699
+ BEGIN
3700
+ IF EXISTS (
3701
+ SELECT 1
3702
+ FROM pg_extension
3703
+ WHERE extname = 'pg_net'
3704
+ )
3705
+ THEN
3706
+ GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
3707
+ ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
3708
+ ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
3709
+ ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
3710
+ ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
3711
+ REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
3712
+ REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
3713
+ GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
3714
+ GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
3715
+ END IF;
3716
+ END
3717
+ $$;
3718
+ -- Event trigger for pg_net
3719
+ CREATE OR REPLACE FUNCTION extensions.grant_pg_net_access()
3720
+ RETURNS event_trigger
3721
+ LANGUAGE plpgsql
3722
+ AS $$
3723
+ BEGIN
3724
+ IF EXISTS (
3725
+ SELECT 1
3726
+ FROM pg_event_trigger_ddl_commands() AS ev
3727
+ JOIN pg_extension AS ext
3728
+ ON ev.objid = ext.oid
3729
+ WHERE ext.extname = 'pg_net'
3730
+ )
3731
+ THEN
3732
+ GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
3733
+ ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
3734
+ ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
3735
+ ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
3736
+ ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
3737
+ REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
3738
+ REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
3739
+ GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
3740
+ GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
3741
+ END IF;
3742
+ END;
3743
+ $$;
3744
+ COMMENT ON FUNCTION extensions.grant_pg_net_access IS 'Grants access to pg_net';
3745
+ DO
3746
+ $$
3747
+ BEGIN
3748
+ IF NOT EXISTS (
3749
+ SELECT 1
3750
+ FROM pg_event_trigger
3751
+ WHERE evtname = 'issue_pg_net_access'
3752
+ ) THEN
3753
+ CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end WHEN TAG IN ('CREATE EXTENSION')
3754
+ EXECUTE PROCEDURE extensions.grant_pg_net_access();
3755
+ END IF;
3756
+ END
3757
+ $$;
3758
+ INSERT INTO supabase_functions.migrations (version) VALUES ('20210809183423_update_grants');
3759
+ ALTER function supabase_functions.http_request() SECURITY DEFINER;
3760
+ ALTER function supabase_functions.http_request() SET search_path = supabase_functions;
3761
+ REVOKE ALL ON FUNCTION supabase_functions.http_request() FROM PUBLIC;
3762
+ GRANT EXECUTE ON FUNCTION supabase_functions.http_request() TO postgres, anon, authenticated, service_role;
3763
+ COMMIT;
3764
+ `
3765
+ },
3766
+ {
3767
+ service: "db",
3768
+ path: "/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql",
3769
+ content: '\\set jwt_secret `echo "$JWT_SECRET"`\n\\set jwt_exp `echo "$JWT_EXP"`\n\nALTER DATABASE postgres SET "app.settings.jwt_secret" TO :\'jwt_secret\';\nALTER DATABASE postgres SET "app.settings.jwt_exp" TO :\'jwt_exp\';\n'
3770
+ },
3771
+ {
3772
+ service: "db",
3773
+ path: "/docker-entrypoint-initdb.d/init-scripts/99-roles.sql",
3774
+ content: "\\set pgpass `echo \"$POSTGRES_PASSWORD\"`\n\nALTER USER authenticator WITH PASSWORD :'pgpass';\nALTER USER pgbouncer WITH PASSWORD :'pgpass';\nALTER USER supabase_auth_admin WITH PASSWORD :'pgpass';\nALTER USER supabase_functions_admin WITH PASSWORD :'pgpass';\nALTER USER supabase_storage_admin WITH PASSWORD :'pgpass';\n"
3775
+ },
3776
+ {
3777
+ service: "db",
3778
+ path: "/docker-entrypoint-initdb.d/migrations/99-realtime.sql",
3779
+ content: '\\set pguser `echo "$POSTGRES_USER"`\n\ncreate schema if not exists _realtime;\nalter schema _realtime owner to :pguser;\n'
3021
3780
  }
3022
3781
  ],
3023
3782
  management: {
@@ -3042,6 +3801,7 @@ var catalog_default = {
3042
3801
  id: "studio",
3043
3802
  label: "Studio",
3044
3803
  source: "publicUrl:kong",
3804
+ kind: "url",
3045
3805
  help: "Open in a browser and sign in with the Studio username + password below."
3046
3806
  },
3047
3807
  {
@@ -3072,12 +3832,12 @@ var catalog_default = {
3072
3832
  {
3073
3833
  id: "dbUrl",
3074
3834
  label: "Database URL",
3075
- source: "template:postgresql://postgres:{{env:db:POSTGRES_PASSWORD}}@{{host}}:5432/postgres",
3835
+ source: "template:postgresql://postgres:{{env:db:POSTGRES_PASSWORD}}@db:5432/postgres",
3076
3836
  service: "db",
3077
3837
  secret: true,
3078
3838
  envKey: "DATABASE_URL",
3079
3839
  recommended: true,
3080
- help: "Direct Postgres connection (superuser) for psql / ORMs. Published on port 5432."
3840
+ help: "Direct Postgres connection (superuser) reachable over this project's shared network. Connect another project to Supabase to inject it automatically."
3081
3841
  },
3082
3842
  {
3083
3843
  id: "dbPassword",
@@ -3099,7 +3859,8 @@ var catalog_default = {
3099
3859
  service: "db",
3100
3860
  port: 5432,
3101
3861
  label: "Database",
3102
- kind: "tcp"
3862
+ kind: "tcp",
3863
+ scope: "internal"
3103
3864
  }
3104
3865
  ],
3105
3866
  settings: [
@@ -3192,12 +3953,8 @@ var catalog_default = {
3192
3953
  },
3193
3954
  healthcheck: {
3194
3955
  test: [
3195
- "CMD",
3196
- "wget",
3197
- "--no-verbose",
3198
- "--tries=1",
3199
- "--spider",
3200
- "http://localhost:8081/"
3956
+ "CMD-SHELL",
3957
+ 'wget -q -S --spider http://127.0.0.1:8081/ 2>&1 | grep -q "HTTP/"'
3201
3958
  ],
3202
3959
  interval: "10s",
3203
3960
  timeout: "5s",
@@ -3241,6 +3998,7 @@ var catalog_default = {
3241
3998
  id: "admin",
3242
3999
  label: "Mongo Express",
3243
4000
  source: "publicUrl:mongo-express",
4001
+ kind: "url",
3244
4002
  envKey: "MONGO_EXPRESS_URL",
3245
4003
  help: "Sign in with the username + password below."
3246
4004
  },
@@ -3299,6 +4057,7 @@ var catalog_default = {
3299
4057
  id: "convex",
3300
4058
  name: "Convex",
3301
4059
  description: "Self-hosted Convex reactive backend + dashboard, with a persistent data volume.",
4060
+ repository: "https://github.com/get-convex/convex-backend",
3302
4061
  kind: "template",
3303
4062
  logo: "convex",
3304
4063
  category: "backend",
@@ -3311,7 +4070,7 @@ var catalog_default = {
3311
4070
  services: [
3312
4071
  {
3313
4072
  name: "backend",
3314
- image: "ghcr.io/get-convex/convex-backend:latest",
4073
+ image: "ghcr.io/get-convex/convex-backend:19431ea0dd90bc55ae58dbbd06d9aa045f97336f",
3315
4074
  ports: [
3316
4075
  "3210:3210",
3317
4076
  "3211:3211"
@@ -3353,7 +4112,7 @@ var catalog_default = {
3353
4112
  },
3354
4113
  {
3355
4114
  name: "dashboard",
3356
- image: "ghcr.io/get-convex/convex-dashboard:latest",
4115
+ image: "ghcr.io/get-convex/convex-dashboard:19431ea0dd90bc55ae58dbbd06d9aa045f97336f",
3357
4116
  ports: [
3358
4117
  "6791:6791"
3359
4118
  ],
@@ -3386,21 +4145,32 @@ var catalog_default = {
3386
4145
  service: "backend",
3387
4146
  command: "./generate_admin_key.sh",
3388
4147
  capture: "adminKey",
3389
- capturePattern: "Admin key:\\s*(\\S+)",
4148
+ capturePattern: "([^\\r\\n]+)\\s*$",
3390
4149
  persistAs: {
3391
4150
  key: "CONVEX_ADMIN_KEY"
3392
4151
  },
3393
- once: true
4152
+ once: true,
4153
+ title: "Generate admin key",
4154
+ description: "Mint the dashboard and CLI admin key from the instance secret.",
4155
+ icon: "key"
3394
4156
  }
3395
4157
  ],
3396
4158
  connection: {
3397
4159
  title: "Connect to this deployment",
3398
4160
  description: "Sign in to the Convex dashboard, or point the Convex CLI here, with these values.",
3399
4161
  outputs: [
4162
+ {
4163
+ id: "dashboard",
4164
+ label: "Dashboard URL",
4165
+ source: "publicUrl:dashboard",
4166
+ kind: "url",
4167
+ help: "Open the Convex dashboard UI, then sign in with the deployment URL and admin key below."
4168
+ },
3400
4169
  {
3401
4170
  id: "url",
3402
4171
  label: "Deployment URL",
3403
- source: "env:backend:CONVEX_CLOUD_ORIGIN"
4172
+ source: "env:backend:CONVEX_CLOUD_ORIGIN",
4173
+ kind: "url"
3404
4174
  },
3405
4175
  {
3406
4176
  id: "adminKey",
@@ -3779,7 +4549,23 @@ var catalog_default = {
3779
4549
  ],
3780
4550
  restart: "unless-stopped"
3781
4551
  }
3782
- ]
4552
+ ],
4553
+ connection: {
4554
+ title: "Sign in to Grafana",
4555
+ outputs: [
4556
+ {
4557
+ id: "url",
4558
+ label: "URL",
4559
+ source: "publicUrl:grafana",
4560
+ kind: "url"
4561
+ }
4562
+ ],
4563
+ firstLogin: {
4564
+ username: "admin",
4565
+ password: "admin",
4566
+ note: "Change the admin password right after your first login."
4567
+ }
4568
+ }
3783
4569
  },
3784
4570
  {
3785
4571
  available: false,
@@ -4060,7 +4846,7 @@ var catalog_default = {
4060
4846
  verified: true,
4061
4847
  id: "minio",
4062
4848
  name: "MinIO",
4063
- description: "S3-compatible object storage with a web console \u2014 point any S3 client (aws-cli, SDKs) at it.",
4849
+ description: "S3-compatible object storage with a web console \u2014 point any S3 client (aws-cli, SDKs) at it, or bind a bucket straight into another project's uploads.",
4064
4850
  kind: "template",
4065
4851
  logo: "minio",
4066
4852
  category: "database",
@@ -4119,6 +4905,29 @@ var catalog_default = {
4119
4905
  help: "Auto-generated. The S3 admin secret \u2014 shown once after install.",
4120
4906
  generate: "secret",
4121
4907
  secret: true
4908
+ },
4909
+ {
4910
+ key: "OPENSHIP_BUCKET",
4911
+ service: "minio",
4912
+ label: "First bucket",
4913
+ help: "Created on the first deploy so an app can use this storage immediately. More can be added from the console.",
4914
+ type: "text",
4915
+ default: "uploads",
4916
+ required: true
4917
+ }
4918
+ ],
4919
+ prepare: [
4920
+ {
4921
+ service: "minio",
4922
+ command: `mc alias set local http://127.0.0.1:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" > /dev/null && mc mb --ignore-existing "local/$OPENSHIP_BUCKET" > /dev/null && printf '%s' "$OPENSHIP_BUCKET"`,
4923
+ capture: "bucket",
4924
+ phase: "post-ready",
4925
+ readiness: {
4926
+ test: 'mc alias set local http://127.0.0.1:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"',
4927
+ interval: 1e3,
4928
+ retries: 30
4929
+ },
4930
+ once: true
4122
4931
  }
4123
4932
  ],
4124
4933
  endpoints: [
@@ -4142,26 +4951,60 @@ var catalog_default = {
4142
4951
  {
4143
4952
  id: "console",
4144
4953
  label: "Console",
4145
- source: "publicUrl:minio:9001"
4954
+ source: "publicUrl:minio:9001",
4955
+ kind: "url"
4146
4956
  },
4147
4957
  {
4148
4958
  id: "endpoint",
4149
4959
  label: "S3 endpoint",
4150
- source: "publicUrl:minio:9000"
4960
+ source: "publicUrl:minio:9000",
4961
+ service: "minio",
4962
+ envKey: "S3_ENDPOINT",
4963
+ recommended: true
4151
4964
  },
4152
4965
  {
4153
4966
  id: "accessKey",
4154
4967
  label: "Access key",
4155
- source: "env:minio:MINIO_ROOT_USER"
4968
+ source: "env:minio:MINIO_ROOT_USER",
4969
+ service: "minio",
4970
+ envKey: "S3_ACCESS_KEY_ID",
4971
+ recommended: true
4156
4972
  },
4157
4973
  {
4158
4974
  id: "secretKey",
4159
4975
  label: "Secret key",
4160
4976
  source: "env:minio:MINIO_ROOT_PASSWORD",
4161
- secret: true
4977
+ service: "minio",
4978
+ envKey: "S3_SECRET_ACCESS_KEY",
4979
+ secret: true,
4980
+ recommended: true
4981
+ },
4982
+ {
4983
+ id: "bucket",
4984
+ label: "Bucket",
4985
+ source: "env:minio:OPENSHIP_BUCKET",
4986
+ service: "minio",
4987
+ envKey: "S3_BUCKET"
4162
4988
  }
4163
- ]
4164
- }
4989
+ ],
4990
+ guide: {
4991
+ intro: "Your app gets an S3-compatible bucket it can write uploads to.",
4992
+ useHint: "Bind it from the consuming project's Configuration \u2192 Object storage to get the framework's own env names (Laravel gets FILESYSTEM_DISK + AWS_*), or wire these values by hand.",
4993
+ defaultMode: "internal"
4994
+ }
4995
+ },
4996
+ provides: [
4997
+ {
4998
+ id: "s3",
4999
+ outputRefs: [
5000
+ "endpoint",
5001
+ "accessKey",
5002
+ "secretKey",
5003
+ "bucket"
5004
+ ],
5005
+ category: "database"
5006
+ }
5007
+ ]
4165
5008
  },
4166
5009
  {
4167
5010
  available: true,
@@ -4255,6 +5098,7 @@ var catalog_default = {
4255
5098
  id: "ui",
4256
5099
  label: "Kafka UI",
4257
5100
  source: "publicUrl:kafka-ui",
5101
+ kind: "url",
4258
5102
  help: "Sign in with the username + password below."
4259
5103
  },
4260
5104
  {
@@ -4347,6 +5191,7 @@ var catalog_default = {
4347
5191
  id: "ui",
4348
5192
  label: "Qdrant Web UI",
4349
5193
  source: "publicUrl:qdrant",
5194
+ kind: "url",
4350
5195
  help: "The dashboard is at this URL under /dashboard. Paste the API key when it asks."
4351
5196
  },
4352
5197
  {
@@ -18933,6 +19778,7 @@ var configField = external_exports.object({
18933
19778
  required: external_exports.boolean().optional(),
18934
19779
  secret: external_exports.boolean().optional()
18935
19780
  });
19781
+ var localized = external_exports.union([external_exports.string(), external_exports.record(external_exports.string(), external_exports.string())]);
18936
19782
  var prepareStep = external_exports.object({
18937
19783
  service: external_exports.string(),
18938
19784
  command: external_exports.string(),
@@ -18942,7 +19788,12 @@ var prepareStep = external_exports.object({
18942
19788
  once: external_exports.boolean().optional(),
18943
19789
  phase: external_exports.enum(["pre-deploy", "post-start", "post-ready"]).optional(),
18944
19790
  mustSucceed: external_exports.boolean().optional(),
18945
- readiness: external_exports.object({ test: external_exports.string(), interval: external_exports.number().optional(), retries: external_exports.number().optional() }).optional()
19791
+ readiness: external_exports.object({ test: external_exports.string(), interval: external_exports.number().optional(), retries: external_exports.number().optional() }).optional(),
19792
+ // Authored display copy for the install stepper's "app-setup" sub-steps. Purely
19793
+ // presentational; absent → the renderer uses a generic label.
19794
+ title: localized.optional(),
19795
+ description: localized.optional(),
19796
+ icon: external_exports.string().optional()
18946
19797
  });
18947
19798
  var outputVariant = external_exports.object({
18948
19799
  id: external_exports.string(),
@@ -18967,7 +19818,11 @@ var connection = external_exports.object({
18967
19818
  recommended: external_exports.boolean().optional(),
18968
19819
  sourceLabel: external_exports.union([external_exports.string(), external_exports.record(external_exports.string(), external_exports.string())]).optional(),
18969
19820
  variants: external_exports.array(outputVariant).optional(),
18970
- width: external_exports.enum(["full", "half"]).optional()
19821
+ width: external_exports.enum(["full", "half"]).optional(),
19822
+ /** "url" marks the resolved value as an openable link — the Connection
19823
+ * card shows an Open-in-new-tab action beside it. Enforced only for a
19824
+ * resolved http(s) value (checked at render); default "text". Additive. */
19825
+ kind: external_exports.enum(["text", "url"]).optional()
18971
19826
  })
18972
19827
  ),
18973
19828
  guide: external_exports.object({
@@ -18975,6 +19830,14 @@ var connection = external_exports.object({
18975
19830
  intro: external_exports.union([external_exports.string(), external_exports.record(external_exports.string(), external_exports.string())]).optional(),
18976
19831
  useHint: external_exports.union([external_exports.string(), external_exports.record(external_exports.string(), external_exports.string())]).optional(),
18977
19832
  defaultMode: external_exports.enum(["internal", "public"]).optional()
19833
+ }).optional(),
19834
+ // Static default credentials the app ships with (e.g. Grafana admin/admin).
19835
+ // These are FIXED values, not derived from a service env, so they can't live in
19836
+ // `outputs` (whose values resolve from running services) — hence their own field.
19837
+ firstLogin: external_exports.object({
19838
+ username: localized.optional(),
19839
+ password: localized.optional(),
19840
+ note: localized.optional()
18978
19841
  }).optional()
18979
19842
  });
18980
19843
  var endpointMode = external_exports.enum(["domain", "port", "publish", "internal"]);
@@ -18988,7 +19851,6 @@ var endpoint = external_exports.object({
18988
19851
  defaultMode: endpointMode.optional(),
18989
19852
  allowedModes: external_exports.array(endpointMode).optional()
18990
19853
  });
18991
- var localized = external_exports.union([external_exports.string(), external_exports.record(external_exports.string(), external_exports.string())]);
18992
19854
  var provides = external_exports.object({
18993
19855
  id: external_exports.string(),
18994
19856
  outputRefs: external_exports.array(external_exports.string()),
@@ -19073,7 +19935,8 @@ var appTemplateSchema = external_exports.object({
19073
19935
  // Versioning / compat gate (see parseAppTemplate).
19074
19936
  schemaVersion: external_exports.number().optional(),
19075
19937
  minEngine: external_exports.string().optional(),
19076
- updatedAt: external_exports.string().optional()
19938
+ updatedAt: external_exports.string().optional(),
19939
+ repository: external_exports.string().url().optional()
19077
19940
  }).superRefine((data, ctx) => {
19078
19941
  const svcNames = new Set((data.services ?? []).map((s) => s.name));
19079
19942
  const refSvc = (service, path, where) => {
@@ -19136,6 +19999,757 @@ function cliInstallCommand(pm, version2) {
19136
19999
  return pm === "bun" ? `bun add -g ${ref}` : `npm install -g ${ref}`;
19137
20000
  }
19138
20001
 
20002
+ // ../../packages/core/src/audit-taxonomy.ts
20003
+ var AUDIT_CATEGORIES = [
20004
+ {
20005
+ id: "deployments",
20006
+ label: "Deployments",
20007
+ description: "Builds and releases \u2014 what shipped, what failed, what was cancelled."
20008
+ },
20009
+ {
20010
+ id: "apps",
20011
+ label: "Apps & services",
20012
+ description: "Projects, the services inside them, and their runtime health."
20013
+ },
20014
+ {
20015
+ id: "domains",
20016
+ label: "Domains & SSL",
20017
+ description: "Custom domains, DNS verification, and certificates."
20018
+ },
20019
+ {
20020
+ id: "servers",
20021
+ label: "Servers",
20022
+ description: "Deployment targets, their configuration, and shell access."
20023
+ },
20024
+ {
20025
+ id: "members",
20026
+ label: "Members & access",
20027
+ description: "Who is in this organization and what they are allowed to do."
20028
+ },
20029
+ {
20030
+ id: "security",
20031
+ label: "Security",
20032
+ description: "Admin credentials, auth mode, data export, and audit recording itself."
20033
+ },
20034
+ {
20035
+ id: "billing",
20036
+ label: "Billing",
20037
+ description: "Subscriptions, credits, spending caps, and quota thresholds."
20038
+ },
20039
+ {
20040
+ id: "system",
20041
+ label: "System",
20042
+ description: "Settings, integrations, notifications, backups, and instance maintenance."
20043
+ }
20044
+ ];
20045
+ var AUDIT_EVENTS = {
20046
+ /* ---------------- Deployments ---------------- */
20047
+ "deployment.succeeded": {
20048
+ category: "deployments",
20049
+ action: "deployed",
20050
+ label: "Deploy succeeded",
20051
+ tone: "success",
20052
+ description: "A deployment finished successfully and is serving traffic."
20053
+ },
20054
+ "deployment.failed": {
20055
+ category: "deployments",
20056
+ action: "failed to deploy",
20057
+ label: "Deploy failed",
20058
+ tone: "danger",
20059
+ description: "A deployment errored out before reaching a healthy state."
20060
+ },
20061
+ "deployment.cancelled": {
20062
+ category: "deployments",
20063
+ action: "cancelled the deploy of",
20064
+ label: "Deploy cancelled",
20065
+ tone: "warning",
20066
+ description: "A deployment was stopped before it finished."
20067
+ },
20068
+ "deployment:write": {
20069
+ category: "deployments",
20070
+ action: "started a deploy for",
20071
+ label: "Deploy action",
20072
+ description: "A deployment endpoint was invoked \u2014 start, retry, rollback, or promote."
20073
+ },
20074
+ "deployment:admin": {
20075
+ category: "deployments",
20076
+ action: "ran an admin action on the deploy for",
20077
+ label: "Deploy admin action",
20078
+ tone: "warning",
20079
+ description: "A destructive deployment endpoint was invoked, such as cancel or destroy."
20080
+ },
20081
+ /* ---------------- Apps & services ---------------- */
20082
+ "project.created": {
20083
+ category: "apps",
20084
+ action: "created",
20085
+ label: "App created",
20086
+ tone: "info"
20087
+ },
20088
+ "project.updated": {
20089
+ category: "apps",
20090
+ action: "updated",
20091
+ label: "App updated",
20092
+ description: "Settings, services, environment variables, or routing were changed."
20093
+ },
20094
+ "project.deleted": {
20095
+ category: "apps",
20096
+ action: "deleted",
20097
+ label: "App deleted",
20098
+ tone: "danger"
20099
+ },
20100
+ "project.deletion.rejected": {
20101
+ category: "apps",
20102
+ action: "tried to delete",
20103
+ label: "App deletion refused",
20104
+ tone: "warning",
20105
+ description: "A delete was refused \u2014 something still depends on this app."
20106
+ },
20107
+ "project:write": {
20108
+ category: "apps",
20109
+ action: "changed",
20110
+ label: "App changed",
20111
+ description: "One of the app's write endpoints was invoked."
20112
+ },
20113
+ "project:admin": {
20114
+ category: "apps",
20115
+ action: "ran an admin action on",
20116
+ label: "App admin action",
20117
+ tone: "warning",
20118
+ description: "A destructive app endpoint was invoked, such as delete or transfer."
20119
+ },
20120
+ "project:service:write": {
20121
+ category: "apps",
20122
+ action: "changed a service in",
20123
+ label: "Service changed",
20124
+ description: "A service was created, updated, started, stopped, or restarted."
20125
+ },
20126
+ "project:service:admin": {
20127
+ category: "apps",
20128
+ action: "ran an admin action on a service in",
20129
+ label: "Service admin action",
20130
+ tone: "warning"
20131
+ },
20132
+ "job:write": {
20133
+ category: "apps",
20134
+ action: "changed the job",
20135
+ label: "Job changed",
20136
+ description: "A scheduled or custom job was created, edited, run, or removed."
20137
+ },
20138
+ "job_run.started": {
20139
+ category: "apps",
20140
+ action: "started the job",
20141
+ label: "Job started"
20142
+ },
20143
+ "job_run.succeeded": {
20144
+ category: "apps",
20145
+ action: "ran the job",
20146
+ label: "Job succeeded",
20147
+ tone: "success"
20148
+ },
20149
+ "job_run.failed": {
20150
+ category: "apps",
20151
+ action: "ran the failing job",
20152
+ label: "Job failed",
20153
+ tone: "danger"
20154
+ },
20155
+ // Written by the health watcher, never by a person — these only ever render
20156
+ // through `label`.
20157
+ "service.down": {
20158
+ category: "apps",
20159
+ action: "reported down",
20160
+ label: "App went down",
20161
+ tone: "danger",
20162
+ description: "A container that should be running isn't \u2014 exited, dead, or OOM-killed."
20163
+ },
20164
+ "service.crash_loop": {
20165
+ category: "apps",
20166
+ action: "reported crash looping",
20167
+ label: "App is crash looping",
20168
+ tone: "danger",
20169
+ description: "A container keeps restarting instead of staying up."
20170
+ },
20171
+ "service.unhealthy": {
20172
+ category: "apps",
20173
+ action: "reported unhealthy",
20174
+ label: "App is unhealthy",
20175
+ tone: "warning",
20176
+ description: "A container is running but its own healthcheck is failing."
20177
+ },
20178
+ "service.recovered": {
20179
+ category: "apps",
20180
+ action: "reported recovered",
20181
+ label: "App recovered",
20182
+ tone: "success",
20183
+ description: "An outage ended and the container is healthy again."
20184
+ },
20185
+ /* ---------------- Domains & SSL ---------------- */
20186
+ "domain.added": {
20187
+ category: "domains",
20188
+ action: "added the domain",
20189
+ label: "Domain added",
20190
+ tone: "info"
20191
+ },
20192
+ "domain.removed": {
20193
+ category: "domains",
20194
+ action: "removed the domain",
20195
+ label: "Domain removed",
20196
+ tone: "danger"
20197
+ },
20198
+ "domain.verified": {
20199
+ category: "domains",
20200
+ action: "verified the domain",
20201
+ label: "Domain verified",
20202
+ tone: "success",
20203
+ description: "DNS verification succeeded, so the domain can be issued a certificate."
20204
+ },
20205
+ "domain.verification_failed": {
20206
+ category: "domains",
20207
+ action: "failed to verify the domain",
20208
+ label: "Domain verification failed",
20209
+ tone: "warning",
20210
+ description: "DNS records for this domain don't point here yet."
20211
+ },
20212
+ "domain.set_primary": {
20213
+ category: "domains",
20214
+ action: "made primary the domain",
20215
+ label: "Primary domain changed",
20216
+ description: "The domain other addresses now redirect to."
20217
+ },
20218
+ "domain.cert_uploaded": {
20219
+ category: "domains",
20220
+ action: "uploaded a certificate for",
20221
+ label: "Certificate uploaded",
20222
+ description: "A certificate and key were supplied by hand instead of issued automatically."
20223
+ },
20224
+ "ssl.renewal_failed": {
20225
+ category: "domains",
20226
+ action: "failed to renew SSL for",
20227
+ label: "SSL renewal failed",
20228
+ tone: "danger",
20229
+ description: "Automatic renewal failed \u2014 the certificate will expire unless this is fixed."
20230
+ },
20231
+ "domain:write": {
20232
+ category: "domains",
20233
+ action: "changed the domain",
20234
+ label: "Domain action",
20235
+ description: "A domain endpoint was invoked \u2014 add, verify, or renew."
20236
+ },
20237
+ "domain:admin": {
20238
+ category: "domains",
20239
+ action: "ran an admin action on the domain",
20240
+ label: "Domain admin action",
20241
+ tone: "warning"
20242
+ },
20243
+ /* ---------------- Servers ---------------- */
20244
+ "server.added": {
20245
+ category: "servers",
20246
+ action: "added the server",
20247
+ label: "Server added",
20248
+ tone: "info"
20249
+ },
20250
+ "server.updated": {
20251
+ category: "servers",
20252
+ action: "updated the server",
20253
+ label: "Server updated"
20254
+ },
20255
+ "server.removed": {
20256
+ category: "servers",
20257
+ action: "removed the server",
20258
+ label: "Server removed",
20259
+ tone: "danger"
20260
+ },
20261
+ "server.unreachable": {
20262
+ category: "servers",
20263
+ action: "reported unreachable",
20264
+ label: "Server unreachable",
20265
+ tone: "danger",
20266
+ description: "Openship could not open an SSH connection to this server."
20267
+ },
20268
+ "server.reachable": {
20269
+ category: "servers",
20270
+ action: "reported reachable",
20271
+ label: "Server reachable again",
20272
+ tone: "success"
20273
+ },
20274
+ "server:write": {
20275
+ category: "servers",
20276
+ action: "changed the server",
20277
+ label: "Server changed"
20278
+ },
20279
+ "server:admin": {
20280
+ category: "servers",
20281
+ action: "ran an admin action on the server",
20282
+ label: "Server admin action",
20283
+ tone: "warning",
20284
+ description: "A destructive server endpoint was invoked, such as delete or component install."
20285
+ },
20286
+ "terminal:write": {
20287
+ category: "servers",
20288
+ action: "opened a terminal on",
20289
+ label: "Terminal session opened",
20290
+ tone: "warning",
20291
+ description: "An interactive shell session was issued against a server."
20292
+ },
20293
+ "mail_server:write": {
20294
+ category: "servers",
20295
+ action: "changed the mail server",
20296
+ label: "Mail server changed",
20297
+ description: "A mail server endpoint was invoked \u2014 setup, domain, DNS acknowledgement."
20298
+ },
20299
+ "mail_server:admin": {
20300
+ category: "servers",
20301
+ action: "ran an admin action on the mail server",
20302
+ label: "Mail server admin action",
20303
+ tone: "warning"
20304
+ },
20305
+ /* ---------------- Members & access ---------------- */
20306
+ "organization.created": {
20307
+ category: "members",
20308
+ action: "created the organization",
20309
+ label: "Organization created",
20310
+ tone: "info"
20311
+ },
20312
+ "organization.deleted": {
20313
+ category: "members",
20314
+ action: "deleted the organization",
20315
+ label: "Organization deleted",
20316
+ tone: "danger"
20317
+ },
20318
+ "organization.deletion.blocked": {
20319
+ category: "members",
20320
+ action: "tried to delete the organization",
20321
+ label: "Organization deletion blocked",
20322
+ tone: "warning",
20323
+ description: "Deletion was refused \u2014 active resources or billing still attached."
20324
+ },
20325
+ "member.added": {
20326
+ category: "members",
20327
+ action: "added",
20328
+ label: "Member added",
20329
+ tone: "info"
20330
+ },
20331
+ "member.removed": {
20332
+ category: "members",
20333
+ action: "removed",
20334
+ label: "Member removed",
20335
+ tone: "danger"
20336
+ },
20337
+ "member.role_changed": {
20338
+ category: "members",
20339
+ action: "changed the role of",
20340
+ label: "Member role changed",
20341
+ tone: "warning"
20342
+ },
20343
+ "member.joined": {
20344
+ category: "members",
20345
+ action: "joined",
20346
+ label: "Member joined",
20347
+ tone: "success",
20348
+ description: "An invitee accepted and now has access."
20349
+ },
20350
+ "member.invited": {
20351
+ category: "members",
20352
+ action: "invited",
20353
+ label: "Member invited",
20354
+ tone: "info"
20355
+ },
20356
+ "member.removal.grant_cleanup_failed": {
20357
+ category: "members",
20358
+ action: "removed, with leftover permissions,",
20359
+ label: "Removed member's permissions not cleaned up",
20360
+ tone: "warning",
20361
+ description: "The member is gone but their permission grants remained \u2014 review by hand."
20362
+ },
20363
+ "member.removal.subscription_cleanup_failed": {
20364
+ category: "members",
20365
+ action: "removed, with leftover billing,",
20366
+ label: "Removed member's billing not cleaned up",
20367
+ tone: "warning",
20368
+ description: "The member is gone but a linked subscription remained \u2014 review billing."
20369
+ },
20370
+ "invitation.created": {
20371
+ category: "members",
20372
+ action: "invited",
20373
+ label: "Invitation sent",
20374
+ tone: "info"
20375
+ },
20376
+ "invitation.sent": {
20377
+ category: "members",
20378
+ action: "invited, with pending permissions,",
20379
+ label: "Invitation sent with pending permissions",
20380
+ tone: "info",
20381
+ description: "Grants were queued and will apply the moment the invitee joins."
20382
+ },
20383
+ "invitation.accepted": {
20384
+ category: "members",
20385
+ action: "accepted the invitation of",
20386
+ label: "Invitation accepted",
20387
+ tone: "success"
20388
+ },
20389
+ "invitation.rejected": {
20390
+ category: "members",
20391
+ action: "declined the invitation of",
20392
+ label: "Invitation declined",
20393
+ tone: "warning"
20394
+ },
20395
+ "invitation.cancelled": {
20396
+ category: "members",
20397
+ action: "cancelled the invitation for",
20398
+ label: "Invitation cancelled",
20399
+ tone: "warning"
20400
+ },
20401
+ "team.created": {
20402
+ category: "members",
20403
+ action: "created the team",
20404
+ label: "Team created",
20405
+ tone: "info"
20406
+ },
20407
+ "grant.granted": {
20408
+ category: "members",
20409
+ action: "granted permissions to",
20410
+ label: "Permission granted",
20411
+ tone: "info"
20412
+ },
20413
+ "grant.revoked": {
20414
+ category: "members",
20415
+ action: "revoked permissions from",
20416
+ label: "Permission revoked",
20417
+ tone: "danger"
20418
+ },
20419
+ "grant.replaced": {
20420
+ category: "members",
20421
+ action: "replaced the permissions of",
20422
+ label: "Permission replaced",
20423
+ tone: "warning",
20424
+ description: "An existing grant was overwritten with a different scope."
20425
+ },
20426
+ "grant.materialized": {
20427
+ category: "members",
20428
+ action: "received their pending permissions,",
20429
+ label: "Pending permission applied",
20430
+ description: "A grant attached to an invitation was applied once the invitee joined."
20431
+ },
20432
+ "permissions:write": {
20433
+ category: "members",
20434
+ action: "changed permissions for",
20435
+ label: "Permissions changed",
20436
+ tone: "warning"
20437
+ },
20438
+ "permissions:admin": {
20439
+ category: "members",
20440
+ action: "ran an admin action on permissions for",
20441
+ label: "Permissions admin action",
20442
+ tone: "warning"
20443
+ },
20444
+ /* ---------------- Security ---------------- */
20445
+ "admin.bootstrapped": {
20446
+ category: "security",
20447
+ action: "bootstrapped the first admin account",
20448
+ label: "First admin account created",
20449
+ tone: "warning",
20450
+ description: "The instance's initial owner was created \u2014 this can only happen once."
20451
+ },
20452
+ "admin.password_reset": {
20453
+ category: "security",
20454
+ action: "reset an admin password",
20455
+ label: "Admin password reset",
20456
+ tone: "danger"
20457
+ },
20458
+ "auth-mode-changed": {
20459
+ category: "security",
20460
+ action: "changed the sign-in mode",
20461
+ label: "Sign-in mode changed",
20462
+ tone: "danger",
20463
+ description: "The instance switched between single-user and full authentication."
20464
+ },
20465
+ "instance.data.exported": {
20466
+ category: "security",
20467
+ action: "exported all instance data",
20468
+ label: "Instance data exported",
20469
+ tone: "warning",
20470
+ description: "A full database dump left this instance."
20471
+ },
20472
+ "instance.data.imported": {
20473
+ category: "security",
20474
+ action: "imported instance data",
20475
+ label: "Instance data imported",
20476
+ tone: "warning",
20477
+ description: "A database dump from another instance was loaded in."
20478
+ },
20479
+ // Written by the audit module itself. "disabled" is recorded before the switch
20480
+ // flips, so turning recording off always leaves this one row behind.
20481
+ "audit.disabled": {
20482
+ category: "security",
20483
+ action: "turned off activity recording",
20484
+ label: "Activity recording turned off",
20485
+ tone: "danger",
20486
+ description: "No further actions were recorded until recording was turned back on."
20487
+ },
20488
+ "audit.enabled": {
20489
+ category: "security",
20490
+ action: "turned on activity recording",
20491
+ label: "Activity recording turned on",
20492
+ tone: "success"
20493
+ },
20494
+ "audit.retention_changed": {
20495
+ category: "security",
20496
+ action: "changed how long activity is kept",
20497
+ label: "Activity retention changed",
20498
+ tone: "warning"
20499
+ },
20500
+ "audit:write": {
20501
+ category: "security",
20502
+ action: "changed activity recording",
20503
+ label: "Activity settings changed",
20504
+ tone: "warning"
20505
+ },
20506
+ /* ---------------- Billing ---------------- */
20507
+ "billing.hard_cap_tripped": {
20508
+ category: "billing",
20509
+ action: "hit the spending cap for",
20510
+ label: "Spending cap reached",
20511
+ tone: "danger",
20512
+ description: "Cloud resources may have been paused \u2014 review billing."
20513
+ },
20514
+ "billing.credit_exhausted": {
20515
+ category: "billing",
20516
+ action: "ran out of credits for",
20517
+ label: "Credits exhausted",
20518
+ tone: "warning",
20519
+ description: "Metered usage is paused until a top-up or the monthly reset."
20520
+ },
20521
+ "billing.credit_restored": {
20522
+ category: "billing",
20523
+ action: "restored credits for",
20524
+ label: "Credits restored",
20525
+ tone: "success"
20526
+ },
20527
+ "billing.anniversary_reset": {
20528
+ category: "billing",
20529
+ action: "reset the monthly credits of",
20530
+ label: "Monthly credits reset"
20531
+ },
20532
+ "quota.threshold_fired": {
20533
+ category: "billing",
20534
+ action: "crossed a usage threshold on",
20535
+ label: "Usage threshold crossed",
20536
+ tone: "warning"
20537
+ },
20538
+ "billing:write": {
20539
+ category: "billing",
20540
+ action: "changed billing for",
20541
+ label: "Billing action",
20542
+ description: "A billing endpoint was invoked \u2014 subscribe, top up, or open the portal."
20543
+ },
20544
+ "billing:admin": {
20545
+ category: "billing",
20546
+ action: "ran an admin action on billing for",
20547
+ label: "Billing admin action",
20548
+ tone: "warning",
20549
+ description: "A destructive billing endpoint was invoked, such as cancelling a subscription."
20550
+ },
20551
+ /* ---------------- System ---------------- */
20552
+ "settings.updated": {
20553
+ category: "system",
20554
+ action: "updated settings",
20555
+ label: "Settings updated"
20556
+ },
20557
+ "settings:write": {
20558
+ category: "system",
20559
+ action: "changed settings",
20560
+ label: "Settings changed"
20561
+ },
20562
+ "settings:admin": {
20563
+ category: "system",
20564
+ action: "ran an admin action on settings",
20565
+ label: "Settings admin action",
20566
+ tone: "warning",
20567
+ description: "A destructive settings endpoint was invoked, such as a wipe or migration."
20568
+ },
20569
+ "updates:write": {
20570
+ category: "system",
20571
+ action: "applied an update",
20572
+ label: "Update applied",
20573
+ description: "An Openship component was updated or an update scan was triggered."
20574
+ },
20575
+ "cloud.disconnect": {
20576
+ category: "system",
20577
+ action: "disconnected from Openship Cloud",
20578
+ label: "Cloud disconnected",
20579
+ tone: "warning"
20580
+ },
20581
+ "cloud:write": {
20582
+ category: "system",
20583
+ action: "ran a cloud action",
20584
+ label: "Cloud action",
20585
+ description: "A cloud bridge endpoint was invoked \u2014 token, preflight, or edge proxy."
20586
+ },
20587
+ "cloud:admin": {
20588
+ category: "system",
20589
+ action: "ran a cloud admin action",
20590
+ label: "Cloud admin action",
20591
+ tone: "warning"
20592
+ },
20593
+ "github.install": {
20594
+ category: "system",
20595
+ action: "installed the GitHub app on",
20596
+ label: "GitHub app installed",
20597
+ tone: "info"
20598
+ },
20599
+ "github.disconnect": {
20600
+ category: "system",
20601
+ action: "disconnected GitHub from",
20602
+ label: "GitHub disconnected",
20603
+ tone: "warning"
20604
+ },
20605
+ "github.repo.create": {
20606
+ category: "system",
20607
+ action: "created the repository",
20608
+ label: "Repository created",
20609
+ tone: "info"
20610
+ },
20611
+ "github.repo.delete": {
20612
+ category: "system",
20613
+ action: "deleted the repository",
20614
+ label: "Repository deleted",
20615
+ tone: "danger"
20616
+ },
20617
+ "github.webhook.register": {
20618
+ category: "system",
20619
+ action: "enabled deploy-on-push for",
20620
+ label: "Deploy-on-push enabled",
20621
+ tone: "info"
20622
+ },
20623
+ "github.webhook.delete": {
20624
+ category: "system",
20625
+ action: "disabled deploy-on-push for",
20626
+ label: "Deploy-on-push disabled",
20627
+ tone: "warning"
20628
+ },
20629
+ "github.instance_token.set": {
20630
+ category: "system",
20631
+ action: "set the instance GitHub token",
20632
+ label: "Instance GitHub token set",
20633
+ tone: "warning",
20634
+ description: "A GitHub token shared by every project on this instance was stored."
20635
+ },
20636
+ "github:write": {
20637
+ category: "system",
20638
+ action: "changed a GitHub connection for",
20639
+ label: "GitHub action"
20640
+ },
20641
+ "github:admin": {
20642
+ category: "system",
20643
+ action: "ran a GitHub admin action on",
20644
+ label: "GitHub admin action",
20645
+ tone: "warning"
20646
+ },
20647
+ "notification_channel.created": {
20648
+ category: "system",
20649
+ action: "added the notification channel",
20650
+ label: "Notification channel added",
20651
+ tone: "info"
20652
+ },
20653
+ "notification_channel.updated": {
20654
+ category: "system",
20655
+ action: "updated the notification channel",
20656
+ label: "Notification channel updated"
20657
+ },
20658
+ "notification_channel.deleted": {
20659
+ category: "system",
20660
+ action: "removed the notification channel",
20661
+ label: "Notification channel removed",
20662
+ tone: "warning"
20663
+ },
20664
+ "notification_subscription.updated": {
20665
+ category: "system",
20666
+ action: "changed what is sent to",
20667
+ label: "Notification subscription changed"
20668
+ },
20669
+ "notification_subscription.deleted": {
20670
+ category: "system",
20671
+ action: "stopped notifications to",
20672
+ label: "Notification subscription removed",
20673
+ tone: "warning"
20674
+ },
20675
+ "notification_default.updated": {
20676
+ category: "system",
20677
+ action: "changed the notification defaults of",
20678
+ label: "Notification defaults changed"
20679
+ },
20680
+ "notifications:write": {
20681
+ category: "system",
20682
+ action: "changed notifications",
20683
+ label: "Notifications changed"
20684
+ },
20685
+ "notifications:admin": {
20686
+ category: "system",
20687
+ action: "ran a notifications admin action",
20688
+ label: "Notifications admin action",
20689
+ tone: "warning"
20690
+ },
20691
+ "backup_run.succeeded": {
20692
+ category: "system",
20693
+ action: "backed up",
20694
+ label: "Backup succeeded",
20695
+ tone: "success"
20696
+ },
20697
+ "backup_run.failed": {
20698
+ category: "system",
20699
+ action: "failed to back up",
20700
+ label: "Backup failed",
20701
+ tone: "danger"
20702
+ },
20703
+ "backup.webhook.fired": {
20704
+ category: "system",
20705
+ action: "triggered a backup of",
20706
+ label: "Backup triggered by webhook"
20707
+ },
20708
+ "backup.webhook.disabled": {
20709
+ category: "system",
20710
+ action: "disabled the backup webhook for",
20711
+ label: "Backup webhook disabled",
20712
+ tone: "warning",
20713
+ description: "The webhook failed too many times and was switched off."
20714
+ },
20715
+ "backup_destination:write": {
20716
+ category: "system",
20717
+ action: "changed the backup destination",
20718
+ label: "Backup destination changed"
20719
+ },
20720
+ "backup_destination:admin": {
20721
+ category: "system",
20722
+ action: "ran an admin action on the backup destination",
20723
+ label: "Backup destination admin action",
20724
+ tone: "warning"
20725
+ },
20726
+ "backup_destination:backup_policy:write": {
20727
+ category: "system",
20728
+ action: "changed the backup schedule of",
20729
+ label: "Backup schedule changed"
20730
+ },
20731
+ "backup_destination:backup_run:write": {
20732
+ category: "system",
20733
+ action: "ran a backup of",
20734
+ label: "Backup run"
20735
+ },
20736
+ "backup_destination:backup_restore:write": {
20737
+ category: "system",
20738
+ action: "restored from a backup of",
20739
+ label: "Restore from backup",
20740
+ tone: "warning"
20741
+ }
20742
+ };
20743
+ var CATEGORY_IDS = new Set(AUDIT_CATEGORIES.map((c) => c.id));
20744
+ var EVENT_TYPES_BY_CATEGORY = (() => {
20745
+ const map2 = {};
20746
+ for (const id of CATEGORY_IDS) map2[id] = [];
20747
+ for (const [eventType, def] of Object.entries(AUDIT_EVENTS)) {
20748
+ map2[def.category]?.push(eventType);
20749
+ }
20750
+ return map2;
20751
+ })();
20752
+
19139
20753
  export {
19140
20754
  LANGUAGES,
19141
20755
  STACKS,
@@ -19143,9 +20757,15 @@ export {
19143
20757
  buildOutputTransferExcludes,
19144
20758
  PACKAGE_ROOT_ONLY_EXCLUDES,
19145
20759
  packageManagerEnsureCommand,
20760
+ appVolumeTargets,
20761
+ isAwsS3Endpoint,
19146
20762
  DEFAULT_IMAGE_REGISTRY,
20763
+ shellSplitWords,
20764
+ commandToArgv,
19147
20765
  buildEdgeImageRef,
20766
+ buildMailImageRef,
19148
20767
  SYSTEM,
20768
+ resolveRedirectStatus,
19149
20769
  isValidEmail,
19150
20770
  formatBytes,
19151
20771
  withTimeout,
@@ -19155,11 +20775,17 @@ export {
19155
20775
  LOCAL_DASHBOARD_URL,
19156
20776
  LOCAL_API_URL,
19157
20777
  CLOUD_DASHBOARD_URL,
20778
+ hasCpuLimit,
20779
+ hasMemoryLimit,
20780
+ formatMemoryMb,
20781
+ formatCpuCores,
19158
20782
  parseOpenshipConfigJson,
19159
20783
  missingOutputDirectoryMessage,
19160
20784
  compareSemver,
19161
20785
  resolveCliUpdatePlan,
19162
20786
  cliInstallCommand,
19163
- PROXY_GZIP_TYPES,
19164
- sanitizeProxySettings
20787
+ PROXY_DIRECTIVES,
20788
+ sanitizeProxySettings,
20789
+ resolveProxyDirectives,
20790
+ parseProxyValue
19165
20791
  };