Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .apm/instructions/architecture.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ instead of re-deriving the answer locally.
| Install success / failure outcome | the canonical install-outcome path |
| Neutral hook shape -> per-target native | the neutral hook IR + per-target integrators |
| File-level deploy / sync / cleanup | BaseIntegrator (see integrators.instructions.md) |
| Windows stable executable path | install.ps1 ($currentDir / $currentExe) |

If you are about to compute one of these locally, stop and call the
owner. If the owner is missing a case you need, EXTEND the owner --
Expand Down
1 change: 1 addition & 0 deletions .github/instructions/architecture.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ instead of re-deriving the answer locally.
| Install success / failure outcome | the canonical install-outcome path |
| Neutral hook shape -> per-target native | the neutral hook IR + per-target integrators |
| File-level deploy / sync / cleanup | BaseIntegrator (see integrators.instructions.md) |
| Windows stable executable path | install.ps1 ($currentDir / $currentExe) |

If you are about to compute one of these locally, stop and call the
owner. If the owner is missing a case you need, EXTEND the owner --
Expand Down
19 changes: 11 additions & 8 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ jobs:
- name: Install dependencies
run: uv sync --extra dev --extra build

# Keep the installer gate ahead of the broad unit suite so an unrelated
# platform test failure cannot hide installer-specific Windows evidence.
- name: Test install.ps1 end-to-end (Windows)
if: matrix.platform == 'windows'
shell: pwsh
env:
APM_E2E_TESTS: "1"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
uv run pytest tests/integration/test_windows_installer_launchers.py -q

# Unit tests run on every push for fast platform-regression signal.
# Smoke is intentionally NOT included here: it duplicates ci-integration.yml's
# merge-time smoke gate and burns a real codex binary download per platform
Expand Down Expand Up @@ -109,14 +120,6 @@ jobs:
run: |
uv run pwsh scripts/windows/build-binary.ps1

- name: Test install.ps1 end-to-end (Windows)
if: matrix.platform == 'windows'
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/windows/test-install-script.ps1

- name: Upload binary as workflow artifact
uses: actions/upload-artifact@v4
with:
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
#2059, #2114)
- `apm install --target intellij` now configures JetBrains Copilot MCP support
while routing package file primitives through the Copilot profile.
(by @sergio-sisternes-epam, closes #1957, #2041)
(by @sergio-sisternes-epam; closes #1957) (#2041)
- The Windows installer now exposes a version-stable `current\apm.exe` on
`PATH` (via a junction to the active release bundle) alongside the existing
`bin\apm.cmd` shim, so Git Bash and Python `subprocess.run(["apm", ...])`
resolve `apm` without `cmd.exe` PATHEXT expansion. (closes #2076) (#2094)

### Performance

Expand Down
4 changes: 2 additions & 2 deletions apm.lock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2772,7 +2772,7 @@ deployments:
owners:
- .
active_owner: .
content_hash: sha256:a24c5c8ce4f50855a10e4b6c0814ab8e6faa4bdb0aaa715f6cf7a5fe6f253121
content_hash: sha256:af293e32eb828c69989d873d9f0b87f8c8204819654bbb216afa9a70c03f13fe
- kind: project-relative
target: copilot
value: .github/instructions/changelog.instructions.md
Expand Down Expand Up @@ -3279,7 +3279,7 @@ local_deployed_file_hashes:
.github/agents/spec-tag-architect.agent.md: sha256:82907265c5e7cf1ac61ad96866fa7c5683b69c8f09b7a4c5f3cc241acc9568ca
.github/agents/supply-chain-security-expert.agent.md: sha256:8fb8cc426d6af17ba084a28b3f026c2b475b62e3ca63ed2f88b83bd823f877af
.github/agents/test-coverage-expert.agent.md: sha256:bc588d89530362469502bfbea788df892a9a0b00e630cd0f3926d3dfd2c2a9e2
.github/instructions/architecture.instructions.md: sha256:a24c5c8ce4f50855a10e4b6c0814ab8e6faa4bdb0aaa715f6cf7a5fe6f253121
.github/instructions/architecture.instructions.md: sha256:af293e32eb828c69989d873d9f0b87f8c8204819654bbb216afa9a70c03f13fe
.github/instructions/changelog.instructions.md: sha256:1e51ec4c74e847967962bd279dc4c6e582c5d3578490b3c28d5f3acd3e05f73e
.github/instructions/cicd.instructions.md: sha256:08d87b7d635761cb41deb8fc71d5d83f54678de463db484afb16d2d4f8713ecb
.github/instructions/cli.instructions.md: sha256:8e39e8d5047ce88575cb02f87c2bcede584dfef258bd86f7466c7badf136541a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ what the test family you want actually requires.
| --- | --- | --- |
| `requires_e2e_mode` | Opt-in for the heavyweight golden-scenario suite | `export APM_E2E_TESTS=1` |
| `requires_network_integration` | Opt-in for tests that hit live registries | `export APM_RUN_INTEGRATION_TESTS=1` |
| `requires_windows` | A Windows-only process or filesystem boundary | Run on Windows |
| `requires_inference` | Opt-in for tests that call inference APIs | `export APM_RUN_INFERENCE_TESTS=1` |
| `requires_github_token` | A token usable against `github.com` / GitHub Models | `export GITHUB_APM_PAT=...` (or `GITHUB_TOKEN`) |
| `requires_ado_pat` | Azure DevOps PAT for ADO host tests | `export ADO_APM_PAT=...` |
Expand Down
7 changes: 5 additions & 2 deletions docs/src/content/docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ irm https://aka.ms/apm-windows | iex

The installer automatically detects your platform (macOS/Linux/Windows, Intel/ARM), downloads the latest binary, and adds `apm` to your `PATH`.

On Windows, the installer adds both `current` and `bin` to `PATH`, with the stable `current\apm.exe` first. Bare `apm` calls therefore resolve the real executable in native shells, Git Bash, and process APIs such as Python `subprocess.run(["apm", ...])`; `bin\apm.cmd` remains available for compatibility.

### Installer options

**macOS / Linux (`install.sh`):**
Expand Down Expand Up @@ -57,7 +59,8 @@ $env:VERSION = "v1.2.3"; irm https://aka.ms/apm-windows | iex
# Saved script: pass -SkipChecksum only when the release has no .sha256 sidecar (not recommended).
# .\install.ps1 v1.2.3 -SkipChecksum

# Custom directory for apm.cmd (default: %LOCALAPPDATA%\Programs\apm\bin)
# Custom directory for apm.cmd (default: %LOCALAPPDATA%\Programs\apm\bin).
# The installer also adds the sibling current directory containing apm.exe.
$env:APM_INSTALL_DIR = "$env:LOCALAPPDATA\Programs\apm\bin"; irm https://aka.ms/apm-windows | iex

# Fork, enterprise host, or internal mirror (GITHUB_URL must be https://)
Expand Down Expand Up @@ -88,7 +91,7 @@ jobs:

| Variable | Default | Description |
|----------|---------|-------------|
| `APM_INSTALL_DIR` | `/usr/local/bin` (Unix) / `%LOCALAPPDATA%\Programs\apm\bin` (Windows) | Directory for the `apm` symlink / `apm.cmd` shim |
| `APM_INSTALL_DIR` | `/usr/local/bin` (Unix) / `%LOCALAPPDATA%\Programs\apm\bin` (Windows) | Directory for the Unix `apm` symlink or Windows `apm.cmd` shim. On Windows, the installer also adds the sibling `current` junction containing `apm.exe` to `PATH`. |
| `APM_LIB_DIR` | `$(dirname APM_INSTALL_DIR)/lib/apm` | *(Unix only)* Directory for the full binary bundle. Must end with `/apm` (for example, `/lib/apm`). The installer rejects shared directories (e.g. `$HOME/.local/share`) to prevent accidental data loss. |
| `GITHUB_URL` | `https://github.com` | Base GitHub URL (asset downloads **and** API host: `api.github.com` on github.com, `{GITHUB_URL}/api/v3` on GHES). Must be `https://` on Windows. |
| `APM_REPO` | `microsoft/apm` | Repository as `owner/name` |
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/reference/cli/self-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ apm config unset self-update.install-dir

## Where the new binary lands

The installer writes to the same location the install script uses -- by default `/usr/local/bin/apm` on macOS/Linux, and a `%LOCALAPPDATA%\Programs\apm\bin\apm.cmd` shim pointing at the staged Windows release binary. Existing configuration under `~/.apm/` and your project files are untouched.
The installer writes to the same location the install script uses -- by default `/usr/local/bin/apm` on macOS/Linux. On Windows, self-update advances the stable executable path described in [Installation](../../../getting-started/installation/). Existing configuration under `~/.apm/` and your project files are untouched.

## After update

Expand Down
50 changes: 49 additions & 1 deletion install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# $env:VERSION = 'v1.2.3'; irm https://aka.ms/apm-windows | iex
# .\install.ps1 v1.2.3
#
# Custom install location (directory that will contain apm.cmd):
# Custom install location (contains apm.cmd; sibling current contains apm.exe):
# $env:APM_INSTALL_DIR = "$env:LOCALAPPDATA\Programs\apm\bin"; irm ... | iex
#
# Fork or private mirror:
Expand Down Expand Up @@ -908,6 +908,48 @@ try {
Remove-Item -Recurse -Force $backupDir -ErrorAction SilentlyContinue
}

# Expose the complete onedir bundle through a version-stable junction.
# Putting this directory on PATH lets CreateProcess callers resolve the
# real apm.exe while keeping its sibling PyInstaller runtime files intact.
$currentDir = Join-Path $installRoot "current"
$currentExe = Join-Path $currentDir "apm.exe"
$newCurrentDir = "$currentDir.new-" + [System.Guid]::NewGuid().ToString("N")
$oldCurrentDir = $null
try {
New-Item -ItemType Junction -Path $newCurrentDir -Target $releaseDir | Out-Null

if (Test-Path $currentDir) {
$currentItem = Get-Item -Force $currentDir
if (($currentItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -eq 0) {
throw "Refusing to replace non-junction path. Move or remove it if safe, then rerun the installer."
}
$oldCurrentDir = "$currentDir.old-" + [System.Guid]::NewGuid().ToString("N")
Move-Item -Path $currentDir -Destination $oldCurrentDir -Force
}

Move-Item -Path $newCurrentDir -Destination $currentDir -Force
} catch {
Write-ErrorText "Failed to update stable executable path ${currentDir}: $_"
if (Test-Path $newCurrentDir) {
try { [System.IO.Directory]::Delete($newCurrentDir) } catch { Write-ErrorText "Could not remove temp junction ${newCurrentDir}: $_" }
}
if ($oldCurrentDir -and (Test-Path $oldCurrentDir) -and -not (Test-Path $currentDir)) {
Move-Item -Path $oldCurrentDir -Destination $currentDir -Force -ErrorAction SilentlyContinue
}
Write-ManualInstallHelp -GithubUrl $githubUrl -ApmRepo $apmRepo
exit 1
}
if ($oldCurrentDir -and (Test-Path $oldCurrentDir)) {
# Directory.Delete removes only the junction. Windows PowerShell 5.1
# Remove-Item prompts for its non-empty target in NonInteractive mode.
[System.IO.Directory]::Delete($oldCurrentDir)
}
if (-not (Test-Path $currentExe)) {
Write-ErrorText "Stable executable path is missing apm.exe: $currentExe"
Write-ManualInstallHelp -GithubUrl $githubUrl -ApmRepo $apmRepo
exit 1
}

$shimPath = Join-Path $binDir "apm.cmd"
# Prefer the literal %LOCALAPPDATA% token over the expanded profile path
# so cmd.exe resolves the shim target at runtime. This avoids "The
Expand Down Expand Up @@ -959,10 +1001,16 @@ try {
Set-Content -Path $shimPath -Value $shimContent -Encoding ASCII -NoNewline

Add-ToUserPath -PathEntry $binDir
# The onedir bundle must stay intact beside apm.exe. Add its stable
# junction so bare executable lookup finds apm.exe where PATHEXT is absent
# (CreateProcess, Git Bash). Add-ToUserPath prepends, so this entry
# precedes bin in PATH.
Add-ToUserPath -PathEntry $currentDir

Write-Host ""
Write-Success "APM $tagName installed successfully!"
Write-Info "Command shim: $shimPath"
Write-Info "Stable executable: $currentExe"
Write-Host ""
Write-Info "Quick start:"
Write-Host " apm init my-app # Create a new APM project"
Expand Down
4 changes: 2 additions & 2 deletions packages/apm-guide/.apm/skills/apm-usage/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ GITHUB_URL=https://github.corp.com VERSION=v1.2.3 sh install.sh

## Installer options (Windows PowerShell)

Uses the same variables as `install.sh` where applicable (`GITHUB_URL`, `APM_REPO`, `VERSION`, `APM_INSTALL_DIR`). See the full variable table, Actions example, and checksum rules in [installation.md](https://github.com/microsoft/apm/blob/main/docs/src/content/docs/getting-started/installation.md).
Uses the same variables as `install.sh` where applicable (`GITHUB_URL`, `APM_REPO`, `VERSION`, `APM_INSTALL_DIR`). See the full variable table, Actions example, checksum rules, and canonical Windows `PATH` layout in [installation.md](https://github.com/microsoft/apm/blob/main/docs/src/content/docs/getting-started/installation.md).

```powershell
# Pin a version (skips releases/latest API). Requires .sha256 on the release unless APM_SKIP_CHECKSUM=1 (emergency).
$env:VERSION = "v1.2.3"; irm https://aka.ms/apm-windows | iex

# Custom shim directory (directory that will contain apm.cmd)
# Custom shim directory (contains apm.cmd; sibling current contains apm.exe)
$env:APM_INSTALL_DIR = "$env:LOCALAPPDATA\Programs\apm\bin"; irm https://aka.ms/apm-windows | iex

$env:GITHUB_URL = "https://github.corp.com"
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ markers = [
"requires_ado_pat: requires ADO_APM_PAT",
"requires_ado_bearer: requires az CLI logged in + APM_TEST_ADO_BEARER=1",
"requires_network_integration: requires APM_RUN_INTEGRATION_TESTS=1",
"requires_windows: requires a Windows host",
"requires_apm_binary: requires built apm binary on PATH (or APM_BINARY_PATH)",
"requires_runtime_codex: requires codex runtime installed",
"requires_runtime_copilot: requires GitHub Copilot CLI runtime installed",
Expand Down
Loading
Loading