gagen 0.0.4

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 (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +553 -0
  3. package/esm/_dnt.polyfills.d.ts +12 -0
  4. package/esm/_dnt.polyfills.d.ts.map +1 -0
  5. package/esm/_dnt.polyfills.js +15 -0
  6. package/esm/_dnt.test_shims.d.ts.map +1 -0
  7. package/esm/artifact.d.ts +20 -0
  8. package/esm/artifact.d.ts.map +1 -0
  9. package/esm/artifact.js +62 -0
  10. package/esm/deps/jsr.io/@std/assert/1.0.18/almost_equals.d.ts.map +1 -0
  11. package/esm/deps/jsr.io/@std/assert/1.0.18/array_includes.d.ts.map +1 -0
  12. package/esm/deps/jsr.io/@std/assert/1.0.18/assert.d.ts.map +1 -0
  13. package/esm/deps/jsr.io/@std/assert/1.0.18/assertion_error.d.ts.map +1 -0
  14. package/esm/deps/jsr.io/@std/assert/1.0.18/equal.d.ts.map +1 -0
  15. package/esm/deps/jsr.io/@std/assert/1.0.18/equals.d.ts.map +1 -0
  16. package/esm/deps/jsr.io/@std/assert/1.0.18/exists.d.ts.map +1 -0
  17. package/esm/deps/jsr.io/@std/assert/1.0.18/fail.d.ts.map +1 -0
  18. package/esm/deps/jsr.io/@std/assert/1.0.18/false.d.ts.map +1 -0
  19. package/esm/deps/jsr.io/@std/assert/1.0.18/greater.d.ts.map +1 -0
  20. package/esm/deps/jsr.io/@std/assert/1.0.18/greater_or_equal.d.ts.map +1 -0
  21. package/esm/deps/jsr.io/@std/assert/1.0.18/instance_of.d.ts.map +1 -0
  22. package/esm/deps/jsr.io/@std/assert/1.0.18/is_error.d.ts.map +1 -0
  23. package/esm/deps/jsr.io/@std/assert/1.0.18/less.d.ts.map +1 -0
  24. package/esm/deps/jsr.io/@std/assert/1.0.18/less_or_equal.d.ts.map +1 -0
  25. package/esm/deps/jsr.io/@std/assert/1.0.18/match.d.ts.map +1 -0
  26. package/esm/deps/jsr.io/@std/assert/1.0.18/mod.d.ts.map +1 -0
  27. package/esm/deps/jsr.io/@std/assert/1.0.18/not_equals.d.ts.map +1 -0
  28. package/esm/deps/jsr.io/@std/assert/1.0.18/not_instance_of.d.ts.map +1 -0
  29. package/esm/deps/jsr.io/@std/assert/1.0.18/not_match.d.ts.map +1 -0
  30. package/esm/deps/jsr.io/@std/assert/1.0.18/not_strict_equals.d.ts.map +1 -0
  31. package/esm/deps/jsr.io/@std/assert/1.0.18/object_match.d.ts.map +1 -0
  32. package/esm/deps/jsr.io/@std/assert/1.0.18/rejects.d.ts.map +1 -0
  33. package/esm/deps/jsr.io/@std/assert/1.0.18/strict_equals.d.ts.map +1 -0
  34. package/esm/deps/jsr.io/@std/assert/1.0.18/string_includes.d.ts.map +1 -0
  35. package/esm/deps/jsr.io/@std/assert/1.0.18/throws.d.ts.map +1 -0
  36. package/esm/deps/jsr.io/@std/assert/1.0.18/unimplemented.d.ts.map +1 -0
  37. package/esm/deps/jsr.io/@std/assert/1.0.18/unreachable.d.ts.map +1 -0
  38. package/esm/deps/jsr.io/@std/internal/1.0.12/build_message.d.ts.map +1 -0
  39. package/esm/deps/jsr.io/@std/internal/1.0.12/diff.d.ts.map +1 -0
  40. package/esm/deps/jsr.io/@std/internal/1.0.12/diff_str.d.ts.map +1 -0
  41. package/esm/deps/jsr.io/@std/internal/1.0.12/format.d.ts.map +1 -0
  42. package/esm/deps/jsr.io/@std/internal/1.0.12/styles.d.ts.map +1 -0
  43. package/esm/deps/jsr.io/@std/internal/1.0.12/types.d.ts.map +1 -0
  44. package/esm/deps/jsr.io/@std/yaml/1.0.11/_chars.d.ts +33 -0
  45. package/esm/deps/jsr.io/@std/yaml/1.0.11/_chars.d.ts.map +1 -0
  46. package/esm/deps/jsr.io/@std/yaml/1.0.11/_chars.js +48 -0
  47. package/esm/deps/jsr.io/@std/yaml/1.0.11/_dumper_state.d.ts +106 -0
  48. package/esm/deps/jsr.io/@std/yaml/1.0.11/_dumper_state.d.ts.map +1 -0
  49. package/esm/deps/jsr.io/@std/yaml/1.0.11/_dumper_state.js +712 -0
  50. package/esm/deps/jsr.io/@std/yaml/1.0.11/_loader_state.d.ts +69 -0
  51. package/esm/deps/jsr.io/@std/yaml/1.0.11/_loader_state.d.ts.map +1 -0
  52. package/esm/deps/jsr.io/@std/yaml/1.0.11/_loader_state.js +1404 -0
  53. package/esm/deps/jsr.io/@std/yaml/1.0.11/_schema.d.ts +44 -0
  54. package/esm/deps/jsr.io/@std/yaml/1.0.11/_schema.d.ts.map +1 -0
  55. package/esm/deps/jsr.io/@std/yaml/1.0.11/_schema.js +117 -0
  56. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/binary.d.ts +3 -0
  57. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/binary.d.ts.map +1 -0
  58. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/binary.js +103 -0
  59. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/bool.d.ts +3 -0
  60. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/bool.d.ts.map +1 -0
  61. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/bool.js +32 -0
  62. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/float.d.ts +3 -0
  63. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/float.d.ts.map +1 -0
  64. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/float.js +96 -0
  65. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/int.d.ts +3 -0
  66. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/int.d.ts.map +1 -0
  67. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/int.js +159 -0
  68. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/map.d.ts +3 -0
  69. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/map.d.ts.map +1 -0
  70. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/map.js +14 -0
  71. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/merge.d.ts +3 -0
  72. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/merge.d.ts.map +1 -0
  73. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/merge.js +10 -0
  74. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/nil.d.ts +3 -0
  75. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/nil.d.ts.map +1 -0
  76. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/nil.js +22 -0
  77. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/omap.d.ts +3 -0
  78. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/omap.d.ts.map +1 -0
  79. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/omap.js +29 -0
  80. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/pairs.d.ts +3 -0
  81. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/pairs.d.ts.map +1 -0
  82. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/pairs.js +19 -0
  83. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/regexp.d.ts +3 -0
  84. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/regexp.d.ts.map +1 -0
  85. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/regexp.js +30 -0
  86. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/seq.d.ts +3 -0
  87. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/seq.d.ts.map +1 -0
  88. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/seq.js +10 -0
  89. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/set.d.ts +3 -0
  90. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/set.d.ts.map +1 -0
  91. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/set.js +14 -0
  92. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/str.d.ts +3 -0
  93. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/str.d.ts.map +1 -0
  94. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/str.js +9 -0
  95. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/timestamp.d.ts +3 -0
  96. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/timestamp.d.ts.map +1 -0
  97. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/timestamp.js +81 -0
  98. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/undefined.d.ts +3 -0
  99. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/undefined.d.ts.map +1 -0
  100. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type/undefined.js +20 -0
  101. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type.d.ts +32 -0
  102. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type.d.ts.map +1 -0
  103. package/esm/deps/jsr.io/@std/yaml/1.0.11/_type.js +6 -0
  104. package/esm/deps/jsr.io/@std/yaml/1.0.11/_utils.d.ts +4 -0
  105. package/esm/deps/jsr.io/@std/yaml/1.0.11/_utils.d.ts.map +1 -0
  106. package/esm/deps/jsr.io/@std/yaml/1.0.11/_utils.js +13 -0
  107. package/esm/deps/jsr.io/@std/yaml/1.0.11/parse.d.ts +76 -0
  108. package/esm/deps/jsr.io/@std/yaml/1.0.11/parse.d.ts.map +1 -0
  109. package/esm/deps/jsr.io/@std/yaml/1.0.11/parse.js +93 -0
  110. package/esm/deps/jsr.io/@std/yaml/1.0.11/stringify.d.ts +100 -0
  111. package/esm/deps/jsr.io/@std/yaml/1.0.11/stringify.d.ts.map +1 -0
  112. package/esm/deps/jsr.io/@std/yaml/1.0.11/stringify.js +33 -0
  113. package/esm/expression.d.ts +157 -0
  114. package/esm/expression.d.ts.map +1 -0
  115. package/esm/expression.js +393 -0
  116. package/esm/expression_test.d.ts.map +1 -0
  117. package/esm/job.d.ts +77 -0
  118. package/esm/job.d.ts.map +1 -0
  119. package/esm/job.js +906 -0
  120. package/esm/matrix.d.ts +15 -0
  121. package/esm/matrix.d.ts.map +1 -0
  122. package/esm/matrix.js +44 -0
  123. package/esm/mod.d.ts +14 -0
  124. package/esm/mod.d.ts.map +1 -0
  125. package/esm/mod.js +7 -0
  126. package/esm/mod_test.d.ts.map +1 -0
  127. package/esm/package.json +3 -0
  128. package/esm/permissions.d.ts +4 -0
  129. package/esm/permissions.d.ts.map +1 -0
  130. package/esm/permissions.js +1 -0
  131. package/esm/step.d.ts +48 -0
  132. package/esm/step.d.ts.map +1 -0
  133. package/esm/step.js +226 -0
  134. package/esm/workflow.d.ts +71 -0
  135. package/esm/workflow.d.ts.map +1 -0
  136. package/esm/workflow.js +136 -0
  137. package/package.json +28 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David Sherret
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,553 @@
1
+ # `jsr:@david/gagen`
2
+
3
+ Generate complex GitHub Actions YAML files using a declarative API.
4
+
5
+ ## Basic usage
6
+
7
+ ```ts
8
+ #!/usr/bin/env -S deno run --allow-read=ci.yml --allow-write=ci.yml
9
+ import {
10
+ conditions,
11
+ createWorkflow,
12
+ step,
13
+ steps,
14
+ } from "jsr:@david/gagen@<version>";
15
+
16
+ const checkout = step({
17
+ uses: "actions/checkout@v6",
18
+ });
19
+
20
+ const test = step({
21
+ name: "Test",
22
+ run: "cargo test",
23
+ }).dependsOn(checkout);
24
+
25
+ const installDeno = step({
26
+ uses: "denoland/setup-deno@v2",
27
+ });
28
+
29
+ const lint = steps(
30
+ {
31
+ name: "Clippy",
32
+ run: "cargo clippy",
33
+ },
34
+ step({
35
+ name: "Deno Lint",
36
+ run: "deno lint",
37
+ }).dependsOn(installDeno),
38
+ ).if(conditions.isBranch("main").not()).dependsOn(checkout);
39
+
40
+ // only specify the leaf steps — the other steps are pulled in automatically
41
+ createWorkflow({
42
+ name: "ci",
43
+ on: { push: { branches: ["main"] } },
44
+ jobs: [{
45
+ id: "build",
46
+ runsOn: "ubuntu-latest",
47
+ steps: [test, lint],
48
+ }],
49
+ }).writeOrLint({
50
+ filePath: new URL("./ci.yml", import.meta.url),
51
+ header: "# GENERATED BY ./ci.generate.ts -- DO NOT DIRECTLY EDIT",
52
+ });
53
+ ```
54
+
55
+ This generates a `ci.yml` with steps in the correct order and figures out that
56
+ it should only install deno when the lint step should be run.
57
+
58
+ When run normally, this writes `ci.yml`. When run with `--lint`, it reads the
59
+ existing file and compares the parsed YAML — exiting with a non-zero code if
60
+ they differ. This lets you add a CI step to verify the generated file is up to
61
+ date:
62
+
63
+ ```yaml
64
+ - name: Lint CI generation
65
+ run: ./.github/workflows/ci.generate.ts --lint
66
+ ```
67
+
68
+ ## Conditions
69
+
70
+ Build type-safe GitHub Actions expressions with a fluent API:
71
+
72
+ ```ts
73
+ import { expr } from "jsr:@david/gagen@<version>";
74
+
75
+ const ref = expr("github.ref");
76
+ const os = expr("matrix.os");
77
+
78
+ // simple comparisons
79
+ ref.equals("refs/heads/main");
80
+ // => github.ref == 'refs/heads/main'
81
+
82
+ ref.startsWith("refs/tags/").not();
83
+ // => !startsWith(github.ref, 'refs/tags/')
84
+
85
+ // compose with .and() / .or()
86
+ os.equals("linux").and(ref.startsWith("refs/tags/"));
87
+ // => matrix.os == 'linux' && startsWith(github.ref, 'refs/tags/')
88
+
89
+ // use on steps
90
+ const deploy = step({
91
+ name: "Deploy",
92
+ run: "deploy.sh",
93
+ if: ref.equals("refs/heads/main").and(os.equals("linux")),
94
+ }).dependsOn(build);
95
+ ```
96
+
97
+ ## Common conditions
98
+
99
+ The `conditions` object provides composable helpers for common GitHub Actions
100
+ patterns:
101
+
102
+ ```ts
103
+ import { conditions } from "jsr:@david/gagen@<version>";
104
+
105
+ const { status, isTag, isBranch, isEvent } = conditions;
106
+
107
+ // status check functions
108
+ status.always(); // always()
109
+ status.failure(); // failure()
110
+ status.success(); // success()
111
+ status.cancelled(); // cancelled()
112
+
113
+ // ref checks
114
+ isTag(); // startsWith(github.ref, 'refs/tags/')
115
+ isTag("v1.0.0"); // github.ref == 'refs/tags/v1.0.0'
116
+ isBranch("main"); // github.ref == 'refs/heads/main'
117
+
118
+ // event checks
119
+ isEvent("push"); // github.event_name == 'push'
120
+ isEvent("pull_request"); // github.event_name == 'pull_request'
121
+
122
+ // compose freely with .and() / .or() / .not()
123
+ const deploy = step({
124
+ name: "Deploy",
125
+ run: "deploy.sh",
126
+ if: isBranch("main").and(isEvent("push")),
127
+ }).dependsOn(build);
128
+
129
+ const cleanup = step({
130
+ name: "Cleanup",
131
+ run: "rm -rf dist",
132
+ if: status.always(),
133
+ }).dependsOn(build);
134
+ ```
135
+
136
+ ## Ternary expressions
137
+
138
+ Build GitHub Actions ternary expressions (`condition && trueVal || falseVal`)
139
+ with a fluent `.then().else()` chain:
140
+
141
+ ```ts
142
+ const os = expr("matrix.os");
143
+
144
+ // simple ternary
145
+ const runner = os.equals("linux").then("ubuntu-latest").else("macos-latest");
146
+ // => matrix.os == 'linux' && 'ubuntu-latest' || 'macos-latest'
147
+
148
+ // multi-branch with elseIf
149
+ const runner = os.equals("linux").then("ubuntu-latest")
150
+ .elseIf(os.equals("macos")).then("macos-latest")
151
+ .else("windows-latest");
152
+ // => matrix.os == 'linux' && 'ubuntu-latest' || matrix.os == 'macos' && 'macos-latest' || 'windows-latest'
153
+
154
+ // use in job config
155
+ createWorkflow({
156
+ ...,
157
+ jobs: [
158
+ { id: "build", runsOn: runner, steps: [test] },
159
+ ],
160
+ });
161
+ ```
162
+
163
+ Values can be strings, numbers, booleans, or `ExpressionValue` references.
164
+ Conditions with `||` are automatically parenthesized to preserve correct
165
+ evaluation order.
166
+
167
+ ## Condition propagation
168
+
169
+ Conditions on leaf steps automatically propagate backward to their dependencies.
170
+ This avoids running expensive setup steps when they aren't needed:
171
+
172
+ ```ts
173
+ const checkout = step({ uses: "actions/checkout@v6" });
174
+ const build = step({ run: "cargo build" }).dependsOn(checkout);
175
+ const test = step({
176
+ run: "cargo test",
177
+ if: expr("matrix.job").equals("test"),
178
+ }).dependsOn(build);
179
+
180
+ // only test is passed — checkout and build inherit its condition
181
+ createWorkflow({
182
+ ...,
183
+ jobs: [
184
+ { id: "test", runsOn: "ubuntu-latest", steps: [test] },
185
+ ],
186
+ });
187
+ // all three steps get: if: matrix.job == 'test'
188
+ ```
189
+
190
+ When multiple leaf steps have different conditions, dependencies get the OR of
191
+ those conditions:
192
+
193
+ ```ts
194
+ const test = step({ run: "cargo test", if: jobExpr.equals("test") }).dependsOn(
195
+ checkout,
196
+ );
197
+ const bench = step({ run: "cargo bench", if: jobExpr.equals("bench") })
198
+ .dependsOn(checkout);
199
+
200
+ createWorkflow({
201
+ ...,
202
+ jobs: [
203
+ { id: "test", runsOn: "ubuntu-latest", steps: [test, bench] },
204
+ ],
205
+ });
206
+ // checkout gets: if: matrix.job == 'test' || matrix.job == 'bench'
207
+ ```
208
+
209
+ To prevent propagation, pass the unconditional steps to `steps` as well. Leaf
210
+ steps act as propagation barriers:
211
+
212
+ ```ts
213
+ const build = step({ run: "cargo build" }).dependsOn(checkout);
214
+ const test = step({ run: "cargo test" }).dependsOn(build);
215
+ const linuxOnly = step({
216
+ run: "linux-specific",
217
+ if: os.equals("linux"),
218
+ }).dependsOn(build, test);
219
+
220
+ // test is a leaf with no condition — blocks propagation to build and checkout
221
+ createWorkflow({
222
+ ...,
223
+ jobs: [
224
+ { id: "test", runsOn: "ubuntu-latest", steps: [test, linuxOnly] },
225
+ ],
226
+ });
227
+ ```
228
+
229
+ ## Step outputs and job dependencies
230
+
231
+ Steps can declare outputs. When a job references another job's outputs, the
232
+ `needs` dependency is inferred automatically.
233
+
234
+ ```ts
235
+ import { createWorkflow, job, step } from "jsr:@david/gagen@<version>";
236
+
237
+ const checkStep = step({
238
+ id: "check",
239
+ name: "Check if draft",
240
+ run: `echo 'skip=true' >> $GITHUB_OUTPUT`,
241
+ outputs: ["skip"],
242
+ });
243
+
244
+ // use job() when you need a handle for cross-job references
245
+ const preBuild = job("pre_build", {
246
+ runsOn: "ubuntu-latest",
247
+ steps: [checkStep],
248
+ outputs: { skip: checkStep.outputs.skip },
249
+ });
250
+
251
+ // preBuild.outputs.skip is an ExpressionValue — using it in the `if`
252
+ // automatically adds needs: [pre_build] to this job
253
+ const wf = createWorkflow({
254
+ name: "ci",
255
+ on: { push: { branches: ["main"] } },
256
+ jobs: [
257
+ preBuild,
258
+ {
259
+ id: "build",
260
+ runsOn: "ubuntu-latest",
261
+ if: preBuild.outputs.skip.notEquals("true"),
262
+ steps: [buildStep],
263
+ },
264
+ ],
265
+ });
266
+ ```
267
+
268
+ ## Diamond dependencies
269
+
270
+ Steps shared across multiple dependency chains are deduplicated and
271
+ topologically sorted:
272
+
273
+ ```ts
274
+ const checkout = step({ name: "Checkout", uses: "actions/checkout@v6" });
275
+ const buildA = step({ name: "Build A", run: "make a" }).dependsOn(checkout);
276
+ const buildB = step({ name: "Build B", run: "make b" }).dependsOn(checkout);
277
+ const integrate = step({ name: "Integrate", run: "make all" }).dependsOn(
278
+ buildA,
279
+ buildB,
280
+ );
281
+
282
+ createWorkflow({
283
+ ...,
284
+ jobs: [
285
+ { id: "ci", runsOn: "ubuntu-latest", steps: [integrate] },
286
+ ],
287
+ });
288
+ // resolves to: checkout → buildA → buildB → integrate
289
+ // checkout appears only once
290
+ ```
291
+
292
+ ## Ordering constraints
293
+
294
+ Use `comesAfter()` to control step ordering without creating a dependency.
295
+ Unlike `dependsOn()`, this does not pull in the step — it only ensures ordering
296
+ when both steps are present in the same job:
297
+
298
+ ```ts
299
+ const setupDeno = step({
300
+ uses: "denoland/setup-deno@v2",
301
+ with: { "deno-version": "canary" },
302
+ });
303
+
304
+ const checkout = step({ uses: "actions/checkout@v6" })
305
+ // ensure checkout runs after setupDeno, without making checkout depend on it
306
+ .comesAfter(setupDeno);
307
+ const build = step({ run: "cargo build" }).dependsOn(checkout);
308
+ const lint = step({ run: "deno lint" }).dependsOn(setupDeno, checkout);
309
+
310
+ createWorkflow({
311
+ ...,
312
+ jobs: [
313
+ { id: "ci", runsOn: "ubuntu-latest", steps: [build, lint] },
314
+ ],
315
+ });
316
+ // resolves to: setupDeno → checkout → build → lint
317
+ ```
318
+
319
+ If the constraint conflicts with existing dependencies (creating a cycle), the
320
+ library throws with the cycle path:
321
+
322
+ ```
323
+ Error: Cycle detected in step ordering: A → B → A
324
+ ```
325
+
326
+ ## Typed matrix
327
+
328
+ `defineMatrix()` gives you typed access to matrix values:
329
+
330
+ ```ts
331
+ import { defineMatrix } from "jsr:@david/gagen@<version>";
332
+
333
+ const matrix = defineMatrix({
334
+ include: [
335
+ { os: "linux", runner: "ubuntu-latest" },
336
+ { os: "macos", runner: "macos-latest" },
337
+ ],
338
+ });
339
+
340
+ matrix.os; // ExpressionValue("matrix.os") — autocompletes
341
+ matrix.runner; // ExpressionValue("matrix.runner")
342
+ matrix.foo; // TypeScript error — not a matrix key
343
+
344
+ createWorkflow({
345
+ ...,
346
+ jobs: [
347
+ {
348
+ id: "build",
349
+ runsOn: matrix.runner,
350
+ strategy: { matrix },
351
+ steps: [test],
352
+ },
353
+ ],
354
+ });
355
+ ```
356
+
357
+ Also works with key-value arrays:
358
+
359
+ ```ts
360
+ const matrix = defineMatrix({
361
+ os: ["linux", "macos"],
362
+ node: [18, 20],
363
+ });
364
+
365
+ matrix.os.equals("linux"); // condition for use in step `if`
366
+ ```
367
+
368
+ ### Matrix exclude
369
+
370
+ Pass an `exclude` array to remove specific combinations:
371
+
372
+ ```ts
373
+ const matrix = defineMatrix({
374
+ os: ["linux", "macos"],
375
+ node: [18, 20],
376
+ exclude: [{ os: "macos", node: 18 }],
377
+ });
378
+
379
+ // matrix.os and matrix.node are still typed — exclude is not a key
380
+ ```
381
+
382
+ ## Permissions
383
+
384
+ Type-safe workflow and job permissions:
385
+
386
+ ```ts
387
+ import { createWorkflow } from "jsr:@david/gagen@<version>";
388
+
389
+ const wf = createWorkflow({
390
+ name: "ci",
391
+ on: { push: { branches: ["main"] } },
392
+ permissions: { contents: "read", packages: "write" },
393
+ });
394
+
395
+ // or use a scalar value
396
+ const wf2 = createWorkflow({
397
+ name: "ci",
398
+ on: { push: { branches: ["main"] } },
399
+ permissions: "read-all",
400
+ });
401
+
402
+ // job-level permissions
403
+ createWorkflow({
404
+ ...,
405
+ jobs: [
406
+ {
407
+ id: "deploy",
408
+ runsOn: "ubuntu-latest",
409
+ permissions: { contents: "read", "id-token": "write" },
410
+ steps: [deploy],
411
+ },
412
+ ],
413
+ });
414
+ ```
415
+
416
+ Permission scopes (`contents`, `packages`, `id-token`, etc.) and levels (`read`,
417
+ `write`, `none`) are fully typed.
418
+
419
+ ## Reusable workflows
420
+
421
+ Define reusable workflows with `workflow_call` triggers and call them from other
422
+ jobs:
423
+
424
+ ```ts
425
+ import { createWorkflow } from "jsr:@david/gagen@<version>";
426
+
427
+ // define a reusable workflow
428
+ const wf = createWorkflow({
429
+ name: "Reusable Build",
430
+ on: {
431
+ workflow_call: {
432
+ inputs: {
433
+ environment: { type: "string", required: true },
434
+ debug: { type: "boolean", default: false },
435
+ },
436
+ outputs: {
437
+ build_id: {
438
+ description: "The build ID",
439
+ value: "${{ jobs.build.outputs.id }}",
440
+ },
441
+ },
442
+ secrets: {
443
+ deploy_key: { required: true },
444
+ },
445
+ },
446
+ },
447
+ });
448
+ ```
449
+
450
+ Call a reusable workflow from a job using `uses` instead of `runsOn`:
451
+
452
+ ```ts
453
+ const wf = createWorkflow({
454
+ name: "CI",
455
+ on: { push: { branches: ["main"] } },
456
+ jobs: [
457
+ {
458
+ id: "build",
459
+ uses: "octo-org/example/.github/workflows/build.yml@main",
460
+ with: { environment: "production" },
461
+ secrets: "inherit",
462
+ },
463
+ ],
464
+ });
465
+ ```
466
+
467
+ Reusable jobs support `with`, `secrets` (either `"inherit"` or a record), and
468
+ all common job fields (`name`, `needs`, `if`, `permissions`, `concurrency`).
469
+
470
+ ## Artifacts
471
+
472
+ Link upload and download artifact steps across jobs with automatic `needs`
473
+ inference:
474
+
475
+ ```ts
476
+ import {
477
+ createWorkflow,
478
+ defineArtifact,
479
+ step,
480
+ } from "jsr:@david/gagen@<version>";
481
+
482
+ const artifact = defineArtifact("build-output");
483
+
484
+ const buildStep = step({ name: "Build", run: "make build" });
485
+ const upload = artifact.upload({ path: "dist/", retentionDays: 5 });
486
+
487
+ const download = artifact.download({ path: "dist/" });
488
+ const deployStep = step({ name: "Deploy", run: "make deploy" }).dependsOn(
489
+ download,
490
+ );
491
+
492
+ const wf = createWorkflow({
493
+ name: "CI",
494
+ on: { push: { branches: ["main"] } },
495
+ jobs: [
496
+ { id: "build", runsOn: "ubuntu-latest", steps: [buildStep, upload] },
497
+ // needs: [build] is inferred automatically from the artifact link
498
+ { id: "deploy", runsOn: "ubuntu-latest", steps: [deployStep] },
499
+ ],
500
+ });
501
+ ```
502
+
503
+ The artifact version defaults to `v6` but can be configured:
504
+
505
+ ```ts
506
+ const artifact = defineArtifact("build-output", { version: "v3" });
507
+ ```
508
+
509
+ ## Job configuration
510
+
511
+ ```ts
512
+ const matrix = defineMatrix({
513
+ include: [
514
+ { os: "linux", runner: "ubuntu-latest" },
515
+ { os: "macos", runner: "macos-latest" },
516
+ ],
517
+ });
518
+
519
+ createWorkflow({
520
+ ...,
521
+ jobs: [
522
+ {
523
+ id: "build",
524
+ name: "Build",
525
+ runsOn: matrix.runner,
526
+ timeoutMinutes: 60,
527
+ defaults: { run: { shell: "bash" } },
528
+ env: { CARGO_TERM_COLOR: "always" },
529
+ permissions: { contents: "read" },
530
+ concurrency: { group: "build-${{ github.ref }}", cancelInProgress: true },
531
+ strategy: { matrix, failFast: true },
532
+ steps: [test],
533
+ },
534
+ ],
535
+ });
536
+ ```
537
+
538
+ ## Step configuration
539
+
540
+ ```ts
541
+ step({
542
+ name: "Deploy",
543
+ id: "deploy",
544
+ uses: "actions/deploy@v1",
545
+ with: { token: "${{ secrets.GITHUB_TOKEN }}" },
546
+ env: { NODE_ENV: "production" },
547
+ if: "github.ref == 'refs/heads/main'",
548
+ shell: "bash",
549
+ workingDirectory: "./app",
550
+ continueOnError: true,
551
+ timeoutMinutes: 10,
552
+ });
553
+ ```
@@ -0,0 +1,12 @@
1
+ declare global {
2
+ interface Object {
3
+ /**
4
+ * Determines whether an object has a property with the specified name.
5
+ * @param o An object.
6
+ * @param v A property name.
7
+ */
8
+ hasOwn(o: object, v: PropertyKey): boolean;
9
+ }
10
+ }
11
+ export {};
12
+ //# sourceMappingURL=_dnt.polyfills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dnt.polyfills.d.ts","sourceRoot":"","sources":["../src/_dnt.polyfills.ts"],"names":[],"mappings":"AAeA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd;;;;WAIG;QACH,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;KAC5C;CACF;AAED,OAAO,EAAE,CAAC"}
@@ -0,0 +1,15 @@
1
+ // https://github.com/tc39/proposal-accessible-object-hasownproperty/blob/main/polyfill.js
2
+ if (!Object.hasOwn) {
3
+ Object.defineProperty(Object, "hasOwn", {
4
+ value: function (object, property) {
5
+ if (object == null) {
6
+ throw new TypeError("Cannot convert undefined or null to object");
7
+ }
8
+ return Object.prototype.hasOwnProperty.call(Object(object), property);
9
+ },
10
+ configurable: true,
11
+ enumerable: false,
12
+ writable: true,
13
+ });
14
+ }
15
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dnt.test_shims.d.ts","sourceRoot":"","sources":["../src/_dnt.test_shims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAKvC,eAAO,MAAM,aAAa;;CAA2C,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { Step } from "./step.js";
2
+ export interface UploadConfig {
3
+ path: string;
4
+ retentionDays?: number;
5
+ }
6
+ export interface DownloadConfig {
7
+ path?: string;
8
+ }
9
+ export interface ArtifactOptions {
10
+ version?: string;
11
+ }
12
+ export declare class Artifact {
13
+ #private;
14
+ readonly name: string;
15
+ constructor(name: string, options?: ArtifactOptions);
16
+ upload(config: UploadConfig): Step;
17
+ download(config?: DownloadConfig): Step;
18
+ }
19
+ export declare function defineArtifact(name: string, options?: ArtifactOptions): Artifact;
20
+ //# sourceMappingURL=artifact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact.d.ts","sourceRoot":"","sources":["../src/artifact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,QAAQ;;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAIV,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe;IAKnD,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAgBlC,QAAQ,CAAC,MAAM,GAAE,cAAmB,GAAG,IAAI;CAgB5C;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,eAAe,GACxB,QAAQ,CAEV"}
@@ -0,0 +1,62 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _Artifact_version, _Artifact_uploadStep;
13
+ import { Step } from "./step.js";
14
+ export class Artifact {
15
+ constructor(name, options) {
16
+ Object.defineProperty(this, "name", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: void 0
21
+ });
22
+ _Artifact_version.set(this, void 0);
23
+ _Artifact_uploadStep.set(this, void 0);
24
+ this.name = name;
25
+ __classPrivateFieldSet(this, _Artifact_version, options?.version ?? "v6", "f");
26
+ }
27
+ upload(config) {
28
+ const withObj = {
29
+ name: this.name,
30
+ path: config.path,
31
+ };
32
+ if (config.retentionDays != null) {
33
+ withObj["retention-days"] = config.retentionDays;
34
+ }
35
+ const s = new Step({
36
+ uses: `actions/upload-artifact@${__classPrivateFieldGet(this, _Artifact_version, "f")}`,
37
+ with: withObj,
38
+ });
39
+ __classPrivateFieldSet(this, _Artifact_uploadStep, s, "f");
40
+ return s;
41
+ }
42
+ download(config = {}) {
43
+ const withObj = {
44
+ name: this.name,
45
+ };
46
+ if (config.path != null) {
47
+ withObj.path = config.path;
48
+ }
49
+ const s = new Step({
50
+ uses: `actions/download-artifact@${__classPrivateFieldGet(this, _Artifact_version, "f")}`,
51
+ with: withObj,
52
+ });
53
+ if (__classPrivateFieldGet(this, _Artifact_uploadStep, "f")) {
54
+ s._crossJobDeps.push(__classPrivateFieldGet(this, _Artifact_uploadStep, "f"));
55
+ }
56
+ return s;
57
+ }
58
+ }
59
+ _Artifact_version = new WeakMap(), _Artifact_uploadStep = new WeakMap();
60
+ export function defineArtifact(name, options) {
61
+ return new Artifact(name, options);
62
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"almost_equals.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.18/almost_equals.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,MAAM,QAmBb"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"array_includes.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.18/array_includes.ts"],"names":[],"mappings":"AAMA,0FAA0F;AAC1F,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;AAOpD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EACvB,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,EACzB,GAAG,CAAC,EAAE,MAAM,GACX,IAAI,CAgCN"}