@launchfile/sdk
TypeScript SDK for parsing, validating, and serializing Launchfiles.
Install
bun add launchfile
# or
npm install launchfile
CLI
The SDK includes a launchfile CLI for validating and inspecting Launchfiles.
# Validate a Launchfile (defaults to ./Launchfile)
launchfile validate
launchfile validate path/to/Launchfile
# Structured JSON output for CI pipelines
launchfile validate --json
# Silent mode — just the exit code
launchfile validate --quiet
# Evaluate as if fetched standalone rather than read from the app's own
# checkout — enables the D-43 reduced-portability check (PROVIDERS.md §6)
launchfile validate --detached
# Print the normalized form (after shorthand expansion) as JSON
launchfile inspect path/to/Launchfile
# Dump the JSON Schema to stdout
launchfile schema
The CLI refuses any long flag it does not declare: it prints Unknown flag: --<name> to stderr and exits 1 before running anything, rather than ignoring the flag. --schema-path without a value exits 1 the same way.
npx launchfile runs the separate launchfile package, not this CLI. This CLI is the @launchfile/sdk/cli export (dist/cli.js).
Global flags
--no-color— Disable colored output (also respectsNO_COLORenv var)--version— Print version--help— Show usage
Reduced-portability warnings (D-40, D-43)
validate warns, non-fatally, when a component has no portable build path
(runtime and/or commands.build/commands.install) or — with --detached
— is source-needing with no repository: to fall back to. SetLAUNCHFILE_NO_PORTABILITY_WARNINGS (to any value except 0/false) to
silence both; every other validate warning keeps firing. The lintLaunch(launch, opts?) SDK export takes the
same two options (detached, suppressPortabilityWarnings) directly.
Validate in CI
# GitHub Actions
- run: npx launchfile validate --quiet
Editor Integration
Add JSON Schema support for autocompletion and validation in your editor:
# yaml-language-server: $schema=https://launchfile.dev/schema/v1
version: launch/v1
name: my-app
Usage
Parse a Launchfile
import { readLaunch } from "launchfile";
const app = readLaunch(`
name: my-app
runtime: node
requires: [postgres]
commands:
start: "node server.js"
health: /health
`);
// app.components.default.requires → [{ type: "postgres" }]
// app.components.default.health → { path: "/health" }
Validate pre-parsed data
import { validateLaunch } from "launchfile";
const app = validateLaunch({
name: "my-app",
runtime: "node",
requires: ["postgres"],
});
Write back to YAML
import { writeLaunch } from "launchfile";
const yaml = writeLaunch(app);
// Collapses shorthands: { type: "postgres" } → "postgres"
Resolve expressions
import { resolveExpression } from "launchfile";
const url = resolveExpression("postgresql://${host}:${port}/${name}", {
resource: { host: "localhost", port: 5432, name: "mydb" },
});
// → "postgresql://localhost:5432/mydb"
Check for expressions
import { isExpression } from "launchfile";
isExpression("$url"); // true
isExpression("hello"); // false
isExpression("$$escaped"); // false (literal $)
API
Every value export of src/index.ts is either a row below or an entry inEXCLUDED_EXPORTS (scripts/check-readme-exports.ts, with a one-line reason —
mostly CLI-command implementations and the provider error-context vocabulary).bun run check:exports (wired as a pretest hook) fails bun run test — and
CI’s sdk job — if a value export is undocumented, or if a row/exclusion goes
stale. For a row that lists parameters, it also checks the list against theexport function declaration: the same number of parameters, optional (? or
a default) in the same positions. A signature it cannot map — overloads, rest,this or destructured parameters, a const or class — fails the check rather
than being skipped, unless ARITY_UNCHECKED in the same script lists the row
with a reason. useKeyOf is listed there: it destructures its one parameter.
The check reads names and parameter counts only. It does not read the
Description column, parameter names, or what a non-function export such asCERTIFICATE means. A row that names a real export, lists the right
parameters, and still describes it wrongly is caught only by a person reading
the source.
Parse, validate, serialize
| Function | Description |
|---|---|
readLaunch(yaml) |
Parse YAML string → validated, normalized NormalizedLaunch |
parseLaunchYaml(yaml) |
Parse YAML string → raw, un-normalized, un-validated data. Used internally by readLaunch; exposed for callers that need the document before validation strips unrecognized keys |
validateLaunch(data) |
Validate a parsed object → NormalizedLaunch |
writeLaunch(launch) |
Serialize NormalizedLaunch → compact YAML string |
LaunchSchema |
Zod schema for direct validation |
parseRepository(repository) |
Split a repository value at its # fragment → { url, ref } |
Expressions
| Function | Description |
|---|---|
parseExpression(value) |
Parse a $-expression into an AST |
resolveExpression(value, context) |
Resolve expression against a context → string |
isExpression(value) |
Check if a string contains $ references |
parseDotPath(path) |
Parse "a.b.c" → ["a", "b", "c"] |
deriveAppUrlProperties(url) |
Split a URL into the { authority, scheme, tls } triple $app.* expressions resolve against |
Named endpoints (D-63)
$app.endpoints.<name>.* addresses one named published endpoint’s public
address; $components.<component>.<endpoint>.* addresses a named listener from
inside the deployment.
| Function | Description |
|---|---|
endpointProperties(provides, host, activeCertificates?) |
The <endpoint>.{host, port, protocol, url} map a provider registers for a component’s named provides entries, reading each entry’s effective listener (D-61 rule 2) |
appEndpointReferences(launch) |
Every $app.endpoints… reference in the file’s env: defaults and set_env: values, in declaration order — so a provider can warn only about the endpoints the app actually asks for |
APP_ENDPOINT_PROPERTIES |
The properties $app.endpoints.<name>.* addresses: the standard $app.* set (D-33, D-35) less name |
UNPUBLISHED_APP_ENDPOINT |
The answer for an endpoint the provider publishes no address for (D-63 rule 4) — every property "", degrading as an unknown $app.* property does (L-4) |
REFUSED_PRIMARY_ADDRESS |
The $app.* address of a primary whose component is refused (D-72): UNPUBLISHED_APP_ENDPOINT with tls reading "false", so a literal on/off flag still receives a boolean. One object for every provider (P-5) |
Publication context (D-58)
The orchestrator-supplied public URL a provider resolves $app.* from when
routing is owned upstream. A malformed value is refused, never degraded.
| Function | Description |
|---|---|
normalizeAppUrl(value) |
Validate and normalize a supplied publication URL → the WHATWG serialization with a lone root path dropped. Idempotent; throws InvalidAppUrlError on anything but an absolute http/https URL with no userinfo, query, or fragment |
suppliedAppAddress(appUrl) |
The address a supplied URL determines (D-58 rule 2): { host, port, url, authority, scheme, tls } — the $app.* set less name |
suppliedAppProperties(name, appUrl) |
name plus suppliedAppAddress, for a provider resolving the whole $app.* set in one step |
httpsOriginSatisfied(appUrl) |
Whether a supplied URL satisfies an https-origin entry (D-60 rule 5): its scheme is https. Syntactic only — undefined and an http URL both fail; a malformed URL throws InvalidAppUrlError. One predicate for every provider (P-5) |
InvalidAppUrlError |
Thrown for a refused appUrl (D-58 rule 3). The constructor masks userinfo in the displayed value, so no refusal path can echo an embedded credential (D-18, CWE-532) |
Listeners and certificates (D-61)
A provides entry’s protocol/port are its declared listener; its
effective listener is what that listener speaks in the configuration the
deployment selected. They differ only when a bound certificate is active.
| Function | Description |
|---|---|
effectiveListener(entry, activeCertificates?) |
Read one provides entry’s listener in both readings. Omit activeCertificates and the entry reads as its baseline |
boundCertificate(entry) |
The certificate name an entry binds, in either spelling (tls: server-cert or tls: { certificate: server-cert }), else undefined |
certificateBindings(component) |
Every certificate binding on one component, as provides entry → certificate name |
CERTIFICATE |
The supports: entry type a tls: binding names — type: certificate (D-61 rule 1) |
atDeclarations(launch) |
Every provides entry that declares at: — its component, index, name and values (D-68). A provider sets up each value or reports it |
atEntryLabel(declaration) |
How an at: declaration’s entry is named in a message: `provides` entry "web" on web |
AT_APP_HOST |
The at: value that names the app host itself — "@" (D-68 rule 2) |
Resource uses
A uses item is either a bare token (db) or a single-key map ({ db: cache })
naming one occurrence of a repeatable use. The use key — db, or db.cache
— is the prefix providers register properties under and $<resource>.<use>.…
addresses.
| Function | Description |
|---|---|
declaredUse(item) |
Decode one uses item → { use, name? } |
useKey(item) |
The use key of one item as written: db, or db.cache for { db: cache } |
useKeyOf(declared) |
The use key of an already-decoded DeclaredUse |
useKeys(uses) |
The use keys of a uses list, in declaration order |
parseUseKey(key) |
Split a use key back into { use, name? } |
formatUseKey(key) |
The spelling diagnostics use: db for a bare key, db: cache for a named one |
allocateDbIndexes(launch) |
One numbered redis database per db use key, app-wide, keyed by resource name — the allocation every provider applies (D-65): blocks in the order of each resource’s first db-declaring entry, requires before supports, the bare db first and named ones in name order |
namedDatabase(instance, name) |
The database a named database use gets on a SQL server: <instance>_<name>, hyphens as underscores |
withDatabasePath(url, database) |
url with its path replaced by /<database>, query and fragment kept |
isRepeatableUse(type, use) |
Whether the standard vocabulary lets use occur more than once on one type entry; undefined outside the registry, where the provider decides (L-4) |
RESOURCE_USE_VOCABULARY |
Standard use vocabulary by resource type → use → the properties it registers. Advisory: lint warns, the schema never rejects |
UnresolvedUseError |
Thrown when a $<resource>.<use>.<property> path names a use the entry does not declare, or a property the use does not register. Not softened by :-default — the path is wrong, not empty |
Command capture
One formatter for every surface a provider prints captures on, so sensitive
means the same thing on each (SPEC.md § Command Capture). Masking is display
only — keeping a value out of logs and state files is the provider redactor’s
job.
| Function | Description |
|---|---|
formatCaptures(captures, captureMeta, reveal, options?) |
The indented lines a provider prints for one command’s captures. reveal: true prints every value; otherwise a sensitive: true entry prints as CAPTURE_MASK, and one REVEAL_HINT line follows unless options.hint is false |
sensitiveCaptureValues(captures, captureMeta) |
The values of every capture whose entry declares sensitive: true — what a provider registers with its redactor |
CAPTURE_MASK |
The mask a sensitive value displays as |
REVEAL_HINT |
The trailing line naming the command that prints masked values |
Component selection
| Function | Description |
|---|---|
selectComponents(launch, requested) |
Resolve a requested component/resource name list against the launch → known, unknown, and resource names |
selectionClosure(launch, requested) |
selectComponents, extended with the D-41 dependency-closure start set |
Linting
validate runs these; call them directly to build a custom check.
| Function | Description |
|---|---|
lintLaunch(launch, opts?) |
Run every structural/portability lint over a normalized launch → warning strings |
lintDeprecations(launch) |
Report deprecated fields present in the file (P-14/D-42), each carrying migration guidance |
lintDurations(launch) |
Check every duration-valued field against the ratified duration grammar (P-9) |
lintUnknownStorageKeys(raw) |
Check the raw (pre-normalization) document for storage: keys the schema doesn’t recognize |
DURATION_PATTERN |
The duration grammar regex every duration field is checked against (P-9) |
isValidDuration(value) |
True when value matches DURATION_PATTERN |
parseDurationMs(value) |
Parse a duration string ("30s", "5m", …) → milliseconds |
Environment, storage, and host capabilities
| Function | Description |
|---|---|
unsuppliedRequiredEnv(component, suppliedKeys) |
List the component’s required: variables that no value source in the file actually supplies |
indexOperatorStoragePaths(launch, suppliedPaths) |
Index operator-supplied storage paths against the launch’s content: operator volumes (D-50), for per-volume lookup |
UnboundOperatorStorageError |
Thrown when a content: operator volume has no supplied path (D-50 row 2) |
MissingOperatorStoragePathError |
Thrown when an operator-supplied storage path does not exist or is not readable on the host (D-50 row 3); the directory is never created |
collectHostCapabilities(launch) |
Collect the app’s requested host capabilities (D-44) as "name=value (required|optional)" strings |
collectOperatorStorage(launch) |
Collect the volumes marked content: operator (D-50) as "component.volume" strings |
checkVersionRange(declared, provided) |
Classify a requires[].version range against the version or version family a provider runs → satisfied, unsatisfied, undecidable, unknown (provided is undefined), or invalid (not a node-semver range). Each provider phrases its own report (D-74) |
RESOURCE_PROPERTY_VOCABULARY |
Standard resource property vocabulary by resource type (SPEC.md § Resource Property Vocabulary, D-46) |
Source mode
| Function | Description |
|---|---|
resolveSourceRunCommand(component) |
Resolve which command runs a source-mode component: commands.dev wins, then commands.start — but image (no dev) returns undefined rather than falling back to start |
resolveSourcePrepareCommand(component) |
Resolve which command prepares a source-mode component (commands.install → commands.build) |
Deployment state
A pure event-sourced state model — fold LaunchEvents into a DeploymentState,
diff two states back into events, and resolve $-references against a state.
| Function | Description |
|---|---|
reduce(state, event, at?) |
Fold one LaunchEvent into DeploymentState → the next state |
diff(prev, next) |
Compare two DeploymentStates → the LaunchEvents that would fold prev into next |
resolveRef(state, ref, vantage) |
Resolve a $-reference against a DeploymentState → string (never throws on an unresolved reference) |
Toolchain detection
| Function | Description |
|---|---|
extractToolchainVersions(repoDir) |
Discover per-language toolchain versions declared in a repo checkout (package.json, .tool-versions, …) → Promise<ToolchainVersions> |
Types
All types are exported:
import type {
Launch,
NormalizedLaunch,
Component,
NormalizedComponent,
Requirement,
Provides,
EnvVar,
// ... see types.ts for full list
} from "launchfile";