@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.
- package/dist/integrity.json +31 -0
- package/dist/manifest.json +15 -0
- package/dist/meta.json +135 -0
- package/dist/rules/deprecated-action-version.ts +49 -0
- package/dist/rules/detect-secrets.ts +53 -0
- package/dist/rules/extract-inline-structs.ts +62 -0
- package/dist/rules/file-job-limit.ts +49 -0
- package/dist/rules/gha006.ts +58 -0
- package/dist/rules/gha009.ts +42 -0
- package/dist/rules/gha011.ts +40 -0
- package/dist/rules/gha017.ts +32 -0
- package/dist/rules/gha018.ts +40 -0
- package/dist/rules/gha019.ts +72 -0
- package/dist/rules/job-timeout.ts +59 -0
- package/dist/rules/missing-recommended-inputs.ts +61 -0
- package/dist/rules/no-hardcoded-secrets.ts +46 -0
- package/dist/rules/no-raw-expressions.ts +51 -0
- package/dist/rules/suggest-cache.ts +71 -0
- package/dist/rules/use-condition-builders.ts +45 -0
- package/dist/rules/use-matrix-builder.ts +44 -0
- package/dist/rules/use-typed-actions.ts +47 -0
- package/dist/rules/validate-concurrency.ts +66 -0
- package/dist/rules/yaml-helpers.ts +129 -0
- package/dist/skills/chant-github.md +29 -0
- package/dist/skills/github-actions-patterns.md +93 -0
- package/dist/types/index.d.ts +358 -0
- package/package.json +33 -0
- package/src/codegen/docs-cli.ts +3 -0
- package/src/codegen/docs.ts +1138 -0
- package/src/codegen/generate-cli.ts +36 -0
- package/src/codegen/generate-lexicon.ts +58 -0
- package/src/codegen/generate-typescript.ts +149 -0
- package/src/codegen/generate.ts +141 -0
- package/src/codegen/naming.ts +57 -0
- package/src/codegen/package.ts +65 -0
- package/src/codegen/parse.ts +700 -0
- package/src/codegen/patches.ts +46 -0
- package/src/composites/cache.ts +25 -0
- package/src/composites/checkout.ts +31 -0
- package/src/composites/composites.test.ts +675 -0
- package/src/composites/deploy-environment.ts +77 -0
- package/src/composites/docker-build.ts +120 -0
- package/src/composites/download-artifact.ts +24 -0
- package/src/composites/go-ci.ts +91 -0
- package/src/composites/index.ts +26 -0
- package/src/composites/node-ci.ts +71 -0
- package/src/composites/node-pipeline.ts +151 -0
- package/src/composites/python-ci.ts +92 -0
- package/src/composites/setup-go.ts +24 -0
- package/src/composites/setup-node.ts +26 -0
- package/src/composites/setup-python.ts +24 -0
- package/src/composites/upload-artifact.ts +27 -0
- package/src/coverage.ts +49 -0
- package/src/expression.test.ts +147 -0
- package/src/expression.ts +214 -0
- package/src/generated/index.d.ts +358 -0
- package/src/generated/index.ts +29 -0
- package/src/generated/lexicon-github.json +135 -0
- package/src/generated/runtime.ts +4 -0
- package/src/import/generator.test.ts +110 -0
- package/src/import/generator.ts +119 -0
- package/src/import/parser.test.ts +98 -0
- package/src/import/parser.ts +73 -0
- package/src/index.ts +53 -0
- package/src/lint/post-synth/gha006.ts +58 -0
- package/src/lint/post-synth/gha009.ts +42 -0
- package/src/lint/post-synth/gha011.ts +40 -0
- package/src/lint/post-synth/gha017.ts +32 -0
- package/src/lint/post-synth/gha018.ts +40 -0
- package/src/lint/post-synth/gha019.ts +72 -0
- package/src/lint/post-synth/post-synth.test.ts +318 -0
- package/src/lint/post-synth/yaml-helpers.ts +129 -0
- package/src/lint/rules/data/deprecated-versions.ts +13 -0
- package/src/lint/rules/data/known-actions.ts +13 -0
- package/src/lint/rules/data/recommended-inputs.ts +10 -0
- package/src/lint/rules/data/secret-patterns.ts +31 -0
- package/src/lint/rules/deprecated-action-version.ts +49 -0
- package/src/lint/rules/detect-secrets.ts +53 -0
- package/src/lint/rules/extract-inline-structs.ts +62 -0
- package/src/lint/rules/file-job-limit.ts +49 -0
- package/src/lint/rules/index.ts +17 -0
- package/src/lint/rules/job-timeout.ts +59 -0
- package/src/lint/rules/missing-recommended-inputs.ts +61 -0
- package/src/lint/rules/no-hardcoded-secrets.ts +46 -0
- package/src/lint/rules/no-raw-expressions.ts +51 -0
- package/src/lint/rules/rules.test.ts +365 -0
- package/src/lint/rules/suggest-cache.ts +71 -0
- package/src/lint/rules/use-condition-builders.ts +45 -0
- package/src/lint/rules/use-matrix-builder.ts +44 -0
- package/src/lint/rules/use-typed-actions.ts +47 -0
- package/src/lint/rules/validate-concurrency.ts +66 -0
- package/src/lsp/completions.test.ts +9 -0
- package/src/lsp/completions.ts +20 -0
- package/src/lsp/hover.test.ts +9 -0
- package/src/lsp/hover.ts +38 -0
- package/src/package-cli.ts +42 -0
- package/src/plugin.test.ts +128 -0
- package/src/plugin.ts +408 -0
- package/src/serializer.test.ts +270 -0
- package/src/serializer.ts +383 -0
- package/src/skills/github-actions-patterns.md +93 -0
- package/src/spec/fetch.ts +55 -0
- package/src/validate-cli.ts +19 -0
- package/src/validate.test.ts +12 -0
- package/src/validate.ts +32 -0
- 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
|
+
}
|