@launchfile/macos-dev 0.2.0 → 0.4.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/dist/bootstrap.d.ts +56 -8
- package/dist/bootstrap.js +170 -93
- package/dist/env-writer.d.ts +40 -11
- package/dist/env-writer.js +84 -23
- package/dist/health.d.ts +8 -2
- package/dist/health.js +11 -17
- package/dist/prereqs.js +2 -2
- package/dist/process-manager.d.ts +13 -0
- package/dist/process-manager.js +59 -3
- package/dist/process-stopper.d.ts +99 -0
- package/dist/process-stopper.js +165 -0
- package/dist/provider.d.ts +50 -0
- package/dist/provider.js +337 -28
- package/dist/redact.d.ts +55 -0
- package/dist/redact.js +92 -0
- package/dist/resources/identifiers.d.ts +22 -0
- package/dist/resources/identifiers.js +31 -0
- package/dist/resources/index.d.ts +3 -3
- package/dist/resources/index.js +21 -10
- package/dist/resources/mysql.d.ts +3 -1
- package/dist/resources/mysql.js +49 -11
- package/dist/resources/postgres.d.ts +4 -1
- package/dist/resources/postgres.js +63 -17
- package/dist/resources/redis.d.ts +3 -1
- package/dist/resources/redis.js +14 -4
- package/dist/resources/sqlite.js +2 -2
- package/dist/resources/types.d.ts +21 -3
- package/dist/runtimes/bun.js +2 -2
- package/dist/runtimes/installed-versions.d.ts +12 -0
- package/dist/runtimes/installed-versions.js +21 -0
- package/dist/runtimes/node.js +13 -8
- package/dist/runtimes/python.js +14 -7
- package/dist/runtimes/ruby.js +13 -8
- package/dist/secret-generator.js +11 -3
- package/dist/shell.d.ts +28 -6
- package/dist/shell.js +83 -28
- package/dist/state.d.ts +38 -0
- package/dist/state.js +12 -1
- package/dist/storage.d.ts +16 -3
- package/dist/storage.js +23 -6
- package/package.json +7 -4
- package/dist/__tests__/bootstrap.test.d.ts +0 -2
- package/dist/__tests__/bootstrap.test.js +0 -90
- package/dist/__tests__/dry-run.test.d.ts +0 -2
- package/dist/__tests__/dry-run.test.js +0 -141
- package/dist/__tests__/env-writer.test.d.ts +0 -2
- package/dist/__tests__/env-writer.test.js +0 -201
- package/dist/__tests__/lockfile-detect.test.d.ts +0 -2
- package/dist/__tests__/lockfile-detect.test.js +0 -75
- package/dist/__tests__/port-allocator.test.d.ts +0 -2
- package/dist/__tests__/port-allocator.test.js +0 -53
- package/dist/__tests__/secret-generator.test.d.ts +0 -2
- package/dist/__tests__/secret-generator.test.js +0 -26
- package/dist/__tests__/state.test.d.ts +0 -2
- package/dist/__tests__/state.test.js +0 -30
package/dist/bootstrap.d.ts
CHANGED
|
@@ -6,13 +6,21 @@
|
|
|
6
6
|
* stdout via regex patterns, re-runnable, and reports failures rather than
|
|
7
7
|
* deploy-failing. Spec: /spec/SPEC.md § Bootstrap stage.
|
|
8
8
|
*
|
|
9
|
-
* The command
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* The command runs through a POSIX shell (SPEC.md § Command interpretation,
|
|
10
|
+
* PROVIDERS.md §10 item 11), so `&&`, `;`, pipes, redirection, grouping and
|
|
11
|
+
* variable expansion behave as authors write them. It is passed as a single
|
|
12
|
+
* argv element to `/bin/sh -c` via spawn({ shell: false }) — it is never
|
|
13
|
+
* interpolated into a command string on this side, so the only interpreter
|
|
14
|
+
* that sees it is the `sh` this provider spawns.
|
|
14
15
|
*/
|
|
15
|
-
import { type CaptureEntry } from "@launchfile/sdk";
|
|
16
|
+
import { parseDurationMs, type CaptureEntry, type NormalizedLaunch, type ResolverContext } from "@launchfile/sdk";
|
|
17
|
+
/** Default budget for a bootstrap command when no `timeout` is declared. */
|
|
18
|
+
export declare const DEFAULT_BOOTSTRAP_TIMEOUT_MS = 120000;
|
|
19
|
+
/**
|
|
20
|
+
* The interpreter every bootstrap command runs under. `/bin/sh` is the POSIX
|
|
21
|
+
* shell guaranteed present on macOS.
|
|
22
|
+
*/
|
|
23
|
+
export declare const BOOTSTRAP_SHELL = "/bin/sh";
|
|
16
24
|
/**
|
|
17
25
|
* Result of running one bootstrap command. Captures may be empty even on
|
|
18
26
|
* success (the command may not produce matching output), and may be
|
|
@@ -38,8 +46,47 @@ export interface BootstrapResult {
|
|
|
38
46
|
* Exported for unit testing.
|
|
39
47
|
*/
|
|
40
48
|
export declare function extractCaptures(stdout: string, captures: Record<string, CaptureEntry>): Record<string, string>;
|
|
41
|
-
/**
|
|
42
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Parse a simple duration string like "5m", "30s", "1h" into milliseconds
|
|
51
|
+
* using the ratified grammar (D-48). Throws on an unparseable value —
|
|
52
|
+
* PROVIDERS.md §10.10 forbids silently substituting a default. Re-exported
|
|
53
|
+
* for unit testing.
|
|
54
|
+
*/
|
|
55
|
+
export declare const parseDuration: typeof parseDurationMs;
|
|
56
|
+
/** One planned bootstrap execution, in component declaration order. */
|
|
57
|
+
export interface BootstrapPlanItem {
|
|
58
|
+
component: string;
|
|
59
|
+
/** The $-resolved command string. */
|
|
60
|
+
command: string;
|
|
61
|
+
/** `["/bin/sh", "-c", command]` — the command as a single argv element. */
|
|
62
|
+
argv: string[];
|
|
63
|
+
timeoutMs: number;
|
|
64
|
+
capture?: Record<string, CaptureEntry>;
|
|
65
|
+
/**
|
|
66
|
+
* Set when the item cannot run at all (empty command, unparseable
|
|
67
|
+
* timeout). Bootstrap failures are reported, never thrown
|
|
68
|
+
* (SPEC.md § Failure semantics), so a bad item stays in the plan and
|
|
69
|
+
* carries its own diagnosis.
|
|
70
|
+
*/
|
|
71
|
+
error?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build the bootstrap plan for a launch. Pure — no I/O — so selection,
|
|
75
|
+
* expression resolution, argv shape and timeout handling are unit-testable.
|
|
76
|
+
*/
|
|
77
|
+
export declare function planBootstraps(launch: NormalizedLaunch, context: ResolverContext, opts?: {
|
|
78
|
+
component?: string;
|
|
79
|
+
}): BootstrapPlanItem[];
|
|
80
|
+
/** Minimal exec contract so tests can inject a fake runner. */
|
|
81
|
+
export type BootstrapExec = (cmd: string, args: string[], opts: {
|
|
82
|
+
cwd: string;
|
|
83
|
+
env: Record<string, string>;
|
|
84
|
+
timeoutMs: number;
|
|
85
|
+
}) => Promise<{
|
|
86
|
+
exitCode: number;
|
|
87
|
+
stdout: string;
|
|
88
|
+
stderr: string;
|
|
89
|
+
}>;
|
|
43
90
|
/**
|
|
44
91
|
* Public entry point for `launch bootstrap`. Loads the Launchfile, rebuilds
|
|
45
92
|
* the resolver context from persisted state (so $app.url resolves to the
|
|
@@ -54,5 +101,6 @@ export declare function parseDuration(s: string): number;
|
|
|
54
101
|
export declare function launchBootstrap(opts?: {
|
|
55
102
|
component?: string;
|
|
56
103
|
projectDir?: string;
|
|
104
|
+
exec?: BootstrapExec;
|
|
57
105
|
}): Promise<BootstrapResult[]>;
|
|
58
106
|
//# sourceMappingURL=bootstrap.d.ts.map
|
package/dist/bootstrap.js
CHANGED
|
@@ -6,19 +6,28 @@
|
|
|
6
6
|
* stdout via regex patterns, re-runnable, and reports failures rather than
|
|
7
7
|
* deploy-failing. Spec: /spec/SPEC.md § Bootstrap stage.
|
|
8
8
|
*
|
|
9
|
-
* The command
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* The command runs through a POSIX shell (SPEC.md § Command interpretation,
|
|
10
|
+
* PROVIDERS.md §10 item 11), so `&&`, `;`, pipes, redirection, grouping and
|
|
11
|
+
* variable expansion behave as authors write them. It is passed as a single
|
|
12
|
+
* argv element to `/bin/sh -c` via spawn({ shell: false }) — it is never
|
|
13
|
+
* interpolated into a command string on this side, so the only interpreter
|
|
14
|
+
* that sees it is the `sh` this provider spawns.
|
|
14
15
|
*/
|
|
15
16
|
import { readFile } from "node:fs/promises";
|
|
16
17
|
import { join } from "node:path";
|
|
17
18
|
import { spawn } from "node:child_process";
|
|
18
|
-
import { readLaunch, resolveExpression, } from "@launchfile/sdk";
|
|
19
|
-
import { loadState } from "./state.js";
|
|
19
|
+
import { parseDurationMs, readLaunch, resolveExpression, } from "@launchfile/sdk";
|
|
20
|
+
import { loadState, saveState } from "./state.js";
|
|
20
21
|
import { buildResolverContext, computeAppProperties, resolveComponentEnv, resolveGenerators, } from "./env-writer.js";
|
|
22
|
+
import { redactSecrets } from "./redact.js";
|
|
21
23
|
import { getProvisioner } from "./resources/index.js";
|
|
24
|
+
/** Default budget for a bootstrap command when no `timeout` is declared. */
|
|
25
|
+
export const DEFAULT_BOOTSTRAP_TIMEOUT_MS = 120_000;
|
|
26
|
+
/**
|
|
27
|
+
* The interpreter every bootstrap command runs under. `/bin/sh` is the POSIX
|
|
28
|
+
* shell guaranteed present on macOS.
|
|
29
|
+
*/
|
|
30
|
+
export const BOOTSTRAP_SHELL = "/bin/sh";
|
|
22
31
|
/**
|
|
23
32
|
* Strip ANSI escape sequences from captured stdout before regex matching.
|
|
24
33
|
* CLI tools that detect a TTY will emit color codes that would otherwise
|
|
@@ -51,73 +60,117 @@ export function extractCaptures(stdout, captures) {
|
|
|
51
60
|
}
|
|
52
61
|
return result;
|
|
53
62
|
}
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Parse a simple duration string like "5m", "30s", "1h" into milliseconds
|
|
65
|
+
* using the ratified grammar (D-48). Throws on an unparseable value —
|
|
66
|
+
* PROVIDERS.md §10.10 forbids silently substituting a default. Re-exported
|
|
67
|
+
* for unit testing.
|
|
68
|
+
*/
|
|
69
|
+
export const parseDuration = parseDurationMs;
|
|
70
|
+
/**
|
|
71
|
+
* Build the bootstrap plan for a launch. Pure — no I/O — so selection,
|
|
72
|
+
* expression resolution, argv shape and timeout handling are unit-testable.
|
|
73
|
+
*/
|
|
74
|
+
export function planBootstraps(launch, context, opts = {}) {
|
|
75
|
+
const plan = [];
|
|
76
|
+
for (const [name, component] of Object.entries(launch.components)) {
|
|
77
|
+
if (opts.component && name !== opts.component)
|
|
78
|
+
continue;
|
|
79
|
+
// `bootstrap` is mode-invariant (D-38): the same command runs from
|
|
80
|
+
// source or artifact. A path or binary that differs by mode belongs in
|
|
81
|
+
// storage:/env/PATH, not a separate command.
|
|
82
|
+
const bootstrap = component.commands?.bootstrap;
|
|
83
|
+
if (!bootstrap)
|
|
84
|
+
continue;
|
|
85
|
+
// Resolve $-expressions in the command string (e.g. $app.url) at
|
|
86
|
+
// invocation time. Bootstrap runs after start, so the resolved URL
|
|
87
|
+
// already reflects the actual allocated port. A `$$` escape becomes a
|
|
88
|
+
// literal `$` here and reaches the shell intact (SPEC.md § References).
|
|
89
|
+
const command = resolveExpression(bootstrap.command, context);
|
|
90
|
+
const base = {
|
|
91
|
+
component: name,
|
|
92
|
+
command,
|
|
93
|
+
// `/bin/sh -c <command>` — one argv element, so the shell interprets
|
|
94
|
+
// the command and nothing else (SPEC.md § Command interpretation).
|
|
95
|
+
argv: [BOOTSTRAP_SHELL, "-c", command],
|
|
96
|
+
capture: bootstrap.capture,
|
|
97
|
+
};
|
|
98
|
+
if (command.trim() === "") {
|
|
99
|
+
plan.push({
|
|
100
|
+
...base,
|
|
101
|
+
timeoutMs: DEFAULT_BOOTSTRAP_TIMEOUT_MS,
|
|
102
|
+
error: "empty command",
|
|
103
|
+
});
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
let timeoutMs = DEFAULT_BOOTSTRAP_TIMEOUT_MS;
|
|
107
|
+
if (bootstrap.timeout !== undefined) {
|
|
108
|
+
try {
|
|
109
|
+
timeoutMs = parseDuration(bootstrap.timeout);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
// An unparseable timeout is surfaced, never silently replaced
|
|
113
|
+
// with a default (PROVIDERS.md §10.10).
|
|
114
|
+
plan.push({
|
|
115
|
+
...base,
|
|
116
|
+
timeoutMs: DEFAULT_BOOTSTRAP_TIMEOUT_MS,
|
|
117
|
+
error: err instanceof Error ? err.message : String(err),
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
plan.push({ ...base, timeoutMs });
|
|
66
123
|
}
|
|
124
|
+
return plan;
|
|
67
125
|
}
|
|
68
126
|
/**
|
|
69
|
-
* Run one command
|
|
127
|
+
* Run one command. `cmd`/`args` cross the process boundary as an argv vector
|
|
128
|
+
* with shell:false — the command string is a single element of it, never
|
|
129
|
+
* interpolated into a string another shell would re-parse. Returns the
|
|
70
130
|
* structured result; does not throw on command failure.
|
|
71
131
|
*/
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return new Promise((resolveP) => {
|
|
79
|
-
const child = spawn(file, args, {
|
|
80
|
-
cwd: opts.cwd,
|
|
81
|
-
env: { ...process.env, ...opts.env },
|
|
82
|
-
shell: false,
|
|
83
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
84
|
-
});
|
|
85
|
-
let stdout = "";
|
|
86
|
-
let stderr = "";
|
|
87
|
-
let settled = false;
|
|
88
|
-
const timer = setTimeout(() => {
|
|
89
|
-
if (!settled) {
|
|
90
|
-
settled = true;
|
|
91
|
-
child.kill("SIGTERM");
|
|
92
|
-
resolveP({
|
|
93
|
-
exitCode: 124,
|
|
94
|
-
stdout,
|
|
95
|
-
stderr: stderr + `\n(killed after ${opts.timeoutMs}ms timeout)`,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}, opts.timeoutMs);
|
|
99
|
-
child.stdout?.on("data", (chunk) => {
|
|
100
|
-
stdout += String(chunk);
|
|
101
|
-
});
|
|
102
|
-
child.stderr?.on("data", (chunk) => {
|
|
103
|
-
stderr += String(chunk);
|
|
104
|
-
});
|
|
105
|
-
child.on("error", (err) => {
|
|
106
|
-
if (!settled) {
|
|
107
|
-
settled = true;
|
|
108
|
-
clearTimeout(timer);
|
|
109
|
-
resolveP({ exitCode: 1, stdout, stderr: `${stderr}\n${err.message}` });
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
child.on("close", (code) => {
|
|
113
|
-
if (!settled) {
|
|
114
|
-
settled = true;
|
|
115
|
-
clearTimeout(timer);
|
|
116
|
-
resolveP({ exitCode: code ?? 1, stdout, stderr });
|
|
117
|
-
}
|
|
118
|
-
});
|
|
132
|
+
const defaultExec = (cmd, args, opts) => new Promise((resolveP) => {
|
|
133
|
+
const child = spawn(cmd, args, {
|
|
134
|
+
cwd: opts.cwd,
|
|
135
|
+
env: { ...process.env, ...opts.env },
|
|
136
|
+
shell: false,
|
|
137
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
119
138
|
});
|
|
120
|
-
|
|
139
|
+
let stdout = "";
|
|
140
|
+
let stderr = "";
|
|
141
|
+
let settled = false;
|
|
142
|
+
const timer = setTimeout(() => {
|
|
143
|
+
if (!settled) {
|
|
144
|
+
settled = true;
|
|
145
|
+
child.kill("SIGTERM");
|
|
146
|
+
resolveP({
|
|
147
|
+
exitCode: 124,
|
|
148
|
+
stdout,
|
|
149
|
+
stderr: stderr + `\n(killed after ${opts.timeoutMs}ms timeout)`,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}, opts.timeoutMs);
|
|
153
|
+
child.stdout?.on("data", (chunk) => {
|
|
154
|
+
stdout += String(chunk);
|
|
155
|
+
});
|
|
156
|
+
child.stderr?.on("data", (chunk) => {
|
|
157
|
+
stderr += String(chunk);
|
|
158
|
+
});
|
|
159
|
+
child.on("error", (err) => {
|
|
160
|
+
if (!settled) {
|
|
161
|
+
settled = true;
|
|
162
|
+
clearTimeout(timer);
|
|
163
|
+
resolveP({ exitCode: 1, stdout, stderr: `${stderr}\n${err.message}` });
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
child.on("close", (code) => {
|
|
167
|
+
if (!settled) {
|
|
168
|
+
settled = true;
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
resolveP({ exitCode: code ?? 1, stdout, stderr });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
});
|
|
121
174
|
/**
|
|
122
175
|
* Public entry point for `launch bootstrap`. Loads the Launchfile, rebuilds
|
|
123
176
|
* the resolver context from persisted state (so $app.url resolves to the
|
|
@@ -154,50 +207,74 @@ export async function launchBootstrap(opts = {}) {
|
|
|
154
207
|
}
|
|
155
208
|
const appProperties = computeAppProperties(launch, state.ports);
|
|
156
209
|
const context = buildResolverContext(resourceMap, state.ports, state.secrets, appProperties);
|
|
210
|
+
const exec = opts.exec ?? defaultExec;
|
|
211
|
+
const plan = planBootstraps(launch, context, { component: opts.component });
|
|
157
212
|
const results = [];
|
|
158
|
-
for (const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
213
|
+
for (const item of plan) {
|
|
214
|
+
const name = item.component;
|
|
215
|
+
// Bootstrap failures are reported, not thrown (SPEC.md \u00a7 Failure
|
|
216
|
+
// semantics) \u2014 an unparseable timeout is surfaced the same way,
|
|
217
|
+
// never silently replaced with a default (PROVIDERS.md \u00a710.10).
|
|
218
|
+
if (item.error) {
|
|
219
|
+
console.error(` \u2717 Bootstrap [${name}]: ${item.error}`);
|
|
220
|
+
results.push({
|
|
221
|
+
component: name,
|
|
222
|
+
command: item.command,
|
|
223
|
+
ok: false,
|
|
224
|
+
exitCode: 1,
|
|
225
|
+
captures: {},
|
|
226
|
+
captureMeta: item.capture ?? {},
|
|
227
|
+
stdout: "",
|
|
228
|
+
stderr: item.error,
|
|
229
|
+
});
|
|
163
230
|
continue;
|
|
164
|
-
|
|
165
|
-
// invocation time. Bootstrap runs after start, so the resolved URL
|
|
166
|
-
// already reflects the actual allocated port.
|
|
167
|
-
const resolvedCommand = resolveExpression(bootstrap.command, context);
|
|
231
|
+
}
|
|
168
232
|
// Resolve env vars so the subprocess gets the same environment as
|
|
169
|
-
// the running component.
|
|
170
|
-
|
|
171
|
-
|
|
233
|
+
// the running component. Minted generator values come from the store
|
|
234
|
+
// `up` persists (D-49); a value minted here (a generator declared
|
|
235
|
+
// after the last `up`) is persisted before the command runs, so
|
|
236
|
+
// `up`/`env`/`bootstrap` keep agreeing on it.
|
|
237
|
+
const component = launch.components[name];
|
|
238
|
+
const { env, unsupplied } = resolveComponentEnv(component, context, resourceMap);
|
|
239
|
+
const minted = await resolveGenerators(component, env, name, (state.generatedEnv ??= {}));
|
|
240
|
+
if (minted)
|
|
241
|
+
await saveState(projectDir, state);
|
|
242
|
+
// `up` took its `required:` values from the launching environment; read the
|
|
243
|
+
// same channel so bootstrap really does see the running component's env.
|
|
244
|
+
for (const { key } of unsupplied) {
|
|
245
|
+
const supplied = process.env[key];
|
|
246
|
+
if (supplied !== undefined)
|
|
247
|
+
env[key] = supplied;
|
|
248
|
+
}
|
|
172
249
|
const port = state.ports[name];
|
|
173
250
|
if (port && !env.PORT)
|
|
174
251
|
env.PORT = String(port);
|
|
175
252
|
console.log(`\n \u2193 Bootstrap [${name}]`);
|
|
176
|
-
|
|
177
|
-
|
|
253
|
+
// `$secrets.*` / `$<resource>.password` / `$<resource>.url` resolve to live
|
|
254
|
+
// credentials here, so the echoed command is scrubbed (CWE-532).
|
|
255
|
+
console.log(` $ ${redactSecrets(item.command)}`);
|
|
256
|
+
const [file, ...args] = item.argv;
|
|
257
|
+
const { exitCode, stdout, stderr } = await exec(file, args, {
|
|
178
258
|
cwd: projectDir,
|
|
179
259
|
env,
|
|
180
|
-
timeoutMs:
|
|
260
|
+
timeoutMs: item.timeoutMs,
|
|
181
261
|
});
|
|
182
|
-
const captures =
|
|
183
|
-
|
|
184
|
-
: {};
|
|
185
|
-
const result = {
|
|
262
|
+
const captures = item.capture ? extractCaptures(stdout, item.capture) : {};
|
|
263
|
+
results.push({
|
|
186
264
|
component: name,
|
|
187
|
-
command:
|
|
265
|
+
command: item.command,
|
|
188
266
|
ok: exitCode === 0,
|
|
189
267
|
exitCode,
|
|
190
268
|
captures,
|
|
191
|
-
captureMeta:
|
|
269
|
+
captureMeta: item.capture ?? {},
|
|
192
270
|
stdout,
|
|
193
271
|
stderr,
|
|
194
|
-
};
|
|
195
|
-
results.push(result);
|
|
272
|
+
});
|
|
196
273
|
// Print captures inline so the user sees them immediately.
|
|
197
274
|
if (Object.keys(captures).length > 0) {
|
|
198
275
|
console.log("\n Captured:");
|
|
199
276
|
for (const [key, value] of Object.entries(captures)) {
|
|
200
|
-
const meta =
|
|
277
|
+
const meta = item.capture?.[key];
|
|
201
278
|
const displayValue = meta?.sensitive ? "***" : value;
|
|
202
279
|
const desc = meta?.description ? ` — ${meta.description}` : "";
|
|
203
280
|
console.log(` ${key}: ${displayValue}${desc}`);
|
|
@@ -206,7 +283,7 @@ export async function launchBootstrap(opts = {}) {
|
|
|
206
283
|
if (exitCode !== 0) {
|
|
207
284
|
console.error(` \u2717 Bootstrap [${name}] failed with exit code ${exitCode}`);
|
|
208
285
|
if (stderr)
|
|
209
|
-
console.error(stderr);
|
|
286
|
+
console.error(redactSecrets(stderr));
|
|
210
287
|
}
|
|
211
288
|
else {
|
|
212
289
|
console.log(` \u2713 Bootstrap [${name}] complete`);
|
package/dist/env-writer.d.ts
CHANGED
|
@@ -4,14 +4,17 @@
|
|
|
4
4
|
* Connects provisioned resource properties to the SDK's expression resolver,
|
|
5
5
|
* then writes the results to .env files.
|
|
6
6
|
*/
|
|
7
|
-
import { type NormalizedComponent, type NormalizedLaunch, type ResolverContext, type Secret } from "@launchfile/sdk";
|
|
7
|
+
import { type NormalizedComponent, type NormalizedLaunch, type ResolverContext, type Secret, type UnsuppliedRequiredEnv } from "@launchfile/sdk";
|
|
8
8
|
import type { ResourceProperties } from "./resources/types.js";
|
|
9
|
-
export type { ResolverContext };
|
|
9
|
+
export type { ResolverContext, UnsuppliedRequiredEnv };
|
|
10
10
|
/**
|
|
11
|
-
* Compute the $app.* property set for a Launchfile under the
|
|
12
|
-
* provider. The app's "primary" port comes from the first component
|
|
13
|
-
* declaration order) that has at least one `exposed: true` provides entry.
|
|
14
|
-
*
|
|
11
|
+
* Compute the $app.* property set (D-33, D-35) for a Launchfile under the
|
|
12
|
+
* macos-dev provider. The app's "primary" port comes from the first component
|
|
13
|
+
* (in declaration order) that has at least one `exposed: true` provides entry.
|
|
14
|
+
* The `authority`/`scheme`/`tls` trio is derived from the resulting URL via the
|
|
15
|
+
* SDK so split-field tokens (e.g. `CMD_DOMAIN: $app.authority`) resolve.
|
|
16
|
+
* Apps with no exposed component get `port: 0` and `url: ""` (and empty
|
|
17
|
+
* authority/scheme/tls).
|
|
15
18
|
*
|
|
16
19
|
* For multi-exposed-component apps that need a specific component's URL,
|
|
17
20
|
* use `$components.<name>.url` instead — `$app.*` always points at the
|
|
@@ -23,19 +26,45 @@ export declare function computeAppProperties(launch: NormalizedLaunch, component
|
|
|
23
26
|
* secrets, and (D-33) the platform-injected app properties.
|
|
24
27
|
*/
|
|
25
28
|
export declare function buildResolverContext(resourceMap: Record<string, ResourceProperties>, componentPorts: Record<string, number>, secrets: Record<string, string>, app: Record<string, string | number>): ResolverContext;
|
|
29
|
+
/**
|
|
30
|
+
* The resolved environment for one component, plus what the file did not supply.
|
|
31
|
+
*/
|
|
32
|
+
export interface ComponentEnvResult {
|
|
33
|
+
/** Keys that actually arrived. An unsupplied `required:` key is ABSENT, never `""`. */
|
|
34
|
+
env: Record<string, string>;
|
|
35
|
+
/**
|
|
36
|
+
* `required:` variables with no `generator:`, no `default:`, and no
|
|
37
|
+
* `set_env:` binding that injected (D-52, PROVIDERS.md §10 rule 8). The
|
|
38
|
+
* caller decides: `up` reads its operator channel then fails by name, `env`
|
|
39
|
+
* reports them without breaking `eval`.
|
|
40
|
+
*/
|
|
41
|
+
unsupplied: UnsuppliedRequiredEnv[];
|
|
42
|
+
}
|
|
26
43
|
/**
|
|
27
44
|
* Resolve all environment variables for a single component.
|
|
28
45
|
*/
|
|
29
|
-
export declare function resolveComponentEnv(component: NormalizedComponent, context: ResolverContext, resourceMap: Record<string, ResourceProperties
|
|
46
|
+
export declare function resolveComponentEnv(component: NormalizedComponent, context: ResolverContext, resourceMap: Record<string, ResourceProperties>, storage?: Record<string, Record<string, string>>): ComponentEnvResult;
|
|
30
47
|
/**
|
|
31
48
|
* Generate all app-wide secrets, reusing values from state when available.
|
|
32
49
|
*/
|
|
33
50
|
export declare function generateSecrets(secretDefs: Record<string, Secret> | undefined, existingSecrets: Record<string, string>): Promise<Record<string, string>>;
|
|
34
51
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
52
|
+
* Resolve values for env vars that declare generators, preserving minted
|
|
53
|
+
* values across runs (D-49: generate once, then preserve).
|
|
54
|
+
*
|
|
55
|
+
* A `secret` or `uuid` value is read from `generatedEnv` when present, and
|
|
56
|
+
* minted and written into it when absent. The store is keyed
|
|
57
|
+
* `<component>.<ENV_NAME>` — one entry per declaration (D-25), so two
|
|
58
|
+
* components declaring the same variable name hold independent values.
|
|
59
|
+
* `generator: port` is exempt: ports have their own preserved home
|
|
60
|
+
* (`state.ports`) and allocator, and a preserved port produces a bind
|
|
61
|
+
* conflict rather than continuity.
|
|
62
|
+
*
|
|
63
|
+
* Mutates `env` and `generatedEnv` in place. Returns true when a new value
|
|
64
|
+
* was minted into `generatedEnv` — the caller must then persist the state
|
|
65
|
+
* before handing the value to anything, so every site that mints persists.
|
|
37
66
|
*/
|
|
38
|
-
export declare function resolveGenerators(component: NormalizedComponent, env: Record<string, string>): Promise<
|
|
67
|
+
export declare function resolveGenerators(component: NormalizedComponent, env: Record<string, string>, componentName: string, generatedEnv: Record<string, string>): Promise<boolean>;
|
|
39
68
|
/**
|
|
40
69
|
* Write resolved env vars to a .env file.
|
|
41
70
|
*/
|
|
@@ -45,5 +74,5 @@ export declare function writeEnvFile(filePath: string, env: Record<string, strin
|
|
|
45
74
|
* Single-component → .env.local at project root.
|
|
46
75
|
* Multi-component → .launchfile/env/<component>.env per component.
|
|
47
76
|
*/
|
|
48
|
-
export declare function writeAllEnvFiles(launch: NormalizedLaunch, context: ResolverContext, resourceMap: Record<string, ResourceProperties>, componentPorts: Record<string, number>, projectDir: string): Promise<Record<string, Record<string, string>>>;
|
|
77
|
+
export declare function writeAllEnvFiles(launch: NormalizedLaunch, context: ResolverContext, resourceMap: Record<string, ResourceProperties>, componentPorts: Record<string, number>, projectDir: string, generatedEnv: Record<string, string>): Promise<Record<string, Record<string, string>>>;
|
|
49
78
|
//# sourceMappingURL=env-writer.d.ts.map
|