@intentius/chant-lexicon-fly 0.124.30 → 0.124.32

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.
Files changed (83) hide show
  1. package/dist/codegen/docs-cli.js +6 -0
  2. package/dist/codegen/docs.js +143 -0
  3. package/dist/codegen/generate-cli.js +26 -0
  4. package/dist/codegen/generate-lexicon.js +38 -0
  5. package/dist/codegen/generate-typescript.js +45 -0
  6. package/dist/codegen/generate.js +82 -0
  7. package/dist/codegen/naming.js +33 -0
  8. package/dist/codegen/package.js +35 -0
  9. package/dist/components/capability-plugin.js +26 -0
  10. package/dist/components/fly-release.js +335 -0
  11. package/dist/components/index.js +10 -0
  12. package/dist/components/run-agent.js +133 -0
  13. package/dist/composites/catalog.js +176 -0
  14. package/dist/composites/fly-deploy.js +91 -0
  15. package/dist/composites/fly-otel-collector.js +66 -0
  16. package/dist/composites/fly-site.js +80 -0
  17. package/dist/coverage.js +29 -0
  18. package/dist/describe-resources.js +443 -0
  19. package/dist/detect.js +63 -0
  20. package/dist/export-resources.js +81 -0
  21. package/dist/generated/index.js +44 -0
  22. package/dist/generated/runtime.js +4 -0
  23. package/dist/import/generator.js +208 -0
  24. package/dist/import/live-export.js +81 -0
  25. package/dist/import/parser.js +157 -0
  26. package/dist/index.js +40 -0
  27. package/dist/integrity.json +3 -3
  28. package/dist/lint/audit-catalog.js +28 -0
  29. package/dist/lint/audit-lineage.js +5 -0
  30. package/dist/lint/post-synth/fly-helpers.js +24 -0
  31. package/dist/lint/post-synth/fly010-machine-requires-image.js +33 -0
  32. package/dist/lint/post-synth/fly011-mount-references-declared-volume.js +58 -0
  33. package/dist/lint/post-synth/index.js +6 -0
  34. package/dist/lint/rules/guest-sizing.js +92 -0
  35. package/dist/lint/rules/index.js +12 -0
  36. package/dist/lint/rules/no-secret-literals.js +58 -0
  37. package/dist/lint/rules/valid-region.js +52 -0
  38. package/dist/lsp/completions.js +12 -0
  39. package/dist/lsp/hover.js +12 -0
  40. package/dist/manifest.json +1 -1
  41. package/dist/mcp/context-tools.js +113 -0
  42. package/dist/okf/types/Machine.md +1 -1
  43. package/dist/op/activities/box-services.js +88 -0
  44. package/dist/op/activities/emulator-images.js +32 -0
  45. package/dist/op/activities/flaps.js +34 -0
  46. package/dist/op/activities/fly-apply.js +911 -0
  47. package/dist/op/activities/fly-release-step.js +21 -0
  48. package/dist/op/activities/fly-rollback-step.js +22 -0
  49. package/dist/op/activities/index.js +53 -0
  50. package/dist/op/activities/machine-release.js +236 -0
  51. package/dist/op/activities/machines-contract.js +81 -0
  52. package/dist/op/activities/machines-fake.js +203 -0
  53. package/dist/op/activities/machines-local-cli.js +85 -0
  54. package/dist/op/activities/machines-local.js +395 -0
  55. package/dist/op/activities/progress.js +10 -0
  56. package/dist/op/activities/sprite-config.js +409 -0
  57. package/dist/op/activities/sprite-fs.js +131 -0
  58. package/dist/op/activities/sprite-service-converge.js +224 -0
  59. package/dist/op/activities/sprite-services.js +174 -0
  60. package/dist/op/activities/sprite-tasks.js +57 -0
  61. package/dist/op/activities/sprites-contract.js +87 -0
  62. package/dist/op/activities/sprites-emulator.js +72 -0
  63. package/dist/op/activities/sprites-fake.js +547 -0
  64. package/dist/op/activities/sprites.js +515 -0
  65. package/dist/op/activity-contracts.js +82 -0
  66. package/dist/op/builders.js +157 -0
  67. package/dist/ownership.js +29 -0
  68. package/dist/package-cli.js +20 -0
  69. package/dist/plugin.js +298 -0
  70. package/dist/pseudo.js +26 -0
  71. package/dist/reference-catalog.js +37 -0
  72. package/dist/release-metadata.js +59 -0
  73. package/dist/release-store.js +46 -0
  74. package/dist/serializer.js +299 -0
  75. package/dist/spec/fetch.js +32 -0
  76. package/dist/spec/parse.js +260 -0
  77. package/dist/teardown.js +194 -0
  78. package/dist/types/index.d.ts +33 -2
  79. package/dist/validate-cli.js +4 -0
  80. package/dist/validate.js +33 -0
  81. package/package.json +11 -11
  82. package/src/codegen/package.ts +2 -2
  83. 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");
@@ -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
+ }