diff --git a/.github/workflows/package-and-publish.yml b/.github/workflows/package-and-publish.yml index 7cb0a0c46..1cb1a6d80 100644 --- a/.github/workflows/package-and-publish.yml +++ b/.github/workflows/package-and-publish.yml @@ -291,7 +291,7 @@ jobs: VITE_DEBUG: ${{ vars.VITE_DEBUG }} - name: Build, package and publish - uses: tauri-apps/tauri-action@v0 + uses: tauri-apps/tauri-action@v1 id: build-tauri env: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} diff --git a/.github/workflows/pr-protection.yml b/.github/workflows/pr-protection.yml deleted file mode 100644 index 177332694..000000000 --- a/.github/workflows/pr-protection.yml +++ /dev/null @@ -1,176 +0,0 @@ -name: PR Protection - Main Branch - -on: - pull_request: - types: [opened, synchronize, reopened] - -permissions: - pull-requests: write - contents: read - -jobs: - check-pr-protection: - runs-on: ubuntu-latest - - steps: - - name: Check PR target branch - id: check-target - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const pr = context.payload.pull_request; - const baseBranch = pr.base.ref; - const headBranch = pr.head.ref; - const prAuthor = pr.user.login; - const prTitle = pr.title; - const prBody = pr.body || ''; - - // Check if PR is targeting main - const isTargetingMain = baseBranch === 'main'; - - if (!isTargetingMain) { - console.log(`PR is targeting ${baseBranch}, not main. Skipping protection check.`); - core.setOutput('needs_check', 'false'); - return; - } - - console.log(`PR #${pr.number} is targeting ${baseBranch}`); - console.log(`PR author: ${prAuthor}`); - console.log(`PR title: ${prTitle}`); - console.log(`Head branch: ${headBranch}`); - - // Check if PR title follows conventional commit format - // Allowed prefixes: feat, fix, chore, refactor, docs, test, style, perf, revert - const conventionalCommitPattern = /^(feat|fix|chore|refactor|docs|test|style|perf|revert)(\(.+\))?: .+/; - const followsConventionalFormat = conventionalCommitPattern.test(prTitle); - - console.log(`Conventional commit format check: ${followsConventionalFormat ? 'PASSED' : 'FAILED'}`); - if (!followsConventionalFormat) { - console.log(`PR title must follow conventional commit format: : `); - console.log(`Allowed types: feat, fix, chore, refactor, docs, test, style, perf, revert`); - } - - // Check if this is the automated PR from create-develop-to-main-pr workflow - // The automated PR has these characteristics: - // 1. Created by github-actions[bot] - // 2. From develop branch to main - // 3. Title matches pattern: "chore: merge develop into main (vX.Y.Z)" - // 4. Body contains "This PR automatically merges changes from develop into main" - - // Validate PR title matches expected pattern: "chore: merge develop into main" followed by optional version - const expectedTitlePattern = /^chore: merge develop into main( \(v\d+\.\d+\.\d+\))?$/; - const titleMatches = expectedTitlePattern.test(prTitle); - - console.log(`Automated PR title validation: ${titleMatches ? 'PASSED' : 'FAILED'}`); - console.log(`Expected pattern: "chore: merge develop into main" or "chore: merge develop into main (vX.Y.Z)"`); - - const isAutomatedPR = - prAuthor === 'github-actions[bot]' && - headBranch === 'develop' && - titleMatches && - prBody.includes('This PR automatically merges changes from develop into main'); - - if (isAutomatedPR) { - console.log('This PR is from the automated workflow. Allowing it.'); - core.setOutput('needs_check', 'false'); - core.setOutput('is_allowed', 'true'); - } else { - console.log('This PR is NOT from the automated workflow.'); - - // Even if not automated, check if title follows conventional commit format - if (!followsConventionalFormat) { - console.log('PR title does not follow conventional commit format. Blocking it.'); - core.setOutput('needs_check', 'true'); - core.setOutput('is_allowed', 'false'); - core.setOutput('conventional_format_failed', 'true'); - } else { - console.log('PR title follows conventional commit format, but PR is not from automated workflow. Blocking it.'); - core.setOutput('needs_check', 'true'); - core.setOutput('is_allowed', 'false'); - core.setOutput('conventional_format_failed', 'false'); - } - } - - // Store the conventional format check result - core.setOutput('conventional_format', followsConventionalFormat ? 'true' : 'false'); - - - name: Block unauthorized PR to main - if: steps.check-target.outputs.needs_check == 'true' && steps.check-target.outputs.is_allowed == 'false' - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const pr = context.payload.pull_request; - const baseBranch = pr.base.ref; - const prAuthor = pr.user.login; - const prTitle = pr.title; - const headBranch = pr.head.ref; - const prBody = pr.body || ''; - - // Check which validation criteria failed - const conventionalCommitPattern = /^(feat|fix|chore|refactor|docs|test|style|perf|revert)(\(.+\))?: .+/; - const followsConventionalFormat = conventionalCommitPattern.test(prTitle); - - const checks = { - author: prAuthor === 'github-actions[bot]', - branch: headBranch === 'develop', - automatedTitle: /^chore: merge develop into main( \(v\d+\.\d+\.\d+\))?$/.test(prTitle), - body: prBody.includes('This PR automatically merges changes from develop into main'), - conventionalFormat: followsConventionalFormat - }; - - const failedChecks = Object.entries(checks) - .filter(([_, passed]) => !passed) - .map(([check]) => check); - - console.log('Validation checks:', JSON.stringify(checks, null, 2)); - console.log('Failed checks:', failedChecks.join(', ')); - - // Build detailed error message - let errorDetails = ''; - if (failedChecks.length > 0) { - errorDetails = `\n\n**Failed validation checks:**\n`; - if (!checks.author) errorDetails += `- ❌ Author must be \`github-actions[bot]\` (found: \`${prAuthor}\`)\n`; - if (!checks.branch) errorDetails += `- ❌ Head branch must be \`develop\` (found: \`${headBranch}\`)\n`; - if (!checks.automatedTitle) errorDetails += `- ❌ Title must match pattern: \`chore: merge develop into main\` or \`chore: merge develop into main (vX.Y.Z)\` (found: \`${prTitle}\`)\n`; - if (!checks.body) errorDetails += `- ❌ Body must contain: "This PR automatically merges changes from develop into main"\n`; - if (!checks.conventionalFormat) errorDetails += `- ❌ PR title must follow conventional commit format: \`: \`\n`; - if (!checks.conventionalFormat) errorDetails += ` Allowed types: \`feat\`, \`fix\`, \`chore\`, \`refactor\`, \`docs\`, \`test\`, \`style\`, \`perf\`, \`revert\`\n`; - if (!checks.conventionalFormat) errorDetails += ` Example: \`feat: add new authentication endpoint\`\n`; - } - - // Comment on the PR explaining why it was blocked - const codeBlock = '`'; - const commentBody = [ - '## PR Protection Rule Violation', - '', - `This PR targets the ${codeBlock}${baseBranch}${codeBlock} branch, which is protected.`, - '', - `**Only automated PRs from the ${codeBlock}create-develop-to-main-pr.yml${codeBlock} workflow are allowed to target ${codeBlock}${baseBranch}${codeBlock}.**${errorDetails}`, - '', - `**To merge changes into ${codeBlock}${baseBranch}${codeBlock}:**`, - `1. Create your PR targeting the ${codeBlock}develop${codeBlock} branch instead`, - `2. Once merged to ${codeBlock}develop${codeBlock}, the automated workflow will create a PR from ${codeBlock}develop${codeBlock} to ${codeBlock}${baseBranch}${codeBlock}`, - '', - `Please update this PR to target ${codeBlock}develop${codeBlock} instead, or close it and create a new PR targeting ${codeBlock}develop${codeBlock}.` - ].join('\n'); - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: commentBody - }); - - // Fail the workflow to block the PR - let failureMessage = `PR #${pr.number} cannot target ${baseBranch} branch. `; - - if (!checks.conventionalFormat) { - failureMessage += `PR title does not follow conventional commit format. `; - } - - failureMessage += `Failed checks: ${failedChecks.join(', ')}. `; - failureMessage += `Only automated PRs from create-develop-to-main-pr.yml workflow are allowed.`; - - core.setFailed(failureMessage); diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a98f411a3..d6e639de9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -277,7 +277,7 @@ jobs: VITE_DEBUG: ${{ vars.VITE_DEBUG }} - name: Build, package and upload to release - uses: tauri-apps/tauri-action@v0 + uses: tauri-apps/tauri-action@v1 env: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} @@ -286,8 +286,8 @@ jobs: APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} BASE_URL: ${{ vars.BASE_URL }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.UPDATER_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.UPDATER_PRIVATE_KEY_PASSWORD }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} WITH_UPDATER: "true" MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }} with: diff --git a/.github/workflows/update-changelog.yml.disable b/.github/workflows/update-changelog.yml similarity index 99% rename from .github/workflows/update-changelog.yml.disable rename to .github/workflows/update-changelog.yml index 3f0df487a..5276cca39 100644 --- a/.github/workflows/update-changelog.yml.disable +++ b/.github/workflows/update-changelog.yml @@ -3,7 +3,7 @@ name: Update Changelog on: push: branches: - - develop + - main permissions: contents: write @@ -155,7 +155,7 @@ jobs: while i < len(lines): line = lines[i] output_lines.append(line) - + # Check if this is the [Unreleased] header line if line.strip() == "## [Unreleased]": # Find the end of the [Unreleased] section (next ## header or end of file) @@ -169,7 +169,7 @@ jobs: inserted = True # Continue with remaining lines continue - + i += 1 # If [Unreleased] not found, prepend diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml deleted file mode 100644 index 233f84c3e..000000000 --- a/.github/workflows/version-bump.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Version Bump (Legacy) - -on: - workflow_dispatch: - -permissions: - contents: write - -jobs: - version-bump: - runs-on: ubuntu-latest - environment: Production - # Skip if this is a version bump commit to avoid infinite loop - if: "!contains(github.event.head_commit.message, 'chore: bump version')" - - steps: - - name: Generate GitHub App token - id: app-token - uses: tibdex/github-app-token@v1 - with: - app_id: ${{ secrets.XGITHUB_APP_ID }} - private_key: ${{ secrets.XGITHUB_APP_PRIVATE_KEY }} - - - name: Checkout code - uses: actions/checkout@v4 - with: - # Use GitHub App token for checkout and push operations - # This token has bypass permissions if the GitHub App is configured with them - token: ${{ steps.app-token.outputs.token }} - fetch-depth: 1 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20.x - - - name: Configure Git - env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - # Update remote URL to use GitHub App token for all git operations - git remote set-url origin https://${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git - - - name: Pull latest changes - run: | - # Ensure we're on main and pull latest changes to avoid push conflicts - git checkout main - git pull origin main --ff-only - - - name: Bump version - id: version - run: | - # Get current version - CURRENT_VERSION=$(node -p "require('./package.json').version") - echo "Current version: $CURRENT_VERSION" - - # Bump minor version (creates commit and tag) - # The commit message includes [skip ci] to prevent triggering this workflow again - NEW_VERSION=$(npm version minor -m "chore: bump version to %s" | sed 's/v//') - echo "New version: $NEW_VERSION" - echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT - - - name: Push changes - env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - run: | - # Push to main and tags using GitHub App token (remote URL already configured) - git push origin main - git push origin --tags diff --git a/.gitignore b/.gitignore index e740f0efc..428fc2ab6 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ e2e-results/ wdio-logs/ test-results/ coverage/ + +tauri.key +tauri.key.pub diff --git a/CLAUDE.md b/CLAUDE.md index 4f3aa03b9..c55363a7b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -212,10 +212,6 @@ Set in `.env` (Vite exposes `VITE_*` prefixed vars): | Variable | Purpose | | ---------------------------- | ------------------------------------------------------------------- | | `VITE_BACKEND_URL` | Backend API URL (default: `http://localhost:5005`) | -| `VITE_TELEGRAM_API_ID` | Telegram MTProto API ID | -| `VITE_TELEGRAM_API_HASH` | Telegram MTProto API hash | -| `VITE_TELEGRAM_BOT_USERNAME` | Telegram bot username | -| `VITE_TELEGRAM_BOT_ID` | Telegram bot numeric ID | | `VITE_SENTRY_DSN` | Sentry DSN for error reporting (optional) | | `VITE_DEBUG` | Debug mode flag | | `ALPHAHUMAN_DAEMON_INTERNAL` | Force internal daemon mode (default: false, uses external services) | diff --git a/docs/src/08-hooks-utils.md b/docs/src/08-hooks-utils.md index f88d24a05..ae2547c4f 100644 --- a/docs/src/08-hooks-utils.md +++ b/docs/src/08-hooks-utils.md @@ -173,12 +173,6 @@ Environment variable access with defaults. // Backend URL export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'https://api.example.com'; -// Telegram configuration -export const TELEGRAM_API_ID = import.meta.env.VITE_TELEGRAM_API_ID; -export const TELEGRAM_API_HASH = import.meta.env.VITE_TELEGRAM_API_HASH; -export const TELEGRAM_BOT_USERNAME = import.meta.env.VITE_TELEGRAM_BOT_USERNAME; -export const TELEGRAM_BOT_ID = import.meta.env.VITE_TELEGRAM_BOT_ID; - // Debug mode export const DEBUG = import.meta.env.VITE_DEBUG === 'true'; ``` diff --git a/scripts/ci-secrets.example.json b/scripts/ci-secrets.example.json index d976485ce..04bd12bb7 100644 --- a/scripts/ci-secrets.example.json +++ b/scripts/ci-secrets.example.json @@ -1,5 +1,7 @@ { "secrets": { + "XGITHUB_APP_ID": "", + "XGITHUB_APP_PRIVATE_KEY": "", "XGH_TOKEN": "", "GITHUB_TOKEN": "", "UPDATER_GIST_URL": "", @@ -12,15 +14,13 @@ "APPLE_SIGNING_IDENTITY": "", "APPLE_ID": "", "APPLE_APP_SPECIFIC_PASSWORD": "", - "APPLE_TEAM_ID": "", - "VITE_TELEGRAM_BOT_USERNAME": "", - "VITE_TELEGRAM_BOT_ID": "", - "VITE_SENTRY_DSN": "" + "APPLE_TEAM_ID": "" }, "vars": { "BASE_URL": "https://localhost", "VITE_BACKEND_URL": "https://localhost:5005", "VITE_SKILLS_GITHUB_REPO": "", + "VITE_SENTRY_DSN": "", "VITE_DEBUG": "true" } } diff --git a/scripts/test-release-act.sh b/scripts/test-release-act.sh new file mode 100755 index 000000000..dc6369302 --- /dev/null +++ b/scripts/test-release-act.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# Test the Release workflow locally using act. +# +# Defaults are safe: +# - Uses scripts/ci-secrets.example.json for secrets/vars. +# - Runs in dry-run mode unless --run is passed. +# +# Usage: +# ./scripts/test-release-act.sh +# ./scripts/test-release-act.sh --run +# ./scripts/test-release-act.sh --list +# ./scripts/test-release-act.sh --job prepare-release +# ./scripts/test-release-act.sh --release-type minor +# ./scripts/test-release-act.sh --secrets-json scripts/ci-secrets.json --run + +set -euo pipefail +cd "$(git rev-parse --show-toplevel)" + +WORKFLOW=".github/workflows/release.yml" +SECRETS_JSON="scripts/ci-secrets.json" +RELEASE_TYPE="patch" +RUN_MODE="dryrun" +JOB_NAME="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --run) + RUN_MODE="run" + shift + ;; + --dryrun) + RUN_MODE="dryrun" + shift + ;; + --list) + RUN_MODE="list" + shift + ;; + --job) + JOB_NAME="${2:-}" + shift 2 + ;; + --release-type) + RELEASE_TYPE="${2:-patch}" + shift 2 + ;; + --secrets-json) + SECRETS_JSON="${2:-}" + shift 2 + ;; + *) + echo "Unknown argument: $1" >&2 + exit 1 + ;; + esac +done + +if [[ ! -f "$SECRETS_JSON" ]]; then + echo "Secrets JSON not found: $SECRETS_JSON" >&2 + exit 1 +fi + +if ! command -v act >/dev/null 2>&1; then + echo "act is required. Install with: brew install act" >&2 + exit 1 +fi + +if ! command -v jq >/dev/null 2>&1; then + echo "jq is required. Install with: brew install jq" >&2 + exit 1 +fi + +case "$RELEASE_TYPE" in + major|minor|patch) ;; + *) + echo "--release-type must be one of: major, minor, patch" >&2 + exit 1 + ;; +esac + +if [[ "$RUN_MODE" == "list" ]]; then + act -W "$WORKFLOW" --list + exit 0 +fi + +SECRETS_FILE="$(mktemp)" +VARS_FILE="$(mktemp)" +EVENT_JSON="$(mktemp)" +trap 'rm -f "$SECRETS_FILE" "$VARS_FILE" "$EVENT_JSON"' EXIT + +jq -r '.secrets // {} | to_entries[] | "\(.key)=\(.value)"' "$SECRETS_JSON" > "$SECRETS_FILE" +jq -r '.vars // {} | to_entries[] | "\(.key)=\(.value)"' "$SECRETS_JSON" > "$VARS_FILE" + +cat > "$EVENT_JSON" < Result<(), String> { - if skill_id != "telegram" { - Err("TDLib operations only available in telegram skill".to_string()) - } else { - Ok(()) - } -} - /// Sanitize error message for use with QuickJS/rquickjs. /// Some messages (e.g. from SQLite "Invalid symbol 45, offset 19") can trigger /// "Invalid symbol" when rquickjs creates the JS exception — avoid comma and hyphen.