@intentius/chant-lexicon-github 0.1.0 → 0.1.5
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 +21 -21
- package/dist/manifest.json +1 -1
- package/dist/meta.json +6 -1
- package/dist/types/index.d.ts +2 -0
- package/package.json +2 -2
- package/src/generated/index.d.ts +2 -0
- package/src/generated/lexicon-github.json +6 -1
- package/src/serializer.test.ts +109 -1
- package/src/serializer.ts +92 -32
package/dist/integrity.json
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "xxhash64",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
5
|
-
"meta.json": "
|
|
6
|
-
"types/index.d.ts": "
|
|
7
|
-
"rules/
|
|
8
|
-
"rules/no-raw-expressions.ts": "6359f4d3135ed351",
|
|
9
|
-
"rules/use-typed-actions.ts": "eeedcc6145b7a132",
|
|
4
|
+
"manifest.json": "62337ef86c01e6d1",
|
|
5
|
+
"meta.json": "2d1cccf3c19883a7",
|
|
6
|
+
"types/index.d.ts": "9d6f903cca5de1e9",
|
|
7
|
+
"rules/deprecated-action-version.ts": "9ec91b190557f25f",
|
|
10
8
|
"rules/extract-inline-structs.ts": "646dce2eccf1fab4",
|
|
9
|
+
"rules/job-timeout.ts": "68f85c741c3d0ae8",
|
|
10
|
+
"rules/validate-concurrency.ts": "c12a1aa4ee8badb5",
|
|
11
11
|
"rules/file-job-limit.ts": "7c46a302f6ba2744",
|
|
12
|
+
"rules/use-typed-actions.ts": "eeedcc6145b7a132",
|
|
13
|
+
"rules/suggest-cache.ts": "c45f7659afde2f15",
|
|
12
14
|
"rules/detect-secrets.ts": "999e6c5b4e048764",
|
|
13
|
-
"rules/deprecated-action-version.ts": "9ec91b190557f25f",
|
|
14
15
|
"rules/no-hardcoded-secrets.ts": "adcdb23f0480a4b7",
|
|
15
|
-
"rules/job-timeout.ts": "68f85c741c3d0ae8",
|
|
16
|
-
"rules/use-condition-builders.ts": "7406215df1f79fb8",
|
|
17
|
-
"rules/suggest-cache.ts": "c45f7659afde2f15",
|
|
18
|
-
"rules/validate-concurrency.ts": "c12a1aa4ee8badb5",
|
|
19
16
|
"rules/use-matrix-builder.ts": "6b1c0ebf43378805",
|
|
20
|
-
"rules/
|
|
17
|
+
"rules/missing-recommended-inputs.ts": "42f2f3b0b6c7b52c",
|
|
18
|
+
"rules/use-condition-builders.ts": "7406215df1f79fb8",
|
|
19
|
+
"rules/no-raw-expressions.ts": "6359f4d3135ed351",
|
|
21
20
|
"rules/gha017.ts": "ff1c08fdedf83afa",
|
|
22
|
-
"rules/
|
|
21
|
+
"rules/gha018.ts": "46acbe27d4c0c817",
|
|
22
|
+
"rules/gha026.ts": "5ace32df6cb850af",
|
|
23
23
|
"rules/gha019.ts": "d9184093f36ac167",
|
|
24
|
+
"rules/gha023.ts": "2d00140d63591c9",
|
|
25
|
+
"rules/gha027.ts": "6071aedb178c90a8",
|
|
26
|
+
"rules/yaml-helpers.ts": "df426df288c175c9",
|
|
24
27
|
"rules/gha024.ts": "ed75a2900c8bf12d",
|
|
25
|
-
"rules/
|
|
26
|
-
"rules/
|
|
28
|
+
"rules/gha025.ts": "d196899f490521ba",
|
|
29
|
+
"rules/gha006.ts": "baca27402ba18d",
|
|
27
30
|
"rules/gha028.ts": "9c1ba1eb9a93d8b6",
|
|
28
31
|
"rules/gha022.ts": "41038ee697a497d1",
|
|
29
|
-
"rules/gha018.ts": "46acbe27d4c0c817",
|
|
30
|
-
"rules/yaml-helpers.ts": "df426df288c175c9",
|
|
31
32
|
"rules/gha011.ts": "105e2d4faeaa9977",
|
|
32
|
-
"rules/
|
|
33
|
-
"rules/
|
|
34
|
-
"rules/gha023.ts": "2d00140d63591c9",
|
|
33
|
+
"rules/gha020.ts": "36ef5e141524bab0",
|
|
34
|
+
"rules/gha009.ts": "df140c0cac573bc4",
|
|
35
35
|
"rules/gha021.ts": "da7e2491926d1817",
|
|
36
36
|
"skills/chant-github.md": "dc14037edaace2af",
|
|
37
37
|
"skills/chant-github-patterns.md": "7678ef5c6b4b9bdf",
|
|
38
38
|
"skills/chant-github-security.md": "f3fcfcd84475b73c"
|
|
39
39
|
},
|
|
40
|
-
"composite": "
|
|
40
|
+
"composite": "92e041a88a3b5d8d"
|
|
41
41
|
}
|
package/dist/manifest.json
CHANGED
package/dist/meta.json
CHANGED
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
"Environment": {
|
|
18
18
|
"resourceType": "GitHub::Actions::Environment",
|
|
19
19
|
"kind": "property",
|
|
20
|
-
"lexicon": "github"
|
|
20
|
+
"lexicon": "github",
|
|
21
|
+
"constraints": {
|
|
22
|
+
"deployment": {
|
|
23
|
+
"default": true
|
|
24
|
+
}
|
|
25
|
+
}
|
|
21
26
|
},
|
|
22
27
|
"Job": {
|
|
23
28
|
"resourceType": "GitHub::Actions::Job",
|
package/dist/types/index.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export declare class Environment {
|
|
|
40
40
|
constructor(props: {
|
|
41
41
|
/** The name of the environment configured in the repo. */
|
|
42
42
|
name: string;
|
|
43
|
+
/** Whether to create a deployment for this job. Setting to false lets the job use environment secrets and variables without creating a deployment record. Wait timers and required reviewers still apply. */
|
|
44
|
+
deployment?: boolean | string;
|
|
43
45
|
/** A deployment URL */
|
|
44
46
|
url?: string;
|
|
45
47
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-github",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "GitHub Actions lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"prepack": "bun run generate && bun run bundle && bun run validate"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@intentius/chant": "0.1.
|
|
46
|
+
"@intentius/chant": "0.1.4",
|
|
47
47
|
"typescript": "^5.9.3"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
package/src/generated/index.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export declare class Environment {
|
|
|
40
40
|
constructor(props: {
|
|
41
41
|
/** The name of the environment configured in the repo. */
|
|
42
42
|
name: string;
|
|
43
|
+
/** Whether to create a deployment for this job. Setting to false lets the job use environment secrets and variables without creating a deployment record. Wait timers and required reviewers still apply. */
|
|
44
|
+
deployment?: boolean | string;
|
|
43
45
|
/** A deployment URL */
|
|
44
46
|
url?: string;
|
|
45
47
|
});
|
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
"Environment": {
|
|
18
18
|
"resourceType": "GitHub::Actions::Environment",
|
|
19
19
|
"kind": "property",
|
|
20
|
-
"lexicon": "github"
|
|
20
|
+
"lexicon": "github",
|
|
21
|
+
"constraints": {
|
|
22
|
+
"deployment": {
|
|
23
|
+
"default": true
|
|
24
|
+
}
|
|
25
|
+
}
|
|
21
26
|
},
|
|
22
27
|
"Job": {
|
|
23
28
|
"resourceType": "GitHub::Actions::Job",
|
package/src/serializer.test.ts
CHANGED
|
@@ -197,16 +197,122 @@ describe("githubSerializer.serialize", () => {
|
|
|
197
197
|
});
|
|
198
198
|
});
|
|
199
199
|
|
|
200
|
+
describe("Workflow.props.jobs", () => {
|
|
201
|
+
test("single-workflow: inline Job entity is serialized into the jobs section", () => {
|
|
202
|
+
const entities = new Map<string, Declarable>();
|
|
203
|
+
entities.set("workflow", new MockWorkflow({
|
|
204
|
+
name: "CI",
|
|
205
|
+
on: { push: { branches: ["main"] } },
|
|
206
|
+
jobs: {
|
|
207
|
+
build: new MockJob({ "runs-on": "ubuntu-latest" }),
|
|
208
|
+
},
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
const output = githubSerializer.serialize(entities) as string;
|
|
212
|
+
expect(output).toContain("jobs:");
|
|
213
|
+
expect(output).toContain("build:");
|
|
214
|
+
expect(output).toContain("runs-on: ubuntu-latest");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("single-workflow: inline Job with steps serializes step content", () => {
|
|
218
|
+
const entities = new Map<string, Declarable>();
|
|
219
|
+
entities.set("workflow", new MockWorkflow({
|
|
220
|
+
name: "CI",
|
|
221
|
+
on: { push: null },
|
|
222
|
+
jobs: {
|
|
223
|
+
test: new MockJob({
|
|
224
|
+
"runs-on": "ubuntu-latest",
|
|
225
|
+
steps: [
|
|
226
|
+
new MockStep({ name: "Run tests", run: "npm test" }),
|
|
227
|
+
],
|
|
228
|
+
}),
|
|
229
|
+
},
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
const output = githubSerializer.serialize(entities) as string;
|
|
233
|
+
expect(output).toContain("test:");
|
|
234
|
+
expect(output).toContain("run: npm test");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("single-workflow: standalone Job export still works when Workflow.props.jobs is absent", () => {
|
|
238
|
+
const entities = new Map<string, Declarable>();
|
|
239
|
+
entities.set("workflow", new MockWorkflow({
|
|
240
|
+
name: "CI",
|
|
241
|
+
on: { push: null },
|
|
242
|
+
}));
|
|
243
|
+
entities.set("build", new MockJob({ "runs-on": "ubuntu-latest" }));
|
|
244
|
+
|
|
245
|
+
const output = githubSerializer.serialize(entities) as string;
|
|
246
|
+
expect(output).toContain("jobs:");
|
|
247
|
+
expect(output).toContain("build:");
|
|
248
|
+
expect(output).toContain("runs-on: ubuntu-latest");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("multi-workflow: each workflow gets its own inline jobs", () => {
|
|
252
|
+
const entities = new Map<string, Declarable>();
|
|
253
|
+
entities.set("ci", new MockWorkflow({
|
|
254
|
+
name: "CI",
|
|
255
|
+
on: { push: { branches: ["main"] } },
|
|
256
|
+
jobs: { build: new MockJob({ "runs-on": "ubuntu-latest", name: "Build" }) },
|
|
257
|
+
}));
|
|
258
|
+
entities.set("deploy", new MockWorkflow({
|
|
259
|
+
name: "Deploy",
|
|
260
|
+
on: { workflowDispatch: null },
|
|
261
|
+
jobs: { release: new MockJob({ "runs-on": "ubuntu-latest", name: "Release" }) },
|
|
262
|
+
}));
|
|
263
|
+
|
|
264
|
+
const result = githubSerializer.serialize(entities) as { primary: string; files: Record<string, string> };
|
|
265
|
+
expect(result.primary).toContain("name: CI");
|
|
266
|
+
expect(result.primary).toContain("build:");
|
|
267
|
+
expect(result.primary).not.toContain("release:");
|
|
268
|
+
expect(result.files["deploy.yml"]).toContain("name: Deploy");
|
|
269
|
+
expect(result.files["deploy.yml"]).toContain("release:");
|
|
270
|
+
expect(result.files["deploy.yml"]).not.toContain("build:");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("multi-workflow: workflow with no props.jobs gets no jobs section", () => {
|
|
274
|
+
const entities = new Map<string, Declarable>();
|
|
275
|
+
entities.set("ci", new MockWorkflow({
|
|
276
|
+
name: "CI",
|
|
277
|
+
on: { push: null },
|
|
278
|
+
jobs: { build: new MockJob({ "runs-on": "ubuntu-latest" }) },
|
|
279
|
+
}));
|
|
280
|
+
entities.set("notify", new MockWorkflow({
|
|
281
|
+
name: "Notify",
|
|
282
|
+
on: { workflowDispatch: null },
|
|
283
|
+
// no jobs
|
|
284
|
+
}));
|
|
285
|
+
|
|
286
|
+
const result = githubSerializer.serialize(entities) as { primary: string; files: Record<string, string> };
|
|
287
|
+
expect(result.primary).toContain("jobs:");
|
|
288
|
+
expect(result.files["notify.yml"]).not.toContain("jobs:");
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("multi-workflow: standalone Job exports fall back to first workflow (backwards compat for composites)", () => {
|
|
292
|
+
const entities = new Map<string, Declarable>();
|
|
293
|
+
entities.set("ci", new MockWorkflow({ name: "CI", on: { push: null } }));
|
|
294
|
+
entities.set("deploy", new MockWorkflow({ name: "Deploy", on: { workflowDispatch: null } }));
|
|
295
|
+
entities.set("build", new MockJob({ "runs-on": "ubuntu-latest" }));
|
|
296
|
+
|
|
297
|
+
const result = githubSerializer.serialize(entities) as { primary: string; files: Record<string, string> };
|
|
298
|
+
// standalone job goes to first workflow only
|
|
299
|
+
expect(result.primary).toContain("build:");
|
|
300
|
+
expect(result.files["deploy.yml"]).not.toContain("jobs:");
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
200
304
|
describe("multi-workflow output", () => {
|
|
201
|
-
test("produces SerializerResult with files", () => {
|
|
305
|
+
test("produces SerializerResult with files, each containing their own jobs", () => {
|
|
202
306
|
const entities = new Map<string, Declarable>();
|
|
203
307
|
entities.set("ci", new MockWorkflow({
|
|
204
308
|
name: "CI",
|
|
205
309
|
on: { push: { branches: ["main"] } },
|
|
310
|
+
jobs: { build: new MockJob({ "runs-on": "ubuntu-latest" }) },
|
|
206
311
|
}));
|
|
207
312
|
entities.set("deploy", new MockWorkflow({
|
|
208
313
|
name: "Deploy",
|
|
209
314
|
on: { push: { branches: ["main"] } },
|
|
315
|
+
jobs: { ship: new MockJob({ "runs-on": "ubuntu-latest" }) },
|
|
210
316
|
}));
|
|
211
317
|
|
|
212
318
|
const output = githubSerializer.serialize(entities);
|
|
@@ -215,6 +321,8 @@ describe("multi-workflow output", () => {
|
|
|
215
321
|
expect(result.files).toBeDefined();
|
|
216
322
|
expect(Object.keys(result.files).length).toBe(2);
|
|
217
323
|
expect(result.primary).toContain("name: CI");
|
|
324
|
+
expect(result.primary).toContain("build:");
|
|
325
|
+
expect(result.files["deploy.yml"]).toContain("ship:");
|
|
218
326
|
});
|
|
219
327
|
});
|
|
220
328
|
|
package/src/serializer.ts
CHANGED
|
@@ -109,6 +109,78 @@ function toYAMLValue(value: unknown, entityNames: Map<Declarable, string>): unkn
|
|
|
109
109
|
return walkValue(preprocessed, entityNames, githubVisitor(entityNames));
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
// ── Inline job serialization ──────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
const JOB_ENTITY_TYPES = new Set([
|
|
115
|
+
"GitHub::Actions::Job",
|
|
116
|
+
"GitHub::Actions::ReusableWorkflowCallJob",
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Serialize a value from Workflow.props.jobs into a YAML job object.
|
|
121
|
+
*
|
|
122
|
+
* When the value is a Job entity (resource Declarable), its props are inlined
|
|
123
|
+
* directly rather than emitted as a resource reference. This allows callers to
|
|
124
|
+
* pass `new Job({...})` directly inside `Workflow({ jobs: { name: new Job({...}) } })`.
|
|
125
|
+
*
|
|
126
|
+
* Plain objects are accepted too (JSON-style job definitions).
|
|
127
|
+
*/
|
|
128
|
+
function serializeInlineJob(
|
|
129
|
+
jobValue: unknown,
|
|
130
|
+
entityNames: Map<Declarable, string>,
|
|
131
|
+
): Record<string, unknown> | undefined {
|
|
132
|
+
if (!jobValue || typeof jobValue !== "object") return undefined;
|
|
133
|
+
const obj = jobValue as Record<string, unknown>;
|
|
134
|
+
|
|
135
|
+
if ("entityType" in obj && "props" in obj && JOB_ENTITY_TYPES.has(obj.entityType as string)) {
|
|
136
|
+
// Job entity: serialize its props inline (not as a resource reference)
|
|
137
|
+
const props = toYAMLValue(obj.props, entityNames);
|
|
138
|
+
return props && typeof props === "object"
|
|
139
|
+
? convertKeys(props as Record<string, unknown>)
|
|
140
|
+
: undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Plain object job definition (JSON-style)
|
|
144
|
+
return convertKeys(convertValueKeys(obj) as Record<string, unknown>);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Build a jobs section from Workflow.props.jobs entries.
|
|
149
|
+
* Returns undefined when props.jobs is absent or empty.
|
|
150
|
+
*/
|
|
151
|
+
function buildInlineJobsSection(
|
|
152
|
+
props: Record<string, unknown>,
|
|
153
|
+
entityNames: Map<Declarable, string>,
|
|
154
|
+
): Record<string, unknown> | undefined {
|
|
155
|
+
if (!props.jobs || typeof props.jobs !== "object" || Array.isArray(props.jobs)) return undefined;
|
|
156
|
+
const jobsSection: Record<string, unknown> = {};
|
|
157
|
+
for (const [jName, jobValue] of Object.entries(props.jobs as Record<string, unknown>)) {
|
|
158
|
+
const serialized = serializeInlineJob(jobValue, entityNames);
|
|
159
|
+
if (serialized) jobsSection[toKebabCase(jName)] = serialized;
|
|
160
|
+
}
|
|
161
|
+
return Object.keys(jobsSection).length > 0 ? jobsSection : undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Build a jobs section from standalone top-level Job entity exports.
|
|
166
|
+
* Returns undefined when there are no standalone jobs.
|
|
167
|
+
*/
|
|
168
|
+
function buildStandaloneJobsSection(
|
|
169
|
+
jobs: Array<[string, Declarable]>,
|
|
170
|
+
entityNames: Map<Declarable, string>,
|
|
171
|
+
): Record<string, unknown> | undefined {
|
|
172
|
+
if (jobs.length === 0) return undefined;
|
|
173
|
+
const jobsSection: Record<string, unknown> = {};
|
|
174
|
+
for (const [name, job] of jobs) {
|
|
175
|
+
const jProps = toYAMLValue(
|
|
176
|
+
(job as unknown as Record<string, unknown>).props,
|
|
177
|
+
entityNames,
|
|
178
|
+
) as Record<string, unknown> | undefined;
|
|
179
|
+
if (jProps) jobsSection[toKebabCase(name)] = convertKeys(jProps);
|
|
180
|
+
}
|
|
181
|
+
return Object.keys(jobsSection).length > 0 ? jobsSection : undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
112
184
|
// ── Key conversion for YAML output ────────────────────────────────
|
|
113
185
|
|
|
114
186
|
/**
|
|
@@ -166,7 +238,7 @@ export const githubSerializer: Serializer = {
|
|
|
166
238
|
for (const [name, entity] of entities) {
|
|
167
239
|
if (isPropertyDeclarable(entity)) continue;
|
|
168
240
|
|
|
169
|
-
const entityType = (entity as Record<string, unknown>).entityType as string;
|
|
241
|
+
const entityType = (entity as unknown as Record<string, unknown>).entityType as string;
|
|
170
242
|
if (entityType === "GitHub::Actions::Workflow") {
|
|
171
243
|
workflows.push([name, entity]);
|
|
172
244
|
} else if (entityType === "GitHub::Actions::Job" || entityType === "GitHub::Actions::ReusableWorkflowCallJob") {
|
|
@@ -200,7 +272,7 @@ function serializeSingleWorkflow(
|
|
|
200
272
|
// Workflow-level properties
|
|
201
273
|
if (workflows.length > 0) {
|
|
202
274
|
const [, wf] = workflows[0];
|
|
203
|
-
const props = toYAMLValue((wf as Record<string, unknown>).props, entityNames) as Record<string, unknown> | undefined;
|
|
275
|
+
const props = toYAMLValue((wf as unknown as Record<string, unknown>).props, entityNames) as Record<string, unknown> | undefined;
|
|
204
276
|
if (props) {
|
|
205
277
|
if (props.name) doc.name = props.name;
|
|
206
278
|
if (props["run-name"] || props.runName) doc["run-name"] = props["run-name"] ?? props.runName;
|
|
@@ -220,11 +292,11 @@ function serializeSingleWorkflow(
|
|
|
220
292
|
if (triggers.length > 0) {
|
|
221
293
|
const onSection = (doc.on as Record<string, unknown>) ?? {};
|
|
222
294
|
for (const [, trigger] of triggers) {
|
|
223
|
-
const entityType = (trigger as Record<string, unknown>).entityType as string;
|
|
295
|
+
const entityType = (trigger as unknown as Record<string, unknown>).entityType as string;
|
|
224
296
|
const eventName = TRIGGER_TYPE_TO_EVENT[entityType];
|
|
225
297
|
if (!eventName) continue;
|
|
226
298
|
|
|
227
|
-
const props = toYAMLValue((trigger as Record<string, unknown>).props, entityNames) as Record<string, unknown> | undefined;
|
|
299
|
+
const props = toYAMLValue((trigger as unknown as Record<string, unknown>).props, entityNames) as Record<string, unknown> | undefined;
|
|
228
300
|
if (props && Object.keys(props).length > 0) {
|
|
229
301
|
onSection[eventName] = convertValueKeys(props);
|
|
230
302
|
} else {
|
|
@@ -234,18 +306,14 @@ function serializeSingleWorkflow(
|
|
|
234
306
|
doc.on = onSection;
|
|
235
307
|
}
|
|
236
308
|
|
|
237
|
-
// Jobs
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (props) {
|
|
243
|
-
const yamlName = toKebabCase(name);
|
|
244
|
-
jobsSection[yamlName] = convertKeys(props);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
doc.jobs = jobsSection;
|
|
309
|
+
// Jobs: prefer Workflow.props.jobs (raw) when present; fall back to standalone Job exports
|
|
310
|
+
let jobsSection: Record<string, unknown> | undefined;
|
|
311
|
+
if (workflows.length > 0) {
|
|
312
|
+
const rawProps = (workflows[0][1] as unknown as Record<string, unknown>).props as Record<string, unknown>;
|
|
313
|
+
jobsSection = buildInlineJobsSection(rawProps, entityNames);
|
|
248
314
|
}
|
|
315
|
+
if (!jobsSection) jobsSection = buildStandaloneJobsSection(jobs, entityNames);
|
|
316
|
+
if (jobsSection) doc.jobs = jobsSection;
|
|
249
317
|
|
|
250
318
|
return emitYAMLDocument(doc);
|
|
251
319
|
}
|
|
@@ -257,16 +325,13 @@ function serializeMultiWorkflow(
|
|
|
257
325
|
_entities: Map<string, Declarable>,
|
|
258
326
|
entityNames: Map<Declarable, string>,
|
|
259
327
|
): SerializerResult {
|
|
260
|
-
// For multi-workflow, each workflow gets its own file.
|
|
261
|
-
// Jobs and triggers need to be associated with workflows somehow.
|
|
262
|
-
// For now, first workflow gets all unscoped entities.
|
|
263
328
|
const files: Record<string, string> = {};
|
|
264
329
|
let primary = "";
|
|
265
330
|
|
|
266
331
|
for (let i = 0; i < workflows.length; i++) {
|
|
267
332
|
const [name, wf] = workflows[i];
|
|
268
333
|
const doc: Record<string, unknown> = {};
|
|
269
|
-
const props = toYAMLValue((wf as Record<string, unknown>).props, entityNames) as Record<string, unknown> | undefined;
|
|
334
|
+
const props = toYAMLValue((wf as unknown as Record<string, unknown>).props, entityNames) as Record<string, unknown> | undefined;
|
|
270
335
|
|
|
271
336
|
if (props) {
|
|
272
337
|
if (props.name) doc.name = props.name;
|
|
@@ -281,10 +346,10 @@ function serializeMultiWorkflow(
|
|
|
281
346
|
if (i === 0 && triggers.length > 0) {
|
|
282
347
|
const onSection = (doc.on as Record<string, unknown>) ?? {};
|
|
283
348
|
for (const [, trigger] of triggers) {
|
|
284
|
-
const entityType = (trigger as Record<string, unknown>).entityType as string;
|
|
349
|
+
const entityType = (trigger as unknown as Record<string, unknown>).entityType as string;
|
|
285
350
|
const eventName = TRIGGER_TYPE_TO_EVENT[entityType];
|
|
286
351
|
if (!eventName) continue;
|
|
287
|
-
const tProps = toYAMLValue((trigger as Record<string, unknown>).props, entityNames) as Record<string, unknown> | undefined;
|
|
352
|
+
const tProps = toYAMLValue((trigger as unknown as Record<string, unknown>).props, entityNames) as Record<string, unknown> | undefined;
|
|
288
353
|
if (tProps && Object.keys(tProps).length > 0) {
|
|
289
354
|
onSection[eventName] = convertValueKeys(tProps);
|
|
290
355
|
} else {
|
|
@@ -294,17 +359,12 @@ function serializeMultiWorkflow(
|
|
|
294
359
|
doc.on = onSection;
|
|
295
360
|
}
|
|
296
361
|
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
jobsSection[toKebabCase(jName)] = convertKeys(jProps);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
doc.jobs = jobsSection;
|
|
307
|
-
}
|
|
362
|
+
// Jobs: use Workflow.props.jobs when defined, otherwise fall back to standalone exports
|
|
363
|
+
// (standalone exports only assigned to first workflow, for backwards compat with composites)
|
|
364
|
+
const rawProps = (wf as unknown as Record<string, unknown>).props as Record<string, unknown>;
|
|
365
|
+
const inlineJobs = buildInlineJobsSection(rawProps, entityNames);
|
|
366
|
+
const jobsSection = inlineJobs ?? (i === 0 ? buildStandaloneJobsSection(jobs, entityNames) : undefined);
|
|
367
|
+
if (jobsSection) doc.jobs = jobsSection;
|
|
308
368
|
|
|
309
369
|
const content = emitYAMLDocument(doc);
|
|
310
370
|
const fileName = `${toKebabCase(name)}.yml`;
|