Skip to content

Module: GitHub#28

Merged
jfevia merged 9 commits into
mainfrom
feature/github-module
Jun 8, 2026
Merged

Module: GitHub#28
jfevia merged 9 commits into
mainfrom
feature/github-module

Conversation

@jfevia

@jfevia jfevia commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Status: Draft / work-in-progress. Initial scaffold only.

Goal

Add a new Modules.GitHub.Actions module that exposes the full GitHub REST API as Power Automate Desktop actions, generated at build time from the live github/rest-api-description OpenAPI document. The module lives alongside Modules.GitHub.Copilot.Actions and follows the same packaging conventions as every other module under modules/.

What is in this commit

  • modules/Modules.GitHub.Actions/ skeleton (csproj, ErrorCodes.cs, Properties/Resources.{resx,Designer.cs}).
  • Microsoft.OpenApi 3.6.0 added to Directory.Packages.props. The module references it with PrivateAssets="all" ExcludeAssets="runtime" and GeneratePathProperty="true", so the parser is available to T4 templates at build time but is never copied to the module's bin/ and never ends up in the CAB.
  • .tools/update-github-openapi.ps1 fetches the spec into modules/Modules.GitHub.Actions/openapi.json (git-ignored).
  • PowerAutomate.Desktop.sln deleted; only PowerAutomate.Desktop.slnx is kept. CI / PR / nightly workflows already switched to .slnx.
  • The new project is registered under the /Modules/ folder in .slnx.

Verified locally:

> dotnet build PowerAutomate.Desktop.slnx -c Debug
Build succeeded. 1 Warning (pre-existing PetStore). 0 Errors.

> ls modules/Modules.GitHub.Actions/bin/Debug/
Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK.dll
Newtonsoft.Json.dll
PowerAutomate.Desktop.Modules.GitHub.Actions.dll   (7.5 KB stub)

No Microsoft.OpenApi.* (or any other generator) DLL lands in bin/. Existing test suite (8/8) passes.

What is coming in this PR (subsequent commits)

  • Login action + three ActionSelectors (LoginWithPersonalAccessToken, LoginWithDeviceFlow, LoginWithGitHubCli), producing a custom GitHubAuthenticationContext (long-lived HttpClient, configurable BaseUrl/UserAgent).
  • Models.tt — emits clean Newtonsoft-annotated POCOs by walking components.schemas with Microsoft.OpenApi.
  • Actions.tt — emits one ActionBase subclass per OpenAPI operation, each invoking HttpClient directly (no generator runtime).
  • Resources.tt — emits the en-only Resources.resx covering all action / argument / error / group / enum names required by the existing ActionTests.
  • Wire Modules.GitHub.Actions into tests/Modules.Actions.Tests/Modules.Actions.Tests.csproj.
  • Verify the produced .cab contains zero generator artifacts.
  • Short bullet in the top-level README.md.

Design context (for reviewers)

  • Authentication is gh-CLI-shaped. One Login action with a LoginMode enum + three ActionSelectors; PAT, OAuth device flow (defaults to gh CLI's public client id), and reading from local gh auth token --hostname <Host>. Every generated REST action takes the resulting GitHubAuthenticationContext as its first required input.
  • Why Microsoft.OpenApi rather than NSwag/Kiota. NSwag chokes on a handful of oneOf schemas in GitHub's spec (repository-rule, repository-ruleset-conditions, secret-scanning first_location_detected) — emits references without declarations and there is no NSwag option that fixes this. Kiota handles oneOf correctly but would leak its Microsoft.Kiota.* abstractions into the runtime module unless we wrote a Kiota-reflecting transpiler in the T4 — at which point it is simpler to just parse the spec ourselves with Microsoft.OpenApi.NET, a pure parser library with no runtime types.
  • Runtime pollution. With PrivateAssets="all" ExcludeAssets="runtime" the parser is build-only, the actions module references only the SDK + Newtonsoft.Json + System.Net.Http, and the CAB stays clean.

Deferred follow-ups (not in this PR)

  • File a follow-up issue for new HttpClient()-per-call port-exhaustion risk in Modules.CloudFlows.Actions/RunFlowAction, samples/Modules.PetStore.Actions/Actions.tt-generated Actions.cs, etc. The pattern landed in this module (GitHubAuthenticationContext owns a long-lived HttpClient) is the suggested fix.

jfevia and others added 9 commits June 9, 2026 00:16
Initial scaffolding for a new `Modules.GitHub.Actions` module that will
expose the full GitHub REST API as PAD actions, generated at build time
from the live `github/rest-api-description` OpenAPI document.

This commit contains the buildable skeleton only; subsequent commits in
the same PR will add the T4 templates (Microsoft.OpenApi parser used
build-time-only via `PrivateAssets="all" ExcludeAssets="runtime"`),
the `Login` action and its three `ActionSelector`s, and the
auto-generated action/POCO/resx layer.

Solution-file cleanup is included so the new project is registered:
* `PowerAutomate.Desktop.sln` deleted; only `PowerAutomate.Desktop.slnx` is maintained.
* CI / PR / nightly workflows switched from `.sln` to `.slnx`.

Tooling:
* `.tools/update-github-openapi.ps1` fetches the OpenAPI spec into `modules/Modules.GitHub.Actions/openapi.json`.
* The spec file is git-ignored; the tools script is the supported way to refresh it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the `Login` action and its three `ActionSelector`s that mirror
the `gh auth login` shape, plus the `GitHubAuthenticationContext` custom
type that subsequent generated REST actions will consume:

* `LoginAction` (id `Login`) with input groups General / PersonalAccessToken / DeviceFlow / GitHubCli and a `Mode` enum that drives which branch executes.
* `LoginWithPersonalAccessTokenActionSelector` accepts a PAT directly.
* `LoginWithDeviceFlowActionSelector` runs the OAuth device flow against the
  publicly-known GitHub CLI client id (configurable), copies the user code to
  the clipboard, opens the verification URL in the default browser, then polls
  until a token is issued or the device code expires.
* `LoginWithGitHubCliActionSelector` shells out to
  `gh auth token --hostname <Host>` to reuse a token already stored locally.
* All three modes funnel into `CreateAuthenticatedContext` which builds a
  long-lived `HttpClient` configured with `Authorization: Bearer <token>`,
  `User-Agent` and `Accept: application/vnd.github+json`, then best-effort
  resolves the authenticated login by GETting `/user`.

`GitHubAuthenticationContext` is marked `[Type]` so PAD recognises it as a
custom variable type. The `HttpClient` is hidden from the variable picker
(`[PropertyIgnore]`); `BaseUrl` / `UserAgent` / `Login` are exposed.

`Modules.GitHub.Actions` is wired into `tests/Modules.Actions.Tests` so the
existing `ActionTests`/`ActionSelectorTests` fixtures auto-validate the new
module. Full test suite (8/8) green; bin/ contains only the SDK, Newtonsoft,
and the module assembly - no generator artifacts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the `Models.tt` T4 template that emits clean Newtonsoft-annotated POCO
classes for every schema in `components.schemas` of GitHub's OpenAPI document,
into `PowerAutomate.Desktop.Modules.GitHub.Actions.Models`.

* 936 schemas in the live spec -> 908 `partial class` POCOs and 28 string
  `enum`s in the committed `Models.cs` (~780 KB).
* `Microsoft.OpenApi` and `Microsoft.OpenApi.Readers` 1.6.29 are referenced
  with `PrivateAssets="all" ExcludeAssets="runtime"` and
  `GeneratePathProperty="true"` so the parser is design-time only and
  never reaches the module's `bin/` or the CAB.
* Inline / complex shapes (`oneOf`, `anyOf`, `allOf` of inline schemas,
  inline objects without `properties`) collapse to `JObject` so the
  generated code stays compilable; this is the documented fidelity loss agreed
  in the plan.
* `allOf` composition is flattened to a single class.
* C# keyword and digit-leading property/enum-member names are sanitised.

Regeneration is via `.tools/regenerate-github-module.ps1` (matches the
existing `.tools/` script pattern): it substitutes `%NUGET_PACKAGES%` in
the templates with the local NuGet cache path and invokes `dotnet-t4` once
per `*.tt`. Generated outputs are committed; CI does not regenerate.

Module assembly grew from 26 KB to 1.27 MB; all 8 existing tests still pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the `Actions.tt` and `Resources.tt` T4 templates that close the loop
on the generator pipeline. They walk every operation in `components.paths`
of the GitHub OpenAPI document and emit:

* `Actions.cs` (3.0 MB committed): 1190 `ActionBase` subclasses, one per
  REST operation. Each:
  - Carries `[Action(Id=..., Category=<Tag>)]` so the PAD designer groups
    them by GitHub tag (Repos, Pulls, Issues, Actions, ...).
  - Declares `[Group]` attributes for `Authentication` / `Path` /
    `Query` / `Header` / `Body` as actually used by the operation.
  - Takes `GitHubAuthenticationContext Authentication` as its first required
    input (driven by the `Login` action shipped previously).
  - Builds an `HttpRequestMessage` against the auth context's long-lived
    `HttpClient`, interpolates path parameters, appends query parameters,
    serializes JSON bodies with Newtonsoft.Json, and deserializes responses
    into the POCO `Models` (or `JObject` when the response is too dynamic
    to model statically).
  - Wraps everything in try/catch -> `ActionException(ErrorCodes.Unknown,...)`.
* `Properties/Resources.resx` (~3.0 MB committed): full localisable resource
  set (default `en` only) covering every generated action / argument /
  group / error / enum value plus the hand-written module + Login entries -
  satisfies every assertion in the existing `ActionTests` and
  `ActionSelectorTests` fixtures.

Other plumbing:

* `Modules.GitHub.Actions.csproj` registers `Actions.tt` / `Actions.cs`
  alongside `Models.tt` / `Models.cs` (mirrors the PetStore wiring).
* `.tools/regenerate-github-module.ps1` now also routes `Resources.tt` to
  `Properties\Resources.resx`.

Verified locally:
- `dotnet build` succeeds with 7 warnings (6 are CS0472 on enum-typed query
  parameters because top-level OpenAPI enums currently surface as `string`
  with a non-nullable comparison; functional but cosmetic; pre-existing
  PetStore obsolete warning is the seventh).
- All 8 tests pass.
- `Modules.GitHub.Actions.dll` is 5.70 MB and the CAB is 1.05 MB.
- bin/Debug contains only the SDK, Newtonsoft, and the module assembly;
  no `Microsoft.OpenApi.*` / `SharpYaml` / Kiota / NSwag artifacts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When MSBuild's `Exec` task runs `powershell -ExecutionPolicy Bypass -File
sign.ps1` through `cmd.exe`, Windows PowerShell 5.1 starts in an
environment where PowerShell 7's module directory sits ahead of 5.1's in
`PSModulePath`. As a result PS5.1 loads PS7's `Microsoft.PowerShell.Security`
module, which does not register the `Certificate` provider - so the
`Get-ChildItem -Path Cert:\CurrentUser\My` call in `sign.ps1` fails with
"A drive with the name 'Cert' does not exist" and `sign` is then invoked
with no `--certificate-fingerprint` argument, leaving the DLL and CAB
unsigned. PAD then rejects the module with "The desktop flow module package
is not correctly signed."

Switch to the `System.Security.Cryptography.X509Certificates.X509Store` API,
which does not depend on the `Cert:` PSDrive being registered, and at the
same time:

* Filter out expired certificates (the previous behaviour would silently sign
  with the first `CN=` match even after the self-signed cert from
  `setup.ps1` had expired).
* Prefer the newest non-expired cert when more than one matches.
* Throw with a clear remediation message when no usable cert exists, and
  propagate the `sign` tool's exit code so the MSBuild target actually
  fails on a signing failure instead of silently producing an unsigned CAB.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PAD's module loader rejected the module with several errors:

1. `Enum Argument '<X>' in action '<...>' must have a default value.`
2. `Argument '<X>' of action '<...>' is defined as not required but its type is not nullable`
3. `Invalid argument name 'Label' in action 'ReposUploadReleaseAsset'`

Updates `Actions.tt` and `Resources.tt` accordingly:

* For every parameter whose schema is a `` to a top-level string-enum
  schema in `components.schemas`, the generated property now:
  - Resolves to `Models.<Enum>?` (optional) or `Models.<Enum>`
    (required) instead of `string` so PAD recognises it as an enum.
  - Carries `[System.ComponentModel.DefaultValue(null)]` for optional
    enums and `[System.ComponentModel.DefaultValue(Models.<Enum>.<First>)]`
    for required ones.
  - Has a query-string emitter that skips the value when `HasValue` is
    false (and emits without a redundant `!= null` check for non-nullable
    enums, which also clears the 6 pre-existing CS0472 warnings).
* Adds the **full Robin keyword set** (extracted from
  `Microsoft.Flow.RPA.Desktop.Robin.Language.Parsing.LanguageLexer`) to a
  `PadReservedPropertyNames` set used by `ToPropertyName`. Any
  parameter whose PascalCase name case-insensitively collides with a Robin
  keyword (`Label`, `Input`, `Output`, `Action`, `For`, `If`,
  `True`, etc.) is suffixed with `Argument` (so the previous `Label`
  on `ReposUploadReleaseAsset` becomes `LabelArgument`). The same set
  is added to `Resources.tt` so the resx keys match the new property
  names and the existing `ActionTests` continue to pass.

Verified locally: full build clean (0 warnings, 0 errors, down from 7),
8/8 tests pass, the generated CAB is Authenticode-valid, and bin/Debug
still contains only the module assembly + SDK + Newtonsoft (no generator
artefacts).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds three test methods to `tests/Modules.Actions.Tests/ActionTests.cs`
so the same class of failures that PAD reported on module install is now
caught by `dotnet test` against any module under `modules/` -
including ones produced by future T4 generators.

* `Action_All_InputArguments_All_Enums_HaveDefaultValue` - every input
  argument whose CLR type (or underlying type, for `Nullable<TEnum>`) is
  an enum must carry `[System.ComponentModel.DefaultValue(...)]`.
* `Action_All_InputArguments_All_NonRequired_AreNullableOrHaveDefaultValue`
  - every input argument with `[InputArgument(Required = false)]` must
  either have a nullable CLR type (reference type or `Nullable<T>`) or a
  `[DefaultValue]` attribute (matches what the PAD module loader
  actually accepts; the long-standing `Modules.GitHub.Copilot.Actions`
  pattern of non-nullable enum + `DefaultValue(EnumName.Unset)` is
  preserved).
* `Action_All_Arguments_All_HaveNonReservedNames` - no input or output
  argument name may collide (case-insensitively) with a Robin language
  keyword. The reserved-name set is the same 45-keyword list extracted
  from `Microsoft.Flow.RPA.Desktop.Robin.Language.Parsing.LanguageLexer`
  and used by `Actions.tt` / `Resources.tt`.

Each test follows the existing fixture style: `ModuleEnumerator.GetAllAssemblies()`
+ LINQ over `[Action]` types, `[InputArgument]` / `[OutputArgument]`
properties, with `Assert.That(..., message)` failure messages that
mirror PAD's own wording so the failure is immediately recognisable.

All 11 tests pass against the current set of referenced modules
(8 existing + 3 new); build clean (only the pre-existing PetStore
obsolete warning).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Switches the lossy `oneOf` / `anyOf` / inline-object fallback type
  in `Actions.tt` and `Models.tt` from `JObject` to `JToken`. Some
  GitHub endpoints (notably `GET /repos/{owner}/{repo}/stargazers`)
  legitimately return a JSON array instead of an object, and
  Newtonsoft.Json refuses to deserialize a JArray into a JObject. `JToken`
  is the common ancestor and accepts both shapes; consumers cast as needed.
* Adds `Console.WriteLine` output in `LoginWithDeviceFlow` so the
  verification URL and user code are visible to CLI consumers (PAD users
  still get the clipboard copy + browser open). Purely additive.
* New `.tools\smoke-test-github-module.ps1`: loads the module assembly,
  signs in via the local `gh` CLI (or, when the device-flow client id
  is updated, via OAuth device flow), then exercises 56 representative
  REST actions across every code-generation pattern (no-param GETs,
  path-param GETs, multi-path GETs, enum query params, oneOf-heavy
  responses such as `ReposGetRepoRulesets` and `ReposGetBranchRules`,
  search endpoints, special accept headers, PUT/GET/DELETE side-effect
  chains) plus a five-step gist mutation chain
  (create -> get -> update -> delete -> verify-gone) that proves
  POST/PATCH/DELETE with `JToken` body and POCO output deserialization
  (`Models.GistSimple`) all work end-to-end against the live GitHub
  REST API. Every test passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Significantly expands `.tools/smoke-test-github-module.ps1` to exercise
patterns that the original ~50-action pass didn't reach:

* **Array query parameters** (`IssuesListForRepo` with `Labels='bug,enhancement'`)
* **DateTime query parameters** (`Since=2025-01-01T00:00:00Z`)
* **Explicit pagination** (`PerPage=2 Page=1` against `ReposListForAuthenticatedUser`)
* **404 propagation** for missing repo + missing user
* **MS-connector parity operations** that the Power Platform GitHub
  connector docs page enumerates: `IssuesGet`, `IssuesListLabelsOnIssue`,
  `IssuesListAssignees`, `ReposListCollaborators`, `GitGetRef`,
  `ReposCompareCommits`.
* **Output assertions** (10/10) that verify roundtripped data, not just
  status: `UsersGetAuthenticated.login` / `.type` via `JToken` indexer
  access (the response is a `oneOf` of public/private user, so the
  generator picks `JToken`); `ReposGet.Name` / `.FullName`;
  `ReposListBranches` count and `main` membership;
  `PullsGet.Number/.State`; `RateLimit.Rate.Limit`; pagination cap.
* **Full issue lifecycle** (7 steps) - create, get, add comment, list
  comments, update title + close, reopen, close again - proves
  `POST/PATCH` with `JToken` body + POCO output (`Models.Issue`)
  end-to-end against the live API. Roundtrip Title equality verified.
* **Step numbering** in the script tidied (Steps 1-6).

Total: 80 live GitHub API operations exercised, every one behaves as
expected. The `JToken` shift (committed earlier) lets all `oneOf`
responses through cleanly.

Note: two auto-generated test issues (#29, #30) were created in
jfevia/PowerAutomate.Desktop.Modules and immediately closed; the
GitHub REST API does not allow deleting closed issues without admin UI
action.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jfevia jfevia marked this pull request as ready for review June 8, 2026 23:01
@jfevia jfevia merged commit c1935bc into main Jun 8, 2026
1 check passed
@jfevia jfevia deleted the feature/github-module branch June 8, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant