Skip to content

fix(bundler): accept .zip archives produced by apm pack (apm 0.20+)#47

Open
danielmeppiel wants to merge 1 commit into
mainfrom
fix/accept-zip-archives
Open

fix(bundler): accept .zip archives produced by apm pack (apm 0.20+)#47
danielmeppiel wants to merge 1 commit into
mainfrom
fix/accept-zip-archives

Conversation

@danielmeppiel

Copy link
Copy Markdown
Collaborator

TL;DR

apm 0.20 made apm pack --archive emit a .zip archive by default (microsoft/apm#1720). The action's bundle-detection paths still assumed .tar.gz, so against apm 0.20+ they silently broke. This PR makes detection accept both .zip and .tar.gz without pinning --archive-format, keeping the action compatible across the full apm-version range it supports.

Problem (WHY)

Observed in the microsoft/apm v0.20.0 release run 27394583646 -- the GH-AW Compatibility job failed with apm pack produced no bundle. apm pack itself succeeded and produced inline-workflow-1.0.0.zip; the action then could not find it.

Three independent surfaces in this repo still assumed .tar.gz:

Surface Symptom on apm 0.20+
findBundleOrNull (src/bundler.ts) filtered entries.endsWith('.tar.gz') -> 0 matches for the .zip -> returned null -> "apm pack produced no bundle" (the failing job)
detectBundleFormat (src/bundler.ts) listed contents with tar tzf, which cannot read a .zip on GNU tar (Linux runners) -> threw before the format gate
parseBundleListFile (src/multibundle.ts) hard-rejected any bundles-file entry not ending in .tar.gz

Approach (WHAT)

Accept both extensions on the detection side; do NOT pin --archive-format on the pack call. The action installs a configurable apm version (apm-version, default 0.14.0, or latest). Pinning --archive-format tar.gz would break older CLIs that do not know the flag. The version-robust fix is to recognize whatever extension apm pack emits.

Implementation (HOW)

  • New listArchiveEntries(bundlePath) helper -- format-aware table-of-contents: unzip -Z1 for .zip (zipinfo short listing, one entry per line), tar tzf for .tar.gz. Both GitHub-hosted Linux and macOS runners ship unzip, and its output shape (wrapper/apm.lock.yaml) matches tar tzf, so the existing marker regexes are unchanged.
  • detectBundleFormat now delegates to listArchiveEntries.
  • findBundleOrNull matches a module-level ARCHIVE_EXTENSIONS = ['.zip', '.tar.gz']; the multiple-archives error generalized to "Multiple bundle archives found...".
  • parseBundleListFile accepts .zip or .tar.gz entries.
  • action.yml input docs (pack, archive, bundles-file) updated to reflect .zip-by-default on apm 0.20+.

Trade-offs / deferred

  • The extractBundle tar-fallback (tar xzf) is reached only when apm is unavailable -- a near-dead path, since the primary apm unpack handles .zip natively. unzip has no --strip-components/exclude equivalent, and the flatten+exclude logic needs its own surface and tests, so zip-aware fallback extraction is deferred with an in-code NOTE. detectBundleFormat (the unconditional gate that runs before the apm-available check) and findBundleOrNull (the failing job) are fixed because they break in normal operation.
  • Bumping the default apm-version to a .zip-emitting release is intentionally out of scope -- the pinned default protects downstream workflows by design.

Validation evidence

  • npm run typecheck -- clean
  • npm run lint (eslint) -- clean
  • npm test -- 186 passed (was 182; +4 new regression traps)
  • npm run build (ncc) -- dist/ rebuilt and committed (CI enforces dist freshness)
  • Mutation-break gate: reverted each guard (ARCHIVE_EXTENSIONS to tar-only; isZip to false; multibundle to tar-only) and confirmed exactly the 4 new tests fail, then restored.

How to test

npm ci && npm run typecheck && npm run lint && npm test && npm run build

Or end-to-end: run this action with pack: 'true' and apm-version: '0.20.0' (or latest) and confirm bundle-path resolves to the produced .zip.

apm 0.20 changed `apm pack --archive` to emit a `.zip` archive by
default (microsoft/apm#1720). The action's bundle-detection paths still
assumed `.tar.gz`, so against apm 0.20+ they silently failed:

- `findBundleOrNull` filtered `entries.endsWith('.tar.gz')`, found zero
  matches for the produced `.zip`, returned null, and the pack step
  errored "apm pack produced no bundle" (the GH-AW Compatibility job in
  microsoft/apm release run 27394583646).
- `detectBundleFormat` listed contents with `tar tzf`, which cannot read
  a `.zip` on GNU tar (Linux runners), failing before the format gate.
- The `bundles-file` (multibundle restore) parser hard-rejected any
  entry not ending in `.tar.gz`.

Fix: accept BOTH `.zip` and `.tar.gz` on the detection side rather than
pinning `--archive-format` on the pack call. The action supports a range
of apm versions via `apm-version`; pinning the flag would break older
CLIs that do not know it. A new `listArchiveEntries` helper is
format-aware (`unzip -Z1` for `.zip`, `tar tzf` for `.tar.gz`); both
runners ship `unzip` and its short listing matches the existing marker
regexes.

Scope note: the `extractBundle` tar-fallback (reached only when apm is
unavailable -- a near-dead path, since the primary `apm unpack` handles
.zip natively) still extracts `.tar.gz` only. `unzip` has no
`--strip-components`/exclude equivalent, so zip-aware fallback
extraction is deferred with an in-code NOTE.

Tests: +4 regression traps (single .zip found; .zip+.tar.gz ambiguity;
.zip format detection via unzip -Z1; multibundle .zip acceptance), each
proven by the mutation-break gate. dist/ rebuilt via ncc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 12, 2026 05:35
@danielmeppiel

Copy link
Copy Markdown
Collaborator Author

shepherd-driver advisory (terminal pass)

Driven to a terminal ready-to-merge state. No copilot-pull-request-reviewer[bot] review was posted, and apm-review-panel is microsoft/apm-scoped (not applicable to this TypeScript repo), so apm-action's own CI is the green gate here.

Folded in this run

  • (root-cause) findBundleOrNull + detectBundleFormat + parseBundleListFile now accept .zip as well as .tar.gz; action.yml docs updated. This is the complete in-scope fix for the apm 0.20 .zip-by-default break.

Deferred (out-of-scope follow-up)

  • extractBundle tar-fallback stays .tar.gz-only -- scope boundary: it is reached only when apm is unavailable (the primary apm unpack path handles .zip natively), and zip-aware fallback extraction needs its own surface/tests (no unzip --strip-components equivalent). Flagged with an in-code NOTE; suggested as a separate follow-up.

Regression-trap evidence (mutation-break gate)

  • runPackStep > finds a single .zip archive -- reverted ARCHIVE_EXTENSIONS to ['.tar.gz']; test FAILED as expected; restored.
  • runPackStep > throws when a .zip and a .tar.gz both linger -- same guard; FAILED as expected; restored.
  • extractBundle > detects format of a .zip bundle via unzip -Z1 -- reverted isZip to false; test FAILED as expected; restored.
  • parseBundleListFile > accepts .zip entries -- reverted multibundle gate to .tar.gz-only; test FAILED as expected; restored.

Gate (substitutes the ruff lint contract for this repo)

npm run typecheck, npm run lint (eslint), npm test (186 passed; +4 new), and npm run build (ncc; dist/ rebuilt + committed) all clean.

CI

All required checks observed green on head 45300bc (build, test-pack apm/plugin, restore jobs, CodeQL, CLA). 0 CI fix iterations needed.

Mergeability status

PR head SHA iters folds defers Copilot rounds CI mergeable mergeStateStatus notes
#47 45300bc 1 1 1 0 green MERGEABLE BLOCKED BLOCKED = branch protection requiring maintainer approval; no merge conflict.

Convergence

1 outer iteration; 0 Copilot rounds. Ready for maintainer review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the action’s bundle detection logic to remain compatible with apm pack --archive output across the supported apm-version range, specifically handling the apm 0.20+ default switch from .tar.gz to .zip archives.

Changes:

  • Add format-aware archive table-of-contents listing (unzip -Z1 for .zip, tar tzf for .tar.gz) and use it for detectBundleFormat.
  • Generalize bundle discovery to accept both .zip and .tar.gz when locating packed archives.
  • Update multibundle parsing, tests, and input documentation to recognize .zip archives.
Show a summary per file
File Description
src/bundler.ts Adds .zip-aware archive entry listing and broadens archive discovery to .zip/.tar.gz.
src/multibundle.ts Allows .zip entries in bundles-file parsing (previously .tar.gz only).
src/tests/bundler.test.ts Adds regression tests for .zip format detection and archive selection.
src/tests/multibundle.test.ts Adds tests ensuring .zip entries are accepted and non-archives rejected.
action.yml Updates input docs to reflect .zip default behavior on apm 0.20+.
dist/index.js Rebuilt compiled action output reflecting the updated bundler behavior.
dist/bundler.d.ts Updates typings/docs for the new archive listing approach.
dist/970.index.js Rebuilt compiled chunk reflecting multibundle parsing updates.
dist/multibundle.d.ts Updates typings/docs for .zip/.tar.gz acceptance.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 5/9 changed files
  • Comments generated: 2

Comment thread src/bundler.ts
Comment on lines +62 to +66
async function listArchiveEntries(bundlePath: string): Promise<string[]> {
const isZip = bundlePath.endsWith('.zip');
const cmd = isZip ? 'unzip' : 'tar';
const args = isZip ? ['-Z1', bundlePath] : ['tzf', bundlePath];
const list = await exec.getExecOutput(cmd, args, {
Comment thread src/bundler.ts
Comment on lines +395 to +397
const archives = entries
.filter(e => ARCHIVE_EXTENSIONS.some(ext => e.endsWith(ext)))
.sort();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants