@launchfile/macos-dev 0.9.0 → 0.10.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.js +8 -19
- package/dist/env-writer.d.ts +82 -9
- package/dist/env-writer.js +147 -22
- package/dist/https-origin.d.ts +48 -0
- package/dist/https-origin.js +81 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/port-allocator.d.ts +1 -0
- package/dist/port-allocator.js +16 -2
- package/dist/provider.d.ts +69 -7
- package/dist/provider.js +231 -55
- package/dist/resources/index.d.ts +2 -1
- package/dist/resources/index.js +1 -0
- package/dist/resources/mysql.d.ts +2 -2
- package/dist/resources/mysql.js +23 -3
- package/dist/resources/postgres.d.ts +2 -2
- package/dist/resources/postgres.js +35 -9
- package/dist/resources/redis.d.ts +2 -2
- package/dist/resources/redis.js +1 -1
- package/dist/resources/sqlite.d.ts +2 -2
- package/dist/resources/sqlite.js +26 -5
- package/dist/resources/types.d.ts +18 -1
- package/dist/resources/uses.d.ts +107 -0
- package/dist/resources/uses.js +217 -0
- package/dist/state.d.ts +36 -0
- package/dist/state.js +36 -3
- package/package.json +2 -2
package/dist/bootstrap.js
CHANGED
|
@@ -18,9 +18,8 @@ import { join } from "node:path";
|
|
|
18
18
|
import { spawn } from "node:child_process";
|
|
19
19
|
import { formatCaptures, parseDurationMs, readLaunch, resolveExpression, sensitiveCaptureValues, } from "@launchfile/sdk";
|
|
20
20
|
import { loadState, saveState } from "./state.js";
|
|
21
|
-
import {
|
|
21
|
+
import { resolveComponentEnv, resolveGenerators, resolverContextFor, resourceMapFromState, } from "./env-writer.js";
|
|
22
22
|
import { redactSecrets, registerDeclaredSecret } from "./redact.js";
|
|
23
|
-
import { getProvisioner } from "./resources/index.js";
|
|
24
23
|
/** Default budget for a bootstrap command when no `timeout` is declared. */
|
|
25
24
|
export const DEFAULT_BOOTSTRAP_TIMEOUT_MS = 120_000;
|
|
26
25
|
/**
|
|
@@ -191,23 +190,13 @@ export async function launchBootstrap(opts = {}) {
|
|
|
191
190
|
if (!state) {
|
|
192
191
|
throw new Error("No active launch state. Run `launch up` first.");
|
|
193
192
|
}
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const resourceMap = {};
|
|
202
|
-
for (const [name, res] of Object.entries(state.resources)) {
|
|
203
|
-
const provisioner = getProvisioner(res.type);
|
|
204
|
-
if (provisioner) {
|
|
205
|
-
const result = await provisioner.provision({ type: res.type, name: res.name }, { appName: state.appName, projectDir }, res);
|
|
206
|
-
resourceMap[name] = result.properties;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
const appProperties = computeAppProperties(launch, state.ports);
|
|
210
|
-
const context = buildResolverContext(resourceMap, state.ports, state.secrets, appProperties);
|
|
193
|
+
// A bootstrap command reads the same values the app's env was written
|
|
194
|
+
// with: the resources registered as `up` registered them — declared uses
|
|
195
|
+
// included — and `$app.*` from the publication context the last `up`
|
|
196
|
+
// recorded (D-58), not this provider's localhost answer under an upstream
|
|
197
|
+
// proxy.
|
|
198
|
+
const resourceMap = await resourceMapFromState(launch, state, projectDir);
|
|
199
|
+
const context = resolverContextFor(launch, resourceMap, state);
|
|
211
200
|
const exec = opts.exec ?? defaultExec;
|
|
212
201
|
const plan = planBootstraps(launch, context, { component: opts.component });
|
|
213
202
|
const results = [];
|
package/dist/env-writer.d.ts
CHANGED
|
@@ -4,28 +4,101 @@
|
|
|
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, type UnsuppliedRequiredEnv } from "@launchfile/sdk";
|
|
7
|
+
import { type AppEndpointProperties, type NormalizedComponent, type NormalizedLaunch, type ResolverContext, type Secret, type UnsuppliedRequiredEnv } from "@launchfile/sdk";
|
|
8
8
|
import type { ResourceProperties } from "./resources/types.js";
|
|
9
|
+
import { type DbIndexes } from "./resources/uses.js";
|
|
10
|
+
import { type LaunchState } from "./state.js";
|
|
9
11
|
export type { ResolverContext, UnsuppliedRequiredEnv };
|
|
10
12
|
/**
|
|
11
13
|
* Compute the $app.* property set (D-33, D-35) for a Launchfile under the
|
|
12
|
-
* macos-dev provider.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* macos-dev provider.
|
|
15
|
+
*
|
|
16
|
+
* With no `appUrl`, this provider's own routing strategy answers: the app's
|
|
17
|
+
* "primary" port is the port of the component that declares an `https-origin`
|
|
18
|
+
* entry (D-60 rule 3 — declaration fixes the primary, fulfilled or not), else
|
|
19
|
+
* the first component (in declaration order) that has at least one
|
|
20
|
+
* `exposed: true` provides entry, and `http://localhost:<port>` is the address.
|
|
16
21
|
* Apps with no exposed component get `port: 0` and `url: ""` (and empty
|
|
17
22
|
* authority/scheme/tls).
|
|
18
23
|
*
|
|
24
|
+
* With an `appUrl` — the orchestrator-supplied publication context (D-58) —
|
|
25
|
+
* routing has moved upstream and the supplied URL answers instead, via the
|
|
26
|
+
* SDK's `suppliedAppProperties`: the same derivation `@launchfile/docker` uses,
|
|
27
|
+
* so one Launchfile behind one proxy resolves identical `$app.*` under either
|
|
28
|
+
* provider (P-5). The allocated local ports stay orthogonal — still bound, just
|
|
29
|
+
* not the address anyone reaches the app at.
|
|
30
|
+
*
|
|
31
|
+
* Either way the `authority`/`scheme`/`tls` trio is derived from the resulting
|
|
32
|
+
* URL via the SDK so split-field tokens (e.g. `CMD_DOMAIN: $app.authority`)
|
|
33
|
+
* resolve from one definition (D-35).
|
|
34
|
+
*
|
|
19
35
|
* For multi-exposed-component apps that need a specific component's URL,
|
|
20
36
|
* use `$components.<name>.url` instead — `$app.*` always points at the
|
|
21
|
-
*
|
|
37
|
+
* primary endpoint to give a single, predictable answer (D-58 rule 4). This
|
|
38
|
+
* provider allocates one port per component, so the named endpoint's address
|
|
39
|
+
* is its component's port.
|
|
22
40
|
*/
|
|
23
|
-
export declare function computeAppProperties(launch: NormalizedLaunch, componentPorts: Record<string, number
|
|
41
|
+
export declare function computeAppProperties(launch: NormalizedLaunch, componentPorts: Record<string, number>, appUrl?: string): Record<string, string | number>;
|
|
42
|
+
/**
|
|
43
|
+
* `$app.endpoints.<name>.*` under this provider (D-63 rule 4): every
|
|
44
|
+
* property of every named published endpoint resolves `""`. The allocator
|
|
45
|
+
* hands out one port per **component** (`allocatePorts`, keyed by component
|
|
46
|
+
* name), so a second `exposed: true` entry on a component has no host-side
|
|
47
|
+
* address to publish — not the primary's, and not its own (#294). The
|
|
48
|
+
* primary's entry is `""` too, rather than a copy of `$app.*`, because the
|
|
49
|
+
* per-endpoint form promises a per-endpoint publication this provider does
|
|
50
|
+
* not perform; `$app.*` keeps its own routing answer. Registering the empty
|
|
51
|
+
* answer explicitly, rather than nothing, records that the provider has read
|
|
52
|
+
* the namespace and declined it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function computeAppEndpoints(launch: NormalizedLaunch): Record<string, AppEndpointProperties>;
|
|
24
55
|
/**
|
|
25
56
|
* Build a ResolverContext from provisioned resources, component ports,
|
|
26
|
-
* secrets,
|
|
57
|
+
* secrets, (D-33) the platform-injected app properties, (D-63) the
|
|
58
|
+
* per-endpoint map — `computeAppEndpoints`, which under this provider is
|
|
59
|
+
* every named published endpoint resolving `""` — and the `uses` each
|
|
60
|
+
* resource entry declares (`declaredUses`), which the resolver reads to
|
|
61
|
+
* resolve `$<resource>.<use>.<property>` strictly.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildResolverContext(resourceMap: Record<string, ResourceProperties>, componentPorts: Record<string, number>, secrets: Record<string, string>, app: Record<string, string | number>, appEndpoints?: Record<string, AppEndpointProperties>, uses?: Record<string, readonly string[]>): ResolverContext;
|
|
64
|
+
/**
|
|
65
|
+
* The use keys each resource entry declares (`db`, `db.cache`), keyed like
|
|
66
|
+
* the resource namespace (`name ?? type`, app-global). Same-name entries pool
|
|
67
|
+
* their keys — D-24 says they describe one resource. Host-capability entries
|
|
68
|
+
* have none.
|
|
69
|
+
*/
|
|
70
|
+
export declare function declaredUses(launch: NormalizedLaunch): Record<string, string[]>;
|
|
71
|
+
/**
|
|
72
|
+
* A resource's property map as `up`, `env` and `bootstrap` all register it:
|
|
73
|
+
* the provisioner's instance vocabulary plus every pooled use this provider
|
|
74
|
+
* covers under `<use>.<property>` / `<use>.<name>.<property>` (D-24:
|
|
75
|
+
* same-name entries describe one resource). `dbIndexes` carries the numbered
|
|
76
|
+
* database each redis `db` use key selects. A pooled `db` key with no index
|
|
77
|
+
* in it stays unregistered, so a `$<resource>.db.<property>` (or
|
|
78
|
+
* `$<resource>.db.<name>.<property>`) reference throws `UnresolvedUseError`
|
|
79
|
+
* instead of falling through to the instance url.
|
|
80
|
+
*/
|
|
81
|
+
export declare function registerResource(type: string, resourceName: string, uses: Record<string, readonly string[]>, base: ResourceProperties, dbIndexes: DbIndexes): ResourceProperties;
|
|
82
|
+
/**
|
|
83
|
+
* The resource map a subcommand run after `up` (`env`, `bootstrap`) resolves
|
|
84
|
+
* against, rebuilt from state and registered exactly as `up` registered it.
|
|
85
|
+
* Each recorded resource is re-provisioned to read its current properties —
|
|
86
|
+
* every provisioner is idempotent, so this neither re-creates nor corrupts
|
|
87
|
+
* the resource — and a redis `db` use reads back the index `up` recorded
|
|
88
|
+
* rather than one re-derived from the file, which may have changed since. A
|
|
89
|
+
* state file written before the index was recorded leaves `db.*`
|
|
90
|
+
* unregistered, so the strict resolver throws for `$<resource>.db.*` — never
|
|
91
|
+
* the instance url — until the next `up` records it.
|
|
92
|
+
*/
|
|
93
|
+
export declare function resourceMapFromState(launch: NormalizedLaunch, state: LaunchState, projectDir: string): Promise<Record<string, ResourceProperties>>;
|
|
94
|
+
/**
|
|
95
|
+
* The resolver context `up`, `env` and `bootstrap` share, built from the same
|
|
96
|
+
* inputs: the registered resources, the recorded ports and secrets, `$app.*`
|
|
97
|
+
* from the recorded publication context (D-58) with a satisfied
|
|
98
|
+
* `https-origin` wired to the same string (D-60 rule 4), and the declared
|
|
99
|
+
* uses the resolver applies strictly.
|
|
27
100
|
*/
|
|
28
|
-
export declare function
|
|
101
|
+
export declare function resolverContextFor(launch: NormalizedLaunch, resourceMap: Record<string, ResourceProperties>, state: LaunchState): ResolverContext;
|
|
29
102
|
/**
|
|
30
103
|
* The resolved environment for one component, plus what the file did not supply.
|
|
31
104
|
*/
|
package/dist/env-writer.js
CHANGED
|
@@ -6,34 +6,61 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { writeFile, mkdir } from "node:fs/promises";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import { deriveAppUrlProperties, resolveExpression, isExpression, unsuppliedRequiredEnv, } from "@launchfile/sdk";
|
|
9
|
+
import { deriveAppUrlProperties, resolveExpression, isExpression, parseUseKey, suppliedAppProperties, UNPUBLISHED_APP_ENDPOINT, unsuppliedRequiredEnv, useKeys, } from "@launchfile/sdk";
|
|
10
|
+
import { declaredPrimaryComponent, wireHttpsOrigins } from "./https-origin.js";
|
|
11
|
+
import { getProvisioner } from "./resources/index.js";
|
|
12
|
+
import { coveredUses, namedDatabases, withCoveredUses } from "./resources/uses.js";
|
|
10
13
|
import { generateValue } from "./secret-generator.js";
|
|
14
|
+
import { recordedDbIndexes } from "./state.js";
|
|
11
15
|
/**
|
|
12
16
|
* Compute the $app.* property set (D-33, D-35) for a Launchfile under the
|
|
13
|
-
* macos-dev provider.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
+
* macos-dev provider.
|
|
18
|
+
*
|
|
19
|
+
* With no `appUrl`, this provider's own routing strategy answers: the app's
|
|
20
|
+
* "primary" port is the port of the component that declares an `https-origin`
|
|
21
|
+
* entry (D-60 rule 3 — declaration fixes the primary, fulfilled or not), else
|
|
22
|
+
* the first component (in declaration order) that has at least one
|
|
23
|
+
* `exposed: true` provides entry, and `http://localhost:<port>` is the address.
|
|
17
24
|
* Apps with no exposed component get `port: 0` and `url: ""` (and empty
|
|
18
25
|
* authority/scheme/tls).
|
|
19
26
|
*
|
|
27
|
+
* With an `appUrl` — the orchestrator-supplied publication context (D-58) —
|
|
28
|
+
* routing has moved upstream and the supplied URL answers instead, via the
|
|
29
|
+
* SDK's `suppliedAppProperties`: the same derivation `@launchfile/docker` uses,
|
|
30
|
+
* so one Launchfile behind one proxy resolves identical `$app.*` under either
|
|
31
|
+
* provider (P-5). The allocated local ports stay orthogonal — still bound, just
|
|
32
|
+
* not the address anyone reaches the app at.
|
|
33
|
+
*
|
|
34
|
+
* Either way the `authority`/`scheme`/`tls` trio is derived from the resulting
|
|
35
|
+
* URL via the SDK so split-field tokens (e.g. `CMD_DOMAIN: $app.authority`)
|
|
36
|
+
* resolve from one definition (D-35).
|
|
37
|
+
*
|
|
20
38
|
* For multi-exposed-component apps that need a specific component's URL,
|
|
21
39
|
* use `$components.<name>.url` instead — `$app.*` always points at the
|
|
22
|
-
*
|
|
40
|
+
* primary endpoint to give a single, predictable answer (D-58 rule 4). This
|
|
41
|
+
* provider allocates one port per component, so the named endpoint's address
|
|
42
|
+
* is its component's port.
|
|
23
43
|
*/
|
|
24
|
-
export function computeAppProperties(launch, componentPorts) {
|
|
44
|
+
export function computeAppProperties(launch, componentPorts, appUrl) {
|
|
45
|
+
if (appUrl !== undefined)
|
|
46
|
+
return suppliedAppProperties(launch.name, appUrl);
|
|
25
47
|
let primaryPort = 0;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
const declared = declaredPrimaryComponent(launch);
|
|
49
|
+
if (declared !== undefined) {
|
|
50
|
+
// A declared `https-origin` names the primary explicitly, so the
|
|
51
|
+
// positional answer below does not run — the point of D-60 rule 3. The
|
|
52
|
+
// SDK requires the named endpoint to be `exposed: true` on this component.
|
|
53
|
+
primaryPort = componentPorts[declared] ?? 0;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
for (const [name, component] of Object.entries(launch.components)) {
|
|
57
|
+
// Only endpoints explicitly marked `exposed: true` are reachable from
|
|
58
|
+
// outside the host (D-27), so only they can be the app's public address.
|
|
59
|
+
const hasExposed = component.provides?.some((p) => p.exposed === true) ?? false;
|
|
60
|
+
if (hasExposed && componentPorts[name]) {
|
|
61
|
+
primaryPort = componentPorts[name];
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
37
64
|
}
|
|
38
65
|
}
|
|
39
66
|
const url = primaryPort > 0 ? `http://localhost:${primaryPort}` : "";
|
|
@@ -45,11 +72,38 @@ export function computeAppProperties(launch, componentPorts) {
|
|
|
45
72
|
...deriveAppUrlProperties(url),
|
|
46
73
|
};
|
|
47
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* `$app.endpoints.<name>.*` under this provider (D-63 rule 4): every
|
|
77
|
+
* property of every named published endpoint resolves `""`. The allocator
|
|
78
|
+
* hands out one port per **component** (`allocatePorts`, keyed by component
|
|
79
|
+
* name), so a second `exposed: true` entry on a component has no host-side
|
|
80
|
+
* address to publish — not the primary's, and not its own (#294). The
|
|
81
|
+
* primary's entry is `""` too, rather than a copy of `$app.*`, because the
|
|
82
|
+
* per-endpoint form promises a per-endpoint publication this provider does
|
|
83
|
+
* not perform; `$app.*` keeps its own routing answer. Registering the empty
|
|
84
|
+
* answer explicitly, rather than nothing, records that the provider has read
|
|
85
|
+
* the namespace and declined it.
|
|
86
|
+
*/
|
|
87
|
+
export function computeAppEndpoints(launch) {
|
|
88
|
+
const endpoints = {};
|
|
89
|
+
for (const component of Object.values(launch.components)) {
|
|
90
|
+
for (const p of component.provides ?? []) {
|
|
91
|
+
if (p.name === undefined || p.exposed !== true)
|
|
92
|
+
continue;
|
|
93
|
+
endpoints[p.name] ??= UNPUBLISHED_APP_ENDPOINT;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return endpoints;
|
|
97
|
+
}
|
|
48
98
|
/**
|
|
49
99
|
* Build a ResolverContext from provisioned resources, component ports,
|
|
50
|
-
* secrets,
|
|
100
|
+
* secrets, (D-33) the platform-injected app properties, (D-63) the
|
|
101
|
+
* per-endpoint map — `computeAppEndpoints`, which under this provider is
|
|
102
|
+
* every named published endpoint resolving `""` — and the `uses` each
|
|
103
|
+
* resource entry declares (`declaredUses`), which the resolver reads to
|
|
104
|
+
* resolve `$<resource>.<use>.<property>` strictly.
|
|
51
105
|
*/
|
|
52
|
-
export function buildResolverContext(resourceMap, componentPorts, secrets, app) {
|
|
106
|
+
export function buildResolverContext(resourceMap, componentPorts, secrets, app, appEndpoints = {}, uses = {}) {
|
|
53
107
|
// Build components map from ports
|
|
54
108
|
const components = {};
|
|
55
109
|
for (const [name, port] of Object.entries(componentPorts)) {
|
|
@@ -70,7 +124,78 @@ export function buildResolverContext(resourceMap, componentPorts, secrets, app)
|
|
|
70
124
|
}
|
|
71
125
|
resources[name] = record;
|
|
72
126
|
}
|
|
73
|
-
return { resources, components, secrets, app };
|
|
127
|
+
return { resources, components, secrets, app, appEndpoints, uses };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The use keys each resource entry declares (`db`, `db.cache`), keyed like
|
|
131
|
+
* the resource namespace (`name ?? type`, app-global). Same-name entries pool
|
|
132
|
+
* their keys — D-24 says they describe one resource. Host-capability entries
|
|
133
|
+
* have none.
|
|
134
|
+
*/
|
|
135
|
+
export function declaredUses(launch) {
|
|
136
|
+
const uses = {};
|
|
137
|
+
for (const component of Object.values(launch.components)) {
|
|
138
|
+
for (const entry of [...(component.requires ?? []), ...(component.supports ?? [])]) {
|
|
139
|
+
if (entry.host || !entry.uses)
|
|
140
|
+
continue;
|
|
141
|
+
const pooled = (uses[entry.name ?? entry.type] ??= []);
|
|
142
|
+
for (const key of useKeys(entry.uses)) {
|
|
143
|
+
if (!pooled.includes(key))
|
|
144
|
+
pooled.push(key);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return uses;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* A resource's property map as `up`, `env` and `bootstrap` all register it:
|
|
152
|
+
* the provisioner's instance vocabulary plus every pooled use this provider
|
|
153
|
+
* covers under `<use>.<property>` / `<use>.<name>.<property>` (D-24:
|
|
154
|
+
* same-name entries describe one resource). `dbIndexes` carries the numbered
|
|
155
|
+
* database each redis `db` use key selects. A pooled `db` key with no index
|
|
156
|
+
* in it stays unregistered, so a `$<resource>.db.<property>` (or
|
|
157
|
+
* `$<resource>.db.<name>.<property>`) reference throws `UnresolvedUseError`
|
|
158
|
+
* instead of falling through to the instance url.
|
|
159
|
+
*/
|
|
160
|
+
export function registerResource(type, resourceName, uses, base, dbIndexes) {
|
|
161
|
+
const pooled = coveredUses(type, uses[resourceName] ?? []);
|
|
162
|
+
const registered = pooled.filter((key) => parseUseKey(key).use !== "db" || Object.hasOwn(dbIndexes, key));
|
|
163
|
+
return withCoveredUses(type, registered, base, dbIndexes);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The resource map a subcommand run after `up` (`env`, `bootstrap`) resolves
|
|
167
|
+
* against, rebuilt from state and registered exactly as `up` registered it.
|
|
168
|
+
* Each recorded resource is re-provisioned to read its current properties —
|
|
169
|
+
* every provisioner is idempotent, so this neither re-creates nor corrupts
|
|
170
|
+
* the resource — and a redis `db` use reads back the index `up` recorded
|
|
171
|
+
* rather than one re-derived from the file, which may have changed since. A
|
|
172
|
+
* state file written before the index was recorded leaves `db.*`
|
|
173
|
+
* unregistered, so the strict resolver throws for `$<resource>.db.*` — never
|
|
174
|
+
* the instance url — until the next `up` records it.
|
|
175
|
+
*/
|
|
176
|
+
export async function resourceMapFromState(launch, state, projectDir) {
|
|
177
|
+
const uses = declaredUses(launch);
|
|
178
|
+
const resourceMap = {};
|
|
179
|
+
for (const [name, res] of Object.entries(state.resources)) {
|
|
180
|
+
const provisioner = getProvisioner(res.type);
|
|
181
|
+
if (!provisioner)
|
|
182
|
+
continue;
|
|
183
|
+
const result = await provisioner.provision({ type: res.type, name: res.name }, { appName: state.appName, projectDir, databases: namedDatabases(uses[name] ?? []) }, res);
|
|
184
|
+
resourceMap[name] = registerResource(res.type, name, uses, result.properties, recordedDbIndexes(res));
|
|
185
|
+
}
|
|
186
|
+
return resourceMap;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The resolver context `up`, `env` and `bootstrap` share, built from the same
|
|
190
|
+
* inputs: the registered resources, the recorded ports and secrets, `$app.*`
|
|
191
|
+
* from the recorded publication context (D-58) with a satisfied
|
|
192
|
+
* `https-origin` wired to the same string (D-60 rule 4), and the declared
|
|
193
|
+
* uses the resolver applies strictly.
|
|
194
|
+
*/
|
|
195
|
+
export function resolverContextFor(launch, resourceMap, state) {
|
|
196
|
+
const appProperties = computeAppProperties(launch, state.ports, state.appUrl);
|
|
197
|
+
wireHttpsOrigins(launch, resourceMap, state.appUrl);
|
|
198
|
+
return buildResolverContext(resourceMap, state.ports, state.secrets, appProperties, computeAppEndpoints(launch), declaredUses(launch));
|
|
74
199
|
}
|
|
75
200
|
/**
|
|
76
201
|
* Resolve all environment variables for a single component.
|
|
@@ -256,7 +381,7 @@ export async function writeAllEnvFiles(launch, context, resourceMap, componentPo
|
|
|
256
381
|
}
|
|
257
382
|
else {
|
|
258
383
|
const envDir = join(projectDir, ".launchfile", "env");
|
|
259
|
-
await mkdir(envDir, { recursive: true });
|
|
384
|
+
await mkdir(envDir, { recursive: true, mode: 0o700 });
|
|
260
385
|
await writeEnvFile(join(envDir, `${name}.env`), env);
|
|
261
386
|
}
|
|
262
387
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `https-origin` (D-60) under this provider — the one backing service that
|
|
3
|
+
* sits in FRONT of the app.
|
|
4
|
+
*
|
|
5
|
+
* This provider runs no edge of its own, so it cannot provision an origin. It
|
|
6
|
+
* can accept one the orchestrator already owns, through the publication-context
|
|
7
|
+
* channel (`LaunchUpOpts.appUrl`, D-58), which for this type IS the D-56
|
|
8
|
+
* supplied-resource channel (D-60 rule 5, PROVIDERS.md §7) — not a second one.
|
|
9
|
+
* Satisfaction is decided on the supplied scheme alone: no request is made, and
|
|
10
|
+
* D-56 rule 3 stands — the provider does not verify the origin exists or is
|
|
11
|
+
* ready.
|
|
12
|
+
*/
|
|
13
|
+
import { type NormalizedLaunch, type NormalizedRequirement } from "@launchfile/sdk";
|
|
14
|
+
import type { ResourceProperties } from "./resources/types.js";
|
|
15
|
+
/** The backing-service type that declares the app's public HTTPS origin (D-60). */
|
|
16
|
+
export declare const HTTPS_ORIGIN = "https-origin";
|
|
17
|
+
/**
|
|
18
|
+
* Whether a supplied publication URL satisfies an `https-origin` entry: its
|
|
19
|
+
* scheme is `https`. Syntactic only — `undefined` (nothing supplied and nothing
|
|
20
|
+
* recorded) and an `http` URL both fail. Expects a normalized value, as
|
|
21
|
+
* `launchUp` records it.
|
|
22
|
+
*/
|
|
23
|
+
export declare function httpsOriginSatisfied(appUrl: string | undefined): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Why one `https-origin` entry is not satisfied, for a refusal or a degraded
|
|
26
|
+
* note — the same two reasons `@launchfile/docker` gives, so one Launchfile
|
|
27
|
+
* behind one proxy reads the same message under either provider (P-5).
|
|
28
|
+
*/
|
|
29
|
+
export declare function httpsOriginShortfall(entry: NormalizedRequirement, appUrl: string | undefined): string;
|
|
30
|
+
/**
|
|
31
|
+
* The component an `https-origin` entry sits on, when the file declares one
|
|
32
|
+
* (D-60 rule 3), else `undefined`. **Declaration** fixes the primary, not
|
|
33
|
+
* fulfillment: a `supports:` entry this provider leaves unsatisfied still
|
|
34
|
+
* names it, so `$app.*` does not change value with the provider's capability.
|
|
35
|
+
* The SDK caps the app at one such entry and requires it to sit on the
|
|
36
|
+
* component that owns the named endpoint, so the first match is the only one.
|
|
37
|
+
*/
|
|
38
|
+
export declare function declaredPrimaryComponent(launch: NormalizedLaunch): string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Register every satisfied `https-origin` entry as a resource so its `set_env`
|
|
41
|
+
* resolves. One registered property, `url` (D-60 rule 4), holding the same
|
|
42
|
+
* string as `$app.url`. Mutates `resourceMap`; a no-op when the recorded
|
|
43
|
+
* publication URL does not satisfy the type, so an unsatisfied entry's
|
|
44
|
+
* `set_env` stays absent (never `""`) exactly as for any other resource this
|
|
45
|
+
* provider did not provision.
|
|
46
|
+
*/
|
|
47
|
+
export declare function wireHttpsOrigins(launch: NormalizedLaunch, resourceMap: Record<string, ResourceProperties>, appUrl: string | undefined): void;
|
|
48
|
+
//# sourceMappingURL=https-origin.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `https-origin` (D-60) under this provider — the one backing service that
|
|
3
|
+
* sits in FRONT of the app.
|
|
4
|
+
*
|
|
5
|
+
* This provider runs no edge of its own, so it cannot provision an origin. It
|
|
6
|
+
* can accept one the orchestrator already owns, through the publication-context
|
|
7
|
+
* channel (`LaunchUpOpts.appUrl`, D-58), which for this type IS the D-56
|
|
8
|
+
* supplied-resource channel (D-60 rule 5, PROVIDERS.md §7) — not a second one.
|
|
9
|
+
* Satisfaction is decided on the supplied scheme alone: no request is made, and
|
|
10
|
+
* D-56 rule 3 stands — the provider does not verify the origin exists or is
|
|
11
|
+
* ready.
|
|
12
|
+
*/
|
|
13
|
+
import { suppliedAppAddress, } from "@launchfile/sdk";
|
|
14
|
+
/** The backing-service type that declares the app's public HTTPS origin (D-60). */
|
|
15
|
+
export const HTTPS_ORIGIN = "https-origin";
|
|
16
|
+
/**
|
|
17
|
+
* Whether a supplied publication URL satisfies an `https-origin` entry: its
|
|
18
|
+
* scheme is `https`. Syntactic only — `undefined` (nothing supplied and nothing
|
|
19
|
+
* recorded) and an `http` URL both fail. Expects a normalized value, as
|
|
20
|
+
* `launchUp` records it.
|
|
21
|
+
*/
|
|
22
|
+
export function httpsOriginSatisfied(appUrl) {
|
|
23
|
+
return appUrl !== undefined && suppliedAppAddress(appUrl).scheme === "https";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Why one `https-origin` entry is not satisfied, for a refusal or a degraded
|
|
27
|
+
* note — the same two reasons `@launchfile/docker` gives, so one Launchfile
|
|
28
|
+
* behind one proxy reads the same message under either provider (P-5).
|
|
29
|
+
*/
|
|
30
|
+
export function httpsOriginShortfall(entry, appUrl) {
|
|
31
|
+
const label = `${entry.name ?? entry.type} (endpoint "${entry.endpoint ?? "?"}")`;
|
|
32
|
+
return appUrl === undefined
|
|
33
|
+
? `${label}: no publication URL was supplied, and this provider has no edge of its own`
|
|
34
|
+
: `${label}: the supplied publication URL's scheme is "${suppliedAppAddress(appUrl).scheme}", not https`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The component an `https-origin` entry sits on, when the file declares one
|
|
38
|
+
* (D-60 rule 3), else `undefined`. **Declaration** fixes the primary, not
|
|
39
|
+
* fulfillment: a `supports:` entry this provider leaves unsatisfied still
|
|
40
|
+
* names it, so `$app.*` does not change value with the provider's capability.
|
|
41
|
+
* The SDK caps the app at one such entry and requires it to sit on the
|
|
42
|
+
* component that owns the named endpoint, so the first match is the only one.
|
|
43
|
+
*/
|
|
44
|
+
export function declaredPrimaryComponent(launch) {
|
|
45
|
+
for (const [name, component] of Object.entries(launch.components)) {
|
|
46
|
+
for (const entry of [
|
|
47
|
+
...(component.requires ?? []),
|
|
48
|
+
...(component.supports ?? []),
|
|
49
|
+
]) {
|
|
50
|
+
if (entry.type === HTTPS_ORIGIN && entry.endpoint !== undefined)
|
|
51
|
+
return name;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Register every satisfied `https-origin` entry as a resource so its `set_env`
|
|
58
|
+
* resolves. One registered property, `url` (D-60 rule 4), holding the same
|
|
59
|
+
* string as `$app.url`. Mutates `resourceMap`; a no-op when the recorded
|
|
60
|
+
* publication URL does not satisfy the type, so an unsatisfied entry's
|
|
61
|
+
* `set_env` stays absent (never `""`) exactly as for any other resource this
|
|
62
|
+
* provider did not provision.
|
|
63
|
+
*/
|
|
64
|
+
export function wireHttpsOrigins(launch, resourceMap, appUrl) {
|
|
65
|
+
if (appUrl === undefined)
|
|
66
|
+
return;
|
|
67
|
+
const { scheme, url } = suppliedAppAddress(appUrl);
|
|
68
|
+
if (scheme !== "https")
|
|
69
|
+
return;
|
|
70
|
+
for (const component of Object.values(launch.components)) {
|
|
71
|
+
for (const entry of [
|
|
72
|
+
...(component.requires ?? []),
|
|
73
|
+
...(component.supports ?? []),
|
|
74
|
+
]) {
|
|
75
|
+
if (entry.type !== HTTPS_ORIGIN)
|
|
76
|
+
continue;
|
|
77
|
+
resourceMap[entry.name ?? entry.type] = { url };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=https-origin.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,6 @@
|
|
|
7
7
|
export { launchUp, launchDown, launchStatus, launchEnv } from "./provider.js";
|
|
8
8
|
export type { LaunchUpOpts } from "./provider.js";
|
|
9
9
|
export { launchBootstrap } from "./bootstrap.js";
|
|
10
|
+
export { InvalidAppUrlError, normalizeAppUrl } from "@launchfile/sdk";
|
|
10
11
|
export type { BootstrapResult } from "./bootstrap.js";
|
|
11
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -6,4 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { launchUp, launchDown, launchStatus, launchEnv } from "./provider.js";
|
|
8
8
|
export { launchBootstrap } from "./bootstrap.js";
|
|
9
|
+
// Re-exported so a caller of this provider catches the publication-URL refusal
|
|
10
|
+
// (D-58 rule 3) without also depending on the SDK, matching
|
|
11
|
+
// `@launchfile/docker`. The SDK owns both.
|
|
12
|
+
export { InvalidAppUrlError, normalizeAppUrl } from "@launchfile/sdk";
|
|
9
13
|
//# sourceMappingURL=index.js.map
|
package/dist/port-allocator.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare function allocatePort(key: string, existingPorts: Set<number>): P
|
|
|
16
16
|
export declare function allocatePorts(components: Record<string, {
|
|
17
17
|
provides?: Array<{
|
|
18
18
|
port: number;
|
|
19
|
+
exposed?: boolean;
|
|
19
20
|
}>;
|
|
20
21
|
}>, appName: string, savedPorts?: Record<string, number>): Promise<Record<string, number>>;
|
|
21
22
|
//# sourceMappingURL=port-allocator.d.ts.map
|
package/dist/port-allocator.js
CHANGED
|
@@ -61,8 +61,22 @@ export async function allocatePorts(components, appName, savedPorts) {
|
|
|
61
61
|
allocated.add(saved);
|
|
62
62
|
continue;
|
|
63
63
|
}
|
|
64
|
-
// Use the component's declared port if free
|
|
65
|
-
|
|
64
|
+
// Use the component's declared port if free.
|
|
65
|
+
//
|
|
66
|
+
// D-27: only an `exposed: true` endpoint is reachable from outside the
|
|
67
|
+
// host, so it anchors the single port this provider allocates. Fully
|
|
68
|
+
// internal components keep provides[0]. The docker provider answers
|
|
69
|
+
// $app.* by this same rule (app-url.ts), and this line is what makes the
|
|
70
|
+
// two agree on a file whose first entry is not the exposed one.
|
|
71
|
+
//
|
|
72
|
+
// Residual: this provider runs one host process per component and
|
|
73
|
+
// allocates it one port, so a component declaring several endpoints
|
|
74
|
+
// collapses to the anchor — every non-anchor endpoint is unallocated
|
|
75
|
+
// here. The allocated port also need not equal any declared port: when
|
|
76
|
+
// the preferred one is taken, the fall-through below picks a
|
|
77
|
+
// deterministic free port instead. See #276 for what that costs
|
|
78
|
+
// `$components.<name>.<endpoint>.*` on this provider.
|
|
79
|
+
const declaredPort = (component.provides?.find((p) => p.exposed === true) ?? component.provides?.[0])?.port;
|
|
66
80
|
if (declaredPort && !allocated.has(declaredPort) && (await isPortFree(declaredPort))) {
|
|
67
81
|
result[name] = declaredPort;
|
|
68
82
|
allocated.add(declaredPort);
|