@ankhorage/infra 4.0.0 → 4.1.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.
@@ -2,5 +2,5 @@ export { type ResolvedInfraProject, resolveInfraProject as resolveInfraWorkspace
2
2
  export { resolveProjectInfrastructureDatabaseUrl } from '../projectDatabase.js';
3
3
  export { readProjectInfrastructureEnvironment } from '../projectEnvironment.js';
4
4
  export { type InfraProjectInspection, type InfraSyncResult, inspectProjectInfrastructure, resolveProjectInfrastructureTarget, syncProjectInfrastructure, } from '../projectInfrastructure.js';
5
- export { type InfraLifecycleScript, type InfraPortForwardInfo, InfraScriptExecutionError, type InfraScriptOutput, resolveProjectInfrastructurePortForward, runProjectInfrastructureLifecycle, } from '../runtime.js';
5
+ export { ensureProjectInfrastructureRuntime, type InfraLifecycleScript, type InfraPortForwardInfo, InfraScriptExecutionError, type InfraScriptOutput, resolveProjectInfrastructurePortForward, runProjectInfrastructureLifecycle, } from '../runtime.js';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,oBAAoB,EACzB,mBAAmB,IAAI,4BAA4B,GACpD,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,uCAAuC,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,oCAAoC,EAAE,MAAM,0BAA0B,CAAC;AAChF,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,4BAA4B,EAC5B,kCAAkC,EAClC,yBAAyB,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,uCAAuC,EACvC,iCAAiC,GAClC,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,oBAAoB,EACzB,mBAAmB,IAAI,4BAA4B,GACpD,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,uCAAuC,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,oCAAoC,EAAE,MAAM,0BAA0B,CAAC;AAChF,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,4BAA4B,EAC5B,kCAAkC,EAClC,yBAAyB,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,kCAAkC,EAClC,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,uCAAuC,EACvC,iCAAiC,GAClC,MAAM,eAAe,CAAC"}
@@ -2,5 +2,5 @@ export { resolveInfraProject as resolveInfraWorkspaceProject, } from '../project
2
2
  export { resolveProjectInfrastructureDatabaseUrl } from '../projectDatabase.js';
3
3
  export { readProjectInfrastructureEnvironment } from '../projectEnvironment.js';
4
4
  export { inspectProjectInfrastructure, resolveProjectInfrastructureTarget, syncProjectInfrastructure, } from '../projectInfrastructure.js';
5
- export { InfraScriptExecutionError, resolveProjectInfrastructurePortForward, runProjectInfrastructureLifecycle, } from '../runtime.js';
5
+ export { ensureProjectInfrastructureRuntime, InfraScriptExecutionError, resolveProjectInfrastructurePortForward, runProjectInfrastructureLifecycle, } from '../runtime.js';
6
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,IAAI,4BAA4B,GACpD,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,uCAAuC,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,oCAAoC,EAAE,MAAM,0BAA0B,CAAC;AAChF,OAAO,EAGL,4BAA4B,EAC5B,kCAAkC,EAClC,yBAAyB,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAGL,yBAAyB,EAEzB,uCAAuC,EACvC,iCAAiC,GAClC,MAAM,eAAe,CAAC","sourcesContent":["export {\n type ResolvedInfraProject,\n resolveInfraProject as resolveInfraWorkspaceProject,\n} from '../project.js';\nexport { resolveProjectInfrastructureDatabaseUrl } from '../projectDatabase.js';\nexport { readProjectInfrastructureEnvironment } from '../projectEnvironment.js';\nexport {\n type InfraProjectInspection,\n type InfraSyncResult,\n inspectProjectInfrastructure,\n resolveProjectInfrastructureTarget,\n syncProjectInfrastructure,\n} from '../projectInfrastructure.js';\nexport {\n type InfraLifecycleScript,\n type InfraPortForwardInfo,\n InfraScriptExecutionError,\n type InfraScriptOutput,\n resolveProjectInfrastructurePortForward,\n runProjectInfrastructureLifecycle,\n} from '../runtime.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,IAAI,4BAA4B,GACpD,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,uCAAuC,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,oCAAoC,EAAE,MAAM,0BAA0B,CAAC;AAChF,OAAO,EAGL,4BAA4B,EAC5B,kCAAkC,EAClC,yBAAyB,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,kCAAkC,EAGlC,yBAAyB,EAEzB,uCAAuC,EACvC,iCAAiC,GAClC,MAAM,eAAe,CAAC","sourcesContent":["export {\n type ResolvedInfraProject,\n resolveInfraProject as resolveInfraWorkspaceProject,\n} from '../project.js';\nexport { resolveProjectInfrastructureDatabaseUrl } from '../projectDatabase.js';\nexport { readProjectInfrastructureEnvironment } from '../projectEnvironment.js';\nexport {\n type InfraProjectInspection,\n type InfraSyncResult,\n inspectProjectInfrastructure,\n resolveProjectInfrastructureTarget,\n syncProjectInfrastructure,\n} from '../projectInfrastructure.js';\nexport {\n ensureProjectInfrastructureRuntime,\n type InfraLifecycleScript,\n type InfraPortForwardInfo,\n InfraScriptExecutionError,\n type InfraScriptOutput,\n resolveProjectInfrastructurePortForward,\n runProjectInfrastructureLifecycle,\n} from '../runtime.js';\n"]}
@@ -28,6 +28,11 @@ import { runCli } from './cli/bin.js';
28
28
  * operations accept an explicit project path and do not require Studio or a
29
29
  * particular workspace layout.
30
30
  *
31
+ * Running applications can repair their provider-aware host endpoints through
32
+ * `ensureProjectInfrastructureRuntime()` from `@ankhorage/infra/project`. This
33
+ * operation only ensures the generated runtime port-forward group; it does not
34
+ * deploy, bootstrap, or reconcile infrastructure.
35
+ *
31
36
  * @usage
32
37
  */
33
38
  await runCli(['--help']);
@@ -1 +1 @@
1
- {"version":3,"file":"readme-usage.js","sourceRoot":"","sources":["../src/readme-usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC","sourcesContent":["import { runCli } from './cli/bin.js';\n\n/***\n * Provider, CLI, and project API surface\n *\n * `@ankhorage/infra` owns infrastructure generation, project reconciliation,\n * and generated lifecycle execution.\n *\n * The same owner implementation backs CLI consumers and the public\n * `@ankhorage/infra/project` application-service boundary. Trusted hosts such\n * as Studio should call that subpath instead of reproducing ledger, generated\n * file, lifecycle-script, or port-forward configuration semantics.\n *\n * CLI entrypoints:\n *\n * - `ankh infra ...`\n * - `bunx @ankhorage/infra ...`\n *\n * Current command surface:\n *\n * - `validate`\n * - `generate`\n * - `status`\n * - `up`\n * - `down`\n *\n * Project resolution for the CLI is workspace-aware: pass `[project]`, or\n * omit it when cwd is already inside `apps/<project>`. Programmatic project\n * operations accept an explicit project path and do not require Studio or a\n * particular workspace layout.\n *\n * @usage\n */\nawait runCli(['--help']);\n"]}
1
+ {"version":3,"file":"readme-usage.js","sourceRoot":"","sources":["../src/readme-usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC","sourcesContent":["import { runCli } from './cli/bin.js';\n\n/***\n * Provider, CLI, and project API surface\n *\n * `@ankhorage/infra` owns infrastructure generation, project reconciliation,\n * and generated lifecycle execution.\n *\n * The same owner implementation backs CLI consumers and the public\n * `@ankhorage/infra/project` application-service boundary. Trusted hosts such\n * as Studio should call that subpath instead of reproducing ledger, generated\n * file, lifecycle-script, or port-forward configuration semantics.\n *\n * CLI entrypoints:\n *\n * - `ankh infra ...`\n * - `bunx @ankhorage/infra ...`\n *\n * Current command surface:\n *\n * - `validate`\n * - `generate`\n * - `status`\n * - `up`\n * - `down`\n *\n * Project resolution for the CLI is workspace-aware: pass `[project]`, or\n * omit it when cwd is already inside `apps/<project>`. Programmatic project\n * operations accept an explicit project path and do not require Studio or a\n * particular workspace layout.\n *\n * Running applications can repair their provider-aware host endpoints through\n * `ensureProjectInfrastructureRuntime()` from `@ankhorage/infra/project`. This\n * operation only ensures the generated runtime port-forward group; it does not\n * deploy, bootstrap, or reconcile infrastructure.\n *\n * @usage\n */\nawait runCli(['--help']);\n"]}
package/dist/runtime.d.ts CHANGED
@@ -33,6 +33,12 @@ export declare function resolveProjectInfrastructurePortForward(args: {
33
33
  readonly projectPath: string;
34
34
  readonly target: string;
35
35
  }): Promise<InfraPortForwardInfo>;
36
+ /** Ensures the generated host forwards required by a running application are active. */
37
+ export declare function ensureProjectInfrastructureRuntime(args: {
38
+ readonly projectId: string;
39
+ readonly projectPath: string;
40
+ readonly target: string;
41
+ }): Promise<InfraScriptOutput>;
36
42
  export declare function runProjectInfraScript(args: {
37
43
  readonly context: InfraCommandContext;
38
44
  readonly projectId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG/D,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEnG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,IAAI,EAAE;QAChB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB;CAOF;AAED,wBAAsB,iCAAiC,CAAC,IAAI,EAAE;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAe7B;AAED,wBAAsB,uCAAuC,CAAC,IAAI,EAAE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAkBhC;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,MAAM,CAET"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG/D,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEnG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,IAAI,EAAE;QAChB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB;CAOF;AAED,wBAAsB,iCAAiC,CAAC,IAAI,EAAE;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAe7B;AAED,wBAAsB,uCAAuC,CAAC,IAAI,EAAE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAkBhC;AAED,wFAAwF;AACxF,wBAAsB,kCAAkC,CAAC,IAAI,EAAE;IAC7D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAwB7B;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,MAAM,CAET"}
package/dist/runtime.js CHANGED
@@ -42,6 +42,30 @@ export async function resolveProjectInfrastructurePortForward(args) {
42
42
  url: `http://127.0.0.1:${localPort}`,
43
43
  };
44
44
  }
45
+ /** Ensures the generated host forwards required by a running application are active. */
46
+ export async function ensureProjectInfrastructureRuntime(args) {
47
+ try {
48
+ return await runProjectInfrastructureLifecycle({
49
+ args: ['start', 'runtime'],
50
+ projectId: args.projectId,
51
+ projectPath: args.projectPath,
52
+ script: 'port-forward',
53
+ target: args.target,
54
+ });
55
+ }
56
+ catch (error) {
57
+ if (!(error instanceof InfraScriptExecutionError)) {
58
+ throw error;
59
+ }
60
+ const detail = error.stderr.trim() || error.stdout.trim();
61
+ throw new InfraScriptExecutionError({
62
+ exitCode: error.exitCode,
63
+ message: `Failed to ensure infrastructure runtime for project '${args.projectId}'${detail.length > 0 ? `: ${detail}` : '.'}`,
64
+ stderr: error.stderr,
65
+ stdout: error.stdout,
66
+ });
67
+ }
68
+ }
45
69
  export async function runProjectInfraScript(args) {
46
70
  await runProjectInfrastructureLifecycle({
47
71
  env: args.context.env,
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'child_process';\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\nimport type { InfraCommandContext } from './commandContext.js';\nimport { readProjectInfrastructureEnvironment } from './projectEnvironment.js';\n\nexport type InfraLifecycleScript = 'destroy' | 'down' | 'port-forward' | 'reset' | 'status' | 'up';\n\nexport interface InfraScriptOutput {\n readonly stderr: string;\n readonly stdout: string;\n}\n\nexport interface InfraPortForwardInfo {\n readonly localPort: number;\n readonly url: string;\n}\n\nexport class InfraScriptExecutionError extends Error {\n readonly exitCode: number | null;\n readonly stderr: string;\n readonly stdout: string;\n\n constructor(args: {\n readonly exitCode: number | null;\n readonly message: string;\n readonly stderr: string;\n readonly stdout: string;\n }) {\n super(args.message);\n this.name = 'InfraScriptExecutionError';\n this.exitCode = args.exitCode;\n this.stderr = args.stderr;\n this.stdout = args.stdout;\n }\n}\n\nexport async function runProjectInfrastructureLifecycle(args: {\n readonly args?: readonly string[];\n readonly env?: Readonly<Record<string, string | undefined>>;\n readonly onStderr?: (chunk: string) => void;\n readonly onStdout?: (chunk: string) => void;\n readonly projectId: string;\n readonly projectPath: string;\n readonly script: InfraLifecycleScript;\n readonly target: string;\n}): Promise<InfraScriptOutput> {\n const scriptPath = resolveProjectInfraScriptPath(args);\n if (!(await pathExists(scriptPath))) {\n throw new Error(\n `Infra script not found: ${scriptPath}. Run infra generate for project '${args.projectId}' first.`,\n );\n }\n\n return runShellScript({\n args: args.args ?? [],\n env: args.env ?? process.env,\n onStderr: args.onStderr,\n onStdout: args.onStdout,\n scriptPath,\n });\n}\n\nexport async function resolveProjectInfrastructurePortForward(args: {\n readonly projectPath: string;\n readonly target: string;\n}): Promise<InfraPortForwardInfo> {\n const environment = await readProjectInfrastructureEnvironment({\n keys: ['APP_PORT_FORWARD_LOCAL_PORT'],\n projectPath: args.projectPath,\n target: args.target,\n });\n const localPort = parsePositivePort(environment.APP_PORT_FORWARD_LOCAL_PORT);\n\n if (localPort === null) {\n throw new Error(\n 'Generated Infra did not provide a valid APP_PORT_FORWARD_LOCAL_PORT. Regenerate infrastructure first.',\n );\n }\n\n return {\n localPort,\n url: `http://127.0.0.1:${localPort}`,\n };\n}\n\nexport async function runProjectInfraScript(args: {\n readonly context: InfraCommandContext;\n readonly projectId: string;\n readonly projectPath: string;\n readonly script: InfraLifecycleScript;\n readonly target: string;\n}): Promise<void> {\n await runProjectInfrastructureLifecycle({\n env: args.context.env,\n onStderr: (chunk) => args.context.writeStderr(chunk),\n onStdout: (chunk) => args.context.writeStdout(chunk),\n projectId: args.projectId,\n projectPath: args.projectPath,\n script: args.script,\n target: args.target,\n });\n}\n\nexport function resolveProjectInfraScriptPath(args: {\n readonly projectPath: string;\n readonly script: InfraLifecycleScript;\n readonly target: string;\n}): string {\n return path.join(args.projectPath, getInfraScriptsDirectory(args.target), `${args.script}.sh`);\n}\n\nfunction getInfraScriptsDirectory(target: string): string {\n switch (target) {\n case 'minikube':\n return 'infra/minikube/scripts';\n default:\n throw new Error(`Unsupported deployment target for infra scripts: ${target}`);\n }\n}\n\nasync function runShellScript(args: {\n readonly args: readonly string[];\n readonly env: Readonly<Record<string, string | undefined>>;\n readonly onStderr?: (chunk: string) => void;\n readonly onStdout?: (chunk: string) => void;\n readonly scriptPath: string;\n}): Promise<InfraScriptOutput> {\n return new Promise<InfraScriptOutput>((resolve, reject) => {\n const child = spawn('bash', [args.scriptPath, ...args.args], {\n cwd: path.dirname(args.scriptPath),\n env: { ...args.env },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n child.stdout.setEncoding('utf8');\n child.stderr.setEncoding('utf8');\n child.stdout.on('data', (chunk: string) => {\n stdout += chunk;\n args.onStdout?.(chunk);\n });\n child.stderr.on('data', (chunk: string) => {\n stderr += chunk;\n args.onStderr?.(chunk);\n });\n child.once('error', (error) =>\n reject(createStartError(args.scriptPath, error, stdout, stderr)),\n );\n child.once('close', (exitCode) => {\n if (exitCode === 0) {\n resolve({ stderr, stdout });\n return;\n }\n reject(createExitError(args.scriptPath, exitCode, stdout, stderr));\n });\n });\n}\n\nfunction createStartError(\n scriptPath: string,\n error: Error,\n stdout: string,\n stderr: string,\n): InfraScriptExecutionError {\n return new InfraScriptExecutionError({\n exitCode: null,\n message: `Failed to start infra script '${scriptPath}': ${error.message}`,\n stderr,\n stdout,\n });\n}\n\nfunction createExitError(\n scriptPath: string,\n exitCode: number | null,\n stdout: string,\n stderr: string,\n): InfraScriptExecutionError {\n return new InfraScriptExecutionError({\n exitCode,\n message: `Infra script '${scriptPath}' exited with code ${exitCode ?? 'unknown'}.`,\n stderr,\n stdout,\n });\n}\n\nfunction parsePositivePort(value: string | undefined): number | null {\n if (value === undefined) return null;\n const parsed = Number.parseInt(value.trim(), 10);\n return Number.isInteger(parsed) && parsed >= 1 && parsed <= 65_535 ? parsed : null;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n"]}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'child_process';\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\nimport type { InfraCommandContext } from './commandContext.js';\nimport { readProjectInfrastructureEnvironment } from './projectEnvironment.js';\n\nexport type InfraLifecycleScript = 'destroy' | 'down' | 'port-forward' | 'reset' | 'status' | 'up';\n\nexport interface InfraScriptOutput {\n readonly stderr: string;\n readonly stdout: string;\n}\n\nexport interface InfraPortForwardInfo {\n readonly localPort: number;\n readonly url: string;\n}\n\nexport class InfraScriptExecutionError extends Error {\n readonly exitCode: number | null;\n readonly stderr: string;\n readonly stdout: string;\n\n constructor(args: {\n readonly exitCode: number | null;\n readonly message: string;\n readonly stderr: string;\n readonly stdout: string;\n }) {\n super(args.message);\n this.name = 'InfraScriptExecutionError';\n this.exitCode = args.exitCode;\n this.stderr = args.stderr;\n this.stdout = args.stdout;\n }\n}\n\nexport async function runProjectInfrastructureLifecycle(args: {\n readonly args?: readonly string[];\n readonly env?: Readonly<Record<string, string | undefined>>;\n readonly onStderr?: (chunk: string) => void;\n readonly onStdout?: (chunk: string) => void;\n readonly projectId: string;\n readonly projectPath: string;\n readonly script: InfraLifecycleScript;\n readonly target: string;\n}): Promise<InfraScriptOutput> {\n const scriptPath = resolveProjectInfraScriptPath(args);\n if (!(await pathExists(scriptPath))) {\n throw new Error(\n `Infra script not found: ${scriptPath}. Run infra generate for project '${args.projectId}' first.`,\n );\n }\n\n return runShellScript({\n args: args.args ?? [],\n env: args.env ?? process.env,\n onStderr: args.onStderr,\n onStdout: args.onStdout,\n scriptPath,\n });\n}\n\nexport async function resolveProjectInfrastructurePortForward(args: {\n readonly projectPath: string;\n readonly target: string;\n}): Promise<InfraPortForwardInfo> {\n const environment = await readProjectInfrastructureEnvironment({\n keys: ['APP_PORT_FORWARD_LOCAL_PORT'],\n projectPath: args.projectPath,\n target: args.target,\n });\n const localPort = parsePositivePort(environment.APP_PORT_FORWARD_LOCAL_PORT);\n\n if (localPort === null) {\n throw new Error(\n 'Generated Infra did not provide a valid APP_PORT_FORWARD_LOCAL_PORT. Regenerate infrastructure first.',\n );\n }\n\n return {\n localPort,\n url: `http://127.0.0.1:${localPort}`,\n };\n}\n\n/** Ensures the generated host forwards required by a running application are active. */\nexport async function ensureProjectInfrastructureRuntime(args: {\n readonly projectId: string;\n readonly projectPath: string;\n readonly target: string;\n}): Promise<InfraScriptOutput> {\n try {\n return await runProjectInfrastructureLifecycle({\n args: ['start', 'runtime'],\n projectId: args.projectId,\n projectPath: args.projectPath,\n script: 'port-forward',\n target: args.target,\n });\n } catch (error) {\n if (!(error instanceof InfraScriptExecutionError)) {\n throw error;\n }\n\n const detail = error.stderr.trim() || error.stdout.trim();\n throw new InfraScriptExecutionError({\n exitCode: error.exitCode,\n message: `Failed to ensure infrastructure runtime for project '${args.projectId}'${\n detail.length > 0 ? `: ${detail}` : '.'\n }`,\n stderr: error.stderr,\n stdout: error.stdout,\n });\n }\n}\n\nexport async function runProjectInfraScript(args: {\n readonly context: InfraCommandContext;\n readonly projectId: string;\n readonly projectPath: string;\n readonly script: InfraLifecycleScript;\n readonly target: string;\n}): Promise<void> {\n await runProjectInfrastructureLifecycle({\n env: args.context.env,\n onStderr: (chunk) => args.context.writeStderr(chunk),\n onStdout: (chunk) => args.context.writeStdout(chunk),\n projectId: args.projectId,\n projectPath: args.projectPath,\n script: args.script,\n target: args.target,\n });\n}\n\nexport function resolveProjectInfraScriptPath(args: {\n readonly projectPath: string;\n readonly script: InfraLifecycleScript;\n readonly target: string;\n}): string {\n return path.join(args.projectPath, getInfraScriptsDirectory(args.target), `${args.script}.sh`);\n}\n\nfunction getInfraScriptsDirectory(target: string): string {\n switch (target) {\n case 'minikube':\n return 'infra/minikube/scripts';\n default:\n throw new Error(`Unsupported deployment target for infra scripts: ${target}`);\n }\n}\n\nasync function runShellScript(args: {\n readonly args: readonly string[];\n readonly env: Readonly<Record<string, string | undefined>>;\n readonly onStderr?: (chunk: string) => void;\n readonly onStdout?: (chunk: string) => void;\n readonly scriptPath: string;\n}): Promise<InfraScriptOutput> {\n return new Promise<InfraScriptOutput>((resolve, reject) => {\n const child = spawn('bash', [args.scriptPath, ...args.args], {\n cwd: path.dirname(args.scriptPath),\n env: { ...args.env },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n child.stdout.setEncoding('utf8');\n child.stderr.setEncoding('utf8');\n child.stdout.on('data', (chunk: string) => {\n stdout += chunk;\n args.onStdout?.(chunk);\n });\n child.stderr.on('data', (chunk: string) => {\n stderr += chunk;\n args.onStderr?.(chunk);\n });\n child.once('error', (error) =>\n reject(createStartError(args.scriptPath, error, stdout, stderr)),\n );\n child.once('close', (exitCode) => {\n if (exitCode === 0) {\n resolve({ stderr, stdout });\n return;\n }\n reject(createExitError(args.scriptPath, exitCode, stdout, stderr));\n });\n });\n}\n\nfunction createStartError(\n scriptPath: string,\n error: Error,\n stdout: string,\n stderr: string,\n): InfraScriptExecutionError {\n return new InfraScriptExecutionError({\n exitCode: null,\n message: `Failed to start infra script '${scriptPath}': ${error.message}`,\n stderr,\n stdout,\n });\n}\n\nfunction createExitError(\n scriptPath: string,\n exitCode: number | null,\n stdout: string,\n stderr: string,\n): InfraScriptExecutionError {\n return new InfraScriptExecutionError({\n exitCode,\n message: `Infra script '${scriptPath}' exited with code ${exitCode ?? 'unknown'}.`,\n stderr,\n stdout,\n });\n}\n\nfunction parsePositivePort(value: string | undefined): number | null {\n if (value === undefined) return null;\n const parsed = Number.parseInt(value.trim(), 10);\n return Number.isInteger(parsed) && parsed >= 1 && parsed <= 65_535 ? parsed : null;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/infra",
3
3
  "type": "module",
4
- "version": "4.0.0",
4
+ "version": "4.1.0",
5
5
  "description": "Project-aware infrastructure generation library and executable Ankh provider for Ankhorage.",
6
6
  "homepage": "https://github.com/ankhorage/infra#readme",
7
7
  "bugs": {
@@ -30,9 +30,17 @@ when provided by caller.
30
30
  - `scripts/reset.sh`: requires `ANKH_RESET_CONFIRM=<slug>` and deletes/recreates namespaces `app` and `supabase`, including Supabase DB and Storage PVC data. It does not delete the Minikube profile.
31
31
  - `scripts/destroy.sh`: stops slug-owned port-forwards and runs `minikube delete -p <slug>`.
32
32
  - `scripts/status.sh`: reports profile, namespace, workload, and port-forward health.
33
- - `scripts/port-forward.sh`: owns named forwards for `app`, `supabase-gateway`, `studio`, and `db-migration`.
33
+ - `scripts/port-forward.sh`: owns named forwards for `app`, `supabase-gateway`, `studio`, and `db-migration`, plus provider-aware `runtime` and `all` groups.
34
34
  - `scripts/build-app-image.sh`: exports Expo web build from app source and builds the Docker image.
35
35
 
36
+ The `runtime` group is the canonical repair lifecycle for host endpoints required by the
37
+ running app. It always contains `app` and adds `supabase-gateway` when the generated
38
+ Supabase Kubernetes runtime is enabled. It deliberately excludes `studio` and
39
+ `db-migration`; `all` retains those operational/bootstrap forwards. Start, stop, or inspect
40
+ the group with `scripts/port-forward.sh {start|stop|status} runtime`. Package consumers
41
+ should call `ensureProjectInfrastructureRuntime()` from `@ankhorage/infra/project` instead
42
+ of depending on concrete provider forward names.
43
+
36
44
  ## Supabase Runtime
37
45
 
38
46
  Runtime ownership is Kubernetes. Migration authoring/history remains Supabase migration
@@ -452,6 +452,19 @@ describe('generateMinikubeBaseArtifacts app-owned cluster model', () => {
452
452
  expect(portForwardScript).toContain('service/gateway');
453
453
  expect(portForwardScript).toContain('service/studio');
454
454
  expect(portForwardScript).toContain('service/postgres');
455
+ expect(portForwardScript).toContain('RUNTIME_FORWARD_NAMES=(app supabase-gateway)');
456
+ expect(portForwardScript).toContain(
457
+ 'ALL_FORWARD_NAMES=(app supabase-gateway studio db-migration)',
458
+ );
459
+ expect(portForwardScript).toContain(
460
+ 'start:runtime) for_each_forward start "${RUNTIME_FORWARD_NAMES[@]}"',
461
+ );
462
+ expect(portForwardScript).toContain(
463
+ 'stop:runtime) for_each_forward stop "${RUNTIME_FORWARD_NAMES[@]}"',
464
+ );
465
+ expect(portForwardScript).toContain(
466
+ 'status:runtime) for_each_forward status "${RUNTIME_FORWARD_NAMES[@]}"',
467
+ );
455
468
  expect(portForwardScript).toContain('${PROFILE}-${1}.pid');
456
469
  expect(portForwardScript).toContain('crashed stale_pid');
457
470
  expect(portForwardScript).toContain('is_port_accepting()');
@@ -488,6 +501,26 @@ describe('generateMinikubeBaseArtifacts app-owned cluster model', () => {
488
501
  expect(upScript).toContain('trap - ERR');
489
502
  expect(downScript).toContain('port-forward.sh" stop all');
490
503
  });
504
+
505
+ test('generates an app-only runtime forward group without Supabase', () => {
506
+ const result = generateInfrastructure(
507
+ {
508
+ deployment: {
509
+ target: 'minikube',
510
+ monitoring: false,
511
+ },
512
+ modules: [],
513
+ },
514
+ {
515
+ appManifest: createAppManifest('frontend-only'),
516
+ },
517
+ );
518
+ const portForwardScript = getFile(result.files, 'infra/minikube/scripts/port-forward.sh');
519
+
520
+ expect(portForwardScript).toContain('RUNTIME_FORWARD_NAMES=(app)');
521
+ expect(portForwardScript).toContain('ALL_FORWARD_NAMES=(app)');
522
+ expect(portForwardScript).not.toContain('RUNTIME_FORWARD_NAMES=(app supabase-gateway)');
523
+ });
491
524
  });
492
525
 
493
526
  function createSupabaseManifest(): InfraManifestInput {
@@ -385,6 +385,15 @@ host-level Supabase Compose runtime.
385
385
  - \`reset.sh\`: requires \`ANKH_RESET_CONFIRM=${appSlug}\`; ${resetDescription}. It does not delete the Minikube profile.
386
386
  - \`destroy.sh\`: deletes only \`minikube -p ${appSlug}\`.
387
387
 
388
+ ### Port-forward groups
389
+
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\`.
393
+ - \`runtime\` excludes \`studio\` and \`db-migration\`, which are operational/bootstrap endpoints.
394
+ - \`all\` retains every generated forward, including operational/bootstrap endpoints.
395
+ - Concrete targets remain available for individual lifecycle control.
396
+
388
397
  ## Generated Resources
389
398
 
390
399
  ${resourceLines}
@@ -2992,6 +3001,11 @@ function getPortForwardScript(args: {
2992
3001
  supabaseHostPorts: SupabaseHostPorts;
2993
3002
  }): string {
2994
3003
  const { appSlug, supabaseKubernetesEnabled, supabaseHostPorts } = args;
3004
+ const runtimeForwardNames = ['app', ...(supabaseKubernetesEnabled ? ['supabase-gateway'] : [])];
3005
+ const allForwardNames = [
3006
+ ...runtimeForwardNames,
3007
+ ...(supabaseKubernetesEnabled ? ['studio', 'db-migration'] : []),
3008
+ ];
2995
3009
 
2996
3010
  return `#!/usr/bin/env bash
2997
3011
  set -euo pipefail
@@ -3010,6 +3024,8 @@ SUPABASE_STUDIO_FORWARD_LOCAL_PORT="\${SUPABASE_STUDIO_FORWARD_LOCAL_PORT:-${sup
3010
3024
  SUPABASE_STUDIO_FORWARD_REMOTE_PORT="\${SUPABASE_STUDIO_FORWARD_REMOTE_PORT:-3000}"
3011
3025
  SUPABASE_DB_FORWARD_LOCAL_PORT="\${SUPABASE_DB_FORWARD_LOCAL_PORT:-${supabaseHostPorts.db}}"
3012
3026
  SUPABASE_DB_FORWARD_REMOTE_PORT="\${SUPABASE_DB_FORWARD_REMOTE_PORT:-5432}"
3027
+ RUNTIME_FORWARD_NAMES=(${runtimeForwardNames.join(' ')})
3028
+ ALL_FORWARD_NAMES=(${allForwardNames.join(' ')})
3013
3029
 
3014
3030
  if [[ -f "\${ROOT_DIR}/.env" ]]; then
3015
3031
  set -a
@@ -3295,26 +3311,28 @@ status_forward() {
3295
3311
 
3296
3312
  for_each_forward() {
3297
3313
  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
3314
+ shift
3315
+ local name
3316
+ for name in "\${@}"; do
3317
+ "\${action}_forward" "\${name}"
3318
+ done
3304
3319
  }
3305
3320
 
3306
3321
  ACTION="\${1:-start}"
3307
3322
  NAME="\${2:-all}"
3308
3323
 
3309
3324
  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 ;;
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[@]}" ;;
3313
3331
  start:*) start_forward "\${NAME}" ;;
3314
3332
  stop:*) stop_forward "\${NAME}" ;;
3315
3333
  status:*) status_forward "\${NAME}" ;;
3316
3334
  *)
3317
- echo "Usage: ./scripts/port-forward.sh {start|stop|status} {app|supabase-gateway|studio|db-migration|all}"
3335
+ echo "Usage: ./scripts/port-forward.sh {start|stop|status} {app|supabase-gateway|studio|db-migration|runtime|all}"
3318
3336
  exit 1
3319
3337
  ;;
3320
3338
  esac
@@ -0,0 +1,288 @@
1
+ import { afterEach, describe, expect, test } from 'bun:test';
2
+ import { promises as fs } from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+
6
+ import { generateInfrastructure } from '../../../index.js';
7
+ import { createAppManifest } from '../../../testSupport.js';
8
+ import type { InfraManifestInput } from '../../../types.js';
9
+
10
+ const temporaryPaths = new Set<string>();
11
+
12
+ afterEach(async () => {
13
+ for (const rootPath of temporaryPaths) {
14
+ await stopFixtureProcesses(rootPath);
15
+ await fs.rm(rootPath, { force: true, recursive: true });
16
+ }
17
+ temporaryPaths.clear();
18
+ });
19
+
20
+ describe('generated Minikube runtime port-forward lifecycle', () => {
21
+ test(
22
+ 'keeps healthy app forwarding, repairs a stopped Supabase gateway, and isolates operational forwards',
23
+ async () => {
24
+ const fixture = await createPortForwardFixture(createSupabaseManifest(), 'runtime-recovery');
25
+
26
+ const firstStart = await runPortForward(fixture, 'start', 'runtime');
27
+ expect(firstStart.stdout).toContain('app: started');
28
+ expect(firstStart.stdout).toContain('supabase-gateway: started');
29
+
30
+ const appPid = await readForwardPid(fixture, 'app');
31
+ const firstGatewayPid = await readForwardPid(fixture, 'supabase-gateway');
32
+ await expectForwardPid(fixture, 'studio', false);
33
+ await expectForwardPid(fixture, 'db-migration', false);
34
+
35
+ const repeatedStart = await runPortForward(fixture, 'start', 'runtime');
36
+ expect(repeatedStart.stdout).toContain(`app: running (pid ${appPid})`);
37
+ expect(repeatedStart.stdout).toContain(`supabase-gateway: running (pid ${firstGatewayPid})`);
38
+ expect(await readForwardPid(fixture, 'app')).toBe(appPid);
39
+ expect(await readForwardPid(fixture, 'supabase-gateway')).toBe(firstGatewayPid);
40
+
41
+ await runPortForward(fixture, 'stop', 'supabase-gateway');
42
+ expect(await readForwardPid(fixture, 'app')).toBe(appPid);
43
+ await expectForwardPid(fixture, 'supabase-gateway', false);
44
+
45
+ const recovery = await runPortForward(fixture, 'start', 'runtime');
46
+ expect(recovery.stdout).toContain(`app: running (pid ${appPid})`);
47
+ expect(recovery.stdout).toContain('supabase-gateway: started');
48
+ expect(await readForwardPid(fixture, 'app')).toBe(appPid);
49
+ expect(await readForwardPid(fixture, 'supabase-gateway')).not.toBe(firstGatewayPid);
50
+ await expectForwardPid(fixture, 'studio', false);
51
+ await expectForwardPid(fixture, 'db-migration', false);
52
+
53
+ await runPortForward(fixture, 'stop', 'runtime');
54
+ },
55
+ { timeout: 30_000 },
56
+ );
57
+
58
+ test(
59
+ 'starts only the app forward when no provider owns another runtime endpoint',
60
+ async () => {
61
+ const fixture = await createPortForwardFixture(
62
+ { deployment: { target: 'minikube', monitoring: false }, modules: [] },
63
+ 'runtime-app-only',
64
+ );
65
+
66
+ const result = await runPortForward(fixture, 'start', 'runtime');
67
+
68
+ expect(result.stdout).toContain('app: started');
69
+ expect(result.stdout).not.toContain('supabase-gateway');
70
+ await expectForwardPid(fixture, 'app', true);
71
+ await expectForwardPid(fixture, 'supabase-gateway', false);
72
+ await expectForwardPid(fixture, 'studio', false);
73
+ await expectForwardPid(fixture, 'db-migration', false);
74
+
75
+ await runPortForward(fixture, 'stop', 'runtime');
76
+ },
77
+ { timeout: 15_000 },
78
+ );
79
+ });
80
+
81
+ interface PortForwardFixture {
82
+ readonly env: Record<string, string>;
83
+ readonly processDirectory: string;
84
+ readonly profile: string;
85
+ readonly rootPath: string;
86
+ readonly scriptPath: string;
87
+ }
88
+
89
+ async function createPortForwardFixture(
90
+ manifest: InfraManifestInput,
91
+ profile: string,
92
+ ): Promise<PortForwardFixture> {
93
+ const rootPath = await fs.mkdtemp(path.join(os.tmpdir(), 'infra-port-forward-runtime-'));
94
+ temporaryPaths.add(rootPath);
95
+ const generated = generateInfrastructure(manifest, {
96
+ appManifest: createAppManifest(profile),
97
+ });
98
+ const generatedScript = generated.files.find(
99
+ (file) => file.path === 'infra/minikube/scripts/port-forward.sh',
100
+ );
101
+ if (generatedScript === undefined) {
102
+ throw new Error('Generated port-forward script was missing.');
103
+ }
104
+
105
+ const scriptPath = path.join(rootPath, generatedScript.path);
106
+ const fakeBin = path.join(rootPath, 'fake-bin');
107
+ const processDirectory = path.join(rootPath, 'fake-processes');
108
+ const serverScript = path.join(rootPath, 'forward-server.ts');
109
+ await fs.mkdir(path.dirname(scriptPath), { recursive: true });
110
+ await fs.mkdir(fakeBin, { recursive: true });
111
+ await fs.mkdir(processDirectory, { recursive: true });
112
+ await fs.writeFile(scriptPath, generatedScript.content, 'utf8');
113
+ await fs.chmod(scriptPath, 0o755);
114
+ await fs.writeFile(
115
+ serverScript,
116
+ [
117
+ "const port = Number.parseInt(Bun.argv[2] ?? '', 10);",
118
+ "Bun.serve({ hostname: '127.0.0.1', port, fetch: () => new Response('ok') });",
119
+ 'await new Promise(() => undefined);',
120
+ '',
121
+ ].join('\n'),
122
+ 'utf8',
123
+ );
124
+ await writeExecutable(
125
+ path.join(fakeBin, 'kubectl'),
126
+ `#!/usr/bin/env bash
127
+ set -euo pipefail
128
+ if [[ " $* " == *" get "* ]]; then
129
+ exit 0
130
+ fi
131
+ if [[ " $* " != *" port-forward "* ]]; then
132
+ exit 1
133
+ fi
134
+ endpoint="\${!#}"
135
+ local_port="\${endpoint%%:*}"
136
+ printf 'kubectl %s\n' "$*" > "\${FAKE_PROCESS_DIRECTORY}/$$.command"
137
+ exec bun "\${FAKE_FORWARD_SERVER_SCRIPT}" "\${local_port}"
138
+ `,
139
+ );
140
+ await writeExecutable(
141
+ path.join(fakeBin, 'ps'),
142
+ `#!/usr/bin/env bash
143
+ set -euo pipefail
144
+ pid=""
145
+ format=""
146
+ while [[ $# -gt 0 ]]; do
147
+ case "$1" in
148
+ -p) pid="$2"; shift 2 ;;
149
+ -o) format="$2"; shift 2 ;;
150
+ *) shift ;;
151
+ esac
152
+ done
153
+ command_file="\${FAKE_PROCESS_DIRECTORY}/\${pid}.command"
154
+ if [[ -f "\${command_file}" && "\${format}" == "comm=" ]]; then
155
+ echo kubectl
156
+ elif [[ -f "\${command_file}" && "\${format}" == "command=" ]]; then
157
+ cat "\${command_file}"
158
+ else
159
+ /bin/ps -p "\${pid}" -o "\${format}"
160
+ fi
161
+ `,
162
+ );
163
+
164
+ const ports = reserveAvailablePorts(4);
165
+ return {
166
+ env: {
167
+ ...process.env,
168
+ ANKH_APP_SLUG: profile,
169
+ APP_PORT_FORWARD_LOCAL_PORT: String(ports[0]),
170
+ SUPABASE_GATEWAY_FORWARD_LOCAL_PORT: String(ports[1]),
171
+ SUPABASE_STUDIO_FORWARD_LOCAL_PORT: String(ports[2]),
172
+ SUPABASE_DB_FORWARD_LOCAL_PORT: String(ports[3]),
173
+ FAKE_FORWARD_SERVER_SCRIPT: serverScript,
174
+ FAKE_PROCESS_DIRECTORY: processDirectory,
175
+ PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
176
+ },
177
+ processDirectory,
178
+ profile,
179
+ rootPath,
180
+ scriptPath,
181
+ };
182
+ }
183
+
184
+ async function runPortForward(
185
+ fixture: PortForwardFixture,
186
+ action: string,
187
+ name: string,
188
+ ): Promise<{ readonly exitCode: number; readonly stderr: string; readonly stdout: string }> {
189
+ const process = Bun.spawn(['bash', fixture.scriptPath, action, name], {
190
+ env: fixture.env,
191
+ stderr: 'pipe',
192
+ stdout: 'pipe',
193
+ });
194
+ const [exitCode, stdout, stderr] = await Promise.all([
195
+ process.exited,
196
+ new Response(process.stdout).text(),
197
+ new Response(process.stderr).text(),
198
+ ]);
199
+ if (exitCode !== 0) {
200
+ throw new Error(`port-forward ${action} ${name} failed (${exitCode}): ${stdout}${stderr}`);
201
+ }
202
+ return { exitCode, stderr, stdout };
203
+ }
204
+
205
+ async function readForwardPid(fixture: PortForwardFixture, name: string): Promise<number> {
206
+ const value = await fs.readFile(forwardPidPath(fixture, name), 'utf8');
207
+ return Number.parseInt(value.trim(), 10);
208
+ }
209
+
210
+ async function expectForwardPid(
211
+ fixture: PortForwardFixture,
212
+ name: string,
213
+ exists: boolean,
214
+ ): Promise<void> {
215
+ expect(await pathExists(forwardPidPath(fixture, name))).toBe(exists);
216
+ }
217
+
218
+ function forwardPidPath(fixture: PortForwardFixture, name: string): string {
219
+ return path.join(
220
+ fixture.rootPath,
221
+ 'infra/minikube/.state/forwards',
222
+ `${fixture.profile}-${name}.pid`,
223
+ );
224
+ }
225
+
226
+ function reserveAvailablePorts(count: number): number[] {
227
+ const listeners = Array.from({ length: count }, () =>
228
+ Bun.listen({
229
+ hostname: '127.0.0.1',
230
+ port: 0,
231
+ socket: { data: () => undefined },
232
+ }),
233
+ );
234
+ const ports = listeners.map((listener) => listener.port);
235
+ listeners.forEach((listener) => listener.stop(true));
236
+ return ports;
237
+ }
238
+
239
+ async function writeExecutable(filePath: string, content: string): Promise<void> {
240
+ await fs.writeFile(filePath, content, 'utf8');
241
+ await fs.chmod(filePath, 0o755);
242
+ }
243
+
244
+ async function pathExists(filePath: string): Promise<boolean> {
245
+ try {
246
+ await fs.access(filePath);
247
+ return true;
248
+ } catch {
249
+ return false;
250
+ }
251
+ }
252
+
253
+ async function stopFixtureProcesses(rootPath: string): Promise<void> {
254
+ const stateDirectory = path.join(rootPath, 'infra/minikube/.state/forwards');
255
+ let entries: string[];
256
+ try {
257
+ entries = await fs.readdir(stateDirectory);
258
+ } catch {
259
+ return;
260
+ }
261
+
262
+ await Promise.all(
263
+ entries
264
+ .filter((entry) => entry.endsWith('.pid'))
265
+ .map(async (entry) => {
266
+ const pid = Number.parseInt(
267
+ await fs.readFile(path.join(stateDirectory, entry), 'utf8'),
268
+ 10,
269
+ );
270
+ if (Number.isInteger(pid)) {
271
+ try {
272
+ process.kill(pid, 'SIGKILL');
273
+ } catch {
274
+ // The generated lifecycle may already have stopped the process.
275
+ }
276
+ }
277
+ }),
278
+ );
279
+ }
280
+
281
+ function createSupabaseManifest(): InfraManifestInput {
282
+ return {
283
+ deployment: { target: 'minikube', monitoring: false },
284
+ auth: { scope: 'global', provider: 'supabase' },
285
+ database: { provider: 'supabase', tier: 'dev' },
286
+ modules: [],
287
+ };
288
+ }