mirror of
https://github.com/walter-base/common-workflow.git
synced 2026-09-08 19:26:46 -04:00
Merge pull request #9 from walter-base/feat/npm-registry-publishing
This commit is contained in:
@@ -98,6 +98,29 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
publish_npm:
|
||||
description: >-
|
||||
Publish the release contents as an npm package so agents that support npm
|
||||
marketplace catalogs can install and auto-update from the registry.
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
npm_package_name:
|
||||
description: >-
|
||||
Scoped npm package name, for example `@acme/agent-skills`. A scope is
|
||||
required because it is what routes installs to the registry, but it does
|
||||
not have to match the registry owner. Required when publish_npm is enabled.
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
npm_registry:
|
||||
description: >-
|
||||
npm registry base URL, for example
|
||||
`https://gitea.example.com/api/packages/acme/npm/`. Defaults to the Gitea
|
||||
npm registry for gitea_user when gitea_url is set.
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
secrets:
|
||||
GITHUB_PAT:
|
||||
description: Optional GitHub token for release-please pull requests.
|
||||
@@ -105,6 +128,11 @@ on:
|
||||
GITEA_TOKEN:
|
||||
description: Gitea token with permission to publish generic packages.
|
||||
required: false
|
||||
NPM_TOKEN:
|
||||
description: >-
|
||||
Token for the npm registry. Falls back to GITEA_TOKEN when unset, which
|
||||
covers publishing to a Gitea npm registry.
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -166,6 +194,93 @@ jobs:
|
||||
done
|
||||
tar -czf "dist/${ARCHIVE_NAME}-${VERSION}.tar.gz" -C dist "${dir}"
|
||||
|
||||
- name: Set up Node
|
||||
if: inputs.publish_npm
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Publish npm package
|
||||
if: inputs.publish_npm
|
||||
id: npm
|
||||
env:
|
||||
ARCHIVE_NAME: ${{ inputs.archive_name }}
|
||||
GITEA_URL: ${{ inputs.gitea_url }}
|
||||
GITEA_USER: ${{ inputs.gitea_user }}
|
||||
NPM_PACKAGE_NAME: ${{ inputs.npm_package_name }}
|
||||
NPM_REGISTRY: ${{ inputs.npm_registry }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN || secrets.GITEA_TOKEN }}
|
||||
PACKAGE_PATHS: ${{ inputs.package_paths }}
|
||||
VERSION: ${{ needs.release-please.outputs.version }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
: "${NPM_PACKAGE_NAME:?Set npm_package_name when publish_npm is enabled}"
|
||||
: "${NPM_TOKEN:?Pass NPM_TOKEN or GITEA_TOKEN when publish_npm is enabled}"
|
||||
|
||||
registry="${NPM_REGISTRY}"
|
||||
if [[ -z "$registry" ]]; then
|
||||
: "${GITEA_URL:?Set npm_registry, or gitea_url and gitea_user, when publish_npm is enabled}"
|
||||
: "${GITEA_USER:?Set npm_registry, or gitea_url and gitea_user, when publish_npm is enabled}"
|
||||
registry="${GITEA_URL%/}/api/packages/${GITEA_USER}/npm/"
|
||||
fi
|
||||
[[ "$registry" == */ ]] || registry="${registry}/"
|
||||
|
||||
scope="${NPM_PACKAGE_NAME%%/*}"
|
||||
if [[ "$scope" != @* ]]; then
|
||||
echo "npm_package_name must be scoped (for example @acme/agent-skills) so the registry mapping is unambiguous" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The staged directory already holds exactly the released content, so the npm
|
||||
# package and the generic archive can never drift apart.
|
||||
pkg_dir="dist/${ARCHIVE_NAME}-${VERSION}"
|
||||
files_json="$(printf '%s\n' ${PACKAGE_PATHS} | jq -R . | jq -s .)"
|
||||
jq -n \
|
||||
--arg name "$NPM_PACKAGE_NAME" \
|
||||
--arg version "$VERSION" \
|
||||
--argjson files "$files_json" \
|
||||
'{name: $name, version: $version, files: $files, private: false}' \
|
||||
> "${pkg_dir}/package.json"
|
||||
|
||||
# `npm pack <path>` without a leading `./` is parsed as a GitHub owner/repo
|
||||
# spec, so npm tries to clone it instead of packing the directory.
|
||||
tarball_name="$(npm pack --pack-destination dist "./${pkg_dir}")"
|
||||
: "${tarball_name:?npm pack did not produce a tarball}"
|
||||
tarball="dist/${tarball_name}"
|
||||
|
||||
# npm's default ignore rules have historically surprised people with
|
||||
# dot-directories, and the agent catalogs live in `.agents/` and
|
||||
# `.claude-plugin/`. Fail here rather than shipping an unusable package.
|
||||
contents="$(tar -tzf "$tarball")"
|
||||
missing=0
|
||||
for path in ${PACKAGE_PATHS}; do
|
||||
[[ -e "$path" ]] || continue
|
||||
if ! grep -q "^package/${path}" <<<"$contents"; then
|
||||
echo "npm package is missing released path: ${path}" >&2
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [[ "$missing" -ne 0 ]]; then
|
||||
echo "Refusing to publish an npm package that does not match the release archive." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
registry_auth="${registry#https://}"
|
||||
registry_auth="${registry_auth#http://}"
|
||||
{
|
||||
printf '%s:registry=%s\n' "$scope" "$registry"
|
||||
printf '//%s:_authToken=%s\n' "$registry_auth" "$NPM_TOKEN"
|
||||
} > "$HOME/.npmrc"
|
||||
trap 'rm -f "$HOME/.npmrc"' EXIT
|
||||
|
||||
npm publish "./${tarball}" --registry "$registry"
|
||||
# Keep the packed tarball out of the GitHub Release upload glob.
|
||||
rm -f "$tarball"
|
||||
|
||||
echo "npm_package=${NPM_PACKAGE_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "npm_registry=${registry}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build Gitea installers
|
||||
if: inputs.publish_gitea
|
||||
env:
|
||||
@@ -363,11 +478,24 @@ jobs:
|
||||
CODEX_MARKETPLACE_NAME: ${{ inputs.codex_marketplace_name }}
|
||||
CURSOR_PLUGIN_NAME: ${{ inputs.cursor_plugin_name }}
|
||||
GITEA_INSTALLER_URL: ${{ steps.gitea.outputs.installer_url }}
|
||||
NPM_PACKAGE: ${{ steps.npm.outputs.npm_package }}
|
||||
NPM_REGISTRY_URL: ${{ steps.npm.outputs.npm_registry }}
|
||||
run: |
|
||||
{
|
||||
printf '# Agent plugin %s\n\n' "$VERSION"
|
||||
printf 'Release tag: `%s`\n\n' "$TAG"
|
||||
|
||||
if [[ -n "$NPM_PACKAGE" ]]; then
|
||||
printf '## npm registry\n\n'
|
||||
printf 'Published `%s@%s` to `%s`.\n\n' "$NPM_PACKAGE" "$VERSION" "$NPM_REGISTRY_URL"
|
||||
printf 'Agents that support npm marketplace catalogs can install this package '
|
||||
printf 'directly and pick up later releases without re-running an installer. '
|
||||
printf 'Authenticate first if the registry is private:\n\n'
|
||||
printf '%s\n' '```bash'
|
||||
printf 'npm config set %s:registry %s\n' "${NPM_PACKAGE%%/*}" "$NPM_REGISTRY_URL"
|
||||
printf '%s\n\n' '```'
|
||||
fi
|
||||
|
||||
if [[ -n "$GITEA_INSTALLER_URL" ]]; then
|
||||
printf '## Claude Code\n\n'
|
||||
printf '%s\n' '```bash'
|
||||
|
||||
@@ -8,6 +8,7 @@ This repository intentionally contains only generic marketplace automation:
|
||||
- skill and plugin manifest validation
|
||||
- pull-request preview archives
|
||||
- release archives and installation summaries
|
||||
- optional npm registry publishing for agents that support npm catalogs
|
||||
|
||||
Container, infrastructure, deployment, and environment-specific workflows are
|
||||
kept in the private `Walter0697/common-workflow` repository.
|
||||
@@ -28,3 +29,36 @@ The validator is also available as:
|
||||
```yaml
|
||||
uses: walter-base/common-workflow/actions/validate-agent-marketplace@master
|
||||
```
|
||||
|
||||
## Publishing to an npm registry
|
||||
|
||||
The generic archive and its installer are opaque to coding agents: something has
|
||||
to download and unpack them, which is what the generated installer script does.
|
||||
Agents can, however, resolve npm packages on their own, so publishing the same
|
||||
release contents to an npm registry lets them install and update without an
|
||||
installer.
|
||||
|
||||
Enable it on the release workflow:
|
||||
|
||||
```yaml
|
||||
with:
|
||||
publish_npm: true
|
||||
npm_package_name: "@acme/agent-skills"
|
||||
# npm_registry defaults to the Gitea npm registry for gitea_user
|
||||
secrets:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- the package is built from the same staged directory as the generic archive, so
|
||||
the two can never drift apart
|
||||
- `package.json` is generated at publish time with the release version, which
|
||||
keeps it in step with the plugin manifest versions that release-please bumps
|
||||
- `npm_package_name` must be scoped, because the scope is what routes installs to
|
||||
the registry in `.npmrc`. The scope does not have to match the registry owner:
|
||||
Gitea's own example maps an `@test` scope to a `testuser` registry URL
|
||||
- `NPM_TOKEN` is used when set; otherwise `GITEA_TOKEN` is reused
|
||||
- publishing fails if any released path is absent from the packed tarball, which
|
||||
guards against npm's ignore rules silently dropping `.agents/` or
|
||||
`.claude-plugin/`
|
||||
|
||||
Reference in New Issue
Block a user