Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b012fcaaac | |||
| 7a1d3e7917 | |||
| 2ee3caac0d | |||
| 78e93e9766 | |||
| 83d66feacc | |||
| d6ace14e70 | |||
| 2cbfd21539 | |||
| f7c070e45b | |||
| 06ed9734c6 | |||
| d93d4c6cd0 | |||
| faba2dee5d | |||
| ca3976fe62 | |||
| 7fea37eb5d | |||
| 68379dcf3a | |||
| 56f276af1b | |||
| f024d21cf5 | |||
| 67bc28cff4 | |||
| d7fa9dd570 | |||
| 428b058cbc | |||
| 0b5e318538 | |||
| 568507ab98 | |||
| 198f53b96f | |||
| 9babb94e07 | |||
| ef6a7b2583 | |||
| d446d711ba | |||
| d1af76f444 | |||
| 914d914b74 | |||
| 8f57224700 | |||
| 1e047eba91 | |||
| 14d6da24db | |||
| 346a30fcbb | |||
| bac4148020 | |||
| 709d408983 | |||
| 18d41743e8 | |||
| 0d71433cc1 |
@@ -0,0 +1,41 @@
|
||||
# pwap/web Docker build context (root) — exclude everything not needed
|
||||
# for `web/` build. Other monorepo subprojects stay out of the image.
|
||||
|
||||
# Other monorepo dirs (we only need web/ + shared/)
|
||||
exchange/
|
||||
mobile/
|
||||
pwap-mobile/
|
||||
docs/
|
||||
res/
|
||||
|
||||
# All node_modules everywhere
|
||||
**/node_modules/
|
||||
**/dist/
|
||||
**/build/
|
||||
|
||||
# Git, GitHub
|
||||
.git/
|
||||
.github/
|
||||
|
||||
# Env files (built-in vars are passed as build-args from CI)
|
||||
**/.env
|
||||
**/.env.*
|
||||
!**/.env.example
|
||||
|
||||
# Editor / OS
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Cache
|
||||
**/.eslintcache
|
||||
**/coverage/
|
||||
|
||||
# Already-built artifacts (we rebuild fresh inside container)
|
||||
web/dist/
|
||||
shared/**/dist/
|
||||
@@ -0,0 +1,58 @@
|
||||
name: CodeQL (SAST)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
schedule:
|
||||
# Every Sunday at 02:00 UTC — catches CVEs disclosed during the week
|
||||
- cron: '0 2 * * 0'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
actions: read
|
||||
|
||||
concurrency:
|
||||
group: codeql-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze ${{ matrix.language }}
|
||||
runs-on: pwap-runner
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [javascript-typescript]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# OWASP top-10 + injection + auth flaws + prototype pollution
|
||||
queries: security-extended,security-and-quality
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
# GitHub Advanced Security dashboard upload requires paid plan.
|
||||
# SARIF saved as a downloadable artifact instead.
|
||||
upload: false
|
||||
output: /tmp/codeql-results
|
||||
|
||||
- name: Upload SARIF as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: codeql-sarif-${{ matrix.language }}
|
||||
path: /tmp/codeql-results/*.sarif
|
||||
retention-days: 7
|
||||
@@ -6,14 +6,25 @@ on:
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
rollback_to:
|
||||
description: 'Rollback to git SHA (skips build, redeploys old image). Empty = normal deploy.'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write # version bump commit
|
||||
packages: write # GHCR push
|
||||
|
||||
env:
|
||||
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
|
||||
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: pezkuwichain/pwap-web
|
||||
|
||||
jobs:
|
||||
# ========================================
|
||||
@@ -21,7 +32,7 @@ jobs:
|
||||
# ========================================
|
||||
web:
|
||||
name: Web App
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: pwap-runner
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -75,13 +86,164 @@ jobs:
|
||||
path: web/dist/
|
||||
|
||||
# ========================================
|
||||
# DEPLOY WEB APP TO VPS
|
||||
# BUILD & PUSH DOCKER IMAGE TO GHCR
|
||||
# Immutable artifact for audit + rollback (vs ephemeral GHA artifact).
|
||||
# Tagged with git SHA so any commit can be redeployed by SHA.
|
||||
# ========================================
|
||||
deploy:
|
||||
name: Deploy Web
|
||||
runs-on: ubuntu-latest
|
||||
build-image:
|
||||
name: Build & Push Image
|
||||
runs-on: pwap-runner
|
||||
needs: [web, telegram-gate]
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' &&
|
||||
(github.event_name == 'push' ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to == ''))
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write # cosign keyless signing via Sigstore OIDC
|
||||
outputs:
|
||||
image_sha: ${{ steps.meta.outputs.image_sha }}
|
||||
image_digest: ${{ steps.build.outputs.digest }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@v3
|
||||
with:
|
||||
cosign-release: 'v2.4.1'
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract image metadata
|
||||
id: meta
|
||||
run: |
|
||||
SHORT_SHA="${GITHUB_SHA:0:7}"
|
||||
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
echo "image_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./
|
||||
file: ./web/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.short_sha }}
|
||||
${{ steps.meta.outputs.image }}:latest
|
||||
build-args: |
|
||||
VITE_NETWORK=MAINNET
|
||||
VITE_WS_ENDPOINT=wss://rpc.pezkuwichain.io
|
||||
VITE_WS_ENDPOINT_FALLBACK_1=wss://mainnet.pezkuwichain.io
|
||||
VITE_ASSET_HUB_ENDPOINT=wss://asset-hub-rpc.pezkuwichain.io
|
||||
VITE_PEOPLE_CHAIN_ENDPOINT=wss://people-rpc.pezkuwichain.io
|
||||
VITE_WALLETCONNECT_PROJECT_ID=8292a793b7640e8364c378e331e76d04
|
||||
VITE_SUPABASE_URL=${{ secrets.VITE_SUPABASE_URL }}
|
||||
VITE_SUPABASE_ANON_KEY=${{ secrets.VITE_SUPABASE_ANON_KEY }}
|
||||
cache-from: type=registry,ref=${{ steps.meta.outputs.image }}:cache
|
||||
cache-to: type=registry,ref=${{ steps.meta.outputs.image }}:cache,mode=max
|
||||
provenance: false
|
||||
|
||||
- name: Sign image with cosign (keyless, Sigstore Fulcio)
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: '1'
|
||||
run: |
|
||||
IMAGE_DIGEST="${{ steps.meta.outputs.image }}@${{ steps.build.outputs.digest }}"
|
||||
# cosign needs its own registry auth — docker/login-action only writes
|
||||
# ~/.docker/config.json which cosign on self-hosted runner can't read
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||
echo "Signing $IMAGE_DIGEST"
|
||||
cosign sign --yes "$IMAGE_DIGEST"
|
||||
echo "✅ Image signed (transparency log: rekor.sigstore.dev)"
|
||||
|
||||
# ========================================
|
||||
# TELEGRAM CEO APPROVAL GATE
|
||||
# Runs on self-hosted pwap-runner (DEV VPS) where pexsec-bot.service
|
||||
# writes the gate file to /tmp/pexsec-gates/<sha> when CEO clicks
|
||||
# Approve/Cancel in Telegram. 30-minute timeout = deploy cancelled.
|
||||
# ========================================
|
||||
telegram-gate:
|
||||
name: Telegram deploy approval
|
||||
runs-on: pwap-runner
|
||||
needs: [web, security-audit]
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
|
||||
|
||||
steps:
|
||||
- name: Send approval request and wait
|
||||
env:
|
||||
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
|
||||
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
|
||||
SHA: ${{ github.sha }}
|
||||
ACTOR: ${{ github.actor }}
|
||||
MESSAGE: ${{ github.event.head_commit.message }}
|
||||
run: |
|
||||
SHORT="${SHA:0:7}"
|
||||
GATE_DIR="/tmp/pexsec-gates"
|
||||
mkdir -p "$GATE_DIR" 2>/dev/null || true
|
||||
rm -f "$GATE_DIR/$SHORT" 2>/dev/null || true
|
||||
|
||||
# Strip Markdown special chars to prevent Telegram parse errors
|
||||
SAFE_MSG=$(echo "${MESSAGE}" | head -1 | tr -d '_*`[]()#|{}!' | cut -c1-120)
|
||||
|
||||
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"chat_id\": \"${CEO_CHAT_ID}\",
|
||||
\"parse_mode\": \"Markdown\",
|
||||
\"text\": \"🚀 *pwap/web Deploy Approval*\\n\\n\`${SHORT}\` — ${ACTOR}\\n\\n_${SAFE_MSG}_\\n\\nTargets: app.pezkuwichain.io + pex.mom\",
|
||||
\"reply_markup\": {
|
||||
\"inline_keyboard\": [[
|
||||
{\"text\": \"✅ Approve\", \"callback_data\": \"deploy_approve:${SHORT}\"},
|
||||
{\"text\": \"❌ Cancel\", \"callback_data\": \"deploy_cancel:${SHORT}\"}
|
||||
]]
|
||||
}
|
||||
}"
|
||||
|
||||
echo "Waiting for Telegram approval (max 30 min)..."
|
||||
TIMEOUT=1800
|
||||
ELAPSED=0
|
||||
while [ $ELAPSED -lt $TIMEOUT ]; do
|
||||
if [ -f "$GATE_DIR/$SHORT" ]; then
|
||||
DECISION=$(cat "$GATE_DIR/$SHORT")
|
||||
rm -f "$GATE_DIR/$SHORT" 2>/dev/null || true
|
||||
if [ "$DECISION" = "approved" ]; then
|
||||
echo "Deploy approved."
|
||||
exit 0
|
||||
else
|
||||
echo "Deploy cancelled."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
sleep 10
|
||||
ELAPSED=$((ELAPSED + 10))
|
||||
done
|
||||
echo "No approval received within 30 minutes — deploy cancelled."
|
||||
exit 1
|
||||
|
||||
# ========================================
|
||||
# VERSION BUMP (RUNS BEFORE BOTH DEPLOYS)
|
||||
# ========================================
|
||||
bump-version:
|
||||
name: Bump Version
|
||||
runs-on: pwap-runner
|
||||
needs: [web, security-audit, telegram-gate, build-image]
|
||||
# Skip on rollback (workflow_dispatch with rollback_to set)
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' &&
|
||||
(github.event_name == 'push' ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to == ''))
|
||||
outputs:
|
||||
new_version: ${{ steps.bump.outputs.version }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -101,61 +263,416 @@ jobs:
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Bump version
|
||||
id: bump
|
||||
working-directory: ./web
|
||||
run: |
|
||||
npm version patch --no-git-tag-version
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
cd ..
|
||||
git add web/package.json
|
||||
git commit -m "chore(web): bump version to $VERSION [skip ci]" || echo "No version change"
|
||||
git push || echo "Nothing to push"
|
||||
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: web-dist
|
||||
path: dist/
|
||||
# ========================================
|
||||
# DEPLOY TO app.pezkuwichain.io (DEV VPS)
|
||||
# Pulls SHA-tagged image from GHCR, extracts /dist, scp to VPS.
|
||||
# Health check + auto-rollback to .deploy-tag-prev on failure.
|
||||
# ========================================
|
||||
deploy-app:
|
||||
name: Deploy app.pezkuwichain.io
|
||||
runs-on: pwap-runner
|
||||
needs: [telegram-gate, bump-version, build-image]
|
||||
if: |
|
||||
always() &&
|
||||
needs.telegram-gate.result == 'success' &&
|
||||
((github.event_name == 'push' && needs.build-image.result == 'success' && needs.bump-version.result == 'success') ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to != ''))
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
env:
|
||||
DOMAIN: app.pezkuwichain.io
|
||||
TARGET_PATH: /var/www/subdomains/app
|
||||
|
||||
- name: Deploy to VPS
|
||||
steps:
|
||||
- name: Determine image SHA
|
||||
id: sha
|
||||
run: |
|
||||
if [ -n "${{ github.event.inputs.rollback_to }}" ]; then
|
||||
echo "sha=${{ github.event.inputs.rollback_to }}" >> $GITHUB_OUTPUT
|
||||
echo "Rolling back to: ${{ github.event.inputs.rollback_to }}"
|
||||
else
|
||||
echo "sha=${{ needs.build-image.outputs.image_sha }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Capture currently-live SHA (for auto-rollback)
|
||||
id: prev
|
||||
run: |
|
||||
# /.deploy-sha is written into every deploy; read what's live now
|
||||
PREV=$(curl -sf --max-time 5 "https://${{ env.DOMAIN }}/.deploy-sha" | head -c 40 | tr -dc 'a-f0-9' || echo "")
|
||||
echo "Previous live SHA: ${PREV:-unknown}"
|
||||
echo "prev=$PREV" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install cosign (for verify)
|
||||
uses: sigstore/cosign-installer@v3
|
||||
with:
|
||||
cosign-release: 'v2.4.1'
|
||||
|
||||
- name: Verify image signature (cosign keyless)
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: '1'
|
||||
run: |
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | cosign login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||
echo "Verifying signature for $IMAGE"
|
||||
cosign verify "$IMAGE" \
|
||||
--certificate-identity-regexp "^https://github.com/pezkuwichain/pwap/.github/workflows/quality-gate.yml@" \
|
||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
||||
> /dev/null
|
||||
echo "✅ Signature valid — image was built by trusted CI"
|
||||
|
||||
- name: Extract /dist from image
|
||||
run: |
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
|
||||
docker pull "$IMAGE"
|
||||
CID=$(docker create "$IMAGE")
|
||||
mkdir -p dist
|
||||
docker cp "$CID:/dist/." dist/
|
||||
docker rm "$CID" >/dev/null
|
||||
# Stamp this build's SHA into dist so future deploys can read PREV
|
||||
echo "${{ steps.sha.outputs.sha }}" > dist/.deploy-sha
|
||||
ls -la dist/ | head -10
|
||||
|
||||
- name: Deploy to DEV VPS
|
||||
uses: appleboy/scp-action@v1.0.0
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: ${{ secrets.VPS_USER }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
port: ${{ secrets.VPS_SSH_PORT || 2222 }}
|
||||
source: 'dist/*'
|
||||
target: '/var/www/subdomains/app'
|
||||
strip_components: 1
|
||||
|
||||
- name: Post-deploy notification
|
||||
- name: Health check (60s window)
|
||||
id: healthcheck
|
||||
run: |
|
||||
echo "✅ Deployed web app v${{ env.NEW_VERSION }} to app.pezkuwichain.io"
|
||||
for i in 1 2 3 4 5 6; do
|
||||
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
|
||||
echo "✅ ${{ env.DOMAIN }} healthy"
|
||||
exit 0
|
||||
fi
|
||||
echo "Attempt $i/6 failed, retrying in 10s..."
|
||||
sleep 10
|
||||
done
|
||||
echo "❌ Health check failed for ${{ env.DOMAIN }}"
|
||||
exit 1
|
||||
|
||||
# ── Automatic rollback: pull PREV SHA image, redeploy, recheck ──
|
||||
- name: Auto-rollback to previous SHA
|
||||
id: rollback
|
||||
if: failure() && steps.healthcheck.conclusion == 'failure' && steps.prev.outputs.prev != ''
|
||||
run: |
|
||||
PREV="${{ steps.prev.outputs.prev }}"
|
||||
echo "🔄 Rolling back ${{ env.DOMAIN }} to $PREV"
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$PREV"
|
||||
docker pull "$IMAGE"
|
||||
CID=$(docker create "$IMAGE")
|
||||
rm -rf dist && mkdir dist
|
||||
docker cp "$CID:/dist/." dist/
|
||||
docker rm "$CID" >/dev/null
|
||||
echo "$PREV" > dist/.deploy-sha
|
||||
echo "rollback_sha=$PREV" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: SCP rollback artifact
|
||||
if: steps.rollback.outcome == 'success'
|
||||
uses: appleboy/scp-action@v1.0.0
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: ${{ secrets.VPS_USER }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
port: ${{ secrets.VPS_SSH_PORT || 2222 }}
|
||||
source: 'dist/*'
|
||||
target: '/var/www/subdomains/app'
|
||||
strip_components: 1
|
||||
|
||||
- name: Re-health-check after rollback
|
||||
if: steps.rollback.outcome == 'success'
|
||||
id: healthcheck_rb
|
||||
run: |
|
||||
for i in 1 2 3 4 5 6; do
|
||||
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
|
||||
echo "✅ Rolled back successfully — ${{ env.DOMAIN }} healthy on ${{ steps.rollback.outputs.rollback_sha }}"
|
||||
exit 0
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
echo "❌ Rollback also failed!"
|
||||
exit 1
|
||||
|
||||
- name: Post-deploy notification
|
||||
if: success()
|
||||
run: |
|
||||
echo "✅ Deployed image ${{ steps.sha.outputs.sha }} to ${{ env.DOMAIN }}"
|
||||
|
||||
- name: Notify failure (Telegram)
|
||||
if: failure()
|
||||
env:
|
||||
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
|
||||
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
|
||||
NEW_SHA: ${{ steps.sha.outputs.sha }}
|
||||
PREV_SHA: ${{ steps.prev.outputs.prev }}
|
||||
ROLLBACK_OUTCOME: ${{ steps.rollback.outcome }}
|
||||
RECHECK_OUTCOME: ${{ steps.healthcheck_rb.outcome }}
|
||||
run: |
|
||||
if [ "$RECHECK_OUTCOME" = "success" ]; then
|
||||
MSG="⚠️ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed health check, AUTO-ROLLED-BACK to $PREV_SHA. Site healthy."
|
||||
elif [ "$ROLLBACK_OUTCOME" = "success" ]; then
|
||||
MSG="🚨 pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed AND rollback to $PREV_SHA also failed. Manual intervention needed."
|
||||
elif [ -z "$PREV_SHA" ]; then
|
||||
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. No previous SHA available (first deploy?). Manual rollback: gh workflow run quality-gate.yml -f rollback_to=<sha>"
|
||||
else
|
||||
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. Auto-rollback was not attempted. Manual: gh workflow run quality-gate.yml -f rollback_to=$PREV_SHA"
|
||||
fi
|
||||
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
|
||||
-d "chat_id=${CEO_CHAT_ID}" --data-urlencode "text=$MSG"
|
||||
|
||||
# ========================================
|
||||
# DEPLOY TO pex.mom (VPS3 — geo-redundant mirror)
|
||||
# ========================================
|
||||
deploy-pex:
|
||||
name: Deploy pex.mom
|
||||
runs-on: pwap-runner
|
||||
needs: [telegram-gate, bump-version, build-image]
|
||||
if: |
|
||||
always() &&
|
||||
needs.telegram-gate.result == 'success' &&
|
||||
((github.event_name == 'push' && needs.build-image.result == 'success' && needs.bump-version.result == 'success') ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_to != ''))
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
env:
|
||||
DOMAIN: pex.mom
|
||||
TARGET_PATH: /var/www/pex.mom
|
||||
|
||||
steps:
|
||||
- name: Determine image SHA
|
||||
id: sha
|
||||
run: |
|
||||
if [ -n "${{ github.event.inputs.rollback_to }}" ]; then
|
||||
echo "sha=${{ github.event.inputs.rollback_to }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "sha=${{ needs.build-image.outputs.image_sha }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Capture currently-live SHA (for auto-rollback)
|
||||
id: prev
|
||||
run: |
|
||||
PREV=$(curl -sf --max-time 5 "https://${{ env.DOMAIN }}/.deploy-sha" | head -c 40 | tr -dc 'a-f0-9' || echo "")
|
||||
echo "Previous live SHA: ${PREV:-unknown}"
|
||||
echo "prev=$PREV" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install cosign (for verify)
|
||||
uses: sigstore/cosign-installer@v3
|
||||
with:
|
||||
cosign-release: 'v2.4.1'
|
||||
|
||||
- name: Verify image signature (cosign keyless)
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: '1'
|
||||
run: |
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
|
||||
echo "Verifying signature for $IMAGE"
|
||||
cosign verify "$IMAGE" \
|
||||
--certificate-identity-regexp "^https://github.com/pezkuwichain/pwap/.github/workflows/quality-gate.yml@" \
|
||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
||||
> /dev/null
|
||||
echo "✅ Signature valid — image was built by trusted CI"
|
||||
|
||||
- name: Extract /dist from image
|
||||
run: |
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
|
||||
docker pull "$IMAGE"
|
||||
CID=$(docker create "$IMAGE")
|
||||
mkdir -p dist
|
||||
docker cp "$CID:/dist/." dist/
|
||||
docker rm "$CID" >/dev/null
|
||||
echo "${{ steps.sha.outputs.sha }}" > dist/.deploy-sha
|
||||
|
||||
- name: Deploy to VPS3
|
||||
uses: appleboy/scp-action@v1.0.0
|
||||
with:
|
||||
host: ${{ secrets.VPS_PEX_HOST }}
|
||||
username: ${{ secrets.VPS_PEX_USER }}
|
||||
key: ${{ secrets.VPS_PEX_SSH_KEY }}
|
||||
port: ${{ secrets.VPS_PEX_SSH_PORT || 22 }}
|
||||
source: 'dist/*'
|
||||
target: '/var/www/pex.mom'
|
||||
strip_components: 1
|
||||
|
||||
- name: Health check (60s window)
|
||||
id: healthcheck
|
||||
run: |
|
||||
for i in 1 2 3 4 5 6; do
|
||||
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
|
||||
echo "✅ ${{ env.DOMAIN }} healthy"
|
||||
exit 0
|
||||
fi
|
||||
echo "Attempt $i/6 failed, retrying in 10s..."
|
||||
sleep 10
|
||||
done
|
||||
echo "❌ Health check failed for ${{ env.DOMAIN }}"
|
||||
exit 1
|
||||
|
||||
- name: Auto-rollback to previous SHA
|
||||
id: rollback
|
||||
if: failure() && steps.healthcheck.conclusion == 'failure' && steps.prev.outputs.prev != ''
|
||||
run: |
|
||||
PREV="${{ steps.prev.outputs.prev }}"
|
||||
echo "🔄 Rolling back ${{ env.DOMAIN }} to $PREV"
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$PREV"
|
||||
docker pull "$IMAGE"
|
||||
CID=$(docker create "$IMAGE")
|
||||
rm -rf dist && mkdir dist
|
||||
docker cp "$CID:/dist/." dist/
|
||||
docker rm "$CID" >/dev/null
|
||||
echo "$PREV" > dist/.deploy-sha
|
||||
echo "rollback_sha=$PREV" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: SCP rollback artifact
|
||||
if: steps.rollback.outcome == 'success'
|
||||
uses: appleboy/scp-action@v1.0.0
|
||||
with:
|
||||
host: ${{ secrets.VPS_PEX_HOST }}
|
||||
username: ${{ secrets.VPS_PEX_USER }}
|
||||
key: ${{ secrets.VPS_PEX_SSH_KEY }}
|
||||
port: ${{ secrets.VPS_PEX_SSH_PORT || 22 }}
|
||||
source: 'dist/*'
|
||||
target: '/var/www/pex.mom'
|
||||
strip_components: 1
|
||||
|
||||
- name: Re-health-check after rollback
|
||||
if: steps.rollback.outcome == 'success'
|
||||
id: healthcheck_rb
|
||||
run: |
|
||||
for i in 1 2 3 4 5 6; do
|
||||
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
|
||||
echo "✅ Rolled back successfully — ${{ env.DOMAIN }} healthy on ${{ steps.rollback.outputs.rollback_sha }}"
|
||||
exit 0
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
echo "❌ Rollback also failed!"
|
||||
exit 1
|
||||
|
||||
- name: Post-deploy notification
|
||||
if: success()
|
||||
run: |
|
||||
echo "✅ Deployed image ${{ steps.sha.outputs.sha }} to ${{ env.DOMAIN }}"
|
||||
|
||||
- name: Notify failure (Telegram)
|
||||
if: failure()
|
||||
env:
|
||||
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
|
||||
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
|
||||
NEW_SHA: ${{ steps.sha.outputs.sha }}
|
||||
PREV_SHA: ${{ steps.prev.outputs.prev }}
|
||||
ROLLBACK_OUTCOME: ${{ steps.rollback.outcome }}
|
||||
RECHECK_OUTCOME: ${{ steps.healthcheck_rb.outcome }}
|
||||
run: |
|
||||
if [ "$RECHECK_OUTCOME" = "success" ]; then
|
||||
MSG="⚠️ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed health check, AUTO-ROLLED-BACK to $PREV_SHA. Site healthy."
|
||||
elif [ "$ROLLBACK_OUTCOME" = "success" ]; then
|
||||
MSG="🚨 pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed AND rollback to $PREV_SHA also failed. Manual intervention needed."
|
||||
elif [ -z "$PREV_SHA" ]; then
|
||||
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. No previous SHA available (first deploy?). Manual rollback: gh workflow run quality-gate.yml -f rollback_to=<sha>"
|
||||
else
|
||||
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. Auto-rollback was not attempted. Manual: gh workflow run quality-gate.yml -f rollback_to=$PREV_SHA"
|
||||
fi
|
||||
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
|
||||
-d "chat_id=${CEO_CHAT_ID}" --data-urlencode "text=$MSG"
|
||||
|
||||
# ========================================
|
||||
# SECURITY CHECKS (BLOCKING)
|
||||
# npm audit (high + critical) + TruffleHog secret scan
|
||||
# ========================================
|
||||
security-audit:
|
||||
name: Security Audit
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: pwap-runner
|
||||
needs: [web]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Web - npm audit (critical only)
|
||||
- name: Web — npm audit (high + critical, production deps only)
|
||||
working-directory: ./web
|
||||
run: |
|
||||
npm install
|
||||
npm audit --audit-level=critical
|
||||
# Audit only production dependencies. Build tooling (vite, esbuild,
|
||||
# vite-plugin-node-polyfills → elliptic, etc.) ships to no user, and
|
||||
# advisories on those dev deps kept blocking production deploys.
|
||||
npm audit --audit-level=high --omit=dev
|
||||
|
||||
- name: TruffleHog Secret Scan
|
||||
- name: TruffleHog — PR diff (verified secrets only)
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: trufflesecurity/trufflehog@main
|
||||
with:
|
||||
base: ${{ github.event.pull_request.base.sha }}
|
||||
head: ${{ github.event.pull_request.head.sha }}
|
||||
extra_args: --only-verified
|
||||
|
||||
- name: TruffleHog — full repo scan (verified secrets only)
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: trufflesecurity/trufflehog@main
|
||||
with:
|
||||
path: ./
|
||||
extra_args: --only-verified
|
||||
|
||||
# ========================================
|
||||
# CI GATE — explicit merge-block
|
||||
# All required checks must succeed (or be skipped, e.g. for rollback path).
|
||||
# Branch protection on main should require this job's success.
|
||||
# ========================================
|
||||
ci-gate:
|
||||
name: CI Gate ✅
|
||||
runs-on: pwap-runner
|
||||
needs: [web, security-audit]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Verify all required jobs succeeded or were intentionally skipped
|
||||
run: |
|
||||
results='${{ toJSON(needs) }}'
|
||||
echo "$results" | python3 -c "
|
||||
import json, sys
|
||||
needs = json.load(sys.stdin)
|
||||
failed = [name for name, job in needs.items() if job['result'] not in ('success', 'skipped')]
|
||||
if failed:
|
||||
print('❌ Required jobs failed: ' + ', '.join(failed))
|
||||
sys.exit(1)
|
||||
print('✅ All required CI jobs passed or skipped')
|
||||
"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Internal resources (never commit)
|
||||
res/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
@@ -26,7 +26,7 @@ pwap/
|
||||
|
||||
**Status:** ✅ Production Ready
|
||||
|
||||
The primary web interface for Pezkuwi blockchain at [pezkuwichain.app](https://pezkuwichain.app)
|
||||
The primary web interface for Pezkuwi blockchain at [app.pezkuwichain.io](https://app.pezkuwichain.io)
|
||||
|
||||
**Tech Stack:**
|
||||
- React 18 + TypeScript
|
||||
@@ -166,9 +166,10 @@ RTL support for CKB, AR, FA.
|
||||
|
||||
## Links
|
||||
|
||||
- **Website:** https://pezkuwichain.app
|
||||
- **SDK UI:** https://pezkuwichain.app/sdk
|
||||
- **Documentation:** https://docs.pezkuwichain.app
|
||||
- **Website:** https://app.pezkuwichain.io
|
||||
- **Website (alt):** https://pex.mom
|
||||
- **Exchange:** https://pex.network
|
||||
- **Documentation:** https://docs.pezkuwichain.io
|
||||
|
||||
## License
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 677 KiB |
@@ -1,31 +0,0 @@
|
||||
# PWAP WSL Dev Environment Setup
|
||||
# Run in PowerShell as Administrator
|
||||
|
||||
Write-Host "=== PWAP Dev Setup ===" -ForegroundColor Cyan
|
||||
|
||||
# 1. Fix .wslconfig - enable mirrored networking
|
||||
$wslconfig = @"
|
||||
[wsl2]
|
||||
memory=48GB
|
||||
swap=16GB
|
||||
networkingMode=mirrored
|
||||
"@
|
||||
Set-Content -Path "$env:USERPROFILE\.wslconfig" -Value $wslconfig
|
||||
Write-Host "[OK] .wslconfig updated (mirrored networking)" -ForegroundColor Green
|
||||
|
||||
# 2. Restart ADB on default port
|
||||
$adbPath = "C:\Users\satos\Desktop\platform-tools\adb.exe"
|
||||
& $adbPath kill-server 2>$null
|
||||
Start-Sleep -Seconds 1
|
||||
& $adbPath start-server
|
||||
Write-Host "[OK] ADB server restarted on port 5037" -ForegroundColor Green
|
||||
& $adbPath devices
|
||||
|
||||
# 3. Shutdown WSL so new config takes effect
|
||||
Write-Host "`nShutting down WSL..." -ForegroundColor Yellow
|
||||
wsl --shutdown
|
||||
Start-Sleep -Seconds 3
|
||||
Write-Host "[OK] WSL shutdown complete" -ForegroundColor Green
|
||||
|
||||
Write-Host "`n=== Done! ===" -ForegroundColor Cyan
|
||||
Write-Host "Now open WSL again and run: cd pwap && claude" -ForegroundColor White
|
||||
@@ -42,7 +42,7 @@ VITE_ENABLE_DEMO_MODE=true
|
||||
# 1. Project URL: Copy from "Project URL" section
|
||||
# 2. Anon key: Copy from "Project API keys" → "anon" → "public"
|
||||
|
||||
VITE_SUPABASE_URL=https://vbhftvdayqfmcgmzdxfv.supabase.co
|
||||
VITE_SUPABASE_URL=https://supabase.pezkuwichain.io
|
||||
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key_here
|
||||
|
||||
# ========================================
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# pwap/web — Static SPA build for distribution.
|
||||
# Build context is the pwap repo ROOT (not web/) because vite aliases like
|
||||
# @pezkuwi/utils, @shared/* resolve to ../shared/* — both web/ and shared/
|
||||
# must be in the build context.
|
||||
# Stage 1: build with Node. Stage 2: pure dist/ in busybox (smallest possible
|
||||
# attacker surface — no shell, no package manager, no node runtime).
|
||||
# Tag the resulting image with the git SHA in CI so rollback is just
|
||||
# "pull pwap-web:<old-sha>".
|
||||
|
||||
# ─── Stage 1: Build ────────────────────────────────────────────
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /build/web
|
||||
|
||||
# Copy package files first to leverage Docker layer cache when only src changes
|
||||
COPY web/package.json web/package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Copy shared/ first (less frequently changed), then web/ source
|
||||
COPY shared/ /build/shared/
|
||||
COPY web/ /build/web/
|
||||
|
||||
# Build args for environment-specific values (passed from CI)
|
||||
ARG VITE_NETWORK=MAINNET
|
||||
ARG VITE_WS_ENDPOINT=wss://rpc.pezkuwichain.io
|
||||
ARG VITE_WS_ENDPOINT_FALLBACK_1=wss://mainnet.pezkuwichain.io
|
||||
ARG VITE_ASSET_HUB_ENDPOINT=wss://asset-hub-rpc.pezkuwichain.io
|
||||
ARG VITE_PEOPLE_CHAIN_ENDPOINT=wss://people-rpc.pezkuwichain.io
|
||||
ARG VITE_WALLETCONNECT_PROJECT_ID=8292a793b7640e8364c378e331e76d04
|
||||
ARG VITE_SUPABASE_URL
|
||||
ARG VITE_SUPABASE_ANON_KEY
|
||||
|
||||
ENV VITE_NETWORK=$VITE_NETWORK
|
||||
ENV VITE_WS_ENDPOINT=$VITE_WS_ENDPOINT
|
||||
ENV VITE_WS_ENDPOINT_FALLBACK_1=$VITE_WS_ENDPOINT_FALLBACK_1
|
||||
ENV VITE_ASSET_HUB_ENDPOINT=$VITE_ASSET_HUB_ENDPOINT
|
||||
ENV VITE_PEOPLE_CHAIN_ENDPOINT=$VITE_PEOPLE_CHAIN_ENDPOINT
|
||||
ENV VITE_WALLETCONNECT_PROJECT_ID=$VITE_WALLETCONNECT_PROJECT_ID
|
||||
ENV VITE_SUPABASE_URL=$VITE_SUPABASE_URL
|
||||
ENV VITE_SUPABASE_ANON_KEY=$VITE_SUPABASE_ANON_KEY
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# ─── Stage 2: Distribution image ───────────────────────────────
|
||||
# busybox:musl gives us a tiny base (~1.5MB) with a shell for `cp` operations
|
||||
# during deploy extraction, but no npm/curl/wget/ssh — minimal attack surface
|
||||
# if the image were ever exposed.
|
||||
FROM busybox:musl
|
||||
WORKDIR /dist
|
||||
COPY --from=builder /build/web/dist /dist
|
||||
LABEL org.opencontainers.image.source="https://github.com/pezkuwichain/pwap"
|
||||
LABEL org.opencontainers.image.description="pwap/web static SPA — Pezkuwi wallet/exchange frontend"
|
||||
LABEL org.opencontainers.image.licenses="proprietary"
|
||||
CMD ["sh", "-c", "echo 'pwap-web image — extract /dist via: docker create + docker cp'; sleep infinity"]
|
||||
@@ -120,7 +120,8 @@
|
||||
"@pezkuwi/x-textdecoder": "^14.0.25",
|
||||
"@pezkuwi/x-textencoder": "^14.0.25",
|
||||
"@pezkuwi/x-ws": "^14.0.25",
|
||||
"@pezkuwi/networks": "^14.0.25"
|
||||
"@pezkuwi/networks": "^14.0.25",
|
||||
"elliptic": "^6.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
@@ -147,6 +148,7 @@
|
||||
"typescript-eslint": "^8.0.1",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-node-polyfills": "^0.25.0",
|
||||
"vite-plugin-subresource-integrity": "^0.0.12",
|
||||
"vitest": "^4.0.10"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
"General Docs": {
|
||||
"AUDIT": "AUDIT.md",
|
||||
"BACKPORT": "BACKPORT.md",
|
||||
"RELEASE": "RELEASE.md",
|
||||
"Workflow Rebranding": "workflow_rebranding.md"
|
||||
"RELEASE": "RELEASE.md"
|
||||
},
|
||||
"Contributor": {
|
||||
"CODE OF CONDUCT": "contributor/CODE_OF_CONDUCT.md",
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Slow Crate Publisher - 6 dakikada bir 1 crate publish eder
|
||||
Rate limit'e takilmamak icin yavas yavas publish yapar.
|
||||
|
||||
Kullanim:
|
||||
nohup python3 publish_crates_slow.py > publish_log.txt 2>&1 &
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
PLACEHOLDER_DIR = '/home/mamostehp/kurdistan-sdk/crate_placeholders'
|
||||
LOG_FILE = '/home/mamostehp/kurdistan-sdk/publish_log.txt'
|
||||
INTERVAL_SECONDS = 360 # 6 dakika
|
||||
|
||||
def log(msg):
|
||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
line = f"[{timestamp}] {msg}"
|
||||
print(line, flush=True)
|
||||
with open(LOG_FILE, 'a') as f:
|
||||
f.write(line + '\n')
|
||||
|
||||
def is_published(name):
|
||||
"""crates.io'da mevcut mu kontrol et"""
|
||||
result = subprocess.run(
|
||||
['cargo', 'search', name, '--limit', '1'],
|
||||
capture_output=True, text=True, timeout=30
|
||||
)
|
||||
return f'{name} = ' in result.stdout
|
||||
|
||||
def publish_crate(name):
|
||||
"""Tek bir crate publish et"""
|
||||
crate_dir = os.path.join(PLACEHOLDER_DIR, name)
|
||||
manifest = os.path.join(crate_dir, 'Cargo.toml')
|
||||
|
||||
if not os.path.exists(manifest):
|
||||
return False, "Cargo.toml not found"
|
||||
|
||||
result = subprocess.run(
|
||||
['cargo', 'publish', '--manifest-path', manifest],
|
||||
capture_output=True, text=True, cwd=crate_dir, timeout=180
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
return True, "Success"
|
||||
elif 'already uploaded' in result.stderr or 'already exists' in result.stderr:
|
||||
return True, "Already exists"
|
||||
elif '429' in result.stderr or 'Too Many Requests' in result.stderr:
|
||||
return False, "Rate limited"
|
||||
else:
|
||||
return False, result.stderr[:200]
|
||||
|
||||
def get_unpublished_crates():
|
||||
"""Henuz publish edilmemis crate'leri bul"""
|
||||
crates = sorted([d for d in os.listdir(PLACEHOLDER_DIR)
|
||||
if os.path.isdir(os.path.join(PLACEHOLDER_DIR, d))])
|
||||
|
||||
unpublished = []
|
||||
for crate in crates:
|
||||
if not is_published(crate):
|
||||
unpublished.append(crate)
|
||||
return unpublished
|
||||
|
||||
def main():
|
||||
log("=" * 60)
|
||||
log("Slow Crate Publisher baslatildi")
|
||||
log(f"Interval: {INTERVAL_SECONDS} saniye (6 dakika)")
|
||||
log("=" * 60)
|
||||
|
||||
unpublished = get_unpublished_crates()
|
||||
total = len(unpublished)
|
||||
log(f"Toplam {total} crate publish edilecek")
|
||||
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
|
||||
for i, crate in enumerate(unpublished, 1):
|
||||
log(f"[{i}/{total}] Publishing: {crate}")
|
||||
|
||||
success, msg = publish_crate(crate)
|
||||
|
||||
if success:
|
||||
log(f" ✓ {msg}")
|
||||
success_count += 1
|
||||
else:
|
||||
log(f" ✗ {msg}")
|
||||
fail_count += 1
|
||||
|
||||
# Rate limit durumunda ekstra bekle
|
||||
if "Rate limited" in msg:
|
||||
log(" Rate limited! 10 dakika bekleniyor...")
|
||||
time.sleep(600)
|
||||
|
||||
# Sonraki crate icin bekle
|
||||
if i < total:
|
||||
log(f" Sonraki crate icin {INTERVAL_SECONDS}s bekleniyor...")
|
||||
time.sleep(INTERVAL_SECONDS)
|
||||
|
||||
log("=" * 60)
|
||||
log(f"Tamamlandi! Basarili: {success_count}, Basarisiz: {fail_count}")
|
||||
log("=" * 60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,214 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Rebranding haritası
|
||||
REBRAND_MAP = [
|
||||
("asset-test-utils", "asset-test-pezutils"),
|
||||
("chain-spec-guide-runtime", "pez-chain-spec-guide-runtime"),
|
||||
("equivocation-detector", "pez-equivocation-detector"),
|
||||
("erasure-coding-fuzzer", "pez-erasure-coding-fuzzer"),
|
||||
("ethereum-standards", "pez-ethereum-standards"),
|
||||
("finality-relay", "pez-finality-relay"),
|
||||
("fork-tree", "pez-fork-tree"),
|
||||
("generate-bags", "pez-generate-bags"),
|
||||
("kitchensink-runtime", "pez-kitchensink-runtime"),
|
||||
("messages-relay", "pez-messages-relay"),
|
||||
("minimal-template-node", "pez-minimal-template-node"),
|
||||
("minimal-template-runtime", "pez-minimal-template-runtime"),
|
||||
("node-bench", "pez-node-bench"),
|
||||
("node-primitives", "pez-node-primitives"),
|
||||
("node-rpc", "pez-node-rpc"),
|
||||
("node-runtime-generate-bags", "pez-node-runtime-generate-bags"),
|
||||
("node-template-release", "pez-node-template-release"),
|
||||
("node-testing", "pez-node-testing"),
|
||||
("penpal-emulated-chain", "pez-penpal-emulated-chain"),
|
||||
("penpal-runtime", "pez-penpal-runtime"),
|
||||
("remote-ext-tests-bags-list", "pez-remote-ext-tests-bags-list"),
|
||||
("revive-dev-node", "pez-revive-dev-node"),
|
||||
("revive-dev-runtime", "pez-revive-dev-runtime"),
|
||||
("slot-range-helper", "pez-slot-range-helper"),
|
||||
("solochain-template-node", "pez-solochain-template-node"),
|
||||
("solochain-template-runtime", "pez-solochain-template-runtime"),
|
||||
("subkey", "pez-subkey"),
|
||||
("template-zombienet-tests", "pez-template-zombienet-tests"),
|
||||
("test-runtime-constants", "peztest-runtime-constants"),
|
||||
("tracing-gum", "pez-tracing-gum"),
|
||||
("tracing-gum-proc-macro", "pez-tracing-gum-proc-macro"),
|
||||
("bp-header-chain", "bp-header-pez-chain"),
|
||||
("bp-runtime", "pezbp-runtime"),
|
||||
("bridge-hub-pezkuwichain-emulated-chain", "pezbridge-hub-pezkuwichain-emulated-chain"),
|
||||
("bridge-hub-pezkuwichain-integration-tests", "pezbridge-hub-pezkuwichain-integration-tests"),
|
||||
("bridge-hub-pezkuwichain-runtime", "pezbridge-hub-pezkuwichain-runtime"),
|
||||
("bridge-hub-test-utils", "pezbridge-hub-test-utils"),
|
||||
("bridge-hub-zagros-emulated-chain", "pezbridge-hub-zagros-emulated-chain"),
|
||||
("bridge-hub-zagros-integration-tests", "pezbridge-hub-zagros-integration-tests"),
|
||||
("bridge-hub-zagros-runtime", "pezbridge-hub-zagros-runtime"),
|
||||
("bridge-runtime-common", "pezbridge-runtime-common"),
|
||||
("mmr-gadget", "pezmmr-gadget"),
|
||||
("mmr-rpc", "pezmmr-rpc"),
|
||||
("snowbridge-beacon-primitives", "pezsnowbridge-beacon-primitives"),
|
||||
("snowbridge-core", "pezsnowbridge-core"),
|
||||
("snowbridge-ethereum", "pezsnowbridge-ethereum"),
|
||||
("snowbridge-inbound-queue-primitives", "pezsnowbridge-inbound-queue-primitives"),
|
||||
("snowbridge-merkle-tree", "pezsnowbridge-merkle-tree"),
|
||||
("snowbridge-outbound-queue-primitives", "pezsnowbridge-outbound-queue-primitives"),
|
||||
("snowbridge-outbound-queue-runtime-api", "pezsnowbridge-outbound-queue-runtime-api"),
|
||||
("snowbridge-outbound-queue-v2-runtime-api", "pezsnowbridge-outbound-queue-v2-runtime-api"),
|
||||
("snowbridge-pezpallet-ethereum-client", "snowbridge-pezpallet-ethereum-client"),
|
||||
("snowbridge-pezpallet-ethereum-client-fixtures", "snowbridge-pezpallet-ethereum-client-fixtures"),
|
||||
("snowbridge-pezpallet-inbound-queue", "snowbridge-pezpallet-inbound-queue"),
|
||||
("snowbridge-pezpallet-inbound-queue-fixtures", "snowbridge-pezpallet-inbound-queue-fixtures"),
|
||||
("snowbridge-pezpallet-inbound-queue-v2", "snowbridge-pezpallet-inbound-queue-v2"),
|
||||
("snowbridge-pezpallet-inbound-queue-v2-fixtures", "snowbridge-pezpallet-inbound-queue-v2-fixtures"),
|
||||
("snowbridge-pezpallet-outbound-queue", "snowbridge-pezpallet-outbound-queue"),
|
||||
("snowbridge-pezpallet-outbound-queue-v2", "snowbridge-pezpallet-outbound-queue-v2"),
|
||||
("snowbridge-pezpallet-system", "snowbridge-pezpallet-system"),
|
||||
("snowbridge-pezpallet-system-frontend", "snowbridge-pezpallet-system-frontend"),
|
||||
("snowbridge-pezpallet-system-v2", "snowbridge-pezpallet-system-v2"),
|
||||
("snowbridge-runtime-common", "pezsnowbridge-runtime-common"),
|
||||
("snowbridge-runtime-test-common", "pezsnowbridge-runtime-test-common"),
|
||||
("snowbridge-system-runtime-api", "pezsnowbridge-system-runtime-api"),
|
||||
("snowbridge-system-v2-runtime-api", "pezsnowbridge-system-v2-runtime-api"),
|
||||
("snowbridge-test-utils", "pezsnowbridge-test-utils"),
|
||||
("snowbridge-verification-primitives", "pezsnowbridge-verification-primitives"),
|
||||
("xcm-docs", "xcm-pez-docs"),
|
||||
("xcm-emulator", "xcm-pez-emulator"),
|
||||
("xcm-executor-integration-tests", "xcm-pez-executor-integration-tests"),
|
||||
("xcm-procedural", "xcm-pez-procedural"),
|
||||
("xcm-runtime-apis", "xcm-runtime-pezapis"),
|
||||
("xcm-simulator", "xcm-pez-simulator"),
|
||||
("xcm-simulator-example", "xcm-pez-simulator-example"),
|
||||
("xcm-simulator-fuzzer", "xcm-pez-simulator-fuzzer"),
|
||||
]
|
||||
|
||||
# Hedef dosya uzantıları
|
||||
TARGET_EXTENSIONS = ('.rs', '.toml', '.md', '.txt', '.yml', '.yaml', '.json', '.py')
|
||||
|
||||
# HARİÇ TUTULACAK KLASÖRLER (KESİN LİSTE)
|
||||
EXCLUDE_DIRS = {'crate_placeholders', '.git', 'target', 'node_modules', '__pycache__'}
|
||||
|
||||
def is_path_excluded(path):
|
||||
"""Verilen yolun yasaklı bir klasörün içinde olup olmadığını kontrol eder."""
|
||||
parts = path.split(os.sep)
|
||||
# Eğer path'in herhangi bir parçası EXCLUDE_DIRS içindeyse True döner
|
||||
return any(excluded in parts for excluded in EXCLUDE_DIRS)
|
||||
|
||||
def replace_in_file(filepath):
|
||||
# Kendi kendimizi değiştirmeyelim
|
||||
if os.path.basename(filepath) == os.path.basename(__file__):
|
||||
return
|
||||
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
|
||||
original_content = content
|
||||
|
||||
for old_name, new_name in REBRAND_MAP:
|
||||
# 1. Normal (tireli)
|
||||
content = content.replace(old_name, new_name)
|
||||
# 2. Snake case (alt çizgili)
|
||||
old_snake = old_name.replace('-', '_')
|
||||
new_snake = new_name.replace('-', '_')
|
||||
content = content.replace(old_snake, new_snake)
|
||||
|
||||
if content != original_content:
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
print(f" [GÜNCELLENDİ] Dosya içeriği: {filepath}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" [HATA] Dosya okunamadı: {filepath} -> {e}")
|
||||
|
||||
def rename_directories_and_files(root_dir):
|
||||
# topdown=True kullanarak yukarıdan aşağıya iniyoruz, böylece dirs listesini modifiye edebiliriz
|
||||
for dirpath, dirnames, filenames in os.walk(root_dir, topdown=True):
|
||||
|
||||
# GÜVENLİK: Yasaklı klasörleri yerinde (in-place) listeden silerek os.walk'un oraya girmesini engelle
|
||||
# Bu en güvenli yöntemdir.
|
||||
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
|
||||
|
||||
# Eğer şu anki dizin zaten yasaklı bir yolun altındaysa (üstteki koruma kaçırdıysa) atla
|
||||
if is_path_excluded(dirpath):
|
||||
continue
|
||||
|
||||
# 1. Dosya isimlerini değiştir
|
||||
for filename in filenames:
|
||||
if filename == os.path.basename(__file__):
|
||||
continue
|
||||
|
||||
for old_name, new_name in REBRAND_MAP:
|
||||
if old_name in filename:
|
||||
old_file_path = os.path.join(dirpath, filename)
|
||||
new_filename = filename.replace(old_name, new_name)
|
||||
new_file_path = os.path.join(dirpath, new_filename)
|
||||
if os.path.exists(old_file_path):
|
||||
try:
|
||||
os.rename(old_file_path, new_file_path)
|
||||
print(f" [RENAME] Dosya: {filename} -> {new_filename}")
|
||||
except OSError as e:
|
||||
print(f" [HATA] Dosya adlandırılamadı {filename}: {e}")
|
||||
|
||||
# 2. Klasör isimlerini değiştir
|
||||
# Not: dirnames listesi üzerinde iterasyon yapıyoruz ama rename işlemi riskli olabilir
|
||||
# O yüzden sadece şu anki seviyedeki klasörleri kontrol ediyoruz
|
||||
# Ancak os.walk çalışırken klasör adı değişirse alt dizin taraması sapıtabilir.
|
||||
# Bu yüzden klasör yeniden adlandırmayı en sona, ayrı bir "bottom-up" geçişe bırakmak daha iyidir
|
||||
# ama basitlik adına burada dikkatli yapıyoruz.
|
||||
|
||||
# İkinci Geçiş: Sadece Klasör İsimleri (Bottom-Up)
|
||||
# Klasör isimlerini değiştirirken path bozulmasın diye en alttan başlıyoruz
|
||||
for dirpath, dirnames, filenames in os.walk(root_dir, topdown=False):
|
||||
if is_path_excluded(dirpath):
|
||||
continue
|
||||
|
||||
for dirname in dirnames:
|
||||
if dirname in EXCLUDE_DIRS:
|
||||
continue
|
||||
|
||||
for old_name, new_name in REBRAND_MAP:
|
||||
if old_name == dirname:
|
||||
old_dir_path = os.path.join(dirpath, dirname)
|
||||
new_dir_path = os.path.join(dirpath, new_name)
|
||||
if os.path.exists(old_dir_path):
|
||||
try:
|
||||
os.rename(old_dir_path, new_dir_path)
|
||||
print(f" [RENAME] Klasör: {dirname} -> {new_name}")
|
||||
except OSError as e:
|
||||
print(f" [HATA] Klasör adlandırılamadı {dirname}: {e}")
|
||||
|
||||
def process_content_updates(root_dir):
|
||||
for dirpath, dirnames, filenames in os.walk(root_dir, topdown=True):
|
||||
# Yasaklı klasörlere girme
|
||||
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
|
||||
|
||||
if is_path_excluded(dirpath):
|
||||
continue
|
||||
|
||||
for filename in filenames:
|
||||
if filename.endswith(TARGET_EXTENSIONS) or filename == 'Cargo.lock':
|
||||
filepath = os.path.join(dirpath, filename)
|
||||
replace_in_file(filepath)
|
||||
|
||||
def main():
|
||||
root_dir = os.getcwd()
|
||||
print("==================================================")
|
||||
print(f"⚠️ DİKKAT: Çalışma dizini: {root_dir}")
|
||||
print(f"⚠️ HARİÇ TUTULANLAR: {EXCLUDE_DIRS}")
|
||||
print("==================================================")
|
||||
|
||||
# Otomatik onay veya soru
|
||||
# confirm = input("Emin misin? (evet/hayir): ")
|
||||
# if confirm.lower() != "evet": return
|
||||
print("İşlem başlatılıyor...")
|
||||
|
||||
print("\n--- Adım 1: Dosya İçeriklerinin Güncellenmesi ---")
|
||||
process_content_updates(root_dir)
|
||||
|
||||
print("\n--- Adım 2: Klasör ve Dosya İsimlerinin Değiştirilmesi ---")
|
||||
rename_directories_and_files(root_dir)
|
||||
|
||||
print("\n✅ Rebranding işlemi tamamlandı.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,320 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
crates.io İsim Rezervasyon Script'i (Gelişmiş Versiyon)
|
||||
|
||||
Özellikler:
|
||||
- Kaldığı yerden devam etme (--start-from)
|
||||
- Ayarlanabilir bekleme süresi (--interval)
|
||||
- Workspace izolasyonu (üst dizindeki Cargo.toml ile çakışmaz)
|
||||
- "Already exists" durumunu akıllıca yönetir (bekleme yapmaz)
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
|
||||
WORKSPACE_ROOT = Path(__file__).parent.resolve()
|
||||
PLACEHOLDER_DIR = WORKSPACE_ROOT / "crate_placeholders"
|
||||
|
||||
# Yeni isim listesi
|
||||
NEW_CRATE_NAMES = [
|
||||
"asset-hub-pezkuwichain-emulated-chain",
|
||||
"asset-hub-pezkuwichain-integration-tests",
|
||||
"asset-hub-pezkuwichain-runtime",
|
||||
"asset-hub-zagros-emulated-chain",
|
||||
"asset-hub-zagros-integration-tests",
|
||||
"asset-hub-zagros-runtime",
|
||||
"asset-test-pezutils",
|
||||
"pez-binary-merkle-tree",
|
||||
"pez-chain-spec-guide-runtime",
|
||||
"collectives-zagros-emulated-chain",
|
||||
"collectives-zagros-integration-tests",
|
||||
"collectives-zagros-runtime",
|
||||
"coretime-pezkuwichain-emulated-chain",
|
||||
"coretime-pezkuwichain-integration-tests",
|
||||
"coretime-pezkuwichain-runtime",
|
||||
"coretime-zagros-emulated-chain",
|
||||
"coretime-zagros-integration-tests",
|
||||
"coretime-zagros-runtime",
|
||||
"emulated-integration-tests-common",
|
||||
"pez-equivocation-detector",
|
||||
"pez-erasure-coding-fuzzer",
|
||||
"pez-ethereum-standards",
|
||||
"pez-finality-relay",
|
||||
"pez-fork-tree",
|
||||
"pezframe-election-solution-type-fuzzer",
|
||||
"pezframe-omni-bencher",
|
||||
"pezframe-remote-externalities",
|
||||
"pezframe-storage-access-test-runtime",
|
||||
"pez-generate-bags",
|
||||
"glutton-zagros-runtime",
|
||||
"governance-zagros-integration-tests",
|
||||
"pez-kitchensink-runtime",
|
||||
"pez-messages-relay",
|
||||
"pez-minimal-template-node",
|
||||
"pez-minimal-template-runtime",
|
||||
"pez-node-bench",
|
||||
"pez-node-primitives",
|
||||
"pez-node-rpc",
|
||||
"pez-node-runtime-pez-generate-bags",
|
||||
"pez-node-template-release",
|
||||
"pez-node-testing",
|
||||
"pez-penpal-emulated-chain",
|
||||
"pez-penpal-runtime",
|
||||
"people-pezkuwichain-emulated-chain",
|
||||
"people-pezkuwichain-integration-tests",
|
||||
"people-pezkuwichain-runtime",
|
||||
"people-zagros-emulated-chain",
|
||||
"people-zagros-integration-tests",
|
||||
"people-zagros-runtime",
|
||||
"pezkuwi",
|
||||
"pezkuwichain-emulated-chain",
|
||||
"pezkuwichain-runtime",
|
||||
"pezkuwichain-runtime-constants",
|
||||
"pezkuwichain-system-emulated-network",
|
||||
"pezkuwichain-teyrchain-runtime",
|
||||
"pezkuwichain-zagros-system-emulated-network",
|
||||
"relay-bizinikiwi-client",
|
||||
"relay-pezutils",
|
||||
"pez-remote-ext-tests-bags-list",
|
||||
"pez-revive-dev-node",
|
||||
"pez-revive-dev-runtime",
|
||||
"pez-slot-range-helper",
|
||||
"pez-solochain-template-node",
|
||||
"pez-solochain-template-runtime",
|
||||
"pez-pez_subkey",
|
||||
"pez-template-zombienet-tests",
|
||||
"peztest-runtime-constants",
|
||||
"test-teyrchain-adder",
|
||||
"test-teyrchain-adder-collator",
|
||||
"test-teyrchain-halt",
|
||||
"test-teyrchain-undying",
|
||||
"test-teyrchain-undying-collator",
|
||||
"testnet-teyrchains-constants",
|
||||
"teyrchain-template",
|
||||
"teyrchain-template-node",
|
||||
"teyrchain-template-runtime",
|
||||
"teyrchains-common",
|
||||
"teyrchains-relay",
|
||||
"teyrchains-runtimes-test-utils",
|
||||
"pez-tracing-gum",
|
||||
"pez-pez-tracing-gum-proc-macro",
|
||||
"yet-another-teyrchain-runtime",
|
||||
"zagros-emulated-chain",
|
||||
"zagros-runtime",
|
||||
"zagros-runtime-constants",
|
||||
"zagros-system-emulated-network",
|
||||
"pez-zombienet-backchannel",
|
||||
"pezassets-common",
|
||||
"bp-asset-hub-pezkuwichain",
|
||||
"bp-asset-hub-zagros",
|
||||
"bp-pezbeefy",
|
||||
"bp-bridge-hub-pezcumulus",
|
||||
"bp-bridge-hub-pezkuwichain",
|
||||
"bp-bridge-hub-zagros",
|
||||
"bp-header-pez-chain",
|
||||
"bp-pez-messages",
|
||||
"bp-pezkuwi-bulletin",
|
||||
"bp-pezkuwi-core",
|
||||
"bp-pezkuwichain",
|
||||
"bp-pez-relayers",
|
||||
"pezbp-runtime",
|
||||
"bp-test-pezutils",
|
||||
"bp-teyrchains",
|
||||
"bp-xcm-pezbridge-hub",
|
||||
"bp-xcm-pezbridge-hub-router",
|
||||
"bp-zagros",
|
||||
"pezbridge-hub-common",
|
||||
"pezbridge-hub-pezkuwichain-emulated-chain",
|
||||
"pezbridge-hub-pezkuwichain-integration-tests",
|
||||
"pezbridge-hub-pezkuwichain-runtime",
|
||||
"pezbridge-hub-test-utils",
|
||||
"pezbridge-hub-zagros-emulated-chain",
|
||||
"pezbridge-hub-zagros-integration-tests",
|
||||
"pezbridge-hub-zagros-runtime",
|
||||
"pezbridge-runtime-common",
|
||||
"pezmmr-gadget",
|
||||
"pezmmr-rpc",
|
||||
"pezsnowbridge-beacon-primitives",
|
||||
"pezsnowbridge-core",
|
||||
"pezsnowbridge-ethereum",
|
||||
"pezsnowbridge-inbound-queue-primitives",
|
||||
"pezsnowbridge-merkle-tree",
|
||||
"pezsnowbridge-outbound-queue-primitives",
|
||||
"pezsnowbridge-outbound-queue-runtime-api",
|
||||
"pezsnowbridge-outbound-queue-v2-runtime-api",
|
||||
"pezsnowbridge-pezpallet-ethereum-client",
|
||||
"pezsnowbridge-pezpallet-ethereum-client-fixtures",
|
||||
"pezsnowbridge-pezpallet-inbound-queue",
|
||||
"pezsnowbridge-pezpallet-inbound-queue-fixtures",
|
||||
"pezsnowbridge-pezpallet-inbound-queue-v2",
|
||||
"pezsnowbridge-pezpallet-inbound-queue-v2-fixtures",
|
||||
"pezsnowbridge-pezpallet-outbound-queue",
|
||||
"pezsnowbridge-pezpallet-outbound-queue-v2",
|
||||
"pezsnowbridge-pezpallet-system",
|
||||
"pezsnowbridge-pezpallet-system-frontend",
|
||||
"pezsnowbridge-pezpallet-system-v2",
|
||||
"pezsnowpezbridge-runtime-common",
|
||||
"pezsnowbridge-runtime-test-common",
|
||||
"pezsnowbridge-system-runtime-api",
|
||||
"pezsnowbridge-system-v2-runtime-api",
|
||||
"pezsnowbridge-test-utils",
|
||||
"pezsnowbridge-verification-primitives",
|
||||
"xcm-pez-docs",
|
||||
"xcm-pez-emulator",
|
||||
"xcm-pez-executor-integration-tests",
|
||||
"xcm-pez-procedural",
|
||||
"xcm-runtime-pezapis",
|
||||
"xcm-pez-simulator",
|
||||
"xcm-pez-simulator-example",
|
||||
"xcm-pez-simulator-fuzzer",
|
||||
]
|
||||
|
||||
def check_crate_available(name: str) -> bool:
|
||||
"""crates.io'da isim müsait mi kontrol et"""
|
||||
result = subprocess.run(
|
||||
["cargo", "search", name, "--limit", "1"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
return f'{name} = "' not in result.stdout
|
||||
|
||||
def create_placeholder(name: str) -> Path:
|
||||
"""Placeholder crate oluştur"""
|
||||
crate_dir = PLACEHOLDER_DIR / name
|
||||
crate_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# [workspace] ekleyerek parent workspace ile ilişkisini kesiyoruz
|
||||
cargo_toml = f'''[package]
|
||||
name = "{name}"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "PezkuwiChain SDK component - placeholder for name reservation"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/pezkuwichain/pezkuwi-sdk"
|
||||
homepage = "https://pezkuwichain.io"
|
||||
documentation = "https://docs.pezkuwichain.io/sdk/"
|
||||
authors = ["Kurdistan Tech Institute <info@pezkuwichain.io>"]
|
||||
keywords = ["pezkuwichain", "blockchain", "sdk"]
|
||||
categories = ["cryptography::cryptocurrencies"]
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
'''
|
||||
(crate_dir / "Cargo.toml").write_text(cargo_toml)
|
||||
|
||||
src_dir = crate_dir / "src"
|
||||
src_dir.mkdir(exist_ok=True)
|
||||
lib_rs = f'''//! {name}
|
||||
//! This crate is part of the PezkuwiChain SDK.
|
||||
//! Full implementation coming soon.
|
||||
#![doc = include_str!("../README.md")]
|
||||
'''
|
||||
(src_dir / "lib.rs").write_text(lib_rs)
|
||||
|
||||
readme = f'''# {name}
|
||||
Part of [PezkuwiChain SDK](https://github.com/pezkuwichain/pezkuwi-sdk).
|
||||
## About
|
||||
This crate is a component of the PezkuwiChain blockchain SDK.
|
||||
'''
|
||||
(crate_dir / "README.md").write_text(readme)
|
||||
return crate_dir
|
||||
|
||||
def publish_placeholder(crate_dir: Path, dry_run: bool = True):
|
||||
"""Placeholder'ı crates.io'ya publish et.
|
||||
Dönüş: (başarılı_mı, bekleme_gerekli_mi)
|
||||
"""
|
||||
args = ["cargo", "publish"]
|
||||
if dry_run:
|
||||
args.append("--dry-run")
|
||||
args.extend(["--manifest-path", str(crate_dir / "Cargo.toml")])
|
||||
|
||||
result = subprocess.run(args, capture_output=True, text=True, cwd=crate_dir)
|
||||
|
||||
if result.returncode == 0:
|
||||
return True, True # Başarılı, bekleme yap
|
||||
|
||||
# "already exists" hatasını kontrol et
|
||||
if "already exists" in result.stderr:
|
||||
return True, False # Zaten var, bekleme yapma
|
||||
|
||||
print(f"\n[HATA] {crate_dir.name} publish edilemedi:\n{result.stderr}")
|
||||
return False, False
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="crates.io isim rezervasyonu")
|
||||
parser.add_argument("--list", action="store_true", help="İsimleri listele")
|
||||
parser.add_argument("--check", action="store_true", help="crates.io'da müsaitlik kontrol et")
|
||||
parser.add_argument("--create", action="store_true", help="Placeholder crate'leri oluştur")
|
||||
parser.add_argument("--publish", action="store_true", help="crates.io'ya publish et")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Publish dry-run")
|
||||
parser.add_argument("--start-from", type=str, help="İşleme bu crate isminden başla (öncekileri atlar)")
|
||||
parser.add_argument("--interval", type=int, default=360, help="Publish arası bekleme süresi (saniye). Varsayılan: 360")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.list:
|
||||
for name in sorted(NEW_CRATE_NAMES):
|
||||
print(f" {name}")
|
||||
return
|
||||
|
||||
# Create/Publish işlemleri
|
||||
if args.create or args.publish:
|
||||
# Placeholder klasörünü oluştur
|
||||
PLACEHOLDER_DIR.mkdir(exist_ok=True)
|
||||
|
||||
start_processing = False
|
||||
if not args.start_from:
|
||||
start_processing = True
|
||||
|
||||
print(f"Toplam Crate Sayısı: {len(NEW_CRATE_NAMES)}")
|
||||
print(f"Bekleme Süresi: {args.interval} saniye")
|
||||
if args.start_from:
|
||||
print(f"Başlangıç: {args.start_from} (Öncekiler atlanacak)")
|
||||
|
||||
success = 0
|
||||
failed = 0
|
||||
skipped = 0
|
||||
|
||||
for i, name in enumerate(NEW_CRATE_NAMES, 1):
|
||||
# Resume mantığı
|
||||
if not start_processing:
|
||||
if name == args.start_from:
|
||||
start_processing = True
|
||||
else:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
print(f"[{i}/{len(NEW_CRATE_NAMES)}] {name}...", end=" ", flush=True)
|
||||
|
||||
# 1. Create
|
||||
crate_dir = create_placeholder(name)
|
||||
|
||||
# 2. Publish (Eğer istenmişse)
|
||||
if args.publish:
|
||||
success_status, needs_wait = publish_placeholder(crate_dir, args.dry_run)
|
||||
|
||||
if success_status:
|
||||
if needs_wait:
|
||||
print("✓ PUBLISHED")
|
||||
success += 1
|
||||
if not args.dry_run:
|
||||
print(f" -> Bekleniyor {args.interval}sn...")
|
||||
time.sleep(args.interval)
|
||||
else:
|
||||
print("✓ ZATEN VAR (Atlandı)")
|
||||
success += 1
|
||||
else:
|
||||
print("✗ FAILED")
|
||||
failed += 1
|
||||
else:
|
||||
print("✓ CREATED")
|
||||
|
||||
print(f"\nSonuç: {success} başarılı, {failed} başarısız, {skipped} atlandı.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,192 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eski (rebrand edilmemiş) kelimeleri tarayan script.
|
||||
Her crate için çalıştırılır ve kalan eski kelimeleri tespit eder.
|
||||
|
||||
Kullanım:
|
||||
python3 scan_old_words.py <crate_path>
|
||||
python3 scan_old_words.py /home/mamostehp/kurdistan-sdk/bizinikiwi/primitives/core
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
# Rebrand kuralları: (eski_pattern, yeni_kelime, açıklama)
|
||||
# Sıralama önemli - daha spesifik olanlar önce
|
||||
REBRAND_RULES = [
|
||||
# Terminoloji
|
||||
(r'\bparachain\b', 'teyrchain', 'parachain → teyrchain'),
|
||||
(r'\bParachain\b', 'Teyrchain', 'Parachain → Teyrchain'),
|
||||
(r'\bPARACHAIN\b', 'TEYRCHAIN', 'PARACHAIN → TEYRCHAIN'),
|
||||
(r'\brococo\b', 'pezkuwichain', 'rococo → pezkuwichain'),
|
||||
(r'\bRococo\b', 'Pezkuwichain', 'Rococo → Pezkuwichain'),
|
||||
(r'\bROCOCO\b', 'PEZKUWICHAIN', 'ROCOCO → PEZKUWICHAIN'),
|
||||
(r'\bwestend\b', 'zagros', 'westend → zagros'),
|
||||
(r'\bWestend\b', 'Zagros', 'Westend → Zagros'),
|
||||
(r'\bWESTEND\b', 'ZAGROS', 'WESTEND → ZAGROS'),
|
||||
(r'\bkusama\b', 'zagros', 'kusama → zagros'),
|
||||
(r'\bKusama\b', 'Zagros', 'Kusama → Zagros'),
|
||||
(r'\bKUSAMA\b', 'ZAGROS', 'KUSAMA → ZAGROS'),
|
||||
|
||||
# Crate prefix'leri (Cargo.toml name ve use statement'larda)
|
||||
# Dikkat: Bunlar sadece crate isimlerinde geçerli, rastgele "sp_" değil
|
||||
(r'\bsp-core\b', 'pezsp-core', 'sp-core → pezsp-core'),
|
||||
(r'\bsp-runtime\b', 'pezsp-runtime', 'sp-runtime → pezsp-runtime'),
|
||||
(r'\bsp-io\b', 'pezsp-io', 'sp-io → pezsp-io'),
|
||||
(r'\bsp-std\b', 'pezsp-std', 'sp-std → pezsp-std'),
|
||||
(r'\bsp-api\b', 'pezsp-api', 'sp-api → pezsp-api'),
|
||||
(r'\bsc-client\b', 'pezsc-client', 'sc-client → pezsc-client'),
|
||||
(r'\bsc-service\b', 'pezsc-service', 'sc-service → pezsc-service'),
|
||||
(r'\bframe-support\b', 'pezframe-support', 'frame-support → pezframe-support'),
|
||||
(r'\bframe-system\b', 'pezframe-system', 'frame-system → pezframe-system'),
|
||||
(r'\bpallet-balances\b', 'pezpallet-balances', 'pallet-balances → pezpallet-balances'),
|
||||
(r'\bcumulus-client\b', 'pezcumulus-client', 'cumulus-client → pezcumulus-client'),
|
||||
(r'\bcumulus-primitives\b', 'pezcumulus-primitives', 'cumulus-primitives → pezcumulus-primitives'),
|
||||
|
||||
# Snowbridge (pezsnowbridge-pezpallet önce, sonra genel snowbridge)
|
||||
(r'\bsnowbridge-pezpallet-', 'pezsnowbridge-pezpallet-', 'snowbridge-pezpallet- → pezsnowbridge-pezpallet-'),
|
||||
(r'\bsnowbridge-pallet-', 'pezsnowbridge-pezpallet-', 'snowbridge-pallet- → pezsnowbridge-pezpallet-'),
|
||||
(r'\bsnowbridge-', 'pezsnowbridge-', 'snowbridge- → pezsnowbridge-'),
|
||||
(r'\bsnowbridge_pallet_', 'pezsnowbridge_pezpallet_', 'snowbridge_pallet_ → pezsnowbridge_pezpallet_'),
|
||||
(r'\bsnowbridge_pezpallet_', 'pezsnowbridge_pezpallet_', 'snowbridge_pezpallet_ → pezsnowbridge_pezpallet_'),
|
||||
|
||||
# Bridge
|
||||
(r'\bbridge-hub-rococo\b', 'pezbridge-hub-pezkuwichain', 'bridge-hub-rococo → pezbridge-hub-pezkuwichain'),
|
||||
(r'\bbridge-hub-westend\b', 'pezbridge-hub-zagros', 'bridge-hub-westend → pezbridge-hub-zagros'),
|
||||
(r'\bbridge-runtime-common\b', 'pezbridge-runtime-common', 'bridge-runtime-common → pezbridge-runtime-common'),
|
||||
|
||||
# MMR
|
||||
(r'\bmmr-gadget\b', 'pezmmr-gadget', 'mmr-gadget → pezmmr-gadget'),
|
||||
(r'\bmmr-rpc\b', 'pezmmr-rpc', 'mmr-rpc → pezmmr-rpc'),
|
||||
|
||||
# Substrate (dikkatli - sadece proje referanslarında)
|
||||
(r'\bsubstrate-wasm-builder\b', 'bizinikiwi-wasm-builder', 'substrate-wasm-builder → bizinikiwi-wasm-builder'),
|
||||
(r'\bsubstrate-build-script-utils\b', 'bizinikiwi-build-script-utils', 'substrate-build-script-utils → bizinikiwi-build-script-utils'),
|
||||
|
||||
# Polkadot referansları
|
||||
(r'\bpolkadot-sdk\b', 'pezkuwi-sdk', 'polkadot-sdk → pezkuwi-sdk'),
|
||||
(r'\bpolkadot-runtime\b', 'pezkuwichain-runtime', 'polkadot-runtime → pezkuwichain-runtime'),
|
||||
(r'\bpolkadot-primitives\b', 'pezkuwi-primitives', 'polkadot-primitives → pezkuwi-primitives'),
|
||||
|
||||
# Rust module isimleri (underscore versiyonları)
|
||||
(r'\bsp_core\b', 'pezsp_core', 'sp_core → pezsp_core'),
|
||||
(r'\bsp_runtime\b', 'pezsp_runtime', 'sp_runtime → pezsp_runtime'),
|
||||
(r'\bsp_io\b', 'pezsp_io', 'sp_io → pezsp_io'),
|
||||
(r'\bsc_client\b', 'pezsc_client', 'sc_client → pezsc_client'),
|
||||
(r'\bframe_support\b', 'pezframe_support', 'frame_support → pezframe_support'),
|
||||
(r'\bframe_system\b', 'pezframe_system', 'frame_system → pezframe_system'),
|
||||
(r'\bpallet_balances\b', 'pezpallet_balances', 'pallet_balances → pezpallet_balances'),
|
||||
(r'\bcumulus_client\b', 'pezcumulus_client', 'cumulus_client → pezcumulus_client'),
|
||||
(r'\bcumulus_primitives\b', 'pezcumulus_primitives', 'cumulus_primitives → pezcumulus_primitives'),
|
||||
]
|
||||
|
||||
# Taranacak dosya uzantıları
|
||||
SCAN_EXTENSIONS = {'.rs', '.toml', '.md', '.json', '.yaml', '.yml'}
|
||||
|
||||
# Atlanacak dizinler
|
||||
SKIP_DIRS = {'target', '.git', 'node_modules', 'crate_placeholders'}
|
||||
|
||||
|
||||
def scan_file(file_path: Path) -> list:
|
||||
"""Tek bir dosyayı tarar ve bulunan eski kelimeleri döndürür."""
|
||||
findings = []
|
||||
|
||||
try:
|
||||
content = file_path.read_text(encoding='utf-8', errors='ignore')
|
||||
except Exception as e:
|
||||
return [(str(file_path), 0, f"OKUMA HATASI: {e}", "", "")]
|
||||
|
||||
lines = content.split('\n')
|
||||
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
for pattern, replacement, description in REBRAND_RULES:
|
||||
matches = re.finditer(pattern, line)
|
||||
for match in matches:
|
||||
findings.append({
|
||||
'file': str(file_path),
|
||||
'line': line_num,
|
||||
'column': match.start() + 1,
|
||||
'found': match.group(),
|
||||
'replacement': replacement,
|
||||
'description': description,
|
||||
'context': line.strip()[:100]
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def scan_crate(crate_path: str) -> list:
|
||||
"""Bir crate dizinini tarar."""
|
||||
crate_dir = Path(crate_path)
|
||||
|
||||
if not crate_dir.exists():
|
||||
print(f"HATA: Dizin bulunamadı: {crate_path}")
|
||||
return []
|
||||
|
||||
all_findings = []
|
||||
|
||||
for root, dirs, files in os.walk(crate_dir):
|
||||
# Skip directories
|
||||
dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
|
||||
|
||||
for file in files:
|
||||
file_path = Path(root) / file
|
||||
|
||||
if file_path.suffix not in SCAN_EXTENSIONS:
|
||||
continue
|
||||
|
||||
findings = scan_file(file_path)
|
||||
all_findings.extend(findings)
|
||||
|
||||
return all_findings
|
||||
|
||||
|
||||
def print_report(findings: list, crate_path: str):
|
||||
"""Bulunan eski kelimelerin raporunu yazdırır."""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"TARAMA RAPORU: {crate_path}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
if not findings:
|
||||
print("✅ ESKİ KELİME BULUNAMADI - Crate temiz!")
|
||||
return
|
||||
|
||||
print(f"❌ {len(findings)} adet eski kelime bulundu:\n")
|
||||
|
||||
# Dosyaya göre grupla
|
||||
by_file = {}
|
||||
for f in findings:
|
||||
if f['file'] not in by_file:
|
||||
by_file[f['file']] = []
|
||||
by_file[f['file']].append(f)
|
||||
|
||||
for file_path, file_findings in sorted(by_file.items()):
|
||||
rel_path = file_path.replace(crate_path, '.')
|
||||
print(f"\n📄 {rel_path}")
|
||||
print(f" {'-'*50}")
|
||||
|
||||
for finding in file_findings:
|
||||
print(f" Satır {finding['line']}: {finding['found']} → {finding['replacement']}")
|
||||
print(f" Bağlam: {finding['context']}")
|
||||
print()
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Kullanım: python3 scan_old_words.py <crate_path>")
|
||||
print("Örnek: python3 scan_old_words.py ./bizinikiwi/primitives/core")
|
||||
sys.exit(1)
|
||||
|
||||
crate_path = sys.argv[1]
|
||||
|
||||
findings = scan_crate(crate_path)
|
||||
print_report(findings, crate_path)
|
||||
|
||||
# Çıkış kodu: bulgu varsa 1, yoksa 0
|
||||
sys.exit(1 if findings else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<!-- Nevroz Fire Gradient - Kurdish Colors -->
|
||||
<linearGradient id="fireGradient" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#2E7D32;stop-opacity:1" /> <!-- Green base -->
|
||||
<stop offset="25%" style="stop-color:#FF6F00;stop-opacity:1" /> <!-- Orange -->
|
||||
<stop offset="55%" style="stop-color:#FFD600;stop-opacity:1" /> <!-- Yellow/Gold -->
|
||||
<stop offset="100%" style="stop-color:#C62828;stop-opacity:1" /> <!-- Red tip -->
|
||||
</linearGradient>
|
||||
|
||||
<!-- Inner flame gradient -->
|
||||
<linearGradient id="innerFlame" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#FFD600;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#FF8F00;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#FF5722;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- Glow filter -->
|
||||
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="6" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Main Nevroz Fire - Outer flame -->
|
||||
<path d="M256 50
|
||||
C285 115 350 155 375 225
|
||||
C400 295 375 365 350 405
|
||||
C325 445 285 475 256 485
|
||||
C227 475 187 445 162 405
|
||||
C137 365 112 295 137 225
|
||||
C162 155 227 115 256 50Z"
|
||||
fill="url(#fireGradient)"
|
||||
filter="url(#glow)"/>
|
||||
|
||||
<!-- Inner flame (yellow/orange core) -->
|
||||
<path d="M256 130
|
||||
C275 175 320 210 335 270
|
||||
C350 330 335 380 310 415
|
||||
C285 450 270 470 256 475
|
||||
C242 470 227 450 202 415
|
||||
C177 380 162 330 177 270
|
||||
C192 210 237 175 256 130Z"
|
||||
fill="url(#innerFlame)"
|
||||
opacity="0.95"/>
|
||||
|
||||
<!-- Brightest core -->
|
||||
<ellipse cx="256" cy="340" rx="55" ry="90" fill="#FFF8E1" opacity="0.85"/>
|
||||
|
||||
<!-- Core highlight -->
|
||||
<ellipse cx="256" cy="360" rx="35" ry="60" fill="#FFFFFF" opacity="0.6"/>
|
||||
|
||||
<!-- Green sparks (Kurdistan green accent) -->
|
||||
<circle cx="195" cy="170" r="10" fill="#43A047" opacity="0.85"/>
|
||||
<circle cx="317" cy="190" r="8" fill="#43A047" opacity="0.8"/>
|
||||
<circle cx="225" cy="140" r="6" fill="#66BB6A" opacity="0.7"/>
|
||||
<circle cx="287" cy="155" r="7" fill="#4CAF50" opacity="0.75"/>
|
||||
<circle cx="170" cy="220" r="5" fill="#81C784" opacity="0.6"/>
|
||||
<circle cx="342" cy="240" r="5" fill="#81C784" opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 5.2 MiB |
@@ -1 +0,0 @@
|
||||
rn_("0UkAAD1RPlE/UUBRQVFCUUNRRFFFUUZRR1FIUUlRSlEBAgA7MAAAAQAAIQEBAChGIQHzAwFrOzAAAAEAAEMBAQA7I0MBAQMAOzAAAAEAAC0BAQANIi0BAQQAOzAAAAEAACEBAQCfSyEBAQYAOzAAAAEAAJAAAQCSUZAAAQQAOzAAAAEAACEBAQB9SiEBAQMAOzAAAAEAACEBAQAxSCEBAQUAOzAAAAEAACEBAQAbUCEB8wOFJSAEOzAAAAEAACEBAQDrICEB8wCEJAEQ8mcZAAABAA==")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("sUABAPXe9t733vje+d763vve/N793v7e/94A3wUARwEABD7sZSDpBQBJAQAG5OxlKuthSQEAYuxj7GTsZexm7GfsaOyzAgSgUAAB6FdhZXN28zvmAQC1AwEA8wACaXMBa+wBALFDAQBvz3DPcc9yz3PPdM91z3bPd894z3nPes8FAcABAAtW6Ach6WVpAQQBoAAAAexPczswAAABAQAYAAIAxNsXAPHoAACxQgEAZFtlW2ZbZ1toW2lbaltrW2xbbVtuW29bvwaHoEAAAexhoKAAAejUhUQF")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQQAOzAAAAEBAJcNAgB0LcsGL8TLBgEGADswAAABAQBcBwIA5oLLBr7nkAABBAA7MAAAAQEAlw0CAKgmywZjvcsGAQMAOzABAAMAAMsGAQDLBgEABeXLBgEAw6/LBgEFADswAAABAQCXDQIAsGHLBg3fywZ3AwCGoIAAAeskJSEEOzAAAAEAAJcNAgB6GssGecTLBg==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("BQBHAAAGuFZl/lQrAqDAAABSxWl0AQcAOzAAAAEAAOcAAQCmTucAAQMAOzAAAAEAAOcAAQCSM+cAAQIAOzAAAAEAAOcAAQBAOucA8wCEAgEkOzAAAAEAAAMBAgDmChsAZQznAA==")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQIAOzAAAAEBAJcNAgB0LcsGL8TLBisCsFHsaQABYXUBAQGgYAAB7DtjOzABAAIAAAAAAQALAPLiAQAB3wsA+wJlcg==")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQoAOzAAAAEBABUAAQDM6RUAAQMAOzAAAAEBABcAAQCwDxcAUwCEoEAAAVw/sJHp8AABAFEBOzABAAMAAGwAAQANAAIAi1sAAOBwawADAEmBCwA66QAAzuwAAA==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("YUcAAOZS51LoUulS6lLrUuxSAQMAOzAAAAEAABEAAQCUQBEA0UMAAIZAh0CIQIlAikCLQIxAjUCOQI9AkECRQJJAk0ABAgGwYFFiAAFyOzAAAAEAACsAAQCqOSsAKwKgsAAAVPhhZfsEbHJ2eg==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("YUAAAKlSqlKrUqxSrVKuUq9S8wIBczswAAABAABSAAYAckAAAINHKwAMSgAABlMGAP9VDgAUVg4A8wABafNKRwAAAQCxDQ==")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("sUABAPXe9t733vje+d763vve/N793v7e/94A3wUARwEABD7sZSDpBQBAAQAEV+hv+ukFAEkBAAbk7GUq62FJAQBi7GPsZOxl7GbsZ+xo7FcDAsagwAAB7GCgEAAButpARgAJOzAAAAEBACYABQA75gAAGekGAPDpAQBZ6gYADusVAPMAAmlzAWvsAQA=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("BQLAAQCwsFqxWrJas1q0WrVatlq3WrhauVq6WrtaahnpGukb6RzpHeke6R/pDWvsMmlz5QBGAQAN7Oxz/uj/6ADpAekC6QPpBOkF6QbpB+kI6QnpCukL6QzpAQIAOzAAAAEBAFMAAQDdW1MAGwKgMAAB6SpheWFKAQDy6PPo9Oj16Pbo9+j46AEFADswAAABAQBfAAEAy6pfAAUBwAEADjPr6NLs0+zU7NXs1uzX7Njs2eza7Nvs3Ozd7N7s3+zg7GVpAQUAOzAAAAEBAFMAAQB3qlMAYUkBAE/oUOhR6FLoU+hU6FXoYUwBAOnp6unr6ezp7enu6e/pYUoBABLpE+kU6RXpFukX6RjpBQHBAQANzOwFDellb/sCb3IBAwA7MAAAAQEAawABABqCawABAwA7MAAAAQEAUwABAMaBUwD7AnJ2AQMAOzAAAAEBAGMAAwB9W18A0OwBAOrsAQABAM6gUAAB6vc3KMc191Ig5ZQq98dGPGKbU05SQzGHsv4NxF+QL15cJvDNfFtbvIJGt91DQfgHSVRGtoodB4hnFm76NoRfUIvfrYsjcdjsQLChYAAB7OpAPRYfOzABAAMAAHcAAQAYAAIAlXFTACt0IwADAHetCwCo5gsAD+kAAA==")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQYAOzAAAAEBAL0AAgA7614AbexeAAEAADswAQABAAAXAAEAAQABABN0FwD36fjpwUUBAM66z7rQutG60rrTutS61brWute62LrZuivr+wJhZSsDoLAAAepgoEAAAen5bHJ5m4SggAAB6PugkAAB6lcBQAU=")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("BQHAAAAhDkoPSv9UFOBB4UFlbwEAAqDAAABUNaDAAABWI3R5OzAAAAEAADkAAgAvSisAglENAAEGAqBAAABS/qFAAABWwHR18s9HAAABAAUBwAAADfxUFN5B30FmcgEDADswAAABAAArAAEAuykrAKcOAIihEAAAVqKwYEfNAAGg0AAAVDSCyAc7MAAAAQAAlwEEALoKKwBILCEBIUArANFHHQA=")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("hQBAAAAGuFZlqVKqUqtSrFKtUq5Sr1KwUv5U8wEBczswAAABAABSAAYAckAAAINHKwAMSgAABlMGAP9VDgAUVg4A")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQcAOzAAAAEBAHMEAQD7ynMEAQIAOzAAAAEAAHMEAQBl3nMEAQIAOzAAAAEAAHMEAQDx2XME+wMxMjYBAgGgAAAB6yhzOzAAAAEBALMAAQCY3bMAKwKgMAAA4/Bpa/sCcng=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQAAOzAAAAEBAFMAAQBgflMAsUMBAFe9WL1ZvVq9W71cvV29Xr1fvWC9Yb1ivfMBAWQBffoAAPsCY3QBAgA7MAAAAQAAywYBANHrywbzAAFtOzAAAAEAAMsGAQDdT8sGYUMBABLpE+kU6RXpFukX6Rjpe4SgMAABus0hIAI=")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("AQYAOzAAAAEAAL0AAgAPVV4AQVZeAAUBwAAAJQ5KD0r/VFCDE4QThROGE8tTzFNhZSsDoLAAAFQ0oEAAAFPNbHJ5m4SggAAAUs+gkAAAVCsBQAU=")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("AQMAOzAAAAEAABsAAQD+PxsAAQgAOzAAAAEAAJAAAQAPU5AA8wIBdTswAAABAABlAwMAYCohAW48IQEKRCEB+wJzdA==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("MUgAAPlU+lT7VMFWAQQAOzAAAAEAACEBAQCfSyEBAQYAOzAAAAEAAJAAAQCSUZAAAQQAOzAAAAEAACEBAQB9SiEBAQMAOzAAAAEAACEBAQAxSCEBAQUAOzAAAAEAACEBAQAbUCEB8wKFJSAEOzAAAAEAACEBAQDrICEB+wJhaA==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("BQHAAADQT0dQR1FHUkdTR1RHVUdWR1dHWEdZR1pHW0dcRwb1UmRuAQMAOzAAAAEAACEBAQCBTSEBMUIAAPlU+lT7VMFWMwEDoJAAAFIqZWlzOzAAAAEAAJEAAgAPU5AAD1YAAGuEoRAAAFY1oIAAAEoFFCEA")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("YUoAAOZS51LoUulS6lLrUuxS4VMAAKZWp1aoVqlWqlarVqxWrVauVq9WsFaxVrJWs1a0VhsCoMAAAFagYXgTgQKgUAAAUuFlbwECADswAAABAAAhAQEAfSchAQEBAaAQAABVB2U7MAAAAQAAHQABAKISHQAFAEQAABN7SnxKZO8pAQsAOzAAAAEAAJAAAQBuVZAAI4QCoKAAAFQrcHT7Am5y8UMAADQTNRM2EzcTOBM5EzoTOxM8Ez0TPhM/E0ATQRNCE0MT8wCGEVEBOzAAAAEAAJUEBABrDSEBIxdDArEeIQF1Rw0A")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("BQLAAAAQ+jn7OWrtUu5S71LwUvFS8lLzUg0/VjJpc+UARgAADcBWc9JS01LUUtVS1lLXUthS2VLaUttS3FLdUt5S31LgUgUBwAAA0i06LjovOjA6MToyOjM6NDo1OjY6Nzo4Ojk6OjoD/lJheWFKAADGUsdSyFLJUspSy1LMUvFFAABdR15HX0dgR2FHYkdjR2RHZUdmR2dHaEdpR2pHa0dsRwUBwAAADgdV6KZWp1aoVqlWqlarVqxWrVauVq9WsFaxVrJWs1a0VmVp0UUAAE9HUEdRR1JHU0dUR1VHVkdXR1hHWUdaR1tHXEdhSQAAI1IkUiVSJlInUihSKVJhTAAAvVO+U79TwFPBU8JTw1NhSgAA5lLnUuhS6VLqUutS7FIFAcEAAA2gVgXhUmVv+wJvcgEDADswAAABAAARAAEAlEARANFDAACGQIdAiECJQIpAi0CMQI1AjkCPQJBAkUCSQJNA+wJydgEDADswAAABAAATAAMAHToPAKRWAQC+VgEAAQDOoFAAAFTLIZyQWJmCSPTrhva0HOkXdac+G75gQGUrMQUuToKiUdUMQzkpKf/PyjGQYKWG8J3jSTB/ZuVUd2wioohTeq41TfioWGWZ7N0KoWAAAFa+QD0WHzswAAABAAAYAAUAFxMNAIcTBQDPRwEAYlEBAONSAAA=")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("AQMAOzAAAAEAAB4AAgATSB0A/lIAACFSAACkVqVWv1YhTQAAEFYRVrdWMUsAAMhUyVTKVKNW84MDY2VzAQgAOzAAAAEAAJAAAQAPU5AAFQBCAAAGD1ZjGVAaUAUBxAAAaABVAVUCVQNVBFUFVQZVZbZTt1O4U7lTulO7U7xTb3MBCQA7MAAAAQAAkAABAG5VkACVAUUAAECxUrJSs1K0UrtWQLVStlK3UrhSvFYyMw5QD1AQUBFQElATUBRQFVAWULpW+wJhdQEFADswAAABAADnAAEApk7nACMAArAwQeAAAWt1OzAAAAEAAF0AAQBdKV0A0UoAAIJRg1GEUYVRhlGHUYhRiVGKUYtRjFGNUY5Rj1HVAEMAAAMLSmRqLWstbC1tLW4tby1wLXEtci1zLXQtdS12LXctYUMAANhB2UHaQdtB3EHdQbVWtQFAAAAEDVMEDlNsdAYmByYIJgkmCiYLJgwmDSYPSBBIEUgSSBsCoGAAAFPKb3LRRAAAPEU9RT5FP0VARUFFQkVDRURFRUVGRUdFSEVJRfsCaW8xQQAAPzoOSg9K/1QBCwA7MAAAAQAAFQABAJJSFQABAAA7MAAAAQAAKwABAC9KKwDzAwFzOzAAAAEAAB0AAQDRRx0AYUcAAItSjFKNUo5Sj1KQUpFSAQMAOzAAAAEAAB0AAQBkUR0AAQEAOzAAAAEAAB0AAQARSh0A84ICZWnVAEEAANMAUAFQAlADUARQBVAGUAdQCFAJUApQC1AMUA1QZvw5/Tn+Of85ADoBOgI6AzoEOgU6BjoHOgg6CTrzgoQQMQAbAqBgAABUNGFvdQBBAAAVpE6lTmMCJgMmBCYFJvY59zkNSA5IEwACsDA6GgABYWPyFBMAAAEAIUQAAIRAhUCRUdFBAAAtOi46LzowOjE6MjozOjQ6NTo2Ojc6ODo5Ojo6YUcAAKlSqlKrUqxSrVKuUq9SYUgAAAZTB1MIUwlTClMLUwxTkwMEoDAAAFPGoHAAAFUHYWhwdjswAAABAAAYAAQA4R8PAHFAAAAKSgAAi1IGAAUBwAAAEhg6GToKqFJhb7cBAIWgcAAAUuIMgAryEhMAAAEAAQDRJ8UsFVs8dJye4DmbC2vLN33VZqXzlKD2d8fUe+vZbhfV/isxO6oEDgb7PgTQYmWWIHO+8qa4EJFq/fKaD2wCsTsgV4Y65bl7P4z1wJHwdCuOr9ilsBBFLAABJhcVFgYqUagZuL4TwG+UXzswAAABAACOAwgADiYdAOkpBQBgKiEBbjwhAbRAAQAKRCEBSkUBAMRSAAA=")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("IUIAADw6PTo+OhUARQAABg9WYxlQGlBBQAAAtVK2UrdSuFK8VkFAAACxUrJSs1K0UrtW8wIEMjNicjswAAABAAAZAAQA3SANAA5QCAA9VgEAulYAAAUARwAABBJWZfRSBQBJAAAGuFZl/lRhSQAANlY3VjhWOVY6VjtWPFazBQSgUAAAUithZXN2809RAAB1AgEA+wJhZg==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("IUAAAHgteS1zQAEDADswAAABAABdAAEAACpdAHFAAABPDDZWN1Y4VjlWOlY7VjxW8wADZGhyOzAAAAEAALYACQDlDgAALSYrAPApDwBKPA8ATUAdAO9HHQBSUQ8A9lIHAKBTFQAbArBgLq4AAWVp")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("sUEBANqC24Lcgt2C3oLfguCC4YLiguOC5ILlgvMCAWU7MAAAAQEAJAACAEzeIwDl7AAAYUcBAOno6ujr6Ozo7eju6O/oAQMAOzAAAAEBABcAAQCLuhcAowCEsJHp9wABoFAAAbrNAUAJOzAAAAEAABcAAQDZ4hcA+wJlbw==")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("BQFMAQAA5+wA6OwyM+bsGwKg4AAB7OlpbwUBwAEADOXsAynrbnQHBoSIoCAAAenzoKAAAezhoTAAAeztsCHp9AABoJAAAezNsHHsaQABOlAG")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQAAOzAAAAEAAC8AAQAbey8A8wIBczswAQABAAALAAEAAAABAExxCwAQ6QEFADswAAABAQAXAAEAxNsXAPsCY24=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("YUcBACzrLesu6y/rMOsx6zLrAQcAOzAAAAEBABgAAgCjuhcAEekAAGFAAQBi7GPsZOxl7GbsZ+xo7PMBAWQ7MAEAAwAABwEBABUAAQDx4wcBAQDM6RUAKwKgQAAB5jlnevMAAmlwOzABAAMAAC8AAQAMAAEArGIvAAIAuoELAL3nAAAFAcEBALJeql+qYKphqmKqY6pkqmWqZqpnqmiqaaplT+hQ6FHoUuhT6FToVehlcruEoKAAAen6AAgL")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("AQUAOzAAAAEAACEBAQBuPCEBAQIAOzAAAAEAAEMCAQAjF0MCAQIAOzAAAAEAAEMCAQDfFEMCYUwAAP9SAFMBUwJTA1MEUwVT8wCECBADOzAAAAEAAN0CAwDGBqEBDSItAQBQDQA=")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("0UMAAGA8YTxiPGM8ZDxlPGY8ZzxoPGk8ajxrPGw8bTzzAgFjOzAAAAEAADEAAwB+LQ0APEUNAMxUFQDxQAAAVQxWDFcMWAxZDFoMWwxcDF0MXgxfDGAMYQxiDGMMZAxjAAOgkAAAUs1hZHQ7MAAAAQAAQwICAFsmIQExSCEB")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("YUQBACzrLesu6y/rMOsx6zLrBQHDAQBoLOst6y7rL+sw6zHrMutl4unj6eTp5enm6efp6OlvcwUBTAEAAOfsAOjsMjPm7NMAhKDgAAHs6QBBUDswAQADAAALAAEADAABAKJYCwACAPXeCwBP7AAA")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("sUMBAHBbcVtyW3NbdFt1W3Zbd1t4W3lbelt7W7FFAQBI1knWStZL1kzWTdZO1k/WUNZR1lLWU9bzAAFjOzABAAMAABcAAQAYAAEA9OIXAAMAmFoLAOuuCwDz6QAAKwOgQAAB6RCg0AAB7DpscnPzAAJhYzswAAABAAALAAEAiHELAA==")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQMAOzAAAAEAAG8FAQC/vm8FAQABoDAAAek6aTswAAABAACnAAEAnkGnAAEAADswAAABAQCXDQIAqCbLBmO9ywYBAAGgMAAB6TlvAXkaAABnAwCGoAAAAbq8oBAAAek5IAgXOzABAAMAAIMAAQAZAAMA/wsLALQYRwAM4y8AAgD3rhcA9OkBAA==")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("BQBCAQC2POY95j7mP+ZA5kHmQuZD5kTmReZG5kfmZf3oAQcAOzAAAAEBAFsAAQD76VsAAQkAOzAAAAEBAF4AAQA7614A84MCZG0BAQA7MAAAAQAAMwIBABH3MwLhTQEAK+ws7C3sLuwv7DDsMewy7DPsNOw17DbsN+w47DnsAQIAOzAAAAEBALsBAQDc27sBIwACoLAAAepYZWw7MAAAAQEA+wMBAI+2+wMBAAA7MAAAAQAAcwQBAJ3ycwQBDQA7MAAAAQEAXgABAG3sXgDzAQF3OzAAAAEBAHMEAQCwOXME+wJsc/MAhQkAFjswAAABAACABAIAZFkAAFBqfwT7AmVp")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQEAOzAAAAEAAHMEAQB3l3MECQEuZbA49F9uAQEBoDAAAbrLZDswAAABAQBtAAQAWg9TALAPFwBf7AAAbOwAAAEEADswAAABAQAjAAEA3GgjADsDoAAAAejUYWZnBQLAAQCwsFqxWrJas1q0WrVatlq3WrhauVq6WrtaahnpGukb6RzpHeke6R/pDWvsMmlz5QBGAQAN7Oxz/uj/6ADpAekC6QPpBOkF6QbpB+kI6QnpCukL6QzpAQIAOzAAAAEBAFMAAQDdW1MAGwKgMAAB6SpheWFKAQDy6PPo9Oj16Pbo9+j46AEFADswAAABAQBfAAEAy6pfAAUBwAEADjPr6NLs0+zU7NXs1uzX7Njs2eza7Nvs3Ozd7N7s3+zg7GVpAQUAOzAAAAEBAFMAAQB3qlMAYUkBAE/oUOhR6FLoU+hU6FXoYUwBAOnp6unr6ezp7enu6e/pYUoBABLpE+kU6RXpFukX6RjpBQHBAQANzOwFDellb/sCb3IBAwA7MAAAAQEAawABABqCawABAwA7MAAAAQEAUwABAMaBUwD7AnJ2AQMAOzAAAAEBAGMAAwB9W18A0OwBAOrsAQABAc6gUAAB6vc3KMc191Ig5ZQq98dGPGKbU05SQzGHsv4NxF+QL15cJvDNfFtbvIJGt91DQfgHSVRGtoodB4hnFm76NoRfUIvfrYsjcdjsQLChYAAB7OpAPRYfOzABAAMAAHcAAQAYAAIAlXFTACt0IwADAHetCwCo5gsAD+kAAAECADswAAABAQCzAAEAN66zAAEGADswAAABAQAXAAEAxNsXAPOBAmFpnQeHAKBwAAHrJKAgAAHpOgMVGBlFcAQ=")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("BQHBAAAGzlI+pFalVr5Wv1ZnaQELADswAAABAAAVAAEA4lQVAAUBwwAAA8dUB0BWaXDzBQF3AQdKAAABAwGgEAAASgZvOzAAAAEAABcAAwB0QAAATlEAAJJSFQCFAEQAAAszVndMR/9SAFMBUwJTA1MEUwVTNFYBCwA7MAAAAQAAFQABAMxUFQABAAA7MAAAAQAAHQABAK9HHQDzAwFz9nVAAABEEgEAAQABAAEE9woDyKBwAABRTbCgU8QAAcBEkAo7MAAAAQAALQACAL8UHwAuRQ0A+wJhZQ==")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("BQBFAQAH/ehlWOoFAsMBADsl6ybrJ+vt7PW+ur+6wLrBusK6w7rEusW6xrrHusi6ybo87D3szOzj7Aj86GxvchUARQEABuTsZdzoKuv7Am5y8wACZXL0JesBAAEAAQDGAQ==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("AYMDoLAAAFajoNAAAFa3oSAAAFa/Y2VzNQFAAAAEDVMEDlNsdA9IEEgRSBJIE4ECoGAAAFPKb3JHAQXFoFAAAFGRsCBIDQABsBBFLAABwAgAGDswAAABAAAjAAIADiYdAOkpBQAVAEIAAAOQUWRrQGxA8wMBc/KAEwAAAQABhAKhEAAAVqKg0AAAViNic2FGAADmUudS6FLpUupS61LsUu8BhbBgOfgAATSEAA==")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQIAOzAAAAEBAFMAAQBbq1MAYUsBADTrNes26zfrOOs56zrrAQUAOzAAAAEBAFMAAQCGglMA4wKEoHAAAexPBUABOzABAAMAAPcBAQBhAAIAOlpPAThwpwADAAx+UwDi6QYALOsGAPMAAXQ7MAAAAQEADgABAFDsDgA=")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("AQYAOzAAAAEAAB4AAwAMSgAA/1UOABRWDgABBAA7MAAAAQAAHQABABNIHQAFAEIAAOAkViVWJlYnVihWKVYqVitWLFYtVi5WL1YwVjFWMlZzKlIBAwA7MAAAAQAAHgACADs6AABMRR0A5wEAxaBgAABUNQBCAQryJRMAAKg/")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("AQIAOzAAAAEAAL0AAQBpGb0ABQHAAAAFxVIEtlZjZJMAhKBgAABS5KEAAABWthMBADswAAABAAB1AwQASAwFADg1TwPwPw0AlEARAA==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("cUUAABpAG0AcQB1AHkAfQCBAoVYFAUwAAAC7VgC8VjIzulYTAAKg4AAAVr1pbwEjVgAABQBCAAAQF1AYUHM/VhsCoGAAAFKoYWkBAwA7MAAAAQAAIQEBAIg4IQErAqAgAABU/Wh10UUAAOI/4z/kP+U/5j/nP+g/6T/qP+s/7D/tP+4/7z8BAgA7MAAAAQAAIQEBALEeIQEBAQA7MAAAAQAAIQEBAI8dIQErAqAwAAA6HGR1AQIAOzAAAAEAAEMCAgBtHCEBKEYhAdFFAADUP9U/1j/XP9g/2T/aP9s/3D/dP94/3z/gP+E/AQEAOzAAAAEAAEMCAQCQPUMCAQIAOzAAAAEAACcCAQDiQScC8wICYXI7MAAAAQAAQwIBACkaQwL3DgCIoMAAAFa5BTQHOzAAAAEAAJIFBwBrBAAAigkhATsjQwGSM+cAgU0hATZUkABuVZAAAQEAOzAAAAEAAL0AAQBpGb0ABQLAAAAQ+jn7OWrtUu5S71LwUvFS8lLzUg0/VjJpc+UARgAADcBWc9JS01LUUtVS1lLXUthS2VLaUttS3FLdUt5S31LgUgUBwAAA0i06LjovOjA6MToyOjM6NDo1OjY6Nzo4Ojk6OjoD/lJheWFKAADGUsdSyFLJUspSy1LMUvFFAABdR15HX0dgR2FHYkdjR2RHZUdmR2dHaEdpR2pHa0dsRwUBwAAADgdV6KZWp1aoVqlWqlarVqxWrVauVq9WsFaxVrJWs1a0VmVp0UUAAE9HUEdRR1JHU0dUR1VHVkdXR1hHWUdaR1tHXEdhSQAAI1IkUiVSJlInUihSKVJhTAAAvVO+U79TwFPBU8JTw1NhSgAA5lLnUuhS6VLqUutS7FIFAcEAAA2gVgXhUmVv+wJvcgEDADswAAABAAARAAEAlEARANFDAACGQIdAiECJQIpAi0CMQI1AjkCPQJBAkUCSQJNA+wJydgEDADswAAABAAATAAMAHToPAKRWAQC+VgEAAQHOoFAAAFTLIZyQWJmCSPTrhva0HOkXdac+G75gQGUrMQUuToKiUdUMQzkpKf/PyjGQYKWG8J3jSTB/ZuVUd2wioohTeq41TfioWGWZ7N0KoWAAAFa+QD0WHzswAAABAAAYAAUAFxMNAIcTBQDPRwEAYlEBAONSAAD5AgARbXQBCAA7MAAAAQAAHwABAFtKHwAbhKDwAABWtqAAAAATgqEgAABWwUAgIQUBwQAABORSAMdTbHIFAcAAAAXFUgS2VmNktboAjAABoKAAAFa1ArAgU8gAAQSgkAAAVqEXIyWwcFY9AAEnO1EPOzAAAAEAAAQECQBIDAUATQ0dAGoPDQAAKl0AODVPA/A/DQCUQBEAjk8DAMVSAAA=")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("BQLCAAA7+VT6VPtUwVZVCEoJShBWEVagVrdWCNBSbG9yBQHAAADSYDxhPGI8YzxkPGU8ZjxnPGg8aTxqPGs8bDxtPAUPVmFoUwADoEAAAEoNY2VuOzAAAAEAAEcACgCsCg0AghMAAAAmAAB+LQ0APzoAADxFDQAOSgEAGVABAMxUFQD/VAAA")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQMAOzAAAAEBAFMAAQAAaVMA8wIBYzswAAABAQC9AAMAyA9TAAqeUwD46hUAAQAAOzAAAAEAAF8AAQBZSV8AYwADoJAAAej5YWR0OzABAAMAAMsGAQDLBgEABeXLBgEAw6/LBg==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("BQBHAAAEElZl9FIFAEkAAAa4VmX+VGFJAAA2VjdWOFY5VjpWO1Y8VrMCBKBQAABSK2Flc3bzT1EAAHUCAQATAAKwAFAXAAFpcwE/VgAABQHAAAALKlIH9VJlaUUARAAAACNWc45Pj0+QT5FPxVKvBIewIDoYAAGgQAAAVjWgoAAAUqiwME1/AAGFRAU=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQIAOzAAAAEBAFMAAQBbq1MAsUUAABL9E/0U/RX9Fv0X/Rj9Gf0a/Rv9HP0d/QUBwAEACFjqajTrNes26zfrOOs56zrrZXIBBAA7MAAAAQEAWwABAPvpWwDhSAEA0uzT7NTs1ezW7Nfs2OzZ7Nrs2+zc7N3s3uzf7ODsBQFGAQAD8ukHM+tocMq6AQUAOzAAAAEBAFMAAQCGglMABQHBAQAFT+wEYexjaPMAiBXJATswAQADAAD3AQEAYQACADpaTwE4cKcAAwAMflMA4ukGACzrBgA=")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("0UEAAE9HUEdRR1JHU0dUR1VHVkdXR1hHWUdaR1tHXEcrA6EQAABWNaCAAABKBWNpbtFEAADUP9U/1j/XP9g/2T/aP9s/3D/dP94/3z/gP+E/O4SgEAAAUuKASnsAFxQBRAI=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("sUUBADzmPeY+5j/mQOZB5kLmQ+ZE5kXmRuZH5gEDADswAAABAQCzAAEA2FmzAAEEADswAAABAQD7AwEAj7b7A/sCY2X7Am54EwCEoEAAAekhoNAAAew/oFAAAezOUAAUOzABAAIAAAAAAQA2ADlaCQCyiSMAa60LANToAAD96AAAV+oAAGDqAAAk6wAAOuwAAOHsAAA=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("tQBCAQADvOdkPYE+gT+BQIFBgUKBQ4FEgUWBRoFHgUiB8wIBczswAAABAAALAAEABnQLALFBAQDoFukW6hbrFuwW7RbuFu8W8BbxFvIW8xYFAcABAAQh6QXO7Gd3AQIAOzAAAAEBAFMAAQAQW1MA+wJkZwEAADswAAABAQBTAAEAd6pTAPsDZG534UABAFDsUexS7FPsVOxV7FbsV+xY7FnsWuxb7FzsXexe7AEEADswAAABAQBzBAEAHp9zBPMAAmVzOzABAAMAAMsGAQDMBgEAj6nLBgIAkqPLBlboAAABAAGgAAAB6yhzOzAAAAEBALMAAQCY3bMAsUEBAOKJ44nkieWJ5onnieiJ6YnqieuJ7IntiQEDADswAAABAQDLBgEAe8/LBjFCAQAl6ybrJ+vt7DMAA6CQAAHoVmVpczswAAABAQCRAAIAO+mQADvsAAABAgA7MAAAAQEAcwQBAGgbcwQBBQA7MAAAAQEAXgABAFjoXgDzAgFjOzAAAAEBAHMEAQD0FnME+wIxMvvFAgKIQMcEAIehEAAB7GGgMAAB57yggAABurugAAAB7D8cIQo7MAAAAQEAXwACAN1bUwA9gQsA")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("0UIAAHVHdkd3R3hHeUd6R3tHfEd9R35Hf0eAR4FHgkdhSwAACFUJVQpVC1UMVQ1VDlXRRQAApkCnQKhAqUCqQKtArECtQK5Ar0CwQLFAskCzQOMAhKBwAABWIwVAATswAAABAABvAAUAMA83ANwSGwDiPw0AtlMGAABVBgDhSwAAJFYlViZWJ1YoVilWKlYrVixWLVYuVi9WMFYxVjJWOwOggAAAUsVkbW4=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQQAOzAAAAEBAFMAAQAMflMAAQgAOzAAAAEBAJAAAQA76ZAA8wQBdTswAQADAADhAgEA6QMBAB794QIBAAAA6QMFAsABAAs65gj66RNf7GzsZGZpsUMAAGuXbJdtl26Xb5dwl3GXcpdzl3SXdZd2lwEHADswAAABAQAkAAIAsokjAOHsAADHBwCHoAAAAek5oOAAAen5EEFOOzAAAAEAALMAAQCxV7MA")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQYAOzAAAAEBAJAAAQCa65AA8wABYTswAAABAQCXDQIAdC3LBi/EywYTAQKgMAAB6Q9jdDswAAABAAAXAAEAp74XAA==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("YUQAALZTt1O4U7lTulO7U7xTMVEAAKRWpVa+Vr9W+wJweWFHAAAAVQFVAlUDVQRVBVUGVUFHAAABSgJKA0oESuVSYUAAADZWN1Y4VjlWOlY7VjxW8wIBZDswAAABAABBAAIALSYrAKBTFQDzAAJpcDswAAABAAAKAAMAmhAHAIRAAQCRUQAAawOwQEdKAAFscnUBBwA7MAAAAQAAWwABAM9TWwABBAA7MAAAAQAAvQACAA9VXgBBVl4ABQHBAAABx1Rg2EHZQdpB20HcQd1BtVZmZyFMAAAQVhFWt1ZlAkAAAA3lUhP0OfU5Df9UYXB21w7YDtkO2g7bDtwO/VT7AnJ4AQkAOzAAAAEAAL0AAgAPVV4AQVZeAAUBwAAA1D1RPlE/UUBRQVFCUUNRRFFFUUZRR1FIUUlRSlE6yFTJVMpUo1Zhdf8XirBwVjMADbBQQG0AARzZEg==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("BQBIAADgJFYlViZWJ1YoVilWKlYrVixWLVYuVi9WMFYxVjJWcypSAQcAOzAAAAEAABcAAgBSUQ8A9lIHAPsCZXQ=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQAAOzABAAIAAAAAAQDLBq5YAQAEUssGGwKgIAAB6mBlaeUARAEADezsc/7o/+gA6QHpAukD6QTpBekG6QfpCOkJ6QrpC+kM6fsCZ2w=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("sUQBAGutbK1trW6tb61wrXGtcq1zrXStda12rQEDADswAAABAQBTAAEAd6pTADsDoVAAAeziY2dwBQHAAQC1b89wz3HPcs9zz3TPdc92z3fPeM95z3rPtV6eX55gnmGeYp5jnmSeZZ5mnmeeaJ5pnmFw4U8BAFDsUexS7FPsVOxV7FbsV+xY7FnsWuxb7FzsXexe7LFGAQCo5qnmquar5qzmreau5q/msOax5rLms+bzAgFyOzAAAAEBAAcBAQDQWAcB7wGFoLAAAeskEUkA")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("YUcAAABVAVUCVQNVBFUFVQZVQUcAAAFKAkoDSgRK5VJhQAAANlY3VjhWOVY6VjtWPFbzAQFkOzAAAAEAAEEAAgAtJisAoFMVACsCoEAAAFFNZ3rzAAJpcDswAAABAAAKAAMAmhAHAIRAAQCRUQAABQHBAAASSkdLR2UjUiRSJVImUidSKFIpUmVyu4SgoAAAU84ACAs=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("BQLAAQCzLeYu5i/mMOYx5jLmM+Y05jXmNuY35jjmBfDoBg/pbnByYUABANXo1ujX6Njo2eja6Nvo8wIBczswAAABAQAuAQYAYoEAAK+rBwHMugAAMukGACvsDgBA7A4AsUEBANqC24Lcgt2C3oLfguCC4YLiguOC5ILlgvMAA2VpdDswAAABAQAxAAQAXqoLAEzeIwAo6wAA5ewAAA==")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQMAOzAAAAEBAKcAAQC0fqcAAQgAOzAAAAEBAJAAAQA76ZAA8wIBdTswAQADAADhAgEAgREBAB794QIDAAAA6QNUacsG3pbLBvsCc3Q=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("wUABAFe9WL1ZvVq9W71cvV29Xr1fvWC9Yb1ivbznAQEAOzAAAAEBAFMAAQB3qlMAKwOhEAAB7GGggAABurtjaW4BBAA7MAAAAQEAUwABALh9UwC7hKAQAAHpDgFEAg==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("AYMDoLAAAFajoNAAAFa3oSAAAFa/Y2VzNQFAAAAEDVMEDlNsdA9IEEgRSBJIE4ECoGAAAFPKb3JHAQXFoFAAAFGRsCBIDQABsBBFLAABwAgAGDswAAABAAAjAAIADiYdAOkpBQAVAEIAAAOQUWRrQGxA8wMBc/KAEwAAAQABAAA7MAAAAQAAIQEBAB8LIQErArBQLq4AAW5wAYQCoRAAAFaioNAAAFYjYnMlAEAAAGTmUudS6FLpUupS61LsUmV4LXktc0ABAwA7MAAAAQAAXQABAAAqXQBxQAAATww2VjdWOFY5VjpWO1Y8VvMAA2RocjswAAABAAC2AAkA5Q4AAC0mKwDwKQ8ASjwPAE1AHQDvRx0AUlEPAPZSBwCgUxUA7wOGsGA5+AABNIUA")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("YUcBANXo1ujX6Njo2eja6NvoYUgBADLpM+k06TXpNuk36TjpkwIEoDAAAenyoHAAAeszYWhwdjswAQADAABfAAEACAABAEe+XwADAGGBAADKugAAt+gGAA==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("NQFAAAA5+VT6VPtUwVZp+k/7T/xP/U/+T/9PuVZjdv1J/kn/SQBKAQ0AOzAAAAEAAF4AAQBBVl4ABQFMAAAAu1YAvFYyM7pWGwKg4AAAVr1pbwUBwAAADLlWA/1UbnQHBoaIoCAAAFPHoKAAAFa1oTAAAFbBsCBTyAABoJAAAFahsHBWPQABOlAGNQBAAAAoDkoPSv9UcFAMUQxSDFMMGwKwADn4AAFlcwUBwwAAA8dUB0BWaXDRQwAAYDxhPGI8YzxkPGU8ZjxnPGg8aTxqPGs8bDxtPPMBAWM7MAAAAQAAMQADAH4tDQA8RQ0AzFQVAPMAAmV3OzAAAAEAABMABAAtDwEANBMPAAdKAAANSgAABQHAAAAH0FLvplanVqhWqVaqVqtWrFatVq5Wr1awVrFWslazVrRWZXUFAcQAAGgAVQFVAlUDVQRVBVUGVWW2U7dTuFO5U7pTu1O8U29z8wUBc/JKRQAAAQBRRAAACEoJShBWEVagVrdWAQAAOzAAAAEAAEMCAgBOMSEBfUohARsCoBAAAFMNcnMBBwA7MAAAAQAA5wABAKZO5wABAwA7MAAAAQAA5wABAJIz5wABAgA7MAAAAQAA5wABAEA65wDzAQNiaXQ7MAAAAQAA5wABAGUM5wDRQQAAT0dQR1FHUkdTR1RHVUdWR1dHWEdZR1pHW0dcRyOBA6EQAABWNaCAAABKBWNpbiFBAABvQHBAzVO/BYiwQEfNAAGhUAAAVrawQBlnAAFF4AMBAAA7MAAAAQAAKwABAC9KKwDzAgFzOzAAAAEAAB0AAQDRRx0AGwKwgEdKAAFhZnFHAABxQItSjFKNUo5Sj1KQUpFSMUoAAPlU+lT7VMFW4VAAAKZWp1aoVqlWqlarVqxWrVauVq9WsFaxVrJWs1a0VgEDADswAAABAAAdAAEAZFEdAAEBADswAAABAAAdAAEAEUodAPOCAmVpIVIAAKRWpVa/ViFNAAAQVhFWt1YxSwAAyFTJVMpUo1bzgwNjZXPVAEMAAAMLSmRqLWstbC1tLW4tby1wLXEtci1zLXQtdS12LXcttQFAAAAEDVMEDlNsdAYmByYIJgkmCiYLJgwmDSYPSBBIEUgSSBOBAqBgAABTym9ydQBBAAAVpE6lTmMCJgMmBCYFJvY59zkNSA5IEwACsDA6GgABYWPyFBMAAAEAFQFAAAATGDoZOiSEQIVAkVFjdRITExO1CwDIAAGgcAAAVDQDBR+wEEUsAAEGwEkAGTswAAABAAAmAAQADiYdAOkpBQBKRQEAxFIAANFDAAAAUAFQAlADUARQBVAGUAdQCFAJUApQC1AMUA1Q8wABZjswAAABAABTAAUAxBEbAPw5DQDUPw0AdkANAD1RDQD7AnJ0BQHAAAAJo04Uf02ATWJjVf8AzACwMCZZAAEBoCAAAE6jDxQVIxIsMBNATqxYOzAAAAEAACUBCABHBCMAYhMdAHo0vQAYOgMAHToPAGA8DQCkVgEAvlYBACFAAAB4LXktc0DzAQFyOzAAAAEAAHMABgDwKQ8ASjwPAE1AHQDvRx0AUlEPAPZSBwAFAcAAAHVtR25Hb0dwR3FHckdzR3RH7SRWJVYmVidWKFYpVipWK1YsVi1WLlYvVjBWMVYyVnN0FQBAAAANE1Z0NFT4VPkCFRZlafsCZGcBAgA7MAAAAQAAGQEDAM9TWwAPVV4AQVZeAAUAQgAABBJWZfRSZQBCAAARtEC1QGX6T/tP/E/9T/5P/0+5VjUDQAAAGctTzFMz/Un+Sf9JAEoFDUpnvVK+Ur9SwFLBUsJSw1Jhb3J1+yX8Jf0l/iXzgQJlbzMAA6CgAABRT2FpdDswAAABAAANAAEA0x8NAAUCwAAAAN8OEScaKBoALA9laXnzAIYMMILyNAMAAAEAlQFGAABAsVKyUrNStFK7VkC1UrZSt1K4UrxWMjMOUA9QEFARUBJQE1AUUBVQFlC6VrUXAMkVFqBwAABUNBgaADSwEEUsAAEbwEmAGTswAAABAAAmAAQADiYdAOkpBQBKRQEAxFIAAAEAAbBgUWIAAXI7MAAAAQAAKwABAKo5KwDzAAFzOzAAAAEAAB0AAQCvRx0AAQAAOzAAAAEAAE8DAQA4NU8D0UAAAIJRg1GEUYVRhlGHUYhRiVGKUYtRjFGNUY5Rj1HzAgFzOzAAAAEAACsAAQAhQCsA8wACYXQ7MAAAAQAASQABAJJPSQCFDgCIoGAAAE6joNAAAFT4oLAAAFPGAKCQAABSzxkEBhSwBzswAAABAABtAQoA5A4AAAYPHQAMEwUARBMdAIg4IQF1QAAAUFEBALlSAwC4VgAAvVYAAPUCQAAAElkmWiYSSkdLR2UjUiRSJVImUidSKFIpUmFlclUMVgxXDFgMWQxaDFsMXAxdDF4MXwxgDGEMYgxjDGQMFQBCAAAFzVJ0XipfKgUBwAAAZsZSx1LIUslSylLLUsxSCfhUaHQlAEIAAHCvFLAUsRSyFLMUtBS1FLYUcwoTCxPkUgECADswAAABAAC9AAEAbi+9AAEFADswAAABAABeAAEALFJeAPMCAWM7MAAAAQAAvQABALAuvQDzggIxMgUBwAAANI5Pj0+QT5FPE39NgE1jcq8Hh6CQAABOo6AQAABSzwYlBgEA0GrlO5/Qd2ZIJtk6wV/BlN+xr6AgAABOowD20rXo4AERgVKqHWzBoJZq+V+JOfkrD3YNtDmHSAOwM8I3BwhRrWI3PCJYpRw/Yj9Hn1gGjl7wuf1+1Gp3+vb7bSHdoQX2S0BOvl47MAAAAQAAbAMNAEcEIwB7BAAAYhMdAP8lAABbJiEBejS9ABg6AwAdOg8AYDwNADFIIQHiUgAApFYBAL5WAQA=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("AQEAOzAAAAEBABQAAwBP6AYA8ugGABLpBgDzAAFvOzAAAAEAAHMEAQCd8nMEAQMAOzAAAAEBAFwHAgDmgssGvueQAAENADswAAABAQBeAAEAbexeAOFQAQDS7NPs1OzV7Nbs1+zY7Nns2uzb7Nzs3eze7N/s4OzzAQJydzswAAABAQBzBAEAsDlzBJ8BhbCB6fcAAaBAAAHq9wCoAw==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("BQBFAAAH0VJlLFQFAsMAADv5VPpU+1TBVlUISglKEFYRVqBWt1YI0FJsb3IVAEUAAAa4VmWwUv5U+wJucvMAAmVy9PlUAAABAAEAxgE=")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("YUoBABLpE+kU6RXpFukX6Rjp4VMBANLs0+zU7NXs1uzX7Njs2eza7Nvs3Ozd7N7s3+zg7BsCoMAAAezMYXgTgQKgUAAB6Q1lbwECADswAAABAADLBgEA0evLBgEBAaAQAAHrM2U7MAAAAQAAswABANxuswCxQwEAV71YvVm9Wr1bvVy9Xb1evV+9YL1hvWK98wQBZAF9+gAAAQsAOzAAAAEBAJAAAQCa65AAI4QCoKAAAepXcHT7Am5yAQMAOzAAAAEAAF8AAQA+cl8A8wCGEVEBOzABAAMAAC8bAQBTAAMA3U/LBtOJlw0nt8sGAQBbq1MA")
|
||||
@@ -0,0 +1 @@
|
||||
rn_("BQFMAQAA5+wA6OwyM+bsGwKg4AAB7OlpbwUBwAEADOXsAynrbnQHBoqIoCAAAenzoKAAAezhoTAAAeztsCHp9AABoJAAAezNsHHsaQABOlAG")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("AQMAOzAAAAEAAOcAAQD1H+cAAQABoDAAAFMOaTswAAABAAAbAAEAAgsbAAEAADswAAABAABDAgIATjEhAX1KIQEFAEAAAAMNU297BGcDAIagAAAASgagEAAAUw0gCBc7MAAAAQAAGwAFAAkCAQAoBAsABiYHAA9IAwDIUwEA")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("AQAAOzAAAAEAAB0AAQBiEx0ABQHAAAASSkdLR2UjUiRSJVImUidSKFIpUmVyUwCEoCAAAA8soDAAAFMNCEkA8nsEAABnTg==")
|
||||
@@ -1 +0,0 @@
|
||||
rn_("YU4AAC1ULlQvVDBUMVQyVDNUAQcAOzAAAAEAAFsAAQDPU1sAAQkAOzAAAAEAAF4AAQAPVV4A84MCZG0BDQA7MAAAAQAAXgABAEFWXgDzAgF3OzAAAAEAAL0AAQB6NL0A8wICYXc7MAAAAQAAvQABAOARvQABAwA7MAAAAQAAIQEBAIFNIQExQgAA+VT6VPtUwVbzAgJlaTswAAABAACRAAIAD1OQAA9WAAAxTgAApFalVr5Wv1YBAgGgIAAATqNmOzAAAAEAABMAAwAdOg8ApFYBAL5WAQDXBgCHoGAAAFLOoDAAAFLPQU0IOzAAAAEAAL4AAgALAgAAakW9AA==")
|
||||