@ankhorage/infra 2.0.1 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +1 -1
  3. package/dist/project/index.d.ts +4 -0
  4. package/dist/project/index.d.ts.map +1 -0
  5. package/dist/project/index.js +4 -0
  6. package/dist/project/index.js.map +1 -0
  7. package/dist/project.d.ts.map +1 -1
  8. package/dist/project.js +5 -58
  9. package/dist/project.js.map +1 -1
  10. package/dist/readme-usage.js +13 -9
  11. package/dist/readme-usage.js.map +1 -1
  12. package/dist/runtime.d.ts +23 -1
  13. package/dist/runtime.d.ts.map +1 -1
  14. package/dist/runtime.js +82 -24
  15. package/dist/runtime.js.map +1 -1
  16. package/dist/testSupport.js +1 -1
  17. package/dist/testSupport.js.map +1 -1
  18. package/package.json +7 -2
  19. package/src/adapters/minikube/auth/supabase/index.test.ts +1 -1
  20. package/src/adapters/minikube/auth/supabase/oauthRuntime.test.ts +1 -1
  21. package/src/adapters/minikube/auth/supabase/profile.integration.test.ts +1 -1
  22. package/src/adapters/minikube/auth/supabase/providerAssignments.test.ts +1 -1
  23. package/src/adapters/minikube/base/appImageBuild.test.ts +1 -1
  24. package/src/adapters/minikube/base/index.test.ts +2 -2
  25. package/src/adapters/minikube/base/twoAppIsolation.integration.test.ts +2 -2
  26. package/src/adapters/minikube/secrets/index.test.ts +1 -1
  27. package/src/commands.test.ts +3 -3
  28. package/src/generatedApiDatabase.test.ts +1 -1
  29. package/src/index.test.ts +6 -6
  30. package/src/minikubeImageLifecycle.test.ts +1 -1
  31. package/src/package.test.ts +5 -0
  32. package/src/project/index.test.ts +70 -0
  33. package/src/project/index.ts +17 -0
  34. package/src/project.test.ts +2 -2
  35. package/src/project.ts +6 -81
  36. package/src/projectContractsParser.test.ts +49 -0
  37. package/src/projectInfrastructure.test.ts +5 -5
  38. package/src/readme-usage.ts +13 -9
  39. package/src/runtime.ts +131 -38
  40. package/src/testSupport.ts +1 -1
package/src/runtime.ts CHANGED
@@ -4,7 +4,17 @@ import path from 'path';
4
4
 
5
5
  import type { InfraCommandContext } from './commandContext.js';
6
6
 
7
- export type InfraLifecycleScript = 'down' | 'status' | 'up';
7
+ export type InfraLifecycleScript = 'destroy' | 'down' | 'port-forward' | 'reset' | 'status' | 'up';
8
+
9
+ export interface InfraScriptOutput {
10
+ readonly stderr: string;
11
+ readonly stdout: string;
12
+ }
13
+
14
+ export interface InfraPortForwardInfo {
15
+ readonly localPort: number;
16
+ readonly url: string;
17
+ }
8
18
 
9
19
  export class InfraScriptExecutionError extends Error {
10
20
  readonly exitCode: number | null;
@@ -25,13 +35,16 @@ export class InfraScriptExecutionError extends Error {
25
35
  }
26
36
  }
27
37
 
28
- export async function runProjectInfraScript(args: {
29
- readonly context: InfraCommandContext;
38
+ export async function runProjectInfrastructureLifecycle(args: {
39
+ readonly args?: readonly string[];
40
+ readonly env?: Readonly<Record<string, string | undefined>>;
41
+ readonly onStderr?: (chunk: string) => void;
42
+ readonly onStdout?: (chunk: string) => void;
30
43
  readonly projectId: string;
31
44
  readonly projectPath: string;
32
45
  readonly script: InfraLifecycleScript;
33
46
  readonly target: string;
34
- }): Promise<void> {
47
+ }): Promise<InfraScriptOutput> {
35
48
  const scriptPath = resolveProjectInfraScriptPath(args);
36
49
  if (!(await pathExists(scriptPath))) {
37
50
  throw new Error(
@@ -39,12 +52,56 @@ export async function runProjectInfraScript(args: {
39
52
  );
40
53
  }
41
54
 
42
- await runShellScript({
43
- context: args.context,
55
+ return runShellScript({
56
+ args: args.args ?? [],
57
+ env: args.env ?? process.env,
58
+ onStderr: args.onStderr,
59
+ onStdout: args.onStdout,
44
60
  scriptPath,
45
61
  });
46
62
  }
47
63
 
64
+ export async function resolveProjectInfrastructurePortForward(args: {
65
+ readonly projectPath: string;
66
+ readonly target: string;
67
+ }): Promise<InfraPortForwardInfo> {
68
+ const infraRoot = path.join(args.projectPath, 'infra', args.target);
69
+ const envPath = path.join(infraRoot, '.env');
70
+ const fallbackEnvPath = path.join(infraRoot, '.env.example');
71
+ const sourcePath = (await pathExists(envPath)) ? envPath : fallbackEnvPath;
72
+ const env = await readSimpleEnvMap(sourcePath);
73
+ const localPort = parsePositivePort(env.get('APP_PORT_FORWARD_LOCAL_PORT'));
74
+
75
+ if (localPort === null) {
76
+ throw new Error(
77
+ `Generated Infra did not provide a valid APP_PORT_FORWARD_LOCAL_PORT in ${sourcePath}. Regenerate infrastructure first.`,
78
+ );
79
+ }
80
+
81
+ return {
82
+ localPort,
83
+ url: `http://127.0.0.1:${localPort}`,
84
+ };
85
+ }
86
+
87
+ export async function runProjectInfraScript(args: {
88
+ readonly context: InfraCommandContext;
89
+ readonly projectId: string;
90
+ readonly projectPath: string;
91
+ readonly script: InfraLifecycleScript;
92
+ readonly target: string;
93
+ }): Promise<void> {
94
+ await runProjectInfrastructureLifecycle({
95
+ env: args.context.env,
96
+ onStderr: (chunk) => args.context.writeStderr(chunk),
97
+ onStdout: (chunk) => args.context.writeStdout(chunk),
98
+ projectId: args.projectId,
99
+ projectPath: args.projectPath,
100
+ script: args.script,
101
+ target: args.target,
102
+ });
103
+ }
104
+
48
105
  export function resolveProjectInfraScriptPath(args: {
49
106
  readonly projectPath: string;
50
107
  readonly script: InfraLifecycleScript;
@@ -63,60 +120,96 @@ function getInfraScriptsDirectory(target: string): string {
63
120
  }
64
121
 
65
122
  async function runShellScript(args: {
66
- readonly context: InfraCommandContext;
123
+ readonly args: readonly string[];
124
+ readonly env: Readonly<Record<string, string | undefined>>;
125
+ readonly onStderr?: (chunk: string) => void;
126
+ readonly onStdout?: (chunk: string) => void;
67
127
  readonly scriptPath: string;
68
- }): Promise<void> {
69
- await new Promise<void>((resolve, reject) => {
70
- const child = spawn('bash', [args.scriptPath], {
128
+ }): Promise<InfraScriptOutput> {
129
+ return new Promise<InfraScriptOutput>((resolve, reject) => {
130
+ const child = spawn('bash', [args.scriptPath, ...args.args], {
71
131
  cwd: path.dirname(args.scriptPath),
72
- env: { ...args.context.env },
132
+ env: { ...args.env },
73
133
  stdio: ['ignore', 'pipe', 'pipe'],
74
134
  });
75
135
 
76
136
  let stdout = '';
77
137
  let stderr = '';
78
-
79
138
  child.stdout.setEncoding('utf8');
139
+ child.stderr.setEncoding('utf8');
80
140
  child.stdout.on('data', (chunk: string) => {
81
141
  stdout += chunk;
82
- args.context.writeStdout(chunk);
142
+ args.onStdout?.(chunk);
83
143
  });
84
-
85
- child.stderr.setEncoding('utf8');
86
144
  child.stderr.on('data', (chunk: string) => {
87
145
  stderr += chunk;
88
- args.context.writeStderr(chunk);
89
- });
90
-
91
- child.once('error', (error) => {
92
- reject(
93
- new InfraScriptExecutionError({
94
- exitCode: null,
95
- message: `Failed to start infra script '${args.scriptPath}': ${error.message}`,
96
- stderr,
97
- stdout,
98
- }),
99
- );
146
+ args.onStderr?.(chunk);
100
147
  });
101
-
148
+ child.once('error', (error) =>
149
+ reject(createStartError(args.scriptPath, error, stdout, stderr)),
150
+ );
102
151
  child.once('close', (exitCode) => {
103
152
  if (exitCode === 0) {
104
- resolve();
153
+ resolve({ stderr, stdout });
105
154
  return;
106
155
  }
107
-
108
- reject(
109
- new InfraScriptExecutionError({
110
- exitCode,
111
- message: `Infra script '${args.scriptPath}' exited with code ${exitCode ?? 'unknown'}.`,
112
- stderr,
113
- stdout,
114
- }),
115
- );
156
+ reject(createExitError(args.scriptPath, exitCode, stdout, stderr));
116
157
  });
117
158
  });
118
159
  }
119
160
 
161
+ function createStartError(
162
+ scriptPath: string,
163
+ error: Error,
164
+ stdout: string,
165
+ stderr: string,
166
+ ): InfraScriptExecutionError {
167
+ return new InfraScriptExecutionError({
168
+ exitCode: null,
169
+ message: `Failed to start infra script '${scriptPath}': ${error.message}`,
170
+ stderr,
171
+ stdout,
172
+ });
173
+ }
174
+
175
+ function createExitError(
176
+ scriptPath: string,
177
+ exitCode: number | null,
178
+ stdout: string,
179
+ stderr: string,
180
+ ): InfraScriptExecutionError {
181
+ return new InfraScriptExecutionError({
182
+ exitCode,
183
+ message: `Infra script '${scriptPath}' exited with code ${exitCode ?? 'unknown'}.`,
184
+ stderr,
185
+ stdout,
186
+ });
187
+ }
188
+
189
+ async function readSimpleEnvMap(filePath: string): Promise<Map<string, string>> {
190
+ const out = new Map<string, string>();
191
+ const content = await fs.readFile(filePath, 'utf8');
192
+ for (const line of content.split(/\r?\n/u)) {
193
+ const trimmed = line.trim();
194
+ if (!trimmed || trimmed.startsWith('#')) continue;
195
+ const separator = trimmed.indexOf('=');
196
+ if (separator <= 0) continue;
197
+ const key = trimmed.slice(0, separator).trim();
198
+ const value = trimmed
199
+ .slice(separator + 1)
200
+ .trim()
201
+ .replace(/^['"]|['"]$/g, '');
202
+ out.set(key, value);
203
+ }
204
+ return out;
205
+ }
206
+
207
+ function parsePositivePort(value: string | undefined): number | null {
208
+ if (value === undefined) return null;
209
+ const parsed = Number.parseInt(value.trim(), 10);
210
+ return Number.isInteger(parsed) && parsed >= 1 && parsed <= 65_535 ? parsed : null;
211
+ }
212
+
120
213
  async function pathExists(filePath: string): Promise<boolean> {
121
214
  try {
122
215
  await fs.access(filePath);
@@ -79,7 +79,7 @@ export async function createWorkspaceFixture(
79
79
 
80
80
  export function createAppManifest(
81
81
  projectId: string,
82
- infra: AppManifest['infra'] = { plugins: [] },
82
+ infra: AppManifest['infra'] = { modules: [] },
83
83
  ): AppManifest {
84
84
  return {
85
85
  metadata: {