diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 9cdc577c..d9a8565f 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -1,9 +1,29 @@ name: "Setup Environment" -description: "Sets up the environment with yarn, Node.js, and turbo" +description: "Sets up the environment with yarn, Node.js, turbo, and Compact compiler" + +inputs: + skip-compact: + description: "Skip Compact compiler installation" + required: false + default: "false" + +outputs: + compact-home: + description: "Path to Compact compiler installation" + value: ${{ steps.compact-outputs.outputs.compact-home }} + compact-version: + description: "Installed Compact compiler version" + value: ${{ steps.compact-outputs.outputs.version }} runs: using: "composite" steps: + - name: Set shared environment variables + shell: bash + run: | + echo "COMPILER_VERSION=0.24.0" >> $GITHUB_ENV + echo "LANGUAGE_VERSION=0.16.0" >> $GITHUB_ENV + - name: Get yarn cache directory path shell: bash id: yarn-cache-dir-path @@ -25,6 +45,14 @@ runs: restore-keys: | ${{ runner.os }}-turbo-${{ hashFiles('.turbo/*') }} + - name: Cache Compact compiler + if: inputs.skip-compact != 'true' + uses: actions/cache@v4 + id: compact-cache + with: + path: ~/compactc + key: compact-compiler-${{ env.COMPILER_VERSION }}-${{ runner.os }} + - name: Setup Node.js uses: actions/setup-node@v4 with: @@ -40,5 +68,86 @@ runs: env: TURBO_MAJOR_VERSION: 2 TURBO_TELEMETRY_DISABLED: 1 + run: npm install turbo@${{ env.TURBO_MAJOR_VERSION }} -g + + - name: Install Compact compiler + if: inputs.skip-compact != 'true' && steps.compact-cache.outputs.cache-hit != 'true' + shell: bash + run: | + set -euo pipefail + + COMPACT_HOME="$HOME/compactc" + COMPACT_ZIP_DIR="$HOME/compactc_download" + + echo "๐Ÿ”ง Installing Compact compiler v$COMPILER_VERSION..." + + mkdir -p "$COMPACT_HOME" + mkdir -p "$COMPACT_ZIP_DIR" + + ZIP_FILE="compactc_v${COMPILER_VERSION}_x86_64-unknown-linux-musl.zip" + DOWNLOAD_URL="https://d3fazakqrumx6p.cloudfront.net/artifacts/compiler/compactc_${COMPILER_VERSION}/${ZIP_FILE}" + + echo "โฌ‡๏ธ Downloading Compact compiler from $DOWNLOAD_URL..." + curl -fLs "$DOWNLOAD_URL" -o "$COMPACT_ZIP_DIR/compactc.zip" + + echo "๐Ÿงช Validating ZIP archive..." + if ! unzip -tq "$COMPACT_ZIP_DIR/compactc.zip"; then + echo "::error::โŒ ZIP file is invalid or corrupted." + exit 1 + fi + + echo "๐Ÿ“ฆ Extracting Compact compiler..." + unzip -q "$COMPACT_ZIP_DIR/compactc.zip" -d "$COMPACT_HOME" + chmod +x "$COMPACT_HOME"/{compactc,compactc.bin,zkir} + + echo "โœ… Compact compiler extracted to $COMPACT_HOME" + + - name: Setup Compact environment + if: inputs.skip-compact != 'true' + shell: bash + run: | + COMPACT_HOME="$HOME/compactc" + echo "๐Ÿ“ Setting Compact environment variables..." + echo "COMPACT_HOME=$COMPACT_HOME" >> "$GITHUB_ENV" + echo "$COMPACT_HOME" >> "$GITHUB_PATH" + + if [ -f "$COMPACT_HOME/compactc" ]; then + echo "โœ… Compact compiler is installed at $COMPACT_HOME" + else + echo "::error::โŒ Compact compiler not found in $COMPACT_HOME" + exit 1 + fi + + - name: Set Compact outputs + if: inputs.skip-compact != 'true' + id: compact-outputs + shell: bash + run: | + echo "compact-home=$HOME/compactc" >> $GITHUB_OUTPUT + echo "version=$COMPILER_VERSION" >> $GITHUB_OUTPUT + + - name: Check compiler and language version + if: inputs.skip-compact != 'true' + shell: bash run: | - npm install turbo@${{ env.TURBO_MAJOR_VERSION }} -g + set -euo pipefail + + echo "๐Ÿ” Checking Compact compiler version..." + COMPILER_OUTPUT=$(compactc --version) + COMPUTED_COMPILER_VERSION=$(echo "$COMPILER_OUTPUT" | grep -oP '\b0\.[0-9]+\.[0-9]+\b' | head -n 1) + + if [ "$COMPUTED_COMPILER_VERSION" != "$COMPILER_VERSION" ]; then + echo "::error::โŒ Compiler version mismatch!%0AExpected: $COMPILER_VERSION%0AGot: $COMPUTED_COMPILER_VERSION" + exit 1 + fi + echo "โœ… Compiler version matches: $COMPUTED_COMPILER_VERSION" + + echo "๐Ÿ” Checking Compact language version..." + LANGUAGE_OUTPUT=$(compactc --language-version) + COMPUTED_LANGUAGE_VERSION=$(echo "$LANGUAGE_OUTPUT" | grep -oP '\b0\.[0-9]+\.[0-9]+\b' | tail -n 1) + + if [ "$COMPUTED_LANGUAGE_VERSION" != "$LANGUAGE_VERSION" ]; then + echo "::error::โŒ Language version mismatch!%0AExpected: $LANGUAGE_VERSION%0AGot: $COMPUTED_LANGUAGE_VERSION" + exit 1 + fi + echo "โœ… Language version matches: $COMPUTED_LANGUAGE_VERSION" diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 00000000..82b1cc93 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,60 @@ +name: Update version on new release branch + +on: + create: + +permissions: + contents: write + pull-requests: write + +jobs: + update_version: + if: github.ref_type == 'branch' && startsWith(github.ref, 'refs/heads/release-v') + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Extract current version + run: | + CURRENT_VERSION=$(node -p "require('./contracts/package.json').version") + echo "CURRENT_VERSION=$CURRENT_VERSION" >> "$GITHUB_ENV" + + - name: Extract new version number + run: echo "NEW_VERSION=${GITHUB_REF#refs/heads/release-v}" >> "$GITHUB_ENV" + + - name: Replace version in files + run: | + echo "Current version: $CURRENT_VERSION" + echo "New version: $NEW_VERSION" + + # Update package.json version field manually + cd contracts + node -e " + const fs = require('fs'); + const pkg = require('./package.json'); + pkg.version = '$NEW_VERSION'; + fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n'); + console.log('Updated package.json to version $NEW_VERSION'); + " + # Update yarn.lock to reflect the new version + yarn install + cd .. + + # Escape special characters for sed + ESCAPED_CURRENT=$(printf '%s' "$CURRENT_VERSION" | sed -e 's/[\/&]/\\&/g') + ESCAPED_NEW=$(printf '%s' "$NEW_VERSION" | sed -e 's/[\/&]/\\&/g') + + # Replace version in contracts/src/ + find ./contracts/src/ -type d -name '.*' -prune -o \ + -type f -exec sed -i "s#$ESCAPED_CURRENT#$ESCAPED_NEW#g" {} + + + # Replace version in docs/, excluding package-lock.json + find ./docs/ -type d -name '.*' -prune -o \ + -type f ! -name 'package-lock.json' -exec sed -i "s#$ESCAPED_CURRENT#$ESCAPED_NEW#g" {} + + + - name: Auto-commit changes + uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 #v6.0.1 + with: + commit_message: Bump version to ${{ env.NEW_VERSION }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..7a57be73 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,94 @@ +name: Publish Package on Release + +on: + release: + types: [published] + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Environment + uses: ./.github/actions/setup + + - name: Build contracts + run: turbo build --filter=!'docs' + + - name: Validate version consistency + run: | + RELEASE_VERSION=${GITHUB_REF#refs/tags/v} + PACKAGE_VERSION=$(node -p "require('./contracts/package.json').version") + if [ "$RELEASE_VERSION" != "$PACKAGE_VERSION" ]; then + echo "โŒ Version mismatch: Release $RELEASE_VERSION vs Package $PACKAGE_VERSION" + exit 1 + fi + echo "โœ… Version consistency validated: $RELEASE_VERSION" + + - name: Setup npm registry + uses: actions/setup-node@v4 + with: + registry-url: 'https://registry.npmjs.org' + + - name: Pack tarball + id: pack + run: | + cd contracts/dist + TARBALL=$(npm pack | tail -1) + echo "tarball_name=$TARBALL" >> $GITHUB_OUTPUT + echo "tarball=$(pwd)/$TARBALL" >> $GITHUB_OUTPUT + + # Determine dist-tag based on semver prerelease + PACKAGE_VERSION=$(node -p "require('./package.json').version") + if [[ "$PACKAGE_VERSION" =~ -.*$ ]]; then + # Has prerelease suffix (anything after -) + if [[ "$PACKAGE_VERSION" =~ -(alpha|beta|rc) ]]; then + echo "tag=beta" >> $GITHUB_OUTPUT + else + echo "tag=next" >> $GITHUB_OUTPUT + fi + else + # Stable release + echo "tag=latest" >> $GITHUB_OUTPUT + fi + + - name: Verify tarball integrity + run: | + echo "=== Verifying tarball contents ===" + PACKAGE_NAME=$(tar xfO "${{ steps.pack.outputs.tarball }}" package/package.json | jq -r .name) + PACKAGE_VERSION=$(tar xfO "${{ steps.pack.outputs.tarball }}" package/package.json | jq -r .version) + PRIVATE_FIELD=$(tar xfO "${{ steps.pack.outputs.tarball }}" package/package.json | jq -r '.private // "not found"') + + echo "๐Ÿ“ฆ Package: $PACKAGE_NAME@$PACKAGE_VERSION" + echo "๐Ÿท๏ธ Tag: ${{ steps.pack.outputs.tag }}" + echo "๐Ÿ”’ Private field: $PRIVATE_FIELD" + + # Ensure no private field + if [ "$PRIVATE_FIELD" = "true" ]; then + echo "โŒ Tarball contains private: true - cannot publish" + exit 1 + fi + + - name: Publish to npm + run: | + # Create .npmrc with auth token + echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc + + # Publish the tarball with appropriate tag + npm publish "${{ steps.pack.outputs.tarball }}" --tag "${{ steps.pack.outputs.tag }}" --access public + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: true + + - name: Log success + run: | + PACKAGE_NAME=$(tar xfO "${{ steps.pack.outputs.tarball }}" package/package.json | jq -r .name) + PACKAGE_VERSION=$(tar xfO "${{ steps.pack.outputs.tarball }}" package/package.json | jq -r .version) + echo "โœ… Successfully published $PACKAGE_NAME@$PACKAGE_VERSION to npm with tag ${{ steps.pack.outputs.tag }}" + echo "๐Ÿ“ฆ Install with: npm install $PACKAGE_NAME@${{ steps.pack.outputs.tag }}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bb523efd..a21f657b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,11 +6,6 @@ on: branches: - main -env: - TURBO_TELEMETRY_DISABLED: 1 - COMPILER_VERSION: "0.24.0" - LANGUAGE_VERSION: "0.16.0" - jobs: run-suite: name: Run Test Suite @@ -26,82 +21,8 @@ jobs: - name: Setup Environment uses: ./.github/actions/setup - - name: Install Compact compiler - id: setup - shell: bash - run: | - set -euo pipefail - # Create directory for compiler - COMPACT_HOME="$HOME/compactc" - mkdir -p "$COMPACT_HOME" - - # Create URL - ZIP_FILE="compactc_v${COMPILER_VERSION}_x86_64-unknown-linux-musl.zip" - DOWNLOAD_URL="https://d3fazakqrumx6p.cloudfront.net/artifacts/compiler/compactc_${COMPILER_VERSION}/${ZIP_FILE}" - - echo "โฌ‡๏ธ Downloading Compact compiler..." - curl -Ls "$DOWNLOAD_URL" -o "$COMPACT_HOME/compactc.zip" - - echo "๐Ÿ“ฆ Extracting..." - unzip -q "$COMPACT_HOME/compactc.zip" -d "$COMPACT_HOME" - chmod +x "$COMPACT_HOME"/{compactc,compactc.bin,zkir} - - echo "๐Ÿ“ Setting environment variables..." - echo "COMPACT_HOME=$COMPACT_HOME" >> "$GITHUB_ENV" - echo "$COMPACT_HOME" >> "$GITHUB_PATH" - - echo "โœ… Verifying installation..." - if [ ! -f "$COMPACT_HOME/compactc" ]; then - echo "::error::โŒ compactc not found in $COMPACT_HOME" - exit 1 - fi - - echo "๐Ÿค– Testing installation..." - "$COMPACT_HOME/compactc" --version - - - name: Check compiler and language version - run: | - COMPILER_OUTPUT=$(compactc --version) - COMPUTED_COMPILER_VERSION=$(echo "$COMPILER_OUTPUT" | grep -oP '\b0\.[0-9]+\.[0-9]+\b' | head -n 1) - if [ "$COMPUTED_COMPILER_VERSION" != "$COMPILER_VERSION" ]; then - errMsg="โŒ Compiler version mismatch!%0AExpected: $COMPILER_VERSION%0AGot: $COMPUTED_COMPILER_VERSION" - echo "::error::$errMsg" - exit 1 - fi - echo "โœ… Compiler version matches: $COMPUTED_COMPILER_VERSION" - - LANGUAGE_OUTPUT=$(compactc --language-version) - COMPUTED_LANGUAGE_VERSION=$(echo "$LANGUAGE_OUTPUT" | grep -oP '\b0\.[0-9]+\.[0-9]+\b' | tail -n 1) - if [ "$COMPUTED_LANGUAGE_VERSION" != "$LANGUAGE_VERSION" ]; then - errMsg="โŒ Language version mismatch!%0AExpected: $LANGUAGE_VERSION%0AGot: $COMPUTED_LANGUAGE_VERSION" - echo "::error::$errMsg" - exit 1 - fi - - echo "โœ… Language version matches: $COMPUTED_LANGUAGE_VERSION" - - - name: Compile contracts (with retry on hash mismatch) - shell: bash - run: | - set -euo pipefail - - compile() { - echo "โš™๏ธ Running Compact compilation..." - if ! output=$(turbo compact --concurrency=1 2>&1); then - echo "โŒ Compilation failed." - if echo "$output" | grep -q "Hash mismatch" && [ -d "$HOME/.cache/midnight/zk-params" ]; then - echo "โš ๏ธ Hash mismatch detected *and* zk-params exists. Removing cache..." - rm -rf "$HOME/.cache/midnight/zk-params" - echo "::notice::โ™ป๏ธ Retrying compilation after clearing zk-params..." - turbo compact --concurrency=1 || { echo "::error::โŒ Retry also failed."; exit 1; } - else - echo "๐Ÿšซ Compilation failed for another reason or zk-params missing. No retry." - exit 1 - fi - fi - } - - compile + - name: Compile contracts (with retry) + run: turbo compact --filter=@openzeppelin-compact/contracts --concurrency=1 - name: Run type checks run: turbo types diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..6ad36f80 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,42 @@ +# Releasing + +(1) Checkout the branch to be released. +This will usually be `main` except in the event of a hotfix. +For hotfixes, checkout the release branch you want to fix. + +(2) Create a new release branch. + +```sh +git checkout -b release-v0.2.0 +``` + +(3) Push and open a PR targeting `main` to carefully review the release changes. +This will trigger a GitHub workflow that automatically bumps the version number throughout the project. + +```sh +git push release-v0.2.0 +``` + +(4) Once merged, pull the changes from the release branch. +Then, create a tag on the release branch and push it to the main repository. +Note that the version changes must be pulled *before* the tag is created; +otherwise, the version validation check will fail in the release workflow. + +```sh +git pull +git tag v0.2.0 +git push origin v0.2.0 +``` + +(5) After that, go to the repo's [releases page](https://github.com/OpenZeppelin/compact-contracts/releases/). +[Create a new release](https://github.com/OpenZeppelin/compact-contracts/releases/new) with the new tag and the base branch as target (`main` except in the event of a hotfix). +Make sure to write a detailed release description and a short changelog. +Once published, this will trigger a workflow to upload the release tarball to npm. + +(6) Finally, from the released tag, +create and push a doc branch to deploy the corresponding version to the doc-site. + +```sh +git checkout -b docs-v0.2.0 +git push docs-v0.2.0 +``` diff --git a/compact/src/Builder.ts b/compact/src/Builder.ts index b8f05197..3c4d2c78 100755 --- a/compact/src/Builder.ts +++ b/compact/src/Builder.ts @@ -13,8 +13,9 @@ const execAsync = promisify(exec); /** * A class to handle the build process for a project. * Runs CompactCompiler as a prerequisite, then executes build steps (TypeScript compilation, - * artifact copying, etc.) - * with progress feedback and colored output for success and error states. + * artifact copying, etc.) with progress feedback and colored output for success and error states. + * + * Creates a clean distribution structure without src/ paths for professional import experience. * * @notice `cmd` scripts discard `stderr` output and fail silently because this is * handled in `executeStep`. @@ -32,45 +33,51 @@ const execAsync = promisify(exec); * Compactc version: 0.24.0 * โœ” [COMPILE] [2/2] Compiled MockAccessControl.compact * Compactc version: 0.24.0 - * โœ” [BUILD] [1/3] Compiling TypeScript - * โœ” [BUILD] [2/3] Copying artifacts - * โœ” [BUILD] [3/3] Copying and cleaning .compact files - * ``` - * - * @example Failed Compilation Output - * ``` - * โ„น [COMPILE] Found 2 .compact file(s) to compile - * โœ– [COMPILE] [1/2] Failed AccessControl.compact - * Compactc version: 0.24.0 - * Error: Expected ';' at line 5 in AccessControl.compact - * ``` - * - * @example Failed Build Step Output - * ``` - * โ„น [COMPILE] Found 2 .compact file(s) to compile - * โœ” [COMPILE] [1/2] Compiled AccessControl.compact - * โœ” [COMPILE] [2/2] Compiled MockAccessControl.compact - * โœ– [BUILD] [1/3] Failed Compiling TypeScript - * error TS1005: ';' expected at line 10 in file.ts - * [BUILD] โŒ Build failed: Command failed: tsc --project tsconfig.build.json + * โœ” [BUILD] [1/4] Cleaning dist directory + * โœ” [BUILD] [2/4] Compiling TypeScript + * โœ” [BUILD] [3/4] Copying .compact files + * โœ” [BUILD] [4/4] Copying package metadata * ``` */ export class CompactBuilder { private readonly compilerFlags: string; private readonly steps: Array<{ cmd: string; msg: string; shell?: string }> = [ + // Step 1: Clean dist directory + { + cmd: 'rm -rf dist && mkdir -p dist', + msg: 'Cleaning dist directory', + shell: '/bin/bash', + }, + + // Step 2: TypeScript compilation (witnesses/ -> dist/witnesses/) { cmd: 'tsc --project tsconfig.build.json', msg: 'Compiling TypeScript', }, + + // Step 3: Copy .compact files preserving structure (excludes Mock* files and archive/) { - cmd: 'mkdir -p dist/artifacts && cp -Rf src/artifacts/* dist/artifacts/ 2>/dev/null || true', - msg: 'Copying artifacts', + cmd: ` + find src -type f -name "*.compact" ! -name "Mock*" ! -path "*/archive/*" | while read file; do + # Remove src/ prefix from path + rel_path="\${file#src/}" + mkdir -p "dist/\$(dirname "\$rel_path")" + cp "\$file" "dist/\$rel_path" + done + `, + msg: 'Copying .compact files (excluding mocks and archive)', shell: '/bin/bash', }, + + // Step 4: Copy essential files for distribution { - cmd: 'mkdir -p dist && find src -type f -name "*.compact" -exec cp {} dist/ \\; 2>/dev/null && rm dist/Mock*.compact 2>/dev/null || true', - msg: 'Copying and cleaning .compact files', + cmd: ` + # Copy package.json and README + cp package.json dist/ 2>/dev/null || true + cp ../README.md dist/ # Go up one level to monorepo root + `, + msg: 'Copying package metadata', shell: '/bin/bash', }, ]; @@ -99,6 +106,8 @@ export class CompactBuilder { for (const [index, step] of this.steps.entries()) { await this.executeStep(step, index, this.steps.length); } + + console.log(chalk.green('\nโœ… Build complete!')); } /** @@ -155,6 +164,8 @@ export class CompactBuilder { .split('\n') .filter((line: string): boolean => line.trim() !== '') .map((line: string): string => ` ${line}`); - console.log(colorFn(lines.join('\n'))); + if (lines.length > 0) { + console.log(colorFn(lines.join('\n'))); + } } } diff --git a/contracts/package.json b/contracts/package.json index acd41414..6290b9f4 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,18 +1,25 @@ { "name": "@openzeppelin-compact/contracts", - "private": true, - "type": "module", - "main": "dist/index.js", - "module": "dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "require": "./dist/index.js", - "import": "./dist/index.js", - "default": "./dist/index.js" - } + "version": "0.0.1", + "description": "OpenZeppelin Compact contract library", + "keywords": [ + "openzeppelin", + "compact", + "midnight", + "contracts", + "security" + ], + "repository": { + "type": "git", + "url": "https://github.com/OpenZeppelin/compact-contracts.git" + }, + "license": "MIT", + "author": "OpenZeppelin Community ", + "bugs": { + "url": "https://github.com/OpenZeppelin/compact-contracts/issues" }, + "homepage": "https://docs.openzeppelin.com/contracts-compact/", + "type": "module", "scripts": { "compact": "compact-compiler", "compact:access": "compact-compiler --dir access", @@ -20,7 +27,7 @@ "compact:security": "compact-compiler --dir security", "compact:token": "compact-compiler --dir token", "compact:utils": "compact-compiler --dir utils", - "build": "compact-builder && tsc", + "build": "compact-builder", "test": "compact-compiler --skip-zk && vitest run", "types": "tsc -p tsconfig.json --noEmit", "clean": "git clean -fXd" diff --git a/contracts/tsconfig.json b/contracts/tsconfig.json index 3e90b0a9..04859e06 100644 --- a/contracts/tsconfig.json +++ b/contracts/tsconfig.json @@ -1,5 +1,8 @@ { - "include": ["src/**/*.ts"], + "include": [ + "src/**/witnesses/**/*.ts" + ], + "exclude": ["src/archive/"], "compilerOptions": { "rootDir": "src", "outDir": "dist", @@ -13,7 +16,6 @@ "noImplicitAny": true, "strict": true, "isolatedModules": true, - "sourceMap": true, "resolveJsonModule": true, "esModuleInterop": true, "skipLibCheck": true