From edf918c866ffded2ee5a9a2688d39512810da256 Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 12:13:28 -0700 Subject: [PATCH 01/23] Enable JUnit output for Pester --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c663d79..0aac17f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,7 +112,9 @@ jobs: if (Test-Path "tests/pester") { $cfg = New-PesterConfiguration $cfg.Run.Path = './tests/pester' - $cfg.TestResult.Enabled = $false + $cfg.TestResult.Enabled = $true + $cfg.TestResult.OutputFormat = 'JUnitXml' + $cfg.TestResult.OutputPath = 'pester-results/pester-junit.xml' $cfg.Run.Exit = $true Invoke-Pester -Configuration $cfg } From 92722c460ea67ec2a0709a808d6564f64adb9bc3 Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 12:32:02 -0700 Subject: [PATCH 02/23] chore: use plural test result env var --- .github/workflows/ci.json | 2 +- .github/workflows/ci.yml | 2 +- scripts/__tests__/generate-ci-summary.test.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.json b/.github/workflows/ci.json index f87bbe5e..2e49f28f 100644 --- a/.github/workflows/ci.json +++ b/.github/workflows/ci.json @@ -46,7 +46,7 @@ { "run": "npm run generate:summary", "env": { - "TEST_RESULTS_GLOB": "test-results/*junit*.xml" + "TEST_RESULTS_GLOBS": "test-results/*junit*.xml" } }, { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0aac17f3..08d3a62f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: - run: npm run derive:registry - run: npm run generate:summary env: - TEST_RESULTS_GLOB: test-results/*junit*.xml + TEST_RESULTS_GLOBS: test-results/*junit*.xml - uses: actions/upload-artifact@v4 if: always() with: diff --git a/scripts/__tests__/generate-ci-summary.test.js b/scripts/__tests__/generate-ci-summary.test.js index 35dc8c53..1d50a1a2 100644 --- a/scripts/__tests__/generate-ci-summary.test.js +++ b/scripts/__tests__/generate-ci-summary.test.js @@ -155,7 +155,7 @@ test('writes outputs to OS-specific directory', async () => { const env = { ...process.env, - TEST_RESULTS_GLOB: junitPath, + TEST_RESULTS_GLOBS: junitPath, EVIDENCE_DIR: tmp, RUNNER_OS: 'Windows', }; @@ -196,7 +196,7 @@ test('partitions requirement groups by runner_type', async () => { const env = { ...process.env, - TEST_RESULTS_GLOB: junitPath, + TEST_RESULTS_GLOBS: junitPath, EVIDENCE_DIR: dir, REQ_MAPPING_FILE: reqPath, RUNNER_OS: 'Linux', From d03d0c8e2c1e7eae62e1aaf38b7b47a8269de87a Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 12:56:02 -0700 Subject: [PATCH 03/23] fix: correct evidence directory for report job --- .github/workflows/ci.json | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.json b/.github/workflows/ci.json index 2e49f28f..d8bcd3ff 100644 --- a/.github/workflows/ci.json +++ b/.github/workflows/ci.json @@ -184,7 +184,7 @@ "TEST_RESULTS_GLOBS": "downloaded/test-results/**/*junit*.xml\ndownloaded/pester-junit-*/pester-junit.xml\n", "REQ_MAPPING_FILE": "requirements.json", "DISPATCHER_REGISTRY": "dispatchers.json", - "EVIDENCE_DIR": "test-screenshots" + "EVIDENCE_DIR": "downloaded/evidence" } }, { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08d3a62f..0643bf2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,5 +147,5 @@ jobs: downloaded/pester-junit-*/pester-junit.xml REQ_MAPPING_FILE: requirements.json DISPATCHER_REGISTRY: dispatchers.json - EVIDENCE_DIR: test-screenshots + EVIDENCE_DIR: downloaded/evidence - run: npx tsx scripts/print-pester-traceability.ts >> "$GITHUB_STEP_SUMMARY" From 47f22d3156aade0b3d95ddaa216f16200c905354 Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 13:13:57 -0700 Subject: [PATCH 04/23] chore: skip report on cancellations --- .github/workflows/ci.json | 2 +- .github/workflows/ci.yml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.json b/.github/workflows/ci.json index d8bcd3ff..4fb7d445 100644 --- a/.github/workflows/ci.json +++ b/.github/workflows/ci.json @@ -152,7 +152,7 @@ "ps-ci" ], "runs-on": "ubuntu-24.04", - "if": "always()", + "if": "(success() || failure()) && !cancelled()", "steps": [ { "uses": "actions/checkout@v4" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0643bf2e..e0cacae5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,17 +27,17 @@ jobs: env: TEST_RESULTS_GLOBS: test-results/*junit*.xml - uses: actions/upload-artifact@v4 - if: always() + if: ${{ (success() || failure()) && !cancelled() }} with: name: traceability path: ${{ env.ARTIFACT_DIR }}/traceability.* - uses: actions/upload-artifact@v4 - if: always() + if: ${{ (success() || failure()) && !cancelled() }} with: name: action-docs path: ${{ env.ARTIFACT_DIR }}/action-docs.* - uses: actions/upload-artifact@v4 - if: always() + if: ${{ (success() || failure()) && !cancelled() }} with: name: evidence path: ${{ env.ARTIFACT_DIR }}/evidence/** @@ -94,7 +94,7 @@ jobs: } } - uses: actions/upload-artifact@v4 - if: always() + if: ${{ (success() || failure()) && !cancelled() }} with: name: setup-info-${{ matrix.os }} path: setup-info-${{ matrix.os }}.md @@ -119,7 +119,7 @@ jobs: Invoke-Pester -Configuration $cfg } - uses: actions/upload-artifact@v4 - if: always() + if: ${{ (success() || failure()) && !cancelled() }} with: name: pester-junit-${{ matrix.os }} path: pester-results/pester-junit.xml @@ -128,7 +128,7 @@ jobs: report: needs: [node-ci, ps-ci] runs-on: ubuntu-24.04 - if: always() + if: ${{ (success() || failure()) && !cancelled() }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 From 0a90427cec069f0575de7f8282b4350d245aff3a Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 13:15:32 -0700 Subject: [PATCH 05/23] docs: document runner types --- README.md | 4 ++++ docs/quickstart.md | 2 +- docs/requirements.md | 2 ++ docs/runner-types.md | 55 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 docs/runner-types.md diff --git a/README.md b/README.md index c2730c99..5ac4aa9a 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,10 @@ Get details about a specific action: pwsh actions/Invoke-OSAction.ps1 -Describe run-unit-tests ``` +## Runner Types + +Workflows distinguish between standard GitHub-hosted images and integration runners with preinstalled tooling. See [docs/runner-types.md](docs/runner-types.md) for a detailed comparison. + ## Testing Run the JavaScript tests with: diff --git a/docs/quickstart.md b/docs/quickstart.md index 69598f0b..449dc150 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1,6 +1,6 @@ # Quickstart -1. **Install Requirements:** Ensure you have **NI LabVIEW** (with command-line interface support, often via *g-cli*) installed on the target runner. Most actions require LabVIEW and the NI g-cli tool to be available (Ubuntu runners are recommended). Also verify PowerShell 7+ (`pwsh`) is available for cross-platform script execution. Install **Node.js 24+** and run `npm install` to pull in the TypeScript dependencies used by helper scripts. +1. **Install Requirements:** Ensure you have **NI LabVIEW** (with command-line interface support, often via *g-cli*) installed on the target runner. Most actions require LabVIEW and the NI g-cli tool to be available (Ubuntu runners are recommended). Also verify PowerShell 7+ (`pwsh`) is available for cross-platform script execution. Install **Node.js 24+** and run `npm install` to pull in the TypeScript dependencies used by helper scripts. Decide whether to execute on a standard GitHub-hosted runner or an integration runner with preinstalled tooling; see [runner-types](runner-types.md) for details. 2. **Invoke via Composite Action (GitHub):** Use the adapter-specific action in your workflow. For example, to **build a LabVIEW Packed Library**: ```yaml diff --git a/docs/requirements.md b/docs/requirements.md index 7e2d2531..c43700c4 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -2,6 +2,8 @@ This project tracks high‑level requirements and maps each one to the Pester test files that verify it. The authoritative mapping is stored in [`requirements.json`](../requirements.json); the table below provides a human‑readable summary for quick reference. +Runner Type indicates whether a requirement runs on a standard GitHub-hosted image or an integration runner with preinstalled tooling. See [runner-types](runner-types.md) for guidance on choosing between them. + | ID | Description | Tests | Runner | Runner Type | Skip Dry Run | |----|-------------|-------|--------|-------------|--------------| | REQ-001 | Dispatcher discovers available actions, describes them, and validates arguments. | `tests/pester/Dispatcher.Tests.ps1` | | | | diff --git a/docs/runner-types.md b/docs/runner-types.md new file mode 100644 index 00000000..b41c7a73 --- /dev/null +++ b/docs/runner-types.md @@ -0,0 +1,55 @@ +# Runner Types: Integration vs Default + +Modern CI pipelines in this repository classify runners into two categories to control cost and coverage. Understanding the difference allows you to target expensive resources only when necessary and keep feedback cycles fast. + +## Default ("standard") runners + +* **Environment** – GitHub-hosted virtual machines such as `ubuntu-latest` or `windows-latest`. +* **Use cases** – Linting, unit tests and any step that can run on a clean ephemeral image. +* **Configuration** – In `requirements.json` omit `runner_type` or set it to `standard`. Workflows simply use `runs-on: ubuntu-latest` or similar. +* **Characteristics** – High concurrency, minimal boot time and no persistent state. Ideal for rapid validation. + +## Integration runners + +* **Environment** – Long-lived machines with preinstalled tooling such as LabVIEW, g-cli and hardware drivers. They may be self-hosted or specialized GitHub images. +* **Use cases** – End‑to‑end scenarios that interact with external systems, require licensed software or need deterministic state. +* **Configuration** – Tag the runner with `runner_type: "integration"` in `requirements.json` and reference the runner by label in workflows. Integration entries often set `skip_dry_run: true` to force real execution. +* **Characteristics** – Limited availability and higher cost; jobs are serialized to protect shared resources. Treat these runners as scarce infrastructure. + +## Declaring runner types + +The `requirements.json` file defines which runner each test or requirement targets: + +```json +{ + "runners": { + "ubuntu-latest": { + "runner_label": "ubuntu-latest", + "runner_type": "integration" + }, + "windows-latest": { + "runner_label": "windows-latest" + /* implicit runner_type: "standard" */ + } + }, + "requirements": [ + { + "id": "REQ-009", + "runner": "ubuntu-latest", + "tests": ["Build.Workflow"] + } + ] +} +``` + +The summarizer partitions results by `runner_type`, producing artifacts such as `summary-integration.md` alongside `summary-standard.md`. This separation keeps integration evidence distinct from fast feedback produced on default runners. + +## Recommendations for CI/CD engineers + +1. **Minimize integration usage.** Start with standard runners and move tests to integration environments only when they require external dependencies or state. +2. **Isolate heavy workflows.** Place integration jobs in separate stages or repositories to avoid blocking quick validation paths. +3. **Protect self-hosted runners.** Apply concurrency limits and explicit `needs` chains so multiple integration jobs do not compete for the same hardware. +4. **Audit runner labels.** Keep `requirements.json` synchronized with the actual fleet of self-hosted machines. Stale labels lead to idle jobs. +5. **Document expectations.** When adding new requirements or workflows, update `docs/requirements.md` so the `Runner` and `Runner Type` columns reflect the intended infrastructure. + +By explicitly classifying jobs, teams can scale the project efficiently—routine tasks remain fast on default runners while integration tests validate real‑world behavior without overloading scarce resources. From 661fd65174b1c068082b035ca8c8ee588789c252 Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 13:27:54 -0700 Subject: [PATCH 06/23] Update requirements.json --- requirements.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.json b/requirements.json index 92e04c9a..274844b4 100644 --- a/requirements.json +++ b/requirements.json @@ -9,8 +9,8 @@ "self-hosted-windows-lv": { "owner": "self-hosted-windows-lv", "runner_label": "self-hosted-windows-lv", - "runner_type": "integration", - "skip_dry_run": false + "runner_type": "default", + "skip_dry_run": true }, "ubuntu-latest": { "owner": "ubuntu-latest", @@ -371,4 +371,4 @@ ] } ] -} \ No newline at end of file +} From 6f30d63a8cec98fc434fe7740589aa87380ff7c4 Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 13:40:15 -0700 Subject: [PATCH 07/23] ci: use preinstalled PowerShell on Windows runners --- .github/workflows/ci.json | 4 ++-- .github/workflows/ci.yml | 25 ++++++++++++++----------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.json b/.github/workflows/ci.json index 4fb7d445..cb2bf908 100644 --- a/.github/workflows/ci.json +++ b/.github/workflows/ci.json @@ -102,10 +102,10 @@ "uses": "actions/checkout@v4" }, { - "name": "Install PowerShell 7.5.1", + "name": "Ensure PowerShell 7.5.1", "if": "matrix.os == 'windows-latest'", "shell": "pwsh", - "run": "$expectedBuild = '10.0.20348'\n$build = (Get-ComputerInfo).OsVersion\nif ($build -ne $expectedBuild) {\n throw \"Unexpected OS build: $build\"\n}\n$msiUrl = 'https://github.com/PowerShell/PowerShell/releases/download/v7.5.1/PowerShell-7.5.1-win-x64.msi'\n$msiPath = Join-Path $env:RUNNER_TEMP 'PowerShell-7.5.1-win-x64.msi'\nInvoke-WebRequest -Uri $msiUrl -OutFile $msiPath\n$expectedHash = 'b110eccaf55bb53ae5e6b6de478587ed8203570b0bda9bd374a0998e24d4033a'\n$actualHash = (Get-FileHash $msiPath -Algorithm SHA256).Hash\nif ($actualHash -ne $expectedHash) {\n throw \"SHA256 mismatch: $actualHash\"\n}\nStart-Process msiexec -Wait -ArgumentList '/i', $msiPath, '/qn', 'ADD_PATH=1'\n$version = (pwsh --version).Trim() -replace '^PowerShell '\nif ($version -ne '7.5.1') {\n throw \"PowerShell version $version is not 7.5.1\"\n}\n" + "run": "$required = [Version]'7.5.1'\nif ($PSVersionTable.PSVersion -lt $required) {\n $msiUrl = 'https://github.com/PowerShell/PowerShell/releases/download/v7.5.1/PowerShell-7.5.1-win-x64.msi'\n $msiPath = Join-Path $env:RUNNER_TEMP 'PowerShell-7.5.1-win-x64.msi'\n Invoke-WebRequest -Uri $msiUrl -OutFile $msiPath\n $expectedHash = 'b110eccaf55bb53ae5e6b6de478587ed8203570b0bda9bd374a0998e24d4033a'\n $actualHash = (Get-FileHash $msiPath -Algorithm SHA256).Hash\n if ($actualHash -ne $expectedHash) {\n throw \"SHA256 mismatch: $actualHash\"\n }\n Start-Process msiexec -Wait -ArgumentList '/i', $msiPath, '/qn', 'ADD_PATH=1'\n}\n$version = (pwsh --version).Trim() -replace '^PowerShell '\nif ([Version]$version -lt $required) {\n throw \"PowerShell version $version is less than $required\"\n}\n" }, { "name": "Capture setup info", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0cacae5..22109f9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,22 +58,25 @@ jobs: RUNNER_TYPE: ${{ matrix.runner_type }} steps: - uses: actions/checkout@v4 - - name: Install PowerShell 7.5.1 + - name: Ensure PowerShell 7.5.1 if: matrix.os == 'windows-latest' shell: pwsh run: | - $msiUrl = 'https://github.com/PowerShell/PowerShell/releases/download/v7.5.1/PowerShell-7.5.1-win-x64.msi' - $msiPath = Join-Path $env:RUNNER_TEMP 'PowerShell-7.5.1-win-x64.msi' - Invoke-WebRequest -Uri $msiUrl -OutFile $msiPath - $expectedHash = 'b110eccaf55bb53ae5e6b6de478587ed8203570b0bda9bd374a0998e24d4033a' - $actualHash = (Get-FileHash $msiPath -Algorithm SHA256).Hash - if ($actualHash -ne $expectedHash) { - throw "SHA256 mismatch: $actualHash" + $required = [Version]'7.5.1' + if ($PSVersionTable.PSVersion -lt $required) { + $msiUrl = 'https://github.com/PowerShell/PowerShell/releases/download/v7.5.1/PowerShell-7.5.1-win-x64.msi' + $msiPath = Join-Path $env:RUNNER_TEMP 'PowerShell-7.5.1-win-x64.msi' + Invoke-WebRequest -Uri $msiUrl -OutFile $msiPath + $expectedHash = 'b110eccaf55bb53ae5e6b6de478587ed8203570b0bda9bd374a0998e24d4033a' + $actualHash = (Get-FileHash $msiPath -Algorithm SHA256).Hash + if ($actualHash -ne $expectedHash) { + throw "SHA256 mismatch: $actualHash" + } + Start-Process msiexec -Wait -ArgumentList '/i', $msiPath, '/qn', 'ADD_PATH=1' } - Start-Process msiexec -Wait -ArgumentList '/i', $msiPath, '/qn', 'ADD_PATH=1' $version = (pwsh --version).Trim() -replace '^PowerShell ' - if ($version -ne '7.5.1') { - throw "PowerShell version $version is not 7.5.1" + if ([Version]$version -lt $required) { + throw "PowerShell version $version is less than $required" } - name: Capture setup info shell: pwsh From d379ba8fd6dea447725f796cf3d312aae863947e Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 13:48:13 -0700 Subject: [PATCH 08/23] Update requirements.json --- requirements.json | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/requirements.json b/requirements.json index 274844b4..9e7c033a 100644 --- a/requirements.json +++ b/requirements.json @@ -1,16 +1,9 @@ { - "runners": { - "windows-latest": { - "owner": "windows-latest", - "runner_label": "windows-latest", - "runner_type": "integration", - "skip_dry_run": false - }, "self-hosted-windows-lv": { "owner": "self-hosted-windows-lv", "runner_label": "self-hosted-windows-lv", "runner_type": "default", - "skip_dry_run": true + "skip_dry_run": false }, "ubuntu-latest": { "owner": "ubuntu-latest", From 68997757f70ffa91f431cf585e513005aba55edf Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 13:59:03 -0700 Subject: [PATCH 09/23] Update requirements.json --- requirements.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.json b/requirements.json index 9e7c033a..1da0394d 100644 --- a/requirements.json +++ b/requirements.json @@ -1,12 +1,12 @@ { "self-hosted-windows-lv": { - "owner": "self-hosted-windows-lv", + "owner": "NI", "runner_label": "self-hosted-windows-lv", "runner_type": "default", "skip_dry_run": false }, "ubuntu-latest": { - "owner": "ubuntu-latest", + "owner": "GitHub Actions", "runner_label": "ubuntu-latest", "runner_type": "integration", "skip_dry_run": false From 0d61fb727846c94e67cfb4926e2a061047a28986 Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 14:28:47 -0700 Subject: [PATCH 10/23] Use distinct self-hosted Windows runners --- .github/actionlint.yaml | 1 + .github/workflows/ci.json | 21 +++++++++++++-------- .github/workflows/ci.yml | 19 +++++++++++-------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index b3ea7cb4..f016bd08 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -1,3 +1,4 @@ self-hosted-runner: labels: - icon-editor-windows + - self-hosted-windows-lv diff --git a/.github/workflows/ci.json b/.github/workflows/ci.json index cb2bf908..527a13c4 100644 --- a/.github/workflows/ci.json +++ b/.github/workflows/ci.json @@ -83,12 +83,17 @@ { "os": "ubuntu-24.04", "runs-on": "ubuntu-24.04", - "runner_type": "default" + "runner_type": "linux" }, { - "os": "windows-latest", - "runs-on": "windows-latest", + "os": "self-hosted-windows-lv", + "runs-on": "self-hosted-windows-lv", "runner_type": "integration" + }, + { + "os": "self-hosted-windows-lv", + "runs-on": "self-hosted-windows-lv", + "runner_type": "default" } ] } @@ -103,21 +108,21 @@ }, { "name": "Ensure PowerShell 7.5.1", - "if": "matrix.os == 'windows-latest'", + "if": "matrix.runner_type != 'linux'", "shell": "pwsh", "run": "$required = [Version]'7.5.1'\nif ($PSVersionTable.PSVersion -lt $required) {\n $msiUrl = 'https://github.com/PowerShell/PowerShell/releases/download/v7.5.1/PowerShell-7.5.1-win-x64.msi'\n $msiPath = Join-Path $env:RUNNER_TEMP 'PowerShell-7.5.1-win-x64.msi'\n Invoke-WebRequest -Uri $msiUrl -OutFile $msiPath\n $expectedHash = 'b110eccaf55bb53ae5e6b6de478587ed8203570b0bda9bd374a0998e24d4033a'\n $actualHash = (Get-FileHash $msiPath -Algorithm SHA256).Hash\n if ($actualHash -ne $expectedHash) {\n throw \"SHA256 mismatch: $actualHash\"\n }\n Start-Process msiexec -Wait -ArgumentList '/i', $msiPath, '/qn', 'ADD_PATH=1'\n}\n$version = (pwsh --version).Trim() -replace '^PowerShell '\nif ([Version]$version -lt $required) {\n throw \"PowerShell version $version is less than $required\"\n}\n" }, { "name": "Capture setup info", "shell": "pwsh", - "run": "$data = [ordered]@{\n 'Current runner version' = $env:RUNNER_VERSION\n 'Runner Image' = $env:ImageOS\n 'ImageVersion' = $env:ImageVersion\n 'RUNNER_NAME' = $env:RUNNER_NAME\n 'RUNNER_OS' = $env:RUNNER_OS\n 'RUNNER_ARCH' = $env:RUNNER_ARCH\n}\n$file = \"setup-info-${{ matrix.os }}.md\"\n\"was captured from the set up job.\" | Out-File -FilePath $file -Encoding utf8\nforeach ($k in $data.Keys) {\n if ($data[$k]) {\n \"${k}: $($data[$k])\" | Out-File -FilePath $file -Encoding utf8 -Append\n }\n}\n" + "run": "$data = [ordered]@{\n 'Current runner version' = $env:RUNNER_VERSION\n 'Runner Image' = $env:ImageOS\n 'ImageVersion' = $env:ImageVersion\n 'RUNNER_NAME' = $env:RUNNER_NAME\n 'RUNNER_OS' = $env:RUNNER_OS\n 'RUNNER_ARCH' = $env:RUNNER_ARCH\n}\n$file = \"setup-info-${{ matrix.runner_type }}.md\"\n\"was captured from the set up job.\" | Out-File -FilePath $file -Encoding utf8\nforeach ($k in $data.Keys) {\n if ($data[$k]) {\n \"${k}: $($data[$k])\" | Out-File -FilePath $file -Encoding utf8 -Append\n }\n}\n" }, { "uses": "actions/upload-artifact@v4", "if": "always()", "with": { - "name": "setup-info-${{ matrix.os }}", - "path": "setup-info-${{ matrix.os }}.md" + "name": "setup-info-${{ matrix.runner_type }}", + "path": "setup-info-${{ matrix.runner_type }}.md" } }, { @@ -139,7 +144,7 @@ "uses": "actions/upload-artifact@v4", "if": "always()", "with": { - "name": "pester-junit-${{ matrix.os }}", + "name": "pester-junit-${{ matrix.runner_type }}", "path": "pester-results/pester-junit.xml", "if-no-files-found": "error" } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22109f9f..dfbd906c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,17 +49,20 @@ jobs: include: - os: ubuntu-24.04 runs-on: ubuntu-24.04 - runner_type: default - - os: windows-latest - runs-on: windows-latest + runner_type: linux + - os: self-hosted-windows-lv + runs-on: self-hosted-windows-lv runner_type: integration + - os: self-hosted-windows-lv + runs-on: self-hosted-windows-lv + runner_type: default runs-on: ${{ matrix.runs-on }} env: RUNNER_TYPE: ${{ matrix.runner_type }} steps: - uses: actions/checkout@v4 - name: Ensure PowerShell 7.5.1 - if: matrix.os == 'windows-latest' + if: matrix.runner_type != 'linux' shell: pwsh run: | $required = [Version]'7.5.1' @@ -89,7 +92,7 @@ jobs: 'RUNNER_OS' = $env:RUNNER_OS 'RUNNER_ARCH' = $env:RUNNER_ARCH } - $file = "setup-info-${{ matrix.os }}.md" + $file = "setup-info-${{ matrix.runner_type }}.md" "was captured from the set up job." | Out-File -FilePath $file -Encoding utf8 foreach ($k in $data.Keys) { if ($data[$k]) { @@ -99,8 +102,8 @@ jobs: - uses: actions/upload-artifact@v4 if: ${{ (success() || failure()) && !cancelled() }} with: - name: setup-info-${{ matrix.os }} - path: setup-info-${{ matrix.os }}.md + name: setup-info-${{ matrix.runner_type }} + path: setup-info-${{ matrix.runner_type }}.md - name: Install Pester shell: pwsh run: | @@ -124,7 +127,7 @@ jobs: - uses: actions/upload-artifact@v4 if: ${{ (success() || failure()) && !cancelled() }} with: - name: pester-junit-${{ matrix.os }} + name: pester-junit-${{ matrix.runner_type }} path: pester-results/pester-junit.xml if-no-files-found: error From 3da1a42caafe7689d3df22d767b25cfce50f04f1 Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 14:42:44 -0700 Subject: [PATCH 11/23] Include runner type in CI artifacts --- .github/workflows/ci.json | 8 ++++---- .github/workflows/ci.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.json b/.github/workflows/ci.json index 527a13c4..e5450c12 100644 --- a/.github/workflows/ci.json +++ b/.github/workflows/ci.json @@ -115,14 +115,14 @@ { "name": "Capture setup info", "shell": "pwsh", - "run": "$data = [ordered]@{\n 'Current runner version' = $env:RUNNER_VERSION\n 'Runner Image' = $env:ImageOS\n 'ImageVersion' = $env:ImageVersion\n 'RUNNER_NAME' = $env:RUNNER_NAME\n 'RUNNER_OS' = $env:RUNNER_OS\n 'RUNNER_ARCH' = $env:RUNNER_ARCH\n}\n$file = \"setup-info-${{ matrix.runner_type }}.md\"\n\"was captured from the set up job.\" | Out-File -FilePath $file -Encoding utf8\nforeach ($k in $data.Keys) {\n if ($data[$k]) {\n \"${k}: $($data[$k])\" | Out-File -FilePath $file -Encoding utf8 -Append\n }\n}\n" + "run": "$data = [ordered]@{\n 'Current runner version' = $env:RUNNER_VERSION\n 'Runner Image' = $env:ImageOS\n 'ImageVersion' = $env:ImageVersion\n 'RUNNER_NAME' = $env:RUNNER_NAME\n 'RUNNER_OS' = $env:RUNNER_OS\n 'RUNNER_ARCH' = $env:RUNNER_ARCH\n}\n$file = \"setup-info-${{ matrix.os }}-${{ matrix.runner_type }}.md\"\n\"was captured from the set up job.\" | Out-File -FilePath $file -Encoding utf8\nforeach ($k in $data.Keys) {\n if ($data[$k]) {\n \"${k}: $($data[$k])\" | Out-File -FilePath $file -Encoding utf8 -Append\n }\n}\n" }, { "uses": "actions/upload-artifact@v4", "if": "always()", "with": { - "name": "setup-info-${{ matrix.runner_type }}", - "path": "setup-info-${{ matrix.runner_type }}.md" + "name": "setup-info-${{ matrix.os }}-${{ matrix.runner_type }}", + "path": "setup-info-${{ matrix.os }}-${{ matrix.runner_type }}.md" } }, { @@ -144,7 +144,7 @@ "uses": "actions/upload-artifact@v4", "if": "always()", "with": { - "name": "pester-junit-${{ matrix.runner_type }}", + "name": "pester-junit-${{ matrix.os }}-${{ matrix.runner_type }}", "path": "pester-results/pester-junit.xml", "if-no-files-found": "error" } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfbd906c..c0dc9356 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,7 +92,7 @@ jobs: 'RUNNER_OS' = $env:RUNNER_OS 'RUNNER_ARCH' = $env:RUNNER_ARCH } - $file = "setup-info-${{ matrix.runner_type }}.md" + $file = "setup-info-${{ matrix.os }}-${{ matrix.runner_type }}.md" "was captured from the set up job." | Out-File -FilePath $file -Encoding utf8 foreach ($k in $data.Keys) { if ($data[$k]) { @@ -102,8 +102,8 @@ jobs: - uses: actions/upload-artifact@v4 if: ${{ (success() || failure()) && !cancelled() }} with: - name: setup-info-${{ matrix.runner_type }} - path: setup-info-${{ matrix.runner_type }}.md + name: setup-info-${{ matrix.os }}-${{ matrix.runner_type }} + path: setup-info-${{ matrix.os }}-${{ matrix.runner_type }}.md - name: Install Pester shell: pwsh run: | @@ -127,7 +127,7 @@ jobs: - uses: actions/upload-artifact@v4 if: ${{ (success() || failure()) && !cancelled() }} with: - name: pester-junit-${{ matrix.runner_type }} + name: pester-junit-${{ matrix.os }}-${{ matrix.runner_type }} path: pester-results/pester-junit.xml if-no-files-found: error From 62d98ea56b44e1045a98baa4271aad9783af393c Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 14:58:48 -0700 Subject: [PATCH 12/23] feat: split self-hosted Windows runners --- requirements.json | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/requirements.json b/requirements.json index 1da0394d..2842df3e 100644 --- a/requirements.json +++ b/requirements.json @@ -1,5 +1,12 @@ { - "self-hosted-windows-lv": { + "runners": { + "self-hosted-windows-lv-integration": { + "owner": "NI", + "runner_label": "self-hosted-windows-lv", + "runner_type": "integration", + "skip_dry_run": false + }, + "self-hosted-windows-lv-default": { "owner": "NI", "runner_label": "self-hosted-windows-lv", "runner_type": "default", @@ -257,7 +264,7 @@ "id": "REQIE-001", "description": "After checking out the LabVIEW icon editor repository, PreSequence: The sequencer shall enumerate and record the build matrix used by the workflow (LabVIEW versions and bitness). Acceptance: a 'matrix.json' file exists listing each tuple {\"lv-version\": \"2021\"|\"2023\", \"bitness\": \"32\"|\"64\"} with at least [ [\"2021\",\"32\"], [\"2021\",\"64\"], [\"2023\",\"64\"] ]. g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", "skip_dry_run": true, - "runner": "self-hosted-windows-lv", + "runner": "self-hosted-windows-lv-integration", "tests": [ "BuildProfile1.IconEditor.PreSequence.matrix-enumeration" ] @@ -266,7 +273,7 @@ "id": "REQIE-002", "description": "After checking out the LabVIEW icon editor repository, Setup: For each matrix entry, the sequencer shall apply VIPC dependencies using the canonical action inputs (minimum_supported_lv_version, vip_lv_version, supported_bitness, relative_path). Evidence: a 'vipc-apply.json' summary per matrix entry capturing inputs, start/end timestamps, exit code, and status. Acceptance: all entries report exit_code == 0 and status == 'success'. g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", "skip_dry_run": true, - "runner": "self-hosted-windows-lv", + "runner": "self-hosted-windows-lv-integration", "tests": [ "BuildProfile1.IconEditor.Setup.apply-vipc-succeeds" ] @@ -275,7 +282,7 @@ "id": "REQIE-003", "description": "After checking out the LabVIEW icon editor repository, Setup: The sequencer shall compute and persist semantic version information. Evidence: a 'version.json' containing VERSION, MAJOR, MINOR, PATCH, BUILD, IS_PRERELEASE and the commit SHA used. Acceptance: VERSION conforms to SemVer and all numeric components are present. g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", "skip_dry_run": true, - "runner": "self-hosted-windows-lv", + "runner": "self-hosted-windows-lv-integration", "tests": [ "BuildProfile1.IconEditor.Setup.version-outputs-captured" ] @@ -284,7 +291,7 @@ "id": "REQIE-004", "description": "After checking out the LabVIEW icon editor repository, Setup: The 'missing-in-project' check shall be executed for the specified LabVIEW version(s) and both 32-bit and 64-bit bitness as applicable. Evidence: 'missing-in-project.json' including project-file, lv-version, bitness, and result. Acceptance: result == 'present' for expected module paths. g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", "skip_dry_run": true, - "runner": "self-hosted-windows-lv", + "runner": "self-hosted-windows-lv-integration", "tests": [ "BuildProfile1.IconEditor.Setup.missing-in-project-matrix" ] @@ -293,7 +300,7 @@ "id": "REQIE-005", "description": "After checking out the LabVIEW icon editor repository, Main: Unit tests shall be executed (Pester) across the configured matrix. Evidence: a 'pester-summary.json' file containing total, passed, failed, skipped, duration_ms, and per-test results; XML output is not required. Acceptance: failed == 0 and total >= 1. g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", "skip_dry_run": true, - "runner": "self-hosted-windows-lv", + "runner": "self-hosted-windows-lv-integration", "tests": [ "BuildProfile1.IconEditor.Main.unit-tests-pass" ] @@ -302,7 +309,7 @@ "id": "REQIE-006", "description": "After checking out the LabVIEW icon editor repository, Main: Build Packed Libraries (PPL) shall succeed for both 32-bit and 64-bit targets. Evidence: existence of 'resource/plugins/lv_icon_x86.lvlibp' and 'resource/plugins/lv_icon_x64.lvlibp' and a 'ppl.hash' file containing SHA256 for each artifact. Acceptance: non-zero artifact sizes and matching SHA256 re-computation. g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", "skip_dry_run": true, - "runner": "self-hosted-windows-lv", + "runner": "self-hosted-windows-lv-integration", "tests": [ "BuildProfile1.IconEditor.Main.ppl-built-and-hashed", "BuildProfile1.IconEditor.Main.artifact-size-nonzero" @@ -312,7 +319,7 @@ "id": "REQIE-007", "description": "After checking out the LabVIEW icon editor repository, Main: Renaming and artifact upload steps shall complete. Evidence: an 'artifact-manifest.json' listing uploaded artifact names ('lv_icon_x86.lvlibp', 'lv_icon_x64.lvlibp'), paths, and sizes. Acceptance: manifest entries match on-disk files and GitHub Artifacts report success. g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", "skip_dry_run": true, - "runner": "self-hosted-windows-lv", + "runner": "self-hosted-windows-lv-integration", "tests": [ "BuildProfile1.IconEditor.Main.artifacts-renamed-and-uploaded" ] @@ -321,7 +328,7 @@ "id": "REQIE-008", "description": "After checking out the LabVIEW icon editor repository, Main: The workflow shall generate a VI Package (.vip) using the prescribed actions and inputs. Evidence: 'vi-package.hash' (SHA256 of produced .vip), and a 'vi-package.json' summarizing package metadata (name, version, company, build). Acceptance: at least one .vip exists, size > 0, and hash is recorded. g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", "skip_dry_run": true, - "runner": "self-hosted-windows-lv", + "runner": "self-hosted-windows-lv-integration", "tests": [ "BuildProfile1.IconEditor.Main.vi-package-built-and-hashed" ] @@ -330,7 +337,7 @@ "id": "REQIE-009", "description": "After checking out the LabVIEW icon editor repository, Main: The workflow shall produce a display information JSON for the VI Package and inject it prior to packaging. Evidence: 'display-info.json' containing the exact keys used by the action (Package Version.major/minor/patch/build, Product/Company/Author fields, etc.). Acceptance: all required keys exist and reflect the computed version (REQ-011). g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", "skip_dry_run": true, - "runner": "self-hosted-windows-lv", + "runner": "self-hosted-windows-lv-integration", "tests": [ "BuildProfile1.IconEditor.Main.display-info-generated" ] @@ -339,7 +346,7 @@ "id": "REQIE-010", "description": "After checking out the LabVIEW icon editor repository, Cleanup: The workflow shall terminate any LabVIEW processes via the 'close-labview' step for each applicable bitness. Evidence: 'close-labview.log' including bitness, attempts, and final process list. Acceptance: no LabVIEW process remains after the step. g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", "skip_dry_run": true, - "runner": "self-hosted-windows-lv", + "runner": "self-hosted-windows-lv-integration", "tests": [ "BuildProfile1.IconEditor.Cleanup.labview-closed" ] @@ -348,7 +355,7 @@ "id": "REQIE-011", "description": "After checking out the LabVIEW icon editor repository, PostSequence: The sequencer shall assemble a canonical single-line CI evidence string summarizing statuses and key facts for REQ-001..REQ-018. The string SHALL be a minified JSON assigned to a step output named 'CI_EVIDENCE' and saved as 'ci_evidence.txt'. Acceptance: the string parses as JSON and includes {\"pipeline\":\"IconEditor\", \"git_sha\":..., \"matrix\":[...], \"version\":{...}, \"artifacts\":{...}, \"req_status\":{\"REQ-001\":\"PASS\"|\"FAIL\", ...}}. g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", "skip_dry_run": true, - "runner": "self-hosted-windows-lv", + "runner": "self-hosted-windows-lv-integration", "tests": [ "BuildProfile1.IconEditor.PostSequence.ci-evidence-assembled", "BuildProfile1.IconEditor.PostSequence.ci-evidence-output" @@ -358,7 +365,7 @@ "id": "REQIE-012", "description": "After checking out the LabVIEW icon editor repository, PostSequence: The CI evidence string shall be logged succinctly to the job summary and made available to downstream jobs via $GITHUB_OUTPUT. Evidence: the step output 'CI_EVIDENCE' is present and a 'summary.md' contains a redacted preview. Acceptance: dependent jobs can read 'needs..outputs.CI_EVIDENCE' and parse it successfully. g-cli is expected at 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe'.", "skip_dry_run": true, - "runner": "self-hosted-windows-lv", + "runner": "self-hosted-windows-lv-integration", "tests": [ "BuildProfile1.IconEditor.PostSequence.ci-evidence-published" ] From 32957be8c154b69c544ab6ac0a1033726ad4fc96 Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 15:38:05 -0700 Subject: [PATCH 13/23] Rename missing-in-project arch input --- .../workflows/missing-in-project-self-hosted.json | 2 +- .github/workflows/missing-in-project-self-hosted.yml | 2 +- actions/OpenSourceActions.psm1 | 10 +++++----- dispatchers.json | 6 +++--- docs/action-call-reference.md | 2 +- docs/actions/missing-in-project.md | 8 ++++---- missing-in-project/action.yml | 6 +++--- .../Invoke-MissingInProjectCLI.ps1 | 12 ++++++------ scripts/missing-in-project/README.md | 10 +++++----- .../missing-in-project/RunMissingCheckWithGCLI.ps1 | 10 +++++----- tests/pester/Dispatcher.DryRun.Tests.ps1 | 1 - tests/pester/MissingInProject.Workflow.Tests.ps1 | 2 +- tests/pester/PathRestoration.Actions.Tests.ps1 | 2 +- 13 files changed, 36 insertions(+), 37 deletions(-) diff --git a/.github/workflows/missing-in-project-self-hosted.json b/.github/workflows/missing-in-project-self-hosted.json index f15eb060..bebe0dfe 100644 --- a/.github/workflows/missing-in-project-self-hosted.json +++ b/.github/workflows/missing-in-project-self-hosted.json @@ -15,7 +15,7 @@ "uses": "./missing-in-project/action.yml", "with": { "lv_version": "2021", - "arch": "64", + "supported_bitness": "64", "project_file": "scripts/missing-in-project/Missing in Project.lvproj", "relative_path": "scripts/missing-in-project" } diff --git a/.github/workflows/missing-in-project-self-hosted.yml b/.github/workflows/missing-in-project-self-hosted.yml index 37ab7806..e0f8fd3a 100644 --- a/.github/workflows/missing-in-project-self-hosted.yml +++ b/.github/workflows/missing-in-project-self-hosted.yml @@ -12,7 +12,7 @@ jobs: uses: ./missing-in-project/action.yml with: lv_version: '2021' - arch: '64' + supported_bitness: '64' project_file: 'scripts/missing-in-project/Missing in Project.lvproj' relative_path: 'scripts/missing-in-project' - name: Upload missing findings diff --git a/actions/OpenSourceActions.psm1 b/actions/OpenSourceActions.psm1 index f893b723..48efc0db 100644 --- a/actions/OpenSourceActions.psm1 +++ b/actions/OpenSourceActions.psm1 @@ -264,7 +264,7 @@ function Invoke-GenerateReleaseNotes { # Lists files referenced in a LabVIEW project that are missing on disk. # LVVersion: LabVIEW version of the project. -# Arch: Target architecture or bitness. +# SupportedBitness: Target LabVIEW bitness (32- or 64-bit). # ProjectFile: Path to the .lvproj file to analyze. # DryRun: If set, prints the command instead of executing it. # gcliPath: Optional path prepended to PATH for locating the g CLI. @@ -272,16 +272,16 @@ function Invoke-MissingInProject { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $LVVersion, - [Parameter(Mandatory)] [string] $Arch, + [Parameter(Mandatory)] [string] $SupportedBitness, [Parameter(Mandatory)] [string] $ProjectFile, [Parameter()] [switch] $DryRun, [Parameter()] [string] $gcliPath ) Write-Information "Executing MissingInProject (DryRun=$DryRun)" $args = @{ - LVVersion = $LVVersion - Arch = $Arch - ProjectFile = $ProjectFile + LVVersion = $LVVersion + SupportedBitness = $SupportedBitness + ProjectFile = $ProjectFile } return Invoke-OpenSourceActionScript -ScriptSegments @('missing-in-project','Invoke-MissingInProjectCLI.ps1') -Arguments $args -DryRun:$DryRun -gcliPath $gcliPath } diff --git a/dispatchers.json b/dispatchers.json index 06691590..16aa13a4 100644 --- a/dispatchers.json +++ b/dispatchers.json @@ -316,12 +316,12 @@ } }, "Invoke-MissingInProject": { - "description": "Lists files referenced in a LabVIEW project that are missing on disk. LVVersion: LabVIEW version of the project. Arch: Target architecture or bitness. ProjectFile: Path to the .lvproj file to analyze. DryRun: If set, prints the command instead of executing it. gcliPath: Optional path prepended to PATH for locating the g CLI.", + "description": "Lists files referenced in a LabVIEW project that are missing on disk. LVVersion: LabVIEW version of the project. SupportedBitness: Target LabVIEW bitness (32- or 64-bit). ProjectFile: Path to the .lvproj file to analyze. DryRun: If set, prints the command instead of executing it. gcliPath: Optional path prepended to PATH for locating the g CLI.", "parameters": { - "Arch": { + "SupportedBitness": { "type": "string", "required": true, - "description": "Target architecture or bitness." + "description": "Target LabVIEW bitness (32- or 64-bit)." }, "DryRun": { "type": "boolean", diff --git a/docs/action-call-reference.md b/docs/action-call-reference.md index a403dfc2..ba47abde 100644 --- a/docs/action-call-reference.md +++ b/docs/action-call-reference.md @@ -111,7 +111,7 @@ See [missing-in-project](actions/missing-in-project.md) for all parameters. - uses: LabVIEW-Community-CI-CD/open-source-actions/missing-in-project@v1 with: lv_version: '2020' - arch: '64' + supported_bitness: '64' project_file: 'MyProject.lvproj' ``` diff --git a/docs/actions/missing-in-project.md b/docs/actions/missing-in-project.md index 0176b083..9263a35d 100644 --- a/docs/actions/missing-in-project.md +++ b/docs/actions/missing-in-project.md @@ -11,7 +11,7 @@ Common parameters are described in [Common parameters](../common-parameters.md). ### Required - **LVVersion** (`string`): LabVIEW version used to open the project. -- **Arch** (`string`): "32" or "64" bitness of LabVIEW. +- **SupportedBitness** (`string`): "32" or "64" bitness of LabVIEW. - **ProjectFile** (`string`): Path to the project file to inspect. ### Optional @@ -23,7 +23,7 @@ None. ```powershell pwsh -File actions/Invoke-OSAction.ps1 -ActionName missing-in-project -ArgsJson '{ "LVVersion": "2020", - "Arch": "64", + "SupportedBitness": "64", "ProjectFile": "MyProject.lvproj" }' ``` @@ -35,7 +35,7 @@ GitHub Action inputs are provided in `snake_case`, while CLI parameters use `Pas | Input | CLI parameter | Description | | --- | --- | --- | | `lv_version` | `LVVersion` | LabVIEW version to use. | -| `arch` | `Arch` | Target architecture (32 or 64). | +| `supported_bitness` | `SupportedBitness` | Target LabVIEW bitness (32 or 64). | | `project_file` | `ProjectFile` | Path to the LabVIEW project (.lvproj). | | `gcli_path` | `gcliPath` | Optional path to the g-cli executable. | | `working_directory` | `WorkingDirectory` | Base directory for the action; relative paths are resolved from here. | @@ -49,7 +49,7 @@ GitHub Action inputs are provided in `snake_case`, while CLI parameters use `Pas uses: LabVIEW-Community-CI-CD/open-source-actions/missing-in-project@v1 with: lv_version: '2020' - arch: '64' + supported_bitness: '64' project_file: 'MyProject.lvproj' ``` diff --git a/missing-in-project/action.yml b/missing-in-project/action.yml index 894ef66a..4c9c8744 100644 --- a/missing-in-project/action.yml +++ b/missing-in-project/action.yml @@ -4,8 +4,8 @@ inputs: lv_version: description: 'LabVIEW version to use.' required: true - arch: - description: 'Target architecture (32 or 64).' + supported_bitness: + description: 'Target LabVIEW bitness (32 or 64).' required: true project_file: description: 'Path to the LabVIEW project (.lvproj).' @@ -33,7 +33,7 @@ runs: $ErrorActionPreference = 'Stop' $args = @{ LVVersion = '${{ inputs.lv_version }}' - Arch = '${{ inputs.arch }}' + SupportedBitness = '${{ inputs.supported_bitness }}' ProjectFile = '${{ inputs.project_file }}' } if ('${{ inputs.gcli_path }}') { $args['gcliPath'] = '${{ inputs.gcli_path }}' } diff --git a/scripts/missing-in-project/Invoke-MissingInProjectCLI.ps1 b/scripts/missing-in-project/Invoke-MissingInProjectCLI.ps1 index 454714f3..865c0595 100644 --- a/scripts/missing-in-project/Invoke-MissingInProjectCLI.ps1 +++ b/scripts/missing-in-project/Invoke-MissingInProjectCLI.ps1 @@ -2,7 +2,7 @@ [CmdletBinding()] param( [Parameter(Mandatory)][string]$LVVersion, - [Parameter(Mandatory)][ValidateSet('32','64')][string]$Arch, + [Parameter(Mandatory)][ValidateSet('32','64')][string]$SupportedBitness, [Parameter(Mandatory)][string]$ProjectFile ) @@ -24,9 +24,9 @@ if (-not (Test-Path $HelperPath)) { # ========================= SETUP ========================= function Setup { Write-Host "=== Setup ===" - Write-Host "LVVersion : $LVVersion" - Write-Host "Arch : $Arch-bit" - Write-Host "ProjectFile: $ProjectFile" + Write-Host "LVVersion : $LVVersion" + Write-Host "SupportedBitness: $SupportedBitness-bit" + Write-Host "ProjectFile : $ProjectFile" # remove an old results file to avoid stale data if (Test-Path $MissingFilePath) { @@ -42,11 +42,11 @@ function MainSequence { Write-Host "Invoking missing‑file check via helper script …`n" # call helper & capture any stdout (not strictly needed now) - & $HelperPath -LVVersion $LVVersion -Arch $Arch -ProjectFile $ProjectFile + & $HelperPath -LVVersion $LVVersion -SupportedBitness $SupportedBitness -ProjectFile $ProjectFile $Script:HelperExitCode = $LASTEXITCODE # Ensure LabVIEW is closed (redundant if helper did it) - & g-cli --lv-ver $LVVersion --arch $Arch QuitLabVIEW | Out-Null + & g-cli --lv-ver $LVVersion --arch $SupportedBitness QuitLabVIEW | Out-Null if ($Script:HelperExitCode -ne 0) { Write-Error "Helper returned non‑zero exit code: $Script:HelperExitCode" diff --git a/scripts/missing-in-project/README.md b/scripts/missing-in-project/README.md index 82fcc7ae..8f0867a6 100644 --- a/scripts/missing-in-project/README.md +++ b/scripts/missing-in-project/README.md @@ -39,7 +39,7 @@ Results are returned as standard GitHub Action outputs so downstream jobs can d | Name | Required | Example | Description | |------|----------|---------|-------------| | `lv-ver` | **Yes** | `2021` | LabVIEW _major_ version number that should be used to run `MissingInProjectCLI.vi` | -| `arch` | **Yes** | `32` or `64` | Bitness of the LabVIEW runtime to launch | +| `supported-bitness` | **Yes** | `32` or `64` | Bitness of the LabVIEW runtime to launch | | `project-file` | No | `source/MyPlugin.lvproj` | Path (absolute or relative to repository root) of the project to inspect. Defaults to **`lv_icon.lvproj`** | --- @@ -70,7 +70,7 @@ jobs: uses: ./.github/actions/missing-in-project with: lv-ver: 2021 - arch: 64 + supported-bitness: 64 - name: Print report if: ${{ steps.mip.outputs.passed == 'false' }} @@ -90,13 +90,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - arch: [32, 64] + supported_bitness: [32, 64] steps: - uses: actions/checkout@v4 - uses: ./.github/actions/missing-in-project with: lv-ver: 2021 - arch: ${{ matrix.arch }} + supported-bitness: ${{ matrix.supported_bitness }} build-package: needs: missing-in-project-check @@ -143,7 +143,7 @@ jobs: ```powershell pwsh -File .github/actions/missing-in-project/Invoke-MissingInProjectCLI.ps1 ` -LVVersion 2021 ` - -Arch 64 ` + -SupportedBitness 64 ` -ProjectFile 'C:\path\to\MyProj.lvproj' echo "Exit code: $LASTEXITCODE" diff --git a/scripts/missing-in-project/RunMissingCheckWithGCLI.ps1 b/scripts/missing-in-project/RunMissingCheckWithGCLI.ps1 index fe5032fa..ff83b18e 100644 --- a/scripts/missing-in-project/RunMissingCheckWithGCLI.ps1 +++ b/scripts/missing-in-project/RunMissingCheckWithGCLI.ps1 @@ -5,7 +5,7 @@ .PARAMETER LVVersion LabVIEW version (e.g. "2021"). -.PARAMETER Arch +.PARAMETER SupportedBitness Bitness ("32" or "64"). .PARAMETER ProjectFile @@ -19,7 +19,7 @@ [CmdletBinding()] param( [Parameter(Mandatory)][string]$LVVersion, - [Parameter(Mandatory)][ValidateSet('32','64')][string]$Arch, + [Parameter(Mandatory)][ValidateSet('32','64')][string]$SupportedBitness, [Parameter(Mandatory)][string]$ProjectFile ) $ErrorActionPreference = 'Stop' @@ -46,13 +46,13 @@ if (-not (Test-Path $ProjectFile)) { Write-Host "ℹ️ VI path : $viPath" Write-Host "ℹ️ Project file : $ProjectFile" -Write-Host "ℹ️ LabVIEW ver : $LVVersion ($Arch-bit)" +Write-Host "ℹ️ LabVIEW ver : $LVVersion ($SupportedBitness-bit)" Write-Host "--------------------------------------------------" # ---------- build argument list & invoke ---------- $gcliArgs = @( '--lv-ver', $LVVersion, - '--arch', $Arch, + '--arch', $SupportedBitness, $viPath, '--', $ProjectFile @@ -71,7 +71,7 @@ if ($exitCode -eq 0) { } # close LabVIEW if still running (harmless if not) -& g-cli --lv-ver $LVVersion --arch $Arch QuitLabVIEW | Out-Null +& g-cli --lv-ver $LVVersion --arch $SupportedBitness QuitLabVIEW | Out-Null $global:LASTEXITCODE = $exitCode return diff --git a/tests/pester/Dispatcher.DryRun.Tests.ps1 b/tests/pester/Dispatcher.DryRun.Tests.ps1 index 803084c2..9ff435d9 100644 --- a/tests/pester/Dispatcher.DryRun.Tests.ps1 +++ b/tests/pester/Dispatcher.DryRun.Tests.ps1 @@ -31,7 +31,6 @@ Describe 'Unified Dispatcher — DryRun behavior for all actions' { LabVIEWMinorRevision = '2021' VIPBPath = 'dummy.vipb' LVVersion = '2021' - Arch = '64' ProjectFile = 'Project.lvproj' Major = 1 Minor = 0 diff --git a/tests/pester/MissingInProject.Workflow.Tests.ps1 b/tests/pester/MissingInProject.Workflow.Tests.ps1 index 341ba137..5dd1cc21 100644 --- a/tests/pester/MissingInProject.Workflow.Tests.ps1 +++ b/tests/pester/MissingInProject.Workflow.Tests.ps1 @@ -24,7 +24,7 @@ Describe 'MissingInProject.Workflow' { $externalCheckout | Should -BeNullOrEmpty $missingStep.with.lv_version | Should -Be '2021' - $missingStep.with.arch | Should -Be '64' + $missingStep.with.supported_bitness | Should -Be '64' $missingStep.with.project_file | Should -Be 'scripts/missing-in-project/Missing in Project.lvproj' $missingStep.with.relative_path | Should -Be 'scripts/missing-in-project' diff --git a/tests/pester/PathRestoration.Actions.Tests.ps1 b/tests/pester/PathRestoration.Actions.Tests.ps1 index f15b8c11..97462006 100644 --- a/tests/pester/PathRestoration.Actions.Tests.ps1 +++ b/tests/pester/PathRestoration.Actions.Tests.ps1 @@ -28,7 +28,7 @@ Describe 'Adapters restore PATH' -Skip { @{ Func='Invoke-BuildLvlibp'; Script=[System.IO.Path]::Combine($repoRoot,'scripts','build-lvlibp','Build_lvlibp.ps1'); Args=@{ MinimumSupportedLVVersion='2021'; SupportedBitness='64'; RelativePath='.'; LabVIEW_Project='Proj'; Build_Spec='Spec'; Major=1; Minor=0; Patch=0; Build=1; Commit='abc' } }, @{ Func='Invoke-CloseLabVIEW'; Script=[System.IO.Path]::Combine($repoRoot,'scripts','close-labview','Close_LabVIEW.ps1'); Args=@{ MinimumSupportedLVVersion='2021'; SupportedBitness='64' } }, @{ Func='Invoke-GenerateReleaseNotes'; Script=[System.IO.Path]::Combine($repoRoot,'scripts','generate-release-notes','GenerateReleaseNotes.ps1'); Args=@{ OutputPath='notes.md' } }, - @{ Func='Invoke-MissingInProject'; Script=[System.IO.Path]::Combine($repoRoot,'scripts','missing-in-project','Invoke-MissingInProjectCLI.ps1'); Args=@{ LVVersion='2021'; Arch='64'; ProjectFile='Proj.lvproj' } }, + @{ Func='Invoke-MissingInProject'; Script=[System.IO.Path]::Combine($repoRoot,'scripts','missing-in-project','Invoke-MissingInProjectCLI.ps1'); Args=@{ LVVersion='2021'; SupportedBitness='64'; ProjectFile='Proj.lvproj' } }, @{ Func='Invoke-ModifyVIPBDisplayInfo'; Script=[System.IO.Path]::Combine($repoRoot,'scripts','modify-vipb-display-info','ModifyVIPBDisplayInfo.ps1'); Args=@{ SupportedBitness='64'; RelativePath='.'; VIPBPath='dummy.vipb'; MinimumSupportedLVVersion='2021'; LabVIEWMinorRevision='2021'; Major=1; Minor=0; Patch=0; Build=1; Commit='abc'; DisplayInformationJSON='{}' } }, @{ Func='Invoke-PrepareLabVIEWSource'; Script=[System.IO.Path]::Combine($repoRoot,'scripts','prepare-labview-source','Prepare_LabVIEW_source.ps1'); Args=@{ MinimumSupportedLVVersion='2021'; SupportedBitness='64'; RelativePath='.'; LabVIEW_Project='Proj'; Build_Spec='Spec' } }, @{ Func='Invoke-RenameFile'; Script=[System.IO.Path]::Combine($repoRoot,'scripts','rename-file','Rename-file.ps1'); Args=@{ CurrentFilename='a'; NewFilename='b' } }, From ba6b4ecafef793d21e2cde52e576bcc909246f4e Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 16:03:50 -0700 Subject: [PATCH 14/23] chore: regenerate dispatcher registry --- dispatchers.json | 231 ++++++++++++-------------- docs/adapter-authoring.md | 2 +- scripts/derive-dispatcher-registry.ts | 15 +- 3 files changed, 118 insertions(+), 130 deletions(-) diff --git a/dispatchers.json b/dispatchers.json index 16aa13a4..a15c1ffb 100644 --- a/dispatchers.json +++ b/dispatchers.json @@ -5,17 +5,17 @@ "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "MinimumSupportedLVVersion": { "type": "string", "required": true, - "description": "Minimum LabVIEW version that the project supports." + "description": "Minimum LabVIEW version that the project supports" }, "RelativePath": { "type": "string", @@ -25,7 +25,7 @@ "SupportedBitness": { "type": "string", "required": true, - "description": "Target LabVIEW bitness (32- or 64-bit)." + "description": "Target LabVIEW bitness (32- or 64-bit)" } } }, @@ -35,17 +35,17 @@ "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "MinimumSupportedLVVersion": { "type": "string", "required": true, - "description": "Minimum LabVIEW version that the project supports." + "description": "Minimum LabVIEW version that the project supports" }, "RelativePath": { "type": "string", @@ -55,17 +55,17 @@ "SupportedBitness": { "type": "string", "required": true, - "description": "Target LabVIEW bitness (32- or 64-bit)." + "description": "Target LabVIEW bitness (32- or 64-bit)" }, "VIP_LVVersion": { "type": "string", "required": true, - "description": "LabVIEW version used to build the VI Package Configuration." + "description": "LabVIEW version used to build the VIPC" }, "VIPCPath": { "type": "string", "required": false, - "description": "Path to the VI Package Configuration file." + "description": "Optional path to the VIPC file" } } }, @@ -75,52 +75,52 @@ "AuthorName": { "type": "string", "required": true, - "description": "Author name recorded in build metadata." + "description": "Author name recorded in build metadata" }, "Build": { "type": "number", "required": true, - "description": "Build number component." + "description": "Build number component" }, "Commit": { "type": "string", "required": true, - "description": "Commit identifier used for the build metadata." + "description": "Commit identifier used for the build metadata" }, "CompanyName": { "type": "string", "required": true, - "description": "Company name recorded in build metadata." + "description": "Company name recorded in build metadata" }, "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "LabVIEWMinorRevision": { "type": "string", "required": true, - "description": "Minor revision of LabVIEW used for the build." + "description": "Minor revision of LabVIEW used for the build" }, "Major": { "type": "number", "required": true, - "description": "Major version component." + "description": "Major version component" }, "Minor": { "type": "number", "required": true, - "description": "Minor version component." + "description": "Minor version component" }, "Patch": { "type": "number", "required": true, - "description": "Patch version component." + "description": "Patch version component" }, "RelativePath": { "type": "string", @@ -135,52 +135,52 @@ "Build": { "type": "number", "required": true, - "description": "Build number component." + "description": "Build number component" }, "Build_Spec": { "type": "string", "required": true, - "description": "Name of the build specification to run." + "description": "Name of the build specification within the project" }, "Commit": { "type": "string", "required": true, - "description": "Commit identifier used for the build metadata." + "description": "Commit identifier used for the build metadata" }, "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "LabVIEW_Project": { "type": "string", "required": true, - "description": "Path to the LabVIEW project file." + "description": "Path to the LabVIEW project file" }, "Major": { "type": "number", "required": true, - "description": "Major version component." + "description": "Major version component" }, "MinimumSupportedLVVersion": { "type": "string", "required": true, - "description": "Minimum LabVIEW version that the library supports." + "description": "Minimum LabVIEW version that the library supports" }, "Minor": { "type": "number", "required": true, - "description": "Minor version component." + "description": "Minor version component" }, "Patch": { "type": "number", "required": true, - "description": "Patch version component." + "description": "Patch version component" }, "RelativePath": { "type": "string", @@ -190,7 +190,7 @@ "SupportedBitness": { "type": "string", "required": true, - "description": "Target LabVIEW bitness (32- or 64-bit)." + "description": "Target LabVIEW bitness (32- or 64-bit)" } } }, @@ -200,52 +200,52 @@ "Build": { "type": "number", "required": true, - "description": "Build number component." + "description": "Build number component" }, "Commit": { "type": "string", "required": true, - "description": "Commit identifier used for the build metadata." + "description": "Commit identifier used for the build metadata" }, "DisplayInformationJSON": { "type": "string", "required": true, - "description": "Path to JSON file containing display information." + "description": "JSON string containing display information for the package" }, "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "LabVIEWMinorRevision": { "type": "string", "required": true, - "description": "Minor revision of LabVIEW used to build the package." + "description": "Minor revision of LabVIEW used to build the package" }, "Major": { "type": "number", "required": true, - "description": "Major version component." + "description": "Major version component" }, "MinimumSupportedLVVersion": { "type": "string", "required": true, - "description": "Minimum LabVIEW version that the package supports." + "description": "Minimum LabVIEW version that the package supports" }, "Minor": { "type": "number", "required": true, - "description": "Minor version component." + "description": "Minor version component" }, "Patch": { "type": "number", "required": true, - "description": "Patch version component." + "description": "Patch version component" }, "RelativePath": { "type": "string", @@ -255,17 +255,17 @@ "ReleaseNotesFile": { "type": "string", "required": false, - "description": "Path to the release notes file." + "description": "Optional path to a release notes file" }, "SupportedBitness": { "type": "string", "required": true, - "description": "Target LabVIEW bitness (32- or 64-bit)." + "description": "Target LabVIEW bitness (32- or 64-bit)" }, "VIPBPath": { "type": "string", "required": true, - "description": "Path to the VI Package Build (.vipb) file." + "description": "Path to the VIPB build specification file" } } }, @@ -275,22 +275,22 @@ "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "MinimumSupportedLVVersion": { "type": "string", "required": true, - "description": "Minimum LabVIEW version that the project supports." + "description": "Minimum LabVIEW version that the project supports" }, "SupportedBitness": { "type": "string", "required": true, - "description": "Target LabVIEW bitness (32- or 64-bit)." + "description": "Target LabVIEW bitness (32- or 64-bit)" } } }, @@ -300,48 +300,48 @@ "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "OutputPath": { "type": "string", "required": false, "default": "Tooling/deployment/release_notes.md", - "description": "Path where generated release notes will be written." + "description": "Path where the release notes should be written" } } }, "Invoke-MissingInProject": { "description": "Lists files referenced in a LabVIEW project that are missing on disk. LVVersion: LabVIEW version of the project. SupportedBitness: Target LabVIEW bitness (32- or 64-bit). ProjectFile: Path to the .lvproj file to analyze. DryRun: If set, prints the command instead of executing it. gcliPath: Optional path prepended to PATH for locating the g CLI.", "parameters": { - "SupportedBitness": { - "type": "string", - "required": true, - "description": "Target LabVIEW bitness (32- or 64-bit)." - }, "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "LVVersion": { "type": "string", "required": true, - "description": "LabVIEW version of the project." + "description": "LabVIEW version of the project" }, "ProjectFile": { "type": "string", "required": true, - "description": "Path to the LabVIEW project file." + "description": "Path to the" + }, + "SupportedBitness": { + "type": "string", + "required": true, + "description": "Target LabVIEW bitness (32- or 64-bit)" } } }, @@ -351,52 +351,52 @@ "Build": { "type": "number", "required": true, - "description": "Build number component." + "description": "Build number component" }, "Commit": { "type": "string", "required": true, - "description": "Commit identifier used for the build metadata." + "description": "Commit identifier used for the build metadata" }, "DisplayInformationJSON": { "type": "string", "required": true, - "description": "Path to JSON file containing display information." + "description": "JSON string containing display information for the package" }, "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "LabVIEWMinorRevision": { "type": "string", "required": true, - "description": "Minor revision of LabVIEW used for the build." + "description": "Minor revision of LabVIEW used for the build" }, "Major": { "type": "number", "required": true, - "description": "Major version component." + "description": "Major version component" }, "MinimumSupportedLVVersion": { "type": "string", "required": true, - "description": "Minimum LabVIEW version that the package supports." + "description": "Minimum LabVIEW version that the package supports" }, "Minor": { "type": "number", "required": true, - "description": "Minor version component." + "description": "Minor version component" }, "Patch": { "type": "number", "required": true, - "description": "Patch version component." + "description": "Patch version component" }, "RelativePath": { "type": "string", @@ -406,17 +406,17 @@ "ReleaseNotesFile": { "type": "string", "required": false, - "description": "Path to the release notes file." + "description": "Optional path to a release notes file" }, "SupportedBitness": { "type": "string", "required": true, - "description": "Target LabVIEW bitness (32- or 64-bit)." + "description": "Target LabVIEW bitness (32- or 64-bit)" }, "VIPBPath": { "type": "string", "required": true, - "description": "Path to the VI Package Build (.vipb) file." + "description": "Path to the VIPB build specification file" } } }, @@ -426,27 +426,27 @@ "Build_Spec": { "type": "string", "required": true, - "description": "Name of the build specification to run." + "description": "Name of the build specification within the project" }, "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "LabVIEW_Project": { "type": "string", "required": true, - "description": "Path to the LabVIEW project file." + "description": "Path to the LabVIEW project file" }, "MinimumSupportedLVVersion": { "type": "string", "required": true, - "description": "Minimum LabVIEW version that the project supports." + "description": "Minimum LabVIEW version that the project supports" }, "RelativePath": { "type": "string", @@ -456,7 +456,7 @@ "SupportedBitness": { "type": "string", "required": true, - "description": "Target LabVIEW bitness (32- or 64-bit)." + "description": "Target LabVIEW bitness (32- or 64-bit)" } } }, @@ -466,22 +466,22 @@ "CurrentFilename": { "type": "string", "required": true, - "description": "Existing path to the file." + "description": "Existing path to the file" }, "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "NewFilename": { "type": "string", "required": true, - "description": "New path for the file." + "description": "New path for the file" } } }, @@ -491,27 +491,27 @@ "Build_Spec": { "type": "string", "required": true, - "description": "Name of the build specification to run." + "description": "Name of the build specification within the project" }, "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "LabVIEW_Project": { "type": "string", "required": true, - "description": "Path to the LabVIEW project file." + "description": "Path to the LabVIEW project file" }, "MinimumSupportedLVVersion": { "type": "string", "required": true, - "description": "Minimum LabVIEW version that the project supports." + "description": "Minimum LabVIEW version that the project supports" }, "RelativePath": { "type": "string", @@ -521,7 +521,7 @@ "SupportedBitness": { "type": "string", "required": true, - "description": "Target LabVIEW bitness (32- or 64-bit)." + "description": "Target LabVIEW bitness (32- or 64-bit)" } } }, @@ -531,12 +531,12 @@ "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "RelativePath": { "type": "string", @@ -551,17 +551,17 @@ "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "WorkingDirectory": { "type": "string", "required": true, - "description": "Path containing the Pester tests to execute." + "description": "Path containing the Pester tests to execute" } } }, @@ -571,22 +571,22 @@ "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "MinimumSupportedLVVersion": { "type": "string", "required": true, - "description": "Minimum LabVIEW version that the project supports." + "description": "Minimum LabVIEW version that the project supports" }, "SupportedBitness": { "type": "string", "required": true, - "description": "Target LabVIEW bitness (32- or 64-bit)." + "description": "Target LabVIEW bitness (32- or 64-bit)" } } }, @@ -596,12 +596,12 @@ "DryRun": { "type": "boolean", "required": false, - "description": "If set, prints the command instead of executing it." + "description": "If set, prints the command instead of executing it" }, "gcliPath": { "type": "string", "required": false, - "description": "Path prepended to PATH for locating the g CLI." + "description": "Optional path prepended to PATH for locating the g CLI" }, "RelativePath": { "type": "string", @@ -610,39 +610,14 @@ } } }, - "Run": { - "description": "Runs a helper script from the repository's scripts directory. ScriptSegments: Path segments under the scripts folder that locate the target script. Arguments: Hashtable of arguments forwarded to the script. DryRun: If set, writes the command without executing it. gcliPath: Optional path prepended to PATH for locating the g CLI.", - "parameters": { - "Arguments": { - "type": "string", - "required": true, - "description": "Hashtable of arguments forwarded to the script." - }, - "DryRun": { - "type": "boolean", - "required": false, - "description": "If set, prints the command instead of executing it." - }, - "gcliPath": { - "type": "string", - "required": false, - "description": "Path prepended to PATH for locating the g CLI." - }, - "ScriptSegments": { - "type": "string", - "required": true, - "description": "Path segments under the scripts folder that locate the target script." - } - } - }, - "Set": { + "Set-LogLevel": { "description": "Sets the verbosity for informational and verbose messages. Level: Desired log level (ERROR, WARN, INFO, DEBUG).", "parameters": { "Level": { "type": "string", "required": false, - "description": "Desired log level (ERROR, WARN, INFO, DEBUG)." + "description": "Desired log level (ERROR, WARN, INFO, DEBUG)" } } } -} +} \ No newline at end of file diff --git a/docs/adapter-authoring.md b/docs/adapter-authoring.md index 9db0f445..17c82aa7 100644 --- a/docs/adapter-authoring.md +++ b/docs/adapter-authoring.md @@ -59,7 +59,7 @@ function Invoke-MyNewAction { After adding your adapter function, regenerate the dispatcher registry: -1. Run `npm run derive:registry` to update `dispatchers.json`. +1. Run `npx tsx scripts/derive-dispatcher-registry.ts` to update `dispatchers.json`. 2. Commit the regenerated file. ## Logging and Verbosity diff --git a/scripts/derive-dispatcher-registry.ts b/scripts/derive-dispatcher-registry.ts index 495a0e06..cf882c46 100644 --- a/scripts/derive-dispatcher-registry.ts +++ b/scripts/derive-dispatcher-registry.ts @@ -15,6 +15,17 @@ interface FuncInfo { parameters: Record; } +function attachParamDescriptions(info: FuncInfo) { + if (!info.description) return; + for (const [name, param] of Object.entries(info.parameters)) { + const regex = new RegExp(`\\b${name}:\\s*([^\\.]+)`, 'i'); + const match = info.description.match(regex); + if (match) { + param.description = match[1].trim(); + } + } +} + function parseParams(block: string): Record { const params: Record = {}; const lines = block.split(/\r?\n/).map(l => l.trim()).filter(Boolean); @@ -94,7 +105,7 @@ async function main() { const registry: Record = {}; for (const file of files) { const content = await fs.readFile(file, 'utf8'); - const fnRegex = /function\s+(\w+)\b/gi; + const fnRegex = /function\s+([\w-]+)\b/gi; let match: RegExpExecArray | null; while ((match = fnRegex.exec(content)) !== null) { const fn = match[1]; @@ -104,8 +115,10 @@ async function main() { if (!paramsBlock) continue; const description = extractDescription(content, match.index); registry[fn] = { description, parameters: parseParams(paramsBlock) }; + attachParamDescriptions(registry[fn]); } } + delete registry['Invoke-OpenSourceActionScript']; for (const info of Object.values(registry)) { if (info.parameters.RelativePath) { info.parameters.RelativePath.description = From 313bb36f47ce426aab7900dcf4f2f5a8cdc58bff Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 16:18:52 -0700 Subject: [PATCH 15/23] Standardize PowerShell error handling --- scripts/build/Build.ps1 | 11 +++++------ scripts/build/README.md | 4 ++++ scripts/revert-development-mode/README.md | 4 ++++ .../revert-development-mode/RevertDevelopmentMode.ps1 | 2 +- scripts/set-development-mode/README.md | 4 ++++ scripts/set-development-mode/Set_Development_Mode.ps1 | 3 ++- tests/pester/Dispatcher.InvalidPaths.Tests.ps1 | 3 ++- 7 files changed, 22 insertions(+), 9 deletions(-) diff --git a/scripts/build/Build.ps1 b/scripts/build/Build.ps1 index c214a313..12b5482c 100644 --- a/scripts/build/Build.ps1 +++ b/scripts/build/Build.ps1 @@ -82,15 +82,14 @@ function Execute-Script { Invoke-Expression $command Write-Verbose "Command completed. Checking exit code..." if ($LASTEXITCODE -ne 0) { - Write-Host "Error occurred while executing: `"$ScriptPath`" with arguments: `"$Arguments`". Exit code: $LASTEXITCODE" -ForegroundColor Red - exit $LASTEXITCODE + Write-Error "Error occurred while executing: `"$ScriptPath`" with arguments: `"$Arguments`". Exit code: $LASTEXITCODE" + throw "Script failed with exit code $LASTEXITCODE" } Write-Verbose "Exit code is 0; no errors detected." } catch { - Write-Host "Error occurred while executing: `"$ScriptPath`" with arguments: `"$Arguments`". Exiting." -ForegroundColor Red - Write-Verbose "Exception details: $($_.Exception.Message)" - exit 1 + Write-Error "Error occurred while executing: `"$ScriptPath`" with arguments: `"$Arguments`"" + throw } } @@ -266,7 +265,7 @@ try { Write-Verbose "Script: Build.ps1 completed without errors." } catch { - Write-Host "An unexpected error occurred during script execution: $($_.Exception.Message)" -ForegroundColor Red + Write-Error "An unexpected error occurred during script execution: $($_.Exception.Message)" Write-Verbose "Stack Trace: $($_.Exception.StackTrace)" exit 1 } diff --git a/scripts/build/README.md b/scripts/build/README.md index 77fc1fe3..fcb1c10c 100644 --- a/scripts/build/README.md +++ b/scripts/build/README.md @@ -33,6 +33,10 @@ Runs **`Build.ps1`** to clean, compile, and package the LabVIEW Icon Editor. See also: [docs/actions/build.md](../../docs/actions/build.md) +## Error handling + +On failure the script outputs `An unexpected error occurred during script execution:
` and returns a non-zero exit code. + ## License This directory inherits the root repository’s license (MIT, unless otherwise noted). diff --git a/scripts/revert-development-mode/README.md b/scripts/revert-development-mode/README.md index 30afed6f..fecd5dcb 100644 --- a/scripts/revert-development-mode/README.md +++ b/scripts/revert-development-mode/README.md @@ -18,6 +18,10 @@ Invoke **`RevertDevelopmentMode.ps1`** to restore packaged sources after develop See also: [docs/actions/revert-development-mode.md](../../docs/actions/revert-development-mode.md) +## Error handling + +Failures emit `An unexpected error occurred during script execution:
` and the script exits with a non-zero status. + ## License This directory inherits the root repository’s license (MIT, unless otherwise noted). diff --git a/scripts/revert-development-mode/RevertDevelopmentMode.ps1 b/scripts/revert-development-mode/RevertDevelopmentMode.ps1 index c8c3b630..0f7589fb 100644 --- a/scripts/revert-development-mode/RevertDevelopmentMode.ps1 +++ b/scripts/revert-development-mode/RevertDevelopmentMode.ps1 @@ -41,7 +41,7 @@ function Execute-Script { throw "Script failed with exit code $LASTEXITCODE" } } catch { - Write-Error "Error occurred while executing: $ScriptCommand. $_" + Write-Error "Error occurred while executing: $ScriptCommand" if (-not $LASTEXITCODE) { $global:LASTEXITCODE = 1 } diff --git a/scripts/set-development-mode/README.md b/scripts/set-development-mode/README.md index d5f2c1bb..9a9a8654 100644 --- a/scripts/set-development-mode/README.md +++ b/scripts/set-development-mode/README.md @@ -18,6 +18,10 @@ Execute **`Set_Development_Mode.ps1`** to prepare the repository for active deve See also: [docs/actions/set-development-mode.md](../../docs/actions/set-development-mode.md) +## Error handling + +Failures produce the message `An unexpected error occurred during script execution:
` and the script exits with a non-zero status. + ## License This directory inherits the root repository’s license (MIT, unless otherwise noted). diff --git a/scripts/set-development-mode/Set_Development_Mode.ps1 b/scripts/set-development-mode/Set_Development_Mode.ps1 index 34d5cf58..23604c93 100644 --- a/scripts/set-development-mode/Set_Development_Mode.ps1 +++ b/scripts/set-development-mode/Set_Development_Mode.ps1 @@ -27,7 +27,8 @@ function Execute-Script { try { Invoke-Expression $ScriptCommand -ErrorAction Stop } catch { - throw "Error occurred while executing: $ScriptCommand" + Write-Error "Error occurred while executing: $ScriptCommand" + throw } } # Sequential script execution with error handling diff --git a/tests/pester/Dispatcher.InvalidPaths.Tests.ps1 b/tests/pester/Dispatcher.InvalidPaths.Tests.ps1 index 4d1d17d1..1010b75a 100644 --- a/tests/pester/Dispatcher.InvalidPaths.Tests.ps1 +++ b/tests/pester/Dispatcher.InvalidPaths.Tests.ps1 @@ -11,6 +11,7 @@ Import-Module (Join-Path $PSScriptRoot 'Helper' 'ArgsJson.psm1') $params = Get-LabVIEWIconEditorArgsJson $projectRoot = $params.WorkingDirectory +$errorText = 'An unexpected error occurred during script execution' Describe 'Invalid RelativePath handling' { $meta = @{ @@ -23,7 +24,7 @@ Describe 'Invalid RelativePath handling' { $json = @{ RelativePath = 'NoSuchDir' } | ConvertTo-Json -Compress $out = pwsh -NonInteractive -NoProfile -File $global:dispatcher -ActionName set-development-mode -ArgsJson $json -WorkingDirectory $projectRoot *>&1 | Out-String $LASTEXITCODE | Should -Not -Be 0 - $out | Should -Match 'An unexpected error occurred during script execution' + $out | Should -Match $errorText } It 'fails when RelativePath is missing' -Tag 'REQ-005' { From 396f2bb6cee8f20a1e8b6884a1f8ce48c7709747 Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 16:35:03 -0700 Subject: [PATCH 16/23] test: expand dispatcher args input tests --- docs/UnifiedDispatcher.md | 11 ++++ tests/pester/Dispatcher.ArgsInputs.Tests.ps1 | 63 +++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/docs/UnifiedDispatcher.md b/docs/UnifiedDispatcher.md index 5c4ccf5c..268d7c49 100644 --- a/docs/UnifiedDispatcher.md +++ b/docs/UnifiedDispatcher.md @@ -34,6 +34,17 @@ Arguments can also be read from a JSON file: pwsh ./actions/Invoke-OSAction.ps1 -ActionName run-unit-tests -ArgsFile ./config/run-tests.json -LogLevel INFO ``` +### Argument precedence + +When multiple argument sources are supplied, values are merged in the following order: + +1. `ArgsFile` +2. `ArgsJson` +3. `ArgsHashtable` +4. dispatcher switches (for example, `-DryRun`) + +Later entries override earlier ones. Unknown parameters from any source are ignored and emitted as warnings. + ## Wrapper action usage ```yaml diff --git a/tests/pester/Dispatcher.ArgsInputs.Tests.ps1 b/tests/pester/Dispatcher.ArgsInputs.Tests.ps1 index 6257bb56..c68c7172 100644 --- a/tests/pester/Dispatcher.ArgsInputs.Tests.ps1 +++ b/tests/pester/Dispatcher.ArgsInputs.Tests.ps1 @@ -1,3 +1,14 @@ +#requires -Version 7.0 +# Pester tests verifying dispatcher argument inputs and precedence. +# Requirement: REQ-000 - Dispatcher merges argument sources and warns on unknown parameters. + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..' '..')).Path +$global:dispatcher = Join-Path $repoRoot 'actions' 'Invoke-OSAction.ps1' +Import-Module (Join-Path $PSScriptRoot 'Helper' 'ArgsJson.psm1') + Describe 'Dispatcher Args Inputs' { $meta = @{ requirement = 'REQ-000' @@ -5,7 +16,55 @@ Describe 'Dispatcher Args Inputs' { Evidence = 'tests/pester/Dispatcher.ArgsInputs.Tests.ps1' } - It 'dummy test' -Tag 'REQ-000' { - $true | Should -BeTrue + It 'accepts ArgsJson and warns on unknown parameters' -Tag 'REQ-000' { + $params = Get-LabVIEWIconEditorArgsJson + $projectRoot = $params.WorkingDirectory + + $json = @{ + MinimumSupportedLVVersion = '2021' + SupportedBitness = '64' + Extra = 'value' + } | ConvertTo-Json -Compress + + $out = & $global:dispatcher -ActionName close-labview -ArgsJson $json -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String + $LASTEXITCODE | Should -Be 0 + $out | Should -Match '"SupportedBitness":"64"' + $out | Should -Match "Ignored unknown parameters for 'close-labview': Extra" + } + + It 'accepts ArgsFile and warns on unknown parameters' -Tag 'REQ-000' { + $params = Get-LabVIEWIconEditorArgsJson + $projectRoot = $params.WorkingDirectory + + $jsonFile = Join-Path $TestDrive 'args.json' + @{ MinimumSupportedLVVersion = '2021'; SupportedBitness = '64'; Extra = 'value' } | + ConvertTo-Json -Compress | Set-Content -Path $jsonFile + + $out = & $global:dispatcher -ActionName close-labview -ArgsFile $jsonFile -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String + $LASTEXITCODE | Should -Be 0 + $out | Should -Match '"SupportedBitness":"64"' + $out | Should -Match "Ignored unknown parameters for 'close-labview': Extra" + } + + It 'uses inline args to override file and warns on all unknown parameters' -Tag 'REQ-000' { + $params = Get-LabVIEWIconEditorArgsJson + $projectRoot = $params.WorkingDirectory + + $file = Join-Path $TestDrive 'args.json' + @{ MinimumSupportedLVVersion = '2021'; SupportedBitness = '32'; FileExtra = 1 } | + ConvertTo-Json -Compress | Set-Content -Path $file + + $json = @{ SupportedBitness = '64'; JsonExtra = 2 } | ConvertTo-Json -Compress + $table = @{ MinimumSupportedLVVersion = '2019'; TableExtra = 3 } + + $out = & $global:dispatcher -ActionName close-labview -ArgsFile $file -ArgsJson $json -ArgsHashtable $table -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String + $LASTEXITCODE | Should -Be 0 + $out | Should -Match '"MinimumSupportedLVVersion":"2019"' + $out | Should -Match '"SupportedBitness":"64"' + $out | Should -Match 'Ignored unknown parameters' + $out | Should -Match 'FileExtra' + $out | Should -Match 'JsonExtra' + $out | Should -Match 'TableExtra' } } + From 7dc212b782c369c2a67aea458c1b38f2b9bad99d Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 16:59:36 -0700 Subject: [PATCH 17/23] Document relative path normalization --- actions/Invoke-OSAction.ps1 | 23 +++++++++++ actions/OpenSourceActions.psm1 | 23 ++++++----- docs/relative-paths.md | 8 ++++ docs/requirements.md | 2 +- tests/pester/RelativePath.Actions.Tests.ps1 | 45 +++++++++++---------- 5 files changed, 68 insertions(+), 33 deletions(-) create mode 100644 docs/relative-paths.md diff --git a/actions/Invoke-OSAction.ps1 b/actions/Invoke-OSAction.ps1 index 3dfd31e6..23e2229e 100644 --- a/actions/Invoke-OSAction.ps1 +++ b/actions/Invoke-OSAction.ps1 @@ -132,6 +132,25 @@ function Filter-Args([hashtable]$InputArgs, [string]$FuncName, [string]$ActionNa return $filtered } +# Normalizes a RelativePath value against an optional base directory. +# RelativePath: Path to normalize. +# BaseDirectory: Directory used to resolve the relative path. Defaults to the current location. +function Normalize-RelativePath { + param( + [Parameter(Mandatory)] [string] $RelativePath, + [string] $BaseDirectory + ) + $base = if ($BaseDirectory) { + [System.IO.Path]::GetFullPath($BaseDirectory) + } else { + [System.IO.Directory]::GetCurrentDirectory() + } + $combined = [System.IO.Path]::Combine($base, $RelativePath) + $full = [System.IO.Path]::GetFullPath($combined) + $relative = [System.IO.Path]::GetRelativePath($base, $full) + return [System.IO.Path]::TrimEndingDirectorySeparator($relative) +} + try { # Discovery short-circuits if ($ListActions) { Show-List; exit 0 } @@ -200,6 +219,10 @@ try { # Only pass parameters that the adapter actually accepts $argsHash = Filter-Args -InputArgs $argsHash -FuncName $funcName -ActionNameForWarn $key + if ($argsHash.ContainsKey('RelativePath')) { + $argsHash['RelativePath'] = Normalize-RelativePath -RelativePath $argsHash['RelativePath'] -BaseDirectory $WorkingDirectory + } + if ($WorkingDirectory) { Push-Location -Path $WorkingDirectory } try { $result = & $funcName @argsHash diff --git a/actions/OpenSourceActions.psm1 b/actions/OpenSourceActions.psm1 index 48efc0db..7e0e374f 100644 --- a/actions/OpenSourceActions.psm1 +++ b/actions/OpenSourceActions.psm1 @@ -15,6 +15,9 @@ function Invoke-OpenSourceActionScript { foreach ($seg in $segments) { $scriptPath = [System.IO.Path]::Combine($scriptPath, $seg) } + if ($Arguments.ContainsKey('RelativePath')) { + $Arguments['RelativePath'] = [System.IO.Path]::TrimEndingDirectorySeparator($Arguments['RelativePath']) + } if ($DryRun) { Write-Information "DryRun: & $scriptPath $($Arguments | ConvertTo-Json -Compress)" return 0 @@ -36,7 +39,7 @@ function Invoke-OpenSourceActionScript { # Adds an authentication token to a LabVIEW installation. # MinimumSupportedLVVersion: Minimum LabVIEW version that the project supports. # SupportedBitness: Target LabVIEW bitness (32- or 64-bit). -# RelativePath: Path to the project root relative to the working directory. +# RelativePath: Normalized path to the project root relative to the working directory. # DryRun: If set, prints the command instead of executing it. # gcliPath: Optional path prepended to PATH for locating the g CLI. function Invoke-AddTokenToLabVIEW { @@ -61,7 +64,7 @@ function Invoke-AddTokenToLabVIEW { # MinimumSupportedLVVersion: Minimum LabVIEW version that the project supports. # VIP_LVVersion: LabVIEW version used to build the VIPC. # SupportedBitness: Target LabVIEW bitness (32- or 64-bit). -# RelativePath: Path to the project root relative to the working directory. +# RelativePath: Normalized path to the project root relative to the working directory. # VIPCPath: Optional path to the VIPC file. # DryRun: If set, prints the command instead of executing it. # gcliPath: Optional path prepended to PATH for locating the g CLI. @@ -91,7 +94,7 @@ function Invoke-ApplyVIPC { # MinimumSupportedLVVersion: Minimum LabVIEW version that the package supports. # SupportedBitness: Target LabVIEW bitness (32- or 64-bit). # LabVIEWMinorRevision: Minor revision of LabVIEW used to build the package. -# RelativePath: Path to the project root relative to the working directory. +# RelativePath: Normalized path to the project root relative to the working directory. # VIPBPath: Path to the VIPB build specification file. # Major: Major version component. # Minor: Minor version component. @@ -139,7 +142,7 @@ function Invoke-BuildViPackage { } # Builds the project and records version information. -# RelativePath: Path to the project root relative to the working directory. +# RelativePath: Normalized path to the project root relative to the working directory. # Major: Major version component. # Minor: Minor version component. # Patch: Patch version component. @@ -183,7 +186,7 @@ function Invoke-Build { # Builds a LabVIEW Packed Library using a project and build spec. # MinimumSupportedLVVersion: Minimum LabVIEW version that the library supports. # SupportedBitness: Target LabVIEW bitness (32- or 64-bit). -# RelativePath: Path to the project root relative to the working directory. +# RelativePath: Normalized path to the project root relative to the working directory. # LabVIEW_Project: Path to the LabVIEW project file. # Build_Spec: Name of the build specification within the project. # Major: Major version component. @@ -288,7 +291,7 @@ function Invoke-MissingInProject { # Updates display information fields in a VIPB build specification. # SupportedBitness: Target LabVIEW bitness (32- or 64-bit). -# RelativePath: Path to the project root relative to the working directory. +# RelativePath: Normalized path to the project root relative to the working directory. # VIPBPath: Path to the VIPB build specification file. # MinimumSupportedLVVersion: Minimum LabVIEW version that the package supports. # LabVIEWMinorRevision: Minor revision of LabVIEW used for the build. @@ -340,7 +343,7 @@ function Invoke-ModifyVIPBDisplayInfo { # Prepares a LabVIEW project for source distribution. # MinimumSupportedLVVersion: Minimum LabVIEW version that the project supports. # SupportedBitness: Target LabVIEW bitness (32- or 64-bit). -# RelativePath: Path to the project root relative to the working directory. +# RelativePath: Normalized path to the project root relative to the working directory. # LabVIEW_Project: Path to the LabVIEW project file. # Build_Spec: Name of the build specification within the project. # DryRun: If set, prints the command instead of executing it. @@ -391,7 +394,7 @@ function Invoke-RenameFile { # Restores the Setup LabVIEW source build specification. # MinimumSupportedLVVersion: Minimum LabVIEW version that the project supports. # SupportedBitness: Target LabVIEW bitness (32- or 64-bit). -# RelativePath: Path to the project root relative to the working directory. +# RelativePath: Normalized path to the project root relative to the working directory. # LabVIEW_Project: Path to the LabVIEW project file. # Build_Spec: Name of the build specification within the project. # DryRun: If set, prints the command instead of executing it. @@ -419,7 +422,7 @@ function Invoke-RestoreSetupLVSource { } # Returns a repository to its previous development mode state. -# RelativePath: Path to the project root relative to the working directory. +# RelativePath: Normalized path to the project root relative to the working directory. # DryRun: If set, prints the command instead of executing it. # gcliPath: Optional path prepended to PATH for locating the g CLI. function Invoke-RevertDevelopmentMode { @@ -472,7 +475,7 @@ function Invoke-RunUnitTests { } # Configures the repository for development mode. -# RelativePath: Path to the project root relative to the working directory. +# RelativePath: Normalized path to the project root relative to the working directory. # DryRun: If set, prints the command instead of executing it. # gcliPath: Optional path prepended to PATH for locating the g CLI. function Invoke-SetDevelopmentMode { diff --git a/docs/relative-paths.md b/docs/relative-paths.md new file mode 100644 index 00000000..4f0b3c29 --- /dev/null +++ b/docs/relative-paths.md @@ -0,0 +1,8 @@ +# Relative Path Normalization + +Dispatcher actions accept a `RelativePath` argument that points to the project root relative to the working directory. Before invoking adapter functions, the dispatcher normalizes this value: + +- trailing directory separators are removed +- `.` segments are resolved + +Inputs such as `./` or `scripts/../` therefore become `.` and `scripts`. Normalization ensures all scripts receive consistent paths regardless of how callers format the argument. diff --git a/docs/requirements.md b/docs/requirements.md index c43700c4..21498d18 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -8,7 +8,7 @@ Runner Type indicates whether a requirement runs on a standard GitHub-hosted ima |----|-------------|-------|--------|-------------|--------------| | REQ-001 | Dispatcher discovers available actions, describes them, and validates arguments. | `tests/pester/Dispatcher.Tests.ps1` | | | | | REQ-002 | Dispatcher dry-run mode prints descriptions and warns on unknown arguments without executing actions. | `tests/pester/Dispatcher.DryRun.Tests.ps1` | | | | -| REQ-003 | Actions resolve RelativePath arguments using the action's WorkingDirectory as the base path and pass them without warnings, including when RelativePath is '.' and the WorkingDirectory targets subdirectories. | `tests/pester/RelativePath.Actions.Tests.ps1` | | | | +| REQ-003 | Actions resolve and normalize RelativePath arguments (trimming separators, resolving '.') using the action's WorkingDirectory as the base path and pass them without warnings, including when RelativePath is '.' and the WorkingDirectory targets subdirectories. | `tests/pester/RelativePath.Actions.Tests.ps1` | | | | | REQ-004 | Every action script exists at the expected path. | `tests/pester/ScriptPath.Tests.ps1` | | | | | REQ-005 | Dispatcher fails when RelativePath is missing or invalid after resolving it relative to the specified WorkingDirectory. | `tests/pester/Dispatcher.InvalidPaths.Tests.ps1` | | | | | REQ-006 | Workflow tests the composite action defined in apply-vipc/action.yml with minimum_supported_lv_version '2021', vip_lv_version '2021', supported_bitness '64', relative_path '.', and vipc_path 'scripts/apply-vipc/runner_dependencies.vipc' on the GitHub-hosted Ubuntu runner labeled ubuntu-latest with dry_run true. | `tests/pester/ApplyVipc.DryRunTrue.Workflow.ps1` | ubuntu-latest | integration | false | diff --git a/tests/pester/RelativePath.Actions.Tests.ps1 b/tests/pester/RelativePath.Actions.Tests.ps1 index 9f704e67..12c99844 100644 --- a/tests/pester/RelativePath.Actions.Tests.ps1 +++ b/tests/pester/RelativePath.Actions.Tests.ps1 @@ -18,14 +18,15 @@ $meta = @{ Describe 'add-token-to-labview resolves RelativePath' { It 'dry-runs without warnings' -Tag 'REQ-003' { $params = Get-LabVIEWIconEditorArgsJson - $json = $params.ArgsJson + $obj = $params.ArgsJson | ConvertFrom-Json + $obj.RelativePath = './' + $json = $obj | ConvertTo-Json -Compress $projectRoot = $params.WorkingDirectory - $expected = ($json | ConvertFrom-Json).RelativePath $out = & $dispatcher -ActionName add-token-to-labview -ArgsJson $json -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String $LASTEXITCODE | Should -Be 0 $jsonLine = $out -split "`n" | Where-Object { $_ -match '{' } | Select-Object -Last 1 $jsonText = $jsonLine -replace '^[^{}]*({.*})','$1' - ($jsonText | ConvertFrom-Json).RelativePath | Should -Be $expected + ($jsonText | ConvertFrom-Json).RelativePath | Should -Be '.' $out | Should -Not -Match 'Ignored unknown parameters' } } @@ -35,12 +36,12 @@ Describe 'apply-vipc resolves RelativePath' { $params = Get-LabVIEWIconEditorArgsJson $b = $params.ArgsJson | ConvertFrom-Json $projectRoot = $params.WorkingDirectory - $args = @{ MinimumSupportedLVVersion = $b.MinimumSupportedLVVersion; SupportedBitness = $b.SupportedBitness; RelativePath = $b.RelativePath; VIP_LVVersion = '2021'; VIPCPath = 'dummy.vipc' } | ConvertTo-Json -Compress + $args = @{ MinimumSupportedLVVersion = $b.MinimumSupportedLVVersion; SupportedBitness = $b.SupportedBitness; RelativePath = './'; VIP_LVVersion = '2021'; VIPCPath = 'dummy.vipc' } | ConvertTo-Json -Compress $out = & $dispatcher -ActionName apply-vipc -ArgsJson $args -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String $LASTEXITCODE | Should -Be 0 $jsonLine = $out -split "`n" | Where-Object { $_ -match '{' } | Select-Object -Last 1 $jsonText = $jsonLine -replace '^[^{}]*({.*})','$1' - ($jsonText | ConvertFrom-Json).RelativePath | Should -Be $b.RelativePath + ($jsonText | ConvertFrom-Json).RelativePath | Should -Be '.' $out | Should -Not -Match 'Ignored unknown parameters' } } @@ -50,12 +51,12 @@ Describe 'build-vi-package resolves RelativePath' { $params = Get-LabVIEWIconEditorArgsJson $b = $params.ArgsJson | ConvertFrom-Json $projectRoot = $params.WorkingDirectory - $args = @{ MinimumSupportedLVVersion=$b.MinimumSupportedLVVersion; SupportedBitness=$b.SupportedBitness; LabVIEWMinorRevision='2021'; RelativePath=$b.RelativePath; VIPBPath='dummy.vipb'; Major=1; Minor=0; Patch=0; Build=1; Commit='deadbeef'; DisplayInformationJSON='{}'; ReleaseNotesFile='notes.md' } | ConvertTo-Json -Compress + $args = @{ MinimumSupportedLVVersion=$b.MinimumSupportedLVVersion; SupportedBitness=$b.SupportedBitness; LabVIEWMinorRevision='2021'; RelativePath='./'; VIPBPath='dummy.vipb'; Major=1; Minor=0; Patch=0; Build=1; Commit='deadbeef'; DisplayInformationJSON='{}'; ReleaseNotesFile='notes.md' } | ConvertTo-Json -Compress $out = & $dispatcher -ActionName build-vi-package -ArgsJson $args -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String $LASTEXITCODE | Should -Be 0 $jsonLine = $out -split "`n" | Where-Object { $_ -match '{' } | Select-Object -Last 1 $jsonText = $jsonLine -replace '^[^{}]*({.*})','$1' - ($jsonText | ConvertFrom-Json).RelativePath | Should -Be $b.RelativePath + ($jsonText | ConvertFrom-Json).RelativePath | Should -Be '.' $out | Should -Not -Match 'Ignored unknown parameters' } } @@ -65,12 +66,12 @@ Describe 'build resolves RelativePath' { $params = Get-LabVIEWIconEditorArgsJson $b = $params.ArgsJson | ConvertFrom-Json $projectRoot = $params.WorkingDirectory - $args = @{ RelativePath=$b.RelativePath; Major=1; Minor=0; Patch=0; Build=1; Commit='deadbeef'; LabVIEWMinorRevision='2021'; CompanyName='Company'; AuthorName='Author' } | ConvertTo-Json -Compress + $args = @{ RelativePath='./'; Major=1; Minor=0; Patch=0; Build=1; Commit='deadbeef'; LabVIEWMinorRevision='2021'; CompanyName='Company'; AuthorName='Author' } | ConvertTo-Json -Compress $out = & $dispatcher -ActionName build -ArgsJson $args -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String $LASTEXITCODE | Should -Be 0 $jsonLine = $out -split "`n" | Where-Object { $_ -match '{' } | Select-Object -Last 1 $jsonText = $jsonLine -replace '^[^{}]*({.*})','$1' - ($jsonText | ConvertFrom-Json).RelativePath | Should -Be $b.RelativePath + ($jsonText | ConvertFrom-Json).RelativePath | Should -Be '.' $out | Should -Not -Match 'Ignored unknown parameters' } } @@ -80,12 +81,12 @@ Describe 'build-lvlibp resolves RelativePath' { $params = Get-LabVIEWIconEditorArgsJson $b = $params.ArgsJson | ConvertFrom-Json $projectRoot = $params.WorkingDirectory - $args = @{ MinimumSupportedLVVersion=$b.MinimumSupportedLVVersion; SupportedBitness=$b.SupportedBitness; RelativePath=$b.RelativePath; LabVIEW_Project='My.lvproj'; Build_Spec='MyBuild'; Major=1; Minor=0; Patch=0; Build=1; Commit='deadbeef' } | ConvertTo-Json -Compress + $args = @{ MinimumSupportedLVVersion=$b.MinimumSupportedLVVersion; SupportedBitness=$b.SupportedBitness; RelativePath='./'; LabVIEW_Project='My.lvproj'; Build_Spec='MyBuild'; Major=1; Minor=0; Patch=0; Build=1; Commit='deadbeef' } | ConvertTo-Json -Compress $out = & $dispatcher -ActionName build-lvlibp -ArgsJson $args -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String $LASTEXITCODE | Should -Be 0 $jsonLine = $out -split "`n" | Where-Object { $_ -match '{' } | Select-Object -Last 1 $jsonText = $jsonLine -replace '^[^{}]*({.*})','$1' - ($jsonText | ConvertFrom-Json).RelativePath | Should -Be $b.RelativePath + ($jsonText | ConvertFrom-Json).RelativePath | Should -Be '.' $out | Should -Not -Match 'Ignored unknown parameters' } } @@ -95,12 +96,12 @@ Describe 'modify-vipb-display-info resolves RelativePath' { $params = Get-LabVIEWIconEditorArgsJson $b = $params.ArgsJson | ConvertFrom-Json $projectRoot = $params.WorkingDirectory - $args = @{ SupportedBitness=$b.SupportedBitness; RelativePath=$b.RelativePath; VIPBPath='dummy.vipb'; MinimumSupportedLVVersion=$b.MinimumSupportedLVVersion; LabVIEWMinorRevision='2021'; Major=1; Minor=0; Patch=0; Build=1; Commit='deadbeef'; DisplayInformationJSON='{}'; ReleaseNotesFile='notes.md' } | ConvertTo-Json -Compress + $args = @{ SupportedBitness=$b.SupportedBitness; RelativePath='./'; VIPBPath='dummy.vipb'; MinimumSupportedLVVersion=$b.MinimumSupportedLVVersion; LabVIEWMinorRevision='2021'; Major=1; Minor=0; Patch=0; Build=1; Commit='deadbeef'; DisplayInformationJSON='{}'; ReleaseNotesFile='notes.md' } | ConvertTo-Json -Compress $out = & $dispatcher -ActionName modify-vipb-display-info -ArgsJson $args -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String $LASTEXITCODE | Should -Be 0 $jsonLine = $out -split "`n" | Where-Object { $_ -match '{' } | Select-Object -Last 1 $jsonText = $jsonLine -replace '^[^{}]*({.*})','$1' - ($jsonText | ConvertFrom-Json).RelativePath | Should -Be $b.RelativePath + ($jsonText | ConvertFrom-Json).RelativePath | Should -Be '.' $out | Should -Not -Match 'Ignored unknown parameters' } } @@ -110,12 +111,12 @@ Describe 'prepare-labview-source resolves RelativePath' { $params = Get-LabVIEWIconEditorArgsJson $b = $params.ArgsJson | ConvertFrom-Json $projectRoot = $params.WorkingDirectory - $args = @{ MinimumSupportedLVVersion=$b.MinimumSupportedLVVersion; SupportedBitness=$b.SupportedBitness; RelativePath=$b.RelativePath; LabVIEW_Project='My.lvproj'; Build_Spec='MyBuild' } | ConvertTo-Json -Compress + $args = @{ MinimumSupportedLVVersion=$b.MinimumSupportedLVVersion; SupportedBitness=$b.SupportedBitness; RelativePath='./'; LabVIEW_Project='My.lvproj'; Build_Spec='MyBuild' } | ConvertTo-Json -Compress $out = & $dispatcher -ActionName prepare-labview-source -ArgsJson $args -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String $LASTEXITCODE | Should -Be 0 $jsonLine = $out -split "`n" | Where-Object { $_ -match '{' } | Select-Object -Last 1 $jsonText = $jsonLine -replace '^[^{}]*({.*})','$1' - ($jsonText | ConvertFrom-Json).RelativePath | Should -Be $b.RelativePath + ($jsonText | ConvertFrom-Json).RelativePath | Should -Be '.' $out | Should -Not -Match 'Ignored unknown parameters' } } @@ -125,12 +126,12 @@ Describe 'restore-setup-lv-source resolves RelativePath' { $params = Get-LabVIEWIconEditorArgsJson $b = $params.ArgsJson | ConvertFrom-Json $projectRoot = $params.WorkingDirectory - $args = @{ MinimumSupportedLVVersion=$b.MinimumSupportedLVVersion; SupportedBitness=$b.SupportedBitness; RelativePath=$b.RelativePath; LabVIEW_Project='My.lvproj'; Build_Spec='MyBuild' } | ConvertTo-Json -Compress + $args = @{ MinimumSupportedLVVersion=$b.MinimumSupportedLVVersion; SupportedBitness=$b.SupportedBitness; RelativePath='./'; LabVIEW_Project='My.lvproj'; Build_Spec='MyBuild' } | ConvertTo-Json -Compress $out = & $dispatcher -ActionName restore-setup-lv-source -ArgsJson $args -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String $LASTEXITCODE | Should -Be 0 $jsonLine = $out -split "`n" | Where-Object { $_ -match '{' } | Select-Object -Last 1 $jsonText = $jsonLine -replace '^[^{}]*({.*})','$1' - ($jsonText | ConvertFrom-Json).RelativePath | Should -Be $b.RelativePath + ($jsonText | ConvertFrom-Json).RelativePath | Should -Be '.' $out | Should -Not -Match 'Ignored unknown parameters' } } @@ -140,12 +141,12 @@ Describe 'revert-development-mode resolves RelativePath' { $params = Get-LabVIEWIconEditorArgsJson $b = $params.ArgsJson | ConvertFrom-Json $projectRoot = $params.WorkingDirectory - $args = @{ RelativePath=$b.RelativePath } | ConvertTo-Json -Compress + $args = @{ RelativePath='./' } | ConvertTo-Json -Compress $out = & $dispatcher -ActionName revert-development-mode -ArgsJson $args -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String $LASTEXITCODE | Should -Be 0 $jsonLine = $out -split "`n" | Where-Object { $_ -match '{' } | Select-Object -Last 1 $jsonText = $jsonLine -replace '^[^{}]*({.*})','$1' - ($jsonText | ConvertFrom-Json).RelativePath | Should -Be $b.RelativePath + ($jsonText | ConvertFrom-Json).RelativePath | Should -Be '.' $out | Should -Not -Match 'Ignored unknown parameters' } } @@ -155,12 +156,12 @@ Describe 'set-development-mode resolves RelativePath' { $params = Get-LabVIEWIconEditorArgsJson $b = $params.ArgsJson | ConvertFrom-Json $projectRoot = $params.WorkingDirectory - $args = @{ RelativePath=$b.RelativePath } | ConvertTo-Json -Compress + $args = @{ RelativePath='./' } | ConvertTo-Json -Compress $out = & $dispatcher -ActionName set-development-mode -ArgsJson $args -WorkingDirectory $projectRoot -DryRun *>&1 | Out-String $LASTEXITCODE | Should -Be 0 $jsonLine = $out -split "`n" | Where-Object { $_ -match '{' } | Select-Object -Last 1 $jsonText = $jsonLine -replace '^[^{}]*({.*})','$1' - ($jsonText | ConvertFrom-Json).RelativePath | Should -Be $b.RelativePath + ($jsonText | ConvertFrom-Json).RelativePath | Should -Be '.' $out | Should -Not -Match 'Ignored unknown parameters' } } @@ -170,7 +171,7 @@ Describe 'RelativePath "." resolves with varying working directories' { It "dry-runs without warnings when WorkingDirectory is $subdir" -Tag 'REQ-003' { $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..' '..')).Path $workingDir = Join-Path $repoRoot $subdir - $args = @{ RelativePath = '.' } | ConvertTo-Json -Compress + $args = @{ RelativePath = './' } | ConvertTo-Json -Compress $out = & $dispatcher -ActionName set-development-mode -ArgsJson $args -WorkingDirectory $workingDir -DryRun *>&1 | Out-String $LASTEXITCODE | Should -Be 0 $jsonLine = $out -split "`n" | Where-Object { $_ -match '{' } | Select-Object -Last 1 From ae75ca3422e1ab8cf1065fd917eda6a7d879b790 Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 17:08:26 -0700 Subject: [PATCH 18/23] docs: add environment setup guide --- AGENTS.md | 7 ++++++ README.md | 2 ++ docs/environment-setup.md | 49 +++++++++++++++++++++++++++++++++++++++ docs/index.md | 1 + 4 files changed, 59 insertions(+) create mode 100644 docs/environment-setup.md diff --git a/AGENTS.md b/AGENTS.md index 8a86b5fc..f98f4c1f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,9 +3,12 @@ ## Environment Setup - Run `apt-get update && apt-get install -y apt-utils` to ensure required APT utilities are available. - Ensure Node.js 24 or newer is installed (e.g. via the NodeSource setup script). + - Verify with `node --version`. - Install `actionlint` and ensure it is on your `PATH`: - `go install github.com/rhysd/actionlint/cmd/actionlint@latest` + - Verify with `actionlint -version`. - Ensure PowerShell 7.5.1 is installed and accessible. + - Verify with `pwsh --version`. - Linux runners rely on preinstalled `pwsh`. - On Windows Server 2022 (build 10.0.20348), download the MSI and verify its SHA256 checksum before installing: @@ -18,6 +21,10 @@ Remove-Item pwsh.msi ``` - PowerShell 7.5.1 includes native YAML support; external modules such as `powershell-yaml` are no longer required. +- Install NI LabVIEW's command-line interface (g-cli) on Windows runners and ensure it is accessible. + - Verify with `g-cli --version` or `& 'C:\\Program Files\\G-CLI\\bin\\g-cli.exe' --version`. +- Optional: set `LABVIEW_ICON_EDITOR_PATH` if a custom icon editor location is required. + - Verify with `echo $LABVIEW_ICON_EDITOR_PATH` (or `$env:LABVIEW_ICON_EDITOR_PATH` in PowerShell). ## Testing - Run `npm run check:node` to verify Node.js satisfies the required version. diff --git a/README.md b/README.md index 5ac4aa9a..89dc09c8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ For setup and action reference, see the [documentation](docs/index.md). The [qui - NI LabVIEW with command-line interface support (g-cli) for LabVIEW-based actions - Supported platforms: Windows for LabVIEW tasks; PowerShell-only scripts also run on macOS and Linux +See [Environment Setup](docs/environment-setup.md) for installation steps and commands to verify each dependency. + ## GitHub Action usage ```yaml diff --git a/docs/environment-setup.md b/docs/environment-setup.md new file mode 100644 index 00000000..5914cfd4 --- /dev/null +++ b/docs/environment-setup.md @@ -0,0 +1,49 @@ +# Environment Setup + +This guide lists the tooling required to contribute to Open Source LabVIEW Actions and how to verify each component. + +## PowerShell 7.5.1 + +PowerShell 7.5.1 or newer is required. Verify the version: + +```bash +pwsh --version +``` + +## Node.js 24+ + +Install Node.js 24 or newer. Verify the installation: + +```bash +node --version +``` + +## actionlint + +[actionlint](https://github.com/rhysd/actionlint) validates GitHub Actions workflows. +Install it with Go and confirm it is on `PATH`: + +```bash +go install github.com/rhysd/actionlint/cmd/actionlint@latest +actionlint -version +``` + +## g-cli + +Some actions rely on NI's LabVIEW command-line interface (g-cli). +On Windows the executable typically resides at `C:\Program Files\G-CLI\bin\g-cli.exe`. +Verify availability with: + +```powershell +& 'C:\Program Files\G-CLI\bin\g-cli.exe' --version +# or if on PATH +g-cli --version +``` + +## Optional: LABVIEW_ICON_EDITOR_PATH + +Set `LABVIEW_ICON_EDITOR_PATH` if a nonstandard icon editor is required. Check the value with: + +```powershell +echo $env:LABVIEW_ICON_EDITOR_PATH +``` diff --git a/docs/index.md b/docs/index.md index a0173424..e3b2466d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,6 +6,7 @@ Open Source LabVIEW Actions unifies LabVIEW CI/CD scripts behind a single PowerS - [Architecture](architecture.md) - [Quickstart](quickstart.md) +- [Environment Setup](environment-setup.md) - [Action Call Reference](action-call-reference.md) - [Common Parameters](common-parameters.md) - [Adapter Authoring Guide](adapter-authoring.md) From c0d7fc8be2f57d45561b6335969c4440c32aac7c Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 17:27:25 -0700 Subject: [PATCH 19/23] Fix dispatcher test suite --- tests/pester/Dispatcher.DryRun.Tests.ps1 | 4 ++-- tests/pester/Dispatcher.Tests.ps1 | 10 +++++----- tests/pester/ScriptPath.Tests.ps1 | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/pester/Dispatcher.DryRun.Tests.ps1 b/tests/pester/Dispatcher.DryRun.Tests.ps1 index 9ff435d9..a9ed5bd5 100644 --- a/tests/pester/Dispatcher.DryRun.Tests.ps1 +++ b/tests/pester/Dispatcher.DryRun.Tests.ps1 @@ -49,14 +49,14 @@ Describe 'Unified Dispatcher — DryRun behavior for all actions' { } foreach ($kvp in $extra.GetEnumerator()) { $script:args | Add-Member -NotePropertyName $kvp.Key -NotePropertyValue $kvp.Value } $script:argsJson = $script:args | ConvertTo-Json -Compress - $actions = pwsh -NoProfile -File $global:dispatcher -ListActions -WorkingDirectory $script:projectRoot | + $actions = & $global:dispatcher -ListActions -WorkingDirectory $script:projectRoot | Where-Object { $_ -match '^\s+- ' } | ForEach-Object { @{ Action = $_.Trim().Substring(2); ArgsJson = $script:argsJson } } It "describes " -Tag 'REQ-002' -ForEach $actions { param($Action, $ArgsJson) Write-Host "Testing $Action with ArgsJson $ArgsJson" - pwsh -NoProfile -File $global:dispatcher -Describe $Action -ArgsJson $ArgsJson -WorkingDirectory $script:projectRoot *> $null + & $global:dispatcher -Describe $Action -ArgsJson $ArgsJson -WorkingDirectory $script:projectRoot *> $null $LASTEXITCODE | Should -Be 0 } diff --git a/tests/pester/Dispatcher.Tests.ps1 b/tests/pester/Dispatcher.Tests.ps1 index 33e05283..4292fb61 100644 --- a/tests/pester/Dispatcher.Tests.ps1 +++ b/tests/pester/Dispatcher.Tests.ps1 @@ -25,7 +25,7 @@ Describe 'Unified Dispatcher — discovery and validation' { $params = Get-LabVIEWIconEditorArgsJson $json = $params.ArgsJson $projectRoot = $params.WorkingDirectory - $out = pwsh -NoProfile -File $global:dispatcher -ListActions -ArgsJson $json -WorkingDirectory $projectRoot | Out-String + $out = & $global:dispatcher -ListActions -ArgsJson $json -WorkingDirectory $projectRoot | Out-String $out | Should -Match 'apply-vipc' $out | Should -Match 'build-lvlibp' $out | Should -Match 'missing-in-project' @@ -34,9 +34,9 @@ Describe 'Unified Dispatcher — discovery and validation' { It 'registry includes all Invoke* adapters in module' -Tag 'REQ-001' { $modulePath = Join-Path (Split-Path $global:dispatcher -Parent) 'OpenSourceActions.psm1' $module = Import-Module $modulePath -PassThru - $fnNames = (Get-Command -Module $module | Where-Object Name -like 'Invoke*').Name + $fnNames = (Get-Command -Module $module | Where-Object { $_.Name -like 'Invoke*' -and $_.Name -ne 'Invoke-OpenSourceActionScript' }).Name function Convert-Name([string]$fn) { - $name = $fn -replace '^Invoke' + $name = $fn -replace '^Invoke-?' $name = $name -creplace '([a-z0-9])([A-Z])', '$1-$2' $name = $name -creplace '([A-Z])([A-Z][a-z])', '$1-$2' $name = $name -ireplace 'Lab-VIEW', 'LabVIEW' @@ -44,7 +44,7 @@ Describe 'Unified Dispatcher — discovery and validation' { } $expected = $fnNames | ForEach-Object { Convert-Name $_ } $wd = (Get-LabVIEWIconEditorArgsJson).WorkingDirectory - $listed = pwsh -NoProfile -File $global:dispatcher -ListActions -WorkingDirectory $wd | Out-String + $listed = & $global:dispatcher -ListActions -WorkingDirectory $wd | Out-String foreach ($action in $expected) { $listed | Should -Match " - $action" } @@ -53,7 +53,7 @@ Describe 'Unified Dispatcher — discovery and validation' { $params = Get-LabVIEWIconEditorArgsJson $json = $params.ArgsJson $projectRoot = $params.WorkingDirectory - $out = pwsh -NoProfile -File $global:dispatcher -Describe build-lvlibp -ArgsJson $json -WorkingDirectory $projectRoot | Out-String + $out = & $global:dispatcher -Describe build-lvlibp -ArgsJson $json -WorkingDirectory $projectRoot | Out-String $out | Should -Match 'Major' $out | Should -Match 'Minor' $out | Should -Match 'Patch' diff --git a/tests/pester/ScriptPath.Tests.ps1 b/tests/pester/ScriptPath.Tests.ps1 index 4d4e1705..bfdfa7d3 100644 --- a/tests/pester/ScriptPath.Tests.ps1 +++ b/tests/pester/ScriptPath.Tests.ps1 @@ -11,7 +11,7 @@ $scriptRoot = Join-Path $repoRoot 'scripts' Import-Module (Join-Path $PSScriptRoot 'Helper' 'ArgsJson.psm1') $projectRoot = (Get-LabVIEWIconEditorArgsJson).WorkingDirectory $dispatcher = Join-Path $repoRoot 'actions' 'Invoke-OSAction.ps1' -$actionNames = pwsh -NoProfile -File $dispatcher -ListActions -WorkingDirectory $projectRoot | +$actionNames = & $dispatcher -ListActions -WorkingDirectory $projectRoot | Where-Object { $_ -match '^\s+- ' } | ForEach-Object { $_.Trim().Substring(2) } From 6049515c6bad6bca971c925df9b64d7cdb6f19ae Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 17:48:22 -0700 Subject: [PATCH 20/23] feat: allow suppressing unknown parameter warnings --- actions/Invoke-OSAction.ps1 | 19 +++++++++++++++---- docs/UnifiedDispatcher.md | 2 +- tests/pester/Dispatcher.Tests.ps1 | 5 ++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/actions/Invoke-OSAction.ps1 b/actions/Invoke-OSAction.ps1 index 23e2229e..05bea4a2 100644 --- a/actions/Invoke-OSAction.ps1 +++ b/actions/Invoke-OSAction.ps1 @@ -97,8 +97,15 @@ function Show-Description([string]$Name) { # InputArgs: Hashtable of supplied arguments. # FuncName: Target dispatcher function name. # ActionNameForWarn: Action name used when emitting warnings. -# ReturnUnknownParams: If set, returns unknown parameters as well. -function Filter-Args([hashtable]$InputArgs, [string]$FuncName, [string]$ActionNameForWarn, [switch]$ReturnUnknownParams) { +# ReturnUnknownParams: If set, returns unknown parameters and suppresses warnings. +# NoWarn: Suppresses warnings for unknown parameters without returning them. +function Filter-Args( + [hashtable]$InputArgs, + [string]$FuncName, + [string]$ActionNameForWarn, + [switch]$ReturnUnknownParams, + [switch]$NoWarn +) { $cmd = Get-Command $FuncName -ErrorAction Stop # Map each alias to its canonical parameter name for the target function @@ -124,7 +131,9 @@ function Filter-Args([hashtable]$InputArgs, [string]$FuncName, [string]$ActionNa $msg = $null if ($unknown.Count) { $msg = "Ignored unknown parameters for '$ActionNameForWarn': $($unknown -join ', ')" - Write-Warning $msg + if (-not $NoWarn -and -not $ReturnUnknownParams) { + Write-Warning $msg + } } if ($ReturnUnknownParams) { return [pscustomobject]@{ Args = $filtered; UnknownParams = $msg } @@ -217,7 +226,9 @@ try { Set-LogLevel -Level $LogLevel # Only pass parameters that the adapter actually accepts - $argsHash = Filter-Args -InputArgs $argsHash -FuncName $funcName -ActionNameForWarn $key + $filterResult = Filter-Args -InputArgs $argsHash -FuncName $funcName -ActionNameForWarn $key -ReturnUnknownParams + $argsHash = $filterResult.Args + if ($filterResult.UnknownParams) { Write-Warning $filterResult.UnknownParams } if ($argsHash.ContainsKey('RelativePath')) { $argsHash['RelativePath'] = Normalize-RelativePath -RelativePath $argsHash['RelativePath'] -BaseDirectory $WorkingDirectory diff --git a/docs/UnifiedDispatcher.md b/docs/UnifiedDispatcher.md index 268d7c49..5956bda1 100644 --- a/docs/UnifiedDispatcher.md +++ b/docs/UnifiedDispatcher.md @@ -43,7 +43,7 @@ When multiple argument sources are supplied, values are merged in the following 3. `ArgsHashtable` 4. dispatcher switches (for example, `-DryRun`) -Later entries override earlier ones. Unknown parameters from any source are ignored and emitted as warnings. +Later entries override earlier ones. Unknown parameters from any source are ignored; by default they emit warnings, which can be suppressed by requesting the message with `-ReturnUnknownParams` or by using `-NoWarn`. ## Wrapper action usage diff --git a/tests/pester/Dispatcher.Tests.ps1 b/tests/pester/Dispatcher.Tests.ps1 index 4292fb61..67626d65 100644 --- a/tests/pester/Dispatcher.Tests.ps1 +++ b/tests/pester/Dispatcher.Tests.ps1 @@ -103,8 +103,11 @@ Describe 'Filter-Args helper' { function Dummy { param([string]$Known) } $args = @{ Known = 'value'; Extra = 'x' } - $result = Filter-Args -InputArgs $args -FuncName 'Dummy' -ActionNameForWarn 'dummy' -ReturnUnknownParams + $warnings = @() + $result = Filter-Args -InputArgs $args -FuncName 'Dummy' -ActionNameForWarn 'dummy' -ReturnUnknownParams -WarningVariable warnings + $warnings | Should -BeNullOrEmpty $result.PSObject.Properties.Name | Should -Contain 'UnknownParams' + $result.UnknownParams | Should -Match 'Extra' } } From d67df0101435efcd3cca1d3eae86c0a71716e50d Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 17:54:55 -0700 Subject: [PATCH 21/23] test: silence filter-args warnings --- tests/pester/Dispatcher.Tests.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/pester/Dispatcher.Tests.ps1 b/tests/pester/Dispatcher.Tests.ps1 index 67626d65..b91a630a 100644 --- a/tests/pester/Dispatcher.Tests.ps1 +++ b/tests/pester/Dispatcher.Tests.ps1 @@ -104,7 +104,8 @@ Describe 'Filter-Args helper' { function Dummy { param([string]$Known) } $args = @{ Known = 'value'; Extra = 'x' } $warnings = @() - $result = Filter-Args -InputArgs $args -FuncName 'Dummy' -ActionNameForWarn 'dummy' -ReturnUnknownParams -WarningVariable warnings + # Capture and suppress warnings to keep test output clean while verifying no warnings are emitted + $result = Filter-Args -InputArgs $args -FuncName 'Dummy' -ActionNameForWarn 'dummy' -ReturnUnknownParams -WarningVariable warnings -WarningAction SilentlyContinue $warnings | Should -BeNullOrEmpty $result.PSObject.Properties.Name | Should -Contain 'UnknownParams' $result.UnknownParams | Should -Match 'Extra' From a13c2ec2bc57a8e4f7984e051a321b5fdc8ec7ce Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 18:04:46 -0700 Subject: [PATCH 22/23] chore: remove placeholder test --- scripts/dummy.test.js | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 scripts/dummy.test.js diff --git a/scripts/dummy.test.js b/scripts/dummy.test.js deleted file mode 100644 index 03adc735..00000000 --- a/scripts/dummy.test.js +++ /dev/null @@ -1,3 +0,0 @@ -import test from 'node:test'; - -test('dummy', () => {}); From 0a2e45b245ecdaba3c0405f23ca4def9141ce875 Mon Sep 17 00:00:00 2001 From: sergiov-ni Date: Mon, 18 Aug 2025 18:27:38 -0700 Subject: [PATCH 23/23] test: verify FailOnUnknown behavior --- README.md | 6 ++++ actions/Invoke-OSAction.ps1 | 9 +++++- docs/common-parameters.md | 10 ++++++ .../pester/Dispatcher.FailOnUnknown.Tests.ps1 | 32 +++++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/pester/Dispatcher.FailOnUnknown.Tests.ps1 diff --git a/README.md b/README.md index 89dc09c8..d33ca36f 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,12 @@ Alternatively, load arguments from a JSON file: pwsh ./actions/Invoke-OSAction.ps1 -ActionName run-unit-tests -ArgsFile ./config/run-tests.json ``` +By default the dispatcher ignores unknown parameters and emits a warning. Add `-FailOnUnknown` to treat unexpected parameters as errors: + +```powershell +pwsh ./actions/Invoke-OSAction.ps1 -ActionName run-unit-tests -ArgsJson $json -FailOnUnknown +``` + ### Discovering actions List all available actions: diff --git a/actions/Invoke-OSAction.ps1 b/actions/Invoke-OSAction.ps1 index 05bea4a2..5785670a 100644 --- a/actions/Invoke-OSAction.ps1 +++ b/actions/Invoke-OSAction.ps1 @@ -8,6 +8,7 @@ param( [Parameter()] [ValidateSet('ERROR','WARN','INFO','DEBUG')] [string] $LogLevel = 'INFO', [switch] $DryRun, [switch] $ListActions, + [switch] $FailOnUnknown, [string] $Describe ) @@ -228,7 +229,13 @@ try { # Only pass parameters that the adapter actually accepts $filterResult = Filter-Args -InputArgs $argsHash -FuncName $funcName -ActionNameForWarn $key -ReturnUnknownParams $argsHash = $filterResult.Args - if ($filterResult.UnknownParams) { Write-Warning $filterResult.UnknownParams } + if ($filterResult.UnknownParams) { + if ($FailOnUnknown) { + throw $filterResult.UnknownParams + } else { + Write-Warning $filterResult.UnknownParams + } + } if ($argsHash.ContainsKey('RelativePath')) { $argsHash['RelativePath'] = Normalize-RelativePath -RelativePath $argsHash['RelativePath'] -BaseDirectory $WorkingDirectory diff --git a/docs/common-parameters.md b/docs/common-parameters.md index 5cbac203..6f3ca973 100644 --- a/docs/common-parameters.md +++ b/docs/common-parameters.md @@ -24,6 +24,16 @@ Example: pwsh ./actions/Invoke-OSAction.ps1 -ActionName run-unit-tests -ArgsJson '{}' -DryRun ``` +### `-FailOnUnknown` + +Treats unsupported parameters as errors. By default, the dispatcher ignores unknown parameters and emits a warning. Specify `-FailOnUnknown` to throw instead. + +Example: + +```powershell +pwsh ./actions/Invoke-OSAction.ps1 -ActionName run-unit-tests -ArgsJson '{}' -FailOnUnknown +``` + ### `-WorkingDirectory` Runs the adapter after changing to the specified directory using `-WorkingDirectory`. diff --git a/tests/pester/Dispatcher.FailOnUnknown.Tests.ps1 b/tests/pester/Dispatcher.FailOnUnknown.Tests.ps1 new file mode 100644 index 00000000..269ea336 --- /dev/null +++ b/tests/pester/Dispatcher.FailOnUnknown.Tests.ps1 @@ -0,0 +1,32 @@ +#requires -Version 7.0 +# Pester tests verifying dispatcher FailOnUnknown switch. +# Requirement: REQ-000 - Dispatcher merges argument sources and warns on unknown parameters. + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..' '..')).Path +$global:dispatcher = Join-Path $repoRoot 'actions' 'Invoke-OSAction.ps1' +Import-Module (Join-Path $PSScriptRoot 'Helper' 'ArgsJson.psm1') + +Describe 'Dispatcher FailOnUnknown' { + $meta = @{ + requirement = 'REQ-000' + Owner = 'DevTools' + Evidence = 'tests/pester/Dispatcher.FailOnUnknown.Tests.ps1' + } + + It 'warns on unknown parameters by default' -Tag 'REQ-000' { + $params = Get-LabVIEWIconEditorArgsJson + $json = @{ MinimumSupportedLVVersion = '2021'; SupportedBitness = '64'; Extra = 'value' } | ConvertTo-Json -Compress + $out = & $global:dispatcher -ActionName close-labview -ArgsJson $json -WorkingDirectory $params.WorkingDirectory -DryRun *>&1 | Out-String + $LASTEXITCODE | Should -Be 0 + $out | Should -Match "Ignored unknown parameters for 'close-labview': Extra" + } + + It 'throws on unknown parameters when FailOnUnknown is set' -Tag 'REQ-000' { + $params = Get-LabVIEWIconEditorArgsJson + $json = @{ MinimumSupportedLVVersion = '2021'; SupportedBitness = '64'; Extra = 'value' } | ConvertTo-Json -Compress + { & $global:dispatcher -ActionName close-labview -ArgsJson $json -WorkingDirectory $params.WorkingDirectory -DryRun -FailOnUnknown } | Should -Throw "Ignored unknown parameters for 'close-labview': Extra" + } +}