# yaml-language-server: $schema=https://json.schemastore.org/github-action.json # permissions: # - packages: write # Required for publishing to GitHub Packages # - contents: read # Required for checking out repository --- name: Publish to NPM description: 'Publishes the package to the NPM registry with configurable scope and registry URL.' author: 'Ismo Vuorinen' branding: icon: package color: green inputs: npm_token: description: 'NPM token.' required: true registry-url: description: 'Registry URL for publishing.' required: false default: 'https://registry.npmjs.org/' scope: description: 'Package scope to use.' required: false default: '@ivuorinen' package-version: description: 'The version to publish.' required: false default: ${{ github.event.release.tag_name }} token: description: 'GitHub token for authentication' required: false default: '' outputs: registry-url: description: 'Registry URL for publishing.' value: ${{ inputs.registry-url }} scope: description: 'Package scope to use.' value: ${{ inputs.scope }} package-version: description: 'The version to publish.' value: ${{ inputs.package-version }} runs: using: composite steps: - name: Mask Secrets shell: sh env: NPM_TOKEN: ${{ inputs.npm_token }} run: | set -eu echo "::add-mask::$NPM_TOKEN" - name: Validate Inputs id: validate shell: sh env: REGISTRY_URL: ${{ inputs.registry-url }} PACKAGE_SCOPE: ${{ inputs.scope }} PACKAGE_VERSION: ${{ inputs.package-version }} NPM_TOKEN: ${{ inputs.npm_token }} run: | set -eu # Validate registry URL format if ! echo "$REGISTRY_URL" | grep -Eq '^https?://[a-zA-Z0-9.-]+(/.*)?/?$'; then echo "::error::Invalid registry URL format: '$REGISTRY_URL'. Expected http:// or https:// URL (e.g., 'https://registry.npmjs.org/')" exit 1 fi # Validate package version format (semver) if ! echo "$PACKAGE_VERSION" | grep -Eq '^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$'; then echo "::error::Invalid package version format: '$PACKAGE_VERSION'. Expected semantic version (e.g., '1.2.3', 'v1.2.3-alpha', '1.2.3+build')" exit 1 fi # Validate scope format (if provided) if [ -n "$PACKAGE_SCOPE" ] && ! echo "$PACKAGE_SCOPE" | grep -Eq '^@[a-z0-9-~][a-z0-9-._~]*$'; then echo "::error::Invalid NPM scope format: '$PACKAGE_SCOPE'. Expected format: @scope-name (e.g., '@myorg', '@my-org')" exit 1 fi # Validate NPM token is provided if [ -z "$NPM_TOKEN" ]; then echo "::error::NPM token is required for publishing" exit 1 fi # Validate package.json exists if [ ! -f "package.json" ]; then echo "::error::package.json not found in current directory" exit 1 fi - name: Checkout Repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: token: ${{ inputs.token || github.token }} - name: Setup Node.js uses: ivuorinen/actions/node-setup@0fa9a68f07a1260b321f814202658a6089a43d42 - name: Authenticate NPM shell: sh env: REGISTRY_URL: ${{ inputs.registry-url }} NPM_TOKEN: ${{ inputs.npm_token }} run: | set -eu registry_host="$(echo "$REGISTRY_URL" | sed -E 's#^https?://##; s#/$##')" echo "//${registry_host}/:_authToken=$NPM_TOKEN" > ~/.npmrc echo "always-auth=true" >> ~/.npmrc - name: Publish Package shell: sh env: REGISTRY_URL: ${{ inputs.registry-url }} PACKAGE_SCOPE: ${{ inputs.scope }} PACKAGE_VERSION: ${{ inputs.package-version }} NPM_TOKEN: ${{ inputs.npm_token }} run: |- set -eu pkg_version=$(node -p "require('./package.json').version") input_version="$PACKAGE_VERSION" # Strip leading v/V and whitespace from input version sanitized_version=$(echo "$input_version" | sed 's/^[[:space:]]*[vV]//' | sed 's/[[:space:]]*$//') if [ "$pkg_version" != "$sanitized_version" ]; then echo "::error::Version mismatch: package.json ($pkg_version) != input (sanitized: $sanitized_version, original: $input_version)" exit 1 fi # Dry run first npm publish \ --registry "$REGISTRY_URL" \ --dry-run \ --scope "$PACKAGE_SCOPE" npm publish \ --registry "$REGISTRY_URL" \ --verbose \ --scope "$PACKAGE_SCOPE" \ --tag "$PACKAGE_VERSION"