@intentius/chant-lexicon-github 0.0.18 → 0.0.24

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 (78) hide show
  1. package/dist/integrity.json +14 -4
  2. package/dist/manifest.json +1 -1
  3. package/dist/rules/gha020.ts +40 -0
  4. package/dist/rules/gha021.ts +48 -0
  5. package/dist/rules/gha022.ts +50 -0
  6. package/dist/rules/gha023.ts +44 -0
  7. package/dist/rules/gha024.ts +42 -0
  8. package/dist/rules/gha025.ts +42 -0
  9. package/dist/rules/gha026.ts +40 -0
  10. package/dist/rules/gha027.ts +57 -0
  11. package/dist/rules/gha028.ts +37 -0
  12. package/dist/skills/{github-actions-patterns.md → chant-github-patterns.md} +2 -1
  13. package/dist/skills/chant-github-security.md +88 -0
  14. package/dist/skills/chant-github.md +569 -4
  15. package/package.json +20 -2
  16. package/src/codegen/docs.test.ts +19 -0
  17. package/src/codegen/generate.test.ts +12 -0
  18. package/src/codegen/package.test.ts +8 -0
  19. package/src/composites/cache.ts +8 -3
  20. package/src/composites/checkout.ts +8 -3
  21. package/src/composites/composites.test.ts +106 -0
  22. package/src/composites/deploy-environment.ts +11 -5
  23. package/src/composites/docker-build.ts +11 -5
  24. package/src/composites/download-artifact.ts +8 -3
  25. package/src/composites/go-ci.ts +17 -9
  26. package/src/composites/node-ci.ts +11 -5
  27. package/src/composites/node-pipeline.ts +14 -7
  28. package/src/composites/python-ci.ts +14 -7
  29. package/src/composites/setup-go.ts +8 -3
  30. package/src/composites/setup-node.ts +8 -3
  31. package/src/composites/setup-python.ts +8 -3
  32. package/src/composites/upload-artifact.ts +8 -3
  33. package/src/coverage.test.ts +23 -0
  34. package/src/import/roundtrip.test.ts +206 -0
  35. package/src/lint/post-synth/gha006.test.ts +56 -0
  36. package/src/lint/post-synth/gha009.test.ts +56 -0
  37. package/src/lint/post-synth/gha011.test.ts +61 -0
  38. package/src/lint/post-synth/gha017.test.ts +51 -0
  39. package/src/lint/post-synth/gha018.test.ts +66 -0
  40. package/src/lint/post-synth/gha019.test.ts +66 -0
  41. package/src/lint/post-synth/gha020.test.ts +66 -0
  42. package/src/lint/post-synth/gha020.ts +40 -0
  43. package/src/lint/post-synth/gha021.test.ts +67 -0
  44. package/src/lint/post-synth/gha021.ts +48 -0
  45. package/src/lint/post-synth/gha022.test.ts +45 -0
  46. package/src/lint/post-synth/gha022.ts +50 -0
  47. package/src/lint/post-synth/gha023.test.ts +52 -0
  48. package/src/lint/post-synth/gha023.ts +44 -0
  49. package/src/lint/post-synth/gha024.test.ts +67 -0
  50. package/src/lint/post-synth/gha024.ts +42 -0
  51. package/src/lint/post-synth/gha025.test.ts +65 -0
  52. package/src/lint/post-synth/gha025.ts +42 -0
  53. package/src/lint/post-synth/gha026.test.ts +65 -0
  54. package/src/lint/post-synth/gha026.ts +40 -0
  55. package/src/lint/post-synth/gha027.test.ts +48 -0
  56. package/src/lint/post-synth/gha027.ts +57 -0
  57. package/src/lint/post-synth/gha028.test.ts +48 -0
  58. package/src/lint/post-synth/gha028.ts +37 -0
  59. package/src/lint/rules/deprecated-action-version.test.ts +26 -0
  60. package/src/lint/rules/detect-secrets.test.ts +25 -0
  61. package/src/lint/rules/extract-inline-structs.test.ts +25 -0
  62. package/src/lint/rules/file-job-limit.test.ts +28 -0
  63. package/src/lint/rules/job-timeout.test.ts +31 -0
  64. package/src/lint/rules/missing-recommended-inputs.test.ts +26 -0
  65. package/src/lint/rules/no-hardcoded-secrets.test.ts +31 -0
  66. package/src/lint/rules/no-raw-expressions.test.ts +31 -0
  67. package/src/lint/rules/suggest-cache.test.ts +25 -0
  68. package/src/lint/rules/use-condition-builders.test.ts +31 -0
  69. package/src/lint/rules/use-matrix-builder.test.ts +25 -0
  70. package/src/lint/rules/use-typed-actions.test.ts +39 -0
  71. package/src/lint/rules/validate-concurrency.test.ts +31 -0
  72. package/src/plugin.test.ts +1 -1
  73. package/src/plugin.ts +70 -145
  74. package/src/skills/{github-actions-patterns.md → chant-github-patterns.md} +2 -1
  75. package/src/skills/chant-github-security.md +88 -0
  76. package/src/skills/chant-github.md +594 -0
  77. package/src/validate.ts +14 -1
  78. package/src/variables.test.ts +48 -0
@@ -1,4 +1,5 @@
1
- import { Composite } from "@intentius/chant";
1
+ import { Composite, mergeDefaults } from "@intentius/chant";
2
+ import type { Step } from "../generated/index";
2
3
 
3
4
  export interface CheckoutProps {
4
5
  ref?: string;
@@ -7,9 +8,13 @@ export interface CheckoutProps {
7
8
  token?: string;
8
9
  submodules?: boolean | string;
9
10
  sshKey?: string;
11
+ defaults?: {
12
+ step?: Partial<ConstructorParameters<typeof Step>[0]>;
13
+ };
10
14
  }
11
15
 
12
16
  export const Checkout = Composite<CheckoutProps>((props) => {
17
+ const { defaults } = props;
13
18
  const withObj: Record<string, string> = {};
14
19
  if (props.ref !== undefined) withObj.ref = props.ref;
15
20
  if (props.repository !== undefined) withObj.repository = props.repository;
@@ -21,11 +26,11 @@ export const Checkout = Composite<CheckoutProps>((props) => {
21
26
  // Import Step lazily to avoid circular dependency at module load time
22
27
  const { createProperty } = require("@intentius/chant/runtime");
23
28
  const StepClass = createProperty("GitHub::Actions::Step", "github");
24
- const step = new StepClass({
29
+ const step = new StepClass(mergeDefaults({
25
30
  name: "Checkout",
26
31
  uses: "actions/checkout@v4",
27
32
  ...(Object.keys(withObj).length > 0 ? { with: withObj } : {}),
28
- });
33
+ }, defaults?.step));
29
34
 
30
35
  return { step };
31
36
  }, "Checkout");
@@ -673,3 +673,109 @@ describe("GoCI", () => {
673
673
  expect(result.testJob.props["runs-on"]).toBe("macos-latest");
674
674
  });
675
675
  });
676
+
677
+ // ── Defaults overrides ─────────────────────────────────────────────
678
+
679
+ describe("defaults overrides", () => {
680
+ test("Checkout defaults override step props", () => {
681
+ const result = Checkout({
682
+ ref: "main",
683
+ defaults: { step: { id: "my-checkout" } },
684
+ });
685
+ expect(result.step.props.id).toBe("my-checkout");
686
+ expect(result.step.props.uses).toBe("actions/checkout@v4");
687
+ });
688
+
689
+ test("SetupNode defaults override step props", () => {
690
+ const result = SetupNode({
691
+ nodeVersion: "20",
692
+ defaults: { step: { id: "setup" } },
693
+ });
694
+ expect(result.step.props.id).toBe("setup");
695
+ expect(result.step.props.name).toBe("Setup Node.js");
696
+ });
697
+
698
+ test("NodeCI defaults override job and workflow props", () => {
699
+ const result = NodeCI({
700
+ defaults: {
701
+ job: { "timeout-minutes": 30 } as any,
702
+ workflow: { concurrency: "ci-${{ github.ref }}" } as any,
703
+ },
704
+ });
705
+ expect(result.job.props["timeout-minutes"]).toBe(30);
706
+ expect(result.workflow.props.concurrency).toBe("ci-${{ github.ref }}");
707
+ });
708
+
709
+ test("NodePipeline defaults override individual jobs", () => {
710
+ const result = NodePipeline({
711
+ defaults: {
712
+ buildJob: { "timeout-minutes": 15 } as any,
713
+ testJob: { "timeout-minutes": 20 } as any,
714
+ },
715
+ });
716
+ expect(result.buildJob.props["timeout-minutes"]).toBe(15);
717
+ expect(result.testJob.props["timeout-minutes"]).toBe(20);
718
+ });
719
+
720
+ test("DockerBuild defaults override job and workflow", () => {
721
+ const result = DockerBuild({
722
+ defaults: {
723
+ job: { "timeout-minutes": 60 } as any,
724
+ workflow: { concurrency: "docker" } as any,
725
+ },
726
+ });
727
+ expect(result.job.props["timeout-minutes"]).toBe(60);
728
+ expect(result.workflow.props.concurrency).toBe("docker");
729
+ });
730
+
731
+ test("DeployEnvironment defaults override deploy and cleanup jobs", () => {
732
+ const result = DeployEnvironment({
733
+ name: "staging",
734
+ deployScript: "npm run deploy",
735
+ defaults: {
736
+ deployJob: { "timeout-minutes": 30 } as any,
737
+ cleanupJob: { "timeout-minutes": 10 } as any,
738
+ },
739
+ });
740
+ expect(result.deployJob.props["timeout-minutes"]).toBe(30);
741
+ expect(result.cleanupJob.props["timeout-minutes"]).toBe(10);
742
+ });
743
+
744
+ test("GoCI defaults override workflow name", () => {
745
+ const result = GoCI({
746
+ defaults: {
747
+ workflow: { name: "Custom Go CI" } as any,
748
+ },
749
+ });
750
+ expect(result.workflow.props.name).toBe("Custom Go CI");
751
+ });
752
+
753
+ test("PythonCI defaults override testJob", () => {
754
+ const result = PythonCI({
755
+ defaults: {
756
+ testJob: { "timeout-minutes": 45 } as any,
757
+ },
758
+ });
759
+ expect(result.testJob.props["timeout-minutes"]).toBe(45);
760
+ });
761
+
762
+ test("UploadArtifact defaults override step props", () => {
763
+ const result = UploadArtifact({
764
+ name: "build",
765
+ path: "dist/",
766
+ defaults: { step: { id: "upload" } },
767
+ });
768
+ expect(result.step.props.id).toBe("upload");
769
+ expect(result.step.props.uses).toBe("actions/upload-artifact@v4");
770
+ });
771
+
772
+ test("CacheAction defaults override step props", () => {
773
+ const result = CacheAction({
774
+ path: "node_modules",
775
+ key: "cache-key",
776
+ defaults: { step: { id: "cache-step" } },
777
+ });
778
+ expect(result.step.props.id).toBe("cache-step");
779
+ expect(result.step.props.uses).toBe("actions/cache@v4");
780
+ });
781
+ });
@@ -1,4 +1,5 @@
1
- import { Composite } from "@intentius/chant";
1
+ import { Composite, mergeDefaults } from "@intentius/chant";
2
+ import type { Job } from "../generated/index";
2
3
 
3
4
  export interface DeployEnvironmentProps {
4
5
  /** Environment name. Required. */
@@ -15,6 +16,10 @@ export interface DeployEnvironmentProps {
15
16
  cancelInProgress?: boolean;
16
17
  /** Runner label. Default: "ubuntu-latest" */
17
18
  runsOn?: string;
19
+ defaults?: {
20
+ deployJob?: Partial<ConstructorParameters<typeof Job>[0]>;
21
+ cleanupJob?: Partial<ConstructorParameters<typeof Job>[0]>;
22
+ };
18
23
  }
19
24
 
20
25
  export const DeployEnvironment = Composite<DeployEnvironmentProps>((props) => {
@@ -26,6 +31,7 @@ export const DeployEnvironment = Composite<DeployEnvironmentProps>((props) => {
26
31
  concurrencyGroup,
27
32
  cancelInProgress = true,
28
33
  runsOn = "ubuntu-latest",
34
+ defaults,
29
35
  } = props;
30
36
 
31
37
  const group = concurrencyGroup ?? `deploy-${name}`;
@@ -49,7 +55,7 @@ export const DeployEnvironment = Composite<DeployEnvironmentProps>((props) => {
49
55
  environment.url = url;
50
56
  }
51
57
 
52
- const deployJob = new JobClass({
58
+ const deployJob = new JobClass(mergeDefaults({
53
59
  "runs-on": runsOn,
54
60
  environment,
55
61
  concurrency: {
@@ -57,7 +63,7 @@ export const DeployEnvironment = Composite<DeployEnvironmentProps>((props) => {
57
63
  "cancel-in-progress": cancelInProgress,
58
64
  },
59
65
  steps: deploySteps,
60
- });
66
+ }, defaults?.deployJob));
61
67
 
62
68
  // ── Cleanup job ────────────────────────────────────────────────────
63
69
  const cleanupSteps = [
@@ -67,11 +73,11 @@ export const DeployEnvironment = Composite<DeployEnvironmentProps>((props) => {
67
73
  ),
68
74
  ];
69
75
 
70
- const cleanupJob = new JobClass({
76
+ const cleanupJob = new JobClass(mergeDefaults({
71
77
  "runs-on": runsOn,
72
78
  environment: { name },
73
79
  steps: cleanupSteps,
74
- });
80
+ }, defaults?.cleanupJob));
75
81
 
76
82
  return { deployJob, cleanupJob };
77
83
  }, "DeployEnvironment");
@@ -1,4 +1,5 @@
1
- import { Composite } from "@intentius/chant";
1
+ import { Composite, mergeDefaults } from "@intentius/chant";
2
+ import type { Job, Workflow } from "../generated/index";
2
3
 
3
4
  export interface DockerBuildProps {
4
5
  /** Image tag. Default: "${{ github.sha }}" */
@@ -21,6 +22,10 @@ export interface DockerBuildProps {
21
22
  platforms?: string[];
22
23
  /** Runner label. Default: "ubuntu-latest" */
23
24
  runsOn?: string;
25
+ defaults?: {
26
+ job?: Partial<ConstructorParameters<typeof Job>[0]>;
27
+ workflow?: Partial<ConstructorParameters<typeof Workflow>[0]>;
28
+ };
24
29
  }
25
30
 
26
31
  export const DockerBuild = Composite<DockerBuildProps>((props) => {
@@ -35,6 +40,7 @@ export const DockerBuild = Composite<DockerBuildProps>((props) => {
35
40
  push = true,
36
41
  platforms,
37
42
  runsOn = "ubuntu-latest",
43
+ defaults,
38
44
  } = props;
39
45
 
40
46
  const { createProperty, createResource } = require("@intentius/chant/runtime");
@@ -100,12 +106,12 @@ export const DockerBuild = Composite<DockerBuildProps>((props) => {
100
106
  with: buildPushWith,
101
107
  });
102
108
 
103
- const job = new JobClass({
109
+ const job = new JobClass(mergeDefaults({
104
110
  "runs-on": runsOn,
105
111
  steps: [checkout, login, setupBuildx, metadata, buildPush],
106
- });
112
+ }, defaults?.job));
107
113
 
108
- const workflow = new WorkflowClass({
114
+ const workflow = new WorkflowClass(mergeDefaults({
109
115
  name: "Docker Build",
110
116
  on: {
111
117
  push: { branches: ["main"] },
@@ -114,7 +120,7 @@ export const DockerBuild = Composite<DockerBuildProps>((props) => {
114
120
  contents: "read",
115
121
  packages: "write",
116
122
  },
117
- });
123
+ }, defaults?.workflow));
118
124
 
119
125
  return { workflow, job };
120
126
  }, "DockerBuild");
@@ -1,12 +1,17 @@
1
- import { Composite } from "@intentius/chant";
1
+ import { Composite, mergeDefaults } from "@intentius/chant";
2
+ import type { Step } from "../generated/index";
2
3
 
3
4
  export interface DownloadArtifactProps {
4
5
  name?: string;
5
6
  path?: string;
6
7
  mergeMultiple?: boolean;
8
+ defaults?: {
9
+ step?: Partial<ConstructorParameters<typeof Step>[0]>;
10
+ };
7
11
  }
8
12
 
9
13
  export const DownloadArtifact = Composite<DownloadArtifactProps>((props) => {
14
+ const { defaults } = props;
10
15
  const withObj: Record<string, string> = {};
11
16
  if (props.name !== undefined) withObj.name = props.name;
12
17
  if (props.path !== undefined) withObj.path = props.path;
@@ -14,11 +19,11 @@ export const DownloadArtifact = Composite<DownloadArtifactProps>((props) => {
14
19
 
15
20
  const { createProperty } = require("@intentius/chant/runtime");
16
21
  const StepClass = createProperty("GitHub::Actions::Step", "github");
17
- const step = new StepClass({
22
+ const step = new StepClass(mergeDefaults({
18
23
  name: "Download Artifact",
19
24
  uses: "actions/download-artifact@v4",
20
25
  ...(Object.keys(withObj).length > 0 ? { with: withObj } : {}),
21
- });
26
+ }, defaults?.step));
22
27
 
23
28
  return { step };
24
29
  }, "DownloadArtifact");
@@ -1,4 +1,5 @@
1
- import { Composite } from "@intentius/chant";
1
+ import { Composite, mergeDefaults } from "@intentius/chant";
2
+ import type { Job, Workflow } from "../generated/index";
2
3
 
3
4
  export interface GoCIProps {
4
5
  /** Go version. Default: "1.22" */
@@ -11,6 +12,12 @@ export interface GoCIProps {
11
12
  lintCommand?: string | null;
12
13
  /** Runner label. Default: "ubuntu-latest" */
13
14
  runsOn?: string;
15
+ defaults?: {
16
+ buildJob?: Partial<ConstructorParameters<typeof Job>[0]>;
17
+ testJob?: Partial<ConstructorParameters<typeof Job>[0]>;
18
+ lintJob?: Partial<ConstructorParameters<typeof Job>[0]>;
19
+ workflow?: Partial<ConstructorParameters<typeof Workflow>[0]>;
20
+ };
14
21
  }
15
22
 
16
23
  export const GoCI = Composite<GoCIProps>((props) => {
@@ -20,6 +27,7 @@ export const GoCI = Composite<GoCIProps>((props) => {
20
27
  buildCommand = "go build ./...",
21
28
  lintCommand = "golangci-lint run",
22
29
  runsOn = "ubuntu-latest",
30
+ defaults,
23
31
  } = props;
24
32
 
25
33
  const { createProperty, createResource } = require("@intentius/chant/runtime");
@@ -28,7 +36,7 @@ export const GoCI = Composite<GoCIProps>((props) => {
28
36
  const WorkflowClass = createResource("GitHub::Actions::Workflow", "github", {});
29
37
 
30
38
  // ── Build job ──────────────────────────────────────────────────────
31
- const buildJob = new JobClass({
39
+ const buildJob = new JobClass(mergeDefaults({
32
40
  "runs-on": runsOn,
33
41
  steps: [
34
42
  new StepClass({ name: "Checkout", uses: "actions/checkout@v4" }),
@@ -39,10 +47,10 @@ export const GoCI = Composite<GoCIProps>((props) => {
39
47
  }),
40
48
  new StepClass({ name: "Build", run: buildCommand }),
41
49
  ],
42
- });
50
+ }, defaults?.buildJob));
43
51
 
44
52
  // ── Test job ───────────────────────────────────────────────────────
45
- const testJob = new JobClass({
53
+ const testJob = new JobClass(mergeDefaults({
46
54
  "runs-on": runsOn,
47
55
  steps: [
48
56
  new StepClass({ name: "Checkout", uses: "actions/checkout@v4" }),
@@ -53,12 +61,12 @@ export const GoCI = Composite<GoCIProps>((props) => {
53
61
  }),
54
62
  new StepClass({ name: "Test", run: testCommand }),
55
63
  ],
56
- });
64
+ }, defaults?.testJob));
57
65
 
58
66
  // ── Lint job (optional) ────────────────────────────────────────────
59
67
  const lintJob =
60
68
  lintCommand !== null
61
- ? new JobClass({
69
+ ? new JobClass(mergeDefaults({
62
70
  "runs-on": runsOn,
63
71
  steps: [
64
72
  new StepClass({ name: "Checkout", uses: "actions/checkout@v4" }),
@@ -73,16 +81,16 @@ export const GoCI = Composite<GoCIProps>((props) => {
73
81
  with: { args: lintCommand },
74
82
  }),
75
83
  ],
76
- })
84
+ }, defaults?.lintJob))
77
85
  : undefined;
78
86
 
79
- const workflow = new WorkflowClass({
87
+ const workflow = new WorkflowClass(mergeDefaults({
80
88
  name: "Go CI",
81
89
  on: {
82
90
  push: { branches: ["main"] },
83
91
  pull_request: { branches: ["main"] },
84
92
  },
85
- });
93
+ }, defaults?.workflow));
86
94
 
87
95
  if (lintJob) {
88
96
  return { workflow, buildJob, testJob, lintJob };
@@ -1,4 +1,5 @@
1
- import { Composite } from "@intentius/chant";
1
+ import { Composite, mergeDefaults } from "@intentius/chant";
2
+ import type { Job, Workflow } from "../generated/index";
2
3
 
3
4
  export interface NodeCIProps {
4
5
  nodeVersion?: string;
@@ -6,6 +7,10 @@ export interface NodeCIProps {
6
7
  buildScript?: string;
7
8
  testScript?: string;
8
9
  installCommand?: string;
10
+ defaults?: {
11
+ job?: Partial<ConstructorParameters<typeof Job>[0]>;
12
+ workflow?: Partial<ConstructorParameters<typeof Workflow>[0]>;
13
+ };
9
14
  }
10
15
 
11
16
  export const NodeCI = Composite<NodeCIProps>((props) => {
@@ -15,6 +20,7 @@ export const NodeCI = Composite<NodeCIProps>((props) => {
15
20
  buildScript = "build",
16
21
  testScript = "test",
17
22
  installCommand,
23
+ defaults,
18
24
  } = props;
19
25
 
20
26
  const install = installCommand ?? (packageManager === "npm" ? "npm ci" : `${packageManager} install`);
@@ -54,18 +60,18 @@ export const NodeCI = Composite<NodeCIProps>((props) => {
54
60
  run: `${run} ${testScript}`,
55
61
  });
56
62
 
57
- const job = new JobClass({
63
+ const job = new JobClass(mergeDefaults({
58
64
  "runs-on": "ubuntu-latest",
59
65
  steps: [checkoutStep, setupNodeStep, installStep, buildStep, testStep],
60
- });
66
+ }, defaults?.job));
61
67
 
62
- const workflow = new WorkflowClass({
68
+ const workflow = new WorkflowClass(mergeDefaults({
63
69
  name: "CI",
64
70
  on: {
65
71
  push: { branches: ["main"] },
66
72
  pull_request: { branches: ["main"] },
67
73
  },
68
- });
74
+ }, defaults?.workflow));
69
75
 
70
76
  return { workflow, job };
71
77
  }, "NodeCI");
@@ -1,4 +1,5 @@
1
- import { Composite, withDefaults } from "@intentius/chant";
1
+ import { Composite, withDefaults, mergeDefaults } from "@intentius/chant";
2
+ import type { Job, Workflow } from "../generated/index";
2
3
 
3
4
  export interface NodePipelineProps {
4
5
  /** Node.js version. Default: "22" */
@@ -19,6 +20,11 @@ export interface NodePipelineProps {
19
20
  artifactRetentionDays?: number;
20
21
  /** Runner label. Default: "ubuntu-latest" */
21
22
  runsOn?: string;
23
+ defaults?: {
24
+ buildJob?: Partial<ConstructorParameters<typeof Job>[0]>;
25
+ testJob?: Partial<ConstructorParameters<typeof Job>[0]>;
26
+ workflow?: Partial<ConstructorParameters<typeof Workflow>[0]>;
27
+ };
22
28
  }
23
29
 
24
30
  const cacheConfig = {
@@ -59,6 +65,7 @@ export const NodePipeline = Composite<NodePipelineProps>((props) => {
59
65
  artifactName = "build-output",
60
66
  artifactRetentionDays = 1,
61
67
  runsOn = "ubuntu-latest",
68
+ defaults,
62
69
  } = props;
63
70
 
64
71
  const pm = cacheConfig[packageManager];
@@ -97,10 +104,10 @@ export const NodePipeline = Composite<NodePipelineProps>((props) => {
97
104
  },
98
105
  });
99
106
 
100
- const buildJob = new JobClass({
107
+ const buildJob = new JobClass(mergeDefaults({
101
108
  "runs-on": runsOn,
102
109
  steps: [buildCheckout, buildSetup, buildInstall, buildRun, buildUpload],
103
- });
110
+ }, defaults?.buildJob));
104
111
 
105
112
  // ── Test job steps ─────────────────────────────────────────────────
106
113
  const testCheckout = new StepClass({ name: "Checkout", uses: "actions/checkout@v4" });
@@ -123,20 +130,20 @@ export const NodePipeline = Composite<NodePipelineProps>((props) => {
123
130
 
124
131
  const testRun = new StepClass({ name: "Test", run: `${run} ${testScript}` });
125
132
 
126
- const testJob = new JobClass({
133
+ const testJob = new JobClass(mergeDefaults({
127
134
  "runs-on": runsOn,
128
135
  needs: ["build"],
129
136
  steps: [testCheckout, testSetup, testInstall, testDownload, testRun],
130
- });
137
+ }, defaults?.testJob));
131
138
 
132
139
  // ── Workflow ───────────────────────────────────────────────────────
133
- const workflow = new WorkflowClass({
140
+ const workflow = new WorkflowClass(mergeDefaults({
134
141
  name: "Node Pipeline",
135
142
  on: {
136
143
  push: { branches: ["main"] },
137
144
  pull_request: { branches: ["main"] },
138
145
  },
139
- });
146
+ }, defaults?.workflow));
140
147
 
141
148
  return { workflow, buildJob, testJob };
142
149
  }, "NodePipeline");
@@ -1,4 +1,5 @@
1
- import { Composite } from "@intentius/chant";
1
+ import { Composite, mergeDefaults } from "@intentius/chant";
2
+ import type { Job, Workflow } from "../generated/index";
2
3
 
3
4
  export interface PythonCIProps {
4
5
  /** Python version. Default: "3.12" */
@@ -13,6 +14,11 @@ export interface PythonCIProps {
13
14
  usePoetry?: boolean;
14
15
  /** Runner label. Default: "ubuntu-latest" */
15
16
  runsOn?: string;
17
+ defaults?: {
18
+ testJob?: Partial<ConstructorParameters<typeof Job>[0]>;
19
+ lintJob?: Partial<ConstructorParameters<typeof Job>[0]>;
20
+ workflow?: Partial<ConstructorParameters<typeof Workflow>[0]>;
21
+ };
16
22
  }
17
23
 
18
24
  export const PythonCI = Composite<PythonCIProps>((props) => {
@@ -23,6 +29,7 @@ export const PythonCI = Composite<PythonCIProps>((props) => {
23
29
  requirementsFile = "requirements.txt",
24
30
  usePoetry = false,
25
31
  runsOn = "ubuntu-latest",
32
+ defaults,
26
33
  } = props;
27
34
 
28
35
  const { createProperty, createResource } = require("@intentius/chant/runtime");
@@ -41,7 +48,7 @@ export const PythonCI = Composite<PythonCIProps>((props) => {
41
48
  ];
42
49
 
43
50
  // ── Test job ───────────────────────────────────────────────────────
44
- const testJob = new JobClass({
51
+ const testJob = new JobClass(mergeDefaults({
45
52
  "runs-on": runsOn,
46
53
  steps: [
47
54
  new StepClass({ name: "Checkout", uses: "actions/checkout@v4" }),
@@ -53,12 +60,12 @@ export const PythonCI = Composite<PythonCIProps>((props) => {
53
60
  ...installSteps,
54
61
  new StepClass({ name: "Test", run: testCommand }),
55
62
  ],
56
- });
63
+ }, defaults?.testJob));
57
64
 
58
65
  // ── Lint job (optional) ────────────────────────────────────────────
59
66
  const lintJob =
60
67
  lintCommand !== null
61
- ? new JobClass({
68
+ ? new JobClass(mergeDefaults({
62
69
  "runs-on": runsOn,
63
70
  steps: [
64
71
  new StepClass({ name: "Checkout", uses: "actions/checkout@v4" }),
@@ -73,17 +80,17 @@ export const PythonCI = Composite<PythonCIProps>((props) => {
73
80
  }),
74
81
  new StepClass({ name: "Lint", run: lintCommand }),
75
82
  ],
76
- })
83
+ }, defaults?.lintJob))
77
84
  : undefined;
78
85
 
79
86
  // ── Workflow ───────────────────────────────────────────────────────
80
- const workflow = new WorkflowClass({
87
+ const workflow = new WorkflowClass(mergeDefaults({
81
88
  name: "Python CI",
82
89
  on: {
83
90
  push: { branches: ["main"] },
84
91
  pull_request: { branches: ["main"] },
85
92
  },
86
- });
93
+ }, defaults?.workflow));
87
94
 
88
95
  if (lintJob) {
89
96
  return { workflow, testJob, lintJob };
@@ -1,12 +1,17 @@
1
- import { Composite } from "@intentius/chant";
1
+ import { Composite, mergeDefaults } from "@intentius/chant";
2
+ import type { Step } from "../generated/index";
2
3
 
3
4
  export interface SetupGoProps {
4
5
  goVersion?: string;
5
6
  goVersionFile?: string;
6
7
  cache?: boolean;
8
+ defaults?: {
9
+ step?: Partial<ConstructorParameters<typeof Step>[0]>;
10
+ };
7
11
  }
8
12
 
9
13
  export const SetupGo = Composite<SetupGoProps>((props) => {
14
+ const { defaults } = props;
10
15
  const withObj: Record<string, string> = {};
11
16
  if (props.goVersion !== undefined) withObj["go-version"] = props.goVersion;
12
17
  if (props.goVersionFile !== undefined) withObj["go-version-file"] = props.goVersionFile;
@@ -14,11 +19,11 @@ export const SetupGo = Composite<SetupGoProps>((props) => {
14
19
 
15
20
  const { createProperty } = require("@intentius/chant/runtime");
16
21
  const StepClass = createProperty("GitHub::Actions::Step", "github");
17
- const step = new StepClass({
22
+ const step = new StepClass(mergeDefaults({
18
23
  name: "Setup Go",
19
24
  uses: "actions/setup-go@v5",
20
25
  ...(Object.keys(withObj).length > 0 ? { with: withObj } : {}),
21
- });
26
+ }, defaults?.step));
22
27
 
23
28
  return { step };
24
29
  }, "SetupGo");
@@ -1,13 +1,18 @@
1
- import { Composite } from "@intentius/chant";
1
+ import { Composite, mergeDefaults } from "@intentius/chant";
2
+ import type { Step } from "../generated/index";
2
3
 
3
4
  export interface SetupNodeProps {
4
5
  nodeVersion?: string;
5
6
  registryUrl?: string;
6
7
  cache?: string;
7
8
  cacheFilePath?: string;
9
+ defaults?: {
10
+ step?: Partial<ConstructorParameters<typeof Step>[0]>;
11
+ };
8
12
  }
9
13
 
10
14
  export const SetupNode = Composite<SetupNodeProps>((props) => {
15
+ const { defaults } = props;
11
16
  const withObj: Record<string, string> = {};
12
17
  if (props.nodeVersion !== undefined) withObj["node-version"] = props.nodeVersion;
13
18
  if (props.registryUrl !== undefined) withObj["registry-url"] = props.registryUrl;
@@ -16,11 +21,11 @@ export const SetupNode = Composite<SetupNodeProps>((props) => {
16
21
 
17
22
  const { createProperty } = require("@intentius/chant/runtime");
18
23
  const StepClass = createProperty("GitHub::Actions::Step", "github");
19
- const step = new StepClass({
24
+ const step = new StepClass(mergeDefaults({
20
25
  name: "Setup Node.js",
21
26
  uses: "actions/setup-node@v4",
22
27
  ...(Object.keys(withObj).length > 0 ? { with: withObj } : {}),
23
- });
28
+ }, defaults?.step));
24
29
 
25
30
  return { step };
26
31
  }, "SetupNode");
@@ -1,12 +1,17 @@
1
- import { Composite } from "@intentius/chant";
1
+ import { Composite, mergeDefaults } from "@intentius/chant";
2
+ import type { Step } from "../generated/index";
2
3
 
3
4
  export interface SetupPythonProps {
4
5
  pythonVersion?: string;
5
6
  cache?: string;
6
7
  architecture?: string;
8
+ defaults?: {
9
+ step?: Partial<ConstructorParameters<typeof Step>[0]>;
10
+ };
7
11
  }
8
12
 
9
13
  export const SetupPython = Composite<SetupPythonProps>((props) => {
14
+ const { defaults } = props;
10
15
  const withObj: Record<string, string> = {};
11
16
  if (props.pythonVersion !== undefined) withObj["python-version"] = props.pythonVersion;
12
17
  if (props.cache !== undefined) withObj.cache = props.cache;
@@ -14,11 +19,11 @@ export const SetupPython = Composite<SetupPythonProps>((props) => {
14
19
 
15
20
  const { createProperty } = require("@intentius/chant/runtime");
16
21
  const StepClass = createProperty("GitHub::Actions::Step", "github");
17
- const step = new StepClass({
22
+ const step = new StepClass(mergeDefaults({
18
23
  name: "Setup Python",
19
24
  uses: "actions/setup-python@v5",
20
25
  ...(Object.keys(withObj).length > 0 ? { with: withObj } : {}),
21
- });
26
+ }, defaults?.step));
22
27
 
23
28
  return { step };
24
29
  }, "SetupPython");