# Build, publish npm packages, and push Docker images
# Runs only on main branch push/tag

variables:
  # Pre-baked CI base (see .woodpecker/ci-image.yml): node:24-alpine +
  # toolchain + warm pnpm store. Kills the second cold install publish pays.
  - &node_image 'git.mosaicstack.dev/mosaicstack/stack/ci-base:latest'
  - &enable_pnpm 'corepack enable'
  # Heavy kaniko image builds (~25 min) — gate them so a merge that only touches
  # the npm-only CLI (@mosaicstack/mosaic) or docs does NOT rebuild the platform
  # images (gateway/appservice/web do not depend on @mosaicstack/mosaic). Releases
  # (tags) always build everything. Exclude-list keeps the default SAFE: any
  # non-excluded change still builds, so no transitive dep can silently go stale.
  # (Woodpecker: `when` entries are OR'd; `path` applies to push/PR only — hence
  # the separate `event: tag` entry.)
  - &image_build_when
    - event: tag
    - event: [push, manual]
      branch: main
      path:
        exclude:
          - 'packages/mosaic/**'
          - 'docs/**'
          - '**/*.md'
          - '.woodpecker/**'

when:
  - branch: [main]
    event: [push, manual, tag]

steps:
  install:
    image: *node_image
    commands:
      - corepack enable
      # Resolve from the baked pnpm store instead of a cold network fetch.
      - pnpm install --frozen-lockfile --prefer-offline

  build:
    image: *node_image
    commands:
      - *enable_pnpm
      - pnpm build
    depends_on:
      - install

  publish-npm:
    image: *node_image
    # Publish only when a publishable package changed (or on a release tag); a
    # pure-docs merge runs no publish. Cheap step, but gated for cleanliness.
    when:
      - event: tag
      - event: [push, manual]
        branch: main
        path:
          include:
            - 'packages/**'
    environment:
      NPM_TOKEN:
        from_secret: gitea_token
    commands:
      - *enable_pnpm
      # Configure auth for Gitea npm registry
      - |
        echo "//git.mosaicstack.dev/api/packages/mosaicstack/npm/:_authToken=$NPM_TOKEN" > ~/.npmrc
        echo "@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/" >> ~/.npmrc
      # Publish non-private packages to Gitea.
      #
      # The only publish failure we tolerate is "version already exists" —
      # that legitimately happens when only some packages were bumped in
      # the merge. Any other failure (registry 404, auth error, network
      # error) MUST fail the pipeline loudly: the previous
      # `|| echo "... continuing"` fallback silently hid a 404 from the
      # Gitea org rename and caused every @mosaicstack/* publish to fall
      # on the floor while CI still reported green.
      - |
        # Portable sh (Alpine ash) — avoid bashisms like PIPESTATUS.
        set +e
        pnpm --filter "@mosaicstack/*" --filter "!@mosaicstack/web" publish --no-git-checks --access public >/tmp/publish.log 2>&1
        EXIT=$?
        set -e
        cat /tmp/publish.log
        if [ "$EXIT" -eq 0 ]; then
          echo "[publish] all packages published successfully"
          exit 0
        fi
        # Hard registry / auth / network errors → fatal. Match npm's own
        # error lines specifically to avoid false positives on arbitrary
        # log text that happens to contain "E404" etc.
        if grep -qE "npm (error|ERR!) code (E404|E401|ENEEDAUTH|ECONNREFUSED|ETIMEDOUT|ENOTFOUND)" /tmp/publish.log; then
          echo "[publish] FATAL: registry/auth/network error detected — failing pipeline" >&2
          exit 1
        fi
        # Only tolerate the explicit "version already published" case.
        # npm returns this as E403 with body "You cannot publish over..."
        # or EPUBLISHCONFLICT depending on version.
        if grep -qE "EPUBLISHCONFLICT|You cannot publish over|previously published" /tmp/publish.log; then
          echo "[publish] some packages already at this version — continuing (non-fatal)"
          exit 0
        fi
        echo "[publish] FATAL: publish failed with unrecognized error — failing pipeline" >&2
        exit 1
    depends_on:
      - build

  # TODO: Uncomment when ready to publish to npmjs.org
  # publish-npmjs:
  #   image: *node_image
  #   environment:
  #     NPM_TOKEN:
  #       from_secret: npmjs_token
  #   commands:
  #     - *enable_pnpm
  #     - apk add --no-cache jq bash
  #     - bash scripts/publish-npmjs.sh
  #   depends_on:
  #     - build
  #   when:
  #     - event: [tag]

  build-gateway:
    image: gcr.io/kaniko-project/executor:debug
    when: *image_build_when
    environment:
      REGISTRY_USER:
        from_secret: gitea_username
      REGISTRY_PASS:
        from_secret: gitea_password
      CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
      CI_COMMIT_TAG: ${CI_COMMIT_TAG}
      CI_COMMIT_SHA: ${CI_COMMIT_SHA}
    commands:
      - mkdir -p /kaniko/.docker
      - echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
      - |
        DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/gateway:sha-${CI_COMMIT_SHA:0:7}"
        if [ "$CI_COMMIT_BRANCH" = "main" ]; then
          DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/gateway:latest"
        fi
        if [ -n "$CI_COMMIT_TAG" ]; then
          DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/gateway:$CI_COMMIT_TAG"
        fi
        /kaniko/executor --context . --dockerfile docker/gateway.Dockerfile $DESTINATIONS
    depends_on:
      - build

  build-appservice:
    image: gcr.io/kaniko-project/executor:debug
    when: *image_build_when
    environment:
      REGISTRY_USER:
        from_secret: gitea_username
      REGISTRY_PASS:
        from_secret: gitea_password
      CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
      CI_COMMIT_TAG: ${CI_COMMIT_TAG}
      CI_COMMIT_SHA: ${CI_COMMIT_SHA}
    commands:
      - mkdir -p /kaniko/.docker
      - echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
      - |
        DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/appservice:sha-${CI_COMMIT_SHA:0:7}"
        if [ "$CI_COMMIT_BRANCH" = "main" ]; then
          DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/appservice:latest"
        fi
        if [ -n "$CI_COMMIT_TAG" ]; then
          DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/appservice:$CI_COMMIT_TAG"
        fi
        /kaniko/executor --context . --dockerfile docker/appservice.Dockerfile $DESTINATIONS
    depends_on:
      - build

  build-web:
    image: gcr.io/kaniko-project/executor:debug
    when: *image_build_when
    environment:
      REGISTRY_USER:
        from_secret: gitea_username
      REGISTRY_PASS:
        from_secret: gitea_password
      CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
      CI_COMMIT_TAG: ${CI_COMMIT_TAG}
      CI_COMMIT_SHA: ${CI_COMMIT_SHA}
    commands:
      - mkdir -p /kaniko/.docker
      - echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
      - |
        DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/web:sha-${CI_COMMIT_SHA:0:7}"
        if [ "$CI_COMMIT_BRANCH" = "main" ]; then
          DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/web:latest"
        fi
        if [ -n "$CI_COMMIT_TAG" ]; then
          DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/web:$CI_COMMIT_TAG"
        fi
        /kaniko/executor --context . --dockerfile docker/web.Dockerfile $DESTINATIONS
    depends_on:
      - build
