87 |
88 |
89 |
90 |
91 |
92 |
93 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
94 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | david.pine.7@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/src/Actions.HttpClient/Clients/DefaultHttpClient.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) David Pine. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | #pragma warning disable IDE0130 // Namespace does not match folder structure
5 | namespace Actions.HttpClient;
6 | #pragma warning restore IDE0130 // Namespace does not match folder structure
7 |
8 | internal sealed class DefaultHttpClient(NetClient client, IRequestHandler? requestHandler = null) : IHttpClient
9 | {
10 | private static async ValueTask> ProcessResponseAsync(
11 | HttpResponseMessage response,
12 | JsonTypeInfo jsonTypeInfo,
13 | CancellationToken cancellationToken = default)
14 | {
15 | TypedResponse typedResponse = new(response.StatusCode)
16 | {
17 | ResponseHttpHeaders = response.Headers
18 | };
19 |
20 | if (response.IsSuccessStatusCode)
21 | {
22 | var result = await response.Content.ReadFromJsonAsync(jsonTypeInfo, cancellationToken);
23 |
24 | typedResponse = typedResponse with
25 | {
26 | Result = result,
27 | };
28 | }
29 |
30 | return typedResponse;
31 | }
32 |
33 | async ValueTask IHttpClient.DeleteAsync(
34 | string requestUri,
35 | Dictionary>? additionalHeaders,
36 | CancellationToken cancellationToken)
37 | {
38 | using var request = PrepareRequest(requestUri, HttpMethod.Delete, additionalHeaders);
39 |
40 | return await client.SendAsync(request, cancellationToken);
41 | }
42 |
43 | async ValueTask> IHttpClient.GetAsync(
44 | string requestUri,
45 | JsonTypeInfo jsonTypeInfo,
46 | Dictionary>? additionalHeaders,
47 | CancellationToken cancellationToken)
48 | {
49 | using var request = PrepareRequest(requestUri, HttpMethod.Get, additionalHeaders);
50 |
51 | var response = await client.SendAsync(request, cancellationToken);
52 |
53 | return await ProcessResponseAsync(response, jsonTypeInfo, cancellationToken);
54 | }
55 |
56 | async ValueTask IHttpClient.OptionsAsync(
57 | string requestUri,
58 | Dictionary>? additionalHeaders,
59 | CancellationToken cancellationToken)
60 | {
61 | using var request = PrepareRequest(requestUri, HttpMethod.Options, additionalHeaders);
62 |
63 | return await client.SendAsync(request, cancellationToken);
64 | }
65 |
66 | ValueTask> IHttpClient.PatchAsync(
67 | string requestUri,
68 | TData data,
69 | JsonTypeInfo dataJsonTypeInfo,
70 | JsonTypeInfo resultJsonTypeInfo,
71 | Dictionary>? additionalHeaders,
72 | CancellationToken cancellationToken)
73 | {
74 | return RequestAsync(requestUri, HttpMethod.Patch, data, dataJsonTypeInfo, resultJsonTypeInfo, additionalHeaders, cancellationToken);
75 | }
76 |
77 | ValueTask> IHttpClient.PostAsync(
78 | string requestUri,
79 | TData data,
80 | JsonTypeInfo dataJsonTypeInfo,
81 | JsonTypeInfo resultJsonTypeInfo,
82 | Dictionary>? additionalHeaders,
83 | CancellationToken cancellationToken)
84 | {
85 | return RequestAsync(requestUri, HttpMethod.Post, data, dataJsonTypeInfo, resultJsonTypeInfo, additionalHeaders, cancellationToken);
86 | }
87 |
88 | ValueTask> IHttpClient.PutAsync(
89 | string requestUri,
90 | TData data,
91 | JsonTypeInfo dataJsonTypeInfo,
92 | JsonTypeInfo resultJsonTypeInfo,
93 | Dictionary>? additionalHeaders,
94 | CancellationToken cancellationToken)
95 | {
96 | return RequestAsync(requestUri, HttpMethod.Put, data, dataJsonTypeInfo, resultJsonTypeInfo, additionalHeaders, cancellationToken);
97 | }
98 |
99 | async ValueTask IHttpClient.HeadAsync(
100 | string requestUri,
101 | Dictionary>? additionalHeaders,
102 | CancellationToken cancellationToken)
103 | {
104 | using var request = PrepareRequest(requestUri, HttpMethod.Head, additionalHeaders);
105 |
106 | return await client.SendAsync(request, cancellationToken);
107 | }
108 |
109 | private async ValueTask> RequestAsync(
110 | string requestUri,
111 | HttpMethod method,
112 | TData data,
113 | JsonTypeInfo dataJsonTypeInfo,
114 | JsonTypeInfo resultJsonTypeInfo,
115 | Dictionary>? headers,
116 | CancellationToken cancellationToken)
117 | {
118 | using var request = PrepareRequest(requestUri, method, headers);
119 |
120 | using var content = GetRequestJsonContent(data, dataJsonTypeInfo);
121 |
122 | request.Content = content;
123 |
124 | using var response = await client.SendAsync(request, cancellationToken);
125 |
126 | return await ProcessResponseAsync(response, resultJsonTypeInfo, cancellationToken);
127 | }
128 |
129 | private HttpRequestMessage PrepareRequest(
130 | string requestUri,
131 | HttpMethod method,
132 | Dictionary>? headers)
133 | {
134 | var request = new HttpRequestMessage(
135 | method,
136 | requestUri);
137 |
138 | var requestHeaders =
139 | requestHandler?.PrepareRequestHeaders(headers ?? []);
140 |
141 | foreach (var (headerKey, headerValues) in requestHeaders ?? [])
142 | {
143 | request.Headers.Add(headerKey, headerValues);
144 | }
145 |
146 | return request;
147 | }
148 |
149 | private static StringContent GetRequestJsonContent(
150 | T data,
151 | JsonTypeInfo jsonTypeInfo)
152 | {
153 | var json = JsonSerializer.Serialize(data, jsonTypeInfo);
154 |
155 | return new StringContent(
156 | content: json,
157 | encoding: Encoding.UTF8,
158 | mediaType: "application/json");
159 | }
160 |
161 | void IDisposable.Dispose()
162 | {
163 | client?.Dispose();
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/Actions.Core/README.md:
--------------------------------------------------------------------------------
1 | # `GitHub.Actions.Core` package
2 |
3 | To install the [`GitHub.Actions.Core`](https://www.nuget.org/packages/GitHub.Actions.Core) NuGet package:
4 |
5 | ```xml
6 |
7 | ```
8 |
9 | Or use the [`dotnet add package`](https://learn.microsoft.com/dotnet/core/tools/dotnet-add-package) .NET CLI command:
10 |
11 | ```bash
12 | dotnet add package GitHub.Actions.Core
13 | ```
14 |
15 | ## Get the `ICoreService` instance
16 |
17 | To use the `ICoreService` in your .NET project, register the services with an `IServiceCollection` instance by calling `AddGitHubActionsCore` and then your consuming code can require the `ICoreService` via constructor dependency injection.
18 |
19 | ```csharp
20 | using Microsoft.Extensions.DependencyInjection;
21 | using Actions.Core;
22 | using Actions.Core.Extensions;
23 |
24 | using var provider = new ServiceCollection()
25 | .AddGitHubActionsCore()
26 | .BuildServiceProvider();
27 |
28 | var core = provider.GetRequiredService();
29 | ```
30 |
31 | ## `GitHub.Actions.Core`
32 |
33 | This was modified, but borrowed from the [_core/README.md_](https://github.com/actions/toolkit/blob/main/packages/core/README.md).
34 |
35 | > Core functions for setting results, logging, registering secrets and exporting variables across actions
36 |
37 | ### Using declarations
38 |
39 | ```csharp
40 | global using Actions.Core;
41 | ```
42 |
43 | #### Inputs/Outputs
44 |
45 | Action inputs can be read with `GetInput` which returns a `string` or `GetBoolInput` which parses a `bool` based on the [yaml 1.2 specification](https://yaml.org/spec/1.2/spec.html#id2804923). If `required` is `false`, the input should have a default value in `action.yml`.
46 |
47 | Outputs can be set with `SetOutputAsync` which makes them available to be mapped into inputs of other actions to ensure they are decoupled.
48 |
49 | ```csharp
50 | var myInput = core.GetInput("inputName", new InputOptions(true));
51 | var myBoolInput = core.GetBoolInput("boolInputName", new InputOptions(true));
52 | var myMultilineInput = core.GetMultilineInput("multilineInputName", new InputOptions(true));
53 | await core.SetOutputAsync("outputKey", "outputVal");
54 | ```
55 |
56 | #### Exporting variables
57 |
58 | Since each step runs in a separate process, you can use `ExportVariableAsync` to add it to this step and future steps environment blocks.
59 |
60 | ```csharp
61 | await core.ExportVariableAsync("envVar", "Val");
62 | ```
63 |
64 | #### Setting a secret
65 |
66 | Setting a secret registers the secret with the runner to ensure it is masked in logs.
67 |
68 | ```csharp
69 | core.SetSecret("myPassword");
70 | ```
71 |
72 | #### PATH manipulation
73 |
74 | To make a tool's path available in the path for the remainder of the job (without altering the machine or containers state), use `AddPathAsync`. The runner will prepend the path given to the jobs PATH.
75 |
76 | ```csharp
77 | await core.AddPathAsync("/path/to/mytool");
78 | ```
79 |
80 | #### Exit codes
81 |
82 | You should use this library to set the failing exit code for your action. If status is not set and the script runs to completion, that will lead to a success.
83 |
84 | ```csharp
85 | using var provider = new ServiceCollection()
86 | .AddGitHubActions()
87 | .BuildServiceProvider();
88 |
89 | var core = provider.GetRequiredService();
90 |
91 | try
92 | {
93 | // Do stuff
94 | }
95 | catch (Exception ex)
96 | {
97 | // SetFailed logs the message and sets a failing exit code
98 | core.SetFailed($"Action failed with error {ex}"");
99 | }
100 | ```
101 |
102 | #### Logging
103 |
104 | Finally, this library provides some utilities for logging. Note that debug logging is hidden from the logs by default. This behavior can be toggled by enabling the [Step Debug Logs](../../docs/action-debugging.md#step-debug-logs).
105 |
106 | ```csharp
107 | using var provider = new ServiceCollection()
108 | .AddGitHubActions()
109 | .BuildServiceProvider();
110 |
111 | var core = provider.GetRequiredService();
112 |
113 | var myInput = core.GetInput("input");
114 | try
115 | {
116 | core.Debug("Inside try block");
117 |
118 | if (!myInput)
119 | {
120 | core.Warning("myInput was not set");
121 | }
122 |
123 | if (core.IsDebug)
124 | {
125 | // curl -v https://github.com
126 | }
127 | else
128 | {
129 | // curl https://github.com
130 | }
131 |
132 | // Do stuff
133 | core.Info("Output to the actions build log");
134 |
135 | core.Notice("This is a message that will also emit an annotation");
136 | }
137 | catch (Exception ex)
138 | {
139 | core.Error($"Error {ex}, action may still succeed though");
140 | }
141 | ```
142 |
143 | This library can also wrap chunks of output in foldable groups.
144 |
145 | ```csharp
146 | using var provider = new ServiceCollection()
147 | .AddGitHubActions()
148 | .BuildServiceProvider();
149 |
150 | var core = provider.GetRequiredService();
151 |
152 | // Manually wrap output
153 | core.StartGroup("Do some function");
154 | SomeFunction();
155 | core.EndGroup();
156 |
157 | // Wrap an asynchronous function call
158 | var result = await core.GroupAsync("Do something async", async () =>
159 | {
160 | var response = await MakeHttpRequestAsync();
161 | return response
162 | });
163 | ```
164 |
165 | #### Styling output
166 |
167 | Colored output is supported in the Action logs via standard [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code). 3/4 bit, 8 bit and 24 bit colors are all supported.
168 |
169 | Foreground colors:
170 |
171 | ```csharp
172 | // 3/4 bit
173 | core.Info("\u001b[35mThis foreground will be magenta");
174 |
175 | // 8 bit
176 | core.Info("\u001b[38;5;6mThis foreground will be cyan");
177 |
178 | // 24 bit
179 | core.Info("\u001b[38;2;255;0;0mThis foreground will be bright red");
180 | ```
181 |
182 | Background colors:
183 |
184 | ```csharp
185 | // 3/4 bit
186 | core.Info("\u001b[43mThis background will be yellow");
187 |
188 | // 8 bit
189 | core.Info("\u001b[48;5;6mThis background will be cyan");
190 |
191 | // 24 bit
192 | core.Info("\u001b[48;2;255;0;0mThis background will be bright red");
193 | ```
194 |
195 | Special styles:
196 |
197 | ```csharp
198 | core.Info("\u001b[1mBold text");
199 | core.Info("\u001b[3mItalic text");
200 | core.Info("\u001b[4mUnderlined text");
201 | ```
202 |
203 | ANSI escape codes can be combined with one another:
204 |
205 | ```csharp
206 | core.Info("\u001b[31;46mRed foreground with a cyan background and \u001b[1mbold text at the end");
207 | ```
208 |
209 | > [!NOTE]
210 | > Escape codes reset at the start of each line.
211 |
212 | ```csharp
213 | core.Info("\u001b[35mThis foreground will be magenta");
214 | core.Info("This foreground will reset to the default");
215 | ```
216 |
--------------------------------------------------------------------------------
/src/Actions.Octokit/Context.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) David Pine. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using Issue = Actions.Octokit.Common.Issue;
5 | using Repository = Actions.Octokit.Common.Repository;
6 |
7 | namespace Actions.Octokit;
8 |
9 | ///
10 | /// Provides access to the GitHub Actions context.
11 | ///
12 | public sealed class Context
13 | {
14 | private static readonly Lazy s_context = new(() => new());
15 |
16 | ///
17 | /// Gets the current GitHub Actions context.
18 | ///
19 | public static Context Current => s_context.Value;
20 |
21 | ///
22 | /// Gets webhook payload in context.
23 | ///
24 | public WebhookPayload? Payload { get; }
25 |
26 | ///
27 | /// Gets event name in context.
28 | ///
29 | public string? EventName { get; }
30 |
31 | ///
32 | /// Gets the SHA in context.
33 | ///
34 | public string? Sha { get; }
35 |
36 | ///
37 | /// Gets the REF in context.
38 | ///
39 | public string? Ref { get; }
40 |
41 | ///
42 | /// Gets the workflow in context.
43 | ///
44 | public string? Workflow { get; }
45 |
46 | ///
47 | /// Gets the action in context.
48 | ///
49 | public string? Action { get; }
50 |
51 | ///
52 | /// Gets the actor in context.
53 | ///
54 | public string? Actor { get; }
55 |
56 | ///
57 | /// Gets the job in context.
58 | ///
59 | public string? Job { get; }
60 |
61 | ///
62 | /// Gets the run attempt number in context.
63 | ///
64 | public long RunAttempt { get; }
65 |
66 | ///
67 | /// Gets the run number in context.
68 | ///
69 | public long RunNumber { get; }
70 |
71 | ///
72 | /// Gets the run id in context.
73 | ///
74 | public long RunId { get; }
75 |
76 | ///
77 | /// Gets the API URL in context.
78 | ///
79 | public string ApiUrl { get; }
80 |
81 | ///
82 | /// Gets the server URL in context.
83 | ///
84 | public string ServerUrl { get; }
85 |
86 | ///
87 | /// Gets the GraphQL URL in context.
88 | ///
89 | public string GraphQlUrl { get; }
90 |
91 | ///
92 | /// Gets the repo in context.
93 | ///
94 | public Repository Repo { get; }
95 |
96 | ///
97 | /// Gets the issue in context.
98 | ///
99 | public Issue Issue { get; }
100 |
101 | ///
102 | /// For testing purposes only!
103 | /// The , , and
104 | /// are the only objects that are
105 | /// deserialized when using this approach. All other members are
106 | /// their value.
107 | ///
108 | public static Context? FromJson(
109 | [StringSyntax(StringSyntaxAttribute.Json)] string? json)
110 | {
111 | return json switch
112 | {
113 | null or { Length: 0 } => null,
114 |
115 | _ => JsonSerializer.Deserialize(
116 | json,
117 | SourceGenerationContexts.Default.Context)
118 | };
119 | }
120 |
121 | [JsonConstructor]
122 | internal Context(WebhookPayload? payload)
123 | {
124 | ApiUrl = "https://api.github.com";
125 | ServerUrl = "https://github.com";
126 | GraphQlUrl = "https://api.github.com/graphql";
127 |
128 | Payload = payload;
129 |
130 | if (Payload is { Repository: { } })
131 | {
132 | Issue = new(
133 | Payload.Repository.Owner.Login,
134 | Payload.Repository.Name,
135 | Payload.Issue?.Number ??
136 | Payload.PullRequest?.Number ?? 0);
137 |
138 | Repo = new Repository(
139 | Payload.Repository.Owner.Login,
140 | Payload.Repository.Name);
141 | }
142 | }
143 |
144 | private Context()
145 | {
146 | var eventPath = GetEnvironmentVariable(GITHUB_EVENT_PATH);
147 | if (!string.IsNullOrWhiteSpace(eventPath) && File.Exists(eventPath))
148 | {
149 | var json = File.ReadAllText(eventPath, Encoding.UTF8);
150 |
151 | Payload = JsonSerializer.Deserialize(
152 | json, SourceGenerationContexts.Default.WebhookPayload)!;
153 | }
154 | else
155 | {
156 | Console.WriteLine($"GITHUB_EVENT_PATH ${eventPath} does not exist");
157 | }
158 |
159 | EventName = GetEnvironmentVariable(GITHUB_EVENT_NAME);
160 | Sha = GetEnvironmentVariable(GITHUB_SHA);
161 | Ref = GetEnvironmentVariable(GITHUB_REF);
162 | Workflow = GetEnvironmentVariable(GITHUB_WORKFLOW);
163 | Action = GetEnvironmentVariable(GITHUB_ACTION);
164 | Actor = GetEnvironmentVariable(GITHUB_ACTOR);
165 | Job = GetEnvironmentVariable(GITHUB_JOB);
166 | RunAttempt = long.TryParse(GetEnvironmentVariable(GITHUB_RUN_ATTEMPT), out var attempt) ? attempt : 10;
167 | RunNumber = long.TryParse(GetEnvironmentVariable(GITHUB_RUN_NUMBER), out var number) ? number : 10;
168 | RunId = long.TryParse(GetEnvironmentVariable(GITHUB_RUN_ID), out var id) ? id : 10;
169 | ApiUrl = GetEnvironmentVariable(GITHUB_API_URL) ?? "https://api.github.com";
170 | ServerUrl = GetEnvironmentVariable(GITHUB_SERVER_URL) ?? "https://github.com";
171 | GraphQlUrl = GetEnvironmentVariable(GITHUB_GRAPHQL_URL) ?? "https://api.github.com/graphql";
172 |
173 | if (Payload is { Repository: { } })
174 | {
175 | Issue = new(
176 | Payload.Repository.Owner.Login,
177 | Payload.Repository.Name,
178 | Payload.Issue?.Number ??
179 | Payload.PullRequest?.Number ?? 0);
180 | }
181 |
182 | var repository = GetEnvironmentVariable(GITHUB_REPOSITORY);
183 | if (!string.IsNullOrWhiteSpace(repository))
184 | {
185 | var parts = repository.Split('/');
186 | if (parts is { Length: 2 })
187 | {
188 | Repo = new Repository(parts[0], parts[1]);
189 | }
190 | }
191 | else if (Payload is { Repository: { } })
192 | {
193 | Repo = new Repository(
194 | Payload.Repository.Owner.Login,
195 | Payload.Repository.Name);
196 | }
197 | }
198 |
199 | ///
200 | /// Gets a JSON representation of the current context.
201 | ///
202 | public override string ToString()
203 | {
204 | return JsonSerializer.Serialize(
205 | this,
206 | SourceGenerationContexts.Default.Context);
207 | }
208 | }
209 |
--------------------------------------------------------------------------------