Skip to content

fix(docker): shell-based CodeLLDB vendoring in the image build; cut v… #60

fix(docker): shell-based CodeLLDB vendoring in the image build; cut v…

fix(docker): shell-based CodeLLDB vendoring in the image build; cut v… #60

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
ref:
description: 'Git ref (tag or branch) to release from'
required: true
default: 'refs/tags/v0.24.2'
permissions: {}
jobs:
build-and-test:
name: Build and Test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.inputs.ref || github.ref }}
- name: Setup pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.x'
cache: 'pnpm'
- name: Setup Python 3.11
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
- name: Install Python dependencies
run: |
python -m pip install --require-hashes -r requirements/pip.txt
python -m pip install --require-hashes -r requirements/debugpy.txt
- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: '1.21'
- name: Install Delve debugger
run: go install github.com/go-delve/delve/cmd/dlv@v1.24.2
- name: Setup Java 21
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Ruby
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
with:
ruby-version: '3.3'
- name: Verify rdbg (bundled debug gem)
run: rdbg --version
- name: Sanity versions
run: |
node -v
npm -v
pnpm -v
- name: Approve build scripts (esbuild)
run: pnpm approve-builds esbuild
continue-on-error: true
- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Audit production dependencies
run: pnpm audit --prod --audit-level=high
- name: Build project
run: pnpm run build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run tests
run: pnpm run test:ci-no-python
docker-publish:
name: Build and Push Docker Image
needs: build-and-test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.inputs.ref || github.ref }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log in to Docker Hub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Determine Docker latest tag strategy
shell: bash
env:
GITHUB_REF_TYPE: ${{ github.ref_type }}
GITHUB_REF_NAME: ${{ github.ref_name }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
if [[ "${GITHUB_REF_TYPE}" == "branch" && "${GITHUB_REF_NAME}" == "${DEFAULT_BRANCH}" ]]; then
echo "PUBLISH_LATEST=true" >> "$GITHUB_ENV"
echo "latest will track default branch build (${GITHUB_REF_NAME})"
elif [[ "${GITHUB_REF_TYPE}" == "tag" && "${GITHUB_REF_NAME}" != *"-alpha"* && "${GITHUB_REF_NAME}" != *"-beta"* && "${GITHUB_REF_NAME}" != *"-rc"* ]]; then
echo "PUBLISH_LATEST=true" >> "$GITHUB_ENV"
echo "latest will point to release ${GITHUB_REF_NAME}"
else
echo "PUBLISH_LATEST=false" >> "$GITHUB_ENV"
echo "latest tag update skipped for ref ${GITHUB_REF_NAME}"
fi
- name: Extract metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: debugmcp/mcp-debugger
tags: |
type=ref,event=tag
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ env.PUBLISH_LATEST == 'true' }}
- name: Build and push Docker image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
pypi-publish:
name: Publish Python Launcher to PyPI
needs: build-and-test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.inputs.ref || github.ref }}
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
- name: Install Python build dependencies
run: |
python -m pip install "pip==25.0.1"
pip install "build==1.2.2" "twine==6.1.0" "tomlkit==0.13.2"
- name: Resolve release ref
run: |
if [ -n "${{ github.event.inputs.ref }}" ]; then
echo "RELEASE_REF=${{ github.event.inputs.ref }}" >> $GITHUB_ENV
else
echo "RELEASE_REF=${GITHUB_REF}" >> $GITHUB_ENV
fi
- name: Set launcher version from tag
run: |
VERSION="${RELEASE_REF#refs/tags/v}"
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Sync version into pyproject.toml
run: |
python - <<'PY'
import os
from tomlkit import parse, dumps
p = os.path.join('mcp_debugger_launcher','pyproject.toml')
with open(p,'r',encoding='utf-8') as f:
doc = parse(f.read())
ver = os.environ.get('VERSION','0.0.0')
if 'project' in doc and 'version' in doc['project']:
doc['project']['version'] = ver
elif 'tool' in doc and 'poetry' in doc['tool'] and 'version' in doc['tool']['poetry']:
doc['tool']['poetry']['version'] = ver
else:
doc.setdefault('project', {})['version'] = ver
with open(p,'w',encoding='utf-8') as f:
f.write(dumps(doc))
print(f"Set pyproject version to {ver}")
PY
- name: Build Python package
run: |
cd mcp_debugger_launcher
rm -rf dist build *.egg-info
python -m build
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: |
cd mcp_debugger_launcher
python -m twine check dist/*
python -m twine upload --skip-existing dist/*
npm-publish:
name: Publish to npm
needs: build-and-test
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Required for npm trusted publishing (OIDC)
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.inputs.ref || github.ref }}
- name: Setup pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
with:
version: 10
# No registry-url here: setup-node would write an _authToken=${NODE_AUTH_TOKEN}
# placeholder into .npmrc, which breaks token-less OIDC publishes when the
# env var is unset. The token step below writes its own scoped .npmrc.
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.x'
cache: 'pnpm'
# npm >= 11.5.1 is required for OIDC trusted publishing; Node 22 bundles npm 10.
- name: Upgrade npm for trusted publishing
run: npm install -g npm@12.0.2
- name: Setup Java 21
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Ruby
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
with:
ruby-version: '3.3'
- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Sanity versions
run: |
node -v
npm -v
pnpm -v
- name: Resolve release ref
run: |
if [ -n "${{ github.event.inputs.ref }}" ]; then
echo "RELEASE_REF=${{ github.event.inputs.ref }}" >> $GITHUB_ENV
else
echo "RELEASE_REF=${GITHUB_REF}" >> $GITHUB_ENV
fi
- name: Set CLI package version from tag (monorepo-safe)
run: |
VERSION="${RELEASE_REF#refs/tags/v}"
echo "Setting CLI package version to $VERSION"
VERSION_STRIPPED="$VERSION" node -e "const fs=require('fs');const p='packages/mcp-debugger/package.json';const pkg=JSON.parse(fs.readFileSync(p,'utf8'));const ver=process.env.VERSION_STRIPPED; if(!/^[0-9]+\\.[0-9]+\\.[0-9]+(-.+)?$/.test(ver)){console.error('Invalid semver:',ver);process.exit(1);} pkg.version=ver; fs.writeFileSync(p,JSON.stringify(pkg,null,2)+'\\n');console.log('Updated',p,'to',pkg.version)"
- name: Capture workspace package versions (robust)
run: |
node -e 'console.log("SHARED_VERSION="+require("./packages/shared/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_MOCK_VERSION="+require("./packages/adapter-mock/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_PYTHON_VERSION="+require("./packages/adapter-python/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_RUBY_VERSION="+require("./packages/adapter-ruby/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_JAVASCRIPT_VERSION="+require("./packages/adapter-javascript/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_GO_VERSION="+require("./packages/adapter-go/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_JAVA_VERSION="+require("./packages/adapter-java/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_DOTNET_VERSION="+require("./packages/adapter-dotnet/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("CLI_VERSION="+require("./packages/mcp-debugger/package.json").version)' >> $GITHUB_ENV
- name: Set npm dist-tag
run: |
if [[ "${CLI_VERSION}" == *"-beta"* ]] || [[ "${CLI_VERSION}" == *"-alpha"* ]]; then
echo "NPM_TAG=beta" >> $GITHUB_ENV
else
echo "NPM_TAG=latest" >> $GITHUB_ENV
fi
- name: Build project
run: pnpm run build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# npm (unlike pnpm) does not rewrite workspace:* at publish time; without
# this step the published adapter packages carry an uninstallable
# "workspace:*" dependency on @debugmcp/shared (EUNSUPPORTEDPROTOCOL).
# The rewrite happens only in the runner checkout, never committed.
- name: Resolve workspace deps for publish
run: node scripts/resolve-workspace-deps.cjs
- name: Pack npm tarballs (dry-run)
run: |
npm pack --dry-run -w @debugmcp/shared
npm pack --dry-run -w @debugmcp/adapter-mock
npm pack --dry-run -w @debugmcp/adapter-python
npm pack --dry-run -w @debugmcp/adapter-ruby
npm pack --dry-run -w @debugmcp/adapter-javascript
npm pack --dry-run -w @debugmcp/adapter-go
npm pack --dry-run -w @debugmcp/adapter-java
npm pack --dry-run -w @debugmcp/adapter-dotnet
npm pack --dry-run -w @debugmcp/mcp-debugger
# Existing packages publish via OIDC trusted publishing: no token anywhere,
# npm mints a short-lived credential from the workflow's OIDC identity and
# generates provenance automatically. Each package must have this repo +
# workflow configured as a trusted publisher on npmjs.com BEFORE tagging.
- name: Publish existing packages to npm (OIDC trusted publishing)
run: |
# Publish packages in dependency order if the target version does NOT yet exist
if npm view @debugmcp/shared@${SHARED_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/shared@${SHARED_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/shared --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/adapter-mock@${ADAPTER_MOCK_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-mock@${ADAPTER_MOCK_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-mock --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/adapter-python@${ADAPTER_PYTHON_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-python@${ADAPTER_PYTHON_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-python --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/adapter-ruby@${ADAPTER_RUBY_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-ruby@${ADAPTER_RUBY_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-ruby --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/mcp-debugger@${CLI_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/mcp-debugger@${CLI_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/mcp-debugger --access public --provenance --tag ${NPM_TAG}
fi
# First-time publishes cannot use trusted publishing (the trusted-publisher
# config lives on an existing npm package). These four publish with the
# scoped NPM_TOKEN this release only; once they exist, configure trusted
# publishers for them and move them into the OIDC step above.
- name: Publish new packages to npm (token, first publish)
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
echo "//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}" > "$HOME/.npmrc"
if npm view @debugmcp/adapter-javascript@${ADAPTER_JAVASCRIPT_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-javascript@${ADAPTER_JAVASCRIPT_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-javascript --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/adapter-go@${ADAPTER_GO_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-go@${ADAPTER_GO_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-go --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/adapter-java@${ADAPTER_JAVA_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-java@${ADAPTER_JAVA_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-java --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/adapter-dotnet@${ADAPTER_DOTNET_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-dotnet@${ADAPTER_DOTNET_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-dotnet --access public --provenance --tag ${NPM_TAG}
fi
rm -f "$HOME/.npmrc"
# The packed tarballs become GitHub Release assets, attested by the
# provenance job below (OpenSSF Scorecard Signed-Releases).
- name: Pack release artifacts for provenance
run: |
mkdir -p release-artifacts
npm pack -w @debugmcp/shared --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-mock --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-python --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-ruby --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-javascript --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-go --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-java --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-dotnet --pack-destination release-artifacts
npm pack -w @debugmcp/mcp-debugger --pack-destination release-artifacts
- name: Upload release artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-artifacts
path: release-artifacts/*.tgz
if-no-files-found: error
provenance:
name: Generate build provenance attestation
needs: npm-publish
runs-on: ubuntu-latest
permissions:
id-token: write # sign the attestation (sigstore)
attestations: write # persist it to the GitHub Attestations API
contents: read
# Uses actions/attest-build-provenance (a plain composite action) rather than
# slsa-framework/slsa-github-generator's reusable workflow: this org has
# "Write permissions for workflows" disabled, and GitHub validates a calling
# job's permissions against that policy at PARSE TIME for external reusable
# *workflow* calls specifically -- even read-only-looking permission sets on
# such a job made the whole run fail with startup_failure before any job
# (even unrelated ones) could start. A normal action inside a normal job
# (like npm-publish's existing id-token: write) isn't subject to that check.
steps:
- name: Download release artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-artifacts
path: release-artifacts
- name: Generate attestation
id: attest
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: release-artifacts/*.tgz
# The bundle is a JSON-serialized Sigstore bundle wrapping an in-toto
# statement -- genuinely valid under both extensions Scorecard's
# Signed-Releases probes scan release assets for (releasesAreSigned:
# .sigstore.json; releasesHaveProvenance: .intoto.jsonl).
- name: Name provenance files for release assets
run: |
cp "${{ steps.attest.outputs.bundle-path }}" multiple.intoto.jsonl
cp "${{ steps.attest.outputs.bundle-path }}" multiple.sigstore.json
- name: Upload provenance files
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: provenance
path: |
multiple.intoto.jsonl
multiple.sigstore.json
if-no-files-found: error
create-release:
name: Create GitHub Release
needs: [docker-publish, pypi-publish, npm-publish, provenance]
runs-on: ubuntu-latest
permissions:
contents: write # Grant permission to create releases
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.inputs.ref || github.ref }}
- name: Resolve release ref
run: |
if [ -n "${{ github.event.inputs.ref }}" ]; then
echo "RELEASE_REF=${{ github.event.inputs.ref }}" >> $GITHUB_ENV
else
echo "RELEASE_REF=${GITHUB_REF}" >> $GITHUB_ENV
fi
- name: Download release artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-artifacts
path: release-artifacts
- name: Download provenance attestation
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: provenance
path: provenance
- name: Generate changelog
id: changelog
run: |
# Extract version from tag
VERSION=${RELEASE_REF#refs/tags/v}
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
# Get changelog for this version
CHANGELOG=$(sed -n "/^## \[$VERSION\]/,/^## \[/p" CHANGELOG.md | sed '$ d')
echo "CHANGELOG<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# SBOMs (SPDX + CycloneDX) are generated from the exact release ref and
# attached to the GitHub release — required by many corporate/government
# procurement processes (EO 14028) and useful for downstream scanners.
- name: Generate SBOM (SPDX)
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
path: .
format: spdx-json
output-file: sbom.spdx.json
upload-artifact: false
upload-release-assets: false
- name: Generate SBOM (CycloneDX)
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
path: .
format: cyclonedx-json
output-file: sbom.cyclonedx.json
upload-artifact: false
upload-release-assets: false
- name: Create Release with GitHub CLI
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Create the release body in a file
cat << 'EOF' > release_notes.md
## 🎉 Release ${{ steps.changelog.outputs.VERSION }}
${{ steps.changelog.outputs.CHANGELOG }}
### 📦 Installation
**Docker:**
```bash
docker pull debugmcp/mcp-debugger:${{ steps.changelog.outputs.VERSION }}
```
**npm (global install):**
```bash
npm install -g @debugmcp/mcp-debugger@${{ steps.changelog.outputs.VERSION }}
```
**npx (no install):**
```bash
npx @debugmcp/mcp-debugger@${{ steps.changelog.outputs.VERSION }} stdio
```
**PyPI:**
```bash
pip install debug-mcp-server-launcher==${{ steps.changelog.outputs.VERSION }}
```
**Optional adapters** (for programmatic embedding; the CLI above bundles all of them):
```bash
npm install @debugmcp/adapter-python # or: adapter-ruby, adapter-javascript,
# adapter-go, adapter-java, adapter-dotnet,
# adapter-mock
```
### 🔏 Verify this release
```bash
gh attestation verify <asset>.tgz --repo debugmcp/mcp-debugger
npm audit signatures # in a project that installs @debugmcp packages
```
SBOMs (SPDX + CycloneDX) are attached as release assets.
### 📚 Documentation
See the [README](https://github.com/debugmcp/mcp-debugger#readme) for usage instructions.
EOF
# Determine if this is a prerelease
if [[ "${{ github.ref_name }}" == *"-beta"* ]] || [[ "${{ github.ref_name }}" == *"-alpha"* ]]; then
PRERELEASE_FLAG="--prerelease"
else
PRERELEASE_FLAG=""
fi
# Create the release using GitHub CLI, attaching the npm tarballs and
# their build provenance attestation (verify with:
# gh attestation verify <tarball> --repo debugmcp/mcp-debugger)
gh release create "${{ github.ref_name }}" \
--title "Release ${{ steps.changelog.outputs.VERSION }}" \
--notes-file release_notes.md \
$PRERELEASE_FLAG \
release-artifacts/*.tgz \
provenance/multiple.intoto.jsonl \
provenance/multiple.sigstore.json \
sbom.spdx.json \
sbom.cyclonedx.json