Skip to content

Commit 5b8ebc6

Browse files
nadav-yclaudedanielmeppielCopilot
authored
feat(pack): switch --archive to .zip and add --archive-format zip|tar.gz (#1720)
* feat(pack): switch --archive output from tar.gz to zip Replace tarfile/gzip with zipfile (ZIP_DEFLATED) in both pack_bundle (apm format) and export_plugin_bundle (plugin format). The unpacker gains .zip support as the primary path; .tar.gz extraction is kept for backward compatibility with existing bundles. Aligns apm pack --archive with apm publish, which switched to .zip in #1695, making the whole toolchain consistent on a single archive format. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(pack): add --archive-format zip|tar.gz option (default zip) Adds --archive-format [zip|tar.gz] to apm pack --archive so callers can opt into .tar.gz output. Default remains zip. Threaded through BuildOptions → pack_bundle → export_plugin_bundle (both apm and plugin bundle formats). * fix(pack): fold zip archive review followups Co-authored-by: nadav-y <nadav-y@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: danielmeppiel <danielmeppiel@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 935389b commit 5b8ebc6

38 files changed

Lines changed: 958 additions & 278 deletions

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2929
offline, or hosted catalogs without publishing a GitHub repo (closes
3030
#676). (#1739)
3131
- Enterprise bootstrap mirror mode (`APM_RELEASE_METADATA_URL`, `APM_RELEASE_BASE_URL`, `APM_INSTALLER_BASE_URL`, `APM_PYPI_INDEX_URL`, `APM_NO_DIRECT_FALLBACK`) routes `install.sh`, `install.ps1`, and `apm self-update` through internal mirrors with fail-closed public fallback; closes #1680. (#1733)
32+
- `apm pack --archive-format [zip|tar.gz]` escape hatch (default `zip`) lets
33+
CI pipelines that depended on the previous `.tar.gz` default opt back in without
34+
changing the project default. Passing `--archive-format` without `--archive` is
35+
now a `UsageError`. (#1720)
36+
37+
### Changed
38+
39+
- **BREAKING:** `apm pack --archive` now produces `.zip` by default instead of `.tar.gz`, matching the format produced by `apm publish` and expected by Claude Code and plugin hosts while staying natively extractable on Windows without WSL or a tar binary. Note: ZIP archives are typically 30-130% larger than `.tar.gz` for text-heavy skill bundles due to per-file compression; use `--archive-format tar.gz` if archive size is a priority. (#1720)
3240

3341
### Fixed
3442

docs/src/content/docs/concepts/glossary.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ Source: `src/apm_cli/commands/audit.py`.
4949
### bundle
5050

5151
A local-install artifact produced by `apm pack`. Either a directory or a
52-
`.tar.gz` containing `plugin.json` at the root and (in current versions)
53-
an embedded `apm.lock.yaml` with per-file SHA-256 hashes. Installed via
54-
`apm install <path-or-tarball>`.
52+
`.zip` (or legacy `.tar.gz`) containing `plugin.json` at the root and (in
53+
current versions) an embedded `apm.lock.yaml` with per-file SHA-256 hashes.
54+
Installed via `apm install <path-or-archive>`.
5555

5656
NOT a package source repository. A bundle is the packed, hash-verified
5757
output of one; you ship bundles, you author packages.

docs/src/content/docs/consumer/deploy-a-bundle.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
---
22
title: Deploy a local bundle
3-
description: Install a plugin-format bundle from a directory or tarball without touching apm.yml.
3+
description: Install a plugin-format bundle from a directory or archive without touching apm.yml.
44
---
55

6-
You have a bundle on disk -- a directory or `.tar.gz` someone handed you, or
7-
the output of `apm pack --format plugin`. Drop it into a project with one
8-
command:
6+
You have a bundle on disk -- a directory or `.zip` someone handed you (or a
7+
legacy `.tar.gz`), or the output of `apm pack --format plugin`. Drop it into a
8+
project with one command:
99

1010
```bash
1111
apm install ./path/to/bundle
12-
apm install ./dist/my-pkg-1.0.0.tar.gz
12+
apm install ./dist/my-pkg-1.0.0.zip
1313
```
1414

1515
This is a sibling flow to [Install packages](../install-packages/). Instead
@@ -19,8 +19,8 @@ the install is imperative, like `dpkg -i` next to `apt install`.
1919

2020
## What counts as a bundle
2121

22-
A plugin-format bundle is a directory (or gzipped tarball of one) with a
23-
`plugin.json` at the root and primitive folders alongside it:
22+
A plugin-format bundle is a directory, zip archive, or legacy gzipped tarball
23+
with a `plugin.json` at the root and primitive folders alongside it:
2424

2525
```
2626
my-bundle/
@@ -44,16 +44,16 @@ warns and proceeds.
4444
## How the install works
4545

4646
```
47-
$ apm install ./dist/my-pkg-1.0.0.tar.gz --target copilot
48-
[>] Installing local bundle from ./dist/my-pkg-1.0.0.tar.gz
47+
$ apm install ./dist/my-pkg-1.0.0.zip --target copilot
48+
[>] Installing local bundle from ./dist/my-pkg-1.0.0.zip
4949
[+] Bundle integrity verified
5050
[+] Deployed 7 files to .github/
5151
```
5252

5353
Steps APM runs:
5454

5555
1. **Detect.** Path exists and contains `plugin.json` at the bundle root
56-
(tarballs are extracted to a temp directory first).
56+
(zip archives and legacy tarballs are extracted to a temp directory first).
5757
2. **Verify integrity.** Hash every file listed in `pack.bundle_files`;
5858
reject any symlink, hash mismatch, or unlisted file.
5959
3. **Deploy.** Map `agents/`, `skills/`, `commands/`, `hooks/` into the

docs/src/content/docs/consumer/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ You're here because you want to install someone else's APM packages and use them
1818
| You manage `apm.yml` and need lockfile / dependency commands | [Manage dependencies](./manage-dependencies/) |
1919
| You want APM to wire MCP servers (GitHub, Atlassian, ...) into your tools | [Install MCP servers](./install-mcp-servers/) |
2020
| You want APM to wire LSP servers into supported runtimes | [Install LSP servers](./install-lsp-servers/) |
21-
| You received a local `.tar.gz` bundle and need to install it | [Deploy a local bundle](./deploy-a-bundle/) |
21+
| You received a local `.zip` or `.tar.gz` bundle and need to install it | [Deploy a local bundle](./deploy-a-bundle/) |
2222
| You hit `Drift detected` after a `git pull` | [Drift and secure-by-default](./drift-and-secure-by-default/) |
2323
| Your org rolled out `apm-policy.yml` and your install is now blocked | [Governance on the consumer ramp](./governance-on-the-consumer-ramp/) |
2424

docs/src/content/docs/enterprise/security.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ A path must pass all three checks. Failure on any check prevents the file from b
215215

216216
### Local bundle install trust model
217217

218-
`apm install <bundle>` accepts a directory or `.tar.gz` produced by `apm pack`. Bundles are imperative (no policy / dependency-resolver / network) and target-agnostic; the consumer's project drives where files land. Trust boundaries:
218+
`apm install <bundle>` accepts a directory or `.zip` (or legacy `.tar.gz`) produced by `apm pack`. Bundles are imperative (no policy / dependency-resolver / network) and target-agnostic; the consumer's project drives where files land. Trust boundaries:
219219

220220
1. **`bundle_files` keys are untrusted.** They come from the bundle's own `apm.lock.yaml` and are validated for traversal sequences before any filesystem path is constructed; resolved destinations must remain within the deploy root. Unsafe entries are skipped with a warning.
221221
2. **`plugin.json` is bundle metadata, never deployed.** It is recognized case-insensitively and skipped in both the manifest-driven deploy loop and the lockfile-less fallback walk so case-folding filesystems (HFS+, NTFS) cannot smuggle a renamed file past the skip.

docs/src/content/docs/integrations/ci-cd.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ Use `apm pack` in CI to build a distributable bundle once, then consume it in do
177177

178178
### Pack in CI (build once)
179179

180-
`apm-action@v1` with `pack: true` emits an APM-format bundle (`--format apm --archive`) so downstream jobs can restore it via `tar xzf` or the action's restore mode.
180+
`apm-action@v1` with `pack: true` emits an APM-format bundle (`--format apm --archive`) so downstream jobs can restore it via `unzip` or the action's restore mode.
181181

182182
```yaml
183183
- uses: microsoft/apm-action@v1
@@ -186,7 +186,7 @@ Use `apm pack` in CI to build a distributable bundle once, then consume it in do
186186
- uses: actions/upload-artifact@v4
187187
with:
188188
name: agent-config
189-
path: build/*.tar.gz
189+
path: build/*.zip
190190
```
191191

192192
### Pack as standalone plugin
@@ -204,19 +204,25 @@ Use `apm pack` in CI to build a distributable bundle once, then consume it in do
204204

205205
The APM bundle layout below assumes the upstream job ran `apm-action@v1` with `pack: true` (or `apm pack --format apm --archive`). Plugin-format output cannot be restored this way because it does not carry the install-time directory tree.
206206

207+
:::caution[Migrating from the previous `.tar.gz` default?]
208+
`apm pack --archive` now writes `.zip`. If a downstream job still expects
209+
`build/*.tar.gz`, add `--archive-format tar.gz` to the pack step instead of
210+
switching the restore step to `unzip`.
211+
:::
212+
207213
```yaml
208214
- uses: actions/download-artifact@v4
209215
with:
210216
name: agent-config
211-
- run: tar xzf build/*.tar.gz -C ./
217+
- run: unzip -o build/*.zip -d ./
212218
```
213219

214220
Or use the apm-action restore mode to unpack a bundle directly:
215221

216222
```yaml
217223
- uses: microsoft/apm-action@v1
218224
with:
219-
bundle: ./agent-config.tar.gz
225+
bundle: ./agent-config.zip
220226
```
221227

222228
See the [Pack a bundle guide](../../producer/pack-a-bundle/) for the full workflow.

docs/src/content/docs/integrations/gh-aw.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ The repo needs an `apm.yml` with dependencies and `apm.lock.yaml` for reproducib
123123

124124
For sandboxed environments where network access is restricted during workflow execution, use pre-built APM bundles:
125125

126-
1. Run `apm pack --format apm --archive` in your CI pipeline to produce a self-contained APM bundle (the format restorable via `tar xzf` or `apm-action` restore mode).
126+
1. Run `apm pack --format apm --archive` in your CI pipeline to produce a self-contained APM bundle (`.zip` by default; restorable via `unzip` or `apm-action` restore mode).
127127
2. Distribute the bundle as a workflow artifact or commit it to the repository.
128128
3. Reference the bundled primitives directly from `.github/agents/` in your workflow.
129129

docs/src/content/docs/producer/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Five steps, in order. Each links to the page that owns it:
1818
| 1 | [Author primitives](./author-primitives/) | Skills, prompts, instructions, agents, hooks, commands, MCP under `.apm/` |
1919
| 2 | [Compile your package](./compile/) | `apm compile` writes deterministic per-target output you can git-diff |
2020
| 3 | [Preview and validate](./preview-and-validate/) | `apm preview` and `apm view` confirm what consumers will receive |
21-
| 4 | [Pack a bundle](./pack-a-bundle/) | `apm pack` produces a `.tar.gz` you can ship offline or to a marketplace |
21+
| 4 | [Pack a bundle](./pack-a-bundle/) | `apm pack` produces a `.zip` you can ship offline or to a marketplace |
2222
| 5 | [Publish to a marketplace](./publish-to-a-marketplace/) | Others install your package with `apm install <owner>/<repo>` |
2323

2424
You don't need a marketplace to start. Step 4 is enough for internal teams; the marketplace step is for public discovery.

docs/src/content/docs/producer/pack-a-bundle.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ description: Build a plugin-format bundle from your .apm/ source so others can d
44
---
55

66
A bundle is the artifact you hand to a consumer when you do not want to publish
7-
to a registry. It is a directory (or `.tar.gz` of one) containing a
7+
to a registry. It is a directory (or archive -- `.zip` by default, `.tar.gz` via
8+
`--archive-format tar.gz`) containing a
89
`plugin.json`, your primitive folders, and an embedded `apm.lock.yaml` that
910
pins every file by SHA-256. Build it with one command from a project that has
1011
`.apm/` and `apm.yml`:
@@ -42,12 +43,18 @@ $ apm pack
4243
[i] Share with: apm install build/my-pkg
4344
```
4445

45-
Add `--archive` to get a single `.tar.gz` instead of a directory; use `-o` to
46-
change the output location (default `./build`).
46+
Add `--archive` to get a single archive (`.zip` by default; use `--archive-format tar.gz`
47+
for legacy CI pipelines) instead of a directory; use `-o` to change the output location
48+
(default `./build`).
49+
50+
:::tip[Windows-native handoff]
51+
ZIP archives are natively extractable on Windows -- no WSL, tar, or additional
52+
tooling required. That is why `.zip` is the default archive format.
53+
:::
4754

4855
```bash
4956
apm pack --archive -o ./dist
50-
# -> ./dist/my-pkg-<version>.tar.gz
57+
# -> ./dist/my-pkg-<version>.zip
5158
```
5259

5360
## The plugin.json contract
@@ -109,8 +116,8 @@ Three common ways to hand off a bundle:
109116
- **Directory + git.** Commit `build/<pkg>/` to a release branch or a separate
110117
artifacts repo. Consumers `git clone` and run `apm install ./build/<pkg>`.
111118
- **Archive + GitHub release.** `apm pack --archive` then upload the
112-
`.tar.gz` as a release asset. Consumers download and run
113-
`apm install ./<pkg>-<version>.tar.gz`.
119+
`.zip` as a release asset. Consumers download and run
120+
`apm install ./<pkg>-<version>.zip`.
114121
- **Marketplace entry.** If your project also has a `marketplace:` block in
115122
`apm.yml`, `apm pack` builds `marketplace.json` alongside the bundle. See
116123
[Publish to a marketplace](./publish-to-a-marketplace/).

docs/src/content/docs/producer/releasing-from-any-ci.md

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ VERSION="${VERSION:?VERSION must be set, e.g. v1.2.3}"
2121

2222
apm pack --check-versions --check-clean --json > pack-report.json
2323

24-
for f in build/*.tar.gz .claude-plugin/marketplace.json; do
24+
for f in build/*.zip .claude-plugin/marketplace.json; do
2525
[ -f "$f" ] || continue
2626
sha256sum "$f" > "${f}.sha256"
2727
done
2828

2929
gh release create "$VERSION" \
30-
build/*.tar.gz \
31-
build/*.tar.gz.sha256 \
30+
build/*.zip \
31+
build/*.zip.sha256 \
3232
.claude-plugin/marketplace.json \
3333
.claude-plugin/marketplace.json.sha256 \
3434
--title "$VERSION" \
@@ -85,23 +85,30 @@ you need to customise any step.
8585
> **Reference deployment.** [`DevExpGbb/zava-agent-config`](https://github.com/DevExpGbb/zava-agent-config)
8686
> runs this exact pipeline. The
8787
> [v6.1.2 release](https://github.com/DevExpGbb/zava-agent-config/releases/tag/v6.1.2)
88-
> attaches 7 per-plugin tarballs + their `.sha256` companions +
88+
> attaches 7 per-plugin bundles + their `.sha256` companions +
8989
> `marketplace-6.1.2.json` (15 assets total) via the workflow in
9090
> [`.github/workflows/release.yml`](https://github.com/DevExpGbb/zava-agent-config/blob/main/.github/workflows/release.yml).
9191
> APM `0.16.0` and apm-action `v1.9.1` or newer required.
9292

93+
:::caution[Migrating release workflows from `.tar.gz`?]
94+
The examples below assume the new `.zip` default from `apm pack --archive`.
95+
If your release job still uploads or hashes `build/*.tar.gz`, either update
96+
those globs to `.zip` or add `--archive-format tar.gz` to preserve the previous
97+
artifact format.
98+
:::
99+
93100
```yaml
94101
- uses: actions/setup-python@v5
95102
with: { python-version: "3.12" }
96103
- run: pip install apm-cli
97104
- run: |
98105
apm pack --check-versions --check-clean --json > pack-report.json
99-
for f in build/*.tar.gz .claude-plugin/marketplace.json; do
106+
for f in build/*.zip .claude-plugin/marketplace.json; do
100107
[ -f "$f" ] || continue
101108
sha256sum "$f" > "${f}.sha256"
102109
done
103110
gh release create "${GITHUB_REF_NAME}" \
104-
build/*.tar.gz build/*.tar.gz.sha256 \
111+
build/*.zip build/*.zip.sha256 \
105112
.claude-plugin/marketplace.json* \
106113
--title "${GITHUB_REF_NAME}" --notes-file CHANGELOG.md
107114
env:
@@ -120,13 +127,13 @@ release:
120127
- pip install apm-cli
121128
- apm pack --check-versions --check-clean --json > pack-report.json
122129
- |
123-
for f in build/*.tar.gz .claude-plugin/marketplace.json; do
130+
for f in build/*.zip .claude-plugin/marketplace.json; do
124131
[ -f "$f" ] || continue
125132
sha256sum "$f" > "${f}.sha256"
126133
done
127134
- |
128135
glab release create "$CI_COMMIT_TAG" \
129-
build/*.tar.gz build/*.tar.gz.sha256 \
136+
build/*.zip build/*.zip.sha256 \
130137
.claude-plugin/marketplace.json* \
131138
--notes-file CHANGELOG.md
132139
```
@@ -143,12 +150,12 @@ pipeline {
143150
sh '''
144151
pip install apm-cli
145152
apm pack --check-versions --check-clean --json > pack-report.json
146-
for f in build/*.tar.gz .claude-plugin/marketplace.json; do
153+
for f in build/*.zip .claude-plugin/marketplace.json; do
147154
[ -f "$f" ] || continue
148155
sha256sum "$f" > "${f}.sha256"
149156
done
150157
gh release create "${TAG_NAME}" \
151-
build/*.tar.gz build/*.tar.gz.sha256 \
158+
build/*.zip build/*.zip.sha256 \
152159
.claude-plugin/marketplace.json* \
153160
--notes-file CHANGELOG.md
154161
'''
@@ -171,13 +178,13 @@ steps:
171178
- script: pip install apm-cli
172179
- script: apm pack --check-versions --check-clean --json > pack-report.json
173180
- script: |
174-
for f in build/*.tar.gz .claude-plugin/marketplace.json; do
181+
for f in build/*.zip .claude-plugin/marketplace.json; do
175182
[ -f "$f" ] || continue
176183
sha256sum "$f" > "${f}.sha256"
177184
done
178185
- script: |
179186
gh release create "$(Build.SourceBranchName)" \
180-
build/*.tar.gz build/*.tar.gz.sha256 \
187+
build/*.zip build/*.zip.sha256 \
181188
.claude-plugin/marketplace.json* \
182189
--notes-file CHANGELOG.md
183190
env:

0 commit comments

Comments
 (0)