Skip to content

chore(cli): Quality of lite improvement in preparation for paseo v2 (… #253

chore(cli): Quality of lite improvement in preparation for paseo v2 (…

chore(cli): Quality of lite improvement in preparation for paseo v2 (… #253

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*'
pull_request:
branches: [main]
paths:
- "packages/cli/**"
- "packages/ui/**"
- ".github/workflows/release.yml"
permissions:
actions: write
contents: write
pull-requests: write
jobs:
build:
name: Build
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
tag: ${{ steps.version.outputs.tag }}
prerelease: ${{ steps.version.outputs.prerelease }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.2.6"
- name: Install dependencies
shell: bash
run: |
set -euo pipefail
bun install --frozen-lockfile
for pkg in packages/*; do
[ -f "$pkg/package.json" ] || continue
grep -q '"file:' "$pkg/package.json" || continue
grep -oP '"[^"]+": "file:\K[^"]+' "$pkg/package.json" | while read -r dep; do
name=$(jq -r .name "$pkg/$dep/package.json")
scope_dir="$pkg/node_modules/$(dirname "$name")"
mkdir -p "$scope_dir"
ln -sfn "$(cd "$pkg/$dep" && pwd)" "$pkg/node_modules/$name"
done
done
- name: Extract version
id: version
shell: bash
run: |
set -euo pipefail
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
VERSION="${GITHUB_REF_NAME#v}"
TAG="${GITHUB_REF_NAME}"
else
VERSION="0.0.0-pr.${{ github.event.pull_request.number }}"
TAG=""
fi
PRERELEASE="false"
if [[ "$VERSION" == *-* ]]; then
PRERELEASE="true"
fi
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "tag=${TAG}" >> "${GITHUB_OUTPUT}"
echo "prerelease=${PRERELEASE}" >> "${GITHUB_OUTPUT}"
- name: Build CLI (capture output)
id: build
continue-on-error: true
working-directory: packages/cli
shell: bash
run: |
set -o pipefail
bun run build 2>&1 | tee "$GITHUB_WORKSPACE/release-build-output.txt"
- name: Upload build output
if: always()
uses: actions/upload-artifact@v4
with:
name: release-build-output
path: release-build-output.txt
retention-days: 7
- name: Prepare package
if: steps.build.outcome == 'success'
working-directory: packages/cli
shell: bash
run: |
set -euo pipefail
rm -rf bin
npm version "${{ steps.version.outputs.version }}" --no-git-tag-version --allow-same-version
jq 'del(.private)' package.json > package.json.tmp
mv package.json.tmp package.json
- name: Pack
if: steps.build.outcome == 'success'
working-directory: packages/cli
run: npm pack
- name: Upload CLI artifact
if: steps.build.outcome == 'success'
uses: actions/upload-artifact@v4
with:
name: cli-release-${{ steps.version.outputs.version }}
path: packages/cli/*.tgz
retention-days: 7
- name: Process results
id: result
shell: bash
run: |
set -euo pipefail
if [[ "${{ steps.build.outcome }}" == "success" ]]; then
echo "status=Passed" >> "${GITHUB_OUTPUT}"
echo "details=Build succeeded" >> "${GITHUB_OUTPUT}"
else
echo "status=Failed" >> "${GITHUB_OUTPUT}"
echo "details=Package build failed" >> "${GITHUB_OUTPUT}"
fi
- name: Update PR comment
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
env:
SECTION: Release
STATUS: ${{ steps.result.outputs.status }}
DETAILS: ${{ steps.result.outputs.details }}
VERSION: ${{ steps.version.outputs.version }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
RUN_ID: ${{ github.run_id }}
REPO: ${{ github.repository }}
with:
script: |
const fs = require("fs");
const marker = "<!-- ci-summary -->";
const detailsMarker = "<!-- details-section -->";
const section = process.env.SECTION;
const status = process.env.STATUS;
const details = process.env.DETAILS;
const version = process.env.VERSION;
const runUrl = process.env.RUN_URL;
const runId = process.env.RUN_ID;
const repoFull = process.env.REPO;
const { owner, repo } = context.repo;
const issue_number = context.payload.pull_request.number;
let output = "";
try {
output = fs.readFileSync("release-build-output.txt", "utf8");
} catch (_) {
output = "(release-build-output.txt not found)";
}
const MAX_CHARS = 60000;
if (output.length > MAX_CHARS) {
output = ["(truncated; showing last " + MAX_CHARS + " chars)", "", output.slice(-MAX_CHARS)].join("\n");
}
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number, per_page: 100,
});
const existing = comments.find(c =>
c.user?.login === "github-actions[bot]" && c.body?.includes(marker)
);
let rows = {};
let existingDetails = {};
if (existing?.body) {
const parts = existing.body.split(detailsMarker);
const tableSection = parts[0] || "";
const lines = tableSection.split("\n");
for (const line of lines) {
const match = line.match(/^\| ([^|]+) \| ([^|]+) \|$/);
if (match) {
const name = match[1].trim();
if (name && name !== "Check" && !name.startsWith(":")) {
rows[name] = match[2].trim();
}
}
}
const detailsRegex = /<details>\s*<summary><strong>([^<]+)<\/strong>.*?<\/summary>([\s\S]*?)<\/details>/g;
let detailMatch;
while ((detailMatch = detailsRegex.exec(existing.body)) !== null) {
existingDetails[detailMatch[1].trim()] = detailMatch[0];
}
}
const resultText = status === "Passed" ? "Passed" : "Failed";
rows[section] = `[${resultText}](${runUrl})`;
if (status === "Passed") {
const installInstructions = [
"**Test this PR**",
"",
"Download artifact (GitHub CLI required):",
"```bash",
`gh run download ${runId} -n cli-release-${version} -R ${repoFull}`,
"```",
"",
"Install globally:",
"```bash",
`npm install -g ./parity-dotns-cli-${version}.tgz`,
"```",
"",
"Verify:",
"```bash",
"dotns --help",
"```"
].join("\n");
existingDetails[section] = [
`<details>`,
`<summary><strong>${section}</strong> - ${resultText}</summary>`,
"",
installInstructions,
"",
"</details>",
].join("\n");
} else {
existingDetails[section] = [
`<details>`,
`<summary><strong>${section}</strong> - ${resultText}</summary>`,
"",
details || "Failed",
"",
`[View run](${runUrl})`,
"",
"```text",
output,
"```",
"</details>",
].join("\n");
}
const order = ["Lint", "Lint (UI)", "Format", "Format (UI)", "Typecheck", "Typecheck (UI)", "Build", "Build (UI)", "Release", "Deploy UI", "Deploy Example", "Benchmark UI", "PR Title", "Labels", "Test"];
const sortedKeys = Object.keys(rows).sort((a, b) => {
const ai = order.indexOf(a), bi = order.indexOf(b);
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
});
let table = `| Check | Result |\n|:------|:-------|\n`;
for (const key of sortedKeys) {
table += `| ${key} | ${rows[key]} |\n`;
}
const detailsOrder = ["Lint", "Lint (UI)", "Format", "Format (UI)", "Typecheck", "Typecheck (UI)", "Build", "Build (UI)", "Release", "Deploy UI", "Deploy Example", "Benchmark UI", "PR Title", "Labels", "Test"];
const sortedDetails = Object.keys(existingDetails).sort((a, b) => {
const ai = detailsOrder.indexOf(a), bi = detailsOrder.indexOf(b);
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
});
let body = `${marker}\n## CI Summary\n\n${table}`;
if (sortedDetails.length > 0) {
body += `\n${detailsMarker}\n\n---\n\n${sortedDetails.map(k => existingDetails[k]).join("\n\n")}`;
}
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number, body });
}
- name: Fail if build failed
if: steps.build.outcome == 'failure'
run: exit 1
build-ui:
name: Build UI
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.2.6"
- name: Install dependencies
shell: bash
run: |
set -euo pipefail
bun install --frozen-lockfile
for pkg in packages/*; do
[ -f "$pkg/package.json" ] || continue
grep -q '"file:' "$pkg/package.json" || continue
grep -oP '"[^"]+": "file:\K[^"]+' "$pkg/package.json" | while read -r dep; do
name=$(jq -r .name "$pkg/$dep/package.json")
scope_dir="$pkg/node_modules/$(dirname "$name")"
mkdir -p "$scope_dir"
ln -sfn "$(cd "$pkg/$dep" && pwd)" "$pkg/node_modules/$name"
done
done
- name: Extract version
id: version
shell: bash
run: |
set -euo pipefail
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
VERSION="${GITHUB_REF_NAME#v}"
else
VERSION="0.0.0-pr.${{ github.event.pull_request.number }}"
fi
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
- name: Build UI
working-directory: packages/ui
run: bun run build
- name: Create UI tarball
shell: bash
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.version }}"
tar -czf "dotns-ui-${VERSION}.tar.gz" -C packages/ui/dist .
- name: Upload UI tarball
uses: actions/upload-artifact@v4
with:
name: ui-release-tarball
path: dotns-ui-*.tar.gz
retention-days: 7
- name: Upload UI site artifact
uses: actions/upload-artifact@v4
with:
name: ui-site
path: packages/ui/dist/
retention-days: 7
release:
name: Create Release
if: startsWith(github.ref, 'refs/tags/v')
needs: [build, build-ui]
runs-on: ubuntu-latest
steps:
- name: Download CLI artifact
uses: actions/download-artifact@v4
with:
name: cli-release-${{ needs.build.outputs.version }}
path: artifacts
- name: Download UI artifact
uses: actions/download-artifact@v4
with:
name: ui-release-tarball
path: artifacts
- name: Create release body
shell: bash
run: |
set -euo pipefail
VERSION="${{ needs.build.outputs.version }}"
TAG="${{ needs.build.outputs.tag }}"
REPO="${{ github.repository }}"
cat > release-body.md <<EOF
## Installation
### CLI
Install from npm:
\`\`\`bash
npm install -g @parity/dotns-cli
\`\`\`
Or with other package managers:
| Package Manager | Command |
|:----------------|:--------|
| npm | \`npm install -g @parity/dotns-cli\` |
| yarn | \`yarn global add @parity/dotns-cli\` |
| bun | \`bun add -g @parity/dotns-cli\` |
| pnpm | \`pnpm add -g @parity/dotns-cli\` |
Verify:
\`\`\`bash
dotns --help
\`\`\`
### UI
Download:
\`\`\`bash
gh release download ${TAG} -p "dotns-ui-*.tar.gz" -R ${REPO}
\`\`\`
Extract:
\`\`\`bash
mkdir -p dotns-ui && tar -xzf dotns-ui-${VERSION}.tar.gz -C dotns-ui
\`\`\`
EOF
- name: Create release
uses: softprops/action-gh-release@v2
with:
files: |
artifacts/*.tgz
artifacts/dotns-ui-*.tar.gz
body_path: release-body.md
generate_release_notes: true
prerelease: ${{ needs.build.outputs.prerelease == 'true' }}
- name: Trigger NPM Release
if: needs.build.outputs.prerelease != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh workflow run npm-release.yml \
--ref main \
-f "tag=${{ needs.build.outputs.tag }}" \
-R "${{ github.repository }}"
deploy-production:
name: Deploy Production
if: github.event_name == 'push' && !contains(github.ref_name, '-')
needs: [build-ui]
uses: ./.github/workflows/deploy.yml
with:
basename: dotns
mode: production
artifact-name: ui-site
cli-source: release
secrets:
dotns-mnemonic: ${{ secrets.DOTNS_MNEMONIC }}
bulletin-mnemonic: ${{ secrets.DOTNS_MNEMONIC_DEPLOY_UI }}