@intentius/chant-lexicon-github 0.0.18

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 (106) hide show
  1. package/dist/integrity.json +31 -0
  2. package/dist/manifest.json +15 -0
  3. package/dist/meta.json +135 -0
  4. package/dist/rules/deprecated-action-version.ts +49 -0
  5. package/dist/rules/detect-secrets.ts +53 -0
  6. package/dist/rules/extract-inline-structs.ts +62 -0
  7. package/dist/rules/file-job-limit.ts +49 -0
  8. package/dist/rules/gha006.ts +58 -0
  9. package/dist/rules/gha009.ts +42 -0
  10. package/dist/rules/gha011.ts +40 -0
  11. package/dist/rules/gha017.ts +32 -0
  12. package/dist/rules/gha018.ts +40 -0
  13. package/dist/rules/gha019.ts +72 -0
  14. package/dist/rules/job-timeout.ts +59 -0
  15. package/dist/rules/missing-recommended-inputs.ts +61 -0
  16. package/dist/rules/no-hardcoded-secrets.ts +46 -0
  17. package/dist/rules/no-raw-expressions.ts +51 -0
  18. package/dist/rules/suggest-cache.ts +71 -0
  19. package/dist/rules/use-condition-builders.ts +45 -0
  20. package/dist/rules/use-matrix-builder.ts +44 -0
  21. package/dist/rules/use-typed-actions.ts +47 -0
  22. package/dist/rules/validate-concurrency.ts +66 -0
  23. package/dist/rules/yaml-helpers.ts +129 -0
  24. package/dist/skills/chant-github.md +29 -0
  25. package/dist/skills/github-actions-patterns.md +93 -0
  26. package/dist/types/index.d.ts +358 -0
  27. package/package.json +33 -0
  28. package/src/codegen/docs-cli.ts +3 -0
  29. package/src/codegen/docs.ts +1138 -0
  30. package/src/codegen/generate-cli.ts +36 -0
  31. package/src/codegen/generate-lexicon.ts +58 -0
  32. package/src/codegen/generate-typescript.ts +149 -0
  33. package/src/codegen/generate.ts +141 -0
  34. package/src/codegen/naming.ts +57 -0
  35. package/src/codegen/package.ts +65 -0
  36. package/src/codegen/parse.ts +700 -0
  37. package/src/codegen/patches.ts +46 -0
  38. package/src/composites/cache.ts +25 -0
  39. package/src/composites/checkout.ts +31 -0
  40. package/src/composites/composites.test.ts +675 -0
  41. package/src/composites/deploy-environment.ts +77 -0
  42. package/src/composites/docker-build.ts +120 -0
  43. package/src/composites/download-artifact.ts +24 -0
  44. package/src/composites/go-ci.ts +91 -0
  45. package/src/composites/index.ts +26 -0
  46. package/src/composites/node-ci.ts +71 -0
  47. package/src/composites/node-pipeline.ts +151 -0
  48. package/src/composites/python-ci.ts +92 -0
  49. package/src/composites/setup-go.ts +24 -0
  50. package/src/composites/setup-node.ts +26 -0
  51. package/src/composites/setup-python.ts +24 -0
  52. package/src/composites/upload-artifact.ts +27 -0
  53. package/src/coverage.ts +49 -0
  54. package/src/expression.test.ts +147 -0
  55. package/src/expression.ts +214 -0
  56. package/src/generated/index.d.ts +358 -0
  57. package/src/generated/index.ts +29 -0
  58. package/src/generated/lexicon-github.json +135 -0
  59. package/src/generated/runtime.ts +4 -0
  60. package/src/import/generator.test.ts +110 -0
  61. package/src/import/generator.ts +119 -0
  62. package/src/import/parser.test.ts +98 -0
  63. package/src/import/parser.ts +73 -0
  64. package/src/index.ts +53 -0
  65. package/src/lint/post-synth/gha006.ts +58 -0
  66. package/src/lint/post-synth/gha009.ts +42 -0
  67. package/src/lint/post-synth/gha011.ts +40 -0
  68. package/src/lint/post-synth/gha017.ts +32 -0
  69. package/src/lint/post-synth/gha018.ts +40 -0
  70. package/src/lint/post-synth/gha019.ts +72 -0
  71. package/src/lint/post-synth/post-synth.test.ts +318 -0
  72. package/src/lint/post-synth/yaml-helpers.ts +129 -0
  73. package/src/lint/rules/data/deprecated-versions.ts +13 -0
  74. package/src/lint/rules/data/known-actions.ts +13 -0
  75. package/src/lint/rules/data/recommended-inputs.ts +10 -0
  76. package/src/lint/rules/data/secret-patterns.ts +31 -0
  77. package/src/lint/rules/deprecated-action-version.ts +49 -0
  78. package/src/lint/rules/detect-secrets.ts +53 -0
  79. package/src/lint/rules/extract-inline-structs.ts +62 -0
  80. package/src/lint/rules/file-job-limit.ts +49 -0
  81. package/src/lint/rules/index.ts +17 -0
  82. package/src/lint/rules/job-timeout.ts +59 -0
  83. package/src/lint/rules/missing-recommended-inputs.ts +61 -0
  84. package/src/lint/rules/no-hardcoded-secrets.ts +46 -0
  85. package/src/lint/rules/no-raw-expressions.ts +51 -0
  86. package/src/lint/rules/rules.test.ts +365 -0
  87. package/src/lint/rules/suggest-cache.ts +71 -0
  88. package/src/lint/rules/use-condition-builders.ts +45 -0
  89. package/src/lint/rules/use-matrix-builder.ts +44 -0
  90. package/src/lint/rules/use-typed-actions.ts +47 -0
  91. package/src/lint/rules/validate-concurrency.ts +66 -0
  92. package/src/lsp/completions.test.ts +9 -0
  93. package/src/lsp/completions.ts +20 -0
  94. package/src/lsp/hover.test.ts +9 -0
  95. package/src/lsp/hover.ts +38 -0
  96. package/src/package-cli.ts +42 -0
  97. package/src/plugin.test.ts +128 -0
  98. package/src/plugin.ts +408 -0
  99. package/src/serializer.test.ts +270 -0
  100. package/src/serializer.ts +383 -0
  101. package/src/skills/github-actions-patterns.md +93 -0
  102. package/src/spec/fetch.ts +55 -0
  103. package/src/validate-cli.ts +19 -0
  104. package/src/validate.test.ts +12 -0
  105. package/src/validate.ts +32 -0
  106. package/src/variables.ts +44 -0
@@ -0,0 +1,700 @@
1
+ /**
2
+ * GitHub Actions Workflow JSON Schema parser.
3
+ *
4
+ * Parses the single workflow schema into multiple entity results — one
5
+ * per resource/property entity (Workflow, Job, Step, triggers, etc.).
6
+ */
7
+
8
+ import type { PropertyConstraints } from "@intentius/chant/codegen/json-schema";
9
+ import {
10
+ extractConstraints as coreExtractConstraints,
11
+ constraintsIsEmpty as coreConstraintsIsEmpty,
12
+ primaryType,
13
+ type JsonSchemaProperty,
14
+ } from "@intentius/chant/codegen/json-schema";
15
+
16
+ // ── Types ──────────────────────────────────────────────────────────
17
+
18
+ export type { PropertyConstraints };
19
+ export { coreConstraintsIsEmpty as constraintsIsEmpty };
20
+
21
+ export interface ParsedProperty {
22
+ name: string;
23
+ tsType: string;
24
+ required: boolean;
25
+ description?: string;
26
+ enum?: string[];
27
+ constraints: PropertyConstraints;
28
+ }
29
+
30
+ export interface ParsedPropertyType {
31
+ name: string;
32
+ defType: string;
33
+ properties: ParsedProperty[];
34
+ }
35
+
36
+ export interface ParsedEnum {
37
+ name: string;
38
+ values: string[];
39
+ }
40
+
41
+ export interface ParsedResource {
42
+ typeName: string;
43
+ description?: string;
44
+ properties: ParsedProperty[];
45
+ attributes: Array<{ name: string; tsType: string }>;
46
+ deprecatedProperties: string[];
47
+ }
48
+
49
+ export interface GitHubParseResult {
50
+ resource: ParsedResource;
51
+ propertyTypes: ParsedPropertyType[];
52
+ enums: ParsedEnum[];
53
+ isProperty?: boolean;
54
+ }
55
+
56
+ // ── Schema types ──────────────────────────────────────────────────
57
+
58
+ interface SchemaDefinition {
59
+ type?: string | string[];
60
+ description?: string;
61
+ properties?: Record<string, SchemaProperty>;
62
+ required?: string[];
63
+ enum?: string[];
64
+ oneOf?: SchemaProperty[];
65
+ anyOf?: SchemaProperty[];
66
+ $ref?: string;
67
+ items?: SchemaProperty;
68
+ const?: unknown;
69
+ default?: unknown;
70
+ additionalProperties?: boolean | SchemaProperty;
71
+ patternProperties?: Record<string, SchemaProperty>;
72
+ minimum?: number;
73
+ maximum?: number;
74
+ minLength?: number;
75
+ maxLength?: number;
76
+ pattern?: string;
77
+ format?: string;
78
+ }
79
+
80
+ interface SchemaProperty extends SchemaDefinition {}
81
+
82
+ interface WorkflowSchema {
83
+ definitions?: Record<string, SchemaDefinition>;
84
+ properties?: Record<string, SchemaProperty>;
85
+ patternProperties?: Record<string, SchemaProperty>;
86
+ additionalProperties?: boolean | SchemaProperty;
87
+ required?: string[];
88
+ [key: string]: unknown;
89
+ }
90
+
91
+ // ── Entity extraction mapping ──────────────────────────────────────
92
+
93
+ const RESOURCE_ENTITIES: Array<{
94
+ typeName: string;
95
+ source: string;
96
+ description?: string;
97
+ }> = [
98
+ {
99
+ typeName: "GitHub::Actions::Workflow",
100
+ source: "root",
101
+ description: "A GitHub Actions workflow definition",
102
+ },
103
+ {
104
+ typeName: "GitHub::Actions::Job",
105
+ source: "#/definitions/normalJob",
106
+ description: "A standard CI job",
107
+ },
108
+ {
109
+ typeName: "GitHub::Actions::ReusableWorkflowCallJob",
110
+ source: "#/definitions/reusableWorkflowCallJob",
111
+ description: "A reusable workflow call job (uses: workflow reference)",
112
+ },
113
+ ];
114
+
115
+ const PROPERTY_ENTITIES: Array<{
116
+ typeName: string;
117
+ source: string;
118
+ description?: string;
119
+ }> = [
120
+ { typeName: "GitHub::Actions::Step", source: "normalJob:steps:item", description: "A workflow step" },
121
+ { typeName: "GitHub::Actions::Strategy", source: "normalJob:strategy", description: "Job strategy configuration" },
122
+ { typeName: "GitHub::Actions::Permissions", source: "#/definitions/permissions", description: "Workflow or job permissions" },
123
+ { typeName: "GitHub::Actions::Concurrency", source: "#/definitions/concurrency", description: "Concurrency control" },
124
+ { typeName: "GitHub::Actions::Container", source: "#/definitions/container", description: "Container configuration for a job" },
125
+ { typeName: "GitHub::Actions::Service", source: "service", description: "Service container configuration" },
126
+ { typeName: "GitHub::Actions::Environment", source: "#/definitions/environment", description: "Deployment environment" },
127
+ { typeName: "GitHub::Actions::Defaults", source: "#/definitions/defaults", description: "Default settings for all jobs" },
128
+ { typeName: "GitHub::Actions::PushTrigger", source: "event:push", description: "Push event trigger" },
129
+ { typeName: "GitHub::Actions::PullRequestTrigger", source: "event:pull_request", description: "Pull request event trigger" },
130
+ { typeName: "GitHub::Actions::PullRequestTargetTrigger", source: "event:pull_request_target", description: "Pull request target event trigger" },
131
+ { typeName: "GitHub::Actions::ScheduleTrigger", source: "event:schedule", description: "Schedule event trigger" },
132
+ { typeName: "GitHub::Actions::WorkflowDispatchTrigger", source: "event:workflow_dispatch", description: "Workflow dispatch event trigger" },
133
+ { typeName: "GitHub::Actions::WorkflowCallTrigger", source: "event:workflow_call", description: "Workflow call event trigger" },
134
+ { typeName: "GitHub::Actions::WorkflowRunTrigger", source: "event:workflow_run", description: "Workflow run event trigger" },
135
+ { typeName: "GitHub::Actions::RepositoryDispatchTrigger", source: "event:repository_dispatch", description: "Repository dispatch event trigger" },
136
+ { typeName: "GitHub::Actions::WorkflowInput", source: "workflow_call:input", description: "Reusable workflow input parameter" },
137
+ { typeName: "GitHub::Actions::WorkflowOutput", source: "workflow_call:output", description: "Reusable workflow output" },
138
+ { typeName: "GitHub::Actions::WorkflowSecret", source: "workflow_call:secret", description: "Reusable workflow secret" },
139
+ ];
140
+
141
+ // ── Parser ─────────────────────────────────────────────────────────
142
+
143
+ /**
144
+ * Parse the GitHub Actions Workflow JSON Schema into multiple entity results.
145
+ */
146
+ export function parseWorkflowSchema(data: string | Buffer): GitHubParseResult[] {
147
+ const schema: WorkflowSchema = JSON.parse(typeof data === "string" ? data : data.toString("utf-8"));
148
+ const results: GitHubParseResult[] = [];
149
+
150
+ for (const entity of RESOURCE_ENTITIES) {
151
+ const result = extractEntity(schema, entity, false);
152
+ if (result) results.push(result);
153
+ }
154
+
155
+ for (const entity of PROPERTY_ENTITIES) {
156
+ const result = extractEntity(schema, entity, true);
157
+ if (result) {
158
+ result.isProperty = true;
159
+ results.push(result);
160
+ }
161
+ }
162
+
163
+ return results;
164
+ }
165
+
166
+ function extractEntity(
167
+ schema: WorkflowSchema,
168
+ entity: { typeName: string; source: string; description?: string },
169
+ isProperty: boolean,
170
+ ): GitHubParseResult | null {
171
+ const def = resolveSource(schema, entity.source);
172
+ if (!def) {
173
+ // Create minimal entry for entities we know exist but can't resolve from schema
174
+ return {
175
+ resource: {
176
+ typeName: entity.typeName,
177
+ description: entity.description,
178
+ properties: buildFallbackProperties(entity.typeName),
179
+ attributes: [],
180
+ deprecatedProperties: [],
181
+ },
182
+ propertyTypes: [],
183
+ enums: [],
184
+ };
185
+ }
186
+
187
+ const objectDef = findObjectVariant(def, schema);
188
+ const properties = objectDef?.properties
189
+ ? parseProperties(objectDef.properties, new Set(objectDef.required ?? []), schema)
190
+ : buildFallbackProperties(entity.typeName);
191
+
192
+ const overrides = PROPERTY_OVERRIDES[entity.typeName];
193
+ if (overrides) {
194
+ for (const prop of properties) {
195
+ if (overrides[prop.name]) {
196
+ prop.tsType = overrides[prop.name];
197
+ }
198
+ }
199
+ }
200
+
201
+ return {
202
+ resource: {
203
+ typeName: entity.typeName,
204
+ description: entity.description ?? objectDef?.description ?? def.description,
205
+ properties,
206
+ attributes: [],
207
+ deprecatedProperties: mineDeprecatedProperties(properties),
208
+ },
209
+ propertyTypes: [],
210
+ enums: [],
211
+ };
212
+ }
213
+
214
+ // ── Source resolution ──────────────────────────────────────────────
215
+
216
+ function resolveSource(schema: WorkflowSchema, source: string): SchemaDefinition | null {
217
+ if (source === "root") {
218
+ return { properties: schema.properties, required: schema.required };
219
+ }
220
+
221
+ if (source.startsWith("#/definitions/")) {
222
+ const defName = source.slice("#/definitions/".length);
223
+ return schema.definitions?.[defName] ?? null;
224
+ }
225
+
226
+ // normalJob:property — resolve from normalJob definition
227
+ if (source.startsWith("normalJob:")) {
228
+ const segments = source.slice("normalJob:".length).split(":");
229
+ let current: SchemaDefinition | null = schema.definitions?.normalJob ?? null;
230
+ for (const seg of segments) {
231
+ if (!current) return null;
232
+ if (seg === "item") {
233
+ if (!current.items) return null;
234
+ return findObjectVariant(current.items, schema);
235
+ }
236
+ if (!current.properties?.[seg]) return null;
237
+ const prop = current.properties[seg];
238
+ current = prop.$ref ? resolveRef(prop.$ref, schema) : prop;
239
+ }
240
+ return current;
241
+ }
242
+
243
+ // event:name — resolve trigger event type
244
+ if (source.startsWith("event:")) {
245
+ const eventName = source.slice("event:".length);
246
+ return resolveEventTrigger(schema, eventName);
247
+ }
248
+
249
+ // workflow_call:input/output/secret
250
+ if (source.startsWith("workflow_call:")) {
251
+ const part = source.slice("workflow_call:".length);
252
+ return resolveWorkflowCallPart(schema, part);
253
+ }
254
+
255
+ // service — from container definitions
256
+ if (source === "service") {
257
+ // Services are containers with additional properties
258
+ const containerDef = schema.definitions?.container;
259
+ return containerDef ?? null;
260
+ }
261
+
262
+ return null;
263
+ }
264
+
265
+ function resolveEventTrigger(schema: WorkflowSchema, eventName: string): SchemaDefinition | null {
266
+ // Try to find in definitions like eventObject, or under "on" properties
267
+ const defName = `eventObject`;
268
+ const eventDef = schema.definitions?.[defName];
269
+ if (eventDef?.properties?.[eventName]) {
270
+ const prop = eventDef.properties[eventName];
271
+ if (prop.$ref) return resolveRef(prop.$ref, schema);
272
+ return prop;
273
+ }
274
+
275
+ // Try direct definition names
276
+ const directNames = [
277
+ eventName,
278
+ `${eventName}Event`,
279
+ ];
280
+ for (const name of directNames) {
281
+ if (schema.definitions?.[name]) {
282
+ return schema.definitions[name];
283
+ }
284
+ }
285
+
286
+ return null;
287
+ }
288
+
289
+ function resolveWorkflowCallPart(schema: WorkflowSchema, part: string): SchemaDefinition | null {
290
+ // Look in workflow_call trigger definition
291
+ const wcDef = schema.definitions?.["eventObject"]?.properties?.workflow_call;
292
+ if (!wcDef) return null;
293
+ const resolved = wcDef.$ref ? resolveRef(wcDef.$ref, schema) : wcDef;
294
+ if (!resolved) return null;
295
+
296
+ const objectDef = findObjectVariant(resolved, schema);
297
+ if (!objectDef?.properties) return null;
298
+
299
+ const mapping: Record<string, string> = {
300
+ input: "inputs",
301
+ output: "outputs",
302
+ secret: "secrets",
303
+ };
304
+ const propName = mapping[part] ?? part;
305
+ const prop = objectDef.properties[propName];
306
+ if (!prop) return null;
307
+
308
+ // Get the "additionalProperties" or "patternProperties" to find the item schema
309
+ const resolvedProp = prop.$ref ? resolveRef(prop.$ref, schema) : prop;
310
+ if (!resolvedProp) return null;
311
+
312
+ if (resolvedProp.patternProperties) {
313
+ const first = Object.values(resolvedProp.patternProperties)[0];
314
+ if (first) return first;
315
+ }
316
+ if (typeof resolvedProp.additionalProperties === "object") {
317
+ return resolvedProp.additionalProperties;
318
+ }
319
+
320
+ return resolvedProp;
321
+ }
322
+
323
+ function resolveRef(ref: string, schema: WorkflowSchema): SchemaDefinition | null {
324
+ const prefix = "#/definitions/";
325
+ if (!ref.startsWith(prefix)) return null;
326
+ const defName = ref.slice(prefix.length);
327
+ return schema.definitions?.[defName] ?? null;
328
+ }
329
+
330
+ function findObjectVariant(def: SchemaDefinition, schema?: WorkflowSchema): SchemaDefinition | null {
331
+ if (def.properties) return def;
332
+
333
+ const variants = def.oneOf ?? def.anyOf;
334
+ if (!variants) return null;
335
+
336
+ const objectVariants: SchemaDefinition[] = [];
337
+ for (const v of variants) {
338
+ let resolved: SchemaDefinition = v;
339
+ if (v.$ref && schema) {
340
+ const r = resolveRef(v.$ref, schema);
341
+ if (r) resolved = r;
342
+ else continue;
343
+ } else if (v.$ref) {
344
+ continue;
345
+ }
346
+ if (resolved.properties) {
347
+ objectVariants.push(resolved);
348
+ }
349
+ }
350
+
351
+ if (objectVariants.length === 0) return null;
352
+ if (objectVariants.length === 1) return objectVariants[0];
353
+
354
+ // Merge: pick variant with most properties as base
355
+ let best = objectVariants[0];
356
+ let bestCount = Object.keys(best.properties!).length;
357
+ for (let i = 1; i < objectVariants.length; i++) {
358
+ const count = Object.keys(objectVariants[i].properties!).length;
359
+ if (count > bestCount) {
360
+ best = objectVariants[i];
361
+ bestCount = count;
362
+ }
363
+ }
364
+
365
+ const mergedProperties: Record<string, SchemaProperty> = { ...best.properties };
366
+ for (const variant of objectVariants) {
367
+ if (variant === best) continue;
368
+ for (const [propName, propDef] of Object.entries(variant.properties!)) {
369
+ if (!(propName in mergedProperties)) {
370
+ mergedProperties[propName] = propDef;
371
+ }
372
+ }
373
+ }
374
+
375
+ return { ...best, properties: mergedProperties };
376
+ }
377
+
378
+ // ── Property parsing ──────────────────────────────────────────────
379
+
380
+ function parseProperties(
381
+ properties: Record<string, SchemaProperty>,
382
+ requiredSet: Set<string>,
383
+ schema: WorkflowSchema,
384
+ ): ParsedProperty[] {
385
+ const result: ParsedProperty[] = [];
386
+ for (const [name, prop] of Object.entries(properties)) {
387
+ const tsType = resolvePropertyType(prop, schema);
388
+ result.push({
389
+ name,
390
+ tsType,
391
+ required: requiredSet.has(name),
392
+ description: prop.description,
393
+ enum: prop.enum,
394
+ constraints: coreExtractConstraints(prop as JsonSchemaProperty),
395
+ });
396
+ }
397
+ return result;
398
+ }
399
+
400
+ function resolvePropertyType(prop: SchemaProperty, schema: WorkflowSchema): string {
401
+ if (!prop) return "any";
402
+
403
+ if (prop.$ref) {
404
+ const def = resolveRef(prop.$ref, schema);
405
+ if (def) {
406
+ if (def.enum && def.enum.length > 0 && !def.properties) {
407
+ return def.enum.map((v) => JSON.stringify(v)).join(" | ");
408
+ }
409
+ if (def.type && !def.properties) {
410
+ return jsonTypeToTs(primaryType(def.type));
411
+ }
412
+ if (def.properties) return "Record<string, any>";
413
+ }
414
+ return "any";
415
+ }
416
+
417
+ if (prop.enum && prop.enum.length > 0) {
418
+ return prop.enum.map((v) => JSON.stringify(v)).join(" | ");
419
+ }
420
+
421
+ if (prop.oneOf || prop.anyOf) {
422
+ const variants = prop.oneOf ?? prop.anyOf ?? [];
423
+ const types = new Set<string>();
424
+ for (const v of variants) {
425
+ types.add(resolvePropertyType(v, schema));
426
+ }
427
+ const uniqueTypes = [...types].filter((t) => t !== "any");
428
+ if (uniqueTypes.length === 0) return "any";
429
+ if (uniqueTypes.length === 1) return uniqueTypes[0];
430
+ return uniqueTypes.join(" | ");
431
+ }
432
+
433
+ const pt = primaryType(prop.type);
434
+ switch (pt) {
435
+ case "string": return "string";
436
+ case "integer":
437
+ case "number": return "number";
438
+ case "boolean": return "boolean";
439
+ case "array":
440
+ if (prop.items) {
441
+ const itemType = resolvePropertyType(prop.items, schema);
442
+ if (itemType.includes(" | ")) return `(${itemType})[]`;
443
+ return `${itemType}[]`;
444
+ }
445
+ return "any[]";
446
+ case "object": return "Record<string, any>";
447
+ default: return "any";
448
+ }
449
+ }
450
+
451
+ function jsonTypeToTs(type: string): string {
452
+ switch (type) {
453
+ case "string": return "string";
454
+ case "integer":
455
+ case "number": return "number";
456
+ case "boolean": return "boolean";
457
+ case "array": return "any[]";
458
+ case "object": return "Record<string, any>";
459
+ default: return "any";
460
+ }
461
+ }
462
+
463
+ // ── Property overrides ────────────────────────────────────────────
464
+
465
+ const PROPERTY_OVERRIDES: Record<string, Record<string, string>> = {
466
+ "GitHub::Actions::Workflow": {
467
+ on: "Record<string, any>",
468
+ jobs: "Record<string, any>",
469
+ permissions: "Permissions | string",
470
+ concurrency: "Concurrency | string",
471
+ defaults: "Defaults",
472
+ env: "Record<string, string>",
473
+ },
474
+ "GitHub::Actions::Job": {
475
+ steps: "Step[]",
476
+ strategy: "Strategy",
477
+ permissions: "Permissions | string",
478
+ concurrency: "Concurrency | string",
479
+ container: "Container | string",
480
+ services: "Record<string, Service>",
481
+ environment: "Environment | string",
482
+ defaults: "Defaults",
483
+ env: "Record<string, string>",
484
+ needs: "string[]",
485
+ outputs: "Record<string, string>",
486
+ "runs-on": "string | string[]",
487
+ "timeout-minutes": "number",
488
+ "continue-on-error": "boolean",
489
+ "if": "string",
490
+ },
491
+ "GitHub::Actions::Step": {
492
+ env: "Record<string, string>",
493
+ with: "Record<string, string>",
494
+ "if": "string",
495
+ "timeout-minutes": "number",
496
+ "continue-on-error": "boolean",
497
+ },
498
+ "GitHub::Actions::Strategy": {
499
+ matrix: "Record<string, any>",
500
+ },
501
+ };
502
+
503
+ // ── Fallback properties ───────────────────────────────────────────
504
+
505
+ /**
506
+ * Provide fallback properties for entities that can't be resolved from the schema.
507
+ */
508
+ function buildFallbackProperties(typeName: string): ParsedProperty[] {
509
+ const fallbacks: Record<string, Array<{ name: string; tsType: string; required: boolean; description?: string }>> = {
510
+ "GitHub::Actions::Workflow": [
511
+ { name: "name", tsType: "string", required: false, description: "The name of the workflow" },
512
+ { name: "on", tsType: "Record<string, any>", required: true, description: "Event triggers" },
513
+ { name: "jobs", tsType: "Record<string, any>", required: true, description: "Jobs in the workflow" },
514
+ { name: "permissions", tsType: "Permissions | string", required: false, description: "Permissions for the workflow" },
515
+ { name: "env", tsType: "Record<string, string>", required: false, description: "Environment variables" },
516
+ { name: "concurrency", tsType: "Concurrency | string", required: false, description: "Concurrency settings" },
517
+ { name: "defaults", tsType: "Defaults", required: false, description: "Default settings" },
518
+ { name: "run-name", tsType: "string", required: false, description: "Dynamic name for workflow runs" },
519
+ ],
520
+ "GitHub::Actions::Job": [
521
+ { name: "name", tsType: "string", required: false, description: "Job display name" },
522
+ { name: "runs-on", tsType: "string | string[]", required: true, description: "Runner label(s)" },
523
+ { name: "steps", tsType: "Step[]", required: false, description: "Job steps" },
524
+ { name: "needs", tsType: "string[]", required: false, description: "Job dependencies" },
525
+ { name: "if", tsType: "string", required: false, description: "Conditional expression" },
526
+ { name: "permissions", tsType: "Permissions | string", required: false, description: "Permissions" },
527
+ { name: "environment", tsType: "Environment | string", required: false, description: "Deployment environment" },
528
+ { name: "concurrency", tsType: "Concurrency | string", required: false, description: "Concurrency settings" },
529
+ { name: "outputs", tsType: "Record<string, string>", required: false, description: "Job outputs" },
530
+ { name: "env", tsType: "Record<string, string>", required: false, description: "Environment variables" },
531
+ { name: "defaults", tsType: "Defaults", required: false, description: "Default settings" },
532
+ { name: "strategy", tsType: "Strategy", required: false, description: "Strategy (matrix, etc.)" },
533
+ { name: "container", tsType: "Container | string", required: false, description: "Container to run in" },
534
+ { name: "services", tsType: "Record<string, Service>", required: false, description: "Service containers" },
535
+ { name: "timeout-minutes", tsType: "number", required: false, description: "Timeout in minutes" },
536
+ { name: "continue-on-error", tsType: "boolean", required: false, description: "Continue on error" },
537
+ ],
538
+ "GitHub::Actions::ReusableWorkflowCallJob": [
539
+ { name: "uses", tsType: "string", required: true, description: "Reusable workflow reference" },
540
+ { name: "with", tsType: "Record<string, any>", required: false, description: "Inputs for the reusable workflow" },
541
+ { name: "secrets", tsType: "Record<string, any> | string", required: false, description: "Secrets to pass" },
542
+ { name: "needs", tsType: "string[]", required: false, description: "Job dependencies" },
543
+ { name: "if", tsType: "string", required: false, description: "Conditional expression" },
544
+ { name: "permissions", tsType: "Permissions | string", required: false, description: "Permissions" },
545
+ { name: "concurrency", tsType: "Concurrency | string", required: false, description: "Concurrency settings" },
546
+ ],
547
+ "GitHub::Actions::Step": [
548
+ { name: "name", tsType: "string", required: false, description: "Step display name" },
549
+ { name: "uses", tsType: "string", required: false, description: "Action reference" },
550
+ { name: "run", tsType: "string", required: false, description: "Shell command" },
551
+ { name: "with", tsType: "Record<string, string>", required: false, description: "Action inputs" },
552
+ { name: "env", tsType: "Record<string, string>", required: false, description: "Environment variables" },
553
+ { name: "if", tsType: "string", required: false, description: "Conditional expression" },
554
+ { name: "id", tsType: "string", required: false, description: "Step ID for output references" },
555
+ { name: "shell", tsType: "string", required: false, description: "Shell to use" },
556
+ { name: "working-directory", tsType: "string", required: false, description: "Working directory" },
557
+ { name: "timeout-minutes", tsType: "number", required: false, description: "Timeout in minutes" },
558
+ { name: "continue-on-error", tsType: "boolean", required: false, description: "Continue on error" },
559
+ ],
560
+ "GitHub::Actions::Strategy": [
561
+ { name: "matrix", tsType: "Record<string, any>", required: false, description: "Matrix configuration" },
562
+ { name: "fail-fast", tsType: "boolean", required: false, description: "Cancel all jobs if one fails" },
563
+ { name: "max-parallel", tsType: "number", required: false, description: "Max parallel jobs" },
564
+ ],
565
+ "GitHub::Actions::Permissions": [
566
+ { name: "actions", tsType: '"read" | "write" | "none"', required: false },
567
+ { name: "checks", tsType: '"read" | "write" | "none"', required: false },
568
+ { name: "contents", tsType: '"read" | "write" | "none"', required: false },
569
+ { name: "deployments", tsType: '"read" | "write" | "none"', required: false },
570
+ { name: "id-token", tsType: '"write" | "none"', required: false },
571
+ { name: "issues", tsType: '"read" | "write" | "none"', required: false },
572
+ { name: "packages", tsType: '"read" | "write" | "none"', required: false },
573
+ { name: "pages", tsType: '"read" | "write" | "none"', required: false },
574
+ { name: "pull-requests", tsType: '"read" | "write" | "none"', required: false },
575
+ { name: "security-events", tsType: '"read" | "write" | "none"', required: false },
576
+ { name: "statuses", tsType: '"read" | "write" | "none"', required: false },
577
+ ],
578
+ "GitHub::Actions::Concurrency": [
579
+ { name: "group", tsType: "string", required: true, description: "Concurrency group name" },
580
+ { name: "cancel-in-progress", tsType: "boolean", required: false, description: "Cancel in-progress runs" },
581
+ ],
582
+ "GitHub::Actions::Container": [
583
+ { name: "image", tsType: "string", required: true, description: "Docker image" },
584
+ { name: "credentials", tsType: "Record<string, string>", required: false, description: "Registry credentials" },
585
+ { name: "env", tsType: "Record<string, string>", required: false, description: "Environment variables" },
586
+ { name: "ports", tsType: "number[]", required: false, description: "Exposed ports" },
587
+ { name: "volumes", tsType: "string[]", required: false, description: "Volume mounts" },
588
+ { name: "options", tsType: "string", required: false, description: "Docker options" },
589
+ ],
590
+ "GitHub::Actions::Service": [
591
+ { name: "image", tsType: "string", required: true, description: "Docker image" },
592
+ { name: "credentials", tsType: "Record<string, string>", required: false },
593
+ { name: "env", tsType: "Record<string, string>", required: false },
594
+ { name: "ports", tsType: "number[]", required: false },
595
+ { name: "volumes", tsType: "string[]", required: false },
596
+ { name: "options", tsType: "string", required: false },
597
+ ],
598
+ "GitHub::Actions::Environment": [
599
+ { name: "name", tsType: "string", required: true, description: "Environment name" },
600
+ { name: "url", tsType: "string", required: false, description: "Environment URL" },
601
+ ],
602
+ "GitHub::Actions::Defaults": [
603
+ { name: "run", tsType: "Record<string, string>", required: false, description: "Default run settings" },
604
+ ],
605
+ "GitHub::Actions::PushTrigger": [
606
+ { name: "branches", tsType: "string[]", required: false },
607
+ { name: "branches-ignore", tsType: "string[]", required: false },
608
+ { name: "tags", tsType: "string[]", required: false },
609
+ { name: "tags-ignore", tsType: "string[]", required: false },
610
+ { name: "paths", tsType: "string[]", required: false },
611
+ { name: "paths-ignore", tsType: "string[]", required: false },
612
+ ],
613
+ "GitHub::Actions::PullRequestTrigger": [
614
+ { name: "branches", tsType: "string[]", required: false },
615
+ { name: "branches-ignore", tsType: "string[]", required: false },
616
+ { name: "paths", tsType: "string[]", required: false },
617
+ { name: "paths-ignore", tsType: "string[]", required: false },
618
+ { name: "types", tsType: "string[]", required: false },
619
+ ],
620
+ "GitHub::Actions::PullRequestTargetTrigger": [
621
+ { name: "branches", tsType: "string[]", required: false },
622
+ { name: "branches-ignore", tsType: "string[]", required: false },
623
+ { name: "paths", tsType: "string[]", required: false },
624
+ { name: "paths-ignore", tsType: "string[]", required: false },
625
+ { name: "types", tsType: "string[]", required: false },
626
+ ],
627
+ "GitHub::Actions::ScheduleTrigger": [
628
+ { name: "cron", tsType: "string", required: true, description: "POSIX cron expression" },
629
+ ],
630
+ "GitHub::Actions::WorkflowDispatchTrigger": [
631
+ { name: "inputs", tsType: "Record<string, any>", required: false },
632
+ ],
633
+ "GitHub::Actions::WorkflowCallTrigger": [
634
+ { name: "inputs", tsType: "Record<string, any>", required: false },
635
+ { name: "outputs", tsType: "Record<string, any>", required: false },
636
+ { name: "secrets", tsType: "Record<string, any>", required: false },
637
+ ],
638
+ "GitHub::Actions::WorkflowRunTrigger": [
639
+ { name: "workflows", tsType: "string[]", required: false },
640
+ { name: "types", tsType: "string[]", required: false },
641
+ { name: "branches", tsType: "string[]", required: false },
642
+ { name: "branches-ignore", tsType: "string[]", required: false },
643
+ ],
644
+ "GitHub::Actions::RepositoryDispatchTrigger": [
645
+ { name: "types", tsType: "string[]", required: false },
646
+ ],
647
+ "GitHub::Actions::WorkflowInput": [
648
+ { name: "description", tsType: "string", required: false },
649
+ { name: "required", tsType: "boolean", required: false },
650
+ { name: "default", tsType: "string", required: false },
651
+ { name: "type", tsType: '"string" | "boolean" | "number" | "choice" | "environment"', required: false },
652
+ { name: "options", tsType: "string[]", required: false },
653
+ ],
654
+ "GitHub::Actions::WorkflowOutput": [
655
+ { name: "description", tsType: "string", required: false },
656
+ { name: "value", tsType: "string", required: true },
657
+ ],
658
+ "GitHub::Actions::WorkflowSecret": [
659
+ { name: "description", tsType: "string", required: false },
660
+ { name: "required", tsType: "boolean", required: false },
661
+ ],
662
+ };
663
+
664
+ const props = fallbacks[typeName];
665
+ if (!props) return [];
666
+ return props.map((p) => ({
667
+ ...p,
668
+ constraints: {},
669
+ }));
670
+ }
671
+
672
+ // ── Helpers ───────────────────────────────────────────────────────
673
+
674
+ const DEPRECATION_RE = /\bDeprecated\b|\bdeprecated\b|\blegacy\b|no longer (available|recommended|used|supported)/i;
675
+
676
+ function mineDeprecatedProperties(properties: ParsedProperty[]): string[] {
677
+ const deprecated: string[] = [];
678
+ for (const prop of properties) {
679
+ if (prop.description && DEPRECATION_RE.test(prop.description)) {
680
+ deprecated.push(prop.name);
681
+ }
682
+ }
683
+ return deprecated;
684
+ }
685
+
686
+ /**
687
+ * Extract short name: "GitHub::Actions::Job" → "Job"
688
+ */
689
+ export function githubShortName(typeName: string): string {
690
+ const parts = typeName.split("::");
691
+ return parts[parts.length - 1];
692
+ }
693
+
694
+ /**
695
+ * Extract service name: "GitHub::Actions::Job" → "Actions"
696
+ */
697
+ export function githubServiceName(typeName: string): string {
698
+ const parts = typeName.split("::");
699
+ return parts.length >= 2 ? parts[1] : "Actions";
700
+ }