--- name: architecture description: Architecture map, layer responsibilities, key files, auth types, and debug runbook by symptom. user-invocable: true --- # Architecture & Debug Guide Use this skill to understand the codebase architecture, trace issues across repos, or debug failures. If given a specific error or symptom, use the architecture map below to identify which layer is failing and follow the targeted debug steps for that layer. --- ## Repo Dependency Map ``` powerplatform-build-tools (THIS REPO — Azure DevOps Extension) │ │ npm import (bundled) ▼ @microsoft/powerplatform-cli-wrapper (TypeScript action library) │ │ child_process.spawn() ▼ pac CLI binary (pac.exe / pac — .NET, downloaded from NuGet at build time) │ │ HTTPS REST ▼ Power Platform API (Dataverse / crm.dynamics.com) ``` --- ## Layer Responsibilities | Layer | Owns | Does NOT own | |-------|------|--------------| | **build-tools** | Azure DevOps task inputs, service connections, pipeline variables, artifact upload, task.json | Power Platform logic, pac CLI args | | **cli-wrapper** | pac CLI argument assembly, auth flow, subprocess spawn/capture | Azure DevOps APIs, pipeline variables | | **pac CLI** | Power Platform REST APIs, auth token management, all pac command semantics | Node.js, npm, Azure DevOps | | **Power Platform API** | Dataverse operations, solution lifecycle | Everything above | --- ## Full Call Chain (export-solution as example) ``` Azure DevOps Pipeline │ task inputs: SolutionName, SolutionOutputFile, authenticationType, Environment ▼ src/tasks/export-solution/export-solution-v2/index.ts ├── TaskParser.getHostParameterEntries(taskDefinitionData) │ └── reads task.json → Record ├── getCredentials() │ ├── tl.getInput("authenticationType") → "PowerPlatformSPN" | "PowerPlatformEnvironment" │ ├── tl.getEndpointAuthorization(endpointName) │ └── returns AuthCredentials { appId, clientSecret, tenantId, cloudInstance } ├── getEnvironmentUrl() │ └── checks (in order): task input → pipeline variable → env var → service connection URL │ └── exportSolution(params, new BuildToolsRunnerParams(), new BuildToolsHost()) │ ↑ ↑ │ runnersDir from getInput() wraps │ POWERPLATFORMTOOLS_ tl.getInput() │ PACCLIPATH env var ▼ cli-wrapper: src/actions/exportSolution.ts ├── createPacRunner(runnerParams) → CommandRunner (spawn wrapper) ├── authenticateEnvironment(pac, credentials, environmentUrl) │ └── pac("auth", "create", "--environment", url, "--applicationId", id, │ "--clientSecret", base64secret, "--tenant", tenantId, "--cloud", "Public") ├── InputValidator.pushInput(args, "--name", params.name) → host.getInput(entry) ├── InputValidator.pushInput(args, "--path", params.path, resolveFolder) ├── InputValidator.pushInput(args, "--managed", params.managed) │ ... (assembles full arg list) │ ├── pac("solution", "export", "--name", "MySolution", "--path", "/out/sol.zip", ...) │ ▼ │ CommandRunner.ts: child_process.spawn("pac.exe", args, { env: { PP_TOOLS_AUTOMATION_AGENT: ... } }) │ ├── stdout lines → logger.log() → Azure DevOps build log │ ├── stderr lines → logger.error() → Azure DevOps build log │ └── exit 0 → resolve() | exit ≠ 0 → reject(RunnerError) → task fails │ └── clearAuthentication(pac) └── pac("auth", "clear") ``` --- ## Key Files by Layer ### build-tools (this repo) | File | Purpose | |------|---------| | `src/tasks//-v2/index.ts` | Task entry point — reads inputs, calls cli-wrapper action | | `src/tasks//-v2/task.json` | Declares all task inputs, metadata, and execution config — execution handler is `Node20_1` (primary) + `Node16` (fallback) | | `src/host/BuildToolsHost.ts` | Implements `IHostAbstractions` — wraps `tl.getInput()`, artifact store | | `src/host/BuildToolsRunnerParams.ts` | Implements `RunnerParameters` — pac path, logger, agent string | | `src/host/CliLocator.ts` | Finds pac.exe/pac binary path by platform (win32 vs linux) | | `src/host/logger.ts` | Implements cli-wrapper's `Logger` interface — routes to `tl.warning/error/debug` and `console.log` | | `src/host/PipelineVariables.ts` | Defines output pipeline variable names (`BuildTools.EnvironmentUrl`, `BuildTools.EnvironmentId`, etc.) — used by create-environment and others | | `src/params/auth/getCredentials.ts` | Extracts auth from service connection — handles `PowerPlatformEnvironment` (UsernamePassword), `PowerPlatformSPN` (SPN or WIF, detected by `authorization.scheme`) | | `src/params/auth/getEnvironmentUrl.ts` | Resolves target environment URL (4-level fallback chain) | | `src/parser/TaskParser.ts` | Converts task.json inputs array → `Record` | | `extension/extension-manifest.json` | Azure DevOps extension metadata | | `extension/task-metadata.json` | Task GUIDs per stage (LIVE / BETA / DEV / EXPERIMENTAL) | | `extension/service-connections.json` | Power Platform SPN endpoint definition | | `nuget.json` | pac CLI version pinned here — update to upgrade pac | | `webpack.config.js` | Finds all task index.ts files, compiles each to dist/tasks/*/index.js | | `gulp/pack.mjs` | Stages artifacts, injects task IDs per stage, runs tfx-cli to create VSIX | ### cli-wrapper (inside node_modules/@microsoft/powerplatform-cli-wrapper) | File | Purpose | |------|---------| | `dist/actions/exportSolution.ts` | Assembles pac args, calls pac, handles auth | | `dist/actions/importSolution.ts` | Same pattern for import | | `dist/actions/*.ts` | One file per pac command | | `dist/pac/createPacRunner.ts` | Resolves pac.exe path, returns `CommandRunner` | | `dist/pac/auth/authenticate.ts` | Builds `pac auth create` / `pac auth clear` calls | | `dist/CommandRunner.ts` | `child_process.spawn()` wrapper — stdout/stderr capture, exit code handling | | `dist/host/InputValidator.ts` | Pulls values through `IHostAbstractions`, normalizes, pushes to arg array | --- ## Authentication Types & Environment Variable Map | Auth Type | Service Connection Scheme | Credentials Passed to pac | |-----------|--------------------------|--------------------------| | Username/Password | `PowerPlatformEnvironment` | `--username` `--password` (base64) | | Service Principal | `PowerPlatformSPN` | `--applicationId` `--clientSecret` (base64) `--tenant` | | Workload Identity Federation | `PowerPlatformSPN` + WIF scheme | `--applicationId` `--tenant` `--azureDevOpsFederated` (via OIDC token env vars) | > **Note:** Managed Identity is NOT supported through PPBT. `getCredentials.ts` only handles the three types above. WIF is detected when `authorization.scheme === "WorkloadIdentityFederation"` inside the `PowerPlatformSPN` branch — PPBT sets `PAC_ADO_ID_TOKEN_REQUEST_URL` / `PAC_ADO_ID_TOKEN_REQUEST_TOKEN` env vars and pac CLI handles the OIDC exchange. **Environment variables used by tasks:** | Variable | Set by | Used by | |----------|--------|---------| | `POWERPLATFORMTOOLS_PACCLIPATH` | tool-installer task | All other tasks (to find pac.exe) | | `BuildTools.EnvironmentUrl` | create-environment task | All other tasks (fallback env URL) | | `agent.diagnostic` | Azure DevOps agent | All tasks (enables `--log-to-console` on pac) | | `PP_TOOLS_AUTOMATION_AGENT` | CommandRunner.ts | pac CLI (user-agent telemetry) | --- ## Bundled Dependency Notes (affects vulnerability fixes) These packages are in `bundleDependencies` — they ship inside the VSIX and their nested transitive deps are marked `inBundle: true` in package-lock.json. **npm overrides cannot reach them. Lock file must be patched directly.** | Bundled Package | Key nested dep | Known issue | |-----------------|---------------|-------------| | `azure-pipelines-task-lib@5.2.8` | — | No known issues (upgraded from 4.x which had minimatch@3.0.5 ReDoS) | | `@microsoft/powerplatform-cli-wrapper@0.1.135` | `minimatch@10.x` via glob@11 | Patched to 10.2.4 in lock file | | `fs-extra` | — | No known issues | | `semver` | — | No known issues | --- ## Debug Runbook by Symptom ### `npm install` hangs indefinitely **Cause:** npm re-resolves the full dependency tree when a new uncached package version is introduced. During re-resolution it hits `npm.pkg.github.com` for `@microsoft/*` packages. 1. Check `~/.npmrc` has a valid GitHub token: `//npm.pkg.github.com/:_authToken=` 2. Test auth: `curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token " https://npm.pkg.github.com/@microsoft%2fpowerplatform-cli-wrapper` → should return 200 3. Run with verbose to find where it hangs: `npm install --verbose 2>&1` 4. Check npm debug log: `cat C:/.tools/.npm/_logs/-debug-0.log | tail -100` 5. **If log shows 50,000+ `placeDep ROOT minimatch` lines** → infinite loop from a conflicting flat minimatch override. Remove it. ### `npm install` errors with registry auth **Cause:** GitHub PAT expired or missing scopes. 1. Token needs `read:packages` scope on `github.com` 2. Token goes in `~/.npmrc` as `//npm.pkg.github.com/:_authToken=` 3. The project `.npmrc` sets `@microsoft:registry=https://npm.pkg.github.com/` and `always-auth=true` ### Task fails: "pac.exe not found" or "POWERPLATFORMTOOLS_PACCLIPATH not set" **Cause:** `tool-installer` task did not run before this task, or failed silently. - All tasks require `tool-installer` to run first — it sets `POWERPLATFORMTOOLS_PACCLIPATH` - Check `CliLocator.ts` for the exact path resolution logic - On Linux: pac binary needs execute permissions — `CliLocator.ts` sets them via `chmod` ### Task fails: "auth create failed" or authentication errors **Layer:** cli-wrapper `authenticate.ts` or pac CLI auth 1. Check service connection credentials in Azure DevOps (Pipelines → Service Connections) 2. Verify the auth type: SPN vs Username/Password vs Workload Identity 3. Check `cloudInstance` — wrong cloud (e.g. sending to `Public` when env is `UsGov`) causes auth failure 4. Check `getCredentials.ts` — `resolveCloudInstance()` maps the endpoint URL hostname to cloud name 5. Enable diagnostics: set `agent.diagnostic=true` variable in pipeline → adds `--log-to-console` to pac ### Task fails: "Solution not found" or pac CLI errors **Layer:** pac CLI → Power Platform API 1. Enable `--log-to-console`: set `system.debug=true` in pipeline variables 2. Check the exact pac command being run in the build log (each arg is logged) 3. The error is from pac.exe — check Power Platform documentation for that specific pac command 4. Verify environment URL: `getEnvironmentUrl.ts` has a 4-level fallback — add explicit `Environment` input to the task to override ### Build fails: TypeScript errors after upgrading packages **Layer:** build-tools compile step 1. `npm run build` → webpack + ts-loader 2. TypeScript target is ES5, module is CommonJS (`tsconfig.json`) 3. `gulp-typescript` is on `^6.0.0-alpha.1` — type errors may surface after TS version upgrades 4. cli-wrapper interface changes break at compile time here — check `IHostAbstractions` and `RunnerParameters` ### VSIX package has wrong task GUIDs / tasks conflict in marketplace **Layer:** gulp/pack.mjs 1. Task GUIDs per stage are in `extension/task-metadata.json` 2. LIVE uses the canonical GUIDs; BETA/DEV/EXPERIMENTAL get unique GUIDs so they don't conflict 3. `pack.mjs` substitutes GUIDs at pack time — verify the substitution logic if tasks show up under wrong IDs ### Task fails: "Can't resolve AAD/OAuth authority" **Source:** IcM recurring pattern (ADO #4863652) **Layer:** pac CLI auth → Power Platform API 1. Usually caused by incorrect `cloudInstance` value — check `getCredentials.ts` → `resolveCloudInstance()` 2. Verify the service connection URL matches the actual cloud (Public vs USGov vs China) 3. Check if the tenant's AAD authority endpoint is reachable from the build agent (network/proxy issues) 4. Enable diagnostics: set `agent.diagnostic=true` in pipeline variables for verbose pac auth logging ### Task fails: WhoAmI error in non-English build agent context **Source:** ADO #4846644 (long-term, no code fix yet) **Cause:** `whoAmI.ts` in cli-wrapper parses localized pac CLI output to extract the environment ID. Fails when the build agent OS locale is non-English. **Workaround:** Ensure build agent uses English locale (`en-US`). Set `LANG=en_US.UTF-8` on Linux agents. **Layer:** cli-wrapper `src/actions/whoAmI.ts` ~line 40 — fix must go upstream to cli-wrapper repo. ### Import solution fails with PVA (Power Virtual Agents) components **Source:** IcM 604312672, ADO #4896777 **Symptom:** `import-solution` task fails when solution contains PVA components. **Layer:** pac CLI → Power Platform API (not a build-tools code issue) **Workaround:** Check pac CLI version — update `nuget.json` to latest pac CLI. If persists, raise with PAC CLI team. ### Extension not working on Azure DevOps Server 2020 (on-prem) **Source:** ADO #5130362 **Official stance:** Extension is **not supported** on Azure DevOps Server 2020 on-prem. **Action:** Deflect IcM tickets. Direct customers to Azure DevOps Services (cloud) or Server 2022+. ### New pac CLI version needed 1. Update version in `nuget.json` 2. The `pac` version is also referenced in `package.json` for documentation — keep in sync 3. Run `gulp restore` (or `npm run prepare-pack`) to download the new binary to `bin/` 4. Test with `bin/pac/pac.exe --version` ### Vulnerability in a bundled dependency **Cause:** Packages in `bundleDependencies` have `inBundle: true` in lock file. `npm audit fix` and `overrides` cannot touch them. **Fix procedure:** ```bash # 1. Get safe version metadata npm view @ dist.tarball dist.integrity --json # 2. Find the exact key in package-lock.json # (may be nested: node_modules/foo/node_modules/bar) node -e "const l=require('./package-lock.json'); console.log(Object.keys(l.packages).filter(k=>k.includes('')))" # 3. Patch the lock file entry: update version, resolved URL, integrity hash # 4. Apply npm install npm audit # verify resolved ``` --- ## Task Pipeline Dependency Order ``` tool-installer ← MUST run first (sets POWERPLATFORMTOOLS_PACCLIPATH) │ ├── create-environment → sets BuildTools.EnvironmentUrl pipeline variable │ │ │ ├── import-solution │ ├── export-solution │ ├── pack-solution / unpack-solution │ ├── publish-customizations │ ├── set-solution-version │ └── deploy-package │ ├── backup-environment → restore-environment ├── copy-environment └── delete-environment ``` --- ## Build Output Structure ``` npm run build └── webpack → dist/tasks//-v2/index.js (32 bundles) npm run prepare-pack ├── webpack (above) └── gulp restore → bin/pac/pac.exe + bin/pac_linux/pac npm run pack └── gulp pack ├── npm pack → out/npm-package/ ├── stages to out/staging/ (dist/ + bin/ + extension/) ├── injects GUIDs from task-metadata.json (4 stages) └── tfx-cli → out/packages/ ├── PowerPlatform-BuildTools--LIVE.vsix ├── PowerPlatform-BuildTools--BETA.vsix ├── PowerPlatform-BuildTools--DEV.vsix └── PowerPlatform-BuildTools--EXPERIMENTAL.vsix ```