Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 670d26408d | |||
|
ef0c22aecc
|
|||
|
c6e0a45bb4
|
|||
|
154c3f77e4
|
|||
| c72d872557 | |||
|
02b0377b1d
|
|||
|
f27c73195f
|
|||
|
c2b64de621
|
|||
| d7d6662804 | |||
|
2878699082
|
|||
| b551493110 | |||
|
24e6e2b4a1
|
|||
| 7f540b4510 | |||
| 92d7d5563b | |||
| 00f3d5d6d8 | |||
|
f6b43492e9
|
|||
| 457a53f987 |
@@ -144,3 +144,4 @@ Makefile.local
|
||||
# ============================================================
|
||||
wiki/
|
||||
docs/
|
||||
.claude/
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# INGROUP: MokoCLI.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||
# PATH: /.mokogit/workflows/auto-release.yml
|
||||
# VERSION: 05.01.02
|
||||
# VERSION: 05.02.00
|
||||
# BRIEF: Universal build & release � detects platform from metadata API
|
||||
#
|
||||
# +=======================================================================+
|
||||
@@ -39,6 +39,12 @@ on:
|
||||
- '.gitattributes'
|
||||
- '.gitmessage'
|
||||
- 'LICENSE'
|
||||
# Daily safety-net: catch merges whose pull_request event never created a run
|
||||
# (e.g. this workflow file being re-synced from the template concurrently with the
|
||||
# merge). Off-round minute to avoid a fleet-wide spike. The safety-net job below
|
||||
# only dispatches a release when main actually has unreleased changes.
|
||||
schedule:
|
||||
- cron: '37 8 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
@@ -505,3 +511,51 @@ jobs:
|
||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Release | [View](${MOKOGIT_URL}/${GIT_ORG}/${GIT_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ── Scheduled safety-net ─────────────────────────────────────────────────────────
|
||||
# A merge to main normally fires the `release` job via the pull_request `closed`
|
||||
# event. If that event never creates a run (observed when this workflow file is
|
||||
# being re-synced from the template in the same window as the merge), the release
|
||||
# is silently skipped. This daily job self-heals that: if main's CHANGELOG
|
||||
# [Unreleased] section still has content — which the release job empties on a
|
||||
# successful promote — it dispatches a normal release. Dependency-free (awk only)
|
||||
# so it runs on any runner; never touches the release job's own conditions.
|
||||
scheduled-safety-net:
|
||||
name: Scheduled release safety-net
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'schedule' && !startsWith(github.event.repository.name, 'Template-')
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Dispatch release if main has unreleased changes
|
||||
env:
|
||||
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||
run: |
|
||||
# [Unreleased] is non-empty precisely when changes were merged but not yet
|
||||
# released (the release job promotes it to a version and empties it).
|
||||
NOTES=""
|
||||
if [ -f CHANGELOG.md ]; then
|
||||
NOTES=$(awk '/^## \[Unreleased\]/{f=1;next} /^## \[/{if(f)exit} f{print}' CHANGELOG.md)
|
||||
fi
|
||||
if [ -z "$(printf '%s' "$NOTES" | tr -d '[:space:]')" ]; then
|
||||
echo "No unreleased changes on main — safety-net has nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
echo "Unreleased changes detected on main — a merge release was likely missed."
|
||||
echo "Dispatching a release via the safety-net."
|
||||
HTTP=$(curl -s -o /dev/null -w '%{http_code}' -X POST \
|
||||
-H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"ref":"main","inputs":{"action":"release"}}' \
|
||||
"${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}/actions/workflows/auto-release.yml/dispatches")
|
||||
if [ "$HTTP" = "204" ] || [ "$HTTP" = "201" ] || [ "$HTTP" = "200" ]; then
|
||||
echo "Release dispatched (HTTP $HTTP)."
|
||||
else
|
||||
echo "::warning::Safety-net release dispatch failed (HTTP $HTTP)"
|
||||
fi
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# BRIEF: Build and deploy to the Dev environment on push to the dev branch.
|
||||
# Portable across Go server repos: ALL deployment config comes from repo
|
||||
# Actions variables (vars.*) and secrets (secrets.*), nothing hardcoded.
|
||||
# The dev branch is the ongoing integration branch.
|
||||
# OWNER: Template-Go (canonical source; syncs to the root workflows dir). See Template-Go#3.
|
||||
# BRIEF: Deploy to the Dev environment on push to the dev branch. THIN TRIGGER —
|
||||
# the deploy LOGIC lives in mokocli (cli/deploy.php) and server-side in the
|
||||
# restricted per-repo deploy pattern (.vault system/deploy, runbook 16).
|
||||
# This workflow validates + invokes only; it carries NO registry token and
|
||||
# NO build script. A leaked DEPLOY_SSH_KEY can only redeploy this one repo.
|
||||
# OWNER: Template-Go (canonical; syncs to each repo's .mokogit/workflows).
|
||||
#
|
||||
# Required repo VARIABLES (all tier-scoped so each environment is independent —
|
||||
# a repo may host its rc/dev/prod tiers on different machines):
|
||||
# DEV_SSH_HOST, DEV_SSH_PORT, DEV_SSH_USERNAME - SSH deploy target for the dev tier
|
||||
# DEV_REGISTRY, DEV_REGISTRY_USER, DEV_IMAGE - container registry + login user + image
|
||||
# DEV_CONTAINER - compose service/container to recreate
|
||||
# DEV_COMPOSE_PROJECT - docker compose -p project name
|
||||
# DEV_COMPOSE_DIR - dir containing docker-compose.yml on host
|
||||
# DEV_SOURCE_DIR - build source checkout on host
|
||||
# DEV_TAG_ENV - compose env-var name that pins the image tag
|
||||
# DEV_HEALTH_URL - external URL to verify after deploy
|
||||
# Required SECRETS (already configured org-wide; reused, not re-set):
|
||||
# DEPLOY_SSH_KEY - deploy private key (repo/org secret)
|
||||
# MOKOGIT_TOKEN - registry/API token (org secret)
|
||||
# Required repo VARIABLES (tier-scoped): DEV_SSH_HOST, DEV_SSH_PORT, DEV_SSH_USERNAME.
|
||||
# Required SECRET: DEPLOY_SSH_KEY (the deploy-<repo> private key).
|
||||
# ONBOARDING: a repo joins the restricted deploy pattern when DEV_SSH_USERNAME is
|
||||
# set to deploy-<repo>. Un-onboarded go repos skip the job (guards below).
|
||||
|
||||
name: Deploy (Dev)
|
||||
|
||||
@@ -26,21 +18,18 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
# Manual trigger for isolated end-to-end tests.
|
||||
# Runs on the ref it is dispatched from.
|
||||
workflow_dispatch:
|
||||
|
||||
# No `concurrency:` block: it triggers a MokoGIT Actions run-creation bug that
|
||||
# silently drops deploys on rapid pushes to the branch. Do not re-add until the
|
||||
# upstream bug is confirmed fixed.
|
||||
# No `concurrency:` block: triggers a MokoGIT run-creation bug that drops deploys.
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
deploy-dev:
|
||||
name: "Build & Deploy to Dev"
|
||||
name: "Deploy to Dev"
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ vars.DEV_SSH_USERNAME != '' }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
@@ -62,75 +51,20 @@ jobs:
|
||||
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
|
||||
- name: Build and deploy to RC via SSH
|
||||
- name: Set up mokocli (deploy logic)
|
||||
env:
|
||||
REGISTRY_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||
TAG: ${{ steps.config.outputs.tag }}
|
||||
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||
run: |
|
||||
# Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell
|
||||
# via an env prefix; a *quoted* heredoc keeps every $var expanding once, on the
|
||||
# remote. Repo variables (vars.*) are substituted inline by Actions before ssh.
|
||||
ssh -i ~/.ssh/deploy_key -p ${{ vars.DEV_SSH_PORT }} \
|
||||
-o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
|
||||
${{ vars.DEV_SSH_USERNAME }}@${{ vars.DEV_SSH_HOST }} \
|
||||
"TAG='$TAG' REGISTRY_TOKEN='$REGISTRY_TOKEN' bash -s" <<'DEPLOY_EOF'
|
||||
set -e
|
||||
echo 'SSH connected to Dev environment'
|
||||
command -v php >/dev/null || { sudo apt-get update -qq && sudo apt-get install -y -qq php-cli; }
|
||||
git clone --depth 1 "https://x-access-token:${MOKOGIT_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoCLI.git" "$RUNNER_TEMP/mokocli"
|
||||
echo "MOKO_CLI=$RUNNER_TEMP/mokocli/cli" >> $GITHUB_ENV
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
echo 'ERROR: TAG is empty; refusing to build an untagged image' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HEALTH_FMT='{{.State.Health.Status}}'
|
||||
|
||||
echo 'Cleaning Docker build cache...'
|
||||
docker builder prune -af 2>/dev/null || true
|
||||
docker image prune -af 2>/dev/null || true
|
||||
|
||||
echo 'Pulling source...'
|
||||
SOURCE_DIR='${{ vars.DEV_SOURCE_DIR }}'
|
||||
if [ ! -d "$SOURCE_DIR/.git" ]; then
|
||||
git clone -b dev https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git "$SOURCE_DIR"
|
||||
fi
|
||||
cd "$SOURCE_DIR"
|
||||
git remote set-url origin https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git 2>/dev/null || true
|
||||
git fetch origin dev
|
||||
git reset --hard origin/dev
|
||||
|
||||
echo "Building image: ${{ vars.DEV_REGISTRY }}/${{ vars.DEV_IMAGE }}:$TAG"
|
||||
docker build --no-cache --build-arg GOFLAGS='-p 1' \
|
||||
--tag "${{ vars.DEV_REGISTRY }}/${{ vars.DEV_IMAGE }}:$TAG" \
|
||||
-f Dockerfile .
|
||||
|
||||
echo 'Pushing to registry...'
|
||||
echo "$REGISTRY_TOKEN" | docker login ${{ vars.DEV_REGISTRY }} -u ${{ vars.DEV_REGISTRY_USER }} --password-stdin
|
||||
docker push "${{ vars.DEV_REGISTRY }}/${{ vars.DEV_IMAGE }}:$TAG"
|
||||
|
||||
echo 'Restarting Dev container...'
|
||||
cd '${{ vars.DEV_COMPOSE_DIR }}'
|
||||
# Drive the rc service image tag via its compose env-var (no sed on the shared
|
||||
# file); remove any lingering fixed-name container first, then force-recreate.
|
||||
docker rm -f '${{ vars.DEV_CONTAINER }}' 2>/dev/null || true
|
||||
${{ vars.DEV_TAG_ENV }}="$TAG" docker compose -p '${{ vars.DEV_COMPOSE_PROJECT }}' up -d --force-recreate '${{ vars.DEV_CONTAINER }}'
|
||||
|
||||
echo 'Health check...'
|
||||
for i in 1 2 3 4 5 6 7 8; do
|
||||
sleep 15
|
||||
if docker inspect --format="$HEALTH_FMT" '${{ vars.DEV_CONTAINER }}' 2>/dev/null | grep -q healthy; then
|
||||
echo 'Dev container healthy!'
|
||||
exit 0
|
||||
fi
|
||||
echo "Waiting... (attempt $i/8)"
|
||||
done
|
||||
echo 'Health check failed'
|
||||
docker logs '${{ vars.DEV_CONTAINER }}' --tail 20
|
||||
exit 1
|
||||
DEPLOY_EOF
|
||||
|
||||
- name: Verify Dev instance
|
||||
continue-on-error: true
|
||||
- name: Deploy (mokocli triggers the restricted deploy pattern)
|
||||
run: |
|
||||
sleep 5
|
||||
ssh -i ~/.ssh/deploy_key -p ${{ vars.DEV_SSH_PORT }} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ vars.DEV_SSH_USERNAME }}@${{ vars.DEV_SSH_HOST }} "curl -sf '${{ vars.DEV_HEALTH_URL }}'" && echo " Dev API healthy"
|
||||
if [ -z "${{ vars.DEV_SSH_USERNAME }}" ]; then
|
||||
echo "DEV_SSH_USERNAME unset — repo not onboarded to the restricted deploy pattern; skipping."
|
||||
exit 0
|
||||
fi
|
||||
php "$MOKO_CLI/deploy.php" --tier dev --tag "${{ steps.config.outputs.tag }}" \
|
||||
--ssh-host "${{ vars.DEV_SSH_HOST }}" --ssh-port "${{ vars.DEV_SSH_PORT }}" \
|
||||
--ssh-user "${{ vars.DEV_SSH_USERNAME }}" --ssh-key ~/.ssh/deploy_key
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# BRIEF: Build and deploy to the Prod environment on push to the main branch.
|
||||
# Portable across Go server repos: ALL deployment config comes from repo
|
||||
# Actions variables (vars.*) and secrets (secrets.*), nothing hardcoded.
|
||||
# Prod deploys on merge to main (dev -> rc -> main pipeline).
|
||||
# OWNER: Template-Go (canonical source; syncs to the root workflows dir). See Template-Go#3.
|
||||
# BRIEF: Deploy to the Prod environment on push to the main branch. THIN TRIGGER —
|
||||
# the deploy LOGIC lives in mokocli (cli/deploy.php) and server-side in the
|
||||
# restricted per-repo deploy pattern (.vault system/deploy, runbook 16).
|
||||
# This workflow validates + invokes only; it carries NO registry token and
|
||||
# NO build script. A leaked DEPLOY_SSH_KEY can only redeploy this one repo.
|
||||
# OWNER: Template-Go (canonical; syncs to each repo's .mokogit/workflows).
|
||||
#
|
||||
# Required repo VARIABLES (all tier-scoped so each environment is independent —
|
||||
# a repo may host its rc/dev/prod tiers on different machines):
|
||||
# PROD_SSH_HOST, PROD_SSH_PORT, PROD_SSH_USERNAME - SSH deploy target for the prod tier
|
||||
# PROD_REGISTRY, PROD_REGISTRY_USER, PROD_IMAGE - container registry + login user + image
|
||||
# PROD_CONTAINER - compose service/container to recreate
|
||||
# PROD_COMPOSE_PROJECT - docker compose -p project name
|
||||
# PROD_COMPOSE_DIR - dir containing docker-compose.yml on host
|
||||
# PROD_SOURCE_DIR - build source checkout on host
|
||||
# PROD_TAG_ENV - compose env-var name that pins the image tag
|
||||
# PROD_HEALTH_URL - external URL to verify after deploy
|
||||
# Required SECRETS (already configured org-wide; reused, not re-set):
|
||||
# DEPLOY_SSH_KEY - deploy private key (repo/org secret)
|
||||
# MOKOGIT_TOKEN - registry/API token (org secret)
|
||||
# Required repo VARIABLES (tier-scoped): PROD_SSH_HOST, PROD_SSH_PORT, PROD_SSH_USERNAME.
|
||||
# Required SECRET: DEPLOY_SSH_KEY (the deploy-<repo> private key).
|
||||
# ONBOARDING: a repo joins the restricted deploy pattern when PROD_SSH_USERNAME is
|
||||
# set to deploy-<repo>. Un-onboarded go repos skip the job (guards below).
|
||||
|
||||
name: Deploy (Prod)
|
||||
|
||||
@@ -26,21 +18,18 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
# Manual trigger for a prod re-deploy.
|
||||
# Runs on the ref it is dispatched from (use main).
|
||||
workflow_dispatch:
|
||||
|
||||
# No `concurrency:` block: it triggers a MokoGIT Actions run-creation bug that
|
||||
# silently drops deploys on rapid pushes to the branch. Do not re-add until the
|
||||
# upstream bug is confirmed fixed.
|
||||
# No `concurrency:` block: triggers a MokoGIT run-creation bug that drops deploys.
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
deploy-prod:
|
||||
name: "Build & Deploy to Prod"
|
||||
name: "Deploy to Prod"
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ vars.PROD_SSH_USERNAME != '' }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
@@ -62,75 +51,20 @@ jobs:
|
||||
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
|
||||
- name: Build and deploy to RC via SSH
|
||||
- name: Set up mokocli (deploy logic)
|
||||
env:
|
||||
REGISTRY_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||
TAG: ${{ steps.config.outputs.tag }}
|
||||
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||
run: |
|
||||
# Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell
|
||||
# via an env prefix; a *quoted* heredoc keeps every $var expanding once, on the
|
||||
# remote. Repo variables (vars.*) are substituted inline by Actions before ssh.
|
||||
ssh -i ~/.ssh/deploy_key -p ${{ vars.PROD_SSH_PORT }} \
|
||||
-o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
|
||||
${{ vars.PROD_SSH_USERNAME }}@${{ vars.PROD_SSH_HOST }} \
|
||||
"TAG='$TAG' REGISTRY_TOKEN='$REGISTRY_TOKEN' bash -s" <<'DEPLOY_EOF'
|
||||
set -e
|
||||
echo 'SSH connected to Prod environment'
|
||||
command -v php >/dev/null || { sudo apt-get update -qq && sudo apt-get install -y -qq php-cli; }
|
||||
git clone --depth 1 "https://x-access-token:${MOKOGIT_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoCLI.git" "$RUNNER_TEMP/mokocli"
|
||||
echo "MOKO_CLI=$RUNNER_TEMP/mokocli/cli" >> $GITHUB_ENV
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
echo 'ERROR: TAG is empty; refusing to build an untagged image' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HEALTH_FMT='{{.State.Health.Status}}'
|
||||
|
||||
echo 'Cleaning Docker build cache...'
|
||||
docker builder prune -af 2>/dev/null || true
|
||||
docker image prune -af 2>/dev/null || true
|
||||
|
||||
echo 'Pulling source...'
|
||||
SOURCE_DIR='${{ vars.PROD_SOURCE_DIR }}'
|
||||
if [ ! -d "$SOURCE_DIR/.git" ]; then
|
||||
git clone -b main https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git "$SOURCE_DIR"
|
||||
fi
|
||||
cd "$SOURCE_DIR"
|
||||
git remote set-url origin https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git 2>/dev/null || true
|
||||
git fetch origin main
|
||||
git reset --hard origin/main
|
||||
|
||||
echo "Building image: ${{ vars.PROD_REGISTRY }}/${{ vars.PROD_IMAGE }}:$TAG"
|
||||
docker build --no-cache --build-arg GOFLAGS='-p 1' \
|
||||
--tag "${{ vars.PROD_REGISTRY }}/${{ vars.PROD_IMAGE }}:$TAG" \
|
||||
-f Dockerfile .
|
||||
|
||||
echo 'Pushing to registry...'
|
||||
echo "$REGISTRY_TOKEN" | docker login ${{ vars.PROD_REGISTRY }} -u ${{ vars.PROD_REGISTRY_USER }} --password-stdin
|
||||
docker push "${{ vars.PROD_REGISTRY }}/${{ vars.PROD_IMAGE }}:$TAG"
|
||||
|
||||
echo 'Restarting Prod container...'
|
||||
cd '${{ vars.PROD_COMPOSE_DIR }}'
|
||||
# Drive the rc service image tag via its compose env-var (no sed on the shared
|
||||
# file); remove any lingering fixed-name container first, then force-recreate.
|
||||
docker rm -f '${{ vars.PROD_CONTAINER }}' 2>/dev/null || true
|
||||
${{ vars.PROD_TAG_ENV }}="$TAG" docker compose -p '${{ vars.PROD_COMPOSE_PROJECT }}' up -d --force-recreate '${{ vars.PROD_CONTAINER }}'
|
||||
|
||||
echo 'Health check...'
|
||||
for i in 1 2 3 4 5 6 7 8; do
|
||||
sleep 15
|
||||
if docker inspect --format="$HEALTH_FMT" '${{ vars.PROD_CONTAINER }}' 2>/dev/null | grep -q healthy; then
|
||||
echo 'Prod container healthy!'
|
||||
exit 0
|
||||
fi
|
||||
echo "Waiting... (attempt $i/8)"
|
||||
done
|
||||
echo 'Health check failed'
|
||||
docker logs '${{ vars.PROD_CONTAINER }}' --tail 20
|
||||
exit 1
|
||||
DEPLOY_EOF
|
||||
|
||||
- name: Verify Prod instance
|
||||
continue-on-error: true
|
||||
- name: Deploy (mokocli triggers the restricted deploy pattern)
|
||||
run: |
|
||||
sleep 5
|
||||
ssh -i ~/.ssh/deploy_key -p ${{ vars.PROD_SSH_PORT }} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ vars.PROD_SSH_USERNAME }}@${{ vars.PROD_SSH_HOST }} "curl -sf '${{ vars.PROD_HEALTH_URL }}'" && echo " Prod API healthy"
|
||||
if [ -z "${{ vars.PROD_SSH_USERNAME }}" ]; then
|
||||
echo "PROD_SSH_USERNAME unset — repo not onboarded to the restricted deploy pattern; skipping."
|
||||
exit 0
|
||||
fi
|
||||
php "$MOKO_CLI/deploy.php" --tier prod --tag "${{ steps.config.outputs.tag }}" \
|
||||
--ssh-host "${{ vars.PROD_SSH_HOST }}" --ssh-port "${{ vars.PROD_SSH_PORT }}" \
|
||||
--ssh-user "${{ vars.PROD_SSH_USERNAME }}" --ssh-key ~/.ssh/deploy_key
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# BRIEF: Build and deploy to the RC environment on push to the rc branch.
|
||||
# Portable across Go server repos: ALL deployment config comes from repo
|
||||
# Actions variables (vars.*) and secrets (secrets.*), nothing hardcoded.
|
||||
# The rc branch is created by promote-rc when a PR to main opens.
|
||||
# OWNER: Template-Go (canonical source; syncs to the root workflows dir). See Template-Go#3.
|
||||
# BRIEF: Deploy to the RC environment on push to the rc branch. THIN TRIGGER —
|
||||
# the deploy LOGIC lives in mokocli (cli/deploy.php) and server-side in the
|
||||
# restricted per-repo deploy pattern (.vault system/deploy, runbook 16).
|
||||
# This workflow validates + invokes only; it carries NO registry token and
|
||||
# NO build script. A leaked DEPLOY_SSH_KEY can only redeploy this one repo.
|
||||
# OWNER: Template-Go (canonical; syncs to each repo's .mokogit/workflows).
|
||||
#
|
||||
# Required repo VARIABLES (all tier-scoped so each environment is independent —
|
||||
# a repo may host its rc/dev/prod tiers on different machines):
|
||||
# RC_SSH_HOST, RC_SSH_PORT, RC_SSH_USERNAME - SSH deploy target for the rc tier
|
||||
# RC_REGISTRY, RC_REGISTRY_USER, RC_IMAGE - container registry + login user + image
|
||||
# RC_CONTAINER - compose service/container to recreate
|
||||
# RC_COMPOSE_PROJECT - docker compose -p project name
|
||||
# RC_COMPOSE_DIR - dir containing docker-compose.yml on host
|
||||
# RC_SOURCE_DIR - build source checkout on host
|
||||
# RC_TAG_ENV - compose env-var name that pins the image tag
|
||||
# RC_HEALTH_URL - external URL to verify after deploy
|
||||
# Required SECRETS (already configured org-wide; reused, not re-set):
|
||||
# DEPLOY_SSH_KEY - deploy private key (repo/org secret)
|
||||
# MOKOGIT_TOKEN - registry/API token (org secret)
|
||||
# Required repo VARIABLES (tier-scoped): RC_SSH_HOST, RC_SSH_PORT, RC_SSH_USERNAME.
|
||||
# Required SECRET: DEPLOY_SSH_KEY (the deploy-<repo> private key).
|
||||
# ONBOARDING: a repo joins the restricted deploy pattern when RC_SSH_USERNAME is
|
||||
# set to deploy-<repo>. Un-onboarded go repos skip the job (guards below), so this
|
||||
# template is safe to cascade to every go repo.
|
||||
|
||||
name: Deploy (RC)
|
||||
|
||||
@@ -26,23 +19,19 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- rc
|
||||
# Manual trigger for isolated end-to-end tests without a full RC promotion.
|
||||
# Runs on the ref it is dispatched from; that ref must carry current source
|
||||
# (>= the RC database migration version) or the rebuilt image will refuse the
|
||||
# newer DB. Dispatch from `rc` once `rc` is current.
|
||||
workflow_dispatch:
|
||||
|
||||
# No `concurrency:` block: it triggers a MokoGIT Actions run-creation bug that
|
||||
# silently drops deploys on rapid pushes to the branch. Do not re-add until the
|
||||
# upstream bug is confirmed fixed.
|
||||
# No `concurrency:` block: triggers a MokoGIT run-creation bug that drops deploys.
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
deploy-rc:
|
||||
name: "Build & Deploy to RC"
|
||||
name: "Deploy to RC"
|
||||
runs-on: ubuntu-latest
|
||||
# Guard 1 (job-level): skip entirely unless onboarded.
|
||||
if: ${{ vars.RC_SSH_USERNAME != '' }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
@@ -64,75 +53,21 @@ jobs:
|
||||
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
|
||||
- name: Build and deploy to RC via SSH
|
||||
- name: Set up mokocli (deploy logic)
|
||||
env:
|
||||
REGISTRY_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||
TAG: ${{ steps.config.outputs.tag }}
|
||||
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||
run: |
|
||||
# Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell
|
||||
# via an env prefix; a *quoted* heredoc keeps every $var expanding once, on the
|
||||
# remote. Repo variables (vars.*) are substituted inline by Actions before ssh.
|
||||
ssh -i ~/.ssh/deploy_key -p ${{ vars.RC_SSH_PORT }} \
|
||||
-o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
|
||||
${{ vars.RC_SSH_USERNAME }}@${{ vars.RC_SSH_HOST }} \
|
||||
"TAG='$TAG' REGISTRY_TOKEN='$REGISTRY_TOKEN' bash -s" <<'DEPLOY_EOF'
|
||||
set -e
|
||||
echo 'SSH connected to RC environment'
|
||||
command -v php >/dev/null || { sudo apt-get update -qq && sudo apt-get install -y -qq php-cli; }
|
||||
git clone --depth 1 "https://x-access-token:${MOKOGIT_TOKEN}@git.mokoconsulting.tech/MokoConsulting/MokoCLI.git" "$RUNNER_TEMP/mokocli"
|
||||
echo "MOKO_CLI=$RUNNER_TEMP/mokocli/cli" >> $GITHUB_ENV
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
echo 'ERROR: TAG is empty; refusing to build an untagged image' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HEALTH_FMT='{{.State.Health.Status}}'
|
||||
|
||||
echo 'Cleaning Docker build cache...'
|
||||
docker builder prune -af 2>/dev/null || true
|
||||
docker image prune -af 2>/dev/null || true
|
||||
|
||||
echo 'Pulling source...'
|
||||
SOURCE_DIR='${{ vars.RC_SOURCE_DIR }}'
|
||||
if [ ! -d "$SOURCE_DIR/.git" ]; then
|
||||
git clone -b rc https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git "$SOURCE_DIR"
|
||||
fi
|
||||
cd "$SOURCE_DIR"
|
||||
git remote set-url origin https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git 2>/dev/null || true
|
||||
git fetch origin rc
|
||||
git reset --hard origin/rc
|
||||
|
||||
echo "Building image: ${{ vars.RC_REGISTRY }}/${{ vars.RC_IMAGE }}:$TAG"
|
||||
docker build --no-cache --build-arg GOFLAGS='-p 1' \
|
||||
--tag "${{ vars.RC_REGISTRY }}/${{ vars.RC_IMAGE }}:$TAG" \
|
||||
-f Dockerfile .
|
||||
|
||||
echo 'Pushing to registry...'
|
||||
echo "$REGISTRY_TOKEN" | docker login ${{ vars.RC_REGISTRY }} -u ${{ vars.RC_REGISTRY_USER }} --password-stdin
|
||||
docker push "${{ vars.RC_REGISTRY }}/${{ vars.RC_IMAGE }}:$TAG"
|
||||
|
||||
echo 'Restarting RC container...'
|
||||
cd '${{ vars.RC_COMPOSE_DIR }}'
|
||||
# Drive the rc service image tag via its compose env-var (no sed on the shared
|
||||
# file); remove any lingering fixed-name container first, then force-recreate.
|
||||
docker rm -f '${{ vars.RC_CONTAINER }}' 2>/dev/null || true
|
||||
${{ vars.RC_TAG_ENV }}="$TAG" docker compose -p '${{ vars.RC_COMPOSE_PROJECT }}' up -d --force-recreate '${{ vars.RC_CONTAINER }}'
|
||||
|
||||
echo 'Health check...'
|
||||
for i in 1 2 3 4 5 6 7 8; do
|
||||
sleep 15
|
||||
if docker inspect --format="$HEALTH_FMT" '${{ vars.RC_CONTAINER }}' 2>/dev/null | grep -q healthy; then
|
||||
echo 'RC container healthy!'
|
||||
exit 0
|
||||
fi
|
||||
echo "Waiting... (attempt $i/8)"
|
||||
done
|
||||
echo 'Health check failed'
|
||||
docker logs '${{ vars.RC_CONTAINER }}' --tail 20
|
||||
exit 1
|
||||
DEPLOY_EOF
|
||||
|
||||
- name: Verify RC instance
|
||||
continue-on-error: true
|
||||
- name: Deploy (mokocli triggers the restricted deploy pattern)
|
||||
run: |
|
||||
sleep 5
|
||||
ssh -i ~/.ssh/deploy_key -p ${{ vars.RC_SSH_PORT }} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ vars.RC_SSH_USERNAME }}@${{ vars.RC_SSH_HOST }} "curl -sf '${{ vars.RC_HEALTH_URL }}'" && echo " RC API healthy"
|
||||
# Guard 2 (step-level, reliable): no-op if not onboarded.
|
||||
if [ -z "${{ vars.RC_SSH_USERNAME }}" ]; then
|
||||
echo "RC_SSH_USERNAME unset — repo not onboarded to the restricted deploy pattern; skipping."
|
||||
exit 0
|
||||
fi
|
||||
php "$MOKO_CLI/deploy.php" --tier rc --tag "${{ steps.config.outputs.tag }}" \
|
||||
--ssh-host "${{ vars.RC_SSH_HOST }}" --ssh-port "${{ vars.RC_SSH_PORT }}" \
|
||||
--ssh-user "${{ vars.RC_SSH_USERNAME }}" --ssh-key ~/.ssh/deploy_key
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- **Update server (Joomla): serve the installable package, not the source archive** — `GenerateJoomlaXML` selected the first `.zip` attachment on a release, so releases carrying extra zips (e.g. `<repo>-<channel>-source.zip` ahead of `pkg_*.zip`) advertised the raw source archive; Joomla then downloaded a non-installable zip and failed with "Install path does not exist" (prerelease/dev channels). New `selectJoomlaArtifact()` prefers an extension-prefixed package (`pkg_`/`com_`/`mod_`/`plg_`/`tpl_`/`lib_`) and never a `-source` archive (hotfixed to prod)
|
||||
- **Actions: `.mokogit/workflows` custom-path detection restored** — `WorkflowDirs` now scans `.mokogit/workflows`; the legacy `.mokogitea/workflows` is fully retired. Workflow indexing, push-triggered CI/deploys, and `workflow_dispatch` work again (#798)
|
||||
- **Issue custom-status dropdown shows its options again** — the status `<select>` carried the `ui compact dropdown` class, so fomantic turned it into an overlay menu that painted behind the page content (options were present in the DOM but invisible); it is now a plain native `<select>` with its inline styles moved to a stylesheet (`web_src/css/repo/issue-status.css`)
|
||||
- Restored the repo's `.mokogit/workflows/` (deploy + org CI, ~20 workflows) from Template-Go after the workflow sync had dropped them
|
||||
|
||||
@@ -13,18 +13,22 @@ services:
|
||||
environment:
|
||||
- USER_UID=${GIT_UID}
|
||||
- USER_GID=${GIT_GID}
|
||||
- GITEA__server__DOMAIN=${GIT_DOMAIN}
|
||||
- GITEA__server__ROOT_URL=${GIT_ROOT_URL}
|
||||
- GITEA__server__SSH_DOMAIN=${GIT_SSH_DOMAIN}
|
||||
- GITEA__server__SSH_PORT=${GIT_SSH_PUBLIC_PORT}
|
||||
- GITEA__database__DB_TYPE=mysql
|
||||
- GITEA__database__HOST=${GIT_DB_HOST}
|
||||
- GITEA__database__NAME=${GIT_DB_NAME}
|
||||
- GITEA__database__USER=${GIT_DB_USER}
|
||||
- GITEA__database__PASSWD=${GIT_DB_PASSWD}
|
||||
- MOKOGIT__server__DOMAIN=${GIT_DOMAIN}
|
||||
- MOKOGIT__server__ROOT_URL=${GIT_ROOT_URL}
|
||||
- MOKOGIT__server__SSH_DOMAIN=${GIT_SSH_DOMAIN}
|
||||
- MOKOGIT__server__SSH_PORT=${GIT_SSH_PUBLIC_PORT}
|
||||
- MOKOGIT__database__DB_TYPE=mysql
|
||||
- MOKOGIT__database__HOST=${GIT_DB_HOST}
|
||||
- MOKOGIT__database__NAME=${GIT_DB_NAME}
|
||||
- MOKOGIT__database__USER=${GIT_DB_USER}
|
||||
- MOKOGIT__database__PASSWD=${GIT_DB_PASSWD}
|
||||
volumes:
|
||||
# RECONCILE: live app.ini at ${GIT_DATA_DIR}/conf/app.ini (/opt/mokogit/dev/conf).
|
||||
- "${GIT_DATA_DIR}:/data"
|
||||
# Clean disconnect layout (#839): MOKOGIT_CUSTOM=/data/mokogit is baked into the image.
|
||||
# Parent (/data/mokogit) listed before child (/data/mokogit/conf) so Docker attaches it first.
|
||||
- "${GIT_DATA_DIR}:/var/lib/gitea"
|
||||
- "${GIT_DATA_DIR}:/data/mokogit"
|
||||
- "${GIT_DATA_DIR}/conf:/data/mokogit/conf"
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
|
||||
@@ -17,19 +17,23 @@ services:
|
||||
environment:
|
||||
- USER_UID=${GIT_UID}
|
||||
- USER_GID=${GIT_GID}
|
||||
- GITEA__server__DOMAIN=${GIT_DOMAIN}
|
||||
- GITEA__server__ROOT_URL=${GIT_ROOT_URL}
|
||||
- GITEA__server__SSH_DOMAIN=${GIT_SSH_DOMAIN}
|
||||
- GITEA__server__SSH_PORT=${GIT_SSH_PUBLIC_PORT}
|
||||
- MOKOGIT__server__DOMAIN=${GIT_DOMAIN}
|
||||
- MOKOGIT__server__ROOT_URL=${GIT_ROOT_URL}
|
||||
- MOKOGIT__server__SSH_DOMAIN=${GIT_SSH_DOMAIN}
|
||||
- MOKOGIT__server__SSH_PORT=${GIT_SSH_PUBLIC_PORT}
|
||||
# DB is HOST MySQL (RECONCILE host reachability: host-gateway / socket / LAN IP).
|
||||
- GITEA__database__DB_TYPE=mysql
|
||||
- GITEA__database__HOST=${GIT_DB_HOST}
|
||||
- GITEA__database__NAME=${GIT_DB_NAME}
|
||||
- GITEA__database__USER=${GIT_DB_USER}
|
||||
- GITEA__database__PASSWD=${GIT_DB_PASSWD}
|
||||
- MOKOGIT__database__DB_TYPE=mysql
|
||||
- MOKOGIT__database__HOST=${GIT_DB_HOST}
|
||||
- MOKOGIT__database__NAME=${GIT_DB_NAME}
|
||||
- MOKOGIT__database__USER=${GIT_DB_USER}
|
||||
- MOKOGIT__database__PASSWD=${GIT_DB_PASSWD}
|
||||
volumes:
|
||||
# RECONCILE: live app.ini is at ${GIT_DATA_DIR}/conf/app.ini (/opt/mokogit/prod/conf).
|
||||
- "${GIT_DATA_DIR}:/data"
|
||||
# Clean disconnect layout (#839): MOKOGIT_CUSTOM=/data/mokogit is baked into the image.
|
||||
# Parent (/data/mokogit) listed before child (/data/mokogit/conf) so Docker attaches it first.
|
||||
- "${GIT_DATA_DIR}:/var/lib/gitea"
|
||||
- "${GIT_DATA_DIR}:/data/mokogit"
|
||||
- "${GIT_DATA_DIR}/conf:/data/mokogit/conf"
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
|
||||
@@ -13,18 +13,22 @@ services:
|
||||
environment:
|
||||
- USER_UID=${GIT_UID}
|
||||
- USER_GID=${GIT_GID}
|
||||
- GITEA__server__DOMAIN=${GIT_DOMAIN}
|
||||
- GITEA__server__ROOT_URL=${GIT_ROOT_URL}
|
||||
- GITEA__server__SSH_DOMAIN=${GIT_SSH_DOMAIN}
|
||||
- GITEA__server__SSH_PORT=${GIT_SSH_PUBLIC_PORT}
|
||||
- GITEA__database__DB_TYPE=mysql
|
||||
- GITEA__database__HOST=${GIT_DB_HOST}
|
||||
- GITEA__database__NAME=${GIT_DB_NAME}
|
||||
- GITEA__database__USER=${GIT_DB_USER}
|
||||
- GITEA__database__PASSWD=${GIT_DB_PASSWD}
|
||||
- MOKOGIT__server__DOMAIN=${GIT_DOMAIN}
|
||||
- MOKOGIT__server__ROOT_URL=${GIT_ROOT_URL}
|
||||
- MOKOGIT__server__SSH_DOMAIN=${GIT_SSH_DOMAIN}
|
||||
- MOKOGIT__server__SSH_PORT=${GIT_SSH_PUBLIC_PORT}
|
||||
- MOKOGIT__database__DB_TYPE=mysql
|
||||
- MOKOGIT__database__HOST=${GIT_DB_HOST}
|
||||
- MOKOGIT__database__NAME=${GIT_DB_NAME}
|
||||
- MOKOGIT__database__USER=${GIT_DB_USER}
|
||||
- MOKOGIT__database__PASSWD=${GIT_DB_PASSWD}
|
||||
volumes:
|
||||
# RECONCILE: live app.ini at ${GIT_DATA_DIR}/conf/app.ini (/opt/mokogit/rc/conf).
|
||||
- "${GIT_DATA_DIR}:/data"
|
||||
# Clean disconnect layout (#839): MOKOGIT_CUSTOM=/data/mokogit is baked into the image.
|
||||
# Parent (/data/mokogit) listed before child (/data/mokogit/conf) so Docker attaches it first.
|
||||
- "${GIT_DATA_DIR}:/var/lib/gitea"
|
||||
- "${GIT_DATA_DIR}:/data/mokogit"
|
||||
- "${GIT_DATA_DIR}/conf:/data/mokogit/conf"
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
|
||||
@@ -200,6 +200,16 @@ func GenerateReleaseArtifacts(ctx context.Context, rel *repo_model.Release) erro
|
||||
} else if meta != nil {
|
||||
entryPoint = meta.EntryPoint
|
||||
}
|
||||
|
||||
// Joomla repos are packaged by the CI pipeline (release_package.php),
|
||||
// which uploads the correct installable zip/tar.gz. Server-generated
|
||||
// source archives are redundant and confuse users about which file to
|
||||
// download, so skip them entirely.
|
||||
if meta != nil && strings.EqualFold(meta.Platform, "joomla") {
|
||||
log.Info("GenerateReleaseArtifacts: skipping source archives for Joomla repo %d (CI pipeline packages)", rel.RepoID)
|
||||
return nil
|
||||
}
|
||||
|
||||
wantSource := !isRootEntryPoint(entryPoint)
|
||||
|
||||
// Serialize the delete+attach sequence per-release so concurrent
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
auth_model "code.mokoconsulting.tech/MokoConsulting/MokoGIT/models/auth"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// apiMetadataResponse mirrors the subset of the repo manifest/metadata API
|
||||
// payload exercised by this test. The handler struct (routers/api/v1/repo)
|
||||
// is unexported, so the fields are re-declared here by their JSON tags.
|
||||
type apiMetadataResponse struct {
|
||||
Name string `json:"name"`
|
||||
NodeMinimum string `json:"node_minimum"`
|
||||
NpmPackage string `json:"npm_package"`
|
||||
PublishTarget string `json:"publish_target"`
|
||||
}
|
||||
|
||||
// TestAPIRepoMetadataNpmFieldsRoundTrip guards against regression of issue #847
|
||||
// (from #827): the npm/mcp fields node_minimum, npm_package and publish_target
|
||||
// must be persisted via PUT and returned via GET on the metadata endpoint,
|
||||
// rather than being silently dropped by the apiMetadata struct.
|
||||
func TestAPIRepoMetadataNpmFieldsRoundTrip(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
// user1 is the site admin in the fixtures; the PUT route requires admin.
|
||||
session := loginUser(t, "user1")
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
const owner, repo = "user2", "repo1"
|
||||
|
||||
// PUT the three npm/mcp fields.
|
||||
req := NewRequestWithJSON(t, "PUT", "/api/v1/repos/"+owner+"/"+repo+"/metadata", map[string]string{
|
||||
"node_minimum": "18",
|
||||
"npm_package": "@moko/foo",
|
||||
"publish_target": "npm",
|
||||
}).AddTokenAuth(token)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// The PUT response should echo the persisted fields.
|
||||
var put apiMetadataResponse
|
||||
DecodeJSON(t, resp, &put)
|
||||
assert.Equal(t, "18", put.NodeMinimum)
|
||||
assert.Equal(t, "@moko/foo", put.NpmPackage)
|
||||
assert.Equal(t, "npm", put.PublishTarget)
|
||||
|
||||
// A subsequent GET should read the same values back from the DB.
|
||||
req = NewRequest(t, "GET", "/api/v1/repos/"+owner+"/"+repo+"/metadata").AddTokenAuth(token)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var got apiMetadataResponse
|
||||
DecodeJSON(t, resp, &got)
|
||||
assert.Equal(t, "18", got.NodeMinimum)
|
||||
assert.Equal(t, "@moko/foo", got.NpmPackage)
|
||||
assert.Equal(t, "npm", got.PublishTarget)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user