@ankhorage/infra 4.1.0 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -60,6 +60,7 @@ const PORT_REFERENCE_SLUG = 'my-app';
60
60
  export function generateMinikubeBaseArtifacts(args: {
61
61
  manifest: InfraManifestInput;
62
62
  appSlug: string;
63
+ appRuntimeEnabled: boolean;
63
64
  extraResources: string[];
64
65
  providerNamespaces: string[];
65
66
  providerLifecycle: MinikubeProviderLifecycle[];
@@ -68,6 +69,7 @@ export function generateMinikubeBaseArtifacts(args: {
68
69
  const {
69
70
  manifest,
70
71
  appSlug,
72
+ appRuntimeEnabled,
71
73
  extraResources,
72
74
  providerNamespaces,
73
75
  providerLifecycle,
@@ -109,6 +111,7 @@ export function generateMinikubeBaseArtifacts(args: {
109
111
  path: `${root}/README.md`,
110
112
  content: getReadmeMarkdown({
111
113
  appSlug,
114
+ appRuntimeEnabled,
112
115
  defaultAppImage,
113
116
  monitoringEnabled,
114
117
  authProvider,
@@ -150,32 +153,37 @@ export function generateMinikubeBaseArtifacts(args: {
150
153
  path: `${k8sRoot}/namespaces/${namespace}.yaml`,
151
154
  content: getNamespaceManifest(namespace),
152
155
  })),
153
- {
154
- path: `${k8sRoot}/app.configmap.yaml`,
155
- content: getAppConfigMap({
156
- appSlug,
157
- authScope,
158
- authProvider,
159
- databaseProvider,
160
- secretStoreProvider,
161
- storageMetadata,
162
- monitoringEnabled,
163
- domain,
164
- }),
165
- },
166
- {
167
- path: `${k8sRoot}/app/deployment.yaml`,
168
- content: getAppDeploymentManifest(defaultAppImage),
169
- },
170
- {
171
- path: `${k8sRoot}/app/service.yaml`,
172
- content: getAppServiceManifest(),
173
- },
156
+ ...(appRuntimeEnabled
157
+ ? [
158
+ {
159
+ path: `${k8sRoot}/app.configmap.yaml`,
160
+ content: getAppConfigMap({
161
+ appSlug,
162
+ authScope,
163
+ authProvider,
164
+ databaseProvider,
165
+ secretStoreProvider,
166
+ storageMetadata,
167
+ monitoringEnabled,
168
+ domain,
169
+ }),
170
+ },
171
+ {
172
+ path: `${k8sRoot}/app/deployment.yaml`,
173
+ content: getAppDeploymentManifest(defaultAppImage),
174
+ },
175
+ {
176
+ path: `${k8sRoot}/app/service.yaml`,
177
+ content: getAppServiceManifest(),
178
+ },
179
+ ]
180
+ : []),
174
181
  ...supabaseResources,
175
182
  {
176
183
  path: `${k8sRoot}/kustomization.yaml`,
177
184
  content: getKustomizationManifest({
178
185
  namespaceResources,
186
+ appRuntimeEnabled,
179
187
  supabaseEnabled: supabaseKubernetesEnabled,
180
188
  extraResources,
181
189
  }),
@@ -184,6 +192,7 @@ export function generateMinikubeBaseArtifacts(args: {
184
192
  path: `${scriptsRoot}/up.sh`,
185
193
  content: getUpScript({
186
194
  appSlug,
195
+ appRuntimeEnabled,
187
196
  defaultAppImage,
188
197
  supabaseKubernetesEnabled,
189
198
  profileModel,
@@ -203,6 +212,7 @@ export function generateMinikubeBaseArtifacts(args: {
203
212
  path: `${scriptsRoot}/port-forward.sh`,
204
213
  content: getPortForwardScript({
205
214
  appSlug,
215
+ appRuntimeEnabled,
206
216
  supabaseKubernetesEnabled,
207
217
  supabaseHostPorts,
208
218
  }),
@@ -231,6 +241,7 @@ export function generateMinikubeBaseArtifacts(args: {
231
241
  path: `${scriptsRoot}/status.sh`,
232
242
  content: getStatusScript({
233
243
  appSlug,
244
+ appRuntimeEnabled,
234
245
  supabaseKubernetesEnabled,
235
246
  profileModel,
236
247
  supabaseHostPorts,
@@ -248,6 +259,7 @@ export function generateMinikubeBaseArtifacts(args: {
248
259
 
249
260
  function getReadmeMarkdown(args: {
250
261
  appSlug: string;
262
+ appRuntimeEnabled: boolean;
251
263
  defaultAppImage: string;
252
264
  monitoringEnabled: boolean;
253
265
  authProvider: string;
@@ -262,6 +274,7 @@ function getReadmeMarkdown(args: {
262
274
  }): string {
263
275
  const {
264
276
  appSlug,
277
+ appRuntimeEnabled,
265
278
  defaultAppImage,
266
279
  monitoringEnabled,
267
280
  authProvider,
@@ -278,9 +291,7 @@ function getReadmeMarkdown(args: {
278
291
  'namespaces/app.yaml',
279
292
  ...(supabaseKubernetesEnabled ? ['namespaces/supabase.yaml'] : []),
280
293
  ...providerNamespaces.map((namespace) => `namespaces/${namespace}.yaml`),
281
- 'app.configmap.yaml',
282
- 'app/deployment.yaml',
283
- 'app/service.yaml',
294
+ ...(appRuntimeEnabled ? ['app.configmap.yaml', 'app/deployment.yaml', 'app/service.yaml'] : []),
284
295
  ...(supabaseKubernetesEnabled
285
296
  ? [
286
297
  'supabase/bootstrap.sql',
@@ -343,6 +354,14 @@ function getReadmeMarkdown(args: {
343
354
  ? `\nConfigured OAuth providers are wired into GoTrue through generated \`GOTRUE_EXTERNAL_*\` runtime keys. The keys are named in \`.env.example\`; values must come from trusted secret resolution for the configured \`credentialsRef\` entries.\n`
344
355
  : '';
345
356
 
357
+ const runtimeForwardDescription = appRuntimeEnabled
358
+ ? supabaseKubernetesEnabled
359
+ ? '`app` and `supabase-gateway`'
360
+ : '`app`'
361
+ : supabaseKubernetesEnabled
362
+ ? '`supabase-gateway`'
363
+ : 'no host endpoints';
364
+
346
365
  return `# Minikube Infra
347
366
 
348
367
  This directory is generated from \`ankh.config.json\` (infra manifest).
@@ -350,7 +369,7 @@ This directory is generated from \`ankh.config.json\` (infra manifest).
350
369
  ## Ownership Model
351
370
 
352
371
  - App slug / Minikube profile: \`${appSlug}\`
353
- - App runtime namespace: \`${APP_NAMESPACE}\`
372
+ - App runtime: \`${appRuntimeEnabled ? `deployment/app-runtime in namespace ${APP_NAMESPACE}` : 'disabled (no enabled Web deploy target)'}\`
354
373
  - Supabase namespace: \`${supabaseKubernetesEnabled ? SUPABASE_NAMESPACE : 'unused'}\`
355
374
  ${providerNamespaceLine}- Default app image: \`${defaultAppImage}\`
356
375
 
@@ -380,16 +399,14 @@ host-level Supabase Compose runtime.
380
399
 
381
400
  ## Lifecycle Semantics
382
401
 
383
- - \`up.sh\`: starts \`minikube -p ${appSlug}\`, deploys provider namespaces, runs migrations with CLI telemetry disabled for that command, starts slug-owned port-forwards, and deploys the app runtime.
402
+ - \`up.sh\`: starts \`minikube -p ${appSlug}\`, deploys provider namespaces, runs migrations with CLI telemetry disabled for that command, starts slug-owned port-forwards${appRuntimeEnabled ? ', and deploys the Web app runtime' : ''}.
384
403
  - \`down.sh\`: stops slug-owned port-forwards, then stops \`minikube -p ${appSlug}\`. Persistent data remains in the profile.
385
404
  - \`reset.sh\`: requires \`ANKH_RESET_CONFIRM=${appSlug}\`; ${resetDescription}. It does not delete the Minikube profile.
386
405
  - \`destroy.sh\`: deletes only \`minikube -p ${appSlug}\`.
387
406
 
388
407
  ### Port-forward groups
389
408
 
390
- - \`./scripts/port-forward.sh start runtime\` idempotently ensures ${
391
- supabaseKubernetesEnabled ? '`app` and `supabase-gateway`' : '`app` only'
392
- }, the host endpoints required by the running application. The same group supports \`stop\` and \`status\`.
409
+ - \`./scripts/port-forward.sh start runtime\` idempotently ensures ${runtimeForwardDescription}, the generated host endpoints required by the running application. The same group supports \`stop\` and \`status\`.
393
410
  - \`runtime\` excludes \`studio\` and \`db-migration\`, which are operational/bootstrap endpoints.
394
411
  - \`all\` retains every generated forward, including operational/bootstrap endpoints.
395
412
  - Concrete targets remain available for individual lifecycle control.
@@ -428,8 +445,7 @@ Pinned images:
428
445
 
429
446
  ## Host URLs
430
447
 
431
- - App: \`http://127.0.0.1:${supabaseHostPorts.app}\`
432
- - Supabase gateway: \`http://127.0.0.1:${supabaseHostPorts.gateway}\`
448
+ ${appRuntimeEnabled ? `- App: \`http://127.0.0.1:${supabaseHostPorts.app}\`\n` : ''}- Supabase gateway: \`http://127.0.0.1:${supabaseHostPorts.gateway}\`
433
449
  - Studio: \`http://127.0.0.1:${supabaseHostPorts.studio}\`
434
450
  - DB migration endpoint: \`127.0.0.1:${supabaseHostPorts.db}\`
435
451
 
@@ -965,6 +981,7 @@ function normalizeBuckets(rawBuckets: readonly string[]): string[] {
965
981
 
966
982
  function getKustomizationManifest(args: {
967
983
  namespaceResources: string[];
984
+ appRuntimeEnabled: boolean;
968
985
  supabaseEnabled: boolean;
969
986
  extraResources: string[];
970
987
  }): string {
@@ -985,9 +1002,9 @@ function getKustomizationManifest(args: {
985
1002
  : [];
986
1003
  const resources = [
987
1004
  ...args.namespaceResources,
988
- 'app.configmap.yaml',
989
- 'app/deployment.yaml',
990
- 'app/service.yaml',
1005
+ ...(args.appRuntimeEnabled
1006
+ ? ['app.configmap.yaml', 'app/deployment.yaml', 'app/service.yaml']
1007
+ : []),
991
1008
  ...supabaseResources,
992
1009
  ...args.extraResources,
993
1010
  ];
@@ -2261,6 +2278,7 @@ spec:
2261
2278
 
2262
2279
  function getUpScript(args: {
2263
2280
  appSlug: string;
2281
+ appRuntimeEnabled: boolean;
2264
2282
  defaultAppImage: string;
2265
2283
  supabaseKubernetesEnabled: boolean;
2266
2284
  profileModel: ResolvedProfileModel;
@@ -2271,6 +2289,7 @@ function getUpScript(args: {
2271
2289
  }): string {
2272
2290
  const {
2273
2291
  appSlug,
2292
+ appRuntimeEnabled,
2274
2293
  defaultAppImage,
2275
2294
  supabaseKubernetesEnabled,
2276
2295
  profileModel,
@@ -2290,6 +2309,14 @@ function getUpScript(args: {
2290
2309
  secretStoreProvider,
2291
2310
  });
2292
2311
  const supabaseMigrationCommand = getSupabaseMigrationCommandScript();
2312
+ const disabledAppRuntimeReconciliation = appRuntimeEnabled
2313
+ ? ''
2314
+ : `reconcile_disabled_app_runtime() {
2315
+ kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" delete deployment/app-runtime service/app-runtime configmap/app-infra-config --ignore-not-found
2316
+ "\${PORT_FORWARD_SCRIPT}" stop obsolete
2317
+ }
2318
+ `;
2319
+ const reconcileDisabledAppRuntime = appRuntimeEnabled ? '' : 'reconcile_disabled_app_runtime';
2293
2320
 
2294
2321
  return `#!/usr/bin/env bash
2295
2322
  set -Eeuo pipefail
@@ -2305,6 +2332,7 @@ DRIVER="\${MINIKUBE_DRIVER:-}"
2305
2332
  APP_NAMESPACE="${APP_NAMESPACE}"
2306
2333
  SUPABASE_NAMESPACE="${SUPABASE_NAMESPACE}"
2307
2334
  SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
2335
+ APP_RUNTIME_ENABLED="${appRuntimeEnabled ? 'true' : 'false'}"
2308
2336
  APP_BUILD_ENABLED="\${APP_BUILD_ENABLED:-}"
2309
2337
  APP_SOURCE_DIR="\${APP_SOURCE_DIR:-}"
2310
2338
  APP_WEB_EXPORT_DIR="\${APP_WEB_EXPORT_DIR:-}"
@@ -2735,6 +2763,7 @@ apply_namespace_manifests() {
2735
2763
  done
2736
2764
  }
2737
2765
 
2766
+ ${disabledAppRuntimeReconciliation}
2738
2767
  ${providerLifecycleStageScripts}
2739
2768
 
2740
2769
  load_env_file_preserving_process_env
@@ -2808,7 +2837,7 @@ else
2808
2837
  kubectl --context "\${PROFILE}" apply -k "\${K8S_DIR}"
2809
2838
  fi
2810
2839
 
2811
- if [[ "\${APP_BUILD_ENABLED}" == "true" ]]; then
2840
+ if [[ "\${APP_RUNTIME_ENABLED}" == "true" && "\${APP_BUILD_ENABLED}" == "true" ]]; then
2812
2841
  if [[ ! -x "\${BUILD_SCRIPT}" ]]; then
2813
2842
  echo "Missing build helper script: \${BUILD_SCRIPT}"
2814
2843
  exit 1
@@ -2823,6 +2852,7 @@ else
2823
2852
  EXPORT_DIR="\${APP_SOURCE_DIR}/\${APP_WEB_EXPORT_DIR}"
2824
2853
  fi
2825
2854
 
2855
+ if [[ "\${APP_RUNTIME_ENABLED}" == "true" ]]; then
2826
2856
  case "\${APP_IMAGE_SYNC_STRATEGY}" in
2827
2857
  minikube-build)
2828
2858
  if [[ ! -f "\${EXPORT_DIR}/index.html" ]]; then
@@ -2844,15 +2874,18 @@ case "\${APP_IMAGE_SYNC_STRATEGY}" in
2844
2874
  exit 1
2845
2875
  ;;
2846
2876
  esac
2877
+ fi
2847
2878
 
2848
2879
  if [[ "\${SUPABASE_RUNTIME_ENABLED}" == "true" ]]; then
2849
2880
  kubectl --context "\${PROFILE}" apply -k "\${K8S_DIR}"
2850
2881
  fi
2851
2882
 
2883
+ ${reconcileDisabledAppRuntime}
2852
2884
  wait_for_provider_readiness
2853
2885
  run_provider_migrations
2854
2886
  run_provider_reconciliation
2855
2887
 
2888
+ if [[ "\${APP_RUNTIME_ENABLED}" == "true" ]]; then
2856
2889
  if [[ -n "\${APP_IMAGE_PULL_SECRET_NAME}" ]]; then
2857
2890
  if [[ -z "\${APP_IMAGE_PULL_SECRET_USERNAME}" || -z "\${APP_IMAGE_PULL_SECRET_PASSWORD}" ]]; then
2858
2891
  echo "APP_IMAGE_PULL_SECRET_NAME is set, but username/password are missing."
@@ -2884,6 +2917,7 @@ fi
2884
2917
 
2885
2918
  kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" rollout status deployment/app-runtime --timeout=180s >/dev/null
2886
2919
  "\${PORT_FORWARD_SCRIPT}" start app >/dev/null
2920
+ fi
2887
2921
 
2888
2922
  trap - ERR
2889
2923
  echo "Minikube infrastructure for '\${PROFILE}' is running."
@@ -2997,15 +3031,44 @@ CMD ["nginx", "-g", "daemon off;"]
2997
3031
 
2998
3032
  function getPortForwardScript(args: {
2999
3033
  appSlug: string;
3034
+ appRuntimeEnabled: boolean;
3000
3035
  supabaseKubernetesEnabled: boolean;
3001
3036
  supabaseHostPorts: SupabaseHostPorts;
3002
3037
  }): string {
3003
- const { appSlug, supabaseKubernetesEnabled, supabaseHostPorts } = args;
3004
- const runtimeForwardNames = ['app', ...(supabaseKubernetesEnabled ? ['supabase-gateway'] : [])];
3038
+ const { appSlug, appRuntimeEnabled, supabaseKubernetesEnabled, supabaseHostPorts } = args;
3039
+ const runtimeForwardNames = [
3040
+ ...(appRuntimeEnabled ? ['app'] : []),
3041
+ ...(supabaseKubernetesEnabled ? ['supabase-gateway'] : []),
3042
+ ];
3005
3043
  const allForwardNames = [
3006
3044
  ...runtimeForwardNames,
3007
3045
  ...(supabaseKubernetesEnabled ? ['studio', 'db-migration'] : []),
3008
3046
  ];
3047
+ const managedForwardNames = ['app', 'supabase-gateway', 'studio', 'db-migration'];
3048
+ const obsoleteForwardNames = managedForwardNames.filter(
3049
+ (name) => !allForwardNames.includes(name),
3050
+ );
3051
+ const appTargetCase = ` app)
3052
+ echo "${APP_NAMESPACE} service/app-runtime \${APP_PORT_FORWARD_LOCAL_PORT} \${APP_PORT_FORWARD_REMOTE_PORT}"
3053
+ ;;`;
3054
+ const supabaseTargetCases = ` supabase-gateway)
3055
+ echo "${SUPABASE_NAMESPACE} service/gateway \${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT} \${SUPABASE_GATEWAY_FORWARD_REMOTE_PORT}"
3056
+ ;;
3057
+ studio)
3058
+ echo "${SUPABASE_NAMESPACE} service/studio \${SUPABASE_STUDIO_FORWARD_LOCAL_PORT} \${SUPABASE_STUDIO_FORWARD_REMOTE_PORT}"
3059
+ ;;
3060
+ db-migration)
3061
+ echo "${SUPABASE_NAMESPACE} service/postgres \${SUPABASE_DB_FORWARD_LOCAL_PORT} \${SUPABASE_DB_FORWARD_REMOTE_PORT}"
3062
+ ;;`;
3063
+ const concreteForwardNames = [
3064
+ ...(appRuntimeEnabled ? ['app'] : []),
3065
+ ...(supabaseKubernetesEnabled ? ['supabase-gateway', 'studio', 'db-migration'] : []),
3066
+ ];
3067
+ const forwardUsage = [...concreteForwardNames, 'runtime', 'all'].join('|');
3068
+ const forwardNamesDescription =
3069
+ concreteForwardNames.length > 0
3070
+ ? `${concreteForwardNames.join(', ')}, runtime, or all`
3071
+ : 'runtime or all';
3009
3072
 
3010
3073
  return `#!/usr/bin/env bash
3011
3074
  set -euo pipefail
@@ -3016,6 +3079,7 @@ STATE_DIR="\${ROOT_DIR}/.state/forwards"
3016
3079
  APP_SLUG="${appSlug}"
3017
3080
  PROFILE="\${ANKH_APP_SLUG:-${appSlug}}"
3018
3081
  SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
3082
+ APP_RUNTIME_ENABLED="${appRuntimeEnabled ? 'true' : 'false'}"
3019
3083
  APP_PORT_FORWARD_LOCAL_PORT="\${APP_PORT_FORWARD_LOCAL_PORT:-${supabaseHostPorts.app}}"
3020
3084
  APP_PORT_FORWARD_REMOTE_PORT="\${APP_PORT_FORWARD_REMOTE_PORT:-80}"
3021
3085
  SUPABASE_GATEWAY_FORWARD_LOCAL_PORT="\${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT:-${supabaseHostPorts.gateway}}"
@@ -3026,6 +3090,8 @@ SUPABASE_DB_FORWARD_LOCAL_PORT="\${SUPABASE_DB_FORWARD_LOCAL_PORT:-${supabaseHos
3026
3090
  SUPABASE_DB_FORWARD_REMOTE_PORT="\${SUPABASE_DB_FORWARD_REMOTE_PORT:-5432}"
3027
3091
  RUNTIME_FORWARD_NAMES=(${runtimeForwardNames.join(' ')})
3028
3092
  ALL_FORWARD_NAMES=(${allForwardNames.join(' ')})
3093
+ MANAGED_FORWARD_NAMES=(${managedForwardNames.join(' ')})
3094
+ OBSOLETE_FORWARD_NAMES=(${obsoleteForwardNames.join(' ')})
3029
3095
 
3030
3096
  if [[ -f "\${ROOT_DIR}/.env" ]]; then
3031
3097
  set -a
@@ -3034,6 +3100,7 @@ if [[ -f "\${ROOT_DIR}/.env" ]]; then
3034
3100
  set +a
3035
3101
  fi
3036
3102
  SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
3103
+ APP_RUNTIME_ENABLED="${appRuntimeEnabled ? 'true' : 'false'}"
3037
3104
 
3038
3105
  if [[ "\${PROFILE}" != "\${APP_SLUG}" ]]; then
3039
3106
  echo "ANKH_APP_SLUG must remain the generated canonical slug '\${APP_SLUG}' for this infra directory."
@@ -3053,20 +3120,10 @@ pid_file_for() {
3053
3120
 
3054
3121
  target_for() {
3055
3122
  case "\${1}" in
3056
- app)
3057
- echo "${APP_NAMESPACE} service/app-runtime \${APP_PORT_FORWARD_LOCAL_PORT} \${APP_PORT_FORWARD_REMOTE_PORT}"
3058
- ;;
3059
- supabase-gateway)
3060
- echo "${SUPABASE_NAMESPACE} service/gateway \${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT} \${SUPABASE_GATEWAY_FORWARD_REMOTE_PORT}"
3061
- ;;
3062
- studio)
3063
- echo "${SUPABASE_NAMESPACE} service/studio \${SUPABASE_STUDIO_FORWARD_LOCAL_PORT} \${SUPABASE_STUDIO_FORWARD_REMOTE_PORT}"
3064
- ;;
3065
- db-migration)
3066
- echo "${SUPABASE_NAMESPACE} service/postgres \${SUPABASE_DB_FORWARD_LOCAL_PORT} \${SUPABASE_DB_FORWARD_REMOTE_PORT}"
3067
- ;;
3123
+ ${appTargetCase}
3124
+ ${supabaseTargetCases}
3068
3125
  *)
3069
- echo "Unknown forward '\${1}'. Use app, supabase-gateway, studio, db-migration, or all." >&2
3126
+ echo "Unknown forward '\${1}'. Use ${forwardNamesDescription}." >&2
3070
3127
  return 1
3071
3128
  ;;
3072
3129
  esac
@@ -3202,6 +3259,11 @@ start_forward() {
3202
3259
  pid_file="$(pid_file_for "\${name}")"
3203
3260
  read -r namespace resource local_port remote_port <<<"$(target_for "\${name}")"
3204
3261
 
3262
+ if [[ "\${name}" == "app" && "\${APP_RUNTIME_ENABLED}" != "true" ]]; then
3263
+ echo "\${name}: skipped (Web app runtime disabled)"
3264
+ return 0
3265
+ fi
3266
+
3205
3267
  if [[ -f "\${pid_file}" ]]; then
3206
3268
  local existing_pid
3207
3269
  existing_pid="$(cat "\${pid_file}")"
@@ -3231,28 +3293,52 @@ start_forward() {
3231
3293
  return 1
3232
3294
  fi
3233
3295
 
3234
- nohup kubectl --context "\${PROFILE}" -n "\${namespace}" port-forward "\${resource}" "\${local_port}:\${remote_port}" >"\${STATE_DIR}/\${PROFILE}-\${name}.log" 2>&1 &
3235
- local pid="$!"
3236
- echo "\${pid}" > "\${pid_file}"
3296
+ for start_attempt in {1..5}; do
3297
+ nohup kubectl --context "\${PROFILE}" -n "\${namespace}" port-forward "\${resource}" "\${local_port}:\${remote_port}" >"\${STATE_DIR}/\${PROFILE}-\${name}.log" 2>&1 &
3298
+ local pid="$!"
3299
+ echo "\${pid}" > "\${pid_file}"
3237
3300
 
3238
- for attempt in {1..40}; do
3239
- if ! is_pid_running "\${pid}"; then
3240
- echo "\${name}: failed to start local port \${local_port} for \${namespace}/\${resource}:\${remote_port}"
3241
- print_forward_log_tail "\${name}"
3301
+ for readiness_attempt in {1..20}; do
3302
+ if ! is_pid_running "\${pid}"; then
3303
+ rm -f "\${pid_file}"
3304
+ if [[ "\${start_attempt}" -lt 5 ]]; then
3305
+ echo "\${name}: port-forward attempt \${start_attempt} exited before readiness; retrying"
3306
+ sleep 2
3307
+ break
3308
+ fi
3309
+
3310
+ echo "\${name}: failed to start local port \${local_port} for \${namespace}/\${resource}:\${remote_port} after \${start_attempt} attempts"
3311
+ print_forward_log_tail "\${name}"
3312
+ return 1
3313
+ fi
3314
+ if is_port_accepting "\${local_port}"; then
3315
+ sleep 1
3316
+ if is_pid_running "\${pid}" && is_port_accepting "\${local_port}"; then
3317
+ sleep 0.5
3318
+ if is_pid_running "\${pid}"; then
3319
+ echo "\${name}: started (pid \${pid}, local port \${local_port})"
3320
+ return 0
3321
+ fi
3322
+ fi
3323
+ fi
3324
+ sleep 0.5
3325
+ done
3326
+
3327
+ if [[ -f "\${pid_file}" ]]; then
3328
+ terminate_forward_process "\${name}" "\${pid}" "\${namespace}" "\${resource}" "\${local_port}" "\${remote_port}" || return 1
3242
3329
  rm -f "\${pid_file}"
3243
- return 1
3244
- fi
3245
- if is_port_accepting "\${local_port}"; then
3246
- echo "\${name}: started (pid \${pid}, local port \${local_port})"
3247
- return 0
3330
+
3331
+ if [[ "\${start_attempt}" -lt 5 ]]; then
3332
+ echo "\${name}: port-forward attempt \${start_attempt} did not become ready; retrying"
3333
+ sleep 2
3334
+ else
3335
+ echo "\${name}: did not become ready on local port \${local_port} for \${namespace}/\${resource}:\${remote_port} after \${start_attempt} attempts"
3336
+ print_forward_log_tail "\${name}"
3337
+ return 1
3338
+ fi
3248
3339
  fi
3249
- sleep 0.5
3250
3340
  done
3251
3341
 
3252
- echo "\${name}: did not become ready on local port \${local_port} for \${namespace}/\${resource}:\${remote_port}"
3253
- print_forward_log_tail "\${name}"
3254
- terminate_forward_process "\${name}" "\${pid}" "\${namespace}" "\${resource}" "\${local_port}" "\${remote_port}" || return 1
3255
- rm -f "\${pid_file}"
3256
3342
  return 1
3257
3343
  }
3258
3344
 
@@ -3313,7 +3399,9 @@ for_each_forward() {
3313
3399
  local action="\${1}"
3314
3400
  shift
3315
3401
  local name
3316
- for name in "\${@}"; do
3402
+ while [[ "\${#}" -gt 0 ]]; do
3403
+ name="\${1}"
3404
+ shift
3317
3405
  "\${action}_forward" "\${name}"
3318
3406
  done
3319
3407
  }
@@ -3322,17 +3410,21 @@ ACTION="\${1:-start}"
3322
3410
  NAME="\${2:-all}"
3323
3411
 
3324
3412
  case "\${ACTION}:\${NAME}" in
3325
- start:runtime) for_each_forward start "\${RUNTIME_FORWARD_NAMES[@]}" ;;
3326
- stop:runtime) for_each_forward stop "\${RUNTIME_FORWARD_NAMES[@]}" ;;
3327
- status:runtime) for_each_forward status "\${RUNTIME_FORWARD_NAMES[@]}" ;;
3328
- start:all) for_each_forward start "\${ALL_FORWARD_NAMES[@]}" ;;
3329
- stop:all) for_each_forward stop "\${ALL_FORWARD_NAMES[@]}" ;;
3330
- status:all) for_each_forward status "\${ALL_FORWARD_NAMES[@]}" ;;
3413
+ start:runtime) for_each_forward start ${runtimeForwardNames.join(' ')} ;;
3414
+ stop:runtime)
3415
+ for_each_forward stop ${runtimeForwardNames.join(' ')}
3416
+ for_each_forward stop ${obsoleteForwardNames.join(' ')}
3417
+ ;;
3418
+ status:runtime) for_each_forward status ${runtimeForwardNames.join(' ')} ;;
3419
+ start:all) for_each_forward start ${allForwardNames.join(' ')} ;;
3420
+ stop:all) for_each_forward stop ${managedForwardNames.join(' ')} ;;
3421
+ status:all) for_each_forward status ${allForwardNames.join(' ')} ;;
3422
+ stop:obsolete) for_each_forward stop ${obsoleteForwardNames.join(' ')} ;;
3331
3423
  start:*) start_forward "\${NAME}" ;;
3332
3424
  stop:*) stop_forward "\${NAME}" ;;
3333
3425
  status:*) status_forward "\${NAME}" ;;
3334
3426
  *)
3335
- echo "Usage: ./scripts/port-forward.sh {start|stop|status} {app|supabase-gateway|studio|db-migration|runtime|all}"
3427
+ echo "Usage: ./scripts/port-forward.sh {start|stop|status} {${forwardUsage}}"
3336
3428
  exit 1
3337
3429
  ;;
3338
3430
  esac
@@ -3466,6 +3558,7 @@ echo "Deleted Minikube profile '\${PROFILE}'."
3466
3558
 
3467
3559
  function getStatusScript(args: {
3468
3560
  appSlug: string;
3561
+ appRuntimeEnabled: boolean;
3469
3562
  supabaseKubernetesEnabled: boolean;
3470
3563
  profileModel: ResolvedProfileModel;
3471
3564
  supabaseHostPorts: SupabaseHostPorts;
@@ -3474,6 +3567,7 @@ function getStatusScript(args: {
3474
3567
  }): string {
3475
3568
  const {
3476
3569
  appSlug,
3570
+ appRuntimeEnabled,
3477
3571
  supabaseKubernetesEnabled,
3478
3572
  profileModel,
3479
3573
  supabaseHostPorts,
@@ -3558,7 +3652,7 @@ for namespace in ${namespaces}; do
3558
3652
  fi
3559
3653
  done
3560
3654
 
3561
- if kubectl --context "\${PROFILE}" -n ${APP_NAMESPACE} get deployment app-runtime >/dev/null 2>&1; then
3655
+ if [[ "${appRuntimeEnabled ? 'true' : 'false'}" == "true" ]] && kubectl --context "\${PROFILE}" -n ${APP_NAMESPACE} get deployment app-runtime >/dev/null 2>&1; then
3562
3656
  kubectl --context "\${PROFILE}" -n ${APP_NAMESPACE} rollout status deployment/app-runtime --timeout=5s >/dev/null 2>&1 && echo "- app-runtime: ready" || echo "- app-runtime: not ready"
3563
3657
  fi
3564
3658