@ankhorage/infra 3.0.0 → 3.2.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 +18 -0
- package/dist/project/index.d.ts +6 -0
- package/dist/project/index.d.ts.map +1 -0
- package/dist/project/index.js +6 -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/projectDatabase.d.ts +5 -0
- package/dist/projectDatabase.d.ts.map +1 -0
- package/dist/projectDatabase.js +22 -0
- package/dist/projectDatabase.js.map +1 -0
- package/dist/projectEnvironment.d.ts +6 -0
- package/dist/projectEnvironment.d.ts.map +1 -0
- package/dist/projectEnvironment.js +45 -0
- package/dist/projectEnvironment.js.map +1 -0
- package/dist/projectInfrastructure.d.ts +16 -0
- package/dist/projectInfrastructure.d.ts.map +1 -1
- package/dist/projectInfrastructure.js +25 -0
- package/dist/projectInfrastructure.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 +64 -24
- package/dist/runtime.js.map +1 -1
- package/package.json +7 -2
- package/src/package.test.ts +5 -0
- package/src/project/index.test.ts +149 -0
- package/src/project/index.ts +21 -0
- package/src/project.ts +6 -81
- package/src/projectContractsParser.test.ts +49 -0
- package/src/projectDatabase.ts +27 -0
- package/src/projectEnvironment.ts +51 -0
- package/src/projectInfrastructure.ts +44 -0
- package/src/readme-usage.ts +13 -9
- package/src/runtime.ts +114 -38
|
@@ -25,6 +25,18 @@ export interface InfraSyncResult {
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export interface InfraProjectInspection {
|
|
29
|
+
readonly generated: boolean;
|
|
30
|
+
readonly generatedAt: string | null;
|
|
31
|
+
readonly hasDeployment: boolean;
|
|
32
|
+
readonly target: string | null;
|
|
33
|
+
readonly trackedFiles: number;
|
|
34
|
+
readonly warnings: readonly string[];
|
|
35
|
+
readonly skipped?: {
|
|
36
|
+
readonly reason: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
28
40
|
export async function syncProjectInfrastructure(args: {
|
|
29
41
|
readonly generateInfrastructureImpl?: typeof generateInfrastructure;
|
|
30
42
|
readonly manifest: AppManifest;
|
|
@@ -97,6 +109,38 @@ export async function syncProjectInfrastructure(args: {
|
|
|
97
109
|
};
|
|
98
110
|
}
|
|
99
111
|
|
|
112
|
+
export async function inspectProjectInfrastructure(args: {
|
|
113
|
+
readonly manifest: AppManifest;
|
|
114
|
+
readonly projectId: string;
|
|
115
|
+
readonly projectPath: string;
|
|
116
|
+
}): Promise<InfraProjectInspection> {
|
|
117
|
+
const supportWarnings = validateInfraSupport(args.manifest.infra);
|
|
118
|
+
|
|
119
|
+
if (args.projectId === STUDIO_PROJECT_ID) {
|
|
120
|
+
return {
|
|
121
|
+
generated: false,
|
|
122
|
+
generatedAt: null,
|
|
123
|
+
hasDeployment: false,
|
|
124
|
+
target: null,
|
|
125
|
+
trackedFiles: 0,
|
|
126
|
+
warnings: supportWarnings,
|
|
127
|
+
skipped: {
|
|
128
|
+
reason: 'apps/studio is the dashboard and is not a generated app target.',
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const ledger = await readInfraLedger(args.projectPath);
|
|
134
|
+
return {
|
|
135
|
+
generated: ledger !== null,
|
|
136
|
+
generatedAt: ledger?.generatedAt ?? null,
|
|
137
|
+
hasDeployment: args.manifest.infra.deployment !== undefined,
|
|
138
|
+
target: args.manifest.infra.deployment?.target ?? ledger?.target ?? null,
|
|
139
|
+
trackedFiles: ledger?.files.length ?? 0,
|
|
140
|
+
warnings: uniqueStrings([...supportWarnings, ...(ledger?.warnings ?? [])]),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
100
144
|
export async function resolveProjectInfrastructureTarget(args: {
|
|
101
145
|
readonly manifest: AppManifest;
|
|
102
146
|
readonly projectPath: string;
|
package/src/readme-usage.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { runCli } from './cli/bin.js';
|
|
2
2
|
|
|
3
3
|
/***
|
|
4
|
-
* Provider and
|
|
4
|
+
* Provider, CLI, and project API surface
|
|
5
5
|
*
|
|
6
|
-
* `@ankhorage/infra` owns
|
|
6
|
+
* `@ankhorage/infra` owns infrastructure generation, project reconciliation,
|
|
7
|
+
* and generated lifecycle execution.
|
|
7
8
|
*
|
|
8
|
-
* The same
|
|
9
|
+
* The same owner implementation backs CLI consumers and the public
|
|
10
|
+
* `@ankhorage/infra/project` application-service boundary. Trusted hosts such
|
|
11
|
+
* as Studio should call that subpath instead of reproducing ledger, generated
|
|
12
|
+
* file, lifecycle-script, or port-forward configuration semantics.
|
|
13
|
+
*
|
|
14
|
+
* CLI entrypoints:
|
|
9
15
|
*
|
|
10
16
|
* - `ankh infra ...`
|
|
11
17
|
* - `bunx @ankhorage/infra ...`
|
|
@@ -18,12 +24,10 @@ import { runCli } from './cli/bin.js';
|
|
|
18
24
|
* - `up`
|
|
19
25
|
* - `down`
|
|
20
26
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* - pass `[project]`, or
|
|
26
|
-
* - omit it when cwd is already inside `apps/<project>`
|
|
27
|
+
* Project resolution for the CLI is workspace-aware: pass `[project]`, or
|
|
28
|
+
* omit it when cwd is already inside `apps/<project>`. Programmatic project
|
|
29
|
+
* operations accept an explicit project path and do not require Studio or a
|
|
30
|
+
* particular workspace layout.
|
|
27
31
|
*
|
|
28
32
|
* @usage
|
|
29
33
|
*/
|
package/src/runtime.ts
CHANGED
|
@@ -3,8 +3,19 @@ import { promises as fs } from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
|
|
5
5
|
import type { InfraCommandContext } from './commandContext.js';
|
|
6
|
+
import { readProjectInfrastructureEnvironment } from './projectEnvironment.js';
|
|
6
7
|
|
|
7
|
-
export type InfraLifecycleScript = 'down' | 'status' | 'up';
|
|
8
|
+
export type InfraLifecycleScript = 'destroy' | 'down' | 'port-forward' | 'reset' | 'status' | 'up';
|
|
9
|
+
|
|
10
|
+
export interface InfraScriptOutput {
|
|
11
|
+
readonly stderr: string;
|
|
12
|
+
readonly stdout: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface InfraPortForwardInfo {
|
|
16
|
+
readonly localPort: number;
|
|
17
|
+
readonly url: string;
|
|
18
|
+
}
|
|
8
19
|
|
|
9
20
|
export class InfraScriptExecutionError extends Error {
|
|
10
21
|
readonly exitCode: number | null;
|
|
@@ -25,13 +36,16 @@ export class InfraScriptExecutionError extends Error {
|
|
|
25
36
|
}
|
|
26
37
|
}
|
|
27
38
|
|
|
28
|
-
export async function
|
|
29
|
-
readonly
|
|
39
|
+
export async function runProjectInfrastructureLifecycle(args: {
|
|
40
|
+
readonly args?: readonly string[];
|
|
41
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
42
|
+
readonly onStderr?: (chunk: string) => void;
|
|
43
|
+
readonly onStdout?: (chunk: string) => void;
|
|
30
44
|
readonly projectId: string;
|
|
31
45
|
readonly projectPath: string;
|
|
32
46
|
readonly script: InfraLifecycleScript;
|
|
33
47
|
readonly target: string;
|
|
34
|
-
}): Promise<
|
|
48
|
+
}): Promise<InfraScriptOutput> {
|
|
35
49
|
const scriptPath = resolveProjectInfraScriptPath(args);
|
|
36
50
|
if (!(await pathExists(scriptPath))) {
|
|
37
51
|
throw new Error(
|
|
@@ -39,12 +53,56 @@ export async function runProjectInfraScript(args: {
|
|
|
39
53
|
);
|
|
40
54
|
}
|
|
41
55
|
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
return runShellScript({
|
|
57
|
+
args: args.args ?? [],
|
|
58
|
+
env: args.env ?? process.env,
|
|
59
|
+
onStderr: args.onStderr,
|
|
60
|
+
onStdout: args.onStdout,
|
|
44
61
|
scriptPath,
|
|
45
62
|
});
|
|
46
63
|
}
|
|
47
64
|
|
|
65
|
+
export async function resolveProjectInfrastructurePortForward(args: {
|
|
66
|
+
readonly projectPath: string;
|
|
67
|
+
readonly target: string;
|
|
68
|
+
}): Promise<InfraPortForwardInfo> {
|
|
69
|
+
const environment = await readProjectInfrastructureEnvironment({
|
|
70
|
+
keys: ['APP_PORT_FORWARD_LOCAL_PORT'],
|
|
71
|
+
projectPath: args.projectPath,
|
|
72
|
+
target: args.target,
|
|
73
|
+
});
|
|
74
|
+
const localPort = parsePositivePort(environment.APP_PORT_FORWARD_LOCAL_PORT);
|
|
75
|
+
|
|
76
|
+
if (localPort === null) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
'Generated Infra did not provide a valid APP_PORT_FORWARD_LOCAL_PORT. Regenerate infrastructure first.',
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
localPort,
|
|
84
|
+
url: `http://127.0.0.1:${localPort}`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function runProjectInfraScript(args: {
|
|
89
|
+
readonly context: InfraCommandContext;
|
|
90
|
+
readonly projectId: string;
|
|
91
|
+
readonly projectPath: string;
|
|
92
|
+
readonly script: InfraLifecycleScript;
|
|
93
|
+
readonly target: string;
|
|
94
|
+
}): Promise<void> {
|
|
95
|
+
await runProjectInfrastructureLifecycle({
|
|
96
|
+
env: args.context.env,
|
|
97
|
+
onStderr: (chunk) => args.context.writeStderr(chunk),
|
|
98
|
+
onStdout: (chunk) => args.context.writeStdout(chunk),
|
|
99
|
+
projectId: args.projectId,
|
|
100
|
+
projectPath: args.projectPath,
|
|
101
|
+
script: args.script,
|
|
102
|
+
target: args.target,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
48
106
|
export function resolveProjectInfraScriptPath(args: {
|
|
49
107
|
readonly projectPath: string;
|
|
50
108
|
readonly script: InfraLifecycleScript;
|
|
@@ -63,60 +121,78 @@ function getInfraScriptsDirectory(target: string): string {
|
|
|
63
121
|
}
|
|
64
122
|
|
|
65
123
|
async function runShellScript(args: {
|
|
66
|
-
readonly
|
|
124
|
+
readonly args: readonly string[];
|
|
125
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
126
|
+
readonly onStderr?: (chunk: string) => void;
|
|
127
|
+
readonly onStdout?: (chunk: string) => void;
|
|
67
128
|
readonly scriptPath: string;
|
|
68
|
-
}): Promise<
|
|
69
|
-
|
|
70
|
-
const child = spawn('bash', [args.scriptPath], {
|
|
129
|
+
}): Promise<InfraScriptOutput> {
|
|
130
|
+
return new Promise<InfraScriptOutput>((resolve, reject) => {
|
|
131
|
+
const child = spawn('bash', [args.scriptPath, ...args.args], {
|
|
71
132
|
cwd: path.dirname(args.scriptPath),
|
|
72
|
-
env: { ...args.
|
|
133
|
+
env: { ...args.env },
|
|
73
134
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
74
135
|
});
|
|
75
136
|
|
|
76
137
|
let stdout = '';
|
|
77
138
|
let stderr = '';
|
|
78
|
-
|
|
79
139
|
child.stdout.setEncoding('utf8');
|
|
140
|
+
child.stderr.setEncoding('utf8');
|
|
80
141
|
child.stdout.on('data', (chunk: string) => {
|
|
81
142
|
stdout += chunk;
|
|
82
|
-
args.
|
|
143
|
+
args.onStdout?.(chunk);
|
|
83
144
|
});
|
|
84
|
-
|
|
85
|
-
child.stderr.setEncoding('utf8');
|
|
86
145
|
child.stderr.on('data', (chunk: string) => {
|
|
87
146
|
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
|
-
);
|
|
147
|
+
args.onStderr?.(chunk);
|
|
100
148
|
});
|
|
101
|
-
|
|
149
|
+
child.once('error', (error) =>
|
|
150
|
+
reject(createStartError(args.scriptPath, error, stdout, stderr)),
|
|
151
|
+
);
|
|
102
152
|
child.once('close', (exitCode) => {
|
|
103
153
|
if (exitCode === 0) {
|
|
104
|
-
resolve();
|
|
154
|
+
resolve({ stderr, stdout });
|
|
105
155
|
return;
|
|
106
156
|
}
|
|
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
|
-
);
|
|
157
|
+
reject(createExitError(args.scriptPath, exitCode, stdout, stderr));
|
|
116
158
|
});
|
|
117
159
|
});
|
|
118
160
|
}
|
|
119
161
|
|
|
162
|
+
function createStartError(
|
|
163
|
+
scriptPath: string,
|
|
164
|
+
error: Error,
|
|
165
|
+
stdout: string,
|
|
166
|
+
stderr: string,
|
|
167
|
+
): InfraScriptExecutionError {
|
|
168
|
+
return new InfraScriptExecutionError({
|
|
169
|
+
exitCode: null,
|
|
170
|
+
message: `Failed to start infra script '${scriptPath}': ${error.message}`,
|
|
171
|
+
stderr,
|
|
172
|
+
stdout,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function createExitError(
|
|
177
|
+
scriptPath: string,
|
|
178
|
+
exitCode: number | null,
|
|
179
|
+
stdout: string,
|
|
180
|
+
stderr: string,
|
|
181
|
+
): InfraScriptExecutionError {
|
|
182
|
+
return new InfraScriptExecutionError({
|
|
183
|
+
exitCode,
|
|
184
|
+
message: `Infra script '${scriptPath}' exited with code ${exitCode ?? 'unknown'}.`,
|
|
185
|
+
stderr,
|
|
186
|
+
stdout,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function parsePositivePort(value: string | undefined): number | null {
|
|
191
|
+
if (value === undefined) return null;
|
|
192
|
+
const parsed = Number.parseInt(value.trim(), 10);
|
|
193
|
+
return Number.isInteger(parsed) && parsed >= 1 && parsed <= 65_535 ? parsed : null;
|
|
194
|
+
}
|
|
195
|
+
|
|
120
196
|
async function pathExists(filePath: string): Promise<boolean> {
|
|
121
197
|
try {
|
|
122
198
|
await fs.access(filePath);
|