@intentius/chant-lexicon-fly 0.124.31 → 0.124.33
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/codegen/docs-cli.js +6 -0
- package/dist/codegen/docs.js +143 -0
- package/dist/codegen/generate-cli.js +26 -0
- package/dist/codegen/generate-lexicon.js +38 -0
- package/dist/codegen/generate-typescript.js +45 -0
- package/dist/codegen/generate.js +82 -0
- package/dist/codegen/naming.js +33 -0
- package/dist/codegen/package.js +35 -0
- package/dist/components/capability-plugin.js +26 -0
- package/dist/components/fly-release.js +335 -0
- package/dist/components/index.js +10 -0
- package/dist/components/run-agent.js +133 -0
- package/dist/composites/catalog.js +176 -0
- package/dist/composites/fly-deploy.js +91 -0
- package/dist/composites/fly-otel-collector.js +66 -0
- package/dist/composites/fly-site.js +80 -0
- package/dist/coverage.js +29 -0
- package/dist/describe-resources.js +443 -0
- package/dist/detect.js +63 -0
- package/dist/export-resources.js +81 -0
- package/dist/generated/index.js +44 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/import/generator.js +208 -0
- package/dist/import/live-export.js +81 -0
- package/dist/import/parser.js +157 -0
- package/dist/index.js +40 -0
- package/dist/integrity.json +3 -3
- package/dist/lint/audit-catalog.js +28 -0
- package/dist/lint/audit-lineage.js +5 -0
- package/dist/lint/post-synth/fly-helpers.js +24 -0
- package/dist/lint/post-synth/fly010-machine-requires-image.js +33 -0
- package/dist/lint/post-synth/fly011-mount-references-declared-volume.js +58 -0
- package/dist/lint/post-synth/index.js +6 -0
- package/dist/lint/rules/guest-sizing.js +92 -0
- package/dist/lint/rules/index.js +12 -0
- package/dist/lint/rules/no-secret-literals.js +58 -0
- package/dist/lint/rules/valid-region.js +52 -0
- package/dist/lsp/completions.js +12 -0
- package/dist/lsp/hover.js +12 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/context-tools.js +113 -0
- package/dist/okf/types/Machine.md +1 -1
- package/dist/op/activities/box-services.js +88 -0
- package/dist/op/activities/emulator-images.js +32 -0
- package/dist/op/activities/flaps.js +34 -0
- package/dist/op/activities/fly-apply.js +911 -0
- package/dist/op/activities/fly-release-step.js +21 -0
- package/dist/op/activities/fly-rollback-step.js +22 -0
- package/dist/op/activities/index.js +53 -0
- package/dist/op/activities/machine-release.js +236 -0
- package/dist/op/activities/machines-contract.js +81 -0
- package/dist/op/activities/machines-fake.js +203 -0
- package/dist/op/activities/machines-local-cli.js +85 -0
- package/dist/op/activities/machines-local.js +395 -0
- package/dist/op/activities/progress.js +10 -0
- package/dist/op/activities/sprite-config.js +409 -0
- package/dist/op/activities/sprite-fs.js +131 -0
- package/dist/op/activities/sprite-service-converge.js +224 -0
- package/dist/op/activities/sprite-services.js +174 -0
- package/dist/op/activities/sprite-tasks.js +57 -0
- package/dist/op/activities/sprites-contract.js +87 -0
- package/dist/op/activities/sprites-emulator.js +72 -0
- package/dist/op/activities/sprites-fake.js +547 -0
- package/dist/op/activities/sprites.js +515 -0
- package/dist/op/activity-contracts.js +82 -0
- package/dist/op/builders.js +157 -0
- package/dist/ownership.js +29 -0
- package/dist/package-cli.js +20 -0
- package/dist/plugin.js +298 -0
- package/dist/pseudo.js +26 -0
- package/dist/reference-catalog.js +37 -0
- package/dist/release-metadata.js +59 -0
- package/dist/release-store.js +46 -0
- package/dist/serializer.js +299 -0
- package/dist/spec/fetch.js +32 -0
- package/dist/spec/parse.js +260 -0
- package/dist/teardown.js +194 -0
- package/dist/types/index.d.ts +33 -2
- package/dist/validate-cli.js +4 -0
- package/dist/validate.js +33 -0
- package/package.json +11 -11
- package/src/codegen/package.ts +2 -2
- package/src/generated/index.d.ts +33 -2
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fly deploy Op (#744) — the integration proof: `boot → build → flyApply → wait
|
|
3
|
+
* → teardown` on the local executor. The peer of the aws/gcp deploy Ops in
|
|
4
|
+
* `examples/local-cloud-trio`, for a single cloud.
|
|
5
|
+
*
|
|
6
|
+
* The phases compose modeled activities, no raw shell:
|
|
7
|
+
* Emulator boot mudflaps (the Fly Machines API fake) via `flapsUp`
|
|
8
|
+
* Build run the project's `build:fly` npm script → the serialized plan
|
|
9
|
+
* Apply `flyApply` the plan straight to flaps (creates App + Machine and
|
|
10
|
+
* waits each machine to `started` via `/wait`)
|
|
11
|
+
* Verify GET the app's machines and assert one reached `started`
|
|
12
|
+
* Teardown stop and remove the mudflaps container via `flapsDown`
|
|
13
|
+
*
|
|
14
|
+
* The activity steps are the generic `activity(fn, args)` builder tagged with
|
|
15
|
+
* the exported activity function names the fly lexicon contributes
|
|
16
|
+
* (`flapsUp`/`flapsDown`/`flyApply` from `src/op/activities`). The core activity
|
|
17
|
+
* registry resolves them by name when a project lists the `fly` lexicon, so
|
|
18
|
+
* these typed builders give `gcpApply()`-style DX without a core change.
|
|
19
|
+
*
|
|
20
|
+
* D3 (local vs real Fly): the Apply step's flaps endpoint defaults to local
|
|
21
|
+
* mudflaps (`http://localhost:4280`). Pass `endpoint: null` to drop the override
|
|
22
|
+
* and let `flyApply` fall through to `FLY_FLAPS_BASE_URL` / real Fly (with a
|
|
23
|
+
* `FLY_API_TOKEN`).
|
|
24
|
+
*/
|
|
25
|
+
import { Op, phase, build, activity, httpCheck } from "@intentius/chant/op";
|
|
26
|
+
/** The local mudflaps endpoint the deploy Op targets by default. */
|
|
27
|
+
export const LOCAL_FLAPS_ENDPOINT = "http://localhost:4280";
|
|
28
|
+
/**
|
|
29
|
+
* Boot a local mudflaps (Fly Machines API emulator) in Docker and point the
|
|
30
|
+
* apply step at it — the typed twin of `flociGcpUp` for flaps. Resolves to the
|
|
31
|
+
* `flapsUp` activity (#740). Defaults to the `longInfra` profile (the image may
|
|
32
|
+
* pull); override via `opts.profile`.
|
|
33
|
+
*/
|
|
34
|
+
export const flapsUp = (opts) => {
|
|
35
|
+
const { profile, ...args } = opts ?? {};
|
|
36
|
+
return activity("flapsUp", args, profile ?? "longInfra");
|
|
37
|
+
};
|
|
38
|
+
/** Stop and remove the local mudflaps container. Resolves to the `flapsDown` activity. Defaults to the `fastIdempotent` profile (override via `opts.profile`). */
|
|
39
|
+
export const flapsDown = (opts) => {
|
|
40
|
+
const { profile, ...args } = opts ?? {};
|
|
41
|
+
return activity("flapsDown", args, profile ?? "fastIdempotent");
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Apply the serialized flaps plan straight to the Machines API — the typed twin
|
|
45
|
+
* of `gcpApply` for fly. Resolves to the `flyApply` activity (#739): create
|
|
46
|
+
* App + Machine, wait each machine to `started`, prune owned-only when asked.
|
|
47
|
+
* Defaults to the `longInfra` profile (override via `opts.profile`).
|
|
48
|
+
*/
|
|
49
|
+
export const flyApplyStep = (planPath, opts) => {
|
|
50
|
+
const { profile, ...rest } = opts ?? {};
|
|
51
|
+
return activity("flyApply", { planPath, ...rest }, profile ?? "longInfra");
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Build a fly deploy Op: boot mudflaps, build the plan, apply App + Machine,
|
|
55
|
+
* verify the machine reached `started`, tear the emulator down. See the module
|
|
56
|
+
* docstring for the phase layout and the D3 endpoint story.
|
|
57
|
+
*/
|
|
58
|
+
export function flyDeploy(opts = {}) {
|
|
59
|
+
const path = opts.path ?? ".";
|
|
60
|
+
const planPath = opts.planPath ?? "dist/fly.json";
|
|
61
|
+
const endpoint = opts.endpoint === undefined ? LOCAL_FLAPS_ENDPOINT : opts.endpoint;
|
|
62
|
+
const applyOpts = {
|
|
63
|
+
...(endpoint ? { endpoint } : {}),
|
|
64
|
+
...(opts.prune ? { prune: true } : {}),
|
|
65
|
+
...(opts.wait ? { wait: opts.wait } : {}),
|
|
66
|
+
};
|
|
67
|
+
const phases = [
|
|
68
|
+
phase("Emulator", [flapsUp()]),
|
|
69
|
+
phase("Build", [build(path, { script: opts.buildScript ?? "build:fly" })]),
|
|
70
|
+
phase("Apply", [flyApplyStep(planPath, applyOpts)]),
|
|
71
|
+
];
|
|
72
|
+
// Verify only makes sense against a reachable local endpoint: mudflaps needs
|
|
73
|
+
// no auth, so a plain GET of the app's machines can assert `started`. Against
|
|
74
|
+
// real Fly (no endpoint) the flaps API needs a bearer token httpCheck can't
|
|
75
|
+
// carry, so the Apply step's own `/wait` is the verification there.
|
|
76
|
+
if (endpoint && opts.app) {
|
|
77
|
+
phases.push(phase("Verify", [
|
|
78
|
+
httpCheck(`${endpoint}/v1/apps/${opts.app}/machines`, {
|
|
79
|
+
contains: "started",
|
|
80
|
+
retries: 15,
|
|
81
|
+
intervalMs: 2000,
|
|
82
|
+
}),
|
|
83
|
+
]));
|
|
84
|
+
}
|
|
85
|
+
phases.push(phase("Teardown", [flapsDown()]));
|
|
86
|
+
return Op({
|
|
87
|
+
name: opts.name ?? "fly",
|
|
88
|
+
overview: opts.overview ?? "Fly: App + Machine → mudflaps (direct flaps apply), local, no account",
|
|
89
|
+
phases,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlyOtelCollector composite: an OpenTelemetry Collector on a Fly Machine.
|
|
3
|
+
*
|
|
4
|
+
* The collector config is declared with the otel lexicon
|
|
5
|
+
* (`@intentius/chant-lexicon-otel`), rendered with `collectorYaml`, and
|
|
6
|
+
* written into the machine as a file (`files[].raw_value`, base64). Other
|
|
7
|
+
* machines in the organization reach it over the private network at
|
|
8
|
+
* `<app>.internal` on the receiver ports. When the config serves a
|
|
9
|
+
* `health_check` endpoint, the machine gets an HTTP check on it.
|
|
10
|
+
*/
|
|
11
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
12
|
+
import { buildCollectorConfig, collectorEndpoints, collectorYaml, otlpCollector, COLLECTOR_CONFIG_PATH, COLLECTOR_IMAGE, } from "@intentius/chant-lexicon-otel";
|
|
13
|
+
import { Fly } from "../pseudo.js";
|
|
14
|
+
import { Machine, MachineConfig, MachineGuest, MachineInit, MachineCheck, MachineRestart, File } from "../generated/index.js";
|
|
15
|
+
/** Base64 of a UTF-8 string, the encoding flaps expects in `raw_value`. */
|
|
16
|
+
function base64(text) {
|
|
17
|
+
return Buffer.from(text, "utf8").toString("base64");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a FlyOtelCollector composite. Returns one Machine.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { App } from "@intentius/chant-lexicon-fly";
|
|
25
|
+
* import { FlyOtelCollector } from "@intentius/chant-lexicon-fly";
|
|
26
|
+
* import { OtlpHttpExporter } from "@intentius/chant-lexicon-otel";
|
|
27
|
+
*
|
|
28
|
+
* export const app = new App({ name: "otel" });
|
|
29
|
+
* export const collector = FlyOtelCollector({
|
|
30
|
+
* exporters: [new OtlpHttpExporter({ endpoint: "https://otlp.example.com" })],
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export const FlyOtelCollector = Composite((props) => {
|
|
35
|
+
const { name = "otel-collector", image = COLLECTOR_IMAGE, cpuKind = "shared", cpus = 1, memoryMb = 512, defaults: defs, } = props;
|
|
36
|
+
const entities = props.config ? [...props.config] : otlpCollector({ exporters: props.exporters, signals: props.signals });
|
|
37
|
+
const { healthCheck } = collectorEndpoints(buildCollectorConfig(entities).config);
|
|
38
|
+
const config = new MachineConfig({
|
|
39
|
+
image,
|
|
40
|
+
init: new MachineInit({ cmd: [`--config=${COLLECTOR_CONFIG_PATH}`] }),
|
|
41
|
+
files: [new File({ guest_path: COLLECTOR_CONFIG_PATH, raw_value: base64(collectorYaml(entities)) })],
|
|
42
|
+
guest: new MachineGuest({ cpu_kind: cpuKind, cpus, memory_mb: memoryMb }),
|
|
43
|
+
restart: new MachineRestart({ policy: "always" }),
|
|
44
|
+
...(healthCheck
|
|
45
|
+
? {
|
|
46
|
+
checks: {
|
|
47
|
+
health: new MachineCheck({
|
|
48
|
+
type: "http",
|
|
49
|
+
port: healthCheck.port,
|
|
50
|
+
path: healthCheck.path,
|
|
51
|
+
interval: "15s",
|
|
52
|
+
timeout: "5s",
|
|
53
|
+
grace_period: "10s",
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
: {}),
|
|
58
|
+
});
|
|
59
|
+
const machine = new Machine(mergeDefaults({
|
|
60
|
+
name,
|
|
61
|
+
region: props.region ?? Fly.Region,
|
|
62
|
+
...(props.app !== undefined ? { app: props.app } : {}),
|
|
63
|
+
config,
|
|
64
|
+
}, defs?.machine));
|
|
65
|
+
return { machine };
|
|
66
|
+
}, "FlyOtelCollector");
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlySite composite: a Fly app that serves one app's releases.
|
|
3
|
+
*
|
|
4
|
+
* It declares the App, the Machine that serves (behind Fly's proxy on 443 and
|
|
5
|
+
* 80), and, when asked for, a Volume the app's data lives on, mounted into the
|
|
6
|
+
* Machine, a public IP, and the app's Secrets. It is the fly lexicon's home
|
|
7
|
+
* for chud's `FlySite` and `ChudLocalSite` sites (ws-056, #2809): a repo that
|
|
8
|
+
* `chant workspace upgrade` takes off the chud lexicon declares its Fly site
|
|
9
|
+
* with it, so `chant workspace graph --composites` lists the site as a
|
|
10
|
+
* composite instance, and a component that names `FlySite` in its
|
|
11
|
+
* `composites` deploys it.
|
|
12
|
+
*
|
|
13
|
+
* The Machine is the one a release ships to: `chant build --lexicon fly`
|
|
14
|
+
* serializes the site into a plan with exactly one Machine, so the
|
|
15
|
+
* `fly-release` steps (a component's deploy, or an Op's `flyRelease`) find it
|
|
16
|
+
* without being told its name. `image` is what the Machine runs until a
|
|
17
|
+
* release puts its own image, or its files and start command, on it.
|
|
18
|
+
*/
|
|
19
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
20
|
+
import { Fly } from "../pseudo.js";
|
|
21
|
+
import { App, IPAddress, Machine, MachineConfig, MachineGuest, MachineMount, MachinePort, MachineService, Secret, Volume, } from "../generated/index.js";
|
|
22
|
+
/** `APP_SECRET` -> `appSecret`: a Secret's member name, never one of the other members'. */
|
|
23
|
+
function memberName(secret) {
|
|
24
|
+
const words = secret.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
25
|
+
const name = words.map((w, i) => (i === 0 ? w : w[0].toUpperCase() + w.slice(1))).join("") || "secret";
|
|
26
|
+
return ["app", "machine", "volume", "ip"].includes(name) ? `${name}Secret` : name;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Create a FlySite composite. Returns the App and the Machine, and the
|
|
30
|
+
* Volume, IP and Secrets it was asked for.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { FlySite } from "@intentius/chant-lexicon-fly";
|
|
35
|
+
*
|
|
36
|
+
* export const flySite = FlySite({
|
|
37
|
+
* app: "notes",
|
|
38
|
+
* region: "iad",
|
|
39
|
+
* image: "node:22-slim",
|
|
40
|
+
* env: { PORT: "8080", APP_DATA: "/data" },
|
|
41
|
+
* volume: { name: "data", sizeGb: 1, path: "/data" },
|
|
42
|
+
* ip: "shared_v4",
|
|
43
|
+
* secrets: { APP_SECRET: process.env.APP_SECRET || undefined },
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export const FlySite = Composite((props) => {
|
|
48
|
+
const { port = 8080, cpuKind = "shared", cpus = 1, memoryMb = 256, defaults: defs } = props;
|
|
49
|
+
const region = props.region ?? Fly.Region;
|
|
50
|
+
const app = new App(mergeDefaults({ name: props.app, org_slug: props.org ?? Fly.OrgSlug }, defs?.app));
|
|
51
|
+
const volume = props.volume
|
|
52
|
+
? new Volume(mergeDefaults({ name: props.volume.name, region, size_gb: props.volume.sizeGb }, defs?.volume))
|
|
53
|
+
: undefined;
|
|
54
|
+
const config = new MachineConfig({
|
|
55
|
+
image: props.image,
|
|
56
|
+
guest: new MachineGuest({ cpu_kind: cpuKind, cpus, memory_mb: memoryMb }),
|
|
57
|
+
...(props.volume ? { mounts: [new MachineMount({ volume: props.volume.name, path: props.volume.path })] } : {}),
|
|
58
|
+
services: [
|
|
59
|
+
new MachineService({
|
|
60
|
+
protocol: "tcp",
|
|
61
|
+
internal_port: port,
|
|
62
|
+
ports: [new MachinePort({ port: 443, handlers: ["tls", "http"] }), new MachinePort({ port: 80, handlers: ["http"] })],
|
|
63
|
+
}),
|
|
64
|
+
],
|
|
65
|
+
...(props.env ? { env: props.env } : {}),
|
|
66
|
+
});
|
|
67
|
+
const machine = new Machine(mergeDefaults({ name: props.machine ?? "web", region, config }, defs?.machine));
|
|
68
|
+
const ip = props.ip ? new IPAddress({ type: props.ip }) : undefined;
|
|
69
|
+
const secrets = {};
|
|
70
|
+
for (const [name, value] of Object.entries(props.secrets ?? {})) {
|
|
71
|
+
secrets[memberName(name)] = new Secret({ name, value });
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
app,
|
|
75
|
+
machine,
|
|
76
|
+
...(volume ? { volume } : {}),
|
|
77
|
+
...(ip ? { ip } : {}),
|
|
78
|
+
...secrets,
|
|
79
|
+
};
|
|
80
|
+
}, "FlySite");
|
package/dist/coverage.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage analysis for the fly lexicon (#804) — how much of the generated
|
|
3
|
+
* Machines API surface the lexicon's typed resources cover. Thin wrapper over
|
|
4
|
+
* the shared `computeCoverage`, mirroring the docker/gcp/github peers.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readFileSync } from "fs";
|
|
7
|
+
import { join, dirname } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { computeCoverage, overallPct, formatSummary, formatVerbose, checkThresholds, } from "@intentius/chant/codegen/coverage";
|
|
10
|
+
export { computeCoverage, overallPct, formatSummary, formatVerbose, checkThresholds };
|
|
11
|
+
/** Run coverage analysis for the fly lexicon. */
|
|
12
|
+
export async function analyzeFlyCoverage(opts) {
|
|
13
|
+
const basePath = opts?.basePath ?? dirname(dirname(fileURLToPath(import.meta.url)));
|
|
14
|
+
const lexiconPath = join(basePath, "src", "generated", "lexicon-fly.json");
|
|
15
|
+
if (!existsSync(lexiconPath)) {
|
|
16
|
+
throw new Error(`Generated lexicon not found at ${lexiconPath}. Run "chant dev generate" first.`);
|
|
17
|
+
}
|
|
18
|
+
const report = computeCoverage(readFileSync(lexiconPath, "utf-8"));
|
|
19
|
+
console.error(opts?.verbose ? formatVerbose(report) : formatSummary(report));
|
|
20
|
+
if (typeof opts?.minOverall === "number") {
|
|
21
|
+
const result = checkThresholds(report, { minOverallPct: opts.minOverall });
|
|
22
|
+
if (!result.ok) {
|
|
23
|
+
for (const v of result.violations)
|
|
24
|
+
console.error(` FAIL: ${v}`);
|
|
25
|
+
throw new Error("Coverage below threshold");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return report;
|
|
29
|
+
}
|