@intentius/chant-lexicon-render 0.53.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 +89 -0
- package/dist/catalog.d.ts +100 -0
- package/dist/catalog.d.ts.map +1 -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 +21 -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 +12 -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/render-deploy.d.ts +88 -0
- package/dist/composites/render-deploy.d.ts.map +1 -0
- package/dist/coverage.d.ts +15 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/describe-resources.d.ts +60 -0
- package/dist/describe-resources.d.ts.map +1 -0
- package/dist/detect.d.ts +12 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/generated/index.d.ts +38 -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/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/integrity.json +18 -0
- package/dist/lint/audit-catalog.d.ts +13 -0
- package/dist/lint/audit-catalog.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/post-synth/ren010-native-runtime-requires-commands.d.ts +12 -0
- package/dist/lint/post-synth/ren010-native-runtime-requires-commands.d.ts.map +1 -0
- package/dist/lint/post-synth/ren011-service-requires-source.d.ts +12 -0
- package/dist/lint/post-synth/ren011-service-requires-source.d.ts.map +1 -0
- package/dist/lint/post-synth/ren012-free-plan-cannot-scale.d.ts +11 -0
- package/dist/lint/post-synth/ren012-free-plan-cannot-scale.d.ts.map +1 -0
- package/dist/lint/post-synth/render-helpers.d.ts +19 -0
- package/dist/lint/post-synth/render-helpers.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-cron-schedule.d.ts +12 -0
- package/dist/lint/rules/valid-cron-schedule.d.ts.map +1 -0
- package/dist/lint/rules/valid-region.d.ts +16 -0
- package/dist/lint/rules/valid-region.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +8 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +6 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/manifest.json +6 -0
- package/dist/meta.json +304 -0
- package/dist/okf/index.md +29 -0
- package/dist/okf/rules/REN001.md +11 -0
- package/dist/okf/rules/REN002.md +15 -0
- package/dist/okf/rules/REN003.md +15 -0
- package/dist/okf/rules/REN010.md +11 -0
- package/dist/okf/rules/REN011.md +11 -0
- package/dist/okf/rules/REN012.md +11 -0
- package/dist/okf/types/BackgroundWorker.md +24 -0
- package/dist/okf/types/CronJob.md +28 -0
- package/dist/okf/types/CustomDomain.md +14 -0
- package/dist/okf/types/Disk.md +16 -0
- package/dist/okf/types/EnvGroup.md +22 -0
- package/dist/okf/types/Environment.md +17 -0
- package/dist/okf/types/KeyValue.md +20 -0
- package/dist/okf/types/Postgres.md +29 -0
- package/dist/okf/types/PrivateService.md +24 -0
- package/dist/okf/types/Project.md +15 -0
- package/dist/okf/types/RegistryCredential.md +17 -0
- package/dist/okf/types/StaticSite.md +24 -0
- package/dist/okf/types/WebService.md +24 -0
- package/dist/okf/types/Webhook.md +17 -0
- package/dist/op/activities/fake-render.d.ts +52 -0
- package/dist/op/activities/fake-render.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +10 -0
- package/dist/op/activities/index.d.ts.map +1 -0
- package/dist/op/activities/render-apply.d.ts +229 -0
- package/dist/op/activities/render-apply.d.ts.map +1 -0
- package/dist/ownership.d.ts +32 -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 +21 -0
- package/dist/pseudo.d.ts.map +1 -0
- package/dist/rules/no-secret-literals.ts +73 -0
- package/dist/rules/ren010-native-runtime-requires-commands.ts +47 -0
- package/dist/rules/ren011-service-requires-source.ts +50 -0
- package/dist/rules/ren012-free-plan-cannot-scale.ts +43 -0
- package/dist/rules/render-helpers.ts +38 -0
- package/dist/rules/valid-cron-schedule.ts +51 -0
- package/dist/rules/valid-region.ts +49 -0
- package/dist/serializer.d.ts +99 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/skills/chant-render-patterns.md +126 -0
- package/dist/skills/chant-render.md +112 -0
- package/dist/spec/fetch.d.ts +21 -0
- package/dist/spec/fetch.d.ts.map +1 -0
- package/dist/spec/parse.d.ts +120 -0
- package/dist/spec/parse.d.ts.map +1 -0
- package/dist/types/index.d.ts +573 -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 +73 -0
- package/src/catalog.ts +286 -0
- package/src/codegen/docs-cli.ts +7 -0
- package/src/codegen/docs.ts +120 -0
- package/src/codegen/generate-cli.ts +36 -0
- package/src/codegen/generate-lexicon.ts +52 -0
- package/src/codegen/generate-typescript.ts +70 -0
- package/src/codegen/generate.ts +106 -0
- package/src/codegen/naming.ts +55 -0
- package/src/codegen/package.ts +30 -0
- package/src/composites/render-deploy.ts +133 -0
- package/src/coverage.ts +49 -0
- package/src/describe-resources.test.ts +143 -0
- package/src/describe-resources.ts +366 -0
- package/src/detect.ts +29 -0
- package/src/generated/index.d.ts +573 -0
- package/src/generated/index.ts +43 -0
- package/src/generated/lexicon-render.json +304 -0
- package/src/generated/runtime.ts +4 -0
- package/src/index.ts +30 -0
- package/src/lint/audit-catalog.ts +47 -0
- package/src/lint/post-synth/index.ts +11 -0
- package/src/lint/post-synth/post-synth.test.ts +151 -0
- package/src/lint/post-synth/ren010-native-runtime-requires-commands.ts +47 -0
- package/src/lint/post-synth/ren011-service-requires-source.ts +50 -0
- package/src/lint/post-synth/ren012-free-plan-cannot-scale.ts +43 -0
- package/src/lint/post-synth/render-helpers.ts +38 -0
- package/src/lint/rules/index.ts +11 -0
- package/src/lint/rules/no-secret-literals.ts +73 -0
- package/src/lint/rules/rules.test.ts +73 -0
- package/src/lint/rules/valid-cron-schedule.ts +51 -0
- package/src/lint/rules/valid-region.ts +49 -0
- package/src/lsp/completions.test.ts +28 -0
- package/src/lsp/completions.ts +22 -0
- package/src/lsp/hover.test.ts +32 -0
- package/src/lsp/hover.ts +50 -0
- package/src/op/activities/fake-render.ts +275 -0
- package/src/op/activities/index.ts +51 -0
- package/src/op/activities/render-apply.test.ts +403 -0
- package/src/op/activities/render-apply.ts +1042 -0
- package/src/ownership.ts +44 -0
- package/src/package-cli.ts +23 -0
- package/src/plugin.test.ts +54 -0
- package/src/plugin.ts +258 -0
- package/src/pseudo.ts +26 -0
- package/src/serializer.test.ts +221 -0
- package/src/serializer.ts +286 -0
- package/src/skills/chant-render-patterns.md +126 -0
- package/src/skills/chant-render.md +112 -0
- package/src/spec/fetch.ts +33 -0
- package/src/spec/parse.ts +721 -0
- package/src/validate-cli.ts +5 -0
- package/src/validate.ts +47 -0
package/src/catalog.ts
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Render resource catalog — the one place that knows, per chant entity
|
|
3
|
+
* type, which REST collection it lives in, how it is identified, and how it is
|
|
4
|
+
* ordered against its neighbours. The serializer, the applier, the observation
|
|
5
|
+
* seam, and the lint rules all read this table so the four cannot drift apart
|
|
6
|
+
* on a resource's wire shape.
|
|
7
|
+
*
|
|
8
|
+
* Render's Public API is a plain REST surface (`https://api.render.com/v1`):
|
|
9
|
+
* - collections are `POST /services`, `POST /postgres`, ...; children of a
|
|
10
|
+
* service are `POST /services/{serviceId}/custom-domains`;
|
|
11
|
+
* - a resource is identified by an opaque id (`srv-…`, `dpg-…`) Render
|
|
12
|
+
* assigns on create; the *name* is the human key chant reconciles by, and
|
|
13
|
+
* is unique per workspace for services (Render enforces it) and, in
|
|
14
|
+
* practice, for the rest of what chant declares;
|
|
15
|
+
* - lists paginate by cursor and filter by `name` + `ownerId`;
|
|
16
|
+
* - updates are `PATCH` with the resource's PATCH DTO — a strict subset of
|
|
17
|
+
* the POST body (`ownerId`, `type`, `envVars` and friends are excluded).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// ── Entity types ───────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export const ENTITY_TYPES = {
|
|
23
|
+
webService: "Render::Services::WebService",
|
|
24
|
+
staticSite: "Render::Services::StaticSite",
|
|
25
|
+
privateService: "Render::Services::PrivateService",
|
|
26
|
+
backgroundWorker: "Render::Services::BackgroundWorker",
|
|
27
|
+
cronJob: "Render::Services::CronJob",
|
|
28
|
+
postgres: "Render::Datastores::Postgres",
|
|
29
|
+
keyValue: "Render::Datastores::KeyValue",
|
|
30
|
+
envGroup: "Render::Config::EnvGroup",
|
|
31
|
+
registryCredential: "Render::Config::RegistryCredential",
|
|
32
|
+
webhook: "Render::Config::Webhook",
|
|
33
|
+
project: "Render::Projects::Project",
|
|
34
|
+
environment: "Render::Projects::Environment",
|
|
35
|
+
disk: "Render::Services::Disk",
|
|
36
|
+
customDomain: "Render::Services::CustomDomain",
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
39
|
+
export type EntityType = (typeof ENTITY_TYPES)[keyof typeof ENTITY_TYPES];
|
|
40
|
+
|
|
41
|
+
/** The five service entity types (share `POST /services`). */
|
|
42
|
+
export const SERVICE_ENTITY_TYPES: readonly string[] = [
|
|
43
|
+
ENTITY_TYPES.webService,
|
|
44
|
+
ENTITY_TYPES.staticSite,
|
|
45
|
+
ENTITY_TYPES.privateService,
|
|
46
|
+
ENTITY_TYPES.backgroundWorker,
|
|
47
|
+
ENTITY_TYPES.cronJob,
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
/** Service entity type → the `type` discriminator `POST /services` takes. */
|
|
51
|
+
export const SERVICE_TYPE_OF: Record<string, string> = {
|
|
52
|
+
[ENTITY_TYPES.webService]: "web_service",
|
|
53
|
+
[ENTITY_TYPES.staticSite]: "static_site",
|
|
54
|
+
[ENTITY_TYPES.privateService]: "private_service",
|
|
55
|
+
[ENTITY_TYPES.backgroundWorker]: "background_worker",
|
|
56
|
+
[ENTITY_TYPES.cronJob]: "cron_job",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** The reverse: a live service's `type` → chant entity type. */
|
|
60
|
+
export const ENTITY_TYPE_OF_SERVICE: Record<string, string> = Object.fromEntries(
|
|
61
|
+
Object.entries(SERVICE_TYPE_OF).map(([k, v]) => [v, k]),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
export function isServiceEntityType(entityType: string | undefined): boolean {
|
|
65
|
+
return !!entityType && SERVICE_ENTITY_TYPES.includes(entityType);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Catalog ────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
/** How one resource kind maps onto the REST API. */
|
|
71
|
+
export interface CatalogEntry {
|
|
72
|
+
/** Short kind name — the generated class name (`WebService`, `Postgres`). */
|
|
73
|
+
kind: string;
|
|
74
|
+
/**
|
|
75
|
+
* The create collection. May carry a `{serviceId}` placeholder that the
|
|
76
|
+
* serializer fills from the resource's `serviceId` prop (a literal id, or a
|
|
77
|
+
* `{ $ref }` the applier resolves before issuing the request).
|
|
78
|
+
*/
|
|
79
|
+
collection: string;
|
|
80
|
+
/**
|
|
81
|
+
* The key in each element of the list envelope: `GET /services` returns
|
|
82
|
+
* `[{ service: {...}, cursor }]`. `null` for lists that return bare objects.
|
|
83
|
+
*/
|
|
84
|
+
listKey: string | null;
|
|
85
|
+
/** Whether the list endpoint accepts `?name=` and `?ownerId=` filters. */
|
|
86
|
+
filters: { name: boolean; ownerId: boolean };
|
|
87
|
+
/** Whether the API offers `PATCH /{collection}/{id}` for updates. */
|
|
88
|
+
patchable: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* The body fields the PATCH DTO accepts. Fields outside this set are
|
|
91
|
+
* create-only (or apply through a side endpoint — env vars) and are excluded
|
|
92
|
+
* from the diff so a rename of `ownerId` never proposes an impossible update.
|
|
93
|
+
*/
|
|
94
|
+
patchFields: readonly string[];
|
|
95
|
+
/** Whether the resource carries chant's env-var ownership marker. */
|
|
96
|
+
marked: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* The ownership boundary for a kind with no marker of its own. `"service"`:
|
|
99
|
+
* the resource hangs off a service (`serviceId`), and inherits that service's
|
|
100
|
+
* verdict — a disk or custom domain under a chant-owned service is chant's,
|
|
101
|
+
* the same way fly treats volumes and IPs at the app boundary. Absent: no
|
|
102
|
+
* boundary either; the verdict is `unknown` and the kind is never pruned.
|
|
103
|
+
*/
|
|
104
|
+
boundary?: "service";
|
|
105
|
+
/**
|
|
106
|
+
* Whether the create body takes an `ownerId` (workspace). When it does and
|
|
107
|
+
* the author omitted it, the serializer fills it from `Render.OwnerId`.
|
|
108
|
+
*/
|
|
109
|
+
ownerScoped: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Apply ordering. Lower applies first; delete runs in reverse. Projects
|
|
112
|
+
* before environments before everything that can join one; services before
|
|
113
|
+
* the disks/domains that hang off them.
|
|
114
|
+
*/
|
|
115
|
+
order: number;
|
|
116
|
+
/** Chant-side props that are association hints, not body fields. */
|
|
117
|
+
nonBodyProps: readonly string[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const SERVICE_PATCH_FIELDS = [
|
|
121
|
+
"autoDeploy",
|
|
122
|
+
"repo",
|
|
123
|
+
"branch",
|
|
124
|
+
"image",
|
|
125
|
+
"name",
|
|
126
|
+
"buildFilter",
|
|
127
|
+
"rootDir",
|
|
128
|
+
"serviceDetails",
|
|
129
|
+
] as const;
|
|
130
|
+
|
|
131
|
+
function serviceEntry(kind: string): CatalogEntry {
|
|
132
|
+
return {
|
|
133
|
+
kind,
|
|
134
|
+
collection: "/services",
|
|
135
|
+
listKey: "service",
|
|
136
|
+
filters: { name: true, ownerId: true },
|
|
137
|
+
patchable: true,
|
|
138
|
+
patchFields: SERVICE_PATCH_FIELDS,
|
|
139
|
+
marked: true,
|
|
140
|
+
ownerScoped: true,
|
|
141
|
+
order: 40,
|
|
142
|
+
nonBodyProps: [],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const CATALOG: Record<string, CatalogEntry> = {
|
|
147
|
+
[ENTITY_TYPES.project]: {
|
|
148
|
+
kind: "Project",
|
|
149
|
+
collection: "/projects",
|
|
150
|
+
listKey: "project",
|
|
151
|
+
filters: { name: true, ownerId: true },
|
|
152
|
+
patchable: true,
|
|
153
|
+
patchFields: ["name"],
|
|
154
|
+
marked: false,
|
|
155
|
+
ownerScoped: true,
|
|
156
|
+
order: 10,
|
|
157
|
+
nonBodyProps: [],
|
|
158
|
+
},
|
|
159
|
+
[ENTITY_TYPES.environment]: {
|
|
160
|
+
kind: "Environment",
|
|
161
|
+
collection: "/environments",
|
|
162
|
+
listKey: "environment",
|
|
163
|
+
filters: { name: true, ownerId: false },
|
|
164
|
+
patchable: true,
|
|
165
|
+
patchFields: ["name", "protectedStatus", "networkIsolationEnabled", "ipAllowList"],
|
|
166
|
+
marked: false,
|
|
167
|
+
ownerScoped: false,
|
|
168
|
+
order: 20,
|
|
169
|
+
nonBodyProps: [],
|
|
170
|
+
},
|
|
171
|
+
[ENTITY_TYPES.registryCredential]: {
|
|
172
|
+
kind: "RegistryCredential",
|
|
173
|
+
collection: "/registrycredentials",
|
|
174
|
+
listKey: "registryCredential",
|
|
175
|
+
filters: { name: true, ownerId: true },
|
|
176
|
+
patchable: true,
|
|
177
|
+
patchFields: ["registry", "name", "username", "authToken"],
|
|
178
|
+
marked: false,
|
|
179
|
+
ownerScoped: true,
|
|
180
|
+
order: 25,
|
|
181
|
+
nonBodyProps: [],
|
|
182
|
+
},
|
|
183
|
+
[ENTITY_TYPES.envGroup]: {
|
|
184
|
+
kind: "EnvGroup",
|
|
185
|
+
collection: "/env-groups",
|
|
186
|
+
listKey: "envGroup",
|
|
187
|
+
filters: { name: true, ownerId: true },
|
|
188
|
+
patchable: true,
|
|
189
|
+
patchFields: ["name", "environmentId"],
|
|
190
|
+
marked: true,
|
|
191
|
+
ownerScoped: true,
|
|
192
|
+
order: 30,
|
|
193
|
+
nonBodyProps: [],
|
|
194
|
+
},
|
|
195
|
+
[ENTITY_TYPES.postgres]: {
|
|
196
|
+
kind: "Postgres",
|
|
197
|
+
collection: "/postgres",
|
|
198
|
+
listKey: "postgres",
|
|
199
|
+
filters: { name: true, ownerId: true },
|
|
200
|
+
patchable: true,
|
|
201
|
+
patchFields: [
|
|
202
|
+
"name",
|
|
203
|
+
"plan",
|
|
204
|
+
"enableHighAvailability",
|
|
205
|
+
"diskSizeGB",
|
|
206
|
+
"enableDiskAutoscaling",
|
|
207
|
+
"readReplicas",
|
|
208
|
+
"ipAllowList",
|
|
209
|
+
"parameterOverrides",
|
|
210
|
+
],
|
|
211
|
+
marked: false,
|
|
212
|
+
ownerScoped: true,
|
|
213
|
+
order: 30,
|
|
214
|
+
nonBodyProps: [],
|
|
215
|
+
},
|
|
216
|
+
[ENTITY_TYPES.keyValue]: {
|
|
217
|
+
kind: "KeyValue",
|
|
218
|
+
collection: "/key-value",
|
|
219
|
+
listKey: "keyValue",
|
|
220
|
+
filters: { name: true, ownerId: true },
|
|
221
|
+
patchable: true,
|
|
222
|
+
patchFields: ["name", "plan", "maxmemoryPolicy", "ipAllowList"],
|
|
223
|
+
marked: false,
|
|
224
|
+
ownerScoped: true,
|
|
225
|
+
order: 30,
|
|
226
|
+
nonBodyProps: [],
|
|
227
|
+
},
|
|
228
|
+
[ENTITY_TYPES.webService]: serviceEntry("WebService"),
|
|
229
|
+
[ENTITY_TYPES.staticSite]: serviceEntry("StaticSite"),
|
|
230
|
+
[ENTITY_TYPES.privateService]: serviceEntry("PrivateService"),
|
|
231
|
+
[ENTITY_TYPES.backgroundWorker]: serviceEntry("BackgroundWorker"),
|
|
232
|
+
[ENTITY_TYPES.cronJob]: serviceEntry("CronJob"),
|
|
233
|
+
[ENTITY_TYPES.disk]: {
|
|
234
|
+
kind: "Disk",
|
|
235
|
+
collection: "/disks",
|
|
236
|
+
listKey: "disk",
|
|
237
|
+
filters: { name: true, ownerId: true },
|
|
238
|
+
patchable: true,
|
|
239
|
+
patchFields: ["name", "sizeGB", "mountPath"],
|
|
240
|
+
marked: false,
|
|
241
|
+
boundary: "service",
|
|
242
|
+
ownerScoped: false,
|
|
243
|
+
order: 50,
|
|
244
|
+
nonBodyProps: [],
|
|
245
|
+
},
|
|
246
|
+
[ENTITY_TYPES.customDomain]: {
|
|
247
|
+
kind: "CustomDomain",
|
|
248
|
+
collection: "/services/{serviceId}/custom-domains",
|
|
249
|
+
listKey: "customDomain",
|
|
250
|
+
filters: { name: true, ownerId: false },
|
|
251
|
+
patchable: false,
|
|
252
|
+
patchFields: [],
|
|
253
|
+
marked: false,
|
|
254
|
+
boundary: "service",
|
|
255
|
+
ownerScoped: false,
|
|
256
|
+
order: 50,
|
|
257
|
+
nonBodyProps: ["serviceId"],
|
|
258
|
+
},
|
|
259
|
+
[ENTITY_TYPES.webhook]: {
|
|
260
|
+
kind: "Webhook",
|
|
261
|
+
collection: "/webhooks",
|
|
262
|
+
listKey: "webhook",
|
|
263
|
+
filters: { name: false, ownerId: true },
|
|
264
|
+
patchable: true,
|
|
265
|
+
patchFields: ["url", "name", "enabled", "eventFilter"],
|
|
266
|
+
marked: false,
|
|
267
|
+
ownerScoped: true,
|
|
268
|
+
order: 60,
|
|
269
|
+
nonBodyProps: [],
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/** Look up a catalog entry, or throw — an unknown entity type is a lexicon bug, not user error. */
|
|
274
|
+
export function catalogEntry(entityType: string): CatalogEntry {
|
|
275
|
+
const entry = CATALOG[entityType];
|
|
276
|
+
if (!entry) throw new Error(`render: no catalog entry for entity type ${entityType}`);
|
|
277
|
+
return entry;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Entity type ← short kind (`WebService` → `Render::Services::WebService`). */
|
|
281
|
+
export function entityTypeOfKind(kind: string): string | undefined {
|
|
282
|
+
for (const [type, entry] of Object.entries(CATALOG)) {
|
|
283
|
+
if (entry.kind === kind) return type;
|
|
284
|
+
}
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render documentation generator.
|
|
3
|
+
*
|
|
4
|
+
* Calls the core docsPipeline with render-specific config: service grouping and
|
|
5
|
+
* overview content. Produces a standalone Starlight docs site at
|
|
6
|
+
* lexicons/render/docs/, like the fly 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
|
+
* Group a render resource type into a bucket for the reference sidebar. Type
|
|
17
|
+
* names are `Render::<Group>::<Kind>`; the middle segment is the group
|
|
18
|
+
* (Services, Datastores, Config, Projects). Property types are all filed under
|
|
19
|
+
* Services by the generator; keep them there.
|
|
20
|
+
*/
|
|
21
|
+
export function serviceFromType(resourceType: string): string {
|
|
22
|
+
return resourceType.split("::")[1] ?? resourceType;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const overview = `The **Render** lexicon defines Render services, datastores, env groups, projects, and environments using chant's declarative TypeScript syntax. Resources are serialized to the JSON create bodies the [Render Public API](https://api-docs.render.com/) accepts, so the applier can POST them straight through — no Blueprint, no CLI, no state file.
|
|
26
|
+
|
|
27
|
+
This lexicon is generated from Render's published [OpenAPI spec](https://api-docs.render.com/v1.0/openapi/render-public-api-1.json).
|
|
28
|
+
|
|
29
|
+
Install it with:
|
|
30
|
+
|
|
31
|
+
\`\`\`bash
|
|
32
|
+
npm install --save-dev @intentius/chant-lexicon-render
|
|
33
|
+
\`\`\`
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
\`\`\`typescript
|
|
38
|
+
import {
|
|
39
|
+
WebService, WebServiceDetails, NativeEnvironmentDetails, EnvVar, GeneratedEnvVar, Postgres, Render,
|
|
40
|
+
} from "@intentius/chant-lexicon-render";
|
|
41
|
+
|
|
42
|
+
const db = new Postgres({ name: "app-db", plan: "free", version: "16", region: Render.Region });
|
|
43
|
+
|
|
44
|
+
const web = new WebService({
|
|
45
|
+
name: "app-web",
|
|
46
|
+
repo: "https://github.com/render-examples/express-hello-world",
|
|
47
|
+
serviceDetails: new WebServiceDetails({
|
|
48
|
+
runtime: "node",
|
|
49
|
+
plan: "starter",
|
|
50
|
+
region: Render.Region,
|
|
51
|
+
envSpecificDetails: new NativeEnvironmentDetails({ buildCommand: "npm ci", startCommand: "npm start" }),
|
|
52
|
+
}),
|
|
53
|
+
envVars: [
|
|
54
|
+
new EnvVar({ key: "DATABASE_URL", value: db.internalConnectionString }),
|
|
55
|
+
new GeneratedEnvVar({ key: "SESSION_SECRET", generateValue: true }),
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export { db, web };
|
|
60
|
+
\`\`\`
|
|
61
|
+
|
|
62
|
+
\`Render.OwnerId\` and \`Render.Region\` are pseudo-parameters resolved from the environment at build time (\`RENDER_OWNER_ID\`, \`RENDER_REGION\`). Every \`ownerId\` chant fills in defaults to \`Render.OwnerId\`, so a stack rarely names it. \`db.internalConnectionString\` is an attribute read the applier resolves from the live database.`;
|
|
63
|
+
|
|
64
|
+
const outputFormat = `The render lexicon serializes resources into the JSON create bodies the Render Public API accepts.
|
|
65
|
+
|
|
66
|
+
## Building
|
|
67
|
+
|
|
68
|
+
Each declared resource becomes a single request keyed by its logical name:
|
|
69
|
+
|
|
70
|
+
\`\`\`json
|
|
71
|
+
{
|
|
72
|
+
"web": {
|
|
73
|
+
"kind": "WebService",
|
|
74
|
+
"entityType": "Render::Services::WebService",
|
|
75
|
+
"endpoint": "/services",
|
|
76
|
+
"method": "POST",
|
|
77
|
+
"name": "app-web",
|
|
78
|
+
"body": { "type": "web_service", "name": "app-web", "ownerId": "tea-…", "serviceDetails": { ... }, "envVars": [ ... ] }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
\`\`\`
|
|
82
|
+
|
|
83
|
+
- Each service type becomes \`POST /services\` with its \`type\` discriminator re-injected and \`serviceDetails\` in that type's shape.
|
|
84
|
+
- \`Postgres\` → \`POST /postgres\`; \`KeyValue\` → \`POST /key-value\`; \`EnvGroup\` → \`POST /env-groups\`; \`Project\` → \`POST /projects\`; \`Environment\` → \`POST /environments\`; \`Disk\` → \`POST /disks\`; \`RegistryCredential\` → \`POST /registrycredentials\`; \`Webhook\` → \`POST /webhooks\`.
|
|
85
|
+
- \`CustomDomain\` → \`POST /services/{serviceId}/custom-domains\`; the service is a path segment carried in \`pathParams\`.
|
|
86
|
+
|
|
87
|
+
Three markers may appear in a body, resolved by the applier once their target is live: \`{ "$ref": "<entity>" }\` (a declared resource's id), \`{ "$attr": { "entity", "attribute" } }\` (an attribute read — \`id\`, \`dashboardUrl\`, or a datastore connection string), and \`{ "$owner": true }\` (the workspace, when neither the author nor \`RENDER_OWNER_ID\` named one).
|
|
88
|
+
|
|
89
|
+
Every serialized service and env group carries the \`CHANT_MANAGED_BY=chant\` ownership marker (plus \`CHANT_STACK\` / \`CHANT_ENV\`) in its \`envVars\`, which the owned-only prune reads back.
|
|
90
|
+
|
|
91
|
+
## Applying
|
|
92
|
+
|
|
93
|
+
The output is applied against the Public API directly by \`renderApply\` (via \`chant run\` and the \`renderDeploy\` Op). Endpoint and auth come from \`RENDER_API_BASE_URL\` and \`RENDER_API_KEY\`; the workspace from \`RENDER_OWNER_ID\` (or the sole workspace the key can see). Each resource is found by name and created or PATCHed; created services are waited to \`live\`; with \`prune\` the owned services and env groups no longer declared — and the disks and custom domains under owned services no longer declared — are deleted.`;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Generate documentation for the render lexicon.
|
|
97
|
+
*/
|
|
98
|
+
export async function generateDocs(options?: { verbose?: boolean }): Promise<void> {
|
|
99
|
+
const config: DocsConfig = {
|
|
100
|
+
name: "render",
|
|
101
|
+
displayName: "Render",
|
|
102
|
+
description: "Typed constructors for Render services, datastores, and env groups, serialized to Public API create bodies",
|
|
103
|
+
distDir: join(pkgDir, "dist"),
|
|
104
|
+
outDir: join(pkgDir, "docs"),
|
|
105
|
+
srcDir: join(pkgDir, "src"),
|
|
106
|
+
basePath: process.env.DOCS_BASE_PATH ?? "/chant/lexicons/render/",
|
|
107
|
+
overview,
|
|
108
|
+
outputFormat,
|
|
109
|
+
serviceFromType,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const result = docsPipeline(config);
|
|
113
|
+
writeDocsSite(config, result);
|
|
114
|
+
|
|
115
|
+
if (options?.verbose) {
|
|
116
|
+
console.error(
|
|
117
|
+
`Generated docs: ${result.stats.resources} resources, ${result.stats.properties} properties, ${result.stats.services} services, ${result.stats.rules} rules`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for render 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 render 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-render.json with metadata for the
|
|
3
|
+
* render resource and property types.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PropertyConstraints } from "@intentius/chant/codegen/json-schema";
|
|
7
|
+
import type { RenderParseResult } from "../spec/parse";
|
|
8
|
+
import { renderShortName } 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 RenderLexiconEntry {
|
|
13
|
+
resourceType: string;
|
|
14
|
+
kind: "resource" | "property";
|
|
15
|
+
lexicon: "render";
|
|
16
|
+
constraints?: Record<string, PropertyConstraints>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate the lexicon-render.json content.
|
|
21
|
+
*/
|
|
22
|
+
export function generateLexiconJSON(results: RenderParseResult[], 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<RenderLexiconEntry>(registryResources, naming, {
|
|
31
|
+
shortName: renderShortName,
|
|
32
|
+
buildEntry: (resource, _tsName, _attrs, propConstraints) => {
|
|
33
|
+
const r = results.find((res) => res.resource.typeName === resource.typeName);
|
|
34
|
+
const entry: RenderLexiconEntry = {
|
|
35
|
+
resourceType: resource.typeName,
|
|
36
|
+
kind: r?.isProperty ? "property" : "resource",
|
|
37
|
+
lexicon: "render",
|
|
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: "render",
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return serializeRegistry(entries);
|
|
52
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript declaration file generator — produces index.d.ts with the render
|
|
3
|
+
* resource classes (WebService, Postgres, EnvGroup, ...) and their
|
|
4
|
+
* property-type classes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RenderParseResult } from "../spec/parse";
|
|
8
|
+
import type { NamingStrategy } from "./naming";
|
|
9
|
+
import {
|
|
10
|
+
writeResourceClass,
|
|
11
|
+
writePropertyClass,
|
|
12
|
+
type DtsProperty,
|
|
13
|
+
type DtsAttribute,
|
|
14
|
+
} from "@intentius/chant/codegen/generate-typescript";
|
|
15
|
+
|
|
16
|
+
interface Entry {
|
|
17
|
+
tsName: string;
|
|
18
|
+
isProperty: boolean;
|
|
19
|
+
properties: DtsProperty[];
|
|
20
|
+
attributes: DtsAttribute[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate the complete .d.ts file content.
|
|
25
|
+
*/
|
|
26
|
+
export function generateTypeScriptDeclarations(results: RenderParseResult[], naming: NamingStrategy): string {
|
|
27
|
+
const lines: string[] = [];
|
|
28
|
+
lines.push("// Code generated by chant render generate. DO NOT EDIT.");
|
|
29
|
+
|
|
30
|
+
const entries: Entry[] = [];
|
|
31
|
+
|
|
32
|
+
for (const r of results) {
|
|
33
|
+
const tsName = naming.resolve(r.resource.typeName);
|
|
34
|
+
if (!tsName) continue;
|
|
35
|
+
|
|
36
|
+
const properties: DtsProperty[] = r.resource.properties.map((p) => ({
|
|
37
|
+
name: p.name,
|
|
38
|
+
type: p.tsType,
|
|
39
|
+
required: p.required,
|
|
40
|
+
description: p.description,
|
|
41
|
+
}));
|
|
42
|
+
const attributes: DtsAttribute[] = r.resource.attributes.map((a) => ({ name: a.name, type: a.tsType }));
|
|
43
|
+
|
|
44
|
+
entries.push({ tsName, isProperty: !!r.isProperty, properties, attributes });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
entries.sort((a, b) => a.tsName.localeCompare(b.tsName));
|
|
48
|
+
|
|
49
|
+
const resources = entries.filter((e) => !e.isProperty);
|
|
50
|
+
const propertyTypes = entries.filter((e) => e.isProperty);
|
|
51
|
+
|
|
52
|
+
if (resources.length > 0) {
|
|
53
|
+
lines.push("");
|
|
54
|
+
lines.push("// --- Render Resource classes ---");
|
|
55
|
+
for (const re of resources) {
|
|
56
|
+
writeResourceClass(lines, re.tsName, re.properties, re.attributes);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (propertyTypes.length > 0) {
|
|
61
|
+
lines.push("");
|
|
62
|
+
lines.push("// --- Property type classes ---");
|
|
63
|
+
for (const pe of propertyTypes) {
|
|
64
|
+
writePropertyClass(lines, pe.tsName, pe.properties);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
lines.push("");
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render generation pipeline — uses core generatePipeline with render-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 { parseRenderOpenAPI, type RenderParseResult } 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 render generation pipeline.
|
|
28
|
+
*/
|
|
29
|
+
export async function generate(opts: GenerateOptions = {}): Promise<GenerateResult> {
|
|
30
|
+
// The Render spec is a single document — parseRenderOpenAPI 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: RenderParseResult[] = [];
|
|
34
|
+
|
|
35
|
+
const config: GeneratePipelineConfig<RenderParseResult> = {
|
|
36
|
+
fetchSchemas: (fetchOpts) => fetchSchemas({ force: fetchOpts.force }),
|
|
37
|
+
|
|
38
|
+
parseSchema: (_typeName, data) => {
|
|
39
|
+
const results = parseRenderOpenAPI(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 render schemas from OpenAPI spec`);
|
|
51
|
+
pendingResults = [];
|
|
52
|
+
}
|
|
53
|
+
log(`Total: ${results.length} render 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-render.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: RenderParseResult[], 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: "render" });
|
|
106
|
+
}
|