@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.
- package/CHANGELOG.md +6 -0
- package/README.md +6 -1
- package/dist/adapters/minikube/base/index.js +26 -10
- package/dist/adapters/minikube/base/index.js.map +1 -1
- package/dist/project/index.d.ts +1 -1
- package/dist/project/index.d.ts.map +1 -1
- package/dist/project/index.js +1 -1
- package/dist/project/index.js.map +1 -1
- package/dist/readme-usage.js +5 -0
- package/dist/readme-usage.js.map +1 -1
- package/dist/runtime.d.ts +6 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +24 -0
- package/dist/runtime.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/minikube/README.md +9 -1
- package/src/adapters/minikube/base/index.test.ts +33 -0
- package/src/adapters/minikube/base/index.ts +28 -10
- package/src/adapters/minikube/base/portForwardRuntime.test.ts +288 -0
- package/src/adapters/minikube/base/twoAppIsolation.integration.test.ts +272 -9
- package/src/project/index.test.ts +46 -0
- package/src/project/index.ts +1 -0
- package/src/readme-usage.ts +5 -0
- package/src/runtime.ts +31 -0
package/dist/project/index.d.ts
CHANGED
|
@@ -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"}
|
package/dist/project/index.js
CHANGED
|
@@ -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,
|
|
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"]}
|
package/dist/readme-usage.js
CHANGED
|
@@ -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']);
|
package/dist/readme-usage.js.map
CHANGED
|
@@ -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
|
|
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;
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -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,
|
package/dist/runtime.js.map
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
"\${action}_forward"
|
|
3302
|
-
|
|
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:
|
|
3311
|
-
stop:
|
|
3312
|
-
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
|
+
}
|