Skip to content

feat: initial release of V2X J2735 codec with Python & Node.js bindings #118

feat: initial release of V2X J2735 codec with Python & Node.js bindings

feat: initial release of V2X J2735 codec with Python & Node.js bindings #118

Workflow file for this run

name: Core CI & Release
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
jobs:
verify-version:
name: Verify Version Match
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
TAG_VERSION=${GITHUB_REF_NAME#v}
FILE_VERSION=$(cat VERSION)
if [[ "$TAG_VERSION" != "$FILE_VERSION"* ]]; then
echo "::error::Version Mismatch! Tag '$TAG_VERSION' vs VERSION '$FILE_VERSION'"
exit 1
fi
build-core:
name: Build WASM Core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache Emscripten
id: cache-emsdk
uses: actions/cache@v4
with:
path: emsdk
key: ${{ runner.os }}-emsdk-4.0.10
- name: Install Emscripten
if: steps.cache-emsdk.outputs.cache-hit != 'true'
run: |
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk && ./emsdk install 4.0.10 && ./emsdk activate 4.0.10
- name: Compile
run: |
source emsdk/emsdk_env.sh
make wasm
# Create __init__.py files
touch j2735codec/bindings/python/src/j2735codec/generated/__init__.py
touch j2735codec/bindings/python/src/j2735codec/protobuf/__init__.py
- name: Upload Python Artifacts
uses: actions/upload-artifact@v4
with:
name: python-wasm-binaries
# Use the parent directory to ensure the j2735codec folder structure is preserved
path: j2735codec/bindings/python/src/j2735codec/
include-hidden-files: true
- name: Upload Node Artifacts
uses: actions/upload-artifact@v4
with:
name: node-wasm-binaries
path: j2735codec/bindings/node/src/generated/
test-bindings:
name: Test ${{ matrix.binding }}
needs: build-core
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
binding: [python, node]
steps:
- uses: actions/checkout@v4
- name: Download Artifacts
uses: actions/download-artifact@v4
with:
name: ${{ matrix.binding }}-wasm-binaries
path: j2735codec/bindings/${{ matrix.binding }}/${{ matrix.binding == 'python' && 'src/j2735codec/' || 'src/generated/' }}
- name: Repair and Verify Structure
run: |
if [ "${{ matrix.binding }}" == "python" ]; then
cd j2735codec/bindings/python/src/j2735codec
touch __init__.py generated/__init__.py protobuf/__init__.py
echo "🔍 Verifying Python Structure:"
ls -R
fi
- name: Setup & Test
run: |
if [ "${{ matrix.binding }}" == "python" ]; then
pip install uv
cd j2735codec/bindings/python
uv sync --reinstall-package j2735codec
uv run pytest tests -s
else
npm install
npm run build -w j2735codec
npm test -w j2735codec
fi
publish-release:
name: Publish Release
needs: [verify-version, test-bindings]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download Artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Build & Stage Assets
run: |
STAGING="$GITHUB_WORKSPACE/dist_release"
mkdir -p "$STAGING"
VERSION=$(cat VERSION)
# --- 2. Build Python Bindings ---
PY_DIR="j2735codec/bindings/python"
# Clean and Recreate Structure
mkdir -p "$PY_DIR/src/j2735codec/generated"
mkdir -p "$PY_DIR/src/j2735codec/protobuf"
# Copy artifacts - using '.' to ensure we copy contents into the pre-made folders
cp -a artifacts/python-wasm-binaries/. "$PY_DIR/src/j2735codec/"
# RE-INJECT Package Markers (Vital for Wheel packaging)
touch "$PY_DIR/src/j2735codec/__init__.py"
touch "$PY_DIR/src/j2735codec/generated/__init__.py"
touch "$PY_DIR/src/j2735codec/protobuf/__init__.py"
# Debug: Log the tree to the console so we can see what's happening
echo "📂 Final Python Build Tree:"
ls -R "$PY_DIR/src/j2735codec/"
cp LICENSE "$PY_DIR/"
cp NOTICE "$PY_DIR/"
pip install uv
cd "$PY_DIR"
uv build --wheel --out-dir ./dist_local
# Verify Wheel Contents before moving (Security Check)
WHL_FILE=$(ls ./dist_local/*.whl)
if ! unzip -l "$WHL_FILE" | grep -q "j2735codec/generated/j2735codec.wasm"; then
echo "::error::WASM file missing from generated wheel!"
exit 1
fi
for f in ./dist_local/*; do cp "$f" "$STAGING/$(basename $f)"; done
cd "$GITHUB_WORKSPACE"
# --- 3. Build Node Bindings ---
JS_DIR="j2735codec/bindings/node"
mkdir -p "$JS_DIR/src/generated/"
cp -a artifacts/node-wasm-binaries/. "$JS_DIR/src/generated/"
cp LICENSE "$JS_DIR/"
cp NOTICE "$JS_DIR/"
npm install
npm run build -w j2735codec
cd "$JS_DIR" && npm pack
for f in *.tgz; do cp "$f" "$STAGING/$f"; done
cd "$GITHUB_WORKSPACE"
# --- 4. Package Python Samples ---
SAMPLE_DIR="etx/examples/python"
if [ -d "$SAMPLE_DIR" ]; then
cp LICENSE "$SAMPLE_DIR/"
cp NOTICE "$SAMPLE_DIR/"
# Inject the language wrapper (the wheel)
mkdir -p "$SAMPLE_DIR/j2735codec"
cp "$STAGING"/*.whl "$SAMPLE_DIR/j2735codec/"
cd "$SAMPLE_DIR"
VERSION=$(cat "$GITHUB_WORKSPACE/VERSION")
ZIP_NAME="python-etx-samples-$VERSION.zip"
zip -r "$STAGING/$ZIP_NAME" . \
-x "*config.json" -x "*/.venv/*" -x "*.venv*" -x "*/.env*" -x "*/__pycache__/*"
cd "$GITHUB_WORKSPACE"
fi
# --- 5. Package Node/TS Samples ---
NODE_SAMPLE_DIR="etx/examples/node"
if [ -d "$NODE_SAMPLE_DIR" ]; then
VERSION=$(cat "$GITHUB_WORKSPACE/VERSION")
PKG_STAGING="$GITHUB_WORKSPACE/node_pkg_temp"
mkdir -p "$PKG_STAGING"
# 1. Copy the source (leaves your repo untouched)
cp -r "$NODE_SAMPLE_DIR"/. "$PKG_STAGING/"
# 2. Rewrite the path to the PORTABLE local path
# This changes the monorepo path to the ZIP-friendly path
sed -i "s|\"j2735codec\": \".*\"|\"j2735codec\": \"file:./j2735codec/j2735codec-$VERSION.tgz\"|g" "$PKG_STAGING/package.json"
# 3. Inject the tarball into the staging area
mkdir -p "$PKG_STAGING/j2735codec"
cp "$STAGING"/j2735codec-$VERSION.tgz "$PKG_STAGING/j2735codec/"
# 4. GENERATE THE LOCKFILE HERE
# This lockfile will now contain the correct, portable reference to the codec
cd "$PKG_STAGING"
npm install --package-lock-only --no-workspaces
# 5. ZIP EVERYTHING
# Now the ZIP contains a package.json and a package-lock.json
# that both point to the local ./j2735codec folder.
ZIP_NAME="node-etx-samples-$VERSION.zip"
zip -r "$STAGING/$ZIP_NAME" . -x "node_modules/*" "dist/*"
# We zip EVERYTHING (*) in this folder, excluding ONLY the junk
zip -r "$STAGING/$ZIP_NAME" . \
-x "certs/*" "dist/*" "node_modules/*" "config.json" ".env*" "npm-debug.log*"
cd "$GITHUB_WORKSPACE"
rm -rf "$PKG_STAGING"
fi
# --- 6. Finalize Staging ---
cp "$PY_DIR/src/j2735codec/generated/j2735codec.wasm" "$STAGING/j2735codec-$VERSION.wasm"
cp LICENSE "$STAGING/"
cp NOTICE "$STAGING/"
# --- 7. FIXED Safe Cleanup ---
echo "🧹 Performing final cleanup..."
# Do NOT use -delete on a broad glob. Delete specific files.
rm -f "$STAGING"/.DS_Store
rm -f "$STAGING"/.env
rm -f "$STAGING"/default.gitignore
echo "✅ Contents of $STAGING for release:"
ls -la "$STAGING"
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: "Release ${{ github.ref_name }}"
files: dist_release/*
generate_release_notes: true