Make the generated action references configurable#535
Conversation
ITaluone
left a comment
There was a problem hiding this comment.
In general: LGTM
Just this one question, tho
|
ChrisonSimtian
left a comment
There was a problem hiding this comment.
I love it, thanks for throwing this in "last minute", I think this is a good addition for the upcoming 10.4 release
| { | ||
| GitHubActionsActionReference.Resolve(defaultAction, value); | ||
| } | ||
| catch (ArgumentException exception) |
There was a problem hiding this comment.
🤔 When is this exception thrown? GitHubActionsActionReference.Resolve doesn't seem to throw it.
There was a problem hiding this comment.
Indirectly — Assert.True throws ArgumentException (src/Fallout.Utilities/Assert.cs:34), so Resolve did throw it, just never visibly. Fair to call that out: catching a type a helper only throws transitively is fragile, and if Assert ever changed its exception the property name would silently vanish from the message.
Removed the try/catch. Resolve now takes an origin describing what carries the value, so the message is built correctly the first time instead of being caught and re-wrapped:
GitHubActionsActionReference.Resolve(defaultAction, value, $"'{property}' in workflow '{name}'");→ 'CacheAction' in workflow 'build': action reference 'actions/cache' is ambiguous — write 'owner/repo@ref' …
| attribute.GetConfiguration(relevantTargets); | ||
| } | ||
|
|
||
| private static string Resolve(string value) |
There was a problem hiding this comment.
🔧 (requires change) Keep this inline as using this private method obscures the test.
There was a problem hiding this comment.
Done — inlined. Each test now passes the default explicitly, which is the part the helper was hiding (the assertions are all about resolving against actions/checkout):
var reference = GitHubActionsActionReference.Resolve(GitHubActionsDefaults.CheckoutAction, value, Origin);
reference.Should().Be("actions/checkout@v8");| act.Should().NotThrow(); | ||
| } | ||
|
|
||
| private static void Generate(Action<TestGitHubActionsAttribute> configure) |
There was a problem hiding this comment.
🔧 This method should either get a much better name, replaced with a Test Data Builder or just copy-pasted in each test. Right now, it makes the tests hard to udnerstand.
There was a problem hiding this comment.
Renamed to GenerateWorkflow, with the parameter as configureAttribute and the lambda arg spelled out, so the call sites now say what they do:
var act = () => GenerateWorkflow(attribute => attribute.CheckoutAction = "releases/v1");Added a summary explaining why it exists — generation is what triggers validation, which is the whole point of these particular specs. Kept it as a method rather than a builder: it takes one argument and every spec uses it the same way, so a builder would be ceremony without a second axis to vary.
dennisdoomen
left a comment
There was a problem hiding this comment.
🔧 You now have 4 commits, of which four are related and caused by rework and one is completely unrelated. Either squash the thre so we can keep the final two for a clean source control history, or split the PR into two please.
| /// <summary> | ||
| /// Resolves a user-supplied action override against the version the generator pins by default. | ||
| /// </summary> | ||
| internal static class GitHubActionsActionReference |
There was a problem hiding this comment.
⛏️Please reformat the file so it aligns with our new standards
There was a problem hiding this comment.
Found it: the three files I created had LF endings, while .editorconfig sets end_of_line = crlf. Files I edited in place kept CRLF, so only the new ones were off — which is why the whole file reads as non-conforming. Converted all three.
dotnet format --verify-no-changes over CI/GitHubActions/ now reports zero ENDOFLINE errors (was 123).
Deliberately did not apply the rest of what dotnet format wants: it also flags 78 WHITESPACE errors in this folder, but untouched folders (CI/AzurePipelines, CI/AppVeyor) have 85 of the same, and they're all the aligned object-initializer style the repo uses everywhere (resharper_wrap_object_and_collection_initializer_style = chop_always). Reformatting to dotnet format defaults would make these files inconsistent with the rest of the repo, so I matched the surrounding code instead. Say the word if the new standard is in fact dotnet format and you'd like it applied repo-wide in a separate PR.
| return comment == null ? reference : $"{reference} {comment}"; | ||
| } | ||
|
|
||
| private static string ResolveActionReference(string defaultAction, string reference) |
There was a problem hiding this comment.
🔧 I like to see some docs on all private methods explaining their purpose.
There was a problem hiding this comment.
Added <summary> docs to all three privates — ResolveMarketplaceReference (also renamed from ResolveActionReference, which said nothing the type name didn't), GetActionName, and SplitComment. Each says what it's for rather than restating the code; the inline // notes that explain why a rule exists are kept alongside.
|
|
||
| public class GitHubActionsArtifactStep : GitHubActionsStep | ||
| { | ||
| private string uses = GitHubActionsDefaults.UploadArtifactAction; |
There was a problem hiding this comment.
♻️ You can use the new field keyword. My IDE directly told me about that. Same for the other new steps.
There was a problem hiding this comment.
Done, on all four steps — the explicit backing fields are gone:
public string Uses
{
get;
set => field = GitHubActionsActionReference.Resolve(
GitHubActionsDefaults.UploadArtifactAction, value, $"{nameof(GitHubActionsArtifactStep)}.{nameof(Uses)}");
} = GitHubActionsDefaults.UploadArtifactAction;LangVersion is preview so it compiles clean, and the property initializer replaces the field initializer that carried the default.
The generator's cache pin sat at v4 while Fallout-build#533 moved checkout, setup-dotnet, and upload-artifact to their current majors — cache was never in that PR's scope. v6.1.0 is current; v4 is two majors behind. - v5.0.0 moved to the node24 runtime and requires a self-hosted runner >= 2.327.1 - v6.0.0 updated dependencies and migrated to ESM The `path:` and `key:` inputs the generator emits are unchanged across both, so the emitted step needs no other edits. Covers the generated workflows, the hand-written publish-packages-* ones, the 14 Verify snapshots with a cache step, and the docs sample. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vn8JQZRwdoovPuctD21Wjx
The generator hard-coded the four actions it emits, so a consumer who wanted a newer major — or a SHA pin — had to subclass GitHubActionsAttribute and reimplement step emission. Two repos migrating off NUKE carried custom step classes for exactly that (Fallout-build#533). Each emitting step now carries a normalizing property, forwarded from four new attribute properties: CheckoutAction, CacheAction, SetupDotNetAction, UploadArtifactAction A trailing '# comment' is split off before the value is classified, so a '/' or '@' inside it never sways the decision — that is what makes the SHA-pinning idiom work. What remains is a complete reference when it contains an '@', emitted verbatim; otherwise a bare ref appended to the pinned action, so "v8" becomes actions/checkout@v8. A ref that itself contains a '/' reads exactly like an owner/repo, so it takes a leading '@' — "@releases/v1" — and the ambiguous bare form is rejected rather than guessed at. Null or whitespace restores the default. The reference token must carry no whitespace: it is emitted into an unquoted YAML scalar, where a stray ': ' would corrupt the whole workflow file. All four are resolved up-front in GetConfiguration. The cache and artifact steps are conditional, so their overrides would otherwise go unvalidated — and silently ignored — whenever caching or publishing is off, surfacing the typo only when the step was re-enabled. Rejections name the property and workflow that carry the value, so the message points at the declaration to fix. Resolution lives in the step setters, so subclasses and hand-built steps get the same shorthand and validation. GitHubActionsCustomStep.Uses has no default to resolve against and is emitted verbatim, so it must now name its action in full — otherwise the shorthand documented for the built-in steps silently produced an unusable 'uses: v8', and a multi-line value could inject arbitrary keys into the generated workflow. GitHubActionsDefaults is internal: the properties take a plain string, so nothing a consumer writes needs the type. That also keeps it out of the Nuke.* transition shims, which mirror public types but cannot carry const members and would have exposed an empty class. Closes Fallout-build#534 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vn8JQZRwdoovPuctD21Wjx
6b8f93f to
a1959d1
Compare
|
All six comments addressed, and the history is restructured. Rebased onto latest Two commits now, as asked — the three rework commits are squashed into one, leaving the unrelated bump on its own:
Happy to split these into two PRs instead if you'd rather; they're independent, the bump just happened to surface while writing the feature. Changes since review
One note for whoever merges: the PR still has no labels — I don't have permission to add them, so it needs |
The generator hard-coded the four actions it emits, so a consumer who wants a newer major — or a SHA pin — has to subclass
GitHubActionsAttributeand reimplement step emission. #533 bumped three of them; this makes all four overridable so lagging a release behind never blocks anyone.Closes #534.
Attribute properties
CheckoutAction,CacheAction,SetupDotNetAction,UploadArtifactAction.A value containing an
@is a complete reference, emitted as-is; anything else is a bare ref appended to the default action:The trailing
# commentis split off before the value is classified, so a slash or an@inside it changes nothing — that's what makes the SHA-pinning idiom work. A ref that itself contains a/(a branch likereleases/v1) reads exactly like anowner/repo, so it takes a leading@—"@releases/v1"— and the ambiguous bare form is rejected rather than guessed at. Null or whitespace restores the default.The reference token must carry no whitespace: emitted into an unquoted
uses:scalar, a stray:would otherwise corrupt the whole workflow file. All four are resolved up-front inGetConfiguration, so an override is validated even when its step isn't emitted (caching off, artifacts disabled) — errors name the property and the workflow.Resolution lives in the step setters, so subclasses and hand-built steps get the same shorthand and validation.
GitHubActionsCustomStep.Useshas no default to resolve against, so it now must name its action in full instead of silently emitting an unusableuses: v8.GitHubActionsDefaultsis internal — the properties take a plain string, so nothing a consumer writes needs the type.actions/cachev4 → v6 (first commit)Cache was never in #533's scope and sat two majors behind. Separate commit; the diff touches only
actions/cache@v4→@v6lines.The
path:/key:inputs are unchanged. Consumers on older self-hosted runners setCacheAction = "v4"— the feature demonstrating itself.Verification
dotnet build fallout.slnxclean;dotnet test tests/Fallout.Common.Specs— 168 passed, 0 failedaction-overridescovers all four properties in one workflow, one per accepted formGitHubActionsActionReferenceSpecscovers the resolver: fallback, bare ref,@-prefixed slash-bearing ref, SHA + comment, comment containing a URL, complete reference, fork, local/container refs, the ambiguous form, comment-only, and YAML-corrupting values — plus validation of a skipped step's overrideGitHubActionsDefaultsintoNuke.*as an empty class (it forwards static methods, never consts)CheckoutAction = "v8"and aCacheActionoverride on this repo'sbuildworkflow, regenerated, confirmed both lines changed, reverted — regeneration with no overrides leaves.github/workflowsuntouchedNot in scope
ITaluone's original "heads-up when a new action release ships" — worth a follow-up issue; this removes the urgency but not the need.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Vn8JQZRwdoovPuctD21Wjx