import { Callout } from "zudoku/ui/Callout";

# Project configuration (`albuild.json`)

ALbuild has **one** developer-facing configuration file: `albuild.json`. It is committed to the repository and gives developers control over the build without touching pipeline YAML.

There are two scopes, merged with the app folder winning:

1. **Workspace root**: `albuild.json` at the repo root: shared defaults for every app.
2. **App folder**: `albuild.json` next to an app's `app.json`: per-app overrides (useful in a multi-root workspace such as separate `app` and `test` apps).

<Callout type="info" title="Project config vs. machine config">
This is separate from the machine/runtime settings managed by [`Get-ALbuildConfig`](../powershell-module/core#get-albuildconfig) / [`Set-ALbuildConfig`](../powershell-module/core#set-albuildconfig) (artifact/package cache folders, retry, telemetry, licensing). Those are per-machine, live machine-wide under `C:\ProgramData\ALbuild\config.json`, and are **not** committed.
</Callout>

---

## Settings

| Key | Type | Default | Purpose |
| --- | --- | --- | --- |
| `country` | string | `w1` | Localisation of the BC target version. See [BC target selection](#bc-target-selection). |
| `artifactType` | string | `Sandbox` | `Sandbox` or `OnPrem`. See [BC target selection](#bc-target-selection). |
| `bcVersion` | string | _(empty)_ | Explicit BC version or prefix; empty = newest matching. See [BC target selection](#bc-target-selection). |
| `select` | string | `Latest` | Which build to pick when several match: `Latest` / `First` / `Closest` / `NextMinor` / `NextMajor`. See [`select` strategies](#select-strategies). |
| `testRunnerCodeunitId` | int | `0` | Test-runner codeunit id; `0` = the isolation default. See [Test settings](#test-settings). |
| `testSuite` | string | `DEFAULT` | Name of the test suite to run in the container. See [Test settings](#test-settings). |
| `disableTestIsolation` | bool | `false` | Run tests without test isolation (uses runner `130451`). See [Test settings](#test-settings). |
| `feeds` | array | `[]` | NuGet dependency feeds (URL strings or objects. See [Feeds](#feeds)). |
| `universalPackages` | array | `[]` | Azure DevOps Universal-feed dependency packages. See [Dependency sources](#dependency-sources). |
| `localPackages` | array | `['.dependencies']` | Repo folders of committed `.app` dependencies. See [Dependency sources](#dependency-sources). |
| `urlPackages` | array | `[]` | `.app` dependencies served from a direct download link. See [Dependency sources](#dependency-sources). |
| `excludeProjects` | array | `[]` | Folder names to skip in the multi-root build. See [Multi-root workspaces](#multi-root-workspaces). |
| `analyzers` | array | `[]` | AL code analyzers to run during compile. See [Code analysis](#code-analysis). |
| `ruleSet` | string | _(empty)_ | Path to a `.ruleset` applied to those analyzers. See [Code analysis](#code-analysis). |
| `licenseFile` | string | _(empty)_ | BC license for the build container, as a path or URL. See [Build container license](#build-container-license). |
| `translation` | object | _(defaults)_ | XLIFF sync and translation-quality settings. See [Translation](#translation). |

When `testRunnerCodeunitId` is `0`, the effective runner is `130450` (test isolation) or, when `disableTestIsolation` is `true`, `130451`.

---

## BC target selection

`country`, `artifactType`, `bcVersion` and `select` together decide **which Business Central build** the app is compiled, published and tested against. They are read by the **Get BC Artifact** task / [`Find-BcArtifactUrl`](../powershell-module/containers#find-bcartifacturl) (an explicit task input always overrides the config value).

- **`country`** — the localisation of the target build. `w1` is the base (worldwide) build; a country code such as `de`, `fr`, `us`, `nl`, `ch`, … selects the localized build, which carries that country's regulatory objects (e.g. the German *Delivery Reminder* / *Liefermahnung*). Use the country your app ships to, or `w1` for a country-neutral app.

- **`artifactType`** — which artifact family to download:

  | Value | Meaning |
  | --- | --- |
  | `Sandbox` | The cloud/**sandbox** artifact (the default). Matches the SaaS platform; use it for AppSource apps and cloud targets. |
  | `OnPrem` | The **on-premises** artifact. Use it when the customer runs BC on-prem (a fixed released build) and for a localized on-prem target — the on-prem artifact carries the full country-specific first-party set. |

- **`bcVersion`** — pins the build. Accepts a **full version** (`26.3.36158.36321`), a **prefix** (`26`, `26.3`), or is left **empty** to take the newest build that matches `country`/`artifactType`. Combine with `select` to control how a prefix (several matching builds) is narrowed to one.

### `select` strategies

When more than one build matches `country`/`artifactType`/`bcVersion`, `select` decides which one wins:

| Value | Picks |
| --- | --- |
| `Latest` (default) | The **newest** matching build. The normal choice — always builds against the most recent patch. |
| `First` | The **oldest** matching build. Useful to test against the minimum build a `bcVersion` prefix still covers. |
| `Closest` | The lowest build **≥ `bcVersion`**, otherwise the newest. `bcVersion` must be a full version (`1.2.3.4`). Use it to land on an exact target build (or the next available one) — e.g. to match a customer's installed build. |
| `NextMinor` | The next **minor** *insider* (pre-release) build. Requires Microsoft Insider access / EULA acceptance; for early compatibility testing only, not for release builds. |
| `NextMajor` | The next **major** *insider* (pre-release) build. Same insider prerequisite as `NextMinor`. |

<Callout type="info" title="Example: on-prem, localized, pinned to one build">
A German on-premises target fixed to one released version:

```json
{ "country": "de", "artifactType": "OnPrem", "bcVersion": "26.3.36158.36321", "select": "Latest" }
```
</Callout>

---

## Test settings

`testRunnerCodeunitId`, `testSuite` and `disableTestIsolation` are read by the **Run AL Tests** task / [`Invoke-BcContainerTest`](../powershell-module/apps#invoke-bccontainertest), resolved per app.

- **`testRunnerCodeunitId`** — the id of the test-runner codeunit to drive the tests with. `0` (the default) means *use the isolation default*: `130450` when tests run **with** test isolation, or `130451` when `disableTestIsolation` is `true`. Set a specific id only to use a custom runner.
- **`disableTestIsolation`** — when `true`, tests run **without** database test isolation (each test's data changes are not rolled back). Defaults to `false` (isolation on, runner `130450`); `true` switches the default runner to `130451`.
- **`testSuite`** — the name of the test suite to execute inside the container. Defaults to `DEFAULT`.

---

## Dependency sources

Dependencies are resolved from four kinds of source and all land in each project's `.alpackages` (used for compilation and then installed into the build container in dependency order):

1. **NuGet feeds** (`feeds`), resolved transitively by app id (Continia, 365bd, SIMOVA/nuget.org, DYCE via a private feed with an `apiKeyVariable` key, …). See [Feeds](#feeds).
2. **Universal packages** (`universalPackages`), named packages from an Azure DevOps Universal feed (e.g. apps you publish to a `Products` feed). The build downloads them with the pipeline's `System.AccessToken`; `organization` defaults to the collection URI and `project` to the running project.
3. **Local packages** (`localPackages`), repository folders of committed `.app` files, for ISVs with no public feed. Defaults to `.dependencies`; paths are relative to the repo root.
4. **URL packages** (`urlPackages`), `.app` files served from a direct download link (an ISV that publishes a link rather than a feed, e.g. bms GmbH's *b-jira*). Each entry is a URL string or `{ url, tokenEnv?, header? }`.

**Source precedence: `localPackages`/`urlPackages` → feeds → `universalPackages`.** An app provided locally or by URL is authoritative — it is pinned and used even when a feed offers a newer version. Feeds take precedence over universal packages (a universal copy is only a fallback for apps no feed provides). A committed **encrypted runtime** `.app` whose app id ALbuild cannot parse on the host is pinned by its `{Publisher}_{Name}_{Version}.app` file name and installed from `localPackages`; it is **also copied into `.alpackages`**, where both the host and the container compiler resolve against it, so no separate symbol package is needed. See [source precedence](dependency-resolver#source-precedence) for the full rules.

```jsonc
{
  "feeds": [
    "https://pkgs.domain.com/nuget/index.json",
    { "url": "https://pkgs.dev.azure.com/isv/_packaging/isv-feed/nuget/v3/index.json", "apiKeyVariable": "ISV_FEED_TOKEN" }
  ],
  "universalPackages": [
    { "feed": "Products", "name": "365_business_api", "version": "*" }
  ],
  "localPackages": [ ".dependencies" ]
}
```

<Callout type="caution" title="Real apps vs. symbol-only">
A dependency must come from a source that ships the **real app** (not symbol-only) if the build installs it to run tests; symbol-only packages can be compiled against but are skipped at install.
</Callout>

---

## Multi-root workspaces

A repository may contain several AL projects side by side (e.g. `app`, `test`, `migration`). With an empty `projectFolder` the pipeline builds **all** of them, in **dependency order** (a project is compiled, published and installed after the in-repo projects it depends on, so `app` before its `test`/`migration`), sharing one container. Each project's compiled `.app` is copied into its dependents' `.alpackages` so they compile against it.

Order is derived from each `app.json`'s `id`/`dependencies` (see [`Get-BcProjectBuildOrder`](../powershell-module/core#get-bcprojectbuildorder)). To skip a project, list its **folder name** in `excludeProjects` (repo-root `albuild.json`) and/or the `excludeProjects` pipeline parameter (comma-separated); the two are merged.

```json
{
  "country": "w1",
  "excludeProjects": [ "migration" ]
}
```

---

## Feeds

Declare the NuGet feeds your dependencies come from once in `albuild.json` instead of repeating [`Register-BcFeed`](../powershell-module/feeds#register-bcfeed) in every pipeline. [`Resolve-BcDependencies`](../powershell-module/feeds#resolve-bcdependencies) reads them automatically (and `Register-BcFeed -FromProjectConfig <folder>` registers them explicitly if you prefer).

Each entry is either a **URL string** or an **object**. Only `url` is required:

| Field | Required | Default | Purpose |
| --- | --- | --- | --- |
| `url` | yes |  | The NuGet v3 service index (`.../index.json`). |
| `name` | no | derived from the URL | A unique feed name. |
| `kind` | no | `apps` | `apps` / `runtime` / `symbols`. Rarely needed. See below. |
| `idScheme` | no | `{publisher}.{name}.{id}` | Package-id template. Rarely needed. See below. |
| `apiKey` | no | _(none)_ | Access key / PAT for a private feed, inline; supports a `${env:NAME}` placeholder. |
| `apiKeyVariable` | no | _(none)_ | Name of an environment variable holding the access key (keeps it out of the file). |

> `token` / `tokenEnv` are still accepted as aliases for `apiKey` / `apiKeyVariable`.

<Callout type="caution" title="Keep secrets out of the file">
Prefer naming an environment variable with `apiKeyVariable` (or a `${env:NAME}` placeholder in `apiKey`). An inline `apiKey` is supported for convenience but commits the secret, so don't use it in a tracked `albuild.json`. If both are given, the inline `apiKey` wins.
</Callout>

**`kind` and `idScheme` are almost never needed.** The resolver auto-detects runtime/indirect ("metapackage") packages from their NuGet metadata and locates packages by app id via the feed's search service, so it copes with feeds whose ids carry a `.runtime.` infix or whose publisher/name segments differ from your `app.json`. Set them only to override an edge case.

```jsonc
{
  "country": "de",
  "feeds": [
    "https://pkgs.dev.azure.com/isv-repository/PublicFeeds/_packaging/AppSourceApps/nuget/v3/index.json",
    "https://pkgs.dev.azure.com/365businessdev/Public/_packaging/MSDyn365BCRuntimeApps/nuget/v3/index.json",
    "https://api.nuget.org/v3/index.json",
    { "name": "ISV",
      "url": "https://pkgs.dev.azure.com/isv/_packaging/isv-feed/nuget/v3/index.json",
      "apiKeyVariable": "ISV_FEED_TOKEN" }
  ]
}
```

With the above, the whole dependency step is just:

```powershell
Resolve-BcDependencies -ProjectFolder ./MyApp -TargetPlatform '27.1.41698.51164'
```

Explicitly passed `-Feeds` (or feeds already registered with `Register-BcFeed`) still take part; the config feeds are added, not a replacement.

---

## Code analysis

`analyzers` names the AL analyzers to run during compilation, and `ruleSet` points at a `.ruleset`
file applied to them. Setting them in `albuild.json` means the pipeline and your editor agree on
what "clean" means, without repeating the list in YAML.

```json
{
  "analyzers": [ "CodeCop", "UICop", "AppSourceCop" ],
  "ruleSet": "build/albuild.ruleset"
}
```

Analyzers are given by token or short name (`CodeCop`, `UICop`, `AppSourceCop`,
`PerTenantExtensionCop`, with or without the `${...}` wrapper), or as an explicit path to an
analyzer DLL. An empty list means no analyzers.

**Precedence**, highest first: an explicit *Compile AL App* task input, then `albuild.json`, then
the project's `.vscode/settings.json` (`al.codeAnalyzers` / `al.ruleSetPath`). So an existing VS
Code setup keeps working, and `albuild.json` overrides it when you want the build to be stricter.

---

## Build container license

The artifact's built-in Cronus demo license is enough for most builds, but it is restrictive in
ways that break real test suites, most commonly its posting-date interval
(`??11*|??12*|??01*|??02*`), which fails any date-dependent test outside those months. Point
`licenseFile` at your own license to replace it. The value is passed to
[`New-BcContainer -LicenseFile`](../powershell-module/containers#new-bccontainer) and may be a path
or a URL.

<Callout type="caution" title="Keep the license out of source control">
`albuild.json` is committed, so do not put a license path that only exists on your machine, and
never commit the license itself. Use a `${env:NAME}` placeholder, resolved exactly like feed
tokens:

```json
{ "licenseFile": "${env:BC_LICENSE_URL}" }
```
</Callout>

---

## Translation

The optional `translation` block controls XLIFF synchronisation and translation quality. Omit it
and ALbuild uses sensible defaults: the target languages are taken from the project's existing
`*.??-??.xlf` files, translation memory draws on the project itself plus the BC base application,
and the pipeline mode is `write` locally but `check` in CI (`ALBUILD_CI=1`), so a build never
silently rewrites translations on the agent.

```jsonc
{
  "translation": {
    "targetLanguages": [ "de-DE", "fr-FR" ],
    "pipelineMode": "check",          // write | check | off
    "failOnMissing": true,
    "memory": {
      "enabled": true,
      "contextAware": true,
      "sources": [
        { "kind": "self" },
        { "kind": "bc-base", "language": "de-DE" },
        { "kind": "xlf", "path": "../shared/translations", "priority": 10 }
      ]
    }
  }
}
```

| Field | Purpose |
| --- | --- |
| `targetLanguages` | Languages to maintain. Default: the languages of the existing `*.??-??.xlf` files. |
| `pipelineMode` | `write` updates the XLIFF files, `check` fails on drift without writing, `off` skips the step. |
| `failOnMissing` | Fail when a translation unit has no translation. |
| `memory.enabled` | Use translation memory to pre-fill units. |
| `memory.contextAware` | Weight memory hits by the unit's context, not just its source text. |
| `memory.sources` | Ordered sources. `kind` is `self` (this project), `bc-base` (the Business Central base application) or `xlf` (a `path` you provide); `priority` breaks ties. |

---

## Who reads it

- **Get BC Artifact** task / [`Find-BcArtifactUrl`](../powershell-module/containers#find-bcartifacturl), `country`, `artifactType`, `bcVersion`, `select` (explicit task inputs still win).
- **Run AL Tests** task / [`Invoke-BcContainerTest`](../powershell-module/apps#invoke-bccontainertest), `testRunnerCodeunitId`, `testSuite`, `disableTestIsolation`, resolved per app.
- **Compile AL App** task / [`Invoke-BcCompiler`](../powershell-module/apps#invoke-bccompiler), `analyzers`, `ruleSet`.
- **Create BC Container** task / [`New-BcContainer`](../powershell-module/containers#new-bccontainer), `licenseFile`.
- **Resolve Dependencies** task / [`Resolve-BcDependencies`](../powershell-module/feeds#resolve-bcdependencies), `feeds`, `universalPackages`, `localPackages`, `urlPackages`.
- **The translation commands**, the `translation` block.

In code, [`Get-ALbuildProjectConfig`](../powershell-module/core#get-albuildprojectconfig) `-AppFolder <app> -WorkspaceRoot <repo-root>` returns the merged configuration.

---

## Deprecated `pipeline.config`

The ALbuild V1 `pipeline.config` file is still read **as a fallback** when no `albuild.json` is present in a folder. Its `alTestRunnerId` maps to `testRunnerCodeunitId`. A deprecation warning is logged, rename the file to `albuild.json`.
