feat(marketplace): adopt apm pack as canonical marketplace.json builder#1570
feat(marketplace): adopt apm pack as canonical marketplace.json builder#1570danielmeppiel wants to merge 6 commits intogithub:stagedfrom
Conversation
There was a problem hiding this comment.
main, but PRs should target staged.
The main branch is auto-published from staged and should not receive direct PRs.
Please close this PR and re-open it against the staged branch.
You can change the base branch using the Edit button at the top of this PR,
or run: gh pr edit 1570 --base staged
Follow-up commit: CI/CD wiring +
|
Introduce APM (microsoft/apm) as the marketplace authoring substrate. Root apm.yml declares all 53 local plugins under marketplace.packages; 'apm pack' emits the Anthropic-spec marketplace.json. A small merge-external-plugins.mjs bridge appends plugins/external.json entries (kept as a separate concern this round) and re-sorts the combined list alphabetically. The legacy generator (eng/generate-marketplace.mjs) is preserved as 'npm run plugin:generate-marketplace:legacy' for parity comparisons during the transition. - npm run build: now invokes apm pack + bridge merge - 54 plugins out, name-parity with previous output verified - per-plugin plugin.json files untouched (follow-up: per-plugin apm.yml) - plugins/external.json untouched (follow-up: native external sources) - CONTRIBUTING.md: apm CLI prerequisite + apm.yml registration step - eng/README.md: marketplace generation section rewritten Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two CI changes mirroring how microsoft/apm uses microsoft/apm-action@v1
in its own self-check workflow:
1. publish.yml: add 'microsoft/apm-action@v1' step before 'npm run build'.
The build now invokes 'apm pack', which requires the apm CLI on PATH.
Without this step the publish-from-staged workflow would fail after
this PR merges.
2. validate-marketplace.yml (new): PR-time gate that runs on changes to
any marketplace.json input. Two subgates:
- Gate A: 'apm audit --ci' for supply-chain integrity (lockfile /
install fidelity, ref consistency, content-integrity scan).
Emits SARIF, uploaded to GitHub code scanning under category
'apm-audit'.
- Gate B: rebuilds marketplace.json with 'apm pack' + the merge
bridge and fails if the result differs from what's committed.
Catches contributors who edit apm.yml without re-running
'npm run build'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
03c3703 to
530351b
Compare
Rebased onto
|
apm-action's audit-report step short-circuits when there is no
apm.lock.yaml ('No apm.lock.yaml found -- nothing to scan') and
writes no SARIF file. The unconditional upload step then failed
with 'Path does not exist: apm-audit.sarif'.
Marketplace-only manifests legitimately have no dependencies to
scan, so the absence of a SARIF file is not an error -- only its
presence-with-failures would be. Guard the upload on
hashFiles('apm-audit.sarif') != '' so the gate stays green for
marketplace-only repos and lights up the moment awesome-copilot
adds a real dependency.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Two follow-ups from the discussion: 1. CI fix pushed ( 2. F3 (retire Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com |
aaronpowell
left a comment
There was a problem hiding this comment.
Some initial questions/observations:
- The
sourcefor each plugin shouldn't contain./pluginsbecause that is defined in themetadata.pluginRootproperty as a root path, so tools would think they are at./plugins/plugins/... - Do we still need the
plugin.jsonfor each plugin? Aren't we just defining all of that in theapm.yml? - Similarly to the last point, shouldn't the file for external plugins be ditched and it just be part of the
apm.ymldefinition?
apm audit --ci catches missing lockfile entries and ref mismatches between apm.yml and apm.lock.yaml, but it does NOT catch the case where apm-action's apm install step regenerates a different lockfile than the one committed (contributor edited apm.yml without running apm install, or an upstream ref moved). Mirror the pattern apm-cli's own self-check uses: after the install step, git status --porcelain apm.lock.yaml and fail on drift with a clear remediation hint. For marketplace-only manifests with no dependencies: block this is a no-op (no lockfile generated, no drift possible) -- the gate activates automatically the moment a real dependency is added. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This reverts commit be4ad25.
|
@aaronpowell -- on findings 1 and 3 of your review, filed microsoft/apm#1061 (expanded from a previous narrower issue) as a single umbrella covering both:
Both are small, additive, no-op-when-unset changes on the same code path. Once they ship upstream, this PR collapses into:
Net result: a single Pragmatic for this PR: pushing the 5-line Finding 2 ( |
feat(marketplace): adopt
apm packas canonical marketplace.json builder + CI gatesTL;DR
Wires
apm packas the canonical aggregation engine for.github/plugin/marketplace.json, replacing the bespokeeng/generate-marketplace.mjsNode script. Adds a rootapm.ymldeclaring all 64 local plugins (no per-pluginplugin.jsonfiles are touched), a small bridge that folds in the 9 entries fromplugins/external.json, and two CI gates --apm audit --cifor supply-chain integrity and amarketplace.jsondrift check -- both wired throughmicrosoft/apm-action@v1so the publish pipeline and PR validation share one CLI install path. Output is byte-equivalent to the legacy build (73 plugins, alphabetical).Note
Companion issue: #1569. Upstream enabler for retiring
plugins/external.json: microsoft/apm#1061. Targetsstaged(notmain) per this repo's publish flow.Problem (WHY)
eng/generate-marketplace.mjsis a repo-specific aggregator with no shared contract -- contributors must read its implementation to understand how the 64 hand-authoredplugins/<name>/.github/plugin/plugin.jsonfiles becomemarketplace.json.marketplace.jsonpost-merge tostaged, but nothing on the PR side validates that the committed file actually matches what the generator would produce -- so a hand-edit lands silently and is rewritten only after merge.Why these matter: APM's anchoring principle is that "Grounding outputs in deterministic tool execution transforms probabilistic generation into verifiable action." A one-off generator without PR-time validation is the opposite -- output identity is tied to script implementation drift, not a versioned spec.
Approach (WHAT)
apm.yml-- declares all 64 local plugins undermarketplace.packages[], mirroring eachplugin.json'sversionanddescriptioneng/merge-external-plugins.mjs-- ~60-line bridge that appends entries fromplugins/external.jsonpost-apm packand re-sorts alphabeticallypackage.json--plugin:generate-marketplace->apm pack ... && node eng/merge-external-plugins.mjs; legacy preserved asplugin:generate-marketplace:legacyfor parity diffing.github/workflows/validate-marketplace.yml-- PR-time Gate A (apm audit --civiamicrosoft/apm-action@v1, conditional SARIF upload) and Gate B (marketplace.json drift).github/workflows/publish.yml-- install APM viamicrosoft/apm-action@v1beforenpm run buildso the materialization job hasapmon PATHCONTRIBUTING.md+eng/README.md-- APM CLI prerequisite + register-in-apm.ymlstepImplementation (HOW)
apm.yml(new, ~280 lines)marketplace.packages[]lists all 64 local plugins withsource: ./plugins/<name>.--marketplace-output .github/plugin/marketplace.jsonkeeps the install path stable. Per-pluginplugin.jsonfiles intentionally untouched.eng/merge-external-plugins.mjs(new)apm pack's output, appendsplugins/external.jsonentries, sorts alphabetically, writes back. Inline comment documents the schema delta blocking native external declaration today (legacy usessource.source/source.repo; APM emitssource.type/source.repository).package.jsonapm pack --marketplace-output .github/plugin/marketplace.json && node ./eng/merge-external-plugins.mjs. Legacy preserved as:legacy..github/workflows/validate-marketplace.yml(new)apm.yml,apm.lock.yaml, plugin manifests, the bridge, the legacy generator, or the committedmarketplace.json. Gate A runsapm-action@v1(which installs APM and runsapm audit -f sarif) thenapm audit --ci; SARIF upload is guarded onhashFiles('apm-audit.sarif') != ''so a marketplace-only manifest with nodependencies:block (and therefore no SARIF artifact) does not falsely fail the upload. Gate B runsnpm run buildthen fails ongit status --porcelainof the committed marketplace.json..github/workflows/publish.ymlmicrosoft/apm-action@v1step beforenpm run build. Without it, the publish job wouldcommand not found: apmpost-merge..github/plugin/marketplace.jsonsource: "./plugins/<name>"vs. bare"<name>"-- both semantically equivalent undermetadata.pluginRoot: ./plugins.CONTRIBUTING.md,eng/README.mdapm.ymlstep in the add-a-plugin walkthrough.Diagrams
Build pipeline (authoring -> validation -> publish)
Dashed-border nodes are added by this PR. The dotted edge is the legacy generator, retained as a parity reference until follow-up F2 retires it.
flowchart LR subgraph Authoring["Authoring sources"] A["apm.yml<br/>(64 local pkgs)"] B["plugins/external.json<br/>(9 entries)"] end subgraph Build["npm run build"] C["apm pack"] D["merge-external-plugins.mjs"] E["generate-marketplace.mjs<br/>(legacy fallback)"] end subgraph Output["Committed artifact"] F[".github/plugin/marketplace.json<br/>(73 plugins, sorted)"] end A --> C C --> D B --> D D --> F E -.->|"DEPRECATED"| F classDef new stroke-dasharray: 5 5; class C,D new; classDef ext fill:#f5f5f5; class E ext;CI gates on a PR
microsoft/apm-action@v1installs the APM CLI once and is shared by both gates.apm audit --ciruns the full check set inapm_cli.policy.ci_checks(lockfile-exists,ref-consistency,deployed-files-present,no-orphaned-packages,config-consistency,content-integrity,includes-consent,skill-subset-consistency), so a separategit statuslockfile check would be redundant.sequenceDiagram participant PR as Pull Request participant Act as microsoft/apm-action@v1 participant Audit as apm audit --ci participant Build as npm run build participant Git as git status PR->>Act: install APM CLI + apm audit -f sarif Act-->>PR: apm on PATH (+ SARIF when deps present) PR->>Audit: Gate A: policy checks Audit-->>PR: pass / fail (lockfile, refs, content) PR->>Build: Gate B: apm pack + merge bridge Build->>Git: rebuild .github/plugin/marketplace.json Git-->>PR: drift = fail with remediation hintTrade-offs
plugins/external.jsonpost-apm packkeeps this PR scoped to the aggregation layer and avoids changing the publishedsourceobject schema. Going native requires upstream work tracked in microsoft/apm#1061 (extendmarketplace.packages[]pass-through to includeauthor/keywords/license/repository, plus let maintainer-supplieddescription/versionoverride the remote-fetch fallback for third-party repos that don't ship anapm.yml). Once it lands, follow-up F3 deletes both the bridge andexternal.json.:legacy. Kept runnable so maintainers can diff outputs across releases until F2 confirms parity. Deleting now would remove the safety net mid-migration. Rationale: "Favor small, chainable primitives over monolithic frameworks.".plugin.jsonfiles untouched. Migrating each to per-pluginapm.yml(follow-up F1) is a separate, larger refactor. Out of scope keeps this PR reviewable in one pass.apm.lock.yamldrift gate viagit status.apm audit --cialready covers every realistic "editedapm.ymlwithout runningapm install" case viaref-consistency+lockfile-exists+no-orphaned-packages. The only thing it deliberately does NOT fail on is upstream-SHA drift on a still-internally-consistent lockfile -- that isapm outdated's informational job, otherwise every long-lived PR would break the moment a referenced ref moves.hashFiles(). For a marketplace-only manifest,apm-action's audit-report step short-circuits ("No apm.lock.yaml found -- nothing to scan") and writes no file. The guard prevents a spurious upload failure today and activates automatically the moment a real dependency is added.Benefits
apm.yml-- no need to read the Node aggregation script.apm), reducing drift risk as the Anthropic plugin spec evolves.apm audit --ciprovides supply-chain integrity from day one (no-op while the manifest is marketplace-only, fully active the moment dependencies land) -- no follow-up workflow change needed.marketplace.jsonimpossible to land silently; the publish pipeline no longer absorbs surprises.awesome-copilotbecomes the highest-profile public consumer ofapm pack(73 plugins from one manifest), validating APM's authoring story at scale.Validation
npm run buildonfeat/apm-pack-marketplace:Validate Marketplaceworkflow on the latest push (run 25150945549): pass (20s).Parity diff against legacy build
How to test
git fetch origin && git checkout feat/apm-pack-marketplacecurl -sSL https://raw.githubusercontent.com/microsoft/apm/main/install.sh | sh && npm installnpm run buildexits 0; verify count:jq '.plugins | length' .github/plugin/marketplace.jsonreturns73apm audit --ciexits 0 withlockfile-existsshort-circuit pass (nodependencies:block today)npm run plugin:generate-marketplace:legacy && git diff -- .github/plugin/marketplace.jsonshows onlysourcepath-format deltas -- no missing or extra entriesFollow-ups
apm.yml(eliminate the 64plugin.jsonfiles).eng/generate-marketplace.mjsafter F1 parity is confirmed.apm.yml(retireplugins/external.json+ bridge), gated on microsoft/apm#1061.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com