@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.
- package/CHANGELOG.md +18 -0
- package/README.md +1 -1
- package/dist/project/index.d.ts +4 -0
- package/dist/project/index.d.ts.map +1 -0
- package/dist/project/index.js +4 -0
- package/dist/project/index.js.map +1 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +5 -58
- package/dist/project.js.map +1 -1
- package/dist/readme-usage.js +13 -9
- package/dist/readme-usage.js.map +1 -1
- package/dist/runtime.d.ts +23 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +82 -24
- package/dist/runtime.js.map +1 -1
- package/dist/testSupport.js +1 -1
- package/dist/testSupport.js.map +1 -1
- package/package.json +7 -2
- package/src/adapters/minikube/auth/supabase/index.test.ts +1 -1
- package/src/adapters/minikube/auth/supabase/oauthRuntime.test.ts +1 -1
- package/src/adapters/minikube/auth/supabase/profile.integration.test.ts +1 -1
- package/src/adapters/minikube/auth/supabase/providerAssignments.test.ts +1 -1
- package/src/adapters/minikube/base/appImageBuild.test.ts +1 -1
- package/src/adapters/minikube/base/index.test.ts +2 -2
- package/src/adapters/minikube/base/twoAppIsolation.integration.test.ts +2 -2
- package/src/adapters/minikube/secrets/index.test.ts +1 -1
- package/src/commands.test.ts +3 -3
- package/src/generatedApiDatabase.test.ts +1 -1
- package/src/index.test.ts +6 -6
- package/src/minikubeImageLifecycle.test.ts +1 -1
- package/src/package.test.ts +5 -0
- package/src/project/index.test.ts +70 -0
- package/src/project/index.ts +17 -0
- package/src/project.test.ts +2 -2
- package/src/project.ts +6 -81
- package/src/projectContractsParser.test.ts +49 -0
- package/src/projectInfrastructure.test.ts +5 -5
- package/src/readme-usage.ts +13 -9
- package/src/runtime.ts +131 -38
- 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
|
|
29
|
-
readonly
|
|
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<
|
|
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
|
-
|
|
43
|
-
|
|
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
|
|
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<
|
|
69
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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);
|
package/src/testSupport.ts
CHANGED