@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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @ankhorage/infra
2
2
 
3
+ ## 4.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 591cecb: Derive Minikube app runtime resources and canonical forward groups from enabled app deployment targets, reconcile obsolete Web runtime resources and owned forwards during native-only upgrades, and retry transient post-restart forwarding failures so native apps can recover provider endpoints without a nonexistent app Service.
8
+
9
+ ## 4.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 66ef9aa: Add a provider-aware runtime port-forward lifecycle and export `ensureProjectInfrastructureRuntime` from `@ankhorage/infra/project`.
14
+
3
15
  ## 4.0.0
4
16
 
5
17
  ### Major Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # INFRA
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v3.3.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v4.0.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
7
7
 
8
8
  Executable infra provider and standalone CLI for Ankhorage project workflows.
9
9
 
@@ -37,6 +37,11 @@ omit it when cwd is already inside `apps/<project>`. Programmatic project
37
37
  operations accept an explicit project path and do not require Studio or a
38
38
  particular workspace layout.
39
39
 
40
+ Running applications can repair their provider-aware host endpoints through
41
+ `ensureProjectInfrastructureRuntime()` from `@ankhorage/infra/project`. This
42
+ operation only ensures the generated runtime port-forward group; it does not
43
+ deploy, bootstrap, or reconcile infrastructure.
44
+
40
45
  Source: `src/readme-usage.ts`
41
46
 
42
47
  ```ts
@@ -5,6 +5,7 @@ export declare const APP_NAMESPACE = "app";
5
5
  export declare function generateMinikubeBaseArtifacts(args: {
6
6
  manifest: InfraManifestInput;
7
7
  appSlug: string;
8
+ appRuntimeEnabled: boolean;
8
9
  extraResources: string[];
9
10
  providerNamespaces: string[];
10
11
  providerLifecycle: MinikubeProviderLifecycle[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapters/minikube/base/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAG9D,eAAO,MAAM,aAAa,QAAQ,CAAC;AAoDnC,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,iBAAiB,EAAE,yBAAyB,EAAE,CAAC;IAC/C,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B,GAAG,2BAA2B,EAAE,CAoLhC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapters/minikube/base/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAG9D,eAAO,MAAM,aAAa,QAAQ,CAAC;AAoDnC,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,iBAAiB,EAAE,yBAAyB,EAAE,CAAC;IAC/C,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B,GAAG,2BAA2B,EAAE,CA8LhC"}
@@ -18,7 +18,7 @@ const PORT_BUCKET_SIZE = 20;
18
18
  const PORT_BUCKET_COUNT = 1000;
19
19
  const PORT_REFERENCE_SLUG = 'my-app';
20
20
  export function generateMinikubeBaseArtifacts(args) {
21
- const { manifest, appSlug, extraResources, providerNamespaces, providerLifecycle, extraEnvEntries, } = args;
21
+ const { manifest, appSlug, appRuntimeEnabled, extraResources, providerNamespaces, providerLifecycle, extraEnvEntries, } = args;
22
22
  const root = 'infra/minikube';
23
23
  const k8sRoot = `${root}/k8s`;
24
24
  const scriptsRoot = `${root}/scripts`;
@@ -50,6 +50,7 @@ export function generateMinikubeBaseArtifacts(args) {
50
50
  path: `${root}/README.md`,
51
51
  content: getReadmeMarkdown({
52
52
  appSlug,
53
+ appRuntimeEnabled,
53
54
  defaultAppImage,
54
55
  monitoringEnabled,
55
56
  authProvider,
@@ -91,32 +92,37 @@ export function generateMinikubeBaseArtifacts(args) {
91
92
  path: `${k8sRoot}/namespaces/${namespace}.yaml`,
92
93
  content: getNamespaceManifest(namespace),
93
94
  })),
94
- {
95
- path: `${k8sRoot}/app.configmap.yaml`,
96
- content: getAppConfigMap({
97
- appSlug,
98
- authScope,
99
- authProvider,
100
- databaseProvider,
101
- secretStoreProvider,
102
- storageMetadata,
103
- monitoringEnabled,
104
- domain,
105
- }),
106
- },
107
- {
108
- path: `${k8sRoot}/app/deployment.yaml`,
109
- content: getAppDeploymentManifest(defaultAppImage),
110
- },
111
- {
112
- path: `${k8sRoot}/app/service.yaml`,
113
- content: getAppServiceManifest(),
114
- },
95
+ ...(appRuntimeEnabled
96
+ ? [
97
+ {
98
+ path: `${k8sRoot}/app.configmap.yaml`,
99
+ content: getAppConfigMap({
100
+ appSlug,
101
+ authScope,
102
+ authProvider,
103
+ databaseProvider,
104
+ secretStoreProvider,
105
+ storageMetadata,
106
+ monitoringEnabled,
107
+ domain,
108
+ }),
109
+ },
110
+ {
111
+ path: `${k8sRoot}/app/deployment.yaml`,
112
+ content: getAppDeploymentManifest(defaultAppImage),
113
+ },
114
+ {
115
+ path: `${k8sRoot}/app/service.yaml`,
116
+ content: getAppServiceManifest(),
117
+ },
118
+ ]
119
+ : []),
115
120
  ...supabaseResources,
116
121
  {
117
122
  path: `${k8sRoot}/kustomization.yaml`,
118
123
  content: getKustomizationManifest({
119
124
  namespaceResources,
125
+ appRuntimeEnabled,
120
126
  supabaseEnabled: supabaseKubernetesEnabled,
121
127
  extraResources,
122
128
  }),
@@ -125,6 +131,7 @@ export function generateMinikubeBaseArtifacts(args) {
125
131
  path: `${scriptsRoot}/up.sh`,
126
132
  content: getUpScript({
127
133
  appSlug,
134
+ appRuntimeEnabled,
128
135
  defaultAppImage,
129
136
  supabaseKubernetesEnabled,
130
137
  profileModel,
@@ -144,6 +151,7 @@ export function generateMinikubeBaseArtifacts(args) {
144
151
  path: `${scriptsRoot}/port-forward.sh`,
145
152
  content: getPortForwardScript({
146
153
  appSlug,
154
+ appRuntimeEnabled,
147
155
  supabaseKubernetesEnabled,
148
156
  supabaseHostPorts,
149
157
  }),
@@ -172,6 +180,7 @@ export function generateMinikubeBaseArtifacts(args) {
172
180
  path: `${scriptsRoot}/status.sh`,
173
181
  content: getStatusScript({
174
182
  appSlug,
183
+ appRuntimeEnabled,
175
184
  supabaseKubernetesEnabled,
176
185
  profileModel,
177
186
  supabaseHostPorts,
@@ -187,14 +196,12 @@ export function generateMinikubeBaseArtifacts(args) {
187
196
  ];
188
197
  }
189
198
  function getReadmeMarkdown(args) {
190
- const { appSlug, defaultAppImage, monitoringEnabled, authProvider, authzEngine, extraResources, supabaseHostPorts, supabaseKubernetesEnabled, profileModel, oauthProviders, providerNamespaces, providerLifecycle, } = args;
199
+ const { appSlug, appRuntimeEnabled, defaultAppImage, monitoringEnabled, authProvider, authzEngine, extraResources, supabaseHostPorts, supabaseKubernetesEnabled, profileModel, oauthProviders, providerNamespaces, providerLifecycle, } = args;
191
200
  const resourceLines = [
192
201
  'namespaces/app.yaml',
193
202
  ...(supabaseKubernetesEnabled ? ['namespaces/supabase.yaml'] : []),
194
203
  ...providerNamespaces.map((namespace) => `namespaces/${namespace}.yaml`),
195
- 'app.configmap.yaml',
196
- 'app/deployment.yaml',
197
- 'app/service.yaml',
204
+ ...(appRuntimeEnabled ? ['app.configmap.yaml', 'app/deployment.yaml', 'app/service.yaml'] : []),
198
205
  ...(supabaseKubernetesEnabled
199
206
  ? [
200
207
  'supabase/bootstrap.sql',
@@ -248,6 +255,13 @@ function getReadmeMarkdown(args) {
248
255
  const oauthDescription = oauthProviders.length > 0
249
256
  ? `\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`
250
257
  : '';
258
+ const runtimeForwardDescription = appRuntimeEnabled
259
+ ? supabaseKubernetesEnabled
260
+ ? '`app` and `supabase-gateway`'
261
+ : '`app`'
262
+ : supabaseKubernetesEnabled
263
+ ? '`supabase-gateway`'
264
+ : 'no host endpoints';
251
265
  return `# Minikube Infra
252
266
 
253
267
  This directory is generated from \`ankh.config.json\` (infra manifest).
@@ -255,7 +269,7 @@ This directory is generated from \`ankh.config.json\` (infra manifest).
255
269
  ## Ownership Model
256
270
 
257
271
  - App slug / Minikube profile: \`${appSlug}\`
258
- - App runtime namespace: \`${APP_NAMESPACE}\`
272
+ - App runtime: \`${appRuntimeEnabled ? `deployment/app-runtime in namespace ${APP_NAMESPACE}` : 'disabled (no enabled Web deploy target)'}\`
259
273
  - Supabase namespace: \`${supabaseKubernetesEnabled ? SUPABASE_NAMESPACE : 'unused'}\`
260
274
  ${providerNamespaceLine}- Default app image: \`${defaultAppImage}\`
261
275
 
@@ -285,11 +299,18 @@ host-level Supabase Compose runtime.
285
299
 
286
300
  ## Lifecycle Semantics
287
301
 
288
- - \`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.
302
+ - \`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' : ''}.
289
303
  - \`down.sh\`: stops slug-owned port-forwards, then stops \`minikube -p ${appSlug}\`. Persistent data remains in the profile.
290
304
  - \`reset.sh\`: requires \`ANKH_RESET_CONFIRM=${appSlug}\`; ${resetDescription}. It does not delete the Minikube profile.
291
305
  - \`destroy.sh\`: deletes only \`minikube -p ${appSlug}\`.
292
306
 
307
+ ### Port-forward groups
308
+
309
+ - \`./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\`.
310
+ - \`runtime\` excludes \`studio\` and \`db-migration\`, which are operational/bootstrap endpoints.
311
+ - \`all\` retains every generated forward, including operational/bootstrap endpoints.
312
+ - Concrete targets remain available for individual lifecycle control.
313
+
293
314
  ## Generated Resources
294
315
 
295
316
  ${resourceLines}
@@ -324,8 +345,7 @@ Pinned images:
324
345
 
325
346
  ## Host URLs
326
347
 
327
- - App: \`http://127.0.0.1:${supabaseHostPorts.app}\`
328
- - Supabase gateway: \`http://127.0.0.1:${supabaseHostPorts.gateway}\`
348
+ ${appRuntimeEnabled ? `- App: \`http://127.0.0.1:${supabaseHostPorts.app}\`\n` : ''}- Supabase gateway: \`http://127.0.0.1:${supabaseHostPorts.gateway}\`
329
349
  - Studio: \`http://127.0.0.1:${supabaseHostPorts.studio}\`
330
350
  - DB migration endpoint: \`127.0.0.1:${supabaseHostPorts.db}\`
331
351
 
@@ -741,9 +761,9 @@ function getKustomizationManifest(args) {
741
761
  : [];
742
762
  const resources = [
743
763
  ...args.namespaceResources,
744
- 'app.configmap.yaml',
745
- 'app/deployment.yaml',
746
- 'app/service.yaml',
764
+ ...(args.appRuntimeEnabled
765
+ ? ['app.configmap.yaml', 'app/deployment.yaml', 'app/service.yaml']
766
+ : []),
747
767
  ...supabaseResources,
748
768
  ...args.extraResources,
749
769
  ];
@@ -1997,7 +2017,7 @@ spec:
1997
2017
  `;
1998
2018
  }
1999
2019
  function getUpScript(args) {
2000
- const { appSlug, defaultAppImage, supabaseKubernetesEnabled, profileModel, secretStoreProvider, supabaseHostPorts, oauthProviders, providerLifecycle, } = args;
2020
+ const { appSlug, appRuntimeEnabled, defaultAppImage, supabaseKubernetesEnabled, profileModel, secretStoreProvider, supabaseHostPorts, oauthProviders, providerLifecycle, } = args;
2001
2021
  const oauthEnvDefaults = getOAuthScriptEnvDefaults(oauthProviders);
2002
2022
  const oauthRuntimeSecretLines = getOAuthRuntimeSecretLines(oauthProviders);
2003
2023
  const oauthValidation = getOAuthRuntimeValidationScript(oauthProviders);
@@ -2009,6 +2029,14 @@ function getUpScript(args) {
2009
2029
  secretStoreProvider,
2010
2030
  });
2011
2031
  const supabaseMigrationCommand = getSupabaseMigrationCommandScript();
2032
+ const disabledAppRuntimeReconciliation = appRuntimeEnabled
2033
+ ? ''
2034
+ : `reconcile_disabled_app_runtime() {
2035
+ kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" delete deployment/app-runtime service/app-runtime configmap/app-infra-config --ignore-not-found
2036
+ "\${PORT_FORWARD_SCRIPT}" stop obsolete
2037
+ }
2038
+ `;
2039
+ const reconcileDisabledAppRuntime = appRuntimeEnabled ? '' : 'reconcile_disabled_app_runtime';
2012
2040
  return `#!/usr/bin/env bash
2013
2041
  set -Eeuo pipefail
2014
2042
 
@@ -2023,6 +2051,7 @@ DRIVER="\${MINIKUBE_DRIVER:-}"
2023
2051
  APP_NAMESPACE="${APP_NAMESPACE}"
2024
2052
  SUPABASE_NAMESPACE="${SUPABASE_NAMESPACE}"
2025
2053
  SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
2054
+ APP_RUNTIME_ENABLED="${appRuntimeEnabled ? 'true' : 'false'}"
2026
2055
  APP_BUILD_ENABLED="\${APP_BUILD_ENABLED:-}"
2027
2056
  APP_SOURCE_DIR="\${APP_SOURCE_DIR:-}"
2028
2057
  APP_WEB_EXPORT_DIR="\${APP_WEB_EXPORT_DIR:-}"
@@ -2453,6 +2482,7 @@ apply_namespace_manifests() {
2453
2482
  done
2454
2483
  }
2455
2484
 
2485
+ ${disabledAppRuntimeReconciliation}
2456
2486
  ${providerLifecycleStageScripts}
2457
2487
 
2458
2488
  load_env_file_preserving_process_env
@@ -2526,7 +2556,7 @@ else
2526
2556
  kubectl --context "\${PROFILE}" apply -k "\${K8S_DIR}"
2527
2557
  fi
2528
2558
 
2529
- if [[ "\${APP_BUILD_ENABLED}" == "true" ]]; then
2559
+ if [[ "\${APP_RUNTIME_ENABLED}" == "true" && "\${APP_BUILD_ENABLED}" == "true" ]]; then
2530
2560
  if [[ ! -x "\${BUILD_SCRIPT}" ]]; then
2531
2561
  echo "Missing build helper script: \${BUILD_SCRIPT}"
2532
2562
  exit 1
@@ -2541,6 +2571,7 @@ else
2541
2571
  EXPORT_DIR="\${APP_SOURCE_DIR}/\${APP_WEB_EXPORT_DIR}"
2542
2572
  fi
2543
2573
 
2574
+ if [[ "\${APP_RUNTIME_ENABLED}" == "true" ]]; then
2544
2575
  case "\${APP_IMAGE_SYNC_STRATEGY}" in
2545
2576
  minikube-build)
2546
2577
  if [[ ! -f "\${EXPORT_DIR}/index.html" ]]; then
@@ -2562,15 +2593,18 @@ case "\${APP_IMAGE_SYNC_STRATEGY}" in
2562
2593
  exit 1
2563
2594
  ;;
2564
2595
  esac
2596
+ fi
2565
2597
 
2566
2598
  if [[ "\${SUPABASE_RUNTIME_ENABLED}" == "true" ]]; then
2567
2599
  kubectl --context "\${PROFILE}" apply -k "\${K8S_DIR}"
2568
2600
  fi
2569
2601
 
2602
+ ${reconcileDisabledAppRuntime}
2570
2603
  wait_for_provider_readiness
2571
2604
  run_provider_migrations
2572
2605
  run_provider_reconciliation
2573
2606
 
2607
+ if [[ "\${APP_RUNTIME_ENABLED}" == "true" ]]; then
2574
2608
  if [[ -n "\${APP_IMAGE_PULL_SECRET_NAME}" ]]; then
2575
2609
  if [[ -z "\${APP_IMAGE_PULL_SECRET_USERNAME}" || -z "\${APP_IMAGE_PULL_SECRET_PASSWORD}" ]]; then
2576
2610
  echo "APP_IMAGE_PULL_SECRET_NAME is set, but username/password are missing."
@@ -2602,6 +2636,7 @@ fi
2602
2636
 
2603
2637
  kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" rollout status deployment/app-runtime --timeout=180s >/dev/null
2604
2638
  "\${PORT_FORWARD_SCRIPT}" start app >/dev/null
2639
+ fi
2605
2640
 
2606
2641
  trap - ERR
2607
2642
  echo "Minikube infrastructure for '\${PROFILE}' is running."
@@ -2710,7 +2745,37 @@ CMD ["nginx", "-g", "daemon off;"]
2710
2745
  `;
2711
2746
  }
2712
2747
  function getPortForwardScript(args) {
2713
- const { appSlug, supabaseKubernetesEnabled, supabaseHostPorts } = args;
2748
+ const { appSlug, appRuntimeEnabled, supabaseKubernetesEnabled, supabaseHostPorts } = args;
2749
+ const runtimeForwardNames = [
2750
+ ...(appRuntimeEnabled ? ['app'] : []),
2751
+ ...(supabaseKubernetesEnabled ? ['supabase-gateway'] : []),
2752
+ ];
2753
+ const allForwardNames = [
2754
+ ...runtimeForwardNames,
2755
+ ...(supabaseKubernetesEnabled ? ['studio', 'db-migration'] : []),
2756
+ ];
2757
+ const managedForwardNames = ['app', 'supabase-gateway', 'studio', 'db-migration'];
2758
+ const obsoleteForwardNames = managedForwardNames.filter((name) => !allForwardNames.includes(name));
2759
+ const appTargetCase = ` app)
2760
+ echo "${APP_NAMESPACE} service/app-runtime \${APP_PORT_FORWARD_LOCAL_PORT} \${APP_PORT_FORWARD_REMOTE_PORT}"
2761
+ ;;`;
2762
+ const supabaseTargetCases = ` supabase-gateway)
2763
+ echo "${SUPABASE_NAMESPACE} service/gateway \${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT} \${SUPABASE_GATEWAY_FORWARD_REMOTE_PORT}"
2764
+ ;;
2765
+ studio)
2766
+ echo "${SUPABASE_NAMESPACE} service/studio \${SUPABASE_STUDIO_FORWARD_LOCAL_PORT} \${SUPABASE_STUDIO_FORWARD_REMOTE_PORT}"
2767
+ ;;
2768
+ db-migration)
2769
+ echo "${SUPABASE_NAMESPACE} service/postgres \${SUPABASE_DB_FORWARD_LOCAL_PORT} \${SUPABASE_DB_FORWARD_REMOTE_PORT}"
2770
+ ;;`;
2771
+ const concreteForwardNames = [
2772
+ ...(appRuntimeEnabled ? ['app'] : []),
2773
+ ...(supabaseKubernetesEnabled ? ['supabase-gateway', 'studio', 'db-migration'] : []),
2774
+ ];
2775
+ const forwardUsage = [...concreteForwardNames, 'runtime', 'all'].join('|');
2776
+ const forwardNamesDescription = concreteForwardNames.length > 0
2777
+ ? `${concreteForwardNames.join(', ')}, runtime, or all`
2778
+ : 'runtime or all';
2714
2779
  return `#!/usr/bin/env bash
2715
2780
  set -euo pipefail
2716
2781
 
@@ -2720,6 +2785,7 @@ STATE_DIR="\${ROOT_DIR}/.state/forwards"
2720
2785
  APP_SLUG="${appSlug}"
2721
2786
  PROFILE="\${ANKH_APP_SLUG:-${appSlug}}"
2722
2787
  SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
2788
+ APP_RUNTIME_ENABLED="${appRuntimeEnabled ? 'true' : 'false'}"
2723
2789
  APP_PORT_FORWARD_LOCAL_PORT="\${APP_PORT_FORWARD_LOCAL_PORT:-${supabaseHostPorts.app}}"
2724
2790
  APP_PORT_FORWARD_REMOTE_PORT="\${APP_PORT_FORWARD_REMOTE_PORT:-80}"
2725
2791
  SUPABASE_GATEWAY_FORWARD_LOCAL_PORT="\${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT:-${supabaseHostPorts.gateway}}"
@@ -2728,6 +2794,10 @@ SUPABASE_STUDIO_FORWARD_LOCAL_PORT="\${SUPABASE_STUDIO_FORWARD_LOCAL_PORT:-${sup
2728
2794
  SUPABASE_STUDIO_FORWARD_REMOTE_PORT="\${SUPABASE_STUDIO_FORWARD_REMOTE_PORT:-3000}"
2729
2795
  SUPABASE_DB_FORWARD_LOCAL_PORT="\${SUPABASE_DB_FORWARD_LOCAL_PORT:-${supabaseHostPorts.db}}"
2730
2796
  SUPABASE_DB_FORWARD_REMOTE_PORT="\${SUPABASE_DB_FORWARD_REMOTE_PORT:-5432}"
2797
+ RUNTIME_FORWARD_NAMES=(${runtimeForwardNames.join(' ')})
2798
+ ALL_FORWARD_NAMES=(${allForwardNames.join(' ')})
2799
+ MANAGED_FORWARD_NAMES=(${managedForwardNames.join(' ')})
2800
+ OBSOLETE_FORWARD_NAMES=(${obsoleteForwardNames.join(' ')})
2731
2801
 
2732
2802
  if [[ -f "\${ROOT_DIR}/.env" ]]; then
2733
2803
  set -a
@@ -2736,6 +2806,7 @@ if [[ -f "\${ROOT_DIR}/.env" ]]; then
2736
2806
  set +a
2737
2807
  fi
2738
2808
  SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
2809
+ APP_RUNTIME_ENABLED="${appRuntimeEnabled ? 'true' : 'false'}"
2739
2810
 
2740
2811
  if [[ "\${PROFILE}" != "\${APP_SLUG}" ]]; then
2741
2812
  echo "ANKH_APP_SLUG must remain the generated canonical slug '\${APP_SLUG}' for this infra directory."
@@ -2755,20 +2826,10 @@ pid_file_for() {
2755
2826
 
2756
2827
  target_for() {
2757
2828
  case "\${1}" in
2758
- app)
2759
- echo "${APP_NAMESPACE} service/app-runtime \${APP_PORT_FORWARD_LOCAL_PORT} \${APP_PORT_FORWARD_REMOTE_PORT}"
2760
- ;;
2761
- supabase-gateway)
2762
- echo "${SUPABASE_NAMESPACE} service/gateway \${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT} \${SUPABASE_GATEWAY_FORWARD_REMOTE_PORT}"
2763
- ;;
2764
- studio)
2765
- echo "${SUPABASE_NAMESPACE} service/studio \${SUPABASE_STUDIO_FORWARD_LOCAL_PORT} \${SUPABASE_STUDIO_FORWARD_REMOTE_PORT}"
2766
- ;;
2767
- db-migration)
2768
- echo "${SUPABASE_NAMESPACE} service/postgres \${SUPABASE_DB_FORWARD_LOCAL_PORT} \${SUPABASE_DB_FORWARD_REMOTE_PORT}"
2769
- ;;
2829
+ ${appTargetCase}
2830
+ ${supabaseTargetCases}
2770
2831
  *)
2771
- echo "Unknown forward '\${1}'. Use app, supabase-gateway, studio, db-migration, or all." >&2
2832
+ echo "Unknown forward '\${1}'. Use ${forwardNamesDescription}." >&2
2772
2833
  return 1
2773
2834
  ;;
2774
2835
  esac
@@ -2904,6 +2965,11 @@ start_forward() {
2904
2965
  pid_file="$(pid_file_for "\${name}")"
2905
2966
  read -r namespace resource local_port remote_port <<<"$(target_for "\${name}")"
2906
2967
 
2968
+ if [[ "\${name}" == "app" && "\${APP_RUNTIME_ENABLED}" != "true" ]]; then
2969
+ echo "\${name}: skipped (Web app runtime disabled)"
2970
+ return 0
2971
+ fi
2972
+
2907
2973
  if [[ -f "\${pid_file}" ]]; then
2908
2974
  local existing_pid
2909
2975
  existing_pid="$(cat "\${pid_file}")"
@@ -2933,28 +2999,52 @@ start_forward() {
2933
2999
  return 1
2934
3000
  fi
2935
3001
 
2936
- nohup kubectl --context "\${PROFILE}" -n "\${namespace}" port-forward "\${resource}" "\${local_port}:\${remote_port}" >"\${STATE_DIR}/\${PROFILE}-\${name}.log" 2>&1 &
2937
- local pid="$!"
2938
- echo "\${pid}" > "\${pid_file}"
3002
+ for start_attempt in {1..5}; do
3003
+ nohup kubectl --context "\${PROFILE}" -n "\${namespace}" port-forward "\${resource}" "\${local_port}:\${remote_port}" >"\${STATE_DIR}/\${PROFILE}-\${name}.log" 2>&1 &
3004
+ local pid="$!"
3005
+ echo "\${pid}" > "\${pid_file}"
2939
3006
 
2940
- for attempt in {1..40}; do
2941
- if ! is_pid_running "\${pid}"; then
2942
- echo "\${name}: failed to start local port \${local_port} for \${namespace}/\${resource}:\${remote_port}"
2943
- print_forward_log_tail "\${name}"
3007
+ for readiness_attempt in {1..20}; do
3008
+ if ! is_pid_running "\${pid}"; then
3009
+ rm -f "\${pid_file}"
3010
+ if [[ "\${start_attempt}" -lt 5 ]]; then
3011
+ echo "\${name}: port-forward attempt \${start_attempt} exited before readiness; retrying"
3012
+ sleep 2
3013
+ break
3014
+ fi
3015
+
3016
+ echo "\${name}: failed to start local port \${local_port} for \${namespace}/\${resource}:\${remote_port} after \${start_attempt} attempts"
3017
+ print_forward_log_tail "\${name}"
3018
+ return 1
3019
+ fi
3020
+ if is_port_accepting "\${local_port}"; then
3021
+ sleep 1
3022
+ if is_pid_running "\${pid}" && is_port_accepting "\${local_port}"; then
3023
+ sleep 0.5
3024
+ if is_pid_running "\${pid}"; then
3025
+ echo "\${name}: started (pid \${pid}, local port \${local_port})"
3026
+ return 0
3027
+ fi
3028
+ fi
3029
+ fi
3030
+ sleep 0.5
3031
+ done
3032
+
3033
+ if [[ -f "\${pid_file}" ]]; then
3034
+ terminate_forward_process "\${name}" "\${pid}" "\${namespace}" "\${resource}" "\${local_port}" "\${remote_port}" || return 1
2944
3035
  rm -f "\${pid_file}"
2945
- return 1
2946
- fi
2947
- if is_port_accepting "\${local_port}"; then
2948
- echo "\${name}: started (pid \${pid}, local port \${local_port})"
2949
- return 0
3036
+
3037
+ if [[ "\${start_attempt}" -lt 5 ]]; then
3038
+ echo "\${name}: port-forward attempt \${start_attempt} did not become ready; retrying"
3039
+ sleep 2
3040
+ else
3041
+ echo "\${name}: did not become ready on local port \${local_port} for \${namespace}/\${resource}:\${remote_port} after \${start_attempt} attempts"
3042
+ print_forward_log_tail "\${name}"
3043
+ return 1
3044
+ fi
2950
3045
  fi
2951
- sleep 0.5
2952
3046
  done
2953
3047
 
2954
- echo "\${name}: did not become ready on local port \${local_port} for \${namespace}/\${resource}:\${remote_port}"
2955
- print_forward_log_tail "\${name}"
2956
- terminate_forward_process "\${name}" "\${pid}" "\${namespace}" "\${resource}" "\${local_port}" "\${remote_port}" || return 1
2957
- rm -f "\${pid_file}"
2958
3048
  return 1
2959
3049
  }
2960
3050
 
@@ -3013,26 +3103,34 @@ status_forward() {
3013
3103
 
3014
3104
  for_each_forward() {
3015
3105
  local action="\${1}"
3016
- "\${action}_forward" app
3017
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" == "true" ]]; then
3018
- "\${action}_forward" supabase-gateway
3019
- "\${action}_forward" studio
3020
- "\${action}_forward" db-migration
3021
- fi
3106
+ shift
3107
+ local name
3108
+ while [[ "\${#}" -gt 0 ]]; do
3109
+ name="\${1}"
3110
+ shift
3111
+ "\${action}_forward" "\${name}"
3112
+ done
3022
3113
  }
3023
3114
 
3024
3115
  ACTION="\${1:-start}"
3025
3116
  NAME="\${2:-all}"
3026
3117
 
3027
3118
  case "\${ACTION}:\${NAME}" in
3028
- start:all) for_each_forward start ;;
3029
- stop:all) for_each_forward stop ;;
3030
- status:all) for_each_forward status ;;
3119
+ start:runtime) for_each_forward start ${runtimeForwardNames.join(' ')} ;;
3120
+ stop:runtime)
3121
+ for_each_forward stop ${runtimeForwardNames.join(' ')}
3122
+ for_each_forward stop ${obsoleteForwardNames.join(' ')}
3123
+ ;;
3124
+ status:runtime) for_each_forward status ${runtimeForwardNames.join(' ')} ;;
3125
+ start:all) for_each_forward start ${allForwardNames.join(' ')} ;;
3126
+ stop:all) for_each_forward stop ${managedForwardNames.join(' ')} ;;
3127
+ status:all) for_each_forward status ${allForwardNames.join(' ')} ;;
3128
+ stop:obsolete) for_each_forward stop ${obsoleteForwardNames.join(' ')} ;;
3031
3129
  start:*) start_forward "\${NAME}" ;;
3032
3130
  stop:*) stop_forward "\${NAME}" ;;
3033
3131
  status:*) status_forward "\${NAME}" ;;
3034
3132
  *)
3035
- echo "Usage: ./scripts/port-forward.sh {start|stop|status} {app|supabase-gateway|studio|db-migration|all}"
3133
+ echo "Usage: ./scripts/port-forward.sh {start|stop|status} {${forwardUsage}}"
3036
3134
  exit 1
3037
3135
  ;;
3038
3136
  esac
@@ -3150,7 +3248,7 @@ echo "Deleted Minikube profile '\${PROFILE}'."
3150
3248
  `;
3151
3249
  }
3152
3250
  function getStatusScript(args) {
3153
- const { appSlug, supabaseKubernetesEnabled, profileModel, supabaseHostPorts, providerNamespaces, providerLifecycle, } = args;
3251
+ const { appSlug, appRuntimeEnabled, supabaseKubernetesEnabled, profileModel, supabaseHostPorts, providerNamespaces, providerLifecycle, } = args;
3154
3252
  const namespaces = [
3155
3253
  APP_NAMESPACE,
3156
3254
  ...(supabaseKubernetesEnabled ? [SUPABASE_NAMESPACE] : []),
@@ -3228,7 +3326,7 @@ for namespace in ${namespaces}; do
3228
3326
  fi
3229
3327
  done
3230
3328
 
3231
- if kubectl --context "\${PROFILE}" -n ${APP_NAMESPACE} get deployment app-runtime >/dev/null 2>&1; then
3329
+ if [[ "${appRuntimeEnabled ? 'true' : 'false'}" == "true" ]] && kubectl --context "\${PROFILE}" -n ${APP_NAMESPACE} get deployment app-runtime >/dev/null 2>&1; then
3232
3330
  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"
3233
3331
  fi
3234
3332