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
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: ""
|
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:
|
secrets:
|
||||||
GITHUB_PAT:
|
GITHUB_PAT:
|
||||||
description: Optional GitHub token for release-please pull requests.
|
description: Optional GitHub token for release-please pull requests.
|
||||||
@@ -105,6 +128,11 @@ on:
|
|||||||
GITEA_TOKEN:
|
GITEA_TOKEN:
|
||||||
description: Gitea token with permission to publish generic packages.
|
description: Gitea token with permission to publish generic packages.
|
||||||
required: false
|
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:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -166,6 +194,93 @@ jobs:
|
|||||||
done
|
done
|
||||||
tar -czf "dist/${ARCHIVE_NAME}-${VERSION}.tar.gz" -C dist "${dir}"
|
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
|
- name: Build Gitea installers
|
||||||
if: inputs.publish_gitea
|
if: inputs.publish_gitea
|
||||||
env:
|
env:
|
||||||
@@ -363,11 +478,24 @@ jobs:
|
|||||||
CODEX_MARKETPLACE_NAME: ${{ inputs.codex_marketplace_name }}
|
CODEX_MARKETPLACE_NAME: ${{ inputs.codex_marketplace_name }}
|
||||||
CURSOR_PLUGIN_NAME: ${{ inputs.cursor_plugin_name }}
|
CURSOR_PLUGIN_NAME: ${{ inputs.cursor_plugin_name }}
|
||||||
GITEA_INSTALLER_URL: ${{ steps.gitea.outputs.installer_url }}
|
GITEA_INSTALLER_URL: ${{ steps.gitea.outputs.installer_url }}
|
||||||
|
NPM_PACKAGE: ${{ steps.npm.outputs.npm_package }}
|
||||||
|
NPM_REGISTRY_URL: ${{ steps.npm.outputs.npm_registry }}
|
||||||
run: |
|
run: |
|
||||||
{
|
{
|
||||||
printf '# Agent plugin %s\n\n' "$VERSION"
|
printf '# Agent plugin %s\n\n' "$VERSION"
|
||||||
printf 'Release tag: `%s`\n\n' "$TAG"
|
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
|
if [[ -n "$GITEA_INSTALLER_URL" ]]; then
|
||||||
printf '## Claude Code\n\n'
|
printf '## Claude Code\n\n'
|
||||||
printf '%s\n' '```bash'
|
printf '%s\n' '```bash'
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ This repository intentionally contains only generic marketplace automation:
|
|||||||
- skill and plugin manifest validation
|
- skill and plugin manifest validation
|
||||||
- pull-request preview archives
|
- pull-request preview archives
|
||||||
- release archives and installation summaries
|
- release archives and installation summaries
|
||||||
|
- optional npm registry publishing for agents that support npm catalogs
|
||||||
|
|
||||||
Container, infrastructure, deployment, and environment-specific workflows are
|
Container, infrastructure, deployment, and environment-specific workflows are
|
||||||
kept in the private `Walter0697/common-workflow` repository.
|
kept in the private `Walter0697/common-workflow` repository.
|
||||||
@@ -28,3 +29,36 @@ The validator is also available as:
|
|||||||
```yaml
|
```yaml
|
||||||
uses: walter-base/common-workflow/actions/validate-agent-marketplace@master
|
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