Skip to content

Resolve a script's module dependencies dynamically from declarative requirement signals #60

Description

The PSModule ecosystem runs PowerShell in CI through thin action wrappers — most prominently GitHub-Script, which executes an inline, multi-line, or file-based script after connecting to GitHub. Today that action installs a single module (the GitHub module, whose version is controlled by the action's Version and Prerelease inputs) and nothing else. When a workflow author passes a script that depends on any other module, the script itself has to carry bespoke Install-PSResource bootstrapping, and every repository reinvents that logic.

A related gap surfaced while bumping the GitHub-Script pin across the org: automated dependency tooling (Dependabot) updates the pinned action reference (uses: PSModule/GitHub-Script@<sha>), but it cannot see inside the script that is handed to the action. The modules that script depends on are invisible to any updater, so they are never surfaced, pinned, or bumped.

Request

Desired capability

When a script runs through a script-executing action, its module dependencies should be discovered and installed automatically from declarative signals that live alongside the script or in the repository — instead of every script carrying its own install code.

  • If the script declares nothing about what it needs, the action installs the latest — this is the current behavior and must not regress.
  • If the script or repository declares requirements, the action honors them: it installs exactly the declared modules, at the declared versions, from the declared repositories, before the script runs.
  • The declaration uses standard, PowerShell-native structures so authors reach for one familiar mechanism rather than a PSModule-specific invention.

Acceptance criteria

  • A script with no declared requirements behaves exactly as it does today (latest; no regression).
  • A script or repository that declares module requirements has those modules installed automatically, at the declared versions, before the script executes.
  • Custom or private repositories can be declared and are registered automatically before resolution and installation.
  • The declaration relies on recognizable PowerShell-native conventions (see Technical decisions) rather than a bespoke format.
  • The behavior is declarative and safe by default: nothing declared means nothing extra is installed.
  • The convention is documented so other script-running actions in the ecosystem can adopt it consistently.

Note

Captured as a separate exploration/improvement track, spun out of the GitHub-Script v1.9.0 rollout. The deliverable of this issue is a documented convention plus a prototype direction, not a finished implementation.


Technical decisions

Prefer native structures over a bespoke format. Build on Microsoft.PowerShell.PSResourceGet rather than inventing a PSModule-specific dependency file. Three complementary requirement signals, from most explicit to most implicit:

  1. Requirements file — a Requirements.psd1 or Requirements.json consumed by Install-PSResource -RequiredResourceFile (that parameter accepts both .psd1 and .json). This is the machine-readable, updater-friendly form.
  2. Inline requirements — a hashtable or JSON string passed to Install-PSResource -RequiredResource, for the inline-script case where no file exists.
  3. #Requires -Modules scan — gather the #Requires -Modules statements already present in the script (and any .ps1 / .psm1 it references) and install from them.

Repository registration. Custom or private repositories are declared in a PSResourceRepository.psd1 (an array of hashtables) and registered via Register-PSResourceRepository -Repository before any install. This mirrors the requirements structure and keeps registry definitions declarative.

Default. No signal present → latest, which preserves the current GitHub-Script behavior.

Proposed action surface (GitHub-Script). Add opt-in inputs while keeping the existing ones:

Input Purpose
RequiredResourceFile Path to a Requirements.psd1 / Requirements.json.
RequiredResource Inline hashtable / JSON string of required resources.
PSResourceRepository Path to a PSResourceRepository.psd1 to register before install.
resolution toggle Auto-detect the conventional files and scan #Requires (default), or disable.
Version / Prerelease Unchanged — these continue to govern the GitHub module specifically.

Where the logic lives. Extend src/init.ps1 (which already installs the GitHub module with retry) with a requirements-resolution phase that runs before the user script, and factor the resolution into a reusable helper so sibling actions can consume it.

Reuse across the ecosystem. This is the action-side counterpart to the module-side dependency work in PSModule/Process-PSModule#356, and it generalizes the pattern already requested for a single module in PSModule/Invoke-Pester#68 (honor #Requires-style version constraints on an action input). Related documentation is tracked in #28.

Open decisions:

  • Precedence / merge when more than one signal is present — for example, an explicit requirements file wins, or the signals are unioned with a defined conflict rule.
  • Context integration — whether repository (and credential) definitions should be stored in and resolved through the Context module instead of a loose PSResourceRepository.psd1.
  • Private-repository credentials — how the action surfaces credentials (SecretManagement CredentialInfo, the Azure Artifacts credential provider, or dedicated action inputs / secrets).
  • Transitive dependenciesInstall-PSResource does not install dependent resources from NuGet v3 repositories, so decide whether the resolver walks transitive #Requires / manifest dependencies itself.

Forward-looking. A machine-readable requirements file is exactly what a future updater could parse and bump, closing the "Dependabot cannot see the script's dependencies" gap noted above — see the native PowerShell ecosystem request dependabot/dependabot-core#15501.


Implementation plan

Specification (this repo)

  • Document the convention: the three requirement signals, the repository-registration file, discovery, precedence, and the latest-by-default rule.
  • Define the file schemas and names — Requirements.psd1 / Requirements.json (the Install-PSResource -RequiredResourceFile format) and PSResourceRepository.psd1 (the Register-PSResourceRepository -Repository format) — with examples.
  • Resolve the open decisions (precedence, Context integration, credentials, transitive dependencies).
  • Align the guidance with Add module dependency versioning documentation #28 (module dependency versioning documentation).

Prototype (PSModule/GitHub-Script)

  • Add the opt-in inputs (RequiredResourceFile, RequiredResource, PSResourceRepository, resolution toggle) to action.yml.
  • Add a requirements-resolution phase to src/init.ps1 that registers declared repositories, then installs declared requirements (or scans #Requires), before the user script — defaulting to current behavior when nothing is declared.
  • Extract the resolution logic into a reusable helper for other actions.
  • Add action tests covering: no signal (latest), requirements file, inline requirements, #Requires scan, and custom repository registration.

Ecosystem follow-up

  • Track adoption in sibling script-running actions and reconcile with PSModule/Invoke-Pester#68.
  • Link the effort under the dependency-management epic PSModule/Process-PSModule#356.

Metadata

Metadata

Assignees

No one assigned

    Labels

    dependenciesPull requests that update a dependency fileenhancementgithub_actionsPull requests that update GitHub Actions code

    Fields

    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions