goal

Registry Configuration

Goal supports configuring multiple package registries for publishing your projects.

Supported Registries

Registry Language Default URL Token Environment
PyPI Python https://pypi.org/simple/ PYPI_TOKEN
Test PyPI Python https://test.pypi.org/simple/ TEST_PYPI_TOKEN
npm Node.js https://registry.npmjs.org/ NPM_TOKEN
GitHub Packages Node.js https://npm.pkg.github.com/ GITHUB_TOKEN
crates.io Rust https://crates.io/ CARGO_REGISTRY_TOKEN
Gemfury Ruby https://rubygems.org/ GEMFURY_TOKEN
Private Registry Any Custom URL Custom

Basic Configuration

Single Registry

# goal.yaml
registries:
  pypi:
    url: "https://pypi.org/simple/"
    token_env: "PYPI_TOKEN"

Multiple Registries

registries:
  pypi:
    url: "https://pypi.org/simple/"
    token_env: "PYPI_TOKEN"
  testpypi:
    url: "https://test.pypi.org/simple/"
    token_env: "TEST_PYPI_TOKEN"
  private:
    url: "https://pypi.mycompany.com/simple/"
    token_env: "PRIVATE_PYPI_TOKEN"

Python Registries

PyPI Production

registries:
  pypi:
    url: "https://pypi.org/simple/"
    token_env: "PYPI_TOKEN"

strategies:
  python:
    publish: "twine upload dist/*"

Test PyPI

registries:
  testpypi:
    url: "https://test.pypi.org/simple/"
    token_env: "TEST_PYPI_TOKEN"

strategies:
  python:
    publish: "twine upload --repository testpypi dist/*"

Private PyPI Server

registries:
  private:
    url: "https://pypi.company.com/simple/"
    token_env: "COMPANY_PYPI_TOKEN"
    username_env: "COMPANY_PYPI_USER"

strategies:
  python:
    publish: |
      twine upload \
        --repository-url https://pypi.company.com/legacy/ \
        --username $COMPANY_PYPI_USER \
        --password $COMPANY_PYPI_TOKEN \
        dist/*

Multiple Python Registries

strategies:
  python:
    publish: |
      # Publish to Test PyPI first
      twine upload --repository testpypi dist/*
      
      # Wait for propagation
      sleep 30
      
      # Then publish to production
      twine upload dist/*

Node.js Registries

npm Public

registries:
  npm:
    url: "https://registry.npmjs.org/"
    token_env: "NPM_TOKEN"

strategies:
  nodejs:
    publish: "npm publish"

GitHub Packages

registries:
  github:
    url: "https://npm.pkg.github.com/"
    token_env: "GITHUB_TOKEN"

strategies:
  nodejs:
    publish: "npm publish --registry https://npm.pkg.github.com/"

Private npm Registry

registries:
  private:
    url: "https://npm.company.com/"
    token_env: "COMPANY_NPM_TOKEN"

strategies:
  nodejs:
    publish: "npm publish --registry https://npm.company.com/"

Scoped Packages

strategies:
  nodejs:
    publish: "npm publish --access public"
    # For private scoped packages:
    # publish: "npm publish --access private"

Rust Registries

crates.io

registries:
  cargo:
    url: "https://crates.io/"
    token_env: "CARGO_REGISTRY_TOKEN"

strategies:
  rust:
    publish: "cargo publish"

Alternative Registry

registries:
  private_cargo:
    url: "https://crates.company.com/"
    token_env: "COMPANY_CARGO_TOKEN"

strategies:
  rust:
    publish: "cargo publish --registry private"

Ruby Registries

RubyGems

registries:
  rubygems:
    url: "https://rubygems.org/"
    token_env: "RUBYGEMS_API_KEY"

strategies:
  ruby:
    publish: "gem push *.gem"

Gemfury

registries:
  gemfury:
    url: "https://push.fury.io/"
    token_env: "GEMFURY_TOKEN"

strategies:
  ruby:
    publish: "gem push *.gem --host https://push.fury.io/username/"

Authentication

API Tokens

Most registries use API tokens:

# Set environment variables
export PYPI_TOKEN="pypi-xxxxxx"
export NPM_TOKEN="npm_xxxxxx"
export GITHUB_TOKEN="ghp_xxxxxx"
export CARGO_REGISTRY_TOKEN="xxxxxx"

In CI/CD

GitHub Actions

env:
  PYPI_TOKEN: $
  NPM_TOKEN: $

GitLab CI

variables:
  PYPI_TOKEN: $PYPI_TOKEN
  NPM_TOKEN: $NPM_TOKEN

Jenkins

environment {
    PYPI_TOKEN = credentials('pypi-token')
    NPM_TOKEN = credentials('npm-token')
}

Username/Password

Some registries require username/password:

registries:
  private:
    url: "https://registry.company.com/"
    username_env: "REGISTRY_USER"
    password_env: "REGISTRY_PASS"

strategies:
  python:
    publish: |
      twine upload \
        --username $REGISTRY_USER \
        --password $REGISTRY_PASS \
        dist/*

Advanced Configuration

Conditional Publishing

strategies:
  python:
    publish: |
      if [ "$BRANCH" = "main" ]; then
        twine upload dist/*
      else
        echo "Not publishing from branch $BRANCH"
      fi

Dry Run Publishing

strategies:
  python:
    publish: "twine upload --skip-existing dist/*"
  nodejs:
    publish: "npm publish --dry-run"
  rust:
    publish: "cargo publish --dry-run"

Publishing with Delay

strategies:
  python:
    publish: |
      twine upload dist/*
      echo "Waiting for index update..."
      sleep 60
      curl -X POST "$NOTIFICATION_WEBHOOK" \
        -d "text='Package published to PyPI'"

Multi-Registry Publishing

strategies:
  nodejs:
    publish: |
      # Publish to npm
      npm publish
      
      # Publish to GitHub Packages
      npm publish --registry https://npm.pkg.github.com/
      
      # Publish to private registry
      npm publish --registry https://npm.company.com/

Environment-Specific Registries

Development

# .goal/development.yaml
registries:
  npm:
    url: "http://localhost:4873/"
    token_env: "DEV_NPM_TOKEN"

Staging

# .goal/staging.yaml
registries:
  pypi:
    url: "https://test.pypi.org/simple/"
    token_env: "STAGING_PYPI_TOKEN"

Production

# .goal/production.yaml
registries:
  pypi:
    url: "https://pypi.org/simple/"
    token_env: "PROD_PYPI_TOKEN"

Security Best Practices

1. Use Environment Variables

Never hardcode tokens in configuration:

# Bad
registries:
  pypi:
    token: "pypi-xxxxxx"

# Good
registries:
  pypi:
    token_env: "PYPI_TOKEN"

2. Scoped Tokens

Use minimal permission tokens:

# PyPI - scoped to specific package
pypi-token --project my-package

# npm - automation-only token
npm token create --read-only false

3. Token Rotation

Regularly rotate tokens:

hooks:
  post_push: |
    # Notify to rotate token
    curl -X POST "$SECURITY_WEBHOOK" \
      -d "text='Remember to rotate $REGISTRY token'"

4. Audit Trail

Log publishing activity:

strategies:
  python:
    publish: |
      echo "Publishing to PyPI at $(date)" >> publish.log
      twine upload dist/*
      echo "Published successfully at $(date)" >> publish.log

Troubleshooting

Authentication Errors

# Test PyPI token
twine check dist/*

# Test npm token
npm whoami

# Test cargo token
cargo login --registry crates.io

Registry Not Found

# Verify URL format
registries:
  pypi:
    url: "https://pypi.org/simple/"  # Must end with /

Permission Denied

# Check token permissions
strategies:
  python:
    publish: |
      twine upload --verbose dist/*  # Shows error details

Network Issues

# Use proxy if needed
strategies:
  python:
    publish: |
      https_proxy=$HTTPS_PROXY twine upload dist/*

Examples

Complete Python Package Setup

# goal.yaml
project:
  name: "my-package"
  type: ["python"]

registries:
  pypi:
    url: "https://pypi.org/simple/"
    token_env: "PYPI_TOKEN"
  testpypi:
    url: "https://test.pypi.org/simple/"
    token_env: "TEST_PYPI_TOKEN"

strategies:
  python:
    test: "pytest -xvs --cov"
    build: "python -m build"
    publish: |
      if [ "$ENVIRONMENT" = "production" ]; then
        twine upload dist/*
      else
        twine upload --repository testpypi dist/*
      fi

hooks:
  post_push: |
    curl -X POST "$SLACK_WEBHOOK" \
      -d "text='Published my-package v$VERSION'"

Multi-Language Project

project:
  name: "fullstack-app"
  type: ["python", "nodejs"]

registries:
  pypi:
    url: "https://pypi.org/simple/"
    token_env: "PYPI_TOKEN"
  npm:
    url: "https://registry.npmjs.org/"
    token_env: "NPM_TOKEN"

strategies:
  python:
    test: "cd backend && pytest"
    build: "cd backend && python -m build"
    publish: "cd backend && twine upload dist/*"
  nodejs:
    test: "cd frontend && npm test"
    build: "cd frontend && npm run build"
    publish: "cd frontend && npm publish"

Registry-Specific Documentation