Publishing Modules

Share reusable code on the Wippy Hub.

Prerequisites

  1. Create an account on hub.wippy.ai
  2. Create an organization or join one
  3. Register your module name under your organization

Module Structure

mymodule/
├── wippy.yaml      # Module manifest
├── src/
│   ├── _index.yaml # Entry definitions
│   └── *.lua       # Source files
└── README.md       # Documentation (optional)

wippy.yaml

Module manifest:

organization: acme
module: http-utils
description: HTTP utilities and helpers
license: MIT
repository: https://github.com/acme/http-utils
homepage: https://acme.dev
keywords:
  - http
  - utilities
Field Required Description
organization Yes Your org name on the hub
module Yes Module name
description No Short description
license No SPDX identifier (MIT, Apache-2.0)
repository No Source repository URL
homepage No Project homepage
keywords No Search keywords

Entry Definitions

Entries are defined in _index.yaml:

version: "1.0"
namespace: acme.http

entries:
  - name: definition
    kind: ns.definition
    meta:
      title: HTTP Utilities
      description: Helpers for HTTP operations

  - name: client
    kind: library.lua
    source: file://client.lua
    modules:
      - http_client
      - json

Dependencies

Declare dependencies on other modules:

entries:
  - name: __dependency.wippy.test
    kind: ns.dependency
    meta:
      description: Testing framework
    component: wippy/test
    version: ">=0.3.0"

Version constraints:

Constraint Meaning
* Any version
1.0.0 Exact version
>=1.0.0 Minimum version
^1.0.0 Compatible (same major)

Requirements

Define configuration that consumers must provide:

entries:
  - name: api_endpoint
    kind: ns.requirement
    meta:
      description: API endpoint URL
    targets:
      - entry: acme.http:client
        path: ".meta.endpoint"
    default: "https://api.example.com"

Targets specify where the value is injected:

  • entry - Full entry ID to configure
  • path - JSONPath for value injection

Consumers configure via override. The -o flag takes a namespace:entry:field=value triple:

wippy run -o acme.http:client:meta.endpoint=https://custom.api.com

Imports

Reference other entries:

- name: handler
  kind: function.lua
  source: file://handler.lua
  modules:
    - json
  imports:
    client: acme.http:client           # Same namespace
    utils: acme.utils:helpers          # Different namespace
    base_registry: :registry           # Built-in

In Lua:

local client = require("client")
local utils = require("utils")

Contracts

Define public interfaces:

- name: http_contract
  kind: contract.definition
  meta:
    name: HTTP Client Contract
  methods:
    - name: get
      description: Perform GET request
    - name: post
      description: Perform POST request

- name: http_contract_binding
  kind: contract.binding
  contracts:
    - contract: acme.http:http_contract
      methods:
        get: acme.http:get_handler
        post: acme.http:post_handler

Publishing Workflow

1. Authenticate

wippy auth login

2. Prepare

wippy init
wippy update
wippy lint

3. Validate

wippy publish --dry-run

4. Publish

wippy publish --version 1.0.0

With release notes:

wippy publish --version 1.0.0 --release-notes "Initial release"

Additional Flags

Flag Description
--label <name> Publish as a mutable label (e.g. latest, beta) instead of an immutable version
--protected Mark the published version as protected (cannot be deleted or overwritten)
--registry <url> Override the registry URL for this publish
--config <dir> Directory containing wippy.yaml (default: current dir)
--create Register the module on the hub if it does not exist yet, then publish
--module-visibility <v> Visibility for --create: private (default) or public
--module-type <t> Type for --create: application (default), library, agent, or plugin
--module-display-name <n> Display name for --create

Embedding Static Files

Modules with fs.directory entries (static assets, templates, public files) must use --embed to include them in the published package. Without it, fs.directory entries are excluded.

wippy publish --version 1.0.0 --embed app:public_files
wippy publish --version 1.0.0 --embed app:assets,app:templates

The --embed flag accepts entry IDs or names matching fs.directory entries. The same flag is available on wippy pack.

First Publish

The first time you publish a module it is registered on the hub automatically (private by default) and the publish retries once. Pass --create to register it up-front and set its properties:

wippy publish --create --version 0.1.0 \
  --module-visibility public \
  --module-type library \
  --module-display-name "HTTP Utils"

--create is idempotent — for an already-registered module the create step is a no-op. If your account cannot create modules in the organization, the hub returns a permission error instead of publishing.

Publishing to a Local Hub

Point --registry at a locally running hub to publish and install without the public registry. Plain HTTP is allowed only for local hosts — localhost, 127.0.0.1, and the container aliases host.docker.internal (Docker Desktop / OrbStack) and host.containers.internal (Podman); any other host must use HTTPS.

wippy auth login --registry http://localhost:8080 --token wpy_xxx
wippy publish --registry http://localhost:8080 --create --version 0.1.0

The registry and token can also come from the WIPPY_REGISTRY and WIPPY_TOKEN environment variables. When unset, the registry defaults to https://hub.wippy.ai.

Quotas

If the organization's private-module quota is exhausted, publish fails with a message such as cannot publish: Private-module quota exhausted (5 of 5).... Make the module public or ask an org admin to raise the quota. Uploads and downloads retry automatically on transient network errors.

Using Published Modules

Add Dependency

wippy add acme/http-utils
wippy add acme/http-utils@1.0.0
wippy install

Configure Requirements

Override values at runtime:

wippy run -o acme.http:client:meta.endpoint=https://my.api.com

Or in .wippy.yaml:

override:
  acme.http:client:meta.endpoint: "https://my.api.com"

Import in Your Code

# your src/_index.yaml
entries:
  - name: __dependency.acme.http
    kind: ns.dependency
    component: acme/http-utils
    version: ">=1.0.0"

  - name: my_handler
    kind: function.lua
    source: file://handler.lua
    imports:
      http: acme.http:client

Complete Example

wippy.yaml:

organization: acme
module: cache
description: In-memory caching with TTL
license: MIT
keywords:
  - cache
  - memory

src/_index.yaml:

version: "1.0"
namespace: acme.cache

entries:
  - name: definition
    kind: ns.definition
    meta:
      title: Cache Module

  - name: max_size
    kind: ns.requirement
    meta:
      description: Maximum cache entries
    targets:
      - entry: acme.cache:cache
        path: ".meta.max_size"
    default: "1000"

  - name: cache
    kind: library.lua
    meta:
      max_size: 1000
    source: file://cache.lua
    modules:
      - time

src/cache.lua:

local time = require("time")

local cache = {}
local store = {}
local max_size = 1000

function cache.set(key, value, ttl)
    if #store >= max_size then
        cache.evict_oldest()
    end
    store[key] = {
        value = value,
        expires = ttl and (time.now():unix() + ttl) or nil
    }
end

function cache.get(key)
    local entry = store[key]
    if not entry then return nil end
    if entry.expires and time.now():unix() > entry.expires then
        store[key] = nil
        return nil
    end
    return entry.value
end

return cache

Publish:

wippy init && wippy update && wippy lint
wippy publish --version 1.0.0

See Also