mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-06 16:53:38 +02:00
Overhaul script handling. Closes #125.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Collect third-party license texts into LICENSES.md
|
||||
# Usage: scripts/build/collect_licenses.sh [output-path]
|
||||
# output-path defaults to LICENSES.md at the repo root
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
OUT="${1:-$REPO_ROOT/LICENSES.md}"
|
||||
FRONTEND_LICENSE_IMAGE="${FRONTEND_LICENSE_IMAGE:-node:20-slim}"
|
||||
FRONTEND_LICENSE_NPM="${FRONTEND_LICENSE_NPM:-10.9.5}"
|
||||
|
||||
# ── Backend (Python) — uses pip-licenses ─────────────────────────────
|
||||
backend_licenses() {
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
# Extract direct dependency names from pyproject.toml
|
||||
local packages
|
||||
packages=$(uv run python3 -c "
|
||||
import re, tomllib
|
||||
with open('pyproject.toml', 'rb') as f:
|
||||
deps = tomllib.load(f)['project']['dependencies']
|
||||
names = [re.split(r'[\[><=!;]', d)[0].strip() for d in deps]
|
||||
print(' '.join(names))
|
||||
")
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
uv run pip-licenses \
|
||||
--packages $packages \
|
||||
--with-license-file \
|
||||
--no-license-path \
|
||||
--format=json \
|
||||
| uv run python3 -c "
|
||||
import json, sys
|
||||
|
||||
data = sorted(json.load(sys.stdin), key=lambda d: d['Name'].lower())
|
||||
for d in data:
|
||||
name = d['Name']
|
||||
version = d['Version']
|
||||
lic = d.get('License', 'Unknown')
|
||||
text = d.get('LicenseText', '').strip()
|
||||
|
||||
print(f'### {name} ({version}) — {lic}\n')
|
||||
if text and text != 'UNKNOWN':
|
||||
print('<details>')
|
||||
print('<summary>Full license text</summary>')
|
||||
print()
|
||||
print('\`\`\`')
|
||||
print(text)
|
||||
print('\`\`\`')
|
||||
print()
|
||||
print('</details>')
|
||||
else:
|
||||
print('*License file not found in package metadata.*')
|
||||
print()
|
||||
"
|
||||
}
|
||||
|
||||
# ── Frontend (npm) ───────────────────────────────────────────────────
|
||||
frontend_licenses_local() {
|
||||
cd "$REPO_ROOT/frontend"
|
||||
node "$REPO_ROOT/scripts/build/print_frontend_licenses.cjs"
|
||||
}
|
||||
|
||||
frontend_licenses_docker() {
|
||||
docker run --rm \
|
||||
-v "$REPO_ROOT:/src:ro" \
|
||||
-w /tmp \
|
||||
"$FRONTEND_LICENSE_IMAGE" \
|
||||
bash -lc "
|
||||
set -euo pipefail
|
||||
cp -a /src/frontend ./frontend
|
||||
cd frontend
|
||||
npm i -g npm@$FRONTEND_LICENSE_NPM >/dev/null
|
||||
npm ci --ignore-scripts >/dev/null
|
||||
node /src/scripts/build/print_frontend_licenses.cjs
|
||||
"
|
||||
}
|
||||
|
||||
frontend_licenses() {
|
||||
frontend_licenses_docker
|
||||
}
|
||||
|
||||
# ── Assemble ─────────────────────────────────────────────────────────
|
||||
{
|
||||
echo "# Third-Party Licenses"
|
||||
echo
|
||||
echo "Auto-generated by \`scripts/build/collect_licenses.sh\` — do not edit by hand."
|
||||
echo
|
||||
echo "## Backend (Python) Dependencies"
|
||||
echo
|
||||
backend_licenses
|
||||
echo "## Frontend (npm) Dependencies"
|
||||
echo
|
||||
frontend_licenses
|
||||
} > "$OUT"
|
||||
|
||||
echo "Wrote $OUT" >&2
|
||||
@@ -0,0 +1,43 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
const depNames = Object.keys(pkg.dependencies || {}).sort((a, b) =>
|
||||
a.toLowerCase().localeCompare(b.toLowerCase())
|
||||
);
|
||||
|
||||
for (const name of depNames) {
|
||||
const pkgDir = path.join('node_modules', name);
|
||||
let version = 'unknown';
|
||||
let licenseType = 'Unknown';
|
||||
let licenseText = null;
|
||||
|
||||
try {
|
||||
const depPkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'));
|
||||
version = depPkg.version || version;
|
||||
licenseType = depPkg.license || licenseType;
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
const files = fs.readdirSync(pkgDir);
|
||||
const licFile = files.find((file) => /^(licen[sc]e|copying)/i.test(file));
|
||||
if (licFile) {
|
||||
licenseText = fs.readFileSync(path.join(pkgDir, licFile), 'utf8').trim();
|
||||
}
|
||||
} catch {}
|
||||
|
||||
console.log(`### ${name} (${version}) — ${licenseType}\n`);
|
||||
if (licenseText) {
|
||||
console.log('<details>');
|
||||
console.log('<summary>Full license text</summary>');
|
||||
console.log();
|
||||
console.log('```');
|
||||
console.log(licenseText);
|
||||
console.log('```');
|
||||
console.log();
|
||||
console.log('</details>');
|
||||
} else {
|
||||
console.log('*License file not found in package.*');
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
RELEASE_WORK_DIR=""
|
||||
RELEASE_BUNDLE_DIR_NAME="Remote-Terminal-for-MeshCore"
|
||||
RELEASE_ASSET=""
|
||||
DOCKER_IMAGE="jkingsman/remoteterm-meshcore"
|
||||
DOCKER_PLATFORMS="linux/amd64,linux/arm64"
|
||||
|
||||
cleanup_release_build_artifacts() {
|
||||
if [ -d "$REPO_ROOT/frontend/prebuilt" ]; then
|
||||
rm -rf "$REPO_ROOT/frontend/prebuilt"
|
||||
fi
|
||||
if [ -n "$RELEASE_WORK_DIR" ] && [ -d "$RELEASE_WORK_DIR" ]; then
|
||||
rm -rf "$RELEASE_WORK_DIR"
|
||||
fi
|
||||
if [ -n "$RELEASE_ASSET" ] && [ -f "$REPO_ROOT/$RELEASE_ASSET" ]; then
|
||||
rm -f "$REPO_ROOT/$RELEASE_ASSET"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup_release_build_artifacts EXIT
|
||||
|
||||
ensure_buildx_builder() {
|
||||
if ! docker buildx version >/dev/null 2>&1; then
|
||||
echo -e "${RED}Error: docker buildx is required for multi-arch Docker builds.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local current_builder
|
||||
current_builder="$(docker buildx inspect --format '{{ .Name }}' 2>/dev/null || true)"
|
||||
|
||||
if [ -n "$current_builder" ]; then
|
||||
docker buildx inspect --bootstrap >/dev/null
|
||||
return
|
||||
fi
|
||||
|
||||
if docker buildx inspect remoteterm-multiarch >/dev/null 2>&1; then
|
||||
docker buildx use remoteterm-multiarch >/dev/null
|
||||
else
|
||||
docker buildx create --name remoteterm-multiarch --use >/dev/null
|
||||
fi
|
||||
docker buildx inspect --bootstrap >/dev/null
|
||||
}
|
||||
|
||||
echo -e "${YELLOW}=== RemoteTerm for MeshCore Publish Script ===${NC}"
|
||||
echo
|
||||
|
||||
# Run backend linting and type checking
|
||||
echo -e "${YELLOW}Running backend lint (Ruff)...${NC}"
|
||||
uv run ruff check app/ tests/ --fix
|
||||
uv run ruff format app/ tests/
|
||||
# validate
|
||||
uv run ruff check app/ tests/
|
||||
uv run ruff format --check app/ tests/
|
||||
echo -e "${GREEN}Backend lint passed!${NC}"
|
||||
echo
|
||||
|
||||
echo -e "${YELLOW}Running backend type check (Pyright)...${NC}"
|
||||
uv run pyright app/
|
||||
echo -e "${GREEN}Backend type check passed!${NC}"
|
||||
echo
|
||||
|
||||
# Run backend tests
|
||||
echo -e "${YELLOW}Running backend tests...${NC}"
|
||||
PYTHONPATH=. uv run pytest tests/ -v
|
||||
echo -e "${GREEN}Backend tests passed!${NC}"
|
||||
echo
|
||||
|
||||
# Run frontend linting and formatting check
|
||||
echo -e "${YELLOW}Running frontend lint (ESLint)...${NC}"
|
||||
cd "$REPO_ROOT/frontend"
|
||||
npm run lint
|
||||
echo -e "${GREEN}Frontend lint passed!${NC}"
|
||||
echo
|
||||
|
||||
echo -e "${YELLOW}Checking frontend formatting (Prettier)...${NC}"
|
||||
npm run format:check
|
||||
echo -e "${GREEN}Frontend formatting OK!${NC}"
|
||||
echo
|
||||
|
||||
# Run frontend tests and build
|
||||
echo -e "${YELLOW}Running frontend tests...${NC}"
|
||||
npm run test:run
|
||||
echo -e "${GREEN}Frontend tests passed!${NC}"
|
||||
echo
|
||||
|
||||
echo -e "${YELLOW}Building frontend...${NC}"
|
||||
npm run build
|
||||
echo -e "${GREEN}Frontend build complete!${NC}"
|
||||
cd "$REPO_ROOT"
|
||||
echo
|
||||
|
||||
echo -e "${YELLOW}Regenerating LICENSES.md...${NC}"
|
||||
bash scripts/build/collect_licenses.sh LICENSES.md
|
||||
echo -e "${GREEN}LICENSES.md updated!${NC}"
|
||||
echo
|
||||
|
||||
# Prompt for version
|
||||
echo -e "${YELLOW}Current versions:${NC}"
|
||||
echo -n " pyproject.toml: "
|
||||
grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/'
|
||||
echo -n " package.json: "
|
||||
grep '"version"' frontend/package.json | head -1 | sed 's/.*"version": "\(.*\)".*/\1/'
|
||||
echo
|
||||
|
||||
read -r -p "Enter new version (e.g., 1.2.3): " VERSION
|
||||
VERSION="$(printf '%s' "$VERSION" | tr -d '\r' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
||||
|
||||
if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo -e "${RED}Error: Version must be in format X.Y.Z${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update pyproject.toml
|
||||
echo -e "${YELLOW}Updating pyproject.toml...${NC}"
|
||||
sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
|
||||
|
||||
# Update frontend package.json
|
||||
echo -e "${YELLOW}Updating frontend/package.json...${NC}"
|
||||
sed -i "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" frontend/package.json
|
||||
|
||||
# Update uv.lock with new version
|
||||
echo -e "${YELLOW}Updating uv.lock...${NC}"
|
||||
uv sync
|
||||
|
||||
echo -e "${GREEN}Version updated to $VERSION${NC}"
|
||||
echo
|
||||
|
||||
# Prompt for changelog entry
|
||||
echo -e "${YELLOW}Enter changelog entry for version $VERSION${NC}"
|
||||
echo -e "${YELLOW}(Enter your changes, then press Ctrl+D when done):${NC}"
|
||||
echo
|
||||
|
||||
CHANGELOG_ENTRY=$(cat)
|
||||
|
||||
# Create changelog entry with date
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
CHANGELOG_HEADER="## [$VERSION] - $DATE"
|
||||
|
||||
# Prepend to CHANGELOG.md (after the title if it exists)
|
||||
if [ -f CHANGELOG.md ]; then
|
||||
# Check if file starts with a title
|
||||
if head -1 CHANGELOG.md | grep -q "^# "; then
|
||||
# Insert after title line
|
||||
{
|
||||
head -1 CHANGELOG.md
|
||||
echo
|
||||
echo "$CHANGELOG_HEADER"
|
||||
echo
|
||||
echo "$CHANGELOG_ENTRY"
|
||||
echo
|
||||
tail -n +2 CHANGELOG.md
|
||||
} > CHANGELOG.md.tmp
|
||||
mv CHANGELOG.md.tmp CHANGELOG.md
|
||||
else
|
||||
# No title, prepend directly
|
||||
{
|
||||
echo "$CHANGELOG_HEADER"
|
||||
echo
|
||||
echo "$CHANGELOG_ENTRY"
|
||||
echo
|
||||
cat CHANGELOG.md
|
||||
} > CHANGELOG.md.tmp
|
||||
mv CHANGELOG.md.tmp CHANGELOG.md
|
||||
fi
|
||||
else
|
||||
# Create new changelog
|
||||
{
|
||||
echo "# Changelog"
|
||||
echo
|
||||
echo "$CHANGELOG_HEADER"
|
||||
echo
|
||||
echo "$CHANGELOG_ENTRY"
|
||||
} > CHANGELOG.md
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -e "${GREEN}Changelog updated!${NC}"
|
||||
echo
|
||||
|
||||
# Commit the changes
|
||||
echo -e "${YELLOW}Committing changes...${NC}"
|
||||
git add .
|
||||
git commit -m "Updating changelog + build for $VERSION"
|
||||
git push
|
||||
echo -e "${GREEN}Changes committed!${NC}"
|
||||
echo
|
||||
|
||||
# Get git hashes (after commit so they reflect the new commit)
|
||||
GIT_HASH=$(git rev-parse --short HEAD)
|
||||
FULL_GIT_HASH=$(git rev-parse HEAD)
|
||||
RELEASE_ASSET="remoteterm-prebuilt-frontend-v${VERSION}-${GIT_HASH}.zip"
|
||||
|
||||
echo -e "${YELLOW}Building packaged frontend artifact...${NC}"
|
||||
cd "$REPO_ROOT/frontend"
|
||||
npm run packaged-build
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
RELEASE_WORK_DIR=$(mktemp -d)
|
||||
RELEASE_BUNDLE_DIR="$RELEASE_WORK_DIR/$RELEASE_BUNDLE_DIR_NAME"
|
||||
mkdir -p "$RELEASE_BUNDLE_DIR"
|
||||
git archive "$FULL_GIT_HASH" | tar -x -C "$RELEASE_BUNDLE_DIR"
|
||||
mkdir -p "$RELEASE_BUNDLE_DIR/frontend"
|
||||
cp -R "$REPO_ROOT/frontend/prebuilt" "$RELEASE_BUNDLE_DIR/frontend/prebuilt"
|
||||
cat > "$RELEASE_BUNDLE_DIR/build_info.json" <<EOF
|
||||
{
|
||||
"version": "$VERSION",
|
||||
"commit_hash": "$GIT_HASH",
|
||||
"build_source": "prebuilt-release"
|
||||
}
|
||||
EOF
|
||||
rm -f "$REPO_ROOT/$RELEASE_ASSET"
|
||||
(
|
||||
cd "$RELEASE_WORK_DIR"
|
||||
zip -qr "$REPO_ROOT/$RELEASE_ASSET" "$(basename "$RELEASE_BUNDLE_DIR")"
|
||||
)
|
||||
echo -e "${GREEN}Packaged release artifact created: $RELEASE_ASSET${NC}"
|
||||
echo
|
||||
|
||||
# Build and push multi-arch docker image
|
||||
echo -e "${YELLOW}Building and pushing multi-arch Docker image...${NC}"
|
||||
ensure_buildx_builder
|
||||
docker buildx build \
|
||||
--platform "$DOCKER_PLATFORMS" \
|
||||
--build-arg COMMIT_HASH="$GIT_HASH" \
|
||||
-t "$DOCKER_IMAGE:latest" \
|
||||
-t "$DOCKER_IMAGE:$VERSION" \
|
||||
-t "$DOCKER_IMAGE:$GIT_HASH" \
|
||||
--push \
|
||||
.
|
||||
echo -e "${GREEN}Multi-arch Docker build + push complete!${NC}"
|
||||
echo
|
||||
|
||||
# Create GitHub release using the changelog notes for this version.
|
||||
echo -e "${YELLOW}Creating GitHub release...${NC}"
|
||||
RELEASE_NOTES_FILE=$(mktemp)
|
||||
{
|
||||
echo "$CHANGELOG_HEADER"
|
||||
echo
|
||||
echo "$CHANGELOG_ENTRY"
|
||||
} > "$RELEASE_NOTES_FILE"
|
||||
|
||||
# Create and push the release tag first so GitHub release creation does not
|
||||
# depend on resolving a symbolic ref like HEAD on the remote side. Use the same
|
||||
# changelog-derived notes for the annotated tag message.
|
||||
if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then
|
||||
echo -e "${YELLOW}Tag $VERSION already exists locally; reusing it.${NC}"
|
||||
else
|
||||
git tag -a "$VERSION" "$FULL_GIT_HASH" -F "$RELEASE_NOTES_FILE"
|
||||
fi
|
||||
|
||||
if git ls-remote --exit-code --tags origin "refs/tags/$VERSION" >/dev/null 2>&1; then
|
||||
echo -e "${YELLOW}Tag $VERSION already exists on origin; not pushing it again.${NC}"
|
||||
else
|
||||
git push origin "$VERSION"
|
||||
fi
|
||||
|
||||
gh release create "$VERSION" \
|
||||
"$RELEASE_ASSET" \
|
||||
--title "$VERSION" \
|
||||
--notes-file "$RELEASE_NOTES_FILE" \
|
||||
--verify-tag
|
||||
|
||||
rm -f "$RELEASE_NOTES_FILE"
|
||||
echo -e "${GREEN}GitHub release created!${NC}"
|
||||
echo
|
||||
|
||||
echo -e "${GREEN}=== Publish complete! ===${NC}"
|
||||
echo -e "Version: ${YELLOW}$VERSION${NC}"
|
||||
echo -e "Git hash: ${YELLOW}$GIT_HASH${NC}"
|
||||
echo -e "Docker tags pushed:"
|
||||
echo -e " - $DOCKER_IMAGE:latest"
|
||||
echo -e " - $DOCKER_IMAGE:$VERSION"
|
||||
echo -e " - $DOCKER_IMAGE:$GIT_HASH"
|
||||
echo -e "Platforms:"
|
||||
echo -e " - linux/amd64"
|
||||
echo -e " - linux/arm64"
|
||||
echo -e "GitHub release:"
|
||||
echo -e " - $VERSION"
|
||||
echo -e "Release artifact:"
|
||||
echo -e " - $RELEASE_ASSET"
|
||||
Reference in New Issue
Block a user