diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 000000000..f071f0dad --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnetnx.tool": { + "version": "0.1.0-alpha.1", + "commands": [ + "nxdn" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index de2a5cba7..3241a76f0 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -183,7 +183,7 @@ You can also pass a pre-built binary: `./eng/smoke-tests/apple-cli-smoke-test.sh ## CI/CD — New Product Checklist -When adding a new product to this repo you **must** set up two CI surfaces: a GitHub Actions workflow for PR validation and a build job + publish stage in the Azure DevOps official pipeline for signing and NuGet.org publishing. +When adding a new product to this repo you only need to wire it into the **Azure DevOps official pipeline** for signing and NuGet.org publishing. PR validation on GitHub Actions is handled by the consolidated `.github/workflows/ci.yml`, which discovers projects via Nx. You **must** also provide documentation: @@ -192,62 +192,56 @@ You **must** also provide documentation: ### Step 1: GitHub Actions PR / Push Workflow -Create `.github/workflows/ci-{product}.yml`. Use the template below — replace every `{Product}` (PascalCase) and `{product}` (lowercase) placeholder. +**Nothing to create.** `.github/workflows/ci.yml` runs Nx's `affected-matrix` action against every project in `MauiLabs.slnx`. New products are picked up automatically. -```yaml -name: CI - {Product} - -on: - push: - branches: [main] - paths: - - 'src/{Product}/**' - # CUSTOMIZE: add paths for cross-product ProjectReference dependencies, e.g.: - # - 'src/OtherProduct/SharedLib/**' - - 'eng/**' - - 'Directory.Build.props' - - 'Directory.Build.targets' - - 'Directory.Packages.props' - - 'global.json' - - 'NuGet.config' - pull_request: - # IMPORTANT: 'edited' is required — without it CI does not run when - # GitHub auto-retargets a PR after a stacked branch merges. - types: [opened, synchronize, reopened, edited] - branches: [main] - paths: - - 'src/{Product}/**' - # CUSTOMIZE: add paths for cross-product ProjectReference dependencies, e.g.: - # - 'src/OtherProduct/SharedLib/**' - - 'eng/**' - - 'Directory.Build.props' - - 'Directory.Build.targets' - - 'Directory.Packages.props' - - 'global.json' - - 'NuGet.config' - -jobs: - build: - uses: ./.github/workflows/_build.yml - with: - project-path: src/{Product}/{Product}.slnf # CUSTOMIZE: path to solution filter (.slnf or .slnx) - project-name: {product} # CUSTOMIZE: lowercase, used in artifact names - run-tests: true - pack: true - install-workloads: true # CUSTOMIZE: set false for net10.0-only products (no MAUI TFMs) - # os: '["macos-latest", "windows-latest"]' # CUSTOMIZE: default is macOS + Windows - # native-deps: 'sudo apt-get install ...' # CUSTOMIZE: if Linux-only with native libs +To control which OS legs build the new product, set `` in the product's `Directory.Build.props` (or per-csproj where finer-grained): + +```xml + + + macos,windows + +``` + +The `@redth/dotnet-nx` plugin emits `os:linux` / `os:macos` / `os:windows` Nx tags from this property; the matrix leg for an OS only runs if at least one affected project carries that tag. Nx's `affected` graph (project + dependency edges + workspace files) replaces the old hand-maintained `paths:` filters — there is **nothing to keep in sync** when you add cross-product `ProjectReference`s. + +### Declaring arbitrary Nx tags via `` / `` + +Set arbitrary `tag:value` metadata on a project to drive workflow selection without hard-coding project names: + +```xml + + + type:integration-test;device:android;requires:emulator + + + + + + ``` -#### `_build.yml` inputs reference +Workflows then select with `--projects=tag::` and exclude with `--exclude=tag::`. Examples in this repo: + +- `ci.yml` excludes integration tests and standalone-CI products via `AFFECTED_EXCLUDES: 'tag:type:integration-test,tag:type:standalone-ci'`. +- `devflow-integration.yml` runs each platform variant via `nxdn nx -- run-many --projects=tag:device: -t test`. + +Inferred tags (no MSBuild change required): `os:`, `tfm:`, `tfm-platform:`, `type:test`/`type:packable`/`type:tool`/`type:nuget`, `package-id:`, `sdk:maui`. See the [Maui.BuildHelpers DotnetNx README](https://github.com/Redth/Maui.BuildHelpers/tree/main/DotnetNx#nx-tags-from-msbuild) for the full list. + +If a product needs special build prerequisites (native apt deps, additional workloads, extra SDKs) on a specific OS leg, edit `ci.yml` directly. The `build` job already conditionalizes Linux apt deps and Windows/macOS Android SDK installs on `osTag`. + +### Step 1b: Products with their own workflow (standalone-CI) + +Some products can't fit the consolidated `ci.yml` matrix — for example, EssentialsAI's macOS→Windows Swift xcframework artifact handoff, or MacOS-AppKit's pinned Xcode version. These keep their own `.github/workflows/ci-{product}.yml` calling the reusable `_build.yml`. Tag the product so `ci.yml` skips it: + +```xml + + + type:standalone-ci + +``` -| Input | Default | When to change | -|-------|---------|---------------| -| `install-workloads` | `true` | Set `false` if the product targets only `net10.0` (no MAUI TFMs) | -| `os` | `["macos-latest", "windows-latest"]` | Override to `["ubuntu-24.04"]` for Linux-only products | -| `native-deps` | *(empty)* | Provide an `apt-get install` command if the product needs native libraries (e.g., GTK4) | -| `pack` | `false` | Set `true` if the product produces NuGet packages | -| `run-tests` | `true` | Set `false` only if there are no tests yet | +Existing standalone-CI products: `src/AI` (EssentialsAI), `src/AppProjectReference`, `platforms/MacOS`, `platforms/Windows.WPF`. ### Step 2: Azure DevOps Official Pipeline diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index 95d13f034..93171574f 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -42,10 +42,13 @@ dotnet test --logger "console;verbosity=detailed" ## CI Matrix -Tests run on **macOS and Windows** in CI (`.github/workflows/_build.yml`): +Tests run via the consolidated **Nx-driven** workflow `.github/workflows/ci.yml`. The `affected` job computes per-OS project lists from `` tags; the `build` job fans out across `linux` / `macos` / `windows` legs that have affected work, invoking `nx affected -t test` (via `Redth/Maui.BuildHelpers/DotnetNx/actions/run-affected@v0.3`). -- **macOS**: `./eng/common/cibuild.sh --configuration Release --prepareMachine --projects src/DevFlow/DevFlow.slnf` -- **Windows**: `eng\common\cibuild.cmd -configuration Release -prepareMachine -projects src/DevFlow/DevFlow.slnf` +To run the same selective test pass locally: + +```bash +./nx affected -t test +``` Test results are uploaded as artifacts: `artifacts/TestResults/**/*.xml` diff --git a/.github/workflows/ci-cli.yml b/.github/workflows/ci-cli.yml deleted file mode 100644 index 8d71a74f5..000000000 --- a/.github/workflows/ci-cli.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: CI - Cli - -on: - push: - branches: [main] - paths: - - '.claude/skills/**' - - 'src/Cli/**' - - 'src/DevFlow/Microsoft.Maui.DevFlow.Driver/**' - - 'src/Go/**' - - 'eng/**' - - 'Directory.Build.props' - - 'Directory.Build.targets' - - 'Directory.Packages.props' - - 'global.json' - - 'NuGet.config' - pull_request: - types: [opened, synchronize, reopened, edited] - branches: [main] - paths: - - '.claude/skills/**' - - 'src/Cli/**' - - 'src/DevFlow/Microsoft.Maui.DevFlow.Driver/**' - - 'src/Go/**' - - 'eng/**' - - 'Directory.Build.props' - - 'Directory.Build.targets' - - 'Directory.Packages.props' - - 'global.json' - - 'NuGet.config' - -jobs: - build: - uses: ./.github/workflows/_build.yml - with: - project-path: src/Cli/Cli.slnf - project-name: cli - run-tests: true - pack: true - install-workloads: false diff --git a/.github/workflows/ci-devflow.yml b/.github/workflows/ci-devflow.yml deleted file mode 100644 index 76bea2031..000000000 --- a/.github/workflows/ci-devflow.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: CI - DevFlow - -on: - push: - branches: [main] - paths: - - 'src/DevFlow/**' - - 'eng/**' - - 'Directory.Build.props' - - 'Directory.Build.targets' - - 'Directory.Packages.props' - - 'global.json' - - 'NuGet.config' - pull_request: - types: [opened, synchronize, reopened, edited] - branches: [main] - paths: - - 'src/DevFlow/**' - - 'eng/**' - - 'Directory.Build.props' - - 'Directory.Build.targets' - - 'Directory.Packages.props' - - 'global.json' - - 'NuGet.config' - -jobs: - build: - uses: ./.github/workflows/_build.yml - with: - project-path: src/DevFlow/DevFlow.slnf - project-name: devflow - run-tests: true - pack: true - os: '["windows-latest"]' diff --git a/.github/workflows/ci-linux-gtk4.yml b/.github/workflows/ci-linux-gtk4.yml deleted file mode 100644 index aa6b712cc..000000000 --- a/.github/workflows/ci-linux-gtk4.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: CI - Linux GTK4 - -on: - push: - branches: [main] - paths: - - 'platforms/Linux.Gtk4/**' - - 'eng/**' - - 'Directory.Build.props' - - 'Directory.Build.targets' - - 'Directory.Packages.props' - - 'global.json' - - 'NuGet.config' - pull_request: - types: [opened, synchronize, reopened, edited] - branches: [main] - paths: - - 'platforms/Linux.Gtk4/**' - - 'eng/**' - - 'Directory.Build.props' - - 'Directory.Build.targets' - - 'Directory.Packages.props' - - 'global.json' - - 'NuGet.config' - -jobs: - build: - uses: ./.github/workflows/_build.yml - with: - project-path: platforms/Linux.Gtk4/Linux.Gtk4.slnx - project-name: linux-gtk4 - os: '["ubuntu-24.04"]' - install-workloads: false - native-deps: >- - sudo apt-get update && sudo apt-get install -y - libgtk-4-dev - libwebkitgtk-6.0-dev - gobject-introspection - libgirepository1.0-dev - gir1.2-gtk-4.0 - gir1.2-webkit-6.0 - pkg-config - run-tests: false - pack: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..5d4561f7a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,267 @@ +name: CI + +# Single Nx-driven CI workflow for the whole repo. +# +# Per-OS routing is driven by project tags emitted by the @redth/dotnet-nx Nx +# plugin (DotnetNx), backed by evaluated through MSBuild. Each +# runner picks up only the affected projects whose tag set includes its os-tag, +# so we don't carry centralised path filters or product-specific workflows. + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + packages: read + +env: + NX_DAEMON: false + NX_CLOUD: false + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + # NuGet credentials for the RedthDotnetNx GitHub Packages feed. + # Source name has no hyphens so the env-var lookup works directly. + NuGetPackageSourceCredentials_RedthDotnetNx: Username=USERNAME;Password=${{ secrets.GITHUB_TOKEN }} + CI_PACKAGE_VERSION_SUFFIX: ci.${{ github.run_number }}.${{ github.run_attempt }} + # Excluded from the consolidated build: + # - tag:type:integration-test -> need real devices/sims/emulators (run via devflow-integration.yml) + # - tag:type:standalone-ci -> products that own their own ci-{product}.yml workflow + # (e.g. EssentialsAI's macOS->Windows xcframework handoff, + # MacOS-AppKit's bespoke Xcode setup, WPF, AppProjectReference). + # Both are metadata-driven via in the product/project files; no project names hard-coded here. + AFFECTED_EXCLUDES: 'tag:type:integration-test,tag:type:standalone-ci' + +jobs: + affected: + name: Compute affected matrix + runs-on: ubuntu-latest + outputs: + base: ${{ steps.matrix.outputs.base }} + head: ${{ steps.matrix.outputs.head }} + matrix: ${{ steps.matrix.outputs.matrix }} + has-work: ${{ steps.matrix.outputs.has-work }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node.js (with GH Packages auth) + uses: actions/setup-node@v6 + with: + node-version: '22' + registry-url: https://npm.pkg.github.com + scope: '@redth' + + - name: Install npm dependencies + run: npm ci + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup nxdn (DotnetNx) + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/setup-nxdn@v0.4 + with: + setup-node: 'false' + + - name: Run DotnetNx doctor + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/doctor@v0.4 + + - name: Compute affected matrix + id: matrix + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/affected-matrix@v0.4 + + - name: Capture affected project graph + if: steps.matrix.outputs.has-work == 'true' + shell: bash + run: | + set -euo pipefail + mkdir -p artifacts/nx + nxdn nx -- graph \ + --affected \ + --base="${{ steps.matrix.outputs.base }}" \ + --head="${{ steps.matrix.outputs.head }}" \ + --file=artifacts/nx/affected-project-graph.json \ + --open=false || true + # Surface the per-OS project lists so the report is useful even when + # downstream legs skip. + for f in "${{ steps.matrix.outputs.linux-projects-json }}" \ + "${{ steps.matrix.outputs.macos-projects-json }}" \ + "${{ steps.matrix.outputs.windows-projects-json }}"; do + [ -n "$f" ] && [ -f "$f" ] && cp "$f" artifacts/nx/ || true + done + + - name: Upload affected project report + if: always() + uses: actions/upload-artifact@v7 + with: + name: nx-affected-report + path: artifacts/nx/ + if-no-files-found: ignore + + build: + name: Build affected (${{ matrix.os }}) + needs: affected + if: needs.affected.outputs.has-work == 'true' + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.affected.outputs.matrix) }} + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Install native dependencies (linux) + if: matrix.osTag == 'linux' + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y \ + libgtk-4-dev \ + libwebkitgtk-6.0-dev \ + gobject-introspection \ + libgirepository1.0-dev \ + gir1.2-gtk-4.0 \ + gir1.2-webkit-6.0 \ + pkg-config + + - name: Setup Node.js (with GH Packages auth) + uses: actions/setup-node@v6 + with: + node-version: '22' + registry-url: https://npm.pkg.github.com + scope: '@redth' + + - name: Install npm dependencies + run: npm ci + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup nxdn (DotnetNx) + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/setup-nxdn@v0.4 + with: + setup-node: 'false' + + - name: Setup DotnetNx cache + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/setup-cache@v0.4 + + - name: Install MAUI workloads + if: matrix.osTag != 'linux' + shell: bash + run: dotnet workload install maui macos maui-tizen + + - name: Install Android SDK dependencies (Windows) + if: matrix.osTag == 'windows' + shell: cmd + run: dotnet build -t:InstallAndroidDependencies -f net10.0-android -p:AndroidSdkDirectory="%ANDROID_HOME%" -p:AcceptAndroidSDKLicenses=true src\DevFlow\Microsoft.Maui.DevFlow.Agent\Microsoft.Maui.DevFlow.Agent.csproj + + - name: Install Android SDK dependencies (macOS) + if: matrix.osTag == 'macos' + shell: bash + run: dotnet build -t:InstallAndroidDependencies -f net10.0-android -p:AndroidSdkDirectory="$ANDROID_HOME" -p:AcceptAndroidSDKLicenses=true src/DevFlow/Microsoft.Maui.DevFlow.Agent/Microsoft.Maui.DevFlow.Agent.csproj + + - name: Restore (Nx cached) + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/run-affected@v0.4 + with: + target: restore + base: ${{ needs.affected.outputs.base }} + head: ${{ needs.affected.outputs.head }} + os-tag: ${{ matrix.osTag }} + + - name: Build (Nx cached) + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/run-affected@v0.4 + with: + target: build + base: ${{ needs.affected.outputs.base }} + head: ${{ needs.affected.outputs.head }} + os-tag: ${{ matrix.osTag }} + + - name: Test (Nx cached) + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/run-affected@v0.4 + with: + target: test + base: ${{ needs.affected.outputs.base }} + head: ${{ needs.affected.outputs.head }} + os-tag: ${{ matrix.osTag }} + exclude: ${{ env.AFFECTED_EXCLUDES }} + + - name: Pack (Nx cached) + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/run-affected@v0.4 + with: + target: pack + base: ${{ needs.affected.outputs.base }} + head: ${{ needs.affected.outputs.head }} + os-tag: ${{ matrix.osTag }} + env: | + VersionSuffix=${{ env.CI_PACKAGE_VERSION_SUFFIX }} + + - name: NativeAOT publish smoke test (CLI on macOS) + if: matrix.osTag == 'macos' && hashFiles('artifacts/bin/Microsoft.Maui.Cli/**') != '' + shell: bash + run: | + set -euo pipefail + + arch="$(uname -m)" + case "$arch" in + arm64) rid="osx-arm64" ;; + x86_64) rid="osx-x64" ;; + *) + echo "Unsupported macOS architecture: $arch" + exit 1 + ;; + esac + + dotnet publish src/Cli/Microsoft.Maui.Cli/Microsoft.Maui.Cli.csproj \ + -c Release \ + -f net10.0 \ + -r "$rid" \ + -p:PublishAot=true \ + -p:PublishTrimmed=true \ + -p:InvariantGlobalization=true \ + -p:TreatWarningsAsErrors=true \ + --self-contained true + + publish_dir="artifacts/bin/Microsoft.Maui.Cli/Release/net10.0/$rid/publish" + cli_path="$publish_dir/maui" + commands_json="$(mktemp)" + agents_json="$(mktemp)" + + "$cli_path" devflow version + "$cli_path" devflow commands --json > "$commands_json" + "$cli_path" devflow broker stop >/dev/null 2>&1 || true + "$cli_path" devflow list --json > "$agents_json" + "$cli_path" devflow broker stop >/dev/null 2>&1 || true + + python - "$commands_json" "$agents_json" <<'PY' + import json + import sys + + with open(sys.argv[1], "r", encoding="utf-8") as fp: + commands = json.load(fp) + with open(sys.argv[2], "r", encoding="utf-8") as fp: + agents = json.load(fp) + + assert isinstance(commands, list), "Expected commands output to be a JSON array" + assert commands, "Expected at least one command description" + assert all("command" in item and "description" in item for item in commands), "Command entries must include command and description" + assert isinstance(agents, list), "Expected list --json output to be a JSON array" + PY + + rm -f "$commands_json" "$agents_json" + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v7 + with: + name: test-results-${{ matrix.osTag }} + path: artifacts/TestResults/**/*.xml + if-no-files-found: ignore + + - name: Upload packages + uses: actions/upload-artifact@v7 + with: + name: packages-${{ matrix.osTag }} + path: artifacts/packages/**/*.nupkg + if-no-files-found: ignore diff --git a/.github/workflows/devflow-integration.yml b/.github/workflows/devflow-integration.yml index 7406a2c27..9b272589b 100644 --- a/.github/workflows/devflow-integration.yml +++ b/.github/workflows/devflow-integration.yml @@ -12,6 +12,11 @@ name: DevFlow Integration Tests # iOS -> src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/iOSSimulatorFixture.cs # Mac Catalyst -> src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/MacCatalystFixture.cs # Windows -> src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/WindowsFixture.cs +# +# Each integration project self-tags via type:integration-test;device: +# in IntegrationTests.Common.props. The job selects its variant with +# `nxdn nx -- run-many --projects=tag:device:`, so adding a new platform +# variant is a one-csproj change with no workflow edits. on: workflow_dispatch: @@ -46,10 +51,16 @@ concurrency: group: devflow-integration-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + packages: read + env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: 1 - TEST_PROJECT: src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Microsoft.Maui.DevFlow.Agent.IntegrationTests.csproj + # NuGet credentials for the RedthDotnetNx GitHub Packages feed. + # Source name has no hyphens so the env-var lookup works directly. + NuGetPackageSourceCredentials_RedthDotnetNx: Username=USERNAME;Password=${{ secrets.GITHUB_TOKEN }} jobs: # Decide which platforms to run based on the trigger. @@ -96,70 +107,73 @@ jobs: env: DEVFLOW_TEST_PLATFORM: android DEVFLOW_TEST_ANDROID_API: ${{ inputs.android-api || '35' }} - DEVFLOW_TEST_ANDROID_AVD: devflow-tests-api${{ inputs.android-api || '35' }} + DEVFLOW_TEST_ANDROID_SERIAL: localhost:5555 steps: - uses: actions/checkout@v6 - # Android emulator needs KVM on Linux runners. - - name: Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: Setup Java - uses: actions/setup-java@v4 + - name: Setup Node.js (with GH Packages auth) + uses: actions/setup-node@v6 with: - distribution: temurin - java-version: '17' + node-version: '22' + registry-url: https://npm.pkg.github.com + scope: '@redth' - - name: Setup .NET - uses: actions/setup-dotnet@v5 + - name: Install npm dependencies + run: npm ci + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup nxdn (DotnetNx) + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/setup-nxdn@v0.4 with: - global-json-file: global.json + setup-node: 'false' - name: Install Android MAUI workload run: dotnet workload install maui-android - - name: Cache AVD - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ env.DEVFLOW_TEST_ANDROID_API }}-x86_64 - - - name: Warm up AVD snapshot - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - avd-name: ${{ env.DEVFLOW_TEST_ANDROID_AVD }} - api-level: ${{ env.DEVFLOW_TEST_ANDROID_API }} - arch: x86_64 - target: google_apis - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: echo "Generated AVD snapshot." + # The image (maui-containers/maui-emulator-linux) ships with a + # deterministic boot sequence and a Docker HEALTHCHECK that waits for + # both `sys.boot_completed=1` and Appium /status before reporting + # healthy. We just `docker run -d` and poll `.State.Health.Status`. + - name: Start Android emulator container + run: | + docker run -d \ + --name maui-emulator \ + --device /dev/kvm \ + -p 5554:5554 \ + -p 5555:5555 \ + -e EMULATOR_BOOT_TIMEOUT=600 \ + ghcr.io/maui-containers/maui-emulator-linux:android${DEVFLOW_TEST_ANDROID_API}-dotnet10.0 + + - name: Wait for emulator to be healthy + run: | + set -euo pipefail + # HEALTHCHECK has a 120s start-period; container reports `starting` + # until then. We just wait for `healthy` and let the outer timeout + # bound the wall clock (60 * 10s = 10 minutes). + status=unknown + for i in $(seq 1 60); do + status=$(docker inspect --format='{{.State.Health.Status}}' maui-emulator 2>/dev/null || echo unknown) + echo "[$i] container health: $status" + if [ "$status" = "healthy" ]; then + break + fi + sleep 10 + done + if [ "$status" != "healthy" ]; then + echo "Emulator container did not reach healthy state in time" + docker logs --tail 200 maui-emulator || true + exit 1 + fi + adb connect localhost:5555 + adb devices - name: Run DevFlow integration tests (android) - uses: reactivecircus/android-emulator-runner@v2 - with: - avd-name: ${{ env.DEVFLOW_TEST_ANDROID_AVD }} - api-level: ${{ env.DEVFLOW_TEST_ANDROID_API }} - arch: x86_64 - target: google_apis - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: > - export DEVFLOW_TEST_ANDROID_SERIAL="$(adb devices | awk '/^emulator-[0-9]+[[:space:]]+device$/ { print $1; exit }')"; - echo "Using Android emulator serial: ${DEVFLOW_TEST_ANDROID_SERIAL:-}"; - dotnet test $TEST_PROJECT -c Debug - --logger "trx;LogFileName=devflow-integration-android.trx" - --logger "console;verbosity=normal" - --results-directory ./artifacts/TestResults/devflow-integration/android + run: nxdn nx -- run-many --projects=tag:device:android -t test + + - name: Dump emulator logs on failure + if: failure() + run: docker logs --tail 500 maui-emulator || true - name: Upload test results if: always() @@ -209,10 +223,22 @@ jobs: xcrun --sdk macosx --show-sdk-path xcrun --sdk macosx --find actool - - name: Setup .NET - uses: actions/setup-dotnet@v5 + - name: Setup Node.js (with GH Packages auth) + uses: actions/setup-node@v6 + with: + node-version: '22' + registry-url: https://npm.pkg.github.com + scope: '@redth' + + - name: Install npm dependencies + run: npm ci + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup nxdn (DotnetNx) + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/setup-nxdn@v0.4 with: - global-json-file: global.json + setup-node: 'false' - name: Install Apple MAUI workloads run: dotnet workload install maui ios macos @@ -221,12 +247,7 @@ jobs: run: xcrun simctl list devices available - name: Run DevFlow integration tests (ios) - run: > - dotnet test $TEST_PROJECT - -c Debug - --logger "trx;LogFileName=devflow-integration-ios.trx" - --logger "console;verbosity=normal" - --results-directory ./artifacts/TestResults/devflow-integration/ios + run: nxdn nx -- run-many --projects=tag:device:ios -t test - name: Upload test results if: always() @@ -275,21 +296,28 @@ jobs: xcrun --sdk macosx --show-sdk-path xcrun --sdk macosx --find actool - - name: Setup .NET - uses: actions/setup-dotnet@v5 + - name: Setup Node.js (with GH Packages auth) + uses: actions/setup-node@v6 with: - global-json-file: global.json + node-version: '22' + registry-url: https://npm.pkg.github.com + scope: '@redth' + + - name: Install npm dependencies + run: npm ci + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup nxdn (DotnetNx) + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/setup-nxdn@v0.4 + with: + setup-node: 'false' - name: Install Apple MAUI workloads run: dotnet workload install maui maccatalyst macos - name: Run DevFlow integration tests (maccatalyst) - run: > - dotnet test $TEST_PROJECT - -c Debug - --logger "trx;LogFileName=devflow-integration-maccatalyst.trx" - --logger "console;verbosity=normal" - --results-directory ./artifacts/TestResults/devflow-integration/maccatalyst + run: nxdn nx -- run-many --projects=tag:device:maccatalyst -t test - name: Upload test results if: always() @@ -309,22 +337,30 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Setup .NET - uses: actions/setup-dotnet@v5 + - name: Setup Node.js (with GH Packages auth) + uses: actions/setup-node@v6 with: - global-json-file: global.json + node-version: '22' + registry-url: https://npm.pkg.github.com + scope: '@redth' + + - name: Install npm dependencies + run: npm ci + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup nxdn (DotnetNx) + uses: Redth/Maui.BuildHelpers/DotnetNx/actions/setup-nxdn@v0.4 + with: + setup-node: 'false' - name: Install MAUI workloads + shell: bash run: dotnet workload install maui - name: Run DevFlow integration tests (windows) - shell: pwsh - run: > - dotnet test $env:TEST_PROJECT - -c Debug - --logger "trx;LogFileName=devflow-integration-windows.trx" - --logger "console;verbosity=normal" - --results-directory .\artifacts\TestResults\devflow-integration\windows + shell: bash + run: nxdn nx -- run-many --projects=tag:device:windows -t test - name: Upload test results if: always() diff --git a/.gitignore b/.gitignore index 6bd718949..43d36e034 100644 --- a/.gitignore +++ b/.gitignore @@ -305,6 +305,9 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ +.nx/cache/ +.nx/installation/ +.nx/workspace-data/ # Visual Studio 6 build log *.plg @@ -433,3 +436,9 @@ sim_tap.py # Personal / per-machine agent state .claude/settings.local.json .claude/skills/**/.skill-version +.claude/worktrees + +# Nx +.nx/installation +.nx/self-healing +.nx/polygraph diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..8a836708d --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@redth:registry=https://npm.pkg.github.com diff --git a/AGENTS.md b/AGENTS.md index d72ce36e6..1088b9da4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,19 +34,26 @@ This repository hosts experimental .NET MAUI packages. It is a **multi-product m ### Build Commands ```bash -# Build everything +# Build everything (raw MSBuild — does not use the Nx graph or cache) dotnet build MauiLabs.sln # Build a single product (recommended for focused development) dotnet build src/DevFlow/DevFlow.slnf -# Build via Arcade CI scripts (matches what CI runs for DevFlow) +# Nx-driven affected build/test (matches what CI runs) +./nx affected -t build # only rebuild projects affected by your branch +./nx affected -t test +./nx run-many -t build # build everything via Nx (uses the cache) + +# Build via Arcade CI scripts (used inside the AzDO official pipeline) # macOS/Linux: ./eng/common/cibuild.sh --configuration Release --prepareMachine --projects src/DevFlow/DevFlow.slnf # Windows: eng\common\cibuild.cmd -configuration Release -prepareMachine -projects src/DevFlow/DevFlow.slnf ``` +> CI uses Nx (`nx affected`) for selective builds. Running `./nx affected` locally mirrors what `.github/workflows/ci.yml` will execute for your PR. + ### Build Troubleshooting - If restore fails, check `NuGet.config` — feeds are internal dnceng proxies, not nuget.org @@ -149,14 +156,16 @@ maui-labs/ ### GitHub Actions (PR validation) -Each product has its own workflow file: `.github/workflows/ci-{product}.yml`, calling the shared `_build.yml` reusable workflow. +A **single** workflow drives all PR validation: `.github/workflows/ci.yml`. It uses Nx (`@redth/dotnet-nx`) to compute the **affected** project set and run only the work that's needed for the change. -- **Matrix**: macOS + Windows (configurable per product via `os` input) -- **Path-filtered**: only triggers for changed product paths + shared build infrastructure (`eng/**`, `Directory.Build.props`, etc.) -- **`pull_request.types`**: Must always include `[opened, synchronize, reopened, edited]` — the `edited` type ensures CI re-runs when GitHub auto-retargets a PR after a stacked branch merges -- Steps: restore → build → test → upload test results + packages +- **Two jobs**: `affected` (computes the matrix using `affected-matrix@v0.3`) and `build` (matrix runs `restore` → `build` → `test` → `pack` per OS leg via `run-affected@v0.3`). +- **No path filters.** Selection is entirely Nx-driven from the `` MSBuild property on each project. The matrix legs are `linux`, `macos`, `windows`; a leg only runs if there are affected projects tagged `os:`. +- **`` controls platform fan-out**: `linux`, `macos`, `windows`, or comma-separated subset. Default (omitted) = all three. Set in `Directory.Build.props` for products that are platform-restricted (e.g., GTK4 → `linux` only). +- **`` declares arbitrary Nx tags**: e.g. `type:integration-test;device:android` in a project's `.csproj` or shared props. The CI workflow excludes integration tests via `tag:type:integration-test`, and `devflow-integration.yml` selects each platform variant via `--projects=tag:device:` — no project names hard-coded in YAML. +- **`pull_request.types`** still includes `[opened, synchronize, reopened, edited]` so re-targeted PRs re-run CI. +- **Comet** is a self-contained sub-product with its own `global.json` (.NET 11 preview) and is not in the Nx graph; it has its own `ci-comet.yml` workflow. -Existing workflows: `ci-cli.yml`, `ci-devflow.yml`, `ci-linux-gtk4.yml` +Existing workflows: `ci.yml` (consolidated), `ci-comet.yml` (self-contained Comet), `devflow-integration.yml` (manually-triggered emulator/simulator integration tests). ### Azure DevOps (official builds) @@ -195,10 +204,10 @@ Each product requires source setup **and** CI/CD configuration across two system ### CI/CD Setup -7. **GitHub Actions**: Create `.github/workflows/ci-{newproduct}.yml` calling the reusable `_build.yml` workflow. Must include `pull_request.types: [opened, synchronize, reopened, edited]` and path filters scoped to the product source plus shared build files. +7. **GitHub Actions**: *No new workflow file needed.* The consolidated `.github/workflows/ci.yml` discovers the new product automatically via Nx. To control which OS legs build it, set `` in the product's `Directory.Build.props` (values: `linux`, `macos`, `windows`, or omit for all three). Nx's `affected` query handles change detection — no path filters to maintain. 8. **Azure DevOps**: Edit `eng/pipelines/devflow-official.yml` — add a publish parameter, a build job in the `build` stage, and a conditional publish stage for NuGet.org. -> **Complete copy-paste templates** for both the GitHub Actions workflow and all three Azure DevOps blocks (parameter, build job, publish stage) are in `.github/copilot-instructions.md` under **"CI/CD — New Product Checklist"**. +> **Complete copy-paste templates** for the Azure DevOps blocks (parameter, build job, publish stage) are in `.github/copilot-instructions.md` under **"CI/CD — New Product Checklist"**. GitHub Actions PR validation requires no per-product workflow — `ci.yml` picks up new products automatically via Nx. ## DevFlow MCP Tools diff --git a/MauiLabs.slnx b/MauiLabs.slnx index a3e2c9c07..06cdcd4f6 100644 --- a/MauiLabs.slnx +++ b/MauiLabs.slnx @@ -37,7 +37,10 @@ - + + + + diff --git a/NuGet.config b/NuGet.config index 59a6a1e57..069777a09 100644 --- a/NuGet.config +++ b/NuGet.config @@ -14,7 +14,13 @@ - + + diff --git a/nx.json b/nx.json new file mode 100644 index 000000000..aae71a5d5 --- /dev/null +++ b/nx.json @@ -0,0 +1,68 @@ +{ + "$schema": "./node_modules/nx/schemas/nx-schema.json", + "analytics": false, + "plugins": [ + "@nx/dotnet", + "@redth/dotnet-nx" + ], + "namedInputs": { + "default": [ + "{projectRoot}/**/*" + ], + "production": [ + "default", + "!{projectRoot}/**/*.Tests/**/*", + "!{projectRoot}/**/bin/**/*", + "!{projectRoot}/**/obj/**/*" + ] + }, + "targetDefaults": { + "restore": { + "cache": false + }, + "build": { + "dependsOn": [ + "^build", + "restore" + ], + "outputs": [ + "{workspaceRoot}/artifacts/bin/{projectName}", + "{workspaceRoot}/artifacts/obj/{projectName}" + ], + "options": { + "args": [ + "--no-restore" + ] + } + }, + "build:release": { + "dependsOn": [ + "^build:release", + "restore" + ], + "outputs": [ + "{workspaceRoot}/artifacts/bin/{projectName}", + "{workspaceRoot}/artifacts/obj/{projectName}" + ], + "options": { + "args": [ + "--no-restore", + "--configuration", + "Release" + ] + } + }, + "test": { + "outputs": [ + "{workspaceRoot}/artifacts/TestResults", + "{projectRoot}/TestResults" + ] + }, + "pack": { + "cache": false + }, + "publish": { + "cache": false + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..fef027f9d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1799 @@ +{ + "name": "maui-labs", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "maui-labs", + "devDependencies": { + "@nx/dotnet": "22.7.0", + "@redth/dotnet-nx": "0.1.0-alpha.1", + "nx": "^22.6.5" + } + }, + "node_modules/@emnapi/core": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emnapi/wasi-threads": "1.0.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", + "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emnapi/core": "^1.1.0", + "@emnapi/runtime": "^1.1.0", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/@nx/devkit": { + "version": "22.7.0", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-22.7.0.tgz", + "integrity": "sha512-AI6t94k37lIv5XL/eesyYFBY4x2IzgseLmf0DiycHLGfAJWsvUlTzOD++t4vek/W80YQSBKRUbLg7r6wo56d0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zkochan/js-yaml": "0.0.7", + "ejs": "5.0.1", + "enquirer": "~2.3.6", + "minimatch": "10.2.4", + "semver": "^7.6.3", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 21 <= 23 || ^22.0.0-0" + } + }, + "node_modules/@nx/dotnet": { + "version": "22.7.0", + "resolved": "https://registry.npmjs.org/@nx/dotnet/-/dotnet-22.7.0.tgz", + "integrity": "sha512-L5BWc9x4/0ZW3BavM9tsmQAtXVR8gHCXpGZDudsVQBGXUYwMS1Mebc7YqZDiR24vW+RnYM9FEABsn5BfG0Fu2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "22.7.0", + "ignore": "^7.0.5", + "tslib": "^2.3.0" + } + }, + "node_modules/@nx/nx-darwin-arm64": { + "version": "22.6.5", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-22.6.5.tgz", + "integrity": "sha512-qT77Omkg5xQuL2+pDbneX2tI+XW5ZeayMylu7UUgK8OhTrAkJLKjpuYRH4xT5XBipxbDtlxmO3aLS3Ib1pKzJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/nx-darwin-x64": { + "version": "22.6.5", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-22.6.5.tgz", + "integrity": "sha512-9jICxb7vfJ56y/7Yuh3b/n1QJqWxO9xnXKYEs6SO8xPoW/KomVckILGc1C6RQSs6/3ixVJC7k1Dh1wm5tKPFrg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/nx-freebsd-x64": { + "version": "22.6.5", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-22.6.5.tgz", + "integrity": "sha512-6B1wEKpqz5dI3AGMqttAVnA6M3DB/besAtuGyQiymK9ROlta1iuWgCcIYwcCQyhLn2Rx7vqj447KKcgCa8HlVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@nx/nx-linux-arm-gnueabihf": { + "version": "22.6.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-22.6.5.tgz", + "integrity": "sha512-xV50B8mnDPboct7JkAHftajI02s+8FszA8WTzhore+YGR+lEKHTLpucwGEaQuMlSdLplH7pQix4B4uK5pcMhZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-arm64-gnu": { + "version": "22.6.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-22.6.5.tgz", + "integrity": "sha512-2JkWuMGj+HpW6oPAvU5VdAx1afTnEbiM10Y3YOrl3fipWV4BiP5VDx762QTrfCraP4hl6yqTgvTe7F9xaby+jQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-arm64-musl": { + "version": "22.6.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-22.6.5.tgz", + "integrity": "sha512-Z/zMqFClnEyqDXouJKEPoWVhMQIif5F0YuECWBYjd3ZLwQsXGTItoh+6Wm3XF/nGMA2uLOHyTq/X7iFXQY3RzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-x64-gnu": { + "version": "22.6.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-22.6.5.tgz", + "integrity": "sha512-FlotSyqNnaXSn0K+yWw+hRdYBwusABrPgKLyixfJIYRzsy+xPKN6pON6vZfqGwzuWF/9mEGReRz+iM8PiW0XSg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-x64-musl": { + "version": "22.6.5", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-22.6.5.tgz", + "integrity": "sha512-RVOe2qcwhoIx6mxQURPjUfAW5SEOmT2gdhewvdcvX9ICq1hj5B2VarmkhTg0qroO7xiyqOqwq26mCzoV2I3NgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-win32-arm64-msvc": { + "version": "22.6.5", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-22.6.5.tgz", + "integrity": "sha512-ZqurqI8VuYnsr2Kn4K4t+Gx6j/BZdf6qz/6Tv4A7XQQ6oNYVQgTqoNEFj+CCkVaIe6aIdCWpousFLqs+ZgBqYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/nx-win32-x64-msvc": { + "version": "22.6.5", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-22.6.5.tgz", + "integrity": "sha512-i2QFBJIuaYg9BHxrrnBV4O7W9rVL2k0pSIdk/rRp3EYJEU93iUng+qbZiY9wh1xvmXuUCE2G7TRd+8/SG/RFKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@redth/dotnet-nx": { + "version": "0.1.0-alpha.1", + "resolved": "https://npm.pkg.github.com/@redth/dotnet-nx/-/dotnet-nx-0.1.0-alpha.1.tgz", + "dev": true, + "license": "MIT", + "peerDependencies": { + "nx": ">=22.0.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@yarnpkg/parsers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.2.tgz", + "integrity": "sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "js-yaml": "^3.10.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@zkochan/js-yaml": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", + "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ejs": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-5.0.1.tgz", + "integrity": "sha512-COqBPFMxuPTPspXl2DkVYaDS3HtrD1GpzOGkNTJ1IYkifq/r9h8SVEFrjA3D9/VJGOEoMQcrlhpntcSUrM8k6A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.12.18" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lines-and-columns": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", + "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nx": { + "version": "22.6.5", + "resolved": "https://registry.npmjs.org/nx/-/nx-22.6.5.tgz", + "integrity": "sha512-VRKhDAt684dXNSz9MNjE7MekkCfQF41P2PSx5jEWQjDEP1Z4jFZbyeygWs5ZyOroG7/n0MoWAJTe6ftvIcBOAg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@napi-rs/wasm-runtime": "0.2.4", + "@yarnpkg/lockfile": "^1.1.0", + "@yarnpkg/parsers": "3.0.2", + "@zkochan/js-yaml": "0.0.7", + "axios": "1.15.0", + "cli-cursor": "3.1.0", + "cli-spinners": "2.6.1", + "cliui": "^8.0.1", + "dotenv": "~16.4.5", + "dotenv-expand": "~11.0.6", + "ejs": "5.0.1", + "enquirer": "~2.3.6", + "figures": "3.2.0", + "flat": "^5.0.2", + "front-matter": "^4.0.2", + "ignore": "^7.0.5", + "jest-diff": "^30.0.2", + "jsonc-parser": "3.2.0", + "lines-and-columns": "2.0.3", + "minimatch": "10.2.4", + "npm-run-path": "^4.0.1", + "open": "^8.4.0", + "ora": "5.3.0", + "picocolors": "^1.1.0", + "resolve.exports": "2.0.3", + "semver": "^7.6.3", + "smol-toml": "1.6.1", + "string-width": "^4.2.3", + "tar-stream": "~2.2.0", + "tmp": "~0.2.1", + "tree-kill": "^1.2.2", + "tsconfig-paths": "^4.1.2", + "tslib": "^2.3.0", + "yaml": "^2.6.0", + "yargs": "^17.6.2", + "yargs-parser": "21.1.1" + }, + "bin": { + "nx": "bin/nx.js", + "nx-cloud": "bin/nx-cloud.js" + }, + "optionalDependencies": { + "@nx/nx-darwin-arm64": "22.6.5", + "@nx/nx-darwin-x64": "22.6.5", + "@nx/nx-freebsd-x64": "22.6.5", + "@nx/nx-linux-arm-gnueabihf": "22.6.5", + "@nx/nx-linux-arm64-gnu": "22.6.5", + "@nx/nx-linux-arm64-musl": "22.6.5", + "@nx/nx-linux-x64-gnu": "22.6.5", + "@nx/nx-linux-x64-musl": "22.6.5", + "@nx/nx-win32-arm64-msvc": "22.6.5", + "@nx/nx-win32-x64-msvc": "22.6.5" + }, + "peerDependencies": { + "@swc-node/register": "^1.11.1", + "@swc/core": "^1.15.8" + }, + "peerDependenciesMeta": { + "@swc-node/register": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", + "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pretty-format": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/smol-toml": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", + "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tmp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", + "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..482a5d915 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "maui-labs", + "private": true, + "devDependencies": { + "@nx/dotnet": "22.7.0", + "@redth/dotnet-nx": "0.1.0-alpha.1", + "nx": "^22.6.5" + } +} diff --git a/platforms/Linux.Gtk4/Directory.Build.props b/platforms/Linux.Gtk4/Directory.Build.props index 8873772cf..eb2584af6 100644 --- a/platforms/Linux.Gtk4/Directory.Build.props +++ b/platforms/Linux.Gtk4/Directory.Build.props @@ -8,6 +8,8 @@ 10.0.41 false + + linux diff --git a/platforms/MacOS/Directory.Build.props b/platforms/MacOS/Directory.Build.props index 83aa668ab..07203de85 100644 --- a/platforms/MacOS/Directory.Build.props +++ b/platforms/MacOS/Directory.Build.props @@ -2,6 +2,9 @@ + + type:standalone-ci preview enable enable diff --git a/platforms/Windows.WPF/Directory.Build.props b/platforms/Windows.WPF/Directory.Build.props index 144ad8ccd..2b490875d 100644 --- a/platforms/Windows.WPF/Directory.Build.props +++ b/platforms/Windows.WPF/Directory.Build.props @@ -2,6 +2,8 @@ + + type:standalone-ci preview enable enable diff --git a/scripts/nxdn b/scripts/nxdn new file mode 100755 index 000000000..a141bbf04 --- /dev/null +++ b/scripts/nxdn @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Shim used as DOTNET_NX_NXDN so the @redth/dotnet-nx Nx plugin can locate the +# nxdn .NET local tool without requiring it on PATH. The local tool manifest +# lives at .config/dotnet-tools.json. +exec dotnet nxdn "$@" diff --git a/scripts/nxdn.cmd b/scripts/nxdn.cmd new file mode 100644 index 000000000..efa5246e3 --- /dev/null +++ b/scripts/nxdn.cmd @@ -0,0 +1,2 @@ +@ECHO OFF +dotnet nxdn %* diff --git a/src/AI/Directory.Build.props b/src/AI/Directory.Build.props new file mode 100644 index 000000000..80d283409 --- /dev/null +++ b/src/AI/Directory.Build.props @@ -0,0 +1,11 @@ + + + + + + type:standalone-ci + + diff --git a/src/AppProjectReference/Directory.Build.props b/src/AppProjectReference/Directory.Build.props new file mode 100644 index 000000000..09699f18a --- /dev/null +++ b/src/AppProjectReference/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + + type:standalone-ci + + diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Android/Microsoft.Maui.DevFlow.Agent.IntegrationTests.Android.csproj b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Android/Microsoft.Maui.DevFlow.Agent.IntegrationTests.Android.csproj new file mode 100644 index 000000000..b8c47a882 --- /dev/null +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Android/Microsoft.Maui.DevFlow.Agent.IntegrationTests.Android.csproj @@ -0,0 +1,9 @@ + + + + android + + + + + diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/AndroidEmulatorFixture.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/AndroidEmulatorFixture.cs index 3383ff950..d1fbea1ff 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/AndroidEmulatorFixture.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/AndroidEmulatorFixture.cs @@ -46,10 +46,9 @@ await WithBuildLockAsync(async () => // Best-effort only; some emulator states reject logcat clear briefly. } - var projectPath = GetSampleProjectPath(); - await BuildSampleAsync(projectPath, "net10.0-android", - $"-p:EmbedAssembliesIntoApk=true -p:MauiDevFlowPort={AgentPort}"); - + // Sample is built by MSBuild as a no-op ProjectReference when this test + // project is built with -p:DevFlowIntegrationPlatform=android. The fixture + // only needs to locate the resulting APK. var apkPath = FindApk(); await InstallApkAsync(apkPath); @@ -513,17 +512,33 @@ static async Task WaitForDeviceBootAsync(string adb, string serial, int timeoutS static string FindApk() { + var prebuiltPath = GetPrebuiltSampleAppPath(); + if (prebuiltPath != null) + return FindApk(prebuiltPath); + var binDir = Path.Combine(GetSampleBuildOutputRoot(), "net10.0-android"); + return FindApk(binDir); + } + + static string FindApk(string path) + { + if (File.Exists(path)) + { + if (Path.GetExtension(path).Equals(".apk", StringComparison.OrdinalIgnoreCase)) + return path; + + throw new InvalidOperationException($"Prebuilt Android sample path is not an APK: {path}"); + } - if (!Directory.Exists(binDir)) - throw new InvalidOperationException($"Android build output not found at: {binDir}"); + if (!Directory.Exists(path)) + throw new InvalidOperationException($"Android build output not found at: {path}"); - var apks = Directory.GetFiles(binDir, "*-Signed.apk", SearchOption.AllDirectories); + var apks = Directory.GetFiles(path, "*-Signed.apk", SearchOption.AllDirectories); if (apks.Length == 0) - apks = Directory.GetFiles(binDir, "*.apk", SearchOption.AllDirectories); + apks = Directory.GetFiles(path, "*.apk", SearchOption.AllDirectories); if (apks.Length == 0) - throw new InvalidOperationException($"No APK found under {binDir}"); + throw new InvalidOperationException($"No APK found under {path}"); return apks[0]; } diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/AppFixtureBase.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/AppFixtureBase.cs index ad7ea96fd..3e3689793 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/AppFixtureBase.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/AppFixtureBase.cs @@ -112,39 +112,6 @@ protected async Task WaitForAgentAsync(int timeoutSeconds) $"Last error: {lastException?.GetType().Name}: {lastException?.Message}"); } - protected static async Task BuildSampleAsync(string projectPath, string targetFramework, string? extraArgs = null) - { - CleanBuildOutputs(projectPath, targetFramework); - - var dotnetPath = File.Exists("/usr/local/share/dotnet/dotnet") - ? "/usr/local/share/dotnet/dotnet" - : "dotnet"; - - var args = $"build \"{projectPath}\" -f {targetFramework} -c Debug --nologo -v q"; - if (!string.IsNullOrEmpty(extraArgs)) - args += $" {extraArgs}"; - - var psi = new ProcessStartInfo(dotnetPath, args) - { - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - }; - - using var process = Process.Start(psi) - ?? throw new InvalidOperationException("Failed to start dotnet build"); - - var stdout = await process.StandardOutput.ReadToEndAsync(); - var stderr = await process.StandardError.ReadToEndAsync(); - await process.WaitForExitAsync(); - - if (process.ExitCode != 0) - { - throw new InvalidOperationException( - $"dotnet build failed (exit code {process.ExitCode}).\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}"); - } - } - protected static async Task WithBuildLockAsync(Func action, TimeSpan? timeout = null) { var repoRoot = FindRepoRoot(); @@ -173,28 +140,6 @@ private static async Task AcquireBuildLockAsync(string lockFilePath, throw new TimeoutException($"Timed out waiting for integration build lock: {lockFilePath}"); } - private static void CleanBuildOutputs(string projectPath, string targetFramework) - { - var projectDir = Path.GetDirectoryName(projectPath) - ?? throw new InvalidOperationException($"Could not determine project directory for '{projectPath}'."); - var projectName = Path.GetFileNameWithoutExtension(projectPath); - var repoRoot = FindRepoRoot(); - - var paths = new[] - { - Path.Combine(repoRoot, "artifacts", "bin", projectName, "Debug", targetFramework), - Path.Combine(repoRoot, "artifacts", "obj", projectName, "Debug", targetFramework), - Path.Combine(projectDir, "bin", "Debug", targetFramework), - Path.Combine(projectDir, "obj", "Debug", targetFramework), - }; - - foreach (var path in paths) - { - if (Directory.Exists(path)) - Directory.Delete(path, recursive: true); - } - } - protected static string FindRepoRoot() { var dir = AppContext.BaseDirectory; @@ -211,14 +156,21 @@ protected static string FindRepoRoot() "Ensure tests are run from within the repository."); } - protected static string GetSampleProjectPath() + protected static string? GetPrebuiltSampleAppPath() { - var repoRoot = FindRepoRoot(); - var path = Path.Combine(repoRoot, "samples", "DevFlow.Sample", "DevFlow.Sample.csproj"); - if (!File.Exists(path)) - throw new InvalidOperationException($"Sample project not found at: {path}"); + var path = Environment.GetEnvironmentVariable("DEVFLOW_TEST_SAMPLE_APP_PATH"); + if (string.IsNullOrWhiteSpace(path)) + return null; + + var fullPath = Path.IsPathRooted(path) + ? Path.GetFullPath(path) + : Path.GetFullPath(Path.Combine(FindRepoRoot(), path)); + + if (!File.Exists(fullPath) && !Directory.Exists(fullPath)) + throw new InvalidOperationException( + $"DEVFLOW_TEST_SAMPLE_APP_PATH points to a path that does not exist: {fullPath}"); - return path; + return fullPath; } protected static string GetSampleBuildOutputRoot() diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/AppFixtureFactory.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/AppFixtureFactory.cs index e86227e1a..cfd200830 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/AppFixtureFactory.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/AppFixtureFactory.cs @@ -1,8 +1,13 @@ +using System.Reflection; + namespace Microsoft.Maui.DevFlow.Agent.IntegrationTests.Fixtures; /// -/// Factory that creates the appropriate platform fixture based on -/// the DEVFLOW_TEST_PLATFORM environment variable. +/// Factory that creates the appropriate platform fixture. The target platform is +/// stamped into the test assembly at build time as [AssemblyMetadata("DevFlowTestPlatform", "...")] +/// by IntegrationTests.Common.props, so each platform-specific test csproj +/// (Android/iOS/MacCatalyst/Windows) self-identifies. The DEVFLOW_TEST_PLATFORM +/// environment variable is honored as an override for ad-hoc runs. /// public static class AppFixtureFactory { @@ -10,6 +15,15 @@ public static IAppFixture Create() { var platform = Environment.GetEnvironmentVariable("DEVFLOW_TEST_PLATFORM")?.ToLowerInvariant(); + if (string.IsNullOrEmpty(platform)) + { + platform = typeof(AppFixtureFactory).Assembly + .GetCustomAttributes() + .FirstOrDefault(a => string.Equals(a.Key, "DevFlowTestPlatform", StringComparison.Ordinal)) + ?.Value + ?.ToLowerInvariant(); + } + if (string.IsNullOrEmpty(platform)) { platform = OperatingSystem.IsWindows() ? "windows" : "maccatalyst"; diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/MacCatalystFixture.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/MacCatalystFixture.cs index 4e8eae182..49852ed25 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/MacCatalystFixture.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/MacCatalystFixture.cs @@ -16,9 +16,9 @@ protected override async Task InitializePlatformAsync() { await WithBuildLockAsync(async () => { - var projectPath = GetSampleProjectPath(); - await BuildSampleAsync(projectPath, "net10.0-maccatalyst"); - + // Sample is built by MSBuild as a no-op ProjectReference when this test + // project is built with -p:DevFlowIntegrationPlatform=maccatalyst. The + // fixture only needs to locate the resulting .app bundle. var appPath = FindAppBundle(); LaunchApp(appPath); _weOwnTheProcess = true; @@ -38,15 +38,26 @@ protected override async Task DisposePlatformAsync() static string FindAppBundle() { + var prebuiltPath = GetPrebuiltSampleAppPath(); + if (prebuiltPath != null) + return FindAppBundle(prebuiltPath); + var sampleBinDir = Path.Combine(GetSampleBuildOutputRoot(), "net10.0-maccatalyst"); + return FindAppBundle(sampleBinDir); + } + + static string FindAppBundle(string path) + { + if (Directory.Exists(path) && Path.GetExtension(path).Equals(".app", StringComparison.OrdinalIgnoreCase)) + return path; - if (!Directory.Exists(sampleBinDir)) - throw new InvalidOperationException($"Build output directory not found: {sampleBinDir}"); + if (!Directory.Exists(path)) + throw new InvalidOperationException($"Build output directory not found: {path}"); - var appBundles = Directory.GetDirectories(sampleBinDir, "*.app", SearchOption.AllDirectories); + var appBundles = Directory.GetDirectories(path, "*.app", SearchOption.AllDirectories); if (appBundles.Length == 0) - throw new InvalidOperationException($"No .app bundle found under {sampleBinDir}"); + throw new InvalidOperationException($"No .app bundle found under {path}"); return appBundles[0]; } diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/WindowsFixture.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/WindowsFixture.cs index a54876cd1..e3fb0f99d 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/WindowsFixture.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/WindowsFixture.cs @@ -15,9 +15,9 @@ protected override async Task InitializePlatformAsync() { await WithBuildLockAsync(async () => { - var projectPath = GetSampleProjectPath(); - await BuildSampleAsync(projectPath, "net10.0-windows10.0.19041.0"); - + // Sample is built by MSBuild as a no-op ProjectReference when this test + // project is built with -p:DevFlowIntegrationPlatform=windows. The fixture + // only needs to locate the resulting executable. var exePath = FindExecutable(); var psi = new ProcessStartInfo(exePath) { @@ -45,11 +45,31 @@ protected override async Task DisposePlatformAsync() static string FindExecutable() { + var prebuiltPath = GetPrebuiltSampleAppPath(); + if (prebuiltPath != null) + return FindExecutable(prebuiltPath); + var binDir = GetSampleBuildOutputRoot(); - var exes = Directory.GetFiles(binDir, "DevFlow.Sample.exe", SearchOption.AllDirectories); + return FindExecutable(binDir); + } + + static string FindExecutable(string path) + { + if (File.Exists(path)) + { + if (Path.GetFileName(path).Equals("DevFlow.Sample.exe", StringComparison.OrdinalIgnoreCase)) + return path; + + throw new InvalidOperationException($"Prebuilt Windows sample path is not DevFlow.Sample.exe: {path}"); + } + + if (!Directory.Exists(path)) + throw new InvalidOperationException($"Windows build output not found at: {path}"); + + var exes = Directory.GetFiles(path, "DevFlow.Sample.exe", SearchOption.AllDirectories); if (exes.Length == 0) - throw new InvalidOperationException($"No DevFlow.Sample.exe found under {binDir}"); + throw new InvalidOperationException($"No DevFlow.Sample.exe found under {path}"); return exes[0]; } diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/iOSSimulatorFixture.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/iOSSimulatorFixture.cs index 34337be23..0028005dd 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/iOSSimulatorFixture.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Fixtures/iOSSimulatorFixture.cs @@ -23,10 +23,9 @@ protected override async Task InitializePlatformAsync() await WithBuildLockAsync(async () => { - var projectPath = GetSampleProjectPath(); - await BuildSampleAsync(projectPath, "net10.0-ios", - $"-p:_DeviceTarget=simulator -p:RuntimeIdentifier={GetSimulatorRuntimeIdentifier()}"); - + // Sample is built by MSBuild as a no-op ProjectReference when this test + // project is built with -p:DevFlowIntegrationPlatform=ios. The fixture + // only needs to locate the resulting .app bundle. var appBundle = FindSimulatorAppBundle(); _appBundleId = ReadBundleId(appBundle); @@ -154,14 +153,25 @@ static string GetSimulatorRuntimeIdentifier() => static string FindSimulatorAppBundle() { + var prebuiltPath = GetPrebuiltSampleAppPath(); + if (prebuiltPath != null) + return FindAppBundle(prebuiltPath); + var binDir = Path.Combine(GetSampleBuildOutputRoot(), "net10.0-ios", GetSimulatorRuntimeIdentifier()); + return FindAppBundle(binDir); + } + + static string FindAppBundle(string path) + { + if (Directory.Exists(path) && Path.GetExtension(path).Equals(".app", StringComparison.OrdinalIgnoreCase)) + return path; - if (!Directory.Exists(binDir)) - throw new InvalidOperationException($"iOS simulator build output not found at: {binDir}"); + if (!Directory.Exists(path)) + throw new InvalidOperationException($"iOS simulator build output not found at: {path}"); - var appBundles = Directory.GetDirectories(binDir, "*.app", SearchOption.AllDirectories); + var appBundles = Directory.GetDirectories(path, "*.app", SearchOption.AllDirectories); if (appBundles.Length == 0) - throw new InvalidOperationException($"No .app bundle found under {binDir}"); + throw new InvalidOperationException($"No .app bundle found under {path}"); return appBundles[0]; } diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/IntegrationTests.Common.props b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/IntegrationTests.Common.props new file mode 100644 index 000000000..f16f50e18 --- /dev/null +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/IntegrationTests.Common.props @@ -0,0 +1,97 @@ + + + + + + net10.0 + false + true + Microsoft.Maui.DevFlow.Agent.IntegrationTests + + type:integration-test;device:$(DevFlowSamplePlatform) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_DevFlowSampleIosRid Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64'">iossimulator-arm64 + <_DevFlowSampleIosRid Condition="'$(_DevFlowSampleIosRid)' == ''">iossimulator-x64 + + + + + <_DevFlowSampleTfm Condition="'$(DevFlowSamplePlatform)' == 'android'">net10.0-android + <_DevFlowSampleTfm Condition="'$(DevFlowSamplePlatform)' == 'ios'">net10.0-ios + <_DevFlowSampleTfm Condition="'$(DevFlowSamplePlatform)' == 'maccatalyst'">net10.0-maccatalyst + <_DevFlowSampleTfm Condition="'$(DevFlowSamplePlatform)' == 'windows'">net10.0-windows10.0.19041.0 + + <_DevFlowSampleAdditionalProps Condition="'$(DevFlowSamplePlatform)' == 'android'">EmbedAssembliesIntoApk=true;MauiDevFlowPort=58100 + <_DevFlowSampleAdditionalProps Condition="'$(DevFlowSamplePlatform)' == 'ios'">_DeviceTarget=simulator;RuntimeIdentifier=$(_DevFlowSampleIosRid) + + + + + + + + diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/MacCatalyst/Microsoft.Maui.DevFlow.Agent.IntegrationTests.MacCatalyst.csproj b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/MacCatalyst/Microsoft.Maui.DevFlow.Agent.IntegrationTests.MacCatalyst.csproj new file mode 100644 index 000000000..c45d3dcf1 --- /dev/null +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/MacCatalyst/Microsoft.Maui.DevFlow.Agent.IntegrationTests.MacCatalyst.csproj @@ -0,0 +1,10 @@ + + + + maccatalyst + macos + + + + + diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Microsoft.Maui.DevFlow.Agent.IntegrationTests.csproj b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Microsoft.Maui.DevFlow.Agent.IntegrationTests.csproj deleted file mode 100644 index a4f28a75d..000000000 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Microsoft.Maui.DevFlow.Agent.IntegrationTests.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net10.0 - false - true - - - - - - - - - - - - - - - - diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Windows/Microsoft.Maui.DevFlow.Agent.IntegrationTests.Windows.csproj b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Windows/Microsoft.Maui.DevFlow.Agent.IntegrationTests.Windows.csproj new file mode 100644 index 000000000..b26a633ac --- /dev/null +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/Windows/Microsoft.Maui.DevFlow.Agent.IntegrationTests.Windows.csproj @@ -0,0 +1,10 @@ + + + + windows + windows + + + + + diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/iOS/Microsoft.Maui.DevFlow.Agent.IntegrationTests.iOS.csproj b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/iOS/Microsoft.Maui.DevFlow.Agent.IntegrationTests.iOS.csproj new file mode 100644 index 000000000..90949b67f --- /dev/null +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/iOS/Microsoft.Maui.DevFlow.Agent.IntegrationTests.iOS.csproj @@ -0,0 +1,10 @@ + + + + ios + macos + + + + +