universal-plugin 0.17.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +16 -0
- package/dist/cli.mjs +162 -10
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/adopt-upx/scripts/rewrite-upx.mjs +1 -1
- package/skills/build-plugin/SKILL.md +1 -1
- package/skills/build-plugin/scripts/build.mjs +1 -1
- package/skills/doctor-universal-plugin/SKILL.md +16 -4
- package/skills/doctor-universal-plugin/scripts/doctor.mjs +225 -2
- package/skills/init-universal-plugin/SKILL.md +60 -1
- package/skills/init-universal-plugin/references/create.md +4 -1
- package/skills/init-universal-plugin/references/release-tools.md +272 -0
- package/skills/init-universal-plugin/scripts/init.mjs +1 -1
- package/skills/marketplace/scripts/add.mjs +1 -1
- package/skills/marketplace/scripts/install-docs.mjs +1 -1
- package/skills/marketplace/scripts/marketplace.mjs +1 -1
- package/skills/marketplace/scripts/update.mjs +1 -1
- package/skills/marketplace/scripts/validate.mjs +1 -1
- package/skills/version/README.md +5 -1
- package/skills/version/SKILL.md +126 -7
- package/skills/version/scripts/version.mjs +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Research and design toolkit for building universal AI coding agent plugins that work across Claude Code, Cursor, Codex, and GitHub Copilot CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "unional"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Research and design toolkit for building universal AI coding agent plugins that work across Claude Code, Cursor, Codex, and GitHub Copilot CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "unional"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Research and design toolkit for building universal AI coding agent plugins that work across Claude Code, Cursor, Codex, and GitHub Copilot CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "unional"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# universal-plugin
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 760b3d7: `doctor` checks semantic-release wiring. It reports `semantic-release-no-stamp` when the semantic-release config has no `@semantic-release/exec` `prepareCmd` running `plugin version`, and `semantic-release-npm-unshipped` when a plugin that ships to no npm package still runs `@semantic-release/npm`, listed or through the default plugins list.
|
|
8
|
+
- c7739a2: The `init-universal-plugin` skill now wires release-it and release-please when the survey finds them or the user picks them, once the user approves. For release-it it adds an `after:bump` hook running `plugin version ${version}`, sets `"npm": false` for a plugin that ships to no npm package, and offers to tag the current commit with the plugin's version when the repository has no tag, since release-it then reads the current version from the tag. For release-please it writes `release-please-config.json` (`release-type: simple` with an `extra-files` entry for `plugin.json`, or `release-type: node` for an npm package) and seeds `.release-please-manifest.json` with the current version, and it hands the user the workflow step that restamps the release PR. `plugin init` gets no new flag.
|
|
9
|
+
- 9f5f59a: The `init-universal-plugin` skill now wires semantic-release when the survey finds it or the user picks it. Once the user approves, it adds `@semantic-release/exec` running `plugin version ${nextRelease.version}` in `prepare`, and `@semantic-release/git` to commit the stamped files back. For a plugin that ships to no npm package it drops `@semantic-release/npm`. For release-it and release-please, the skill names the stamp each tool must call. The wiring stays in the skill, and `plugin init` gets no new flag.
|
|
10
|
+
- 11c42a6: A plugin that ships to no npm package can now be released with changesets. In a changesets repository with no `package.json` at the plugin root, `plugin init` writes a minimal private one (`name`, `"private": true`, `version`), wires the root `version` script, and adds `"release": "changeset publish"`; `--no-changesets` opts out. `plugin version --from-package` with no package in a changesets repository names that `package.json` and the `privatePackages` setting. `doctor` reports `changesets-no-package`, `changesets-private-untagged`, and `changesets-no-publish-script`.
|
|
11
|
+
- fa31d36: release-it is now a documented release model. With `"npm": false` release-it reads the version from the latest git tag and needs no `package.json`; an `after:bump` hook running `universal-plugin plugin version ${version}` stamps it before the release commit and tag. `doctor` reports `release-it-no-stamp` for a release-it config with no hook running `plugin version`.
|
|
12
|
+
- bea7278: `doctor` reports `release-please-restamp-no-ci` when a workflow pushes the `--from-manifest` restamp with the default `GITHUB_TOKEN`. That push starts no workflow run, so the release PR's checks never run on its head commit and a required check blocks the merge. The `version` skill's release-please workflow now mints a GitHub App token with `actions/create-github-app-token` and passes it to release-please and to `actions/checkout`.
|
|
13
|
+
- 8e20a28: A plugin that ships to no npm package can now be released with release-please. `plugin version --from-manifest` restamps every place at the version `plugin.json` already holds, for a CI step on the release PR after `extra-files` bumps `plugin.json`; it is refused where `packagePath` is declared. `doctor` reports `release-please-no-restamp` when a release-please config bumps `plugin.json` and no workflow runs `plugin version`. The `version` skill gives the config and the workflow.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 7e8b2e1: `doctor` no longer reports `unreleased-content` for a non-npm plugin that a release tool versions. A plugin released with changesets (through a private `package.json`), release-please (its config bumps `plugin.json`), semantic-release, or release-it is skipped like a plugin that declares `packagePath`, because the tool picks the next number at release time.
|
|
18
|
+
|
|
3
19
|
## 0.17.1
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/cli.mjs
CHANGED
|
@@ -8608,10 +8608,31 @@ function ownPackage(state) {
|
|
|
8608
8608
|
json: state.rootPackageJson ?? null
|
|
8609
8609
|
};
|
|
8610
8610
|
}
|
|
8611
|
+
/** Changesets releases only what has a `package.json`, so a plugin that ships to no npm package
|
|
8612
|
+
* needs a private one to carry its version (#234). Names the file and the config that makes
|
|
8613
|
+
* `changeset publish` tag it. */
|
|
8614
|
+
function changesetsPackageHint(state) {
|
|
8615
|
+
const name = typeof state.manifest?.name === "string" ? state.manifest.name : "<plugin-name>";
|
|
8616
|
+
const version = typeof state.manifest?.version === "string" ? state.manifest.version : "0.0.0";
|
|
8617
|
+
return [
|
|
8618
|
+
"changesets releases only a package, so a plugin that is not published to npm needs a private package.json at the plugin root:",
|
|
8619
|
+
` ${JSON.stringify({
|
|
8620
|
+
name,
|
|
8621
|
+
private: true,
|
|
8622
|
+
version
|
|
8623
|
+
})}`,
|
|
8624
|
+
"and .changeset/config.json needs \"privatePackages\": { \"version\": true, \"tag\": true } so changeset publish tags the release.",
|
|
8625
|
+
"plugin init writes the package.json when it finds .changeset/."
|
|
8626
|
+
].join("\n");
|
|
8627
|
+
}
|
|
8611
8628
|
/** Reads the version a release tool already wrote into the package. */
|
|
8612
8629
|
function versionFromPackage(state) {
|
|
8613
8630
|
const pkg = ownPackage(state);
|
|
8614
|
-
if (pkg.json === null)
|
|
8631
|
+
if (pkg.json === null) {
|
|
8632
|
+
if (state.packagePath !== null) throw new Error(`No package.json found at packagePath "${state.packagePath}"`);
|
|
8633
|
+
const message = "No package.json to read the version from: packagePath is not set in .agents/universal-plugin.json and the plugin root has no package.json";
|
|
8634
|
+
throw new Error(state.changesets ? `${message}\n${changesetsPackageHint(state)}` : message);
|
|
8635
|
+
}
|
|
8615
8636
|
const version = pkg.json["version"];
|
|
8616
8637
|
if (typeof version !== "string" || version.length === 0) throw new Error(`No version found in ${pkg.path}`);
|
|
8617
8638
|
if (version === "0.0.0-development") throw new Error(`${pkg.path} holds the placeholder ${UNSTAMPED_VERSION} — pass the release's version explicitly (plugin version \${nextRelease.version})`);
|
|
@@ -8619,15 +8640,33 @@ function versionFromPackage(state) {
|
|
|
8619
8640
|
if (valid === null) throw new Error(`Version "${version}" in ${pkg.path} is not valid semver`);
|
|
8620
8641
|
return valid;
|
|
8621
8642
|
}
|
|
8622
|
-
/**
|
|
8643
|
+
/** Reads the version a release tool already wrote into the canonical manifest (#238). Under a
|
|
8644
|
+
* declared `packagePath` the package owns the number, so the manifest is not a source there. */
|
|
8645
|
+
function versionFromManifest(state, from) {
|
|
8646
|
+
if (state.packagePath !== null) {
|
|
8647
|
+
const pkg = joinRelative(state.packagePath, "package.json");
|
|
8648
|
+
throw new Error(`packagePath is declared, so ${pkg} owns the number, not plugin.json — run plugin version --from-package`);
|
|
8649
|
+
}
|
|
8650
|
+
if (from === null) throw new Error("plugin.json has no version to restamp — pass an explicit version (e.g. 0.1.0) to set the first one");
|
|
8651
|
+
if (from === "0.0.0-development") throw new Error(`plugin.json holds the placeholder ${UNSTAMPED_VERSION} — pass the release's version explicitly`);
|
|
8652
|
+
const valid = import_semver.valid(from);
|
|
8653
|
+
if (valid === null) throw new Error(`Version "${from}" in plugin.json is not valid semver`);
|
|
8654
|
+
return valid;
|
|
8655
|
+
}
|
|
8656
|
+
/** Picks the number. Exactly one source: a bump argument, `--from-package`, or `--from-manifest`. Under ADR-0010 §2 a
|
|
8623
8657
|
* plugin with `packagePath` gets its number from the package, so a release type there is refused —
|
|
8624
8658
|
* the explicit and `--from-package` forms stay, because release tools call them. */
|
|
8625
8659
|
function resolveVersion(state, from, opts) {
|
|
8660
|
+
if (opts.fromPackage && opts.fromManifest) throw new Error("Pass --from-package or --from-manifest, not both");
|
|
8661
|
+
if (opts.fromManifest) {
|
|
8662
|
+
if (opts.bump !== void 0) throw new Error(`Pass a version or --from-manifest, not both (got "${opts.bump}")`);
|
|
8663
|
+
return versionFromManifest(state, from);
|
|
8664
|
+
}
|
|
8626
8665
|
if (opts.fromPackage) {
|
|
8627
8666
|
if (opts.bump !== void 0) throw new Error(`Pass a version or --from-package, not both (got "${opts.bump}")`);
|
|
8628
8667
|
return versionFromPackage(state);
|
|
8629
8668
|
}
|
|
8630
|
-
if (opts.bump === void 0) throw new Error(`Pass a release type (${RELEASE_TYPES.join(", ")}), an explicit version, or --from-
|
|
8669
|
+
if (opts.bump === void 0) throw new Error(`Pass a release type (${RELEASE_TYPES.join(", ")}), an explicit version, --from-package, or --from-manifest`);
|
|
8631
8670
|
if (state.packagePath !== null && isReleaseType(opts.bump)) {
|
|
8632
8671
|
const pkg = joinRelative(state.packagePath, "package.json");
|
|
8633
8672
|
throw new Error(`packagePath is declared, so the release picks the number — bump ${pkg} with your release tool (e.g. changeset version), then run plugin version --from-package`);
|
|
@@ -8896,6 +8935,10 @@ function gatherMcpFiles(root, declaration) {
|
|
|
8896
8935
|
}
|
|
8897
8936
|
return files;
|
|
8898
8937
|
}
|
|
8938
|
+
/** Whether `.changeset/` sits at the plugin root or at the repository root above it. */
|
|
8939
|
+
function hasChangesets(root) {
|
|
8940
|
+
return [root, gitToplevel(root)].some((dir) => dir !== void 0 && fs$1.existsSync(path$1.join(dir, ".changeset")));
|
|
8941
|
+
}
|
|
8899
8942
|
function toRelative(root, abs) {
|
|
8900
8943
|
return path$1.relative(root, abs).split(path$1.sep).join("/");
|
|
8901
8944
|
}
|
|
@@ -8921,7 +8964,8 @@ const realVersionFs = {
|
|
|
8921
8964
|
})),
|
|
8922
8965
|
workspace: Object.fromEntries(discoverWorkspace(root)),
|
|
8923
8966
|
mcpFiles: gatherMcpFiles(root, uext["mcpServers"]),
|
|
8924
|
-
pinsMapExists: fs$1.existsSync(path$1.join(root, PINS_MAP_PATH))
|
|
8967
|
+
pinsMapExists: fs$1.existsSync(path$1.join(root, PINS_MAP_PATH)),
|
|
8968
|
+
changesets: hasChangesets(root)
|
|
8925
8969
|
};
|
|
8926
8970
|
},
|
|
8927
8971
|
apply(root, plan) {
|
|
@@ -9164,9 +9208,22 @@ function gatherChangesets(root, repo) {
|
|
|
9164
9208
|
if (!fs$1.existsSync(path$1.join(workspace, ".changeset"))) return void 0;
|
|
9165
9209
|
return {
|
|
9166
9210
|
pluginPath,
|
|
9167
|
-
packageJson: readJson$1(packageJsonPath(workspace))
|
|
9211
|
+
packageJson: readJson$1(packageJsonPath(workspace)),
|
|
9212
|
+
config: readJsonSafe(path$1.join(workspace, ".changeset", "config.json"))
|
|
9168
9213
|
};
|
|
9169
9214
|
}
|
|
9215
|
+
function readJsonSafe(file) {
|
|
9216
|
+
try {
|
|
9217
|
+
return readJson$1(file);
|
|
9218
|
+
} catch {
|
|
9219
|
+
return null;
|
|
9220
|
+
}
|
|
9221
|
+
}
|
|
9222
|
+
/** The version an existing `plugin.json` carries, which a new private package.json is seeded from. */
|
|
9223
|
+
function readManifestVersion(root) {
|
|
9224
|
+
const version = readJsonSafe(manifestPath(root))?.version;
|
|
9225
|
+
return typeof version === "string" && version !== "" ? version : void 0;
|
|
9226
|
+
}
|
|
9170
9227
|
function writeJson(file, value) {
|
|
9171
9228
|
const existing = fs$1.existsSync(file) ? fs$1.readFileSync(file, "utf8") : void 0;
|
|
9172
9229
|
fs$1.writeFileSync(file, formatJson(value, {
|
|
@@ -9179,6 +9236,7 @@ const realInitFs = {
|
|
|
9179
9236
|
const repo = gatherRepo(root);
|
|
9180
9237
|
return {
|
|
9181
9238
|
manifestExists: fs$1.existsSync(manifestPath(root)),
|
|
9239
|
+
manifestVersion: readManifestVersion(root),
|
|
9182
9240
|
packageJson: readJson$1(packageJsonPath(root)),
|
|
9183
9241
|
repo,
|
|
9184
9242
|
changesets: gatherChangesets(root, repo)
|
|
@@ -9284,6 +9342,89 @@ function wireChangesets(pkg, pluginPath, cliVersion) {
|
|
|
9284
9342
|
};
|
|
9285
9343
|
return { content: next };
|
|
9286
9344
|
}
|
|
9345
|
+
const CHANGESET_PUBLISH = "changeset publish";
|
|
9346
|
+
/** The version a private package starts at when the manifest carries none to seed it from. */
|
|
9347
|
+
const FIRST_VERSION = "0.0.0";
|
|
9348
|
+
/** The package.json a plugin that ships to no npm package needs before changesets can release it
|
|
9349
|
+
* (#234). Changesets finds only packages, and `@changesets/apply-release-plan` always writes
|
|
9350
|
+
* `<dir>/package.json`, so this file is what carries the version. `private` keeps
|
|
9351
|
+
* `changeset publish` to a git tag; no `files`, `main`, or `exports`, because nothing is packed. */
|
|
9352
|
+
function privatePackage(name, version) {
|
|
9353
|
+
return {
|
|
9354
|
+
name,
|
|
9355
|
+
private: true,
|
|
9356
|
+
version
|
|
9357
|
+
};
|
|
9358
|
+
}
|
|
9359
|
+
/** Adds `"release": "changeset publish"`, the script changesets/action runs as its publish step.
|
|
9360
|
+
* For a private package it creates the git tag the GitHub release hangs on. A custom `release`
|
|
9361
|
+
* script is left alone with a note. Returns `null` content when nothing changes. */
|
|
9362
|
+
function wireReleaseScript(pkg) {
|
|
9363
|
+
const scripts = typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {};
|
|
9364
|
+
const script = typeof scripts.release === "string" ? scripts.release : void 0;
|
|
9365
|
+
if (script?.includes(CHANGESET_PUBLISH)) return { content: null };
|
|
9366
|
+
if (script !== void 0) return {
|
|
9367
|
+
content: null,
|
|
9368
|
+
note: `changesets: the root "release" script is custom; make sure it runs "${CHANGESET_PUBLISH}" so a release is tagged`
|
|
9369
|
+
};
|
|
9370
|
+
return { content: {
|
|
9371
|
+
...pkg,
|
|
9372
|
+
scripts: {
|
|
9373
|
+
...scripts,
|
|
9374
|
+
release: CHANGESET_PUBLISH
|
|
9375
|
+
}
|
|
9376
|
+
} };
|
|
9377
|
+
}
|
|
9378
|
+
/** Whether `.changeset/config.json` has changesets bump and tag private packages. Changesets bumps
|
|
9379
|
+
* them by default but tags them only when `privatePackages.tag` is `true`. */
|
|
9380
|
+
function tagsPrivatePackages(config) {
|
|
9381
|
+
const setting = config?.privatePackages;
|
|
9382
|
+
if (typeof setting !== "object" || setting === null) return false;
|
|
9383
|
+
const { version, tag } = setting;
|
|
9384
|
+
return version !== false && tag === true;
|
|
9385
|
+
}
|
|
9386
|
+
/** Wires a plugin that ships to no npm package for a changesets release: a private package.json at
|
|
9387
|
+
* the plugin root, and the `version` and `release` scripts at the workspace root. Appends to `rows`
|
|
9388
|
+
* and `notes` in place. */
|
|
9389
|
+
function planPrivateRelease(opts, name, version, changesets, rows, notes) {
|
|
9390
|
+
const sameFile = changesets.pluginPath === "";
|
|
9391
|
+
let packageJson = privatePackage(name, version);
|
|
9392
|
+
rows.push({
|
|
9393
|
+
path: "package.json",
|
|
9394
|
+
action: "created"
|
|
9395
|
+
});
|
|
9396
|
+
if (sameFile) packageJson = {
|
|
9397
|
+
...packageJson,
|
|
9398
|
+
scripts: { version: CHANGESET_VERSION }
|
|
9399
|
+
};
|
|
9400
|
+
else notes.push(`changesets: ${changesets.pluginPath}/package.json must be a workspace package (pnpm-workspace.yaml or "workspaces") for changesets to release it`);
|
|
9401
|
+
let workspacePackageJson = null;
|
|
9402
|
+
const rootPkg = sameFile ? packageJson : changesets.packageJson;
|
|
9403
|
+
if (rootPkg === null) notes.push(`changesets: no package.json at the workspace root to wire; add a "version" script running "${CHANGESET_VERSION} && ${syncVersionCommand(changesets.pluginPath)}" and a "release" script running "${CHANGESET_PUBLISH}"`);
|
|
9404
|
+
else {
|
|
9405
|
+
const versioned = wireChangesets(rootPkg, changesets.pluginPath, opts.cliVersion);
|
|
9406
|
+
if (versioned.note) notes.push(versioned.note);
|
|
9407
|
+
const released = wireReleaseScript(versioned.content ?? rootPkg);
|
|
9408
|
+
if (released.note) notes.push(released.note);
|
|
9409
|
+
const wired = released.content ?? versioned.content;
|
|
9410
|
+
if (wired && sameFile) packageJson = wired;
|
|
9411
|
+
else if (wired) {
|
|
9412
|
+
workspacePackageJson = {
|
|
9413
|
+
path: `${changesets.pluginPath.split("/").map(() => "..").join("/")}/package.json`,
|
|
9414
|
+
content: wired
|
|
9415
|
+
};
|
|
9416
|
+
rows.push({
|
|
9417
|
+
path: workspacePackageJson.path,
|
|
9418
|
+
action: "updated"
|
|
9419
|
+
});
|
|
9420
|
+
}
|
|
9421
|
+
}
|
|
9422
|
+
if (!tagsPrivatePackages(changesets.config)) notes.push("changesets: set \"privatePackages\": { \"version\": true, \"tag\": true } in .changeset/config.json so changeset publish tags each release");
|
|
9423
|
+
return {
|
|
9424
|
+
packageJson,
|
|
9425
|
+
workspacePackageJson
|
|
9426
|
+
};
|
|
9427
|
+
}
|
|
9287
9428
|
/** The local marketplace a repository carries is named after the repository, not after the plugin:
|
|
9288
9429
|
* the catalog sits at the repository root and lists every plugin the repository develops. `-local`
|
|
9289
9430
|
* separates it from a published marketplace of the same plugins. */
|
|
@@ -9356,9 +9497,12 @@ function planCatalogs(state, opts, manifest, notes) {
|
|
|
9356
9497
|
function planInit(state, opts, rootDirName, resolveShippedPaths) {
|
|
9357
9498
|
if (opts.npm && state.packageJson === null) throw new Error("error: --npm requires a package.json at the project root");
|
|
9358
9499
|
if (state.manifestExists && !opts.force) throw new Error("plugin.json already exists — pass --force to overwrite");
|
|
9359
|
-
const
|
|
9500
|
+
const name = opts.name ?? rootDirName;
|
|
9501
|
+
const manifest = buildManifest(name, opts.vendors);
|
|
9360
9502
|
const packageVersion = state.packageJson?.version;
|
|
9361
9503
|
if (opts.npm && typeof packageVersion === "string" && packageVersion !== "") manifest.version = packageVersion;
|
|
9504
|
+
const privateVersion = !opts.npm && opts.changesets !== false && state.changesets && state.packageJson === null ? state.manifestVersion ?? FIRST_VERSION : void 0;
|
|
9505
|
+
if (privateVersion !== void 0) manifest.version = privateVersion;
|
|
9362
9506
|
const dirs = opts.scaffold ? [...SCAFFOLD_DIRS] : [];
|
|
9363
9507
|
const rows = [{
|
|
9364
9508
|
path: "plugin.json",
|
|
@@ -9384,7 +9528,7 @@ function planInit(state, opts, rootDirName, resolveShippedPaths) {
|
|
|
9384
9528
|
});
|
|
9385
9529
|
}
|
|
9386
9530
|
let workspacePackageJson = null;
|
|
9387
|
-
const changesets = state.changesets;
|
|
9531
|
+
const changesets = opts.changesets === false ? void 0 : state.changesets;
|
|
9388
9532
|
if (opts.npm && changesets) {
|
|
9389
9533
|
const sameFile = changesets.pluginPath === "";
|
|
9390
9534
|
const rootPkg = sameFile ? packageJson : changesets.packageJson;
|
|
@@ -9405,6 +9549,11 @@ function planInit(state, opts, rootDirName, resolveShippedPaths) {
|
|
|
9405
9549
|
}
|
|
9406
9550
|
}
|
|
9407
9551
|
}
|
|
9552
|
+
if (privateVersion !== void 0 && changesets) {
|
|
9553
|
+
const wired = planPrivateRelease(opts, name, privateVersion, changesets, rows, notes);
|
|
9554
|
+
packageJson = wired.packageJson;
|
|
9555
|
+
workspacePackageJson = wired.workspacePackageJson;
|
|
9556
|
+
}
|
|
9408
9557
|
const created = rows.filter((r) => r.action === "created").length;
|
|
9409
9558
|
const updated = rows.filter((r) => r.action === "updated").length;
|
|
9410
9559
|
const unchanged = rows.filter((r) => r.action === "unchanged").length;
|
|
@@ -9431,7 +9580,7 @@ function collect$1(value, previous) {
|
|
|
9431
9580
|
}
|
|
9432
9581
|
function initCommand$1(deps = { fs: realInitFs }) {
|
|
9433
9582
|
const cmd = new Command("init").description("Scaffold the canonical plugin.json (with --npm, wire an npm package to ship it)");
|
|
9434
|
-
cmd.option("--name <name>", "Plugin name (default: the root directory name)").option("--vendor <id>", "Target vendor; repeatable", collect$1, []).option("--scaffold", "Create the standard skills/ agents/ references/ commands/ directories").option("--force", "Overwrite an existing plugin.json").option("--yes", "Non-interactive (compatibility no-op; init never prompts)").option("--npm", "Wire package.json to ship the plugin (files, version; changesets version sync)").option("--no-marketplace", "Skip the repository's local marketplace catalogs").option("--format <format>", "Output format: json or toon (default: toon)").addOption(new Option("--json").hideHelp()).addOption(ROOT_OPTION).addHelpText("after", "\nExample:\n $ universal-plugin plugin init --name my-plugin --scaffold\n").action((opts) => {
|
|
9583
|
+
cmd.option("--name <name>", "Plugin name (default: the root directory name)").option("--vendor <id>", "Target vendor; repeatable", collect$1, []).option("--scaffold", "Create the standard skills/ agents/ references/ commands/ directories").option("--force", "Overwrite an existing plugin.json").option("--yes", "Non-interactive (compatibility no-op; init never prompts)").option("--npm", "Wire package.json to ship the plugin (files, version; changesets version sync)").option("--no-marketplace", "Skip the repository's local marketplace catalogs").option("--no-changesets", "Skip the changesets release wiring (private package.json, version and release scripts)").option("--format <format>", "Output format: json or toon (default: toon)").addOption(new Option("--json").hideHelp()).addOption(ROOT_OPTION).addHelpText("after", "\nExample:\n $ universal-plugin plugin init --name my-plugin --scaffold\n").action((opts) => {
|
|
9435
9584
|
try {
|
|
9436
9585
|
const root = resolveRoot(opts.root);
|
|
9437
9586
|
const plan = planInit(deps.fs.gather(root), {
|
|
@@ -9441,6 +9590,7 @@ function initCommand$1(deps = { fs: realInitFs }) {
|
|
|
9441
9590
|
force: Boolean(opts.force),
|
|
9442
9591
|
npm: Boolean(opts.npm),
|
|
9443
9592
|
marketplace: opts.marketplace !== false,
|
|
9593
|
+
changesets: opts.changesets !== false,
|
|
9444
9594
|
cliVersion: deps.version
|
|
9445
9595
|
}, path$1.basename(root), (vendor) => VENDOR_SHIPPED_PATHS[vendor] ?? []);
|
|
9446
9596
|
deps.fs.apply(root, plan);
|
|
@@ -18114,12 +18264,13 @@ function prepareCommand() {
|
|
|
18114
18264
|
//#region src/version/cli.ts
|
|
18115
18265
|
function versionCommand(deps = { fs: realVersionFs }) {
|
|
18116
18266
|
const cmd = new Command("version").description("Stamp the plugin version into every file that carries one").argument("[bump]", `Release type (${RELEASE_TYPES.join(", ")}) or an explicit version (1.4.0)`);
|
|
18117
|
-
cmd.option("--from-package", "Take the version the package's package.json already holds (changesets, release-please)").option("--preid <id>", "Prerelease identifier for the pre* release types").option("--force", "Allow a version that does not advance on the current one").option("--no-build", "Skip re-deriving the vendor manifests").option("--dry-run", "Print what would be written without writing").option("--format <format>", "Output format: json or toon (default: toon)").addOption(new Option("--json").hideHelp()).addOption(ROOT_OPTION).addHelpText("after", [
|
|
18267
|
+
cmd.option("--from-package", "Take the version the package's package.json already holds (changesets, release-please)").option("--from-manifest", "Restamp every place with the version plugin.json already holds (release-please extra-files)").option("--preid <id>", "Prerelease identifier for the pre* release types").option("--force", "Allow a version that does not advance on the current one").option("--no-build", "Skip re-deriving the vendor manifests").option("--dry-run", "Print what would be written without writing").option("--format <format>", "Output format: json or toon (default: toon)").addOption(new Option("--json").hideHelp()).addOption(ROOT_OPTION).addHelpText("after", [
|
|
18118
18268
|
"",
|
|
18119
18269
|
"Examples:",
|
|
18120
18270
|
" $ universal-plugin plugin version minor # plain repository: the author picks",
|
|
18121
18271
|
" $ universal-plugin plugin version 1.4.0 # semantic-release: prepareCmd",
|
|
18122
|
-
" $ universal-plugin plugin version --from-package # changesets / release-please",
|
|
18272
|
+
" $ universal-plugin plugin version --from-package # changesets / release-please, npm package",
|
|
18273
|
+
" $ universal-plugin plugin version --from-manifest # release-please, plugin.json bumped by extra-files",
|
|
18123
18274
|
""
|
|
18124
18275
|
].join("\n")).action((bump, opts) => runVersion(bump, opts, deps.fs));
|
|
18125
18276
|
return cmd;
|
|
@@ -18132,6 +18283,7 @@ function runVersion(bump, opts, versionFs = realVersionFs) {
|
|
|
18132
18283
|
const plan = planVersion(versionFs.gather(root), {
|
|
18133
18284
|
...bump === void 0 ? {} : { bump },
|
|
18134
18285
|
fromPackage: Boolean(opts.fromPackage),
|
|
18286
|
+
fromManifest: Boolean(opts.fromManifest),
|
|
18135
18287
|
...opts.preid === void 0 ? {} : { preid: opts.preid },
|
|
18136
18288
|
force: Boolean(opts.force)
|
|
18137
18289
|
});
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "universal-plugin",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"description": "Research and design toolkit for building universal AI coding agent plugins that work across Claude Code, Cursor, Codex, and GitHub Copilot CLI.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "unional"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Compiled by `universal-plugin plugin build` from src/skill-scripts/rewrite-upx.ts. Edit the source, not this file.
|
|
3
|
-
globalThis.__SKILL_SCRIPT__ = {"package":"universal-plugin","bin":"universal-plugin","version":"0.
|
|
3
|
+
globalThis.__SKILL_SCRIPT__ = {"package":"universal-plugin","bin":"universal-plugin","version":"0.18.0","entry":"skills/adopt-upx/scripts/rewrite-upx.mjs","commands":{"marketplace add":"skills/marketplace/scripts/add.mjs","marketplace init":"skills/marketplace/scripts/marketplace.mjs","marketplace update":"skills/marketplace/scripts/update.mjs","marketplace validate":"skills/marketplace/scripts/validate.mjs","plugin build":"skills/build-plugin/scripts/build.mjs","plugin init":"skills/init-universal-plugin/scripts/init.mjs","plugin version":"skills/version/scripts/version.mjs"}};
|
|
4
4
|
import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
//#region src/skill-scripts/rewrite-upx.ts
|
|
@@ -47,7 +47,7 @@ node scripts/build.mjs --root <plugin-root>
|
|
|
47
47
|
|
|
48
48
|
Resolve `scripts/build.mjs` against this skill's own directory. It runs the CLI the project installed
|
|
49
49
|
(`pnpm exec universal-plugin`), else the CLI pinned to the version this skill shipped with
|
|
50
|
-
(`npx universal-plugin@0.
|
|
50
|
+
(`npx universal-plugin@0.18.0`), so it works in a skill that was linked or copied on its own. `--root` defaults to the working directory and
|
|
51
51
|
must name the directory that holds the canonical `plugin.json`.
|
|
52
52
|
|
|
53
53
|
The command never prompts, so it is safe to run unattended.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Compiled by `universal-plugin plugin build` from src/skill-scripts/run-cli.ts. Edit the source, not this file.
|
|
3
|
-
globalThis.__SKILL_SCRIPT__ = {"package":"universal-plugin","bin":"universal-plugin","version":"0.
|
|
3
|
+
globalThis.__SKILL_SCRIPT__ = {"package":"universal-plugin","bin":"universal-plugin","version":"0.18.0","entry":"skills/build-plugin/scripts/build.mjs","commands":{"marketplace add":"skills/marketplace/scripts/add.mjs","marketplace init":"skills/marketplace/scripts/marketplace.mjs","marketplace update":"skills/marketplace/scripts/update.mjs","marketplace validate":"skills/marketplace/scripts/validate.mjs","plugin build":"skills/build-plugin/scripts/build.mjs","plugin init":"skills/init-universal-plugin/scripts/init.mjs","plugin version":"skills/version/scripts/version.mjs"}};
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
//#region src/compile/scripts.ts
|
|
6
6
|
/** The global the header assigns the script data to. Framework-neutral: a script's own code, or the
|
|
@@ -95,6 +95,14 @@ Each `code` below is what the script emits.
|
|
|
95
95
|
| `package-path-missing` | `packagePath` names a directory with no readable `package.json` | fix `packagePath`, or create the package |
|
|
96
96
|
| `package-path-unknown` | the CLI could not report `packagePath` (a version too old to read it), so `version-drift` and `unreleased-content` were skipped rather than guessed | upgrade universal-plugin |
|
|
97
97
|
| `misplaced-package-path` | `plugin.json` declares `packagePath` under `extensions["org.cyberuni.universal-plugin"]`, where the CLI never reads it — the plugin is silently treated as not shipping to npm | move it to `.agents/universal-plugin.json`, relative to the plugin root |
|
|
98
|
+
| `changesets-no-package` | the repository is released with changesets, the plugin ships to no npm package, and its root has no `package.json` — changesets releases only a package, so `changeset version` and `changeset publish` fail | `/universal-plugin:init-universal-plugin` (pnpm install: `pnpm-universal-plugin-init-universal-plugin`) — `plugin init` writes a private `package.json` |
|
|
99
|
+
| `changesets-private-untagged` | a non-npm plugin in a changesets repository, and `.changeset/config.json` does not set `"privatePackages": { "version": true, "tag": true }` — `changeset publish` tags no private package, so no release is made | set `privatePackages` in `.changeset/config.json` |
|
|
100
|
+
| `changesets-no-publish-script` | a non-npm plugin in a changesets repository, and a workflow runs `changesets/action` with no `publish-script` (`publish` in v1) — after the Version Packages PR merges the action does nothing, so no tag or GitHub release is made | add `publish-script: pnpm release` to the step, with a `release` script running `changeset publish` |
|
|
101
|
+
| `release-please-no-restamp` | the plugin ships to no npm package, `release-please-config.json` bumps its `plugin.json` through `extra-files`, and no workflow runs `plugin version` — release-please runs no command before its release commit, so the release PR leaves the vendor manifests, catalogs, skill pins and compiled scripts on the old version | add a workflow step on the release PR branch running `universal-plugin plugin version --from-manifest` and pushing the stamped files — `/universal-plugin:version` (pnpm install: `pnpm-universal-plugin-version`) has the workflow |
|
|
102
|
+
| `release-please-restamp-no-ci` | a workflow restamps the release PR with `plugin version --from-manifest` and pushes with the default `GITHUB_TOKEN` — that push starts no workflow run, so the PR's checks never run on its head commit and a required check blocks the merge. A `workflow_dispatch` re-run does not fix it: its checks do not count toward a required check | mint a GitHub App token with `actions/create-github-app-token` and pass it as `token:` to `release-please-action` and `actions/checkout` — `/universal-plugin:version` (pnpm install: `pnpm-universal-plugin-version`) has the workflow |
|
|
103
|
+
| `release-it-no-stamp` | a release-it config (`.release-it.*`, or the `release-it` key of `package.json`) has no hook running `plugin version` — release-it commits and tags the release with `plugin.json` and the vendor manifests still on the previous version | add `"hooks": { "after:bump": "universal-plugin plugin version ${version}" }` to the config |
|
|
104
|
+
| `semantic-release-no-stamp` | a semantic-release config (`.releaserc*`, `.config/releaserc*`, `release.config.*`, or the `release` key of `package.json`) has no `@semantic-release/exec` `prepareCmd` running `plugin version` — the release is tagged with `plugin.json`, the vendor manifests and the catalogs still on the previous version | add `["@semantic-release/exec", { "prepareCmd": "universal-plugin plugin version ${nextRelease.version}" }]` before `@semantic-release/git` — `/universal-plugin:init-universal-plugin` (pnpm install: `pnpm-universal-plugin-init-universal-plugin`) wires it |
|
|
105
|
+
| `semantic-release-npm-unshipped` | the plugin ships to no npm package, and its semantic-release config lists `@semantic-release/npm` or relies on the default `plugins` list, which includes it — with no `package.json` the release fails (`ENOPKG`), and with one the plugin still rewrites its version | remove `@semantic-release/npm`, writing the `plugins` list out if the config relies on the defaults — `/universal-plugin:init-universal-plugin` (pnpm install: `pnpm-universal-plugin-init-universal-plugin`) wires it |
|
|
98
106
|
| `unparsable-manifest` | root `plugin.json` is not valid JSON | fix the syntax error |
|
|
99
107
|
| `invalid-catalog` | a marketplace catalog — at the repository root, or at a `--marketplace-root` clone — is not a shape its runtime loads — it is found, read, and refused at install time, in the user's terminal | `/universal-plugin:marketplace` (pnpm install: `pnpm-universal-plugin-marketplace`) |
|
|
100
108
|
| `marketplace-root-missing` | a `--marketplace-root` path does not exist, so it could not be checked at all | clone the marketplace repository, or fix the path |
|
|
@@ -172,7 +180,7 @@ it does not, offer to add it — ask first, because both are the user's files:
|
|
|
172
180
|
semantic-release bot opens.
|
|
173
181
|
|
|
174
182
|
Use the locked `universal-plugin` from the repository's devDependencies when it has one; otherwise
|
|
175
|
-
`npx universal-plugin@0.
|
|
183
|
+
`npx universal-plugin@0.18.0`, pinned. This check matters most where the release tool picks the
|
|
176
184
|
number, because nothing else runs between that bump and publish.
|
|
177
185
|
|
|
178
186
|
## Unreleased content
|
|
@@ -187,9 +195,13 @@ The script compares the shipped paths — the canonical manifest, the skills dir
|
|
|
187
195
|
emits `unreleased-content` for anything committed since. Uncommitted work is not reported; it has not
|
|
188
196
|
shipped.
|
|
189
197
|
|
|
190
|
-
Two cases are deliberately silent. A plugin
|
|
191
|
-
|
|
192
|
-
|
|
198
|
+
Two cases are deliberately silent. A plugin whose release picks the number is skipped, because
|
|
199
|
+
content waiting ahead of the last released version is the normal state of a branch there
|
|
200
|
+
([ADR-0010](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/design/decisions/0010-version-policy.md) §6). That is a plugin that declares `packagePath`, or a plugin a release tool versions:
|
|
201
|
+
changesets (`.changeset/` and a private or absent plugin-root `package.json`), release-please (its
|
|
202
|
+
config bumps this `plugin.json` through `extra-files`), semantic-release (`.releaserc*`,
|
|
203
|
+
`release.config.*`, or a `release` key in `package.json`), or release-it (a `.release-it.*` file, or a
|
|
204
|
+
`release-it` key in `package.json`). A tree with no git history is skipped rather than guessed at.
|
|
193
205
|
|
|
194
206
|
The repair is the bump, and it belongs to `/universal-plugin:version` (pnpm install: `pnpm-universal-plugin-version`). Judge first whether the change
|
|
195
207
|
is meant to ship — content that is still being worked on is not a finding to act on.
|