feat: bundle-format input + setup-only mode (#24)#31
Conversation
There was a problem hiding this comment.
Pull request overview
This PR keeps the action’s pack: true mode compatible with the existing restore pipeline by forcing apm pack to emit the legacy APM bundle layout even after upstream APM changes its default pack format.
Changes:
- Pin
apm packto--format apmin the bundling step to preserve legacy bundle output. - Update unit tests to assert the new CLI argument is included.
- Rebuild
dist/artifacts and update user-facing docs/metadata to clarify legacy bundle behavior.
Show a summary per file
| File | Description |
|---|---|
| src/bundler.ts | Adds --format apm to the apm pack invocation and documents why it’s pinned. |
| src/tests/bundler.test.ts | Extends assertions to confirm --format apm is passed. |
| dist/index.js | Compiled output reflecting the pinned --format apm args. |
| dist/bundler.d.ts | Updates generated typings/docs to match the new behavior. |
| action.yml | Clarifies that pack produces a legacy APM bundle layout (not Claude plugin format). |
| README.md | Adds a note explaining the pinned pack format and implications. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 4/6 changed files
- Comments generated: 0
APM Review Panel — verdict:
|
- action.yml: pack: description uses 'classic' (not 'legacy') to avoid signaling deprecation in the marketplace UI. - README.md: parallel rename + reassure longevity, point users to apm pack for plugin output, suggest pinning apm-version for full repro. - CHANGELOG.md: new file with [Unreleased] entry documenting the --format apm pin and the upstream coordination requirement. Per apm-review-panel verdict on PR #31. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two changes folded into one PR: 1. bundle-format input (default 'apm', opt-in 'plugin'). The upstream apm CLI is flipping its default 'apm pack' format from 'apm' to 'plugin' in the next consumer-facing release. Plugin bundles do not contain apm.lock.yaml, so 'apm unpack' (and therefore this action's restore path) cannot consume them. This action now pins bundle-format=apm in the pack call by default, so existing pack -> restore round-trips keep working regardless of the upstream default. bundle-format=plugin is a first-class opt-in for marketplace publishers. Single- and multi-bundle restore detect plugin-format archives via tar tzf and reject them with an actionable error that names the archive and points at the upstream tracking issue. Prevents silent corruption when a plugin bundle is fed into restore. 2. setup-only mode (closes #24). Mirrors actions/setup-node: install the APM CLI onto PATH and exit. No apm.yml read, no apm install, no primitives deployed. Mutually exclusive with pack/bundle/bundles-file. New apm-version and apm-path outputs let downstream steps branch on the resolved CLI. Installer refactored: explicit apm-version always installs the requested version into the tool cache (no PATH short-circuit), so the resolved version matches the requested version. apm-version 'latest' (the default) still reuses an APM already on PATH when available. Validated end-to-end against a locally built apm HEAD binary (0.11.0) across 10 scenarios: setup-only, mutex rejection, pack(apm) layout, pack(plugin) layout, restore(apm) round-trip, restore(plugin) rejection, invalid bundle-format rejection, and bundle-format set without pack rejection. CI updated with a matrix on bundle-format, a setup-only smoke test, and a plugin-restore-rejected job. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2c1f21c to
2953cb4
Compare
- Always populate apm-version and apm-path outputs (all modes), not just setup-only. CHANGELOG promised always-set; align code with promise. Resolve binaryPath via 'which apm' on PATH-reuse so the output is meaningful instead of empty. - Drop 'archive' from the setup-only mutex list. archive is a sub-option of pack mode; rejecting pack already covers it. Flagging archive separately surprised composite-action templates that emit archive: 'true' by default. Old check was also inverted (allowed 'true', rejected 'false'). - Drop maintainer 'Tracking:' line from plugin-restore-rejected error in bundler.ts. Tell the operator what to do, not what we plan to do. - Update apm-version/apm-path output descriptions in action.yml, README.md, and CHANGELOG.md to match new always-set semantics. Tests: 95/95 unit (added always-set apm-path assertions and which-probe failure tolerance test). Integration: 10/10 against apm HEAD binary; apm-path now populated in all modes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Review panel fixes applied (2b6d996)Adopted
Deferred (with rationale)
Validation
CI run pending on this push. |
…apshot The 'no install ran' check was a false positive on the apm-action repo because the repo itself ships .github/agents/, .github/aw/, and .github/workflows/ via checkout. Compare the .github subdirectory tree before and after invoking the action, and additionally assert that setup-only does not produce apm.lock.yaml (which apm install would). Also assert apm-path output is set (now always-set after 2b6d996). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Supply-chain (R1): apm-version: latest no longer silently reuses an
arbitrarily-old apm that happened to be on PATH. Resolve the latest
GitHub release tag first, then reuse PATH apm only if its version
matches latest. Otherwise install fresh. If the GitHub Releases API
is unreachable, fall back to PATH apm with an explicit warning so
operators know they may be running stale.
Supply-chain (N1): tighten detectBundleFormat plugin.json/apm.lock.yaml
detection to match only at the top-level wrapper depth. Avoids false
positives where a nested file inside a dependency payload happens to
be named plugin.json or apm.lock.yaml.
UX: rewrite plugin-restore error messages (single + multi-bundle paths)
to spell out concrete remediation -- including pointing out that
'apm unpack' itself rejects plugin tarballs, so the limitation is
upstream, not just an action constraint. Mention both the action input
('bundle-format: apm') and the upstream CLI flag ('apm pack --format
apm --archive') so users in either context know what to change.
Tests: 97/97 unit (added 'PATH equals latest', 'PATH older than
latest', 'GitHub unreachable' coverage). Integration: 10/10 against
apm HEAD binary.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The CHANGELOG.md added in PR #31 only carried an Unreleased section. This commit backfills the historical record by inventorying every merged PR per release tag (v1.0.0 through v1.5.1) and graduates the Unreleased section to a [1.6.0] heading dated 2026-05-02. v1.6.0 delivers the defensive 'bundle-format: apm' default plus the 'setup-only' mode (closes #24), unblocking apm-action consumers ahead of the upstream apm 0.12 release that flips the default 'apm pack' output to the plugin layout. Sources: - gh pr list (--state merged) cross-referenced with git tag dates - existing GitHub release notes for v1.4.2, v1.5.0, v1.5.1 - commit messages in each release window No code change. CHANGELOG-only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TL;DR
Microsoft/apm 0.11+ flips
apm packdefault fromapmtopluginformat. Plugin tarballs cannot be restored byapm unpack(noapm.lock.yaml), so any apm-action user packing today and restoring tomorrow withapm-version: latestwould silently break.This PR makes apm-action robust to the flip and adds the long-requested setup-only mode (#24).
bundle-format: apm | plugininput. Default isapm(the formatapm unpackunderstands). Pack is always invoked with--format <chosen>, decoupling the action from the upstream CLI default.setup-only: trueinput. Installs the APM CLI ontoPATHand exits — likeactions/setup-node. Mutually exclusive with all project-level operations.apm-versionandapm-pathoutputs are now always set in every mode.apm-version: latestno longer silently reuses an arbitrarily-old apm that happened to be onPATH. Resolves the latest tag from GitHub first, only reusesPATHapm when its version equals latest (or when GitHub is unreachable, with a warning).Empirically verified end-to-end against
apmHEAD (0.11.0, commit2b9501ab) with 10 integration scenarios + 97 unit tests, plus the existing CI matrix expanded withbundle-formataxis and dedicated setup-only / plugin-rejection jobs.Why this matters now
apmHEADpack --helpsays:apmHEADunpack(verified empirically):So the moment microsoft/apm ships a release, every existing apm-action consumer that uses the default pack/restore round-trip would break — silently if they pin
apm-version: latest, loudly otherwise.apm-action's
apm-versiondefaults tolatestand was not pinned. Without this PR, the next microsoft/apm release would break the round-trip immediately.What changed
1.
bundle-formatinput (defensive default)bundle-formatis wired into pack asapm pack --format <value> --archive.tar tzf) and rejects plugin-format bundles with a clear, actionable error pointing the user atbundle-format: apmandapm pack --format apm --archive.apm unpackitself rejects plugin tarballs (different deployment contract — no lockfile to drivedeployed_files). That belongs upstream inapm unpack, not here.2.
setup-onlyinput (issue #24)PATHand returns. Skipsapm installand every project-level operation.apm-version(resolved version) andapm-path(absolute path to the binary).pack,bundle,bundles-file,isolated,compile,script,dependencies,audit-report,target,bundle-format. Conflicts produce a single consolidated error listing every offending input.3. Always-set installer outputs
apm-versionandapm-pathare now emitted in every mode — install, single-bundle restore, multi-bundle restore, pack, and setup-only.apm-pathis resolved via tool-cache when the action installed APM, or viawhich apmwhen reusing a pre-existing CLI.4. Installer hardening
apm-version: <pinned>(e.g.0.11.0) always installs that exact version into the tool-cache. No silent short-circuit to a different apm that happens to be onPATH.apm-version: latestresolves the latest GitHub release tag first, then reusesPATHapm only if its version equals latest. Otherwise installs fresh. If the GitHub Releases API is unreachable, falls back toPATHapm with a warning.tar tzf) only matchesapm.lock.yamlandplugin.jsonat the top-level wrapper depth, avoiding false positives from nested files of the same name shipped by dependencies.Breaking changes
None for existing apm-action users. The defensive
bundle-format: apmdefault preserves the prior pack/restore round-trip behavior even after the upstream CLI default flips. Users who explicitly want a plugin tarball must opt in withbundle-format: 'plugin'(and accept that the action cannot restore that tarball).How to use
setup-only: 'true'pack: 'true'pack: 'true',bundle-format: 'plugin'bundle: 'path/to/bundle.tar.gz'apm unpackitself rejects themPin the CLI for reproducibility:
Validation
npm test).apmHEAD binary (0.11.0, commit2b9501ab):setup-only,mutex-setup-pack,pack-apm,apm-layout,pack-plugin,plugin-layout,restore-apm,restore-plugin-rejected,invalid-format-rejected,fmt-without-pack-rejected.test-packmatrix onbundle-format: [apm, plugin],test-setup-only, andtest-restore-plugin-rejectedjobs.Review
Drafted via the apm-review-panel personas (devx-ux, supply-chain-security, code-review). Findings adopted in commits
2b6d996and82fcbca:apm-version/apm-pathoutputs (CHANGELOG promised it; code now matches).archivemutex check (was rejecting the validarchive: 'false'value while allowing everything else; redundant with thepackrejection).apm-version: latestno longer silently reuses old PATH apm; bundle-format detection tightened to top-level depth only.Deferred (low-impact, follow-up issues):
@v1pinning advice and serialtar tzfare minor.tarfallback inheriting env not exploitable today and consistent with prior behavior.Files
src/installer.ts—InstallResultreturn; explicit-version always installs;latestconsults GitHub first;which apmresolves binaryPath on PATH-reuse.src/bundler.ts—BundleFormattype;detectBundleFormat(top-level-only marker match);extractBundlerejects plugin format with operator-friendly error;runPackStepacceptsformat.src/multibundle.ts— multi-bundle restore pre-flights every bundle, rejects the whole batch if any is plugin format.src/runner.ts—setup-onlybranch with consolidated mutex;bundle-formatwiring on pack and restore; outputs always set.action.yml—bundle-formatandsetup-onlyinputs;apm-version,apm-path,bundle-formatoutputs.README.md,CHANGELOG.md— documented..github/workflows/ci.yml—bundle-formatmatrix on test-pack; newtest-setup-onlyandtest-restore-plugin-rejectedjobs.