모듈 게시

Wippy Hub에서 재사용 가능한 코드를 공유합니다.

사전 요구사항

  1. hub.wippy.ai에서 계정 생성
  2. 조직 생성 또는 가입
  3. 해당 조직에서 모듈을 생성할 권한 보유 — 첫 wippy publish가 모듈을 자동으로 등록

모듈 구조

mymodule/
├── wippy.yaml      # 모듈 매니페스트
├── src/
│   ├── _index.yaml # 엔트리 정의
│   └── *.lua       # 소스 파일
└── README.md       # 문서 (선택)

wippy.yaml {#wippy-yaml}

모듈 매니페스트:

organization: acme
module: http-utils
type: library
description: HTTP utilities and helpers
license: MIT
repository: https://github.com/acme/http-utils
homepage: https://acme.dev
keywords:
  - http
  - utilities
authors:
  - Acme Engineering <eng@acme.dev>
embed:
  - acme.http:assets
exclude:
  - test/**
  - "*.test.lua"
  - acme.http:debug_handler
exclude_meta:
  stage:
    - experimental
metadata:
  support_url: https://acme.dev/support
필드 필수 설명
organization Hub의 조직 이름
module 모듈 이름
type 아니오 모듈 타입: library, application, agent, 또는 plugin
description 아니오 짧은 설명
license 아니오 SPDX 식별자 (MIT, Apache-2.0)
repository 아니오 소스 저장소 URL
homepage 아니오 프로젝트 홈페이지
keywords 아니오 검색 키워드
authors 아니오 작성자 목록
version 아니오 시맨틱 버전. --version이 이를 재정의합니다
exclude 아니오 제외할 패턴: :를 포함하는 값은 엔트리 ID이고, 그 외는 소스 파일 glob입니다
embed 아니오 --embed가 전달되지 않았을 때 사용할 기본 fs.directory 임베드 패턴
exclude_meta 아니오 메타데이터 필드와 값의 맵. 메타데이터가 일치하는 엔트리는 제외됩니다
metadata 아니오 게시된 모듈과 함께 전달되는 임의의 키/값 메타데이터
publish.profiles 아니오 패키지에 포함할 설정 프로파일 (프로파일 게시 참조)
publish.runtime 아니오 패키지 기본값으로 포함할 런타임 설정 섹션. type: application에서만 사용

exclude는 별도의 필드가 아니라 값의 형태로 구분됩니다. _old/**, test/**, *.test.lua는 소스 파일이 수집될 때 필터링하고, acme.http:debug_handler는 엔트리가 디코딩된 뒤 레지스트리 엔트리를 비활성화합니다. ** 세그먼트는 임의 개수의 디렉토리 세그먼트에 걸칩니다.

type은 허브가 모듈을 분류하는 방식의 원천이며 이후 게시에서 변경할 수 있습니다; --module-type은 단일 게시에 한해 이를 재정의합니다. 생략하면 새로 생성되는 모듈은 사용 중단 경고와 함께 application을 기본값으로 사용합니다.

엔트리 정의

엔트리는 _index.yaml에 정의됩니다:

version: "1.0"
namespace: acme.http

entries:
  - name: definition
    kind: ns.definition
    meta:
      title: HTTP Utilities
      description: Helpers for HTTP operations
    readme: file://README.md
    wiki:
      GUIDE.md: file://docs/GUIDE.md
      examples/auth.md: file://docs/auth.md

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

ns.definitionwiki: 맵은 readme 옆에 추가 문서 페이지를 게시합니다: 키는 페이지 경로, 값은 file:// 참조입니다. 내용은 팩 시점에 인라인되며 허브가 모듈별로 탐색 가능한 위키로 제공합니다.

의존성

다른 모듈에 대한 의존성을 선언합니다:

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

버전 제약:

제약 의미
* 모든 버전
1.0.0 정확한 버전
>=1.0.0 최소 버전
^1.0.0 호환 (동일한 메이저)

요구사항

소비자가 제공해야 하는 설정을 정의합니다:

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"

타겟은 값이 주입될 위치를 지정합니다:

  • entry - 설정할 전체 엔트리 ID
  • path - 값 주입을 위한 JSONPath

default는 모든 스칼라 타입을 받습니다 — default: 20은 숫자 타겟에 문자열이 아닌 숫자로 흘러갑니다. ns.dependency 엔트리의 parameters[].value에도 동일하게 적용되며, 둘 다 ${env:NAME} 참조를 받습니다 — 참조는 그대로 전달되고 타겟 엔트리가 디코드될 때 해석됩니다.

소비자는 오버라이드를 통해 설정합니다. -o 플래그는 namespace:entry:field=value 트리플을 받습니다:

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

임포트

다른 엔트리를 참조합니다:

- name: handler
  kind: function.lua
  source: file://handler.lua
  modules:
    - json
  imports:
    client: acme.http:client           # 동일한 네임스페이스
    utils: acme.utils:helpers          # 다른 네임스페이스
    base_registry: :registry           # 내장

Lua에서:

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

계약

공개 인터페이스 정의:

- 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

게시 워크플로우

1. 인증

wippy auth login

2. 준비

wippy init
wippy update
wippy lint

3. 검증

wippy publish --dry-run

게시는 --dry-run 여부와 관계없이 동일한 방식으로 패키지를 빌드하므로, 검증은 실제 게시가 만들어낼 모든 것을 포괄합니다:

  • organizationmodule은 소문자 영숫자에 하이픈을 내부에만 포함해야 하고, version은 semver여야 하며, type은 네 가지 모듈 타입 중 하나여야 합니다.
  • publish.runtime은 애플리케이션 전용입니다: type: application 없이 그 아래에 source, sections, vars를 선언하면 실패합니다.
  • meta.artifact.format을 선언한 모든 리소스는 해당 포맷이 검사합니다. 잘못된 아티팩트는 소비자 쪽이 아니라 여기서 실패하며, 출력이 겹치는 디렉토리에 놓이게 되는 두 아티팩트는 거부됩니다.
  • node-package 포맷은 추가로 package.json게시되는 모듈 버전과 동일한 시맨틱 version과 유효한 패키지 name을 담을 것, 그리고 preinstall, install, postinstall, prepare 라이프사이클 스크립트가 없을 것을 요구합니다.

릴리스 중에 발목을 잡는 것은 마지막 규칙입니다: wippy.yaml과 아티팩트의 package.json에서 version을 함께 올리지 않으면 게시가 중단됩니다.

4. 게시

wippy publish --version 1.0.0

릴리스 노트와 함께:

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

추가 플래그

플래그 설명
--label <name> 불변 버전 대신 가변 레이블(예: latest, beta)로 게시
--protected 게시된 버전을 보호됨으로 표시 (삭제 또는 덮어쓰기 불가)
--registry <url> 이 게시에 대해 레지스트리 URL 재정의
--config <dir> wippy.yaml을 포함한 디렉토리 (기본값: 현재 디렉토리)
--create 모듈이 아직 존재하지 않으면 허브에 등록한 뒤 게시
--module-visibility <v> --create에 대한 가시성: private(기본값) 또는 public
--module-type <t> 모듈 타입: library, application, agent, 또는 plugin (wippy.yaml의 type:을 재정의)
--module-display-name <n> --create에 대한 표시 이름

정적 파일 임베딩

fs.directory 엔트리(정적 자산, 템플릿, 공개 파일)가 있는 모듈은 게시된 패키지에 포함하려면 --embed를 사용해야 합니다. 그렇지 않으면 fs.directory 엔트리는 디렉터리 내용 없이 패킹됩니다.

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

--embed 플래그는 fs.directory 엔트리와 일치하는 엔트리 ID 또는 이름을 받습니다. wippy pack에서도 동일한 플래그를 사용할 수 있습니다.

최초 게시

모듈을 처음 게시할 때는 허브에 자동으로 등록되며(기본값은 private), 게시가 한 번 재시도됩니다. --create를 전달하면 모듈을 미리 등록하고 속성을 설정할 수 있습니다:

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

--create는 멱등적입니다 — 이미 등록된 모듈에 대해서는 create 단계가 아무 동작도 하지 않습니다. 계정이 해당 조직에서 모듈을 생성할 수 없는 경우, 허브는 게시 대신 권한 오류를 반환합니다.

로컬 허브에 게시

--registry를 로컬에서 실행 중인 허브로 지정하면 공개 레지스트리 없이 게시하고 설치할 수 있습니다. 평문 HTTP는 로컬 호스트에서만 허용됩니다 — localhost, 127.0.0.1, 그리고 컨테이너 별칭인 host.docker.internal(Docker Desktop / OrbStack)과 host.containers.internal(Podman). 그 외의 호스트는 HTTPS를 사용해야 합니다.

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

레지스트리와 토큰은 WIPPY_REGISTRYWIPPY_TOKEN 환경 변수로도 지정할 수 있습니다. 설정하지 않으면 레지스트리는 기본적으로 https://hub.wippy.ai가 됩니다.

할당량

조직의 private 모듈 할당량이 소진되면 게시는 cannot publish: Private-module quota exhausted (5 of 5)...와 같은 메시지와 함께 실패합니다. 모듈을 public으로 만들거나 조직 관리자에게 할당량 상향을 요청하세요. 업로드와 다운로드는 일시적인 네트워크 오류 발생 시 자동으로 재시도됩니다.

런타임 기본값 게시 {#publishing-runtime-defaults}

애플리케이션(type: application 전용)은 wippy.yamlpublish.runtime을 통해 팩 안에 런타임 설정 기본값을 실어 보낼 수 있습니다:

type: application
publish:
  runtime:
    source: .wippy.yaml            # default: .wippy.yaml
    sections: [security, registry, override]
    vars: [public_url]
필드 설명
source 섹션을 읽어올 설정 파일 (기본값: .wippy.yaml)
sections 기본값으로 팩 메타데이터에 복사되는 런타임 설정 섹션
vars 참조되지 않아도 팩에 포함할 변수의 명시적 허용 목록

규칙:

  • 선택된 섹션이나 게시된 프로파일이 참조하는 변수만 팩에 포함됩니다 (전이적으로 추적됨); 그 외에는 vars 항목이 필요합니다.
  • 내보내는 설정 안의 ${env:...} 참조는 거부됩니다 — 게시자의 환경은 절대 팩으로 유출되지 않습니다.
  • 머신 로컬 섹션인 boot, extensions, workspace는 내보낼 수 없습니다.
  • 메인 애플리케이션 팩만 호스트 런타임 기본값을 제공합니다; 의존성 팩의 런타임 메타데이터는 무시됩니다.

대상 환경에서 설정은 낮은 것부터 높은 것 순으로 적용됩니다: 앱 팩 기본값, 런타임 내장 기본값, 로컬 설정 파일, 선택된 프로파일, CLI 오버라이드.

프로파일 게시 {#publishing-profiles}

루트 애플리케이션 프로파일은 팩의 runtime.profiles 메타데이터로 내보내집니다. 게시는 프로파일을 선택하거나 굽지 않습니다 — 소비자가 실행 시점에 wippy run --profile <name>으로 선택합니다:

publish:
  profiles:
    enabled: true
    source: config/profiles.yaml   # default: .wippy.yaml
    include: [production]          # omit to publish all non-workspace profiles

include: []는 아무것도 게시하지 않습니다; 알 수 없는 이름은 게시를 실패시킵니다. workspace 하위 섹션은 게시된 프로파일 안에서도 절대 내보내지지 않습니다. 프로파일 선언은 설정을 참조하세요.

게시된 모듈 사용

의존성 추가

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

요구사항 설정

런타임에 값 재정의:

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

또는 .wippy.yaml에서:

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

코드에서 임포트

# 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

전체 예제

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

게시:

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

참고