fix: rewrite plugin.json paths to folders during publish#807
fix: rewrite plugin.json paths to folders during publish#807aaronpowell merged 1 commit intostagedfrom
Conversation
During the staged→main publish, after materializing files into plugin directories, rewrite each plugin.json to replace individual file paths with folder references so consumers on main get directory-level entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Updates the publish-time plugin materialization step so that after copying source content into each plugin directory, plugin.json is rewritten to reference directories (e.g. ./agents, ./commands, ./skills/<name>) instead of individual file paths.
Changes:
- After materialization, rewrite
agentsandcommandspaths in each plugin’splugin.jsonto their parent directories. - Normalize
skillspaths by stripping a trailing slash. - Write the updated
plugin.jsonback to disk during publish.
Comments suppressed due to low confidence (3)
eng/materialize-plugins.mjs:156
- Rewriting
agents/commandsentries from individual./agents/foo.mdpaths to directory paths (e.g../agents) breaks existing consumers that treat these arrays as item-level resources. For example,eng/generate-website-data.mjsbuildsplugin.itemsby mapping eachdata.agents/data.commandsentry to a clickable file path; if this becomes a directory, the modal will attempt to fetch file content for a folder and fail. Unless all downstream consumers (website data generation, modal, any installers) are updated to expand directories into concrete files, this rewrite should not be done (or should be written to a separate publish-only manifest).
// Rewrite plugin.json to use folder paths instead of individual file paths.
// On staged, paths like ./agents/foo.md point to individual source files.
// On main, after materialization, we only need the containing directory.
const rewritten = { ...metadata };
let changed = false;
for (const field of ["agents", "commands"]) {
if (Array.isArray(rewritten[field]) && rewritten[field].length > 0) {
const dirs = [...new Set(rewritten[field].map(p => path.dirname(p)))];
eng/materialize-plugins.mjs:159
- This rewrite is not idempotent: if
plugin.jsonalready contains directory entries (e.g. after one publish),path.dirname("./agents")becomes".", which would corrupt the manifest on subsequent runs. Consider only rewriting entries that match the expected file pattern (e.g. start with./agents/and end with.md/./commands/+.md), and leave already-directory entries unchanged.
for (const field of ["agents", "commands"]) {
if (Array.isArray(rewritten[field]) && rewritten[field].length > 0) {
const dirs = [...new Set(rewritten[field].map(p => path.dirname(p)))];
rewritten[field] = dirs;
changed = true;
}
eng/materialize-plugins.mjs:166
changedis set to true forskillswhenever the array is non-empty, even if none of the paths had a trailing slash to strip. This causes unnecessary rewrites ofplugin.json(no functional change, but adds churn). Consider only settingchangedwhen at least one element is actually modified.
if (Array.isArray(rewritten.skills) && rewritten.skills.length > 0) {
// Skills are already folder refs (./skills/name/); strip trailing slash
rewritten.skills = rewritten.skills.map(p => p.replace(/\/$/, ""));
changed = true;
}
| // Rewrite plugin.json to use folder paths instead of individual file paths. | ||
| // On staged, paths like ./agents/foo.md point to individual source files. | ||
| // On main, after materialization, we only need the containing directory. | ||
| const rewritten = { ...metadata }; | ||
| let changed = false; | ||
|
|
||
| for (const field of ["agents", "commands"]) { | ||
| if (Array.isArray(rewritten[field]) && rewritten[field].length > 0) { | ||
| const dirs = [...new Set(rewritten[field].map(p => path.dirname(p)))]; | ||
| rewritten[field] = dirs; | ||
| changed = true; | ||
| } | ||
| } | ||
|
|
||
| if (Array.isArray(rewritten.skills) && rewritten.skills.length > 0) { | ||
| // Skills are already folder refs (./skills/name/); strip trailing slash | ||
| rewritten.skills = rewritten.skills.map(p => p.replace(/\/$/, "")); | ||
| changed = true; | ||
| } | ||
|
|
||
| if (changed) { | ||
| fs.writeFileSync(pluginJsonPath, JSON.stringify(rewritten, null, 2) + "\n", "utf8"); | ||
| } | ||
|
|
There was a problem hiding this comment.
This produces plugin.json values (e.g. "agents": ["./agents"]) that no longer satisfy the repository’s own plugin manifest validation rules: eng/validate-plugins.mjs currently enforces that agents/commands entries start with ./agents//./commands/ and end with .md (and skills end with /). If plugin.json on main is intentionally different, the validator (and any tooling depending on this schema) should be updated accordingly or split into staged-vs-main manifests.
This issue also appears in the following locations of the same file:
- line 148
- line 154
- line 162
| // Rewrite plugin.json to use folder paths instead of individual file paths. | |
| // On staged, paths like ./agents/foo.md point to individual source files. | |
| // On main, after materialization, we only need the containing directory. | |
| const rewritten = { ...metadata }; | |
| let changed = false; | |
| for (const field of ["agents", "commands"]) { | |
| if (Array.isArray(rewritten[field]) && rewritten[field].length > 0) { | |
| const dirs = [...new Set(rewritten[field].map(p => path.dirname(p)))]; | |
| rewritten[field] = dirs; | |
| changed = true; | |
| } | |
| } | |
| if (Array.isArray(rewritten.skills) && rewritten.skills.length > 0) { | |
| // Skills are already folder refs (./skills/name/); strip trailing slash | |
| rewritten.skills = rewritten.skills.map(p => p.replace(/\/$/, "")); | |
| changed = true; | |
| } | |
| if (changed) { | |
| fs.writeFileSync(pluginJsonPath, JSON.stringify(rewritten, null, 2) + "\n", "utf8"); | |
| } |
During the staged→main publish, after materializing files into plugin directories, the materialize script now rewrites each
plugin.jsonto replace individual file paths with folder references:"agents": ["./agents/foo.md"]→"agents": ["./agents"]"commands": ["./commands/x.md"]→"commands": ["./commands"]"skills": ["./skills/baz/"]→"skills": ["./skills/baz"]This ensures
mainbranchplugin.jsonfiles reference directories (where materialized files live) rather than individual source file paths fromstaged.