@intentius/chant-lexicon-fly 0.16.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/README.md +91 -0
- package/dist/codegen/docs-cli.d.ts +3 -0
- package/dist/codegen/docs-cli.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +27 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +6 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate-lexicon.d.ts +18 -0
- package/dist/codegen/generate-lexicon.d.ts.map +1 -0
- package/dist/codegen/generate-typescript.d.ts +11 -0
- package/dist/codegen/generate-typescript.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +15 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/naming.d.ts +14 -0
- package/dist/codegen/naming.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +17 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/composites/fly-deploy.d.ts +110 -0
- package/dist/composites/fly-deploy.d.ts.map +1 -0
- package/dist/describe-resources.d.ts +74 -0
- package/dist/describe-resources.d.ts.map +1 -0
- package/dist/detect.d.ts +22 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/export-resources.d.ts +25 -0
- package/dist/export-resources.d.ts.map +1 -0
- package/dist/generated/index.d.ts +42 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/runtime.d.ts +5 -0
- package/dist/generated/runtime.d.ts.map +1 -0
- package/dist/import/generator.d.ts +21 -0
- package/dist/import/generator.d.ts.map +1 -0
- package/dist/import/live-export.d.ts +42 -0
- package/dist/import/live-export.d.ts.map +1 -0
- package/dist/import/parser.d.ts +40 -0
- package/dist/import/parser.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/integrity.json +9 -0
- package/dist/lint/post-synth/fly-helpers.d.ts +15 -0
- package/dist/lint/post-synth/fly-helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/fly010-machine-requires-image.d.ts +10 -0
- package/dist/lint/post-synth/fly010-machine-requires-image.d.ts.map +1 -0
- package/dist/lint/post-synth/fly011-mount-references-declared-volume.d.ts +12 -0
- package/dist/lint/post-synth/fly011-mount-references-declared-volume.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts +3 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -0
- package/dist/lint/rules/guest-sizing.d.ts +9 -0
- package/dist/lint/rules/guest-sizing.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +7 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lint/rules/no-secret-literals.d.ts +13 -0
- package/dist/lint/rules/no-secret-literals.d.ts.map +1 -0
- package/dist/lint/rules/valid-region.d.ts +9 -0
- package/dist/lint/rules/valid-region.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +9 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +9 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/manifest.json +4 -0
- package/dist/meta.json +285 -0
- package/dist/op/activities/flaps.d.ts +29 -0
- package/dist/op/activities/flaps.d.ts.map +1 -0
- package/dist/op/activities/fly-apply.d.ts +382 -0
- package/dist/op/activities/fly-apply.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +12 -0
- package/dist/op/activities/index.d.ts.map +1 -0
- package/dist/ownership.d.ts +27 -0
- package/dist/ownership.d.ts.map +1 -0
- package/dist/package-cli.d.ts +3 -0
- package/dist/package-cli.d.ts.map +1 -0
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/pseudo.d.ts +23 -0
- package/dist/pseudo.d.ts.map +1 -0
- package/dist/serializer.d.ts +36 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/spec/fetch.d.ts +12 -0
- package/dist/spec/fetch.d.ts.map +1 -0
- package/dist/spec/parse.d.ts +58 -0
- package/dist/spec/parse.d.ts.map +1 -0
- package/dist/types/index.d.ts +590 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate.d.ts +15 -0
- package/dist/validate.d.ts.map +1 -0
- package/package.json +76 -0
- package/src/codegen/docs-cli.ts +7 -0
- package/src/codegen/docs.ts +208 -0
- package/src/codegen/generate-cli.ts +36 -0
- package/src/codegen/generate-lexicon.ts +52 -0
- package/src/codegen/generate-typescript.ts +69 -0
- package/src/codegen/generate.ts +106 -0
- package/src/codegen/naming.ts +45 -0
- package/src/codegen/package.ts +25 -0
- package/src/composites/fly-deploy.ts +169 -0
- package/src/describe-resources.test.ts +188 -0
- package/src/describe-resources.ts +0 -0
- package/src/detect.ts +64 -0
- package/src/export-resources.ts +123 -0
- package/src/generated/.gitkeep +0 -0
- package/src/generated/index.d.ts +590 -0
- package/src/generated/index.ts +47 -0
- package/src/generated/lexicon-fly.json +285 -0
- package/src/generated/runtime.ts +4 -0
- package/src/import/generator.test.ts +123 -0
- package/src/import/generator.ts +242 -0
- package/src/import/live-export.test.ts +150 -0
- package/src/import/live-export.ts +107 -0
- package/src/import/parser.test.ts +132 -0
- package/src/import/parser.ts +185 -0
- package/src/import/roundtrip.test.ts +77 -0
- package/src/index.ts +23 -0
- package/src/lint/post-synth/fly-helpers.ts +25 -0
- package/src/lint/post-synth/fly010-machine-requires-image.test.ts +59 -0
- package/src/lint/post-synth/fly010-machine-requires-image.ts +39 -0
- package/src/lint/post-synth/fly011-mount-references-declared-volume.test.ts +92 -0
- package/src/lint/post-synth/fly011-mount-references-declared-volume.ts +61 -0
- package/src/lint/post-synth/index.ts +9 -0
- package/src/lint/rules/guest-sizing.ts +100 -0
- package/src/lint/rules/index.ts +15 -0
- package/src/lint/rules/no-secret-literals.ts +67 -0
- package/src/lint/rules/rules.test.ts +127 -0
- package/src/lint/rules/valid-region.ts +58 -0
- package/src/lsp/completions.test.ts +10 -0
- package/src/lsp/completions.ts +14 -0
- package/src/lsp/hover.test.ts +10 -0
- package/src/lsp/hover.ts +14 -0
- package/src/op/activities/describe-resources.integration.test.ts +105 -0
- package/src/op/activities/flaps.test.ts +29 -0
- package/src/op/activities/flaps.ts +44 -0
- package/src/op/activities/fly-apply.integration.test.ts +245 -0
- package/src/op/activities/fly-apply.test.ts +513 -0
- package/src/op/activities/fly-apply.ts +1014 -0
- package/src/op/activities/index.ts +48 -0
- package/src/ownership.ts +32 -0
- package/src/package-cli.ts +24 -0
- package/src/plugin.test.ts +17 -0
- package/src/plugin.ts +285 -0
- package/src/pseudo.test.ts +32 -0
- package/src/pseudo.ts +29 -0
- package/src/serializer.test.ts +232 -0
- package/src/serializer.ts +293 -0
- package/src/skills/chant-fly-ops.md +55 -0
- package/src/skills/chant-fly-patterns.md +71 -0
- package/src/skills/chant-fly.md +123 -0
- package/src/spec/fetch.ts +34 -0
- package/src/spec/parse.ts +323 -0
- package/src/validate-cli.ts +5 -0
- package/src/validate.ts +28 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fly (Machines API / "flaps") documentation generator.
|
|
3
|
+
*
|
|
4
|
+
* Calls the core docsPipeline with fly-specific config: service grouping and
|
|
5
|
+
* overview content. Produces a standalone Starlight docs site at
|
|
6
|
+
* lexicons/fly/docs/, like the aws and gcp lexicons.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { dirname, join } from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { docsPipeline, writeDocsSite, type DocsConfig } from "@intentius/chant/codegen/docs";
|
|
12
|
+
|
|
13
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The curated top-level fly resources (#741), in the order they read best in a
|
|
17
|
+
* reference table. Property types (MachineConfig, MachineGuest, ...) are grouped
|
|
18
|
+
* separately by the pipeline and are not repeated here.
|
|
19
|
+
*/
|
|
20
|
+
const RESOURCES: Array<{ className: string; resourceType: string }> = [
|
|
21
|
+
{ className: "App", resourceType: "Fly::Machines::App" },
|
|
22
|
+
{ className: "Machine", resourceType: "Fly::Machines::Machine" },
|
|
23
|
+
{ className: "Volume", resourceType: "Fly::Machines::Volume" },
|
|
24
|
+
{ className: "IPAddress", resourceType: "Fly::Machines::IPAddress" },
|
|
25
|
+
{ className: "Certificate", resourceType: "Fly::Machines::Certificate" },
|
|
26
|
+
{ className: "Secret", resourceType: "Fly::Machines::Secret" },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Group a fly resource type into a service bucket for the reference sidebar.
|
|
31
|
+
* fly type names are `Fly::Machines::<Kind>`, so the middle segment is always
|
|
32
|
+
* "Machines"; grouping by the leaf kind gives a more useful split (Apps,
|
|
33
|
+
* Machines, Storage, Networking, Secrets).
|
|
34
|
+
*/
|
|
35
|
+
export function serviceFromType(resourceType: string): string {
|
|
36
|
+
const kind = resourceType.split("::")[2] ?? resourceType;
|
|
37
|
+
switch (kind) {
|
|
38
|
+
case "App":
|
|
39
|
+
return "Apps";
|
|
40
|
+
case "Machine":
|
|
41
|
+
return "Machines";
|
|
42
|
+
case "Volume":
|
|
43
|
+
return "Storage";
|
|
44
|
+
case "IPAddress":
|
|
45
|
+
case "Certificate":
|
|
46
|
+
return "Networking";
|
|
47
|
+
case "Secret":
|
|
48
|
+
return "Secrets";
|
|
49
|
+
default:
|
|
50
|
+
return "Machines";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The reference doc URL for a fly resource type. The Machines API (flaps) is
|
|
56
|
+
* documented at docs.machines.dev, tagged by resource; resources flaps does not
|
|
57
|
+
* own (public IPs, certificates) link to the matching fly.io docs page.
|
|
58
|
+
*/
|
|
59
|
+
export function resourceTypeUrl(resourceType: string): string {
|
|
60
|
+
const kind = resourceType.split("::")[2] ?? resourceType;
|
|
61
|
+
switch (kind) {
|
|
62
|
+
case "App":
|
|
63
|
+
return "https://docs.machines.dev/#tag/apps";
|
|
64
|
+
case "Machine":
|
|
65
|
+
return "https://docs.machines.dev/#tag/machines";
|
|
66
|
+
case "Volume":
|
|
67
|
+
return "https://docs.machines.dev/#tag/volumes";
|
|
68
|
+
case "Secret":
|
|
69
|
+
return "https://docs.machines.dev/#tag/secrets";
|
|
70
|
+
case "IPAddress":
|
|
71
|
+
return "https://fly.io/docs/networking/services/";
|
|
72
|
+
case "Certificate":
|
|
73
|
+
return "https://fly.io/docs/networking/custom-domains-with-fly/";
|
|
74
|
+
default:
|
|
75
|
+
return "https://docs.machines.dev/";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const overview = `The **Fly Machines** lexicon defines Fly.io apps and machines using chant's declarative TypeScript syntax. Resources are serialized to the JSON create bodies the Machines API ("flaps") accepts, so the applier can POST them straight through and the mudflaps emulator can round-trip them offline.
|
|
80
|
+
|
|
81
|
+
This lexicon is generated from Fly's published [Machines API OpenAPI spec](https://docs.machines.dev/openapi.json).
|
|
82
|
+
|
|
83
|
+
Install it with:
|
|
84
|
+
|
|
85
|
+
\`\`\`bash
|
|
86
|
+
npm install --save-dev @intentius/chant-lexicon-fly
|
|
87
|
+
\`\`\`
|
|
88
|
+
|
|
89
|
+
## Quick Start
|
|
90
|
+
|
|
91
|
+
\`\`\`typescript
|
|
92
|
+
import { App, Machine, MachineConfig, MachineGuest, Fly } from "@intentius/chant-lexicon-fly";
|
|
93
|
+
|
|
94
|
+
const app = new App({ name: "my-app", org_slug: Fly.OrgSlug });
|
|
95
|
+
|
|
96
|
+
const web = new Machine({
|
|
97
|
+
name: "web",
|
|
98
|
+
region: Fly.Region,
|
|
99
|
+
config: new MachineConfig({
|
|
100
|
+
image: "flyio/hellofly:latest",
|
|
101
|
+
guest: new MachineGuest({ cpu_kind: "shared", cpus: 1, memory_mb: 256 }),
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export { app, web };
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
\`Fly.Region\` and \`Fly.OrgSlug\` are pseudo-parameters resolved from the environment at build time (\`FLY_REGION\`, \`FLY_ORG\` / \`FLY_ORG_SLUG\`).`;
|
|
109
|
+
|
|
110
|
+
const outputFormat = `The fly lexicon serializes resources into the JSON create bodies the Machines API ("flaps") accepts.
|
|
111
|
+
|
|
112
|
+
## Building
|
|
113
|
+
|
|
114
|
+
Each declared resource becomes a single flaps request keyed by its logical name:
|
|
115
|
+
|
|
116
|
+
\`\`\`json
|
|
117
|
+
{
|
|
118
|
+
"web": {
|
|
119
|
+
"endpoint": "/v1/apps/my-app/machines",
|
|
120
|
+
"method": "POST",
|
|
121
|
+
"body": { "name": "web", "region": "iad", "config": { ... } }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
\`\`\`
|
|
125
|
+
|
|
126
|
+
- An \`App\` becomes \`POST /v1/apps { app_name, org_slug? }\`.
|
|
127
|
+
- A \`Machine\` becomes \`POST /v1/apps/{app}/machines\` with the full \`MachineConfig\` as \`config\`. The owning app is a URL path segment, not a body field.
|
|
128
|
+
- \`Volume\`, \`IPAddress\`, \`Certificate\`, and \`Secret\` are app-scoped and POST under their app.
|
|
129
|
+
|
|
130
|
+
Every serialized machine carries the \`managed-by: chant\` ownership marker in \`config.metadata\`, which the owned-only prune reads back.
|
|
131
|
+
|
|
132
|
+
## Applying
|
|
133
|
+
|
|
134
|
+
The output is applied against flaps directly (or the mudflaps emulator offline). Endpoint and auth come from \`FLY_FLAPS_BASE_URL\` and \`FLY_API_TOKEN\`.`;
|
|
135
|
+
|
|
136
|
+
const resourcesPage = `The fly lexicon ships ${RESOURCES.length} top-level resources. Each maps to a Machines API create body; follow the reference link for the underlying API shape.
|
|
137
|
+
|
|
138
|
+
| Resource | Type | Service | Machines API |
|
|
139
|
+
|----------|------|---------|--------------|
|
|
140
|
+
${RESOURCES.map(
|
|
141
|
+
(r) =>
|
|
142
|
+
`| \`${r.className}\` | \`${r.resourceType}\` | ${serviceFromType(r.resourceType)} | [reference](${resourceTypeUrl(r.resourceType)}) |`,
|
|
143
|
+
).join("\n")}
|
|
144
|
+
|
|
145
|
+
Property types such as \`MachineConfig\`, \`MachineGuest\`, \`MachineService\`, \`MachinePort\`, and \`MachineMount\` are authored inline on a resource and are documented alongside the resources they belong to.`;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Generate documentation for the fly Machines lexicon.
|
|
149
|
+
*/
|
|
150
|
+
export async function generateDocs(options?: { verbose?: boolean }): Promise<void> {
|
|
151
|
+
const config: DocsConfig = {
|
|
152
|
+
name: "fly",
|
|
153
|
+
displayName: "Fly Machines",
|
|
154
|
+
description: "Typed constructors for Fly.io apps and machines, serialized to Machines API create bodies",
|
|
155
|
+
distDir: join(pkgDir, "dist"),
|
|
156
|
+
outDir: join(pkgDir, "docs"),
|
|
157
|
+
srcDir: join(pkgDir, "src"),
|
|
158
|
+
basePath: process.env.DOCS_BASE_PATH ?? "/chant/lexicons/fly/",
|
|
159
|
+
overview,
|
|
160
|
+
outputFormat,
|
|
161
|
+
serviceFromType,
|
|
162
|
+
extraPages: [
|
|
163
|
+
{
|
|
164
|
+
slug: "resources",
|
|
165
|
+
title: "Resource Reference",
|
|
166
|
+
description: "The fly resource types and their Machines API documentation links",
|
|
167
|
+
content: resourcesPage,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
slug: "pseudo-params",
|
|
171
|
+
title: "Pseudo-parameters",
|
|
172
|
+
description: "Environment-resolved values: Fly.Region, Fly.OrgSlug, Fly.AppName",
|
|
173
|
+
content: `Pseudo-parameters stand in for values that vary by environment. Reference them instead of hard-coding, and the serializer resolves each from an environment variable at build time.
|
|
174
|
+
|
|
175
|
+
| Pseudo-parameter | Environment variable | Fallback |
|
|
176
|
+
|------------------|----------------------|----------|
|
|
177
|
+
| \`Fly.Region\` | \`FLY_REGION\` | \`iad\` |
|
|
178
|
+
| \`Fly.OrgSlug\` | \`FLY_ORG\`, then \`FLY_ORG_SLUG\` | \`personal\` |
|
|
179
|
+
| \`Fly.AppName\` | \`FLY_APP_NAME\` | \`app\` |
|
|
180
|
+
|
|
181
|
+
\`\`\`typescript
|
|
182
|
+
import { App, Machine, MachineConfig, MachineGuest, Fly } from "@intentius/chant-lexicon-fly";
|
|
183
|
+
|
|
184
|
+
const app = new App({ name: "my-app", org_slug: Fly.OrgSlug });
|
|
185
|
+
|
|
186
|
+
const web = new Machine({
|
|
187
|
+
region: Fly.Region,
|
|
188
|
+
config: new MachineConfig({
|
|
189
|
+
image: "flyio/hellofly:latest",
|
|
190
|
+
guest: new MachineGuest({ cpu_kind: "shared", cpus: 1, memory_mb: 256 }),
|
|
191
|
+
}),
|
|
192
|
+
});
|
|
193
|
+
\`\`\`
|
|
194
|
+
|
|
195
|
+
With \`FLY_REGION=lhr\` set, \`region\` serializes to \`"lhr"\`; unset, it falls back to \`"iad"\`.`,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const result = docsPipeline(config);
|
|
201
|
+
writeDocsSite(config, result);
|
|
202
|
+
|
|
203
|
+
if (options?.verbose) {
|
|
204
|
+
console.error(
|
|
205
|
+
`Generated docs: ${result.stats.resources} resources, ${result.stats.properties} properties, ${result.stats.services} services, ${result.stats.rules} rules`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for fly lexicon generation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { dirname } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { generate, writeGeneratedFiles } from "./generate";
|
|
9
|
+
|
|
10
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
14
|
+
const force = process.argv.includes("--force") || process.argv.includes("-f");
|
|
15
|
+
|
|
16
|
+
console.error("Generating fly lexicon...");
|
|
17
|
+
|
|
18
|
+
const result = await generate({ verbose, force });
|
|
19
|
+
writeGeneratedFiles(result, pkgDir);
|
|
20
|
+
|
|
21
|
+
console.error(
|
|
22
|
+
`Generated ${result.resources} resources, ${result.properties} property types, ${result.enums} enums`,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (result.warnings.length > 0) {
|
|
26
|
+
console.error(`${result.warnings.length} warnings:`);
|
|
27
|
+
for (const w of result.warnings) {
|
|
28
|
+
console.error(` ${w.file}: ${w.error}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
main().catch((err) => {
|
|
34
|
+
console.error("Generation failed:", err);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lexicon JSON generator — produces lexicon-fly.json with metadata for the
|
|
3
|
+
* fly resource and property types.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PropertyConstraints } from "@intentius/chant/codegen/json-schema";
|
|
7
|
+
import type { FlyParseResult } from "../spec/parse";
|
|
8
|
+
import { flyShortName } from "../spec/parse";
|
|
9
|
+
import type { NamingStrategy } from "./naming";
|
|
10
|
+
import { buildRegistry, serializeRegistry, type RegistryResource } from "@intentius/chant/codegen/generate-registry";
|
|
11
|
+
|
|
12
|
+
export interface FlyLexiconEntry {
|
|
13
|
+
resourceType: string;
|
|
14
|
+
kind: "resource" | "property";
|
|
15
|
+
lexicon: "fly";
|
|
16
|
+
constraints?: Record<string, PropertyConstraints>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate the lexicon-fly.json content.
|
|
21
|
+
*/
|
|
22
|
+
export function generateLexiconJSON(results: FlyParseResult[], naming: NamingStrategy): string {
|
|
23
|
+
const registryResources: RegistryResource[] = results.map((r) => ({
|
|
24
|
+
typeName: r.resource.typeName,
|
|
25
|
+
attributes: r.resource.attributes,
|
|
26
|
+
properties: r.resource.properties,
|
|
27
|
+
propertyTypes: [],
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
const entries = buildRegistry<FlyLexiconEntry>(registryResources, naming, {
|
|
31
|
+
shortName: flyShortName,
|
|
32
|
+
buildEntry: (resource, _tsName, _attrs, propConstraints) => {
|
|
33
|
+
const r = results.find((res) => res.resource.typeName === resource.typeName);
|
|
34
|
+
const entry: FlyLexiconEntry = {
|
|
35
|
+
resourceType: resource.typeName,
|
|
36
|
+
kind: r?.isProperty ? "property" : "resource",
|
|
37
|
+
lexicon: "fly",
|
|
38
|
+
};
|
|
39
|
+
if (propConstraints && Object.keys(propConstraints).length > 0) {
|
|
40
|
+
entry.constraints = propConstraints;
|
|
41
|
+
}
|
|
42
|
+
return entry;
|
|
43
|
+
},
|
|
44
|
+
buildPropertyEntry: (resourceType, propertyType) => ({
|
|
45
|
+
resourceType: `${resourceType}.${propertyType}`,
|
|
46
|
+
kind: "property",
|
|
47
|
+
lexicon: "fly",
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return serializeRegistry(entries);
|
|
52
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript declaration file generator — produces index.d.ts with the fly
|
|
3
|
+
* resource classes (App, Machine, Volume) and their property-type classes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FlyParseResult } from "../spec/parse";
|
|
7
|
+
import type { NamingStrategy } from "./naming";
|
|
8
|
+
import {
|
|
9
|
+
writeResourceClass,
|
|
10
|
+
writePropertyClass,
|
|
11
|
+
type DtsProperty,
|
|
12
|
+
type DtsAttribute,
|
|
13
|
+
} from "@intentius/chant/codegen/generate-typescript";
|
|
14
|
+
|
|
15
|
+
interface Entry {
|
|
16
|
+
tsName: string;
|
|
17
|
+
isProperty: boolean;
|
|
18
|
+
properties: DtsProperty[];
|
|
19
|
+
attributes: DtsAttribute[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate the complete .d.ts file content.
|
|
24
|
+
*/
|
|
25
|
+
export function generateTypeScriptDeclarations(results: FlyParseResult[], naming: NamingStrategy): string {
|
|
26
|
+
const lines: string[] = [];
|
|
27
|
+
lines.push("// Code generated by chant fly generate. DO NOT EDIT.");
|
|
28
|
+
|
|
29
|
+
const entries: Entry[] = [];
|
|
30
|
+
|
|
31
|
+
for (const r of results) {
|
|
32
|
+
const tsName = naming.resolve(r.resource.typeName);
|
|
33
|
+
if (!tsName) continue;
|
|
34
|
+
|
|
35
|
+
const properties: DtsProperty[] = r.resource.properties.map((p) => ({
|
|
36
|
+
name: p.name,
|
|
37
|
+
type: p.tsType,
|
|
38
|
+
required: p.required,
|
|
39
|
+
description: p.description,
|
|
40
|
+
}));
|
|
41
|
+
const attributes: DtsAttribute[] = r.resource.attributes.map((a) => ({ name: a.name, type: a.tsType }));
|
|
42
|
+
|
|
43
|
+
entries.push({ tsName, isProperty: !!r.isProperty, properties, attributes });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
entries.sort((a, b) => a.tsName.localeCompare(b.tsName));
|
|
47
|
+
|
|
48
|
+
const resources = entries.filter((e) => !e.isProperty);
|
|
49
|
+
const propertyTypes = entries.filter((e) => e.isProperty);
|
|
50
|
+
|
|
51
|
+
if (resources.length > 0) {
|
|
52
|
+
lines.push("");
|
|
53
|
+
lines.push("// --- Fly Resource classes ---");
|
|
54
|
+
for (const re of resources) {
|
|
55
|
+
writeResourceClass(lines, re.tsName, re.properties, re.attributes);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (propertyTypes.length > 0) {
|
|
60
|
+
lines.push("");
|
|
61
|
+
lines.push("// --- Property type classes ---");
|
|
62
|
+
for (const pe of propertyTypes) {
|
|
63
|
+
writePropertyClass(lines, pe.tsName, pe.properties);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
lines.push("");
|
|
68
|
+
return lines.join("\n");
|
|
69
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fly generation pipeline — uses core generatePipeline with fly-specific
|
|
3
|
+
* fetch, parse, naming, and generation callbacks.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
generatePipeline,
|
|
8
|
+
writeGeneratedArtifacts,
|
|
9
|
+
type GenerateOptions,
|
|
10
|
+
type GenerateResult,
|
|
11
|
+
type GeneratePipelineConfig,
|
|
12
|
+
} from "@intentius/chant/codegen/generate";
|
|
13
|
+
import { fetchSchemas } from "../spec/fetch";
|
|
14
|
+
import { parseFlyOpenAPI, type FlyParseResult } from "../spec/parse";
|
|
15
|
+
import { NamingStrategy } from "./naming";
|
|
16
|
+
import { generateLexiconJSON } from "./generate-lexicon";
|
|
17
|
+
import { generateTypeScriptDeclarations } from "./generate-typescript";
|
|
18
|
+
import {
|
|
19
|
+
generateRuntimeIndex as coreGenerateRuntimeIndex,
|
|
20
|
+
type RuntimeIndexEntry,
|
|
21
|
+
type RuntimeIndexPropertyEntry,
|
|
22
|
+
} from "@intentius/chant/codegen/generate-runtime-index";
|
|
23
|
+
|
|
24
|
+
export type { GenerateResult };
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Run the full fly generation pipeline.
|
|
28
|
+
*/
|
|
29
|
+
export async function generate(opts: GenerateOptions = {}): Promise<GenerateResult> {
|
|
30
|
+
// The flaps spec is a single document — parseFlyOpenAPI returns multiple
|
|
31
|
+
// results. The pipeline calls parseSchema once, so we return the first result
|
|
32
|
+
// and inject the rest via augmentResults.
|
|
33
|
+
let pendingResults: FlyParseResult[] = [];
|
|
34
|
+
|
|
35
|
+
const config: GeneratePipelineConfig<FlyParseResult> = {
|
|
36
|
+
fetchSchemas: (fetchOpts) => fetchSchemas({ force: fetchOpts.force }),
|
|
37
|
+
|
|
38
|
+
parseSchema: (_typeName, data) => {
|
|
39
|
+
const results = parseFlyOpenAPI(data);
|
|
40
|
+
if (results.length === 0) return null;
|
|
41
|
+
pendingResults = results.slice(1);
|
|
42
|
+
return results[0];
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
createNaming: (results) => new NamingStrategy(results),
|
|
46
|
+
|
|
47
|
+
augmentResults: (results, _opts, log) => {
|
|
48
|
+
if (pendingResults.length > 0) {
|
|
49
|
+
results.push(...pendingResults);
|
|
50
|
+
log(`Added ${pendingResults.length} additional fly schemas from OpenAPI spec`);
|
|
51
|
+
pendingResults = [];
|
|
52
|
+
}
|
|
53
|
+
log(`Total: ${results.length} fly resource/property schemas`);
|
|
54
|
+
return { results };
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
generateRegistry: (results, naming) => generateLexiconJSON(results, naming as NamingStrategy),
|
|
58
|
+
|
|
59
|
+
generateTypes: (results, naming) => generateTypeScriptDeclarations(results, naming as NamingStrategy),
|
|
60
|
+
|
|
61
|
+
generateRuntimeIndex: (results, naming) => generateRuntimeIndex(results, naming as NamingStrategy),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return generatePipeline(config, opts);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Write generated artifacts to disk.
|
|
69
|
+
*/
|
|
70
|
+
export function writeGeneratedFiles(result: GenerateResult, baseDir: string): void {
|
|
71
|
+
writeGeneratedArtifacts({
|
|
72
|
+
baseDir,
|
|
73
|
+
files: {
|
|
74
|
+
"lexicon-fly.json": result.lexiconJSON,
|
|
75
|
+
"index.d.ts": result.typesDTS,
|
|
76
|
+
"index.ts": result.indexTS,
|
|
77
|
+
"runtime.ts": `/**\n * Runtime factory constructors — re-exported from core.\n */\nexport { createResource, createProperty } from "@intentius/chant/runtime";\n`,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Generate the runtime index.ts with factory constructor exports.
|
|
84
|
+
*/
|
|
85
|
+
function generateRuntimeIndex(results: FlyParseResult[], naming: NamingStrategy): string {
|
|
86
|
+
const resourceEntries: RuntimeIndexEntry[] = [];
|
|
87
|
+
const propertyEntries: RuntimeIndexPropertyEntry[] = [];
|
|
88
|
+
|
|
89
|
+
for (const r of results) {
|
|
90
|
+
const typeName = r.resource.typeName;
|
|
91
|
+
const tsName = naming.resolve(typeName);
|
|
92
|
+
if (!tsName) continue;
|
|
93
|
+
|
|
94
|
+
if (r.isProperty) {
|
|
95
|
+
propertyEntries.push({ tsName, resourceType: typeName });
|
|
96
|
+
} else {
|
|
97
|
+
const attrs: Record<string, string> = {};
|
|
98
|
+
for (const attr of r.resource.attributes) {
|
|
99
|
+
attrs[attr.name] = attr.name;
|
|
100
|
+
}
|
|
101
|
+
resourceEntries.push({ tsName, resourceType: typeName, attrs });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return coreGenerateRuntimeIndex(resourceEntries, propertyEntries, { lexiconName: "fly" });
|
|
106
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fly naming strategy — maps Fly::Machines::{Kind} type names to
|
|
3
|
+
* user-friendly TypeScript class names.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
NamingStrategy as CoreNamingStrategy,
|
|
8
|
+
type NamingConfig,
|
|
9
|
+
type NamingInput,
|
|
10
|
+
} from "@intentius/chant/codegen/naming";
|
|
11
|
+
import { flyShortName, flyServiceName, type FlyParseResult } from "../spec/parse";
|
|
12
|
+
|
|
13
|
+
export { propertyTypeName, extractDefName } from "@intentius/chant/codegen/naming";
|
|
14
|
+
|
|
15
|
+
const flyNamingConfig: NamingConfig = {
|
|
16
|
+
priorityNames: {
|
|
17
|
+
"Fly::Machines::App": "App",
|
|
18
|
+
"Fly::Machines::Machine": "Machine",
|
|
19
|
+
"Fly::Machines::Volume": "Volume",
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
priorityAliases: {},
|
|
23
|
+
|
|
24
|
+
priorityPropertyAliases: {},
|
|
25
|
+
|
|
26
|
+
serviceAbbreviations: {
|
|
27
|
+
Machines: "Machines",
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
shortName: flyShortName,
|
|
31
|
+
serviceName: flyServiceName,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Fly-specific naming strategy. Extends the core strategy with the fly config.
|
|
36
|
+
*/
|
|
37
|
+
export class NamingStrategy extends CoreNamingStrategy {
|
|
38
|
+
constructor(results: FlyParseResult[]) {
|
|
39
|
+
const inputs: NamingInput[] = results.map((r) => ({
|
|
40
|
+
typeName: r.resource.typeName,
|
|
41
|
+
propertyTypes: r.propertyTypes,
|
|
42
|
+
}));
|
|
43
|
+
super(inputs, flyNamingConfig);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { packagePipeline } from "@intentius/chant/codegen/package";
|
|
2
|
+
import type { PackagePipelineConfig } from "@intentius/chant/codegen/package";
|
|
3
|
+
import { generate } from "./generate";
|
|
4
|
+
import { dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Package the fly lexicon for distribution.
|
|
9
|
+
*/
|
|
10
|
+
export async function packageLexicon(options?: { verbose?: boolean; force?: boolean }) {
|
|
11
|
+
const srcDir = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
const { spec, stats } = await packagePipeline({
|
|
14
|
+
generate: (opts) => generate({ verbose: opts?.verbose, force: opts?.force }),
|
|
15
|
+
buildManifest: (genResult) => ({
|
|
16
|
+
name: "fly",
|
|
17
|
+
version: "0.0.1",
|
|
18
|
+
}),
|
|
19
|
+
srcDir,
|
|
20
|
+
collectSkills: () => new Map(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules`);
|
|
24
|
+
return { spec, stats };
|
|
25
|
+
}
|