@ankhorage/infra 4.0.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,11 +399,18 @@ 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
 
407
+ ### Port-forward groups
408
+
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\`.
410
+ - \`runtime\` excludes \`studio\` and \`db-migration\`, which are operational/bootstrap endpoints.
411
+ - \`all\` retains every generated forward, including operational/bootstrap endpoints.
412
+ - Concrete targets remain available for individual lifecycle control.
413
+
388
414
  ## Generated Resources
389
415
 
390
416
  ${resourceLines}
@@ -419,8 +445,7 @@ Pinned images:
419
445
 
420
446
  ## Host URLs
421
447
 
422
- - App: \`http://127.0.0.1:${supabaseHostPorts.app}\`
423
- - 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}\`
424
449
  - Studio: \`http://127.0.0.1:${supabaseHostPorts.studio}\`
425
450
  - DB migration endpoint: \`127.0.0.1:${supabaseHostPorts.db}\`
426
451
 
@@ -956,6 +981,7 @@ function normalizeBuckets(rawBuckets: readonly string[]): string[] {
956
981
 
957
982
  function getKustomizationManifest(args: {
958
983
  namespaceResources: string[];
984
+ appRuntimeEnabled: boolean;
959
985
  supabaseEnabled: boolean;
960
986
  extraResources: string[];
961
987
  }): string {
@@ -976,9 +1002,9 @@ function getKustomizationManifest(args: {
976
1002
  : [];
977
1003
  const resources = [
978
1004
  ...args.namespaceResources,
979
- 'app.configmap.yaml',
980
- 'app/deployment.yaml',
981
- 'app/service.yaml',
1005
+ ...(args.appRuntimeEnabled
1006
+ ? ['app.configmap.yaml', 'app/deployment.yaml', 'app/service.yaml']
1007
+ : []),
982
1008
  ...supabaseResources,
983
1009
  ...args.extraResources,
984
1010
  ];
@@ -2252,6 +2278,7 @@ spec:
2252
2278
 
2253
2279
  function getUpScript(args: {
2254
2280
  appSlug: string;
2281
+ appRuntimeEnabled: boolean;
2255
2282
  defaultAppImage: string;
2256
2283
  supabaseKubernetesEnabled: boolean;
2257
2284
  profileModel: ResolvedProfileModel;
@@ -2262,6 +2289,7 @@ function getUpScript(args: {
2262
2289
  }): string {
2263
2290
  const {
2264
2291
  appSlug,
2292
+ appRuntimeEnabled,
2265
2293
  defaultAppImage,
2266
2294
  supabaseKubernetesEnabled,
2267
2295
  profileModel,
@@ -2281,6 +2309,14 @@ function getUpScript(args: {
2281
2309
  secretStoreProvider,
2282
2310
  });
2283
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';
2284
2320
 
2285
2321
  return `#!/usr/bin/env bash
2286
2322
  set -Eeuo pipefail
@@ -2296,6 +2332,7 @@ DRIVER="\${MINIKUBE_DRIVER:-}"
2296
2332
  APP_NAMESPACE="${APP_NAMESPACE}"
2297
2333
  SUPABASE_NAMESPACE="${SUPABASE_NAMESPACE}"
2298
2334
  SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
2335
+ APP_RUNTIME_ENABLED="${appRuntimeEnabled ? 'true' : 'false'}"
2299
2336
  APP_BUILD_ENABLED="\${APP_BUILD_ENABLED:-}"
2300
2337
  APP_SOURCE_DIR="\${APP_SOURCE_DIR:-}"
2301
2338
  APP_WEB_EXPORT_DIR="\${APP_WEB_EXPORT_DIR:-}"
@@ -2726,6 +2763,7 @@ apply_namespace_manifests() {
2726
2763
  done
2727
2764
  }
2728
2765
 
2766
+ ${disabledAppRuntimeReconciliation}
2729
2767
  ${providerLifecycleStageScripts}
2730
2768
 
2731
2769
  load_env_file_preserving_process_env
@@ -2799,7 +2837,7 @@ else
2799
2837
  kubectl --context "\${PROFILE}" apply -k "\${K8S_DIR}"
2800
2838
  fi
2801
2839
 
2802
- if [[ "\${APP_BUILD_ENABLED}" == "true" ]]; then
2840
+ if [[ "\${APP_RUNTIME_ENABLED}" == "true" && "\${APP_BUILD_ENABLED}" == "true" ]]; then
2803
2841
  if [[ ! -x "\${BUILD_SCRIPT}" ]]; then
2804
2842
  echo "Missing build helper script: \${BUILD_SCRIPT}"
2805
2843
  exit 1
@@ -2814,6 +2852,7 @@ else
2814
2852
  EXPORT_DIR="\${APP_SOURCE_DIR}/\${APP_WEB_EXPORT_DIR}"
2815
2853
  fi
2816
2854
 
2855
+ if [[ "\${APP_RUNTIME_ENABLED}" == "true" ]]; then
2817
2856
  case "\${APP_IMAGE_SYNC_STRATEGY}" in
2818
2857
  minikube-build)
2819
2858
  if [[ ! -f "\${EXPORT_DIR}/index.html" ]]; then
@@ -2835,15 +2874,18 @@ case "\${APP_IMAGE_SYNC_STRATEGY}" in
2835
2874
  exit 1
2836
2875
  ;;
2837
2876
  esac
2877
+ fi
2838
2878
 
2839
2879
  if [[ "\${SUPABASE_RUNTIME_ENABLED}" == "true" ]]; then
2840
2880
  kubectl --context "\${PROFILE}" apply -k "\${K8S_DIR}"
2841
2881
  fi
2842
2882
 
2883
+ ${reconcileDisabledAppRuntime}
2843
2884
  wait_for_provider_readiness
2844
2885
  run_provider_migrations
2845
2886
  run_provider_reconciliation
2846
2887
 
2888
+ if [[ "\${APP_RUNTIME_ENABLED}" == "true" ]]; then
2847
2889
  if [[ -n "\${APP_IMAGE_PULL_SECRET_NAME}" ]]; then
2848
2890
  if [[ -z "\${APP_IMAGE_PULL_SECRET_USERNAME}" || -z "\${APP_IMAGE_PULL_SECRET_PASSWORD}" ]]; then
2849
2891
  echo "APP_IMAGE_PULL_SECRET_NAME is set, but username/password are missing."
@@ -2875,6 +2917,7 @@ fi
2875
2917
 
2876
2918
  kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" rollout status deployment/app-runtime --timeout=180s >/dev/null
2877
2919
  "\${PORT_FORWARD_SCRIPT}" start app >/dev/null
2920
+ fi
2878
2921
 
2879
2922
  trap - ERR
2880
2923
  echo "Minikube infrastructure for '\${PROFILE}' is running."
@@ -2988,10 +3031,44 @@ CMD ["nginx", "-g", "daemon off;"]
2988
3031
 
2989
3032
  function getPortForwardScript(args: {
2990
3033
  appSlug: string;
3034
+ appRuntimeEnabled: boolean;
2991
3035
  supabaseKubernetesEnabled: boolean;
2992
3036
  supabaseHostPorts: SupabaseHostPorts;
2993
3037
  }): string {
2994
- const { appSlug, supabaseKubernetesEnabled, supabaseHostPorts } = args;
3038
+ const { appSlug, appRuntimeEnabled, supabaseKubernetesEnabled, supabaseHostPorts } = args;
3039
+ const runtimeForwardNames = [
3040
+ ...(appRuntimeEnabled ? ['app'] : []),
3041
+ ...(supabaseKubernetesEnabled ? ['supabase-gateway'] : []),
3042
+ ];
3043
+ const allForwardNames = [
3044
+ ...runtimeForwardNames,
3045
+ ...(supabaseKubernetesEnabled ? ['studio', 'db-migration'] : []),
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';
2995
3072
 
2996
3073
  return `#!/usr/bin/env bash
2997
3074
  set -euo pipefail
@@ -3002,6 +3079,7 @@ STATE_DIR="\${ROOT_DIR}/.state/forwards"
3002
3079
  APP_SLUG="${appSlug}"
3003
3080
  PROFILE="\${ANKH_APP_SLUG:-${appSlug}}"
3004
3081
  SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
3082
+ APP_RUNTIME_ENABLED="${appRuntimeEnabled ? 'true' : 'false'}"
3005
3083
  APP_PORT_FORWARD_LOCAL_PORT="\${APP_PORT_FORWARD_LOCAL_PORT:-${supabaseHostPorts.app}}"
3006
3084
  APP_PORT_FORWARD_REMOTE_PORT="\${APP_PORT_FORWARD_REMOTE_PORT:-80}"
3007
3085
  SUPABASE_GATEWAY_FORWARD_LOCAL_PORT="\${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT:-${supabaseHostPorts.gateway}}"
@@ -3010,6 +3088,10 @@ SUPABASE_STUDIO_FORWARD_LOCAL_PORT="\${SUPABASE_STUDIO_FORWARD_LOCAL_PORT:-${sup
3010
3088
  SUPABASE_STUDIO_FORWARD_REMOTE_PORT="\${SUPABASE_STUDIO_FORWARD_REMOTE_PORT:-3000}"
3011
3089
  SUPABASE_DB_FORWARD_LOCAL_PORT="\${SUPABASE_DB_FORWARD_LOCAL_PORT:-${supabaseHostPorts.db}}"
3012
3090
  SUPABASE_DB_FORWARD_REMOTE_PORT="\${SUPABASE_DB_FORWARD_REMOTE_PORT:-5432}"
3091
+ RUNTIME_FORWARD_NAMES=(${runtimeForwardNames.join(' ')})
3092
+ ALL_FORWARD_NAMES=(${allForwardNames.join(' ')})
3093
+ MANAGED_FORWARD_NAMES=(${managedForwardNames.join(' ')})
3094
+ OBSOLETE_FORWARD_NAMES=(${obsoleteForwardNames.join(' ')})
3013
3095
 
3014
3096
  if [[ -f "\${ROOT_DIR}/.env" ]]; then
3015
3097
  set -a
@@ -3018,6 +3100,7 @@ if [[ -f "\${ROOT_DIR}/.env" ]]; then
3018
3100
  set +a
3019
3101
  fi
3020
3102
  SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
3103
+ APP_RUNTIME_ENABLED="${appRuntimeEnabled ? 'true' : 'false'}"
3021
3104
 
3022
3105
  if [[ "\${PROFILE}" != "\${APP_SLUG}" ]]; then
3023
3106
  echo "ANKH_APP_SLUG must remain the generated canonical slug '\${APP_SLUG}' for this infra directory."
@@ -3037,20 +3120,10 @@ pid_file_for() {
3037
3120
 
3038
3121
  target_for() {
3039
3122
  case "\${1}" in
3040
- app)
3041
- echo "${APP_NAMESPACE} service/app-runtime \${APP_PORT_FORWARD_LOCAL_PORT} \${APP_PORT_FORWARD_REMOTE_PORT}"
3042
- ;;
3043
- supabase-gateway)
3044
- echo "${SUPABASE_NAMESPACE} service/gateway \${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT} \${SUPABASE_GATEWAY_FORWARD_REMOTE_PORT}"
3045
- ;;
3046
- studio)
3047
- echo "${SUPABASE_NAMESPACE} service/studio \${SUPABASE_STUDIO_FORWARD_LOCAL_PORT} \${SUPABASE_STUDIO_FORWARD_REMOTE_PORT}"
3048
- ;;
3049
- db-migration)
3050
- echo "${SUPABASE_NAMESPACE} service/postgres \${SUPABASE_DB_FORWARD_LOCAL_PORT} \${SUPABASE_DB_FORWARD_REMOTE_PORT}"
3051
- ;;
3123
+ ${appTargetCase}
3124
+ ${supabaseTargetCases}
3052
3125
  *)
3053
- echo "Unknown forward '\${1}'. Use app, supabase-gateway, studio, db-migration, or all." >&2
3126
+ echo "Unknown forward '\${1}'. Use ${forwardNamesDescription}." >&2
3054
3127
  return 1
3055
3128
  ;;
3056
3129
  esac
@@ -3186,6 +3259,11 @@ start_forward() {
3186
3259
  pid_file="$(pid_file_for "\${name}")"
3187
3260
  read -r namespace resource local_port remote_port <<<"$(target_for "\${name}")"
3188
3261
 
3262
+ if [[ "\${name}" == "app" && "\${APP_RUNTIME_ENABLED}" != "true" ]]; then
3263
+ echo "\${name}: skipped (Web app runtime disabled)"
3264
+ return 0
3265
+ fi
3266
+
3189
3267
  if [[ -f "\${pid_file}" ]]; then
3190
3268
  local existing_pid
3191
3269
  existing_pid="$(cat "\${pid_file}")"
@@ -3215,28 +3293,52 @@ start_forward() {
3215
3293
  return 1
3216
3294
  fi
3217
3295
 
3218
- nohup kubectl --context "\${PROFILE}" -n "\${namespace}" port-forward "\${resource}" "\${local_port}:\${remote_port}" >"\${STATE_DIR}/\${PROFILE}-\${name}.log" 2>&1 &
3219
- local pid="$!"
3220
- 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}"
3221
3300
 
3222
- for attempt in {1..40}; do
3223
- if ! is_pid_running "\${pid}"; then
3224
- echo "\${name}: failed to start local port \${local_port} for \${namespace}/\${resource}:\${remote_port}"
3225
- 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
3226
3329
  rm -f "\${pid_file}"
3227
- return 1
3228
- fi
3229
- if is_port_accepting "\${local_port}"; then
3230
- echo "\${name}: started (pid \${pid}, local port \${local_port})"
3231
- 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
3232
3339
  fi
3233
- sleep 0.5
3234
3340
  done
3235
3341
 
3236
- echo "\${name}: did not become ready on local port \${local_port} for \${namespace}/\${resource}:\${remote_port}"
3237
- print_forward_log_tail "\${name}"
3238
- terminate_forward_process "\${name}" "\${pid}" "\${namespace}" "\${resource}" "\${local_port}" "\${remote_port}" || return 1
3239
- rm -f "\${pid_file}"
3240
3342
  return 1
3241
3343
  }
3242
3344
 
@@ -3295,26 +3397,34 @@ status_forward() {
3295
3397
 
3296
3398
  for_each_forward() {
3297
3399
  local action="\${1}"
3298
- "\${action}_forward" app
3299
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" == "true" ]]; then
3300
- "\${action}_forward" supabase-gateway
3301
- "\${action}_forward" studio
3302
- "\${action}_forward" db-migration
3303
- fi
3400
+ shift
3401
+ local name
3402
+ while [[ "\${#}" -gt 0 ]]; do
3403
+ name="\${1}"
3404
+ shift
3405
+ "\${action}_forward" "\${name}"
3406
+ done
3304
3407
  }
3305
3408
 
3306
3409
  ACTION="\${1:-start}"
3307
3410
  NAME="\${2:-all}"
3308
3411
 
3309
3412
  case "\${ACTION}:\${NAME}" in
3310
- start:all) for_each_forward start ;;
3311
- stop:all) for_each_forward stop ;;
3312
- status:all) for_each_forward status ;;
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(' ')} ;;
3313
3423
  start:*) start_forward "\${NAME}" ;;
3314
3424
  stop:*) stop_forward "\${NAME}" ;;
3315
3425
  status:*) status_forward "\${NAME}" ;;
3316
3426
  *)
3317
- echo "Usage: ./scripts/port-forward.sh {start|stop|status} {app|supabase-gateway|studio|db-migration|all}"
3427
+ echo "Usage: ./scripts/port-forward.sh {start|stop|status} {${forwardUsage}}"
3318
3428
  exit 1
3319
3429
  ;;
3320
3430
  esac
@@ -3448,6 +3558,7 @@ echo "Deleted Minikube profile '\${PROFILE}'."
3448
3558
 
3449
3559
  function getStatusScript(args: {
3450
3560
  appSlug: string;
3561
+ appRuntimeEnabled: boolean;
3451
3562
  supabaseKubernetesEnabled: boolean;
3452
3563
  profileModel: ResolvedProfileModel;
3453
3564
  supabaseHostPorts: SupabaseHostPorts;
@@ -3456,6 +3567,7 @@ function getStatusScript(args: {
3456
3567
  }): string {
3457
3568
  const {
3458
3569
  appSlug,
3570
+ appRuntimeEnabled,
3459
3571
  supabaseKubernetesEnabled,
3460
3572
  profileModel,
3461
3573
  supabaseHostPorts,
@@ -3540,7 +3652,7 @@ for namespace in ${namespaces}; do
3540
3652
  fi
3541
3653
  done
3542
3654
 
3543
- 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
3544
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"
3545
3657
  fi
3546
3658