From 15b9a0e7fd677353ec4d83199d20eebe68af0a99 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Tue, 19 May 2026 16:23:03 -0700 Subject: [PATCH 01/14] trying docker --- .github/workflows/pr-check.yml | 161 ++++++++++----------------------- 1 file changed, 48 insertions(+), 113 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index caa9da2b..4f45daa1 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -1,162 +1,97 @@ name: pr-check -# Note: If you need to make changes to this file, please use a branch off the main branch instead of a fork. -# The pull_request target from a forked repo will not have access to the secrets needed for this workflow. +# Tests PR code against a local SQL Server container so no Azure credentials are required. +# This workflow uses the pull_request trigger (not pull_request_target), so fork PRs run +# with no secrets and no elevated permissions. on: - pull_request_target: pull_request: - paths: - - '.github/workflows/pr-check.yml' permissions: {} jobs: - # Build job that safely builds artifacts from PR code without access to secrets - build: - environment: Automation test # Require approval before running the action - runs-on: ${{ matrix.os }} + test: + runs-on: ubuntu-latest permissions: contents: read - strategy: - matrix: - os: [windows-latest, ubuntu-latest] - steps: - - name: Checkout from PR branch - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.sha }} - - - name: Verify package-lock.json exists - run: | - if (!(Test-Path package-lock.json)) { - Write-Error "package-lock.json not found. Please commit package-lock.json to ensure reproducible builds." - exit 1 - } - shell: pwsh - - - name: Check if package-lock.json was modified - run: | - # Check git log to see if package-lock.json was modified in this PR - git fetch origin ${{ github.base_ref }} --depth=1 - $changedFiles = git diff --name-only origin/${{ github.base_ref }}...HEAD - - if ($changedFiles -match "package-lock.json") { - Write-Warning "⚠️ package-lock.json has been modified in this PR." - Write-Warning "This requires manual review to ensure no malicious dependencies were added." - Write-Warning "Reviewers: Please carefully examine the dependency changes before approving." - } else { - Write-Host "✓ package-lock.json unchanged - no new dependencies" -ForegroundColor Green - } - shell: pwsh - continue-on-error: true - - - name: Verify package.json integrity - run: | - # Check for suspicious scripts that could be used for attacks - $packageJson = Get-Content package.json | ConvertFrom-Json - $suspiciousScripts = @('preinstall', 'postinstall', 'prepack', 'postpack') - - foreach ($script in $suspiciousScripts) { - if ($packageJson.scripts.$script) { - Write-Warning "⚠️ Found lifecycle script '$script' in package.json" - Write-Warning "Script content: $($packageJson.scripts.$script)" - Write-Warning "Reviewers: Please verify this script is legitimate" - } - } - shell: pwsh - - - name: Installing node_modules with ci (uses lockfile, ignores scripts) - run: npm ci --ignore-scripts - - - name: Audit dependencies for known vulnerabilities - run: npm audit --audit-level=high - continue-on-error: true - - - name: Build GitHub Action - run: npm run build - - - name: Upload build artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: action-build-${{ matrix.os }} - path: | - lib/ - node_modules/ - action.yml - package.json - package-lock.json - retention-days: 1 - - # Deploy job that uses the built artifacts and has access to secrets - deploy: - needs: build - environment: Automation test # this environment requires approval before running the action - runs-on: ${{ matrix.os }} - permissions: checks: write - id-token: write # This is needed for Azure login with OIDC - continue-on-error: true - strategy: - matrix: - os: [windows-latest, ubuntu-latest] + + services: + sqlserver: + image: mcr.microsoft.com/mssql/server:2022-latest + env: + ACCEPT_EULA: Y + # Bootstrap password - rotated to a random value in the first step + MSSQL_SA_PASSWORD: Bootstrap1! + ports: + - 1433:1433 + options: >- + --health-cmd "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Bootstrap1!' -C -Q 'SELECT 1' || exit 1" + --health-interval 10s + --health-timeout 5s + --health-retries 10 env: - TEST_DB: 'SqlActionTest-${{ matrix.os }}' + TEST_DB: SqlActionTest + # Password is appended after rotation in the first step; composed into connection strings below + BASE_CS: 'Server=localhost;User ID=sa;TrustServerCertificate=True;' steps: - - name: Checkout base repository (for test data only) - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Rotate SA password + run: | + SA_PASSWORD="$(openssl rand -base64 18 | tr -d '/+=')Aa1!" + /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Bootstrap1!' -C \ + -Q "ALTER LOGIN sa WITH PASSWORD='${SA_PASSWORD}'" + echo "SA_PASSWORD=${SA_PASSWORD}" >> "$GITHUB_ENV" - - name: Download build artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + - name: Checkout PR + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - name: action-build-${{ matrix.os }} - path: . + ref: ${{ github.event.pull_request.head.sha }} + + + - name: Build GitHub Action + run: npm ci --ignore-scripts && npm run build - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: '8.x' - - name: Install SqlPackage (Linux only) - if: runner.os == 'Linux' - run: dotnet tool install -g microsoft.sqlpackage - - name: Azure Login - uses: azure/login@a65d910e8af852a8061c627c456678983e180302 # v2.2.0 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - name: Install SqlPackage + run: dotnet tool install -g microsoft.sqlpackage - # Deploy a DACPAC with only a table to server + # Deploy a DACPAC with only a table to server (sqlpackage creates the DB if needed) - name: Test DACPAC Action uses: ./ with: - connection-string: 'Server=${{ secrets.TEST_SERVER }};Initial Catalog=${{ env.TEST_DB }};Authentication=Active Directory Default;' + connection-string: '${{ env.BASE_CS }}Password=${{ env.SA_PASSWORD }};Initial Catalog=${{ env.TEST_DB }};' path: ./__testdata__/sql-action.dacpac action: 'publish' + skip-firewall-check: true # Build and publish sqlproj that should create a new view - name: Test Build and Publish uses: ./ with: - connection-string: 'Server=${{ secrets.TEST_SERVER }};Initial Catalog=${{ env.TEST_DB }};Authentication=Active Directory Default;' + connection-string: '${{ env.BASE_CS }}Password=${{ env.SA_PASSWORD }};Initial Catalog=${{ env.TEST_DB }};' path: ./__testdata__/TestProject/sql-action.sqlproj action: 'publish' + skip-firewall-check: true # Execute testsql.sql via script action on server - name: Test SQL Action uses: ./ with: - connection-string: 'Server=${{ secrets.TEST_SERVER }};Initial Catalog=${{ env.TEST_DB }};Authentication=Active Directory Default;' + connection-string: '${{ env.BASE_CS }}Password=${{ env.SA_PASSWORD }};Initial Catalog=${{ env.TEST_DB }};' path: ./__testdata__/testsql.sql + skip-firewall-check: true - name: Cleanup Test Database if: always() uses: ./ - with: - connection-string: 'Server=${{ secrets.TEST_SERVER }};Initial Catalog=master;Authentication=Active Directory Default;' + with: + connection-string: '${{ env.BASE_CS }}Password=${{ env.SA_PASSWORD }};Initial Catalog=master;' path: ./__testdata__/cleanup.sql arguments: '-v DbName="${{ env.TEST_DB }}"' + skip-firewall-check: true From 92d73c9acd587de719ead5b74bd0fcb54d1c9a2d Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Tue, 23 Jun 2026 13:56:36 -0700 Subject: [PATCH 02/14] finding sqlcmd --- .github/workflows/pr-check.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 4f45daa1..0531375d 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -37,10 +37,18 @@ jobs: BASE_CS: 'Server=localhost;User ID=sa;TrustServerCertificate=True;' steps: + - name: Find sqlcmd + run: | + echo "=== which sqlcmd ===" && which sqlcmd || echo "not on PATH" + echo "=== find mssql-tools ===" && find /opt -name sqlcmd 2>/dev/null || echo "not found in /opt" + echo "=== sqlcmd in container ===" && docker exec $(docker ps -q --filter ancestor=mcr.microsoft.com/mssql/server:2022-latest) find / -name sqlcmd 2>/dev/null | head -5 + - name: Rotate SA password run: | SA_PASSWORD="$(openssl rand -base64 18 | tr -d '/+=')Aa1!" - /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Bootstrap1!' -C \ + CONTAINER=$(docker ps -q --filter ancestor=mcr.microsoft.com/mssql/server:2022-latest) + docker exec "$CONTAINER" /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U sa -P 'Bootstrap1!' -C \ -Q "ALTER LOGIN sa WITH PASSWORD='${SA_PASSWORD}'" echo "SA_PASSWORD=${SA_PASSWORD}" >> "$GITHUB_ENV" From fd8a58885e6cf51884a6e463ffa7a6b452a71250 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Tue, 23 Jun 2026 14:56:31 -0700 Subject: [PATCH 03/14] cleanup --- .github/workflows/pr-check.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 0531375d..963ea7af 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -1,8 +1,7 @@ name: pr-check -# Tests PR code against a local SQL Server container so no Azure credentials are required. -# This workflow uses the pull_request trigger (not pull_request_target), so fork PRs run -# with no secrets and no elevated permissions. +# Note: If you need to make changes to this file, please use a branch off the main branch instead of a fork. +# The pull_request target from a forked repo will not have access to the secrets needed for this workflow. on: pull_request: @@ -37,12 +36,6 @@ jobs: BASE_CS: 'Server=localhost;User ID=sa;TrustServerCertificate=True;' steps: - - name: Find sqlcmd - run: | - echo "=== which sqlcmd ===" && which sqlcmd || echo "not on PATH" - echo "=== find mssql-tools ===" && find /opt -name sqlcmd 2>/dev/null || echo "not found in /opt" - echo "=== sqlcmd in container ===" && docker exec $(docker ps -q --filter ancestor=mcr.microsoft.com/mssql/server:2022-latest) find / -name sqlcmd 2>/dev/null | head -5 - - name: Rotate SA password run: | SA_PASSWORD="$(openssl rand -base64 18 | tr -d '/+=')Aa1!" From d1492ba43bcac20389e23921e21784330d05e56c Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Tue, 23 Jun 2026 14:57:03 -0700 Subject: [PATCH 04/14] attempting to add Windows support back --- .github/workflows/pr-check.yml | 83 +++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 963ea7af..47ec9d50 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -1,7 +1,8 @@ name: pr-check -# Note: If you need to make changes to this file, please use a branch off the main branch instead of a fork. -# The pull_request target from a forked repo will not have access to the secrets needed for this workflow. +# Tests PR code against a local SQL Server container so no Azure credentials are required. +# This workflow uses the pull_request trigger (not pull_request_target), so fork PRs run +# with no secrets and no elevated permissions. on: pull_request: @@ -10,47 +11,71 @@ permissions: {} jobs: test: - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} permissions: contents: read checks: write - services: - sqlserver: - image: mcr.microsoft.com/mssql/server:2022-latest - env: - ACCEPT_EULA: Y - # Bootstrap password - rotated to a random value in the first step - MSSQL_SA_PASSWORD: Bootstrap1! - ports: - - 1433:1433 - options: >- - --health-cmd "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Bootstrap1!' -C -Q 'SELECT 1' || exit 1" - --health-interval 10s - --health-timeout 5s - --health-retries 10 - env: TEST_DB: SqlActionTest - # Password is appended after rotation in the first step; composed into connection strings below + MSSQL_IMAGE: mcr.microsoft.com/mssql/server:2022-latest + # Password is appended after rotation; composed into connection strings below BASE_CS: 'Server=localhost;User ID=sa;TrustServerCertificate=True;' + defaults: + run: + # Use bash on both Linux and Windows (Git Bash is preinstalled on windows-latest). + # This keeps the docker / openssl / sqlcmd commands identical across runners. + shell: bash + steps: + - name: Checkout PR + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Switch Docker to Linux containers (Windows only) + if: runner.os == 'Windows' + shell: pwsh + run: | + & 'C:\Program Files\Docker\Docker\DockerCli.exe' -SwitchLinuxEngine + docker version + + - name: Start SQL Server container + run: | + docker run -d --name sqlserver \ + -e "ACCEPT_EULA=Y" \ + -e "MSSQL_SA_PASSWORD=Bootstrap1!" \ + -p 1433:1433 \ + "$MSSQL_IMAGE" + + - name: Wait for SQL Server to be ready + run: | + for i in $(seq 1 30); do + if docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U sa -P 'Bootstrap1!' -C -Q 'SELECT 1' >/dev/null 2>&1; then + echo "SQL Server is ready" + exit 0 + fi + echo "Waiting for SQL Server... ($i/30)" + sleep 5 + done + echo "SQL Server did not become ready in time" + docker logs sqlserver + exit 1 + - name: Rotate SA password run: | SA_PASSWORD="$(openssl rand -base64 18 | tr -d '/+=')Aa1!" - CONTAINER=$(docker ps -q --filter ancestor=mcr.microsoft.com/mssql/server:2022-latest) - docker exec "$CONTAINER" /opt/mssql-tools18/bin/sqlcmd \ + docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd \ -S localhost -U sa -P 'Bootstrap1!' -C \ -Q "ALTER LOGIN sa WITH PASSWORD='${SA_PASSWORD}'" echo "SA_PASSWORD=${SA_PASSWORD}" >> "$GITHUB_ENV" - - name: Checkout PR - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: Build GitHub Action run: npm ci --ignore-scripts && npm run build @@ -96,3 +121,7 @@ jobs: path: ./__testdata__/cleanup.sql arguments: '-v DbName="${{ env.TEST_DB }}"' skip-firewall-check: true + + - name: Stop SQL Server container + if: always() + run: docker rm -f sqlserver || true From b45482ba71a87b19e8802e02c9ac77a571130e84 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Tue, 23 Jun 2026 15:05:34 -0700 Subject: [PATCH 05/14] trying with SqlExpress server --- .github/workflows/pr-check.yml | 62 ++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 47ec9d50..5be79fa1 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -1,8 +1,11 @@ name: pr-check -# Tests PR code against a local SQL Server container so no Azure credentials are required. +# Tests PR code against a local SQL Server instance so no Azure credentials are required. # This workflow uses the pull_request trigger (not pull_request_target), so fork PRs run # with no secrets and no elevated permissions. +# +# - Linux runners: spin up SQL Server 2022 in a Docker container with SA auth. +# - Windows runners: use the pre-installed SQL Server Express with Integrated Security. on: pull_request: @@ -23,13 +26,9 @@ jobs: env: TEST_DB: SqlActionTest MSSQL_IMAGE: mcr.microsoft.com/mssql/server:2022-latest - # Password is appended after rotation; composed into connection strings below - BASE_CS: 'Server=localhost;User ID=sa;TrustServerCertificate=True;' defaults: run: - # Use bash on both Linux and Windows (Git Bash is preinstalled on windows-latest). - # This keeps the docker / openssl / sqlcmd commands identical across runners. shell: bash steps: @@ -38,14 +37,10 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - - name: Switch Docker to Linux containers (Windows only) - if: runner.os == 'Windows' - shell: pwsh - run: | - & 'C:\Program Files\Docker\Docker\DockerCli.exe' -SwitchLinuxEngine - docker version + # --- Linux setup: Docker container + SA auth with rotated password --- - - name: Start SQL Server container + - name: Start SQL Server container (Linux) + if: runner.os == 'Linux' run: | docker run -d --name sqlserver \ -e "ACCEPT_EULA=Y" \ @@ -53,7 +48,8 @@ jobs: -p 1433:1433 \ "$MSSQL_IMAGE" - - name: Wait for SQL Server to be ready + - name: Wait for SQL Server to be ready (Linux) + if: runner.os == 'Linux' run: | for i in $(seq 1 30); do if docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd \ @@ -68,13 +64,37 @@ jobs: docker logs sqlserver exit 1 - - name: Rotate SA password + - name: Rotate SA password and set connection string (Linux) + if: runner.os == 'Linux' run: | SA_PASSWORD="$(openssl rand -base64 18 | tr -d '/+=')Aa1!" docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd \ -S localhost -U sa -P 'Bootstrap1!' -C \ -Q "ALTER LOGIN sa WITH PASSWORD='${SA_PASSWORD}'" - echo "SA_PASSWORD=${SA_PASSWORD}" >> "$GITHUB_ENV" + echo "BASE_CS=Server=localhost;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV" + + # --- Windows setup: pre-installed SQL Server Express + Integrated Security --- + + - name: Start SQL Server Express service (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $service = Get-Service -Name 'MSSQL$SQLEXPRESS' -ErrorAction Stop + if ($service.Status -ne 'Running') { + Start-Service -Name 'MSSQL$SQLEXPRESS' + } + # SQL Server Browser helps with named-instance resolution + Set-Service -Name SQLBrowser -StartupType Manual + Start-Service -Name SQLBrowser -ErrorAction SilentlyContinue + Get-Service -Name 'MSSQL$SQLEXPRESS','SQLBrowser' | Format-Table + + - name: Set connection string (Windows) + if: runner.os == 'Windows' + run: | + # Integrated Security uses the runner's local identity — no password needed + echo 'BASE_CS=Server=localhost\SQLEXPRESS;Integrated Security=True;TrustServerCertificate=True;' >> "$GITHUB_ENV" + + # --- Common build and test steps --- - name: Build GitHub Action run: npm ci --ignore-scripts && npm run build @@ -91,7 +111,7 @@ jobs: - name: Test DACPAC Action uses: ./ with: - connection-string: '${{ env.BASE_CS }}Password=${{ env.SA_PASSWORD }};Initial Catalog=${{ env.TEST_DB }};' + connection-string: '${{ env.BASE_CS }}Initial Catalog=${{ env.TEST_DB }};' path: ./__testdata__/sql-action.dacpac action: 'publish' skip-firewall-check: true @@ -100,7 +120,7 @@ jobs: - name: Test Build and Publish uses: ./ with: - connection-string: '${{ env.BASE_CS }}Password=${{ env.SA_PASSWORD }};Initial Catalog=${{ env.TEST_DB }};' + connection-string: '${{ env.BASE_CS }}Initial Catalog=${{ env.TEST_DB }};' path: ./__testdata__/TestProject/sql-action.sqlproj action: 'publish' skip-firewall-check: true @@ -109,7 +129,7 @@ jobs: - name: Test SQL Action uses: ./ with: - connection-string: '${{ env.BASE_CS }}Password=${{ env.SA_PASSWORD }};Initial Catalog=${{ env.TEST_DB }};' + connection-string: '${{ env.BASE_CS }}Initial Catalog=${{ env.TEST_DB }};' path: ./__testdata__/testsql.sql skip-firewall-check: true @@ -117,11 +137,11 @@ jobs: if: always() uses: ./ with: - connection-string: '${{ env.BASE_CS }}Password=${{ env.SA_PASSWORD }};Initial Catalog=master;' + connection-string: '${{ env.BASE_CS }}Initial Catalog=master;' path: ./__testdata__/cleanup.sql arguments: '-v DbName="${{ env.TEST_DB }}"' skip-firewall-check: true - - name: Stop SQL Server container - if: always() + - name: Stop SQL Server container (Linux) + if: always() && runner.os == 'Linux' run: docker rm -f sqlserver || true From f2d9d6d4e908f327d93f1ccf38dc7ac42571e99b Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Tue, 23 Jun 2026 15:09:18 -0700 Subject: [PATCH 06/14] sqllocaldb --- .github/workflows/pr-check.yml | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 5be79fa1..7c80a6a6 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -73,26 +73,25 @@ jobs: -Q "ALTER LOGIN sa WITH PASSWORD='${SA_PASSWORD}'" echo "BASE_CS=Server=localhost;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV" - # --- Windows setup: pre-installed SQL Server Express + Integrated Security --- + # --- Windows setup: install SqlLocalDB via Chocolatey, Integrated Security --- - - name: Start SQL Server Express service (Windows) + - name: Install SqlLocalDB (Windows) if: runner.os == 'Windows' shell: pwsh run: | - $service = Get-Service -Name 'MSSQL$SQLEXPRESS' -ErrorAction Stop - if ($service.Status -ne 'Running') { - Start-Service -Name 'MSSQL$SQLEXPRESS' - } - # SQL Server Browser helps with named-instance resolution - Set-Service -Name SQLBrowser -StartupType Manual - Start-Service -Name SQLBrowser -ErrorAction SilentlyContinue - Get-Service -Name 'MSSQL$SQLEXPRESS','SQLBrowser' | Format-Table + choco install sqllocaldb -y --no-progress + # Refresh PATH so SqlLocalDB.exe is reachable + $env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + + [System.Environment]::GetEnvironmentVariable('Path','User') + SqlLocalDB.exe info + SqlLocalDB.exe start MSSQLLocalDB + SqlLocalDB.exe info MSSQLLocalDB - name: Set connection string (Windows) if: runner.os == 'Windows' run: | - # Integrated Security uses the runner's local identity — no password needed - echo 'BASE_CS=Server=localhost\SQLEXPRESS;Integrated Security=True;TrustServerCertificate=True;' >> "$GITHUB_ENV" + # LocalDB uses a named-pipe instance; Integrated Security authenticates as the runner's local user + echo 'BASE_CS=Server=(localdb)\MSSQLLocalDB;Integrated Security=True;TrustServerCertificate=True;' >> "$GITHUB_ENV" # --- Common build and test steps --- From 1cc219da08a15b28f3c1d5bc6f6d1bbb586e1519 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Tue, 23 Jun 2026 15:23:54 -0700 Subject: [PATCH 07/14] sqlexpress for sqlauth --- .github/workflows/pr-check.yml | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 7c80a6a6..2a68feba 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -73,25 +73,31 @@ jobs: -Q "ALTER LOGIN sa WITH PASSWORD='${SA_PASSWORD}'" echo "BASE_CS=Server=localhost;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV" - # --- Windows setup: install SqlLocalDB via Chocolatey, Integrated Security --- + # --- Windows setup: install SQL Server Express with SQL auth --- - - name: Install SqlLocalDB (Windows) + - name: Generate SA password (Windows) + if: runner.os == 'Windows' + run: | + SA_PASSWORD="$(openssl rand -base64 18 | tr -d '/+=')Aa1!" + echo "::add-mask::${SA_PASSWORD}" + echo "SA_PASSWORD=${SA_PASSWORD}" >> "$GITHUB_ENV" + + - name: Install SQL Server Express (Windows) if: runner.os == 'Windows' shell: pwsh run: | - choco install sqllocaldb -y --no-progress - # Refresh PATH so SqlLocalDB.exe is reachable - $env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + - [System.Environment]::GetEnvironmentVariable('Path','User') - SqlLocalDB.exe info - SqlLocalDB.exe start MSSQLLocalDB - SqlLocalDB.exe info MSSQLLocalDB + choco install sql-server-express -y --no-progress ` + --params "/SECURITYMODE=SQL /SAPWD=$env:SA_PASSWORD /TCPENABLED=1" + # SQL Server Browser helps named-instance resolution; not strictly required when using a port + Set-Service -Name SQLBrowser -StartupType Manual -ErrorAction SilentlyContinue + Start-Service -Name SQLBrowser -ErrorAction SilentlyContinue + # Confirm the engine service is running + Get-Service | Where-Object { $_.Name -like 'MSSQL*' } | Format-Table - name: Set connection string (Windows) if: runner.os == 'Windows' run: | - # LocalDB uses a named-pipe instance; Integrated Security authenticates as the runner's local user - echo 'BASE_CS=Server=(localdb)\MSSQLLocalDB;Integrated Security=True;TrustServerCertificate=True;' >> "$GITHUB_ENV" + echo "BASE_CS=Server=localhost\SQLEXPRESS;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV" # --- Common build and test steps --- From 401ff3b3885481386f08c19c3a29f1bac1a79dd4 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Tue, 23 Jun 2026 15:32:09 -0700 Subject: [PATCH 08/14] trying to set login mode --- .github/workflows/pr-check.yml | 38 +++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 2a68feba..637cff24 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -73,7 +73,7 @@ jobs: -Q "ALTER LOGIN sa WITH PASSWORD='${SA_PASSWORD}'" echo "BASE_CS=Server=localhost;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV" - # --- Windows setup: install SQL Server Express with SQL auth --- + # --- Windows setup: install SQL Server Express, then enable SQL auth with sa --- - name: Generate SA password (Windows) if: runner.os == 'Windows' @@ -86,14 +86,38 @@ jobs: if: runner.os == 'Windows' shell: pwsh run: | - choco install sql-server-express -y --no-progress ` - --params "/SECURITYMODE=SQL /SAPWD=$env:SA_PASSWORD /TCPENABLED=1" - # SQL Server Browser helps named-instance resolution; not strictly required when using a port - Set-Service -Name SQLBrowser -StartupType Manual -ErrorAction SilentlyContinue - Start-Service -Name SQLBrowser -ErrorAction SilentlyContinue - # Confirm the engine service is running + choco install sql-server-express -y --no-progress Get-Service | Where-Object { $_.Name -like 'MSSQL*' } | Format-Table + - name: Enable mixed-mode auth and sa login (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + # Find sqlcmd.exe (installed by Express) + $sqlcmd = (Get-ChildItem 'C:\Program Files\Microsoft SQL Server' -Recurse -Filter sqlcmd.exe -ErrorAction SilentlyContinue | + Select-Object -First 1).FullName + if (-not $sqlcmd) { throw "sqlcmd.exe not found after SQL Express install" } + Write-Host "Using sqlcmd at: $sqlcmd" + + # Connect with Windows auth (runner is local admin), switch to mixed mode + & $sqlcmd -S 'localhost\SQLEXPRESS' -E -b -Q @" + EXEC xp_instance_regwrite N'HKEY_LOCAL_MACHINE', + N'Software\Microsoft\MSSQLServer\MSSQLServer', + N'LoginMode', REG_DWORD, 2; +"@ + if ($LASTEXITCODE -ne 0) { throw "Failed to set LoginMode" } + + # Restart so LoginMode change takes effect + Restart-Service 'MSSQL$SQLEXPRESS' -Force + + # Enable the sa login and set its password + & $sqlcmd -S 'localhost\SQLEXPRESS' -E -b -Q "ALTER LOGIN sa ENABLE; ALTER LOGIN sa WITH PASSWORD='$env:SA_PASSWORD';" + if ($LASTEXITCODE -ne 0) { throw "Failed to enable/set sa login" } + + # Verify SQL auth works + & $sqlcmd -S 'localhost\SQLEXPRESS' -U sa -P $env:SA_PASSWORD -b -Q "SELECT @@VERSION" + if ($LASTEXITCODE -ne 0) { throw "SQL auth verification failed" } + - name: Set connection string (Windows) if: runner.os == 'Windows' run: | From 489295768f1c20ec07c4714199520f9373425c67 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Tue, 23 Jun 2026 16:24:32 -0700 Subject: [PATCH 09/14] Trying direct download --- .github/workflows/pr-check.yml | 79 +++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 637cff24..07154b7a 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -73,7 +73,7 @@ jobs: -Q "ALTER LOGIN sa WITH PASSWORD='${SA_PASSWORD}'" echo "BASE_CS=Server=localhost;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV" - # --- Windows setup: install SQL Server Express, then enable SQL auth with sa --- + # --- Windows setup: install SQL Server 2022 Express directly from Microsoft --- - name: Generate SA password (Windows) if: runner.os == 'Windows' @@ -82,39 +82,70 @@ jobs: echo "::add-mask::${SA_PASSWORD}" echo "SA_PASSWORD=${SA_PASSWORD}" >> "$GITHUB_ENV" - - name: Install SQL Server Express (Windows) + - name: Download SQL Server 2022 Express installer (Windows) if: runner.os == 'Windows' shell: pwsh run: | - choco install sql-server-express -y --no-progress + $ssei = Join-Path $env:RUNNER_TEMP 'SQL2022-SSEI-Expr.exe' + $media = Join-Path $env:RUNNER_TEMP 'sql-media' + # Stable Microsoft fwlink for the SQL Server 2022 Express bootstrapper (SSEI) + Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/?linkid=2216019' -OutFile $ssei + # Tell the bootstrapper to download (not install) the full installer package + & $ssei /Quiet /Action=Download /Language=en-US /MediaPath=$media /MediaType=Core /HideProgressBar + if ($LASTEXITCODE -ne 0) { throw "SSEI download failed with exit code $LASTEXITCODE" } + Get-ChildItem $media + + - name: Install SQL Server Express with SA auth (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $media = Join-Path $env:RUNNER_TEMP 'sql-media' + # SSEI produces a self-extracting installer (SQLEXPR*.exe). Extract its contents first. + $selfExtract = Get-ChildItem $media -Filter 'SQLEXPR*.exe' | Select-Object -First 1 + if (-not $selfExtract) { throw "Installer EXE not found in $media" } + $extracted = Join-Path $env:RUNNER_TEMP 'sql-extracted' + Write-Host "Extracting $($selfExtract.FullName) -> $extracted" + & $selfExtract.FullName /Q /X:"$extracted" | Out-Null + # Self-extraction is async; wait for setup.exe to appear + $deadline = (Get-Date).AddMinutes(2) + while (-not (Test-Path (Join-Path $extracted 'setup.exe'))) { + if ((Get-Date) -gt $deadline) { throw "Extraction did not complete in time" } + Start-Sleep -Seconds 2 + } + + $setup = Join-Path $extracted 'setup.exe' + Write-Host "Running $setup with SECURITYMODE=SQL" + & $setup ` + /Q ` + /ACTION=Install ` + /FEATURES=SQLEngine ` + /INSTANCENAME=SQLEXPRESS ` + /SECURITYMODE=SQL ` + /SAPWD="$env:SA_PASSWORD" ` + /TCPENABLED=1 ` + /IACCEPTSQLSERVERLICENSETERMS ` + /UPDATEENABLED=False ` + /SQLSYSADMINACCOUNTS="BUILTIN\Administrators" + if ($LASTEXITCODE -ne 0) { + Write-Host "Setup failed with exit code $LASTEXITCODE" + # Surface the most recent setup logs to make failures diagnosable + $logDir = 'C:\Program Files\Microsoft SQL Server\160\Setup Bootstrap\Log' + if (Test-Path $logDir) { + Get-ChildItem $logDir -Recurse -Filter 'Summary*.txt' | + Sort-Object LastWriteTime -Descending | Select-Object -First 1 | + ForEach-Object { Write-Host "=== $($_.FullName) ==="; Get-Content $_.FullName } + } + throw "SQL Server install failed" + } Get-Service | Where-Object { $_.Name -like 'MSSQL*' } | Format-Table - - name: Enable mixed-mode auth and sa login (Windows) + - name: Verify SQL auth (Windows) if: runner.os == 'Windows' shell: pwsh run: | - # Find sqlcmd.exe (installed by Express) $sqlcmd = (Get-ChildItem 'C:\Program Files\Microsoft SQL Server' -Recurse -Filter sqlcmd.exe -ErrorAction SilentlyContinue | Select-Object -First 1).FullName - if (-not $sqlcmd) { throw "sqlcmd.exe not found after SQL Express install" } - Write-Host "Using sqlcmd at: $sqlcmd" - - # Connect with Windows auth (runner is local admin), switch to mixed mode - & $sqlcmd -S 'localhost\SQLEXPRESS' -E -b -Q @" - EXEC xp_instance_regwrite N'HKEY_LOCAL_MACHINE', - N'Software\Microsoft\MSSQLServer\MSSQLServer', - N'LoginMode', REG_DWORD, 2; -"@ - if ($LASTEXITCODE -ne 0) { throw "Failed to set LoginMode" } - - # Restart so LoginMode change takes effect - Restart-Service 'MSSQL$SQLEXPRESS' -Force - - # Enable the sa login and set its password - & $sqlcmd -S 'localhost\SQLEXPRESS' -E -b -Q "ALTER LOGIN sa ENABLE; ALTER LOGIN sa WITH PASSWORD='$env:SA_PASSWORD';" - if ($LASTEXITCODE -ne 0) { throw "Failed to enable/set sa login" } - - # Verify SQL auth works + if (-not $sqlcmd) { throw "sqlcmd.exe not found after install" } & $sqlcmd -S 'localhost\SQLEXPRESS' -U sa -P $env:SA_PASSWORD -b -Q "SELECT @@VERSION" if ($LASTEXITCODE -ne 0) { throw "SQL auth verification failed" } From 1f979b1053e880927b7aebbd2fe05ebf8c23ec84 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Tue, 23 Jun 2026 16:53:42 -0700 Subject: [PATCH 10/14] 2025 SSEI --- .github/workflows/pr-check.yml | 73 +++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 07154b7a..59ce3c7c 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -82,17 +82,29 @@ jobs: echo "::add-mask::${SA_PASSWORD}" echo "SA_PASSWORD=${SA_PASSWORD}" >> "$GITHUB_ENV" - - name: Download SQL Server 2022 Express installer (Windows) + - name: Download SQL Server 2025 Express installer (Windows) if: runner.os == 'Windows' shell: pwsh run: | - $ssei = Join-Path $env:RUNNER_TEMP 'SQL2022-SSEI-Expr.exe' + $ssei = Join-Path $env:RUNNER_TEMP 'SQL2025-SSEI-Expr.exe' $media = Join-Path $env:RUNNER_TEMP 'sql-media' - # Stable Microsoft fwlink for the SQL Server 2022 Express bootstrapper (SSEI) + # Stable Microsoft fwlink redirecting to the SQL Server 2025 Express bootstrapper (SSEI) Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/?linkid=2216019' -OutFile $ssei - # Tell the bootstrapper to download (not install) the full installer package - & $ssei /Quiet /Action=Download /Language=en-US /MediaPath=$media /MediaType=Core /HideProgressBar - if ($LASTEXITCODE -ne 0) { throw "SSEI download failed with exit code $LASTEXITCODE" } + Write-Host "Downloaded SSEI: $((Get-Item $ssei).Length) bytes" + + # Run SSEI in download-only mode. Use Start-Process -Wait so we get a real exit code + # (PowerShell's `&` returns asynchronously for installer-class executables). + $p = Start-Process -FilePath $ssei -Wait -PassThru -ArgumentList @( + '/Quiet', + '/Action=Download', + '/Language=en-US', + "/MediaPath=$media", + '/MediaType=Core', + '/HideProgressBar' + ) + if ($p.ExitCode -ne 0) { throw "SSEI download failed with exit code $($p.ExitCode)" } + + Write-Host "=== Media directory contents ===" Get-ChildItem $media - name: Install SQL Server Express with SA auth (Windows) @@ -100,38 +112,37 @@ jobs: shell: pwsh run: | $media = Join-Path $env:RUNNER_TEMP 'sql-media' - # SSEI produces a self-extracting installer (SQLEXPR*.exe). Extract its contents first. $selfExtract = Get-ChildItem $media -Filter 'SQLEXPR*.exe' | Select-Object -First 1 if (-not $selfExtract) { throw "Installer EXE not found in $media" } + $extracted = Join-Path $env:RUNNER_TEMP 'sql-extracted' Write-Host "Extracting $($selfExtract.FullName) -> $extracted" - & $selfExtract.FullName /Q /X:"$extracted" | Out-Null - # Self-extraction is async; wait for setup.exe to appear - $deadline = (Get-Date).AddMinutes(2) - while (-not (Test-Path (Join-Path $extracted 'setup.exe'))) { - if ((Get-Date) -gt $deadline) { throw "Extraction did not complete in time" } - Start-Sleep -Seconds 2 - } + $extract = Start-Process -FilePath $selfExtract.FullName -Wait -PassThru ` + -ArgumentList '/Q', "/X:$extracted" + if ($extract.ExitCode -ne 0) { throw "Extraction failed with exit code $($extract.ExitCode)" } $setup = Join-Path $extracted 'setup.exe' + if (-not (Test-Path $setup)) { throw "setup.exe not found at $setup" } + Write-Host "Running $setup with SECURITYMODE=SQL" - & $setup ` - /Q ` - /ACTION=Install ` - /FEATURES=SQLEngine ` - /INSTANCENAME=SQLEXPRESS ` - /SECURITYMODE=SQL ` - /SAPWD="$env:SA_PASSWORD" ` - /TCPENABLED=1 ` - /IACCEPTSQLSERVERLICENSETERMS ` - /UPDATEENABLED=False ` - /SQLSYSADMINACCOUNTS="BUILTIN\Administrators" - if ($LASTEXITCODE -ne 0) { - Write-Host "Setup failed with exit code $LASTEXITCODE" - # Surface the most recent setup logs to make failures diagnosable - $logDir = 'C:\Program Files\Microsoft SQL Server\160\Setup Bootstrap\Log' - if (Test-Path $logDir) { - Get-ChildItem $logDir -Recurse -Filter 'Summary*.txt' | + $install = Start-Process -FilePath $setup -Wait -PassThru -ArgumentList @( + '/Q', + '/ACTION=Install', + '/FEATURES=SQLEngine', + '/INSTANCENAME=SQLEXPRESS', + '/SECURITYMODE=SQL', + "/SAPWD=$env:SA_PASSWORD", + '/TCPENABLED=1', + '/IACCEPTSQLSERVERLICENSETERMS', + '/UPDATEENABLED=False', + '/SQLSYSADMINACCOUNTS=BUILTIN\Administrators' + ) + + if ($install.ExitCode -ne 0) { + Write-Host "Setup failed with exit code $($install.ExitCode)" + $logRoot = 'C:\Program Files\Microsoft SQL Server' + if (Test-Path $logRoot) { + Get-ChildItem $logRoot -Recurse -Filter 'Summary*.txt' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | ForEach-Object { Write-Host "=== $($_.FullName) ==="; Get-Content $_.FullName } } From 00110e71ded17070dff6ab08ff7c8428bdd74ad6 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Tue, 23 Jun 2026 21:48:33 -0700 Subject: [PATCH 11/14] default instance --- .github/workflows/pr-check.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 59ce3c7c..c4360f5b 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -129,7 +129,7 @@ jobs: '/Q', '/ACTION=Install', '/FEATURES=SQLEngine', - '/INSTANCENAME=SQLEXPRESS', + '/INSTANCENAME=MSSQLSERVER', '/SECURITYMODE=SQL', "/SAPWD=$env:SA_PASSWORD", '/TCPENABLED=1', @@ -157,13 +157,13 @@ jobs: $sqlcmd = (Get-ChildItem 'C:\Program Files\Microsoft SQL Server' -Recurse -Filter sqlcmd.exe -ErrorAction SilentlyContinue | Select-Object -First 1).FullName if (-not $sqlcmd) { throw "sqlcmd.exe not found after install" } - & $sqlcmd -S 'localhost\SQLEXPRESS' -U sa -P $env:SA_PASSWORD -b -Q "SELECT @@VERSION" + & $sqlcmd -S 'localhost' -U sa -P $env:SA_PASSWORD -b -Q "SELECT @@VERSION" if ($LASTEXITCODE -ne 0) { throw "SQL auth verification failed" } - name: Set connection string (Windows) if: runner.os == 'Windows' run: | - echo "BASE_CS=Server=localhost\SQLEXPRESS;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV" + echo "BASE_CS=Server=localhost;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV" # --- Common build and test steps --- From 00b5547831f7144be0abfa63d3c68e8d20b5d64d Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Tue, 23 Jun 2026 22:42:01 -0700 Subject: [PATCH 12/14] refactoring --- .github/actions/setup-sql-linux/action.yml | 51 +++++++ .github/actions/setup-sql-windows/action.yml | 96 +++++++++++++ .github/workflows/pr-check.yml | 135 ++----------------- 3 files changed, 158 insertions(+), 124 deletions(-) create mode 100644 .github/actions/setup-sql-linux/action.yml create mode 100644 .github/actions/setup-sql-windows/action.yml diff --git a/.github/actions/setup-sql-linux/action.yml b/.github/actions/setup-sql-linux/action.yml new file mode 100644 index 00000000..7ce4bac5 --- /dev/null +++ b/.github/actions/setup-sql-linux/action.yml @@ -0,0 +1,51 @@ +name: 'Set up SQL Server on Linux' +description: 'Starts a SQL Server 2022 Docker container with SA auth and exports BASE_CS.' + +inputs: + sa-password: + description: 'SA password to use for the SQL Server instance.' + required: true + image: + description: 'SQL Server Docker image to use.' + required: false + default: 'mcr.microsoft.com/mssql/server:2022-latest' + +runs: + using: composite + steps: + - name: Start SQL Server container + shell: bash + env: + SA_PASSWORD: ${{ inputs.sa-password }} + MSSQL_IMAGE: ${{ inputs.image }} + run: | + docker run -d --name sqlserver \ + -e "ACCEPT_EULA=Y" \ + -e "MSSQL_SA_PASSWORD=${SA_PASSWORD}" \ + -p 1433:1433 \ + "$MSSQL_IMAGE" + + - name: Wait for SQL Server to be ready + shell: bash + env: + SA_PASSWORD: ${{ inputs.sa-password }} + run: | + for i in $(seq 1 30); do + if docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U sa -P "${SA_PASSWORD}" -C -Q 'SELECT 1' >/dev/null 2>&1; then + echo "SQL Server is ready" + exit 0 + fi + echo "Waiting for SQL Server... ($i/30)" + sleep 5 + done + echo "SQL Server did not become ready in time" + docker logs sqlserver + exit 1 + + - name: Set connection string + shell: bash + env: + SA_PASSWORD: ${{ inputs.sa-password }} + run: | + echo "BASE_CS=Server=localhost;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV" diff --git a/.github/actions/setup-sql-windows/action.yml b/.github/actions/setup-sql-windows/action.yml new file mode 100644 index 00000000..414fd7c5 --- /dev/null +++ b/.github/actions/setup-sql-windows/action.yml @@ -0,0 +1,96 @@ +name: 'Set up SQL Server on Windows' +description: 'Installs SQL Server 2025 Express with SA auth and exports BASE_CS.' + +inputs: + sa-password: + description: 'SA password to use for the SQL Server instance.' + required: true + +runs: + using: composite + steps: + - name: Download SQL Server 2025 Express installer + shell: pwsh + run: | + $ssei = Join-Path $env:RUNNER_TEMP 'SQL2025-SSEI-Expr.exe' + $media = Join-Path $env:RUNNER_TEMP 'sql-media' + # Stable Microsoft fwlink redirecting to the SQL Server 2025 Express bootstrapper (SSEI) + Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/?linkid=2216019' -OutFile $ssei + Write-Host "Downloaded SSEI: $((Get-Item $ssei).Length) bytes" + + # Run SSEI in download-only mode. Use Start-Process -Wait so we get a real exit code + # (PowerShell's `&` returns asynchronously for installer-class executables). + $p = Start-Process -FilePath $ssei -Wait -PassThru -ArgumentList @( + '/Quiet', + '/Action=Download', + '/Language=en-US', + "/MediaPath=$media", + '/MediaType=Core', + '/HideProgressBar' + ) + if ($p.ExitCode -ne 0) { throw "SSEI download failed with exit code $($p.ExitCode)" } + + Write-Host "=== Media directory contents ===" + Get-ChildItem $media + + - name: Install SQL Server Express with SA auth + shell: pwsh + env: + SA_PASSWORD: ${{ inputs.sa-password }} + run: | + $media = Join-Path $env:RUNNER_TEMP 'sql-media' + $selfExtract = Get-ChildItem $media -Filter 'SQLEXPR*.exe' | Select-Object -First 1 + if (-not $selfExtract) { throw "Installer EXE not found in $media" } + + $extracted = Join-Path $env:RUNNER_TEMP 'sql-extracted' + Write-Host "Extracting $($selfExtract.FullName) -> $extracted" + $extract = Start-Process -FilePath $selfExtract.FullName -Wait -PassThru ` + -ArgumentList '/Q', "/X:$extracted" + if ($extract.ExitCode -ne 0) { throw "Extraction failed with exit code $($extract.ExitCode)" } + + $setup = Join-Path $extracted 'setup.exe' + if (-not (Test-Path $setup)) { throw "setup.exe not found at $setup" } + + Write-Host "Running $setup with SECURITYMODE=SQL" + $install = Start-Process -FilePath $setup -Wait -PassThru -ArgumentList @( + '/Q', + '/ACTION=Install', + '/FEATURES=SQLEngine', + '/INSTANCENAME=MSSQLSERVER', + '/SECURITYMODE=SQL', + "/SAPWD=$env:SA_PASSWORD", + '/TCPENABLED=1', + '/IACCEPTSQLSERVERLICENSETERMS', + '/UPDATEENABLED=False', + '/SQLSYSADMINACCOUNTS=BUILTIN\Administrators' + ) + + if ($install.ExitCode -ne 0) { + Write-Host "Setup failed with exit code $($install.ExitCode)" + $logRoot = 'C:\Program Files\Microsoft SQL Server' + if (Test-Path $logRoot) { + Get-ChildItem $logRoot -Recurse -Filter 'Summary*.txt' -ErrorAction SilentlyContinue | + Sort-Object LastWriteTime -Descending | Select-Object -First 1 | + ForEach-Object { Write-Host "=== $($_.FullName) ==="; Get-Content $_.FullName } + } + throw "SQL Server install failed" + } + Get-Service | Where-Object { $_.Name -like 'MSSQL*' } | Format-Table + + - name: Verify SQL auth + shell: pwsh + env: + SA_PASSWORD: ${{ inputs.sa-password }} + run: | + $sqlcmd = (Get-ChildItem 'C:\Program Files\Microsoft SQL Server' -Recurse -Filter sqlcmd.exe -ErrorAction SilentlyContinue | + Select-Object -First 1).FullName + if (-not $sqlcmd) { throw "sqlcmd.exe not found after install" } + & $sqlcmd -S 'localhost' -U sa -P $env:SA_PASSWORD -b -Q "SELECT @@VERSION" + if ($LASTEXITCODE -ne 0) { throw "SQL auth verification failed" } + + - name: Set connection string + shell: bash + env: + SA_PASSWORD: ${{ inputs.sa-password }} + run: | + echo "BASE_CS=Server=localhost;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV" diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index c4360f5b..358a0771 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -5,7 +5,7 @@ name: pr-check # with no secrets and no elevated permissions. # # - Linux runners: spin up SQL Server 2022 in a Docker container with SA auth. -# - Windows runners: use the pre-installed SQL Server Express with Integrated Security. +# - Windows runners: install SQL Server 2025 Express directly from Microsoft with SA auth. on: pull_request: @@ -25,7 +25,6 @@ jobs: env: TEST_DB: SqlActionTest - MSSQL_IMAGE: mcr.microsoft.com/mssql/server:2022-latest defaults: run: @@ -37,135 +36,23 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - # --- Linux setup: Docker container + SA auth with rotated password --- - - - name: Start SQL Server container (Linux) - if: runner.os == 'Linux' - run: | - docker run -d --name sqlserver \ - -e "ACCEPT_EULA=Y" \ - -e "MSSQL_SA_PASSWORD=Bootstrap1!" \ - -p 1433:1433 \ - "$MSSQL_IMAGE" - - - name: Wait for SQL Server to be ready (Linux) - if: runner.os == 'Linux' - run: | - for i in $(seq 1 30); do - if docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U sa -P 'Bootstrap1!' -C -Q 'SELECT 1' >/dev/null 2>&1; then - echo "SQL Server is ready" - exit 0 - fi - echo "Waiting for SQL Server... ($i/30)" - sleep 5 - done - echo "SQL Server did not become ready in time" - docker logs sqlserver - exit 1 - - - name: Rotate SA password and set connection string (Linux) - if: runner.os == 'Linux' - run: | - SA_PASSWORD="$(openssl rand -base64 18 | tr -d '/+=')Aa1!" - docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U sa -P 'Bootstrap1!' -C \ - -Q "ALTER LOGIN sa WITH PASSWORD='${SA_PASSWORD}'" - echo "BASE_CS=Server=localhost;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV" - - # --- Windows setup: install SQL Server 2022 Express directly from Microsoft --- - - - name: Generate SA password (Windows) - if: runner.os == 'Windows' + - name: Generate SA password run: | SA_PASSWORD="$(openssl rand -base64 18 | tr -d '/+=')Aa1!" echo "::add-mask::${SA_PASSWORD}" echo "SA_PASSWORD=${SA_PASSWORD}" >> "$GITHUB_ENV" - - name: Download SQL Server 2025 Express installer (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - $ssei = Join-Path $env:RUNNER_TEMP 'SQL2025-SSEI-Expr.exe' - $media = Join-Path $env:RUNNER_TEMP 'sql-media' - # Stable Microsoft fwlink redirecting to the SQL Server 2025 Express bootstrapper (SSEI) - Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/?linkid=2216019' -OutFile $ssei - Write-Host "Downloaded SSEI: $((Get-Item $ssei).Length) bytes" - - # Run SSEI in download-only mode. Use Start-Process -Wait so we get a real exit code - # (PowerShell's `&` returns asynchronously for installer-class executables). - $p = Start-Process -FilePath $ssei -Wait -PassThru -ArgumentList @( - '/Quiet', - '/Action=Download', - '/Language=en-US', - "/MediaPath=$media", - '/MediaType=Core', - '/HideProgressBar' - ) - if ($p.ExitCode -ne 0) { throw "SSEI download failed with exit code $($p.ExitCode)" } - - Write-Host "=== Media directory contents ===" - Get-ChildItem $media - - - name: Install SQL Server Express with SA auth (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - $media = Join-Path $env:RUNNER_TEMP 'sql-media' - $selfExtract = Get-ChildItem $media -Filter 'SQLEXPR*.exe' | Select-Object -First 1 - if (-not $selfExtract) { throw "Installer EXE not found in $media" } - - $extracted = Join-Path $env:RUNNER_TEMP 'sql-extracted' - Write-Host "Extracting $($selfExtract.FullName) -> $extracted" - $extract = Start-Process -FilePath $selfExtract.FullName -Wait -PassThru ` - -ArgumentList '/Q', "/X:$extracted" - if ($extract.ExitCode -ne 0) { throw "Extraction failed with exit code $($extract.ExitCode)" } - - $setup = Join-Path $extracted 'setup.exe' - if (-not (Test-Path $setup)) { throw "setup.exe not found at $setup" } - - Write-Host "Running $setup with SECURITYMODE=SQL" - $install = Start-Process -FilePath $setup -Wait -PassThru -ArgumentList @( - '/Q', - '/ACTION=Install', - '/FEATURES=SQLEngine', - '/INSTANCENAME=MSSQLSERVER', - '/SECURITYMODE=SQL', - "/SAPWD=$env:SA_PASSWORD", - '/TCPENABLED=1', - '/IACCEPTSQLSERVERLICENSETERMS', - '/UPDATEENABLED=False', - '/SQLSYSADMINACCOUNTS=BUILTIN\Administrators' - ) - - if ($install.ExitCode -ne 0) { - Write-Host "Setup failed with exit code $($install.ExitCode)" - $logRoot = 'C:\Program Files\Microsoft SQL Server' - if (Test-Path $logRoot) { - Get-ChildItem $logRoot -Recurse -Filter 'Summary*.txt' -ErrorAction SilentlyContinue | - Sort-Object LastWriteTime -Descending | Select-Object -First 1 | - ForEach-Object { Write-Host "=== $($_.FullName) ==="; Get-Content $_.FullName } - } - throw "SQL Server install failed" - } - Get-Service | Where-Object { $_.Name -like 'MSSQL*' } | Format-Table - - - name: Verify SQL auth (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - $sqlcmd = (Get-ChildItem 'C:\Program Files\Microsoft SQL Server' -Recurse -Filter sqlcmd.exe -ErrorAction SilentlyContinue | - Select-Object -First 1).FullName - if (-not $sqlcmd) { throw "sqlcmd.exe not found after install" } - & $sqlcmd -S 'localhost' -U sa -P $env:SA_PASSWORD -b -Q "SELECT @@VERSION" - if ($LASTEXITCODE -ne 0) { throw "SQL auth verification failed" } + - name: Set up SQL Server (Linux) + if: runner.os == 'Linux' + uses: ./.github/actions/setup-sql-linux + with: + sa-password: ${{ env.SA_PASSWORD }} - - name: Set connection string (Windows) + - name: Set up SQL Server (Windows) if: runner.os == 'Windows' - run: | - echo "BASE_CS=Server=localhost;User ID=sa;Password=${SA_PASSWORD};TrustServerCertificate=True;" >> "$GITHUB_ENV" - - # --- Common build and test steps --- + uses: ./.github/actions/setup-sql-windows + with: + sa-password: ${{ env.SA_PASSWORD }} - name: Build GitHub Action run: npm ci --ignore-scripts && npm run build From 3f48f769fb02b260800e26c8531690eee76636c1 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Wed, 24 Jun 2026 10:02:32 -0700 Subject: [PATCH 13/14] bumping .net and sql versions --- .github/actions/setup-sql-linux/action.yml | 2 +- .github/workflows/pr-check.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-sql-linux/action.yml b/.github/actions/setup-sql-linux/action.yml index 7ce4bac5..1def1b13 100644 --- a/.github/actions/setup-sql-linux/action.yml +++ b/.github/actions/setup-sql-linux/action.yml @@ -8,7 +8,7 @@ inputs: image: description: 'SQL Server Docker image to use.' required: false - default: 'mcr.microsoft.com/mssql/server:2022-latest' + default: 'mcr.microsoft.com/mssql/server:2025-latest' runs: using: composite diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 358a0771..db2c3495 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -60,7 +60,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.x' + dotnet-version: '10.x' - name: Install SqlPackage run: dotnet tool install -g microsoft.sqlpackage From c656f59dd1fdd25c52dca7ad3933f7b8c64d3ce6 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Wed, 24 Jun 2026 10:08:44 -0700 Subject: [PATCH 14/14] Comment cleanup --- .github/actions/setup-sql-windows/action.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/actions/setup-sql-windows/action.yml b/.github/actions/setup-sql-windows/action.yml index 414fd7c5..c551e4ed 100644 --- a/.github/actions/setup-sql-windows/action.yml +++ b/.github/actions/setup-sql-windows/action.yml @@ -14,12 +14,9 @@ runs: run: | $ssei = Join-Path $env:RUNNER_TEMP 'SQL2025-SSEI-Expr.exe' $media = Join-Path $env:RUNNER_TEMP 'sql-media' - # Stable Microsoft fwlink redirecting to the SQL Server 2025 Express bootstrapper (SSEI) Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/?linkid=2216019' -OutFile $ssei Write-Host "Downloaded SSEI: $((Get-Item $ssei).Length) bytes" - # Run SSEI in download-only mode. Use Start-Process -Wait so we get a real exit code - # (PowerShell's `&` returns asynchronously for installer-class executables). $p = Start-Process -FilePath $ssei -Wait -PassThru -ArgumentList @( '/Quiet', '/Action=Download',