Compare commits
39 Commits
18d41743e8
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 070d682759 | |||
| cd56ab8fb6 | |||
| 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 | |||
| 69789548e7 | |||
| 86ff43e206 | |||
| 09da6e80b7 | |||
| d3362173df |
@@ -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,72 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
|
||||
<linearGradient id="fireGradient" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#1B5E20;stop-opacity:1"></stop>
|
||||
<stop offset="30%" style="stop-color:#FF6F00;stop-opacity:1"></stop>
|
||||
<stop offset="60%" style="stop-color:#FFD600;stop-opacity:1"></stop>
|
||||
<stop offset="100%" style="stop-color:#D32F2F;stop-opacity:1"></stop>
|
||||
</linearGradient>
|
||||
|
||||
|
||||
<linearGradient id="innerFlame" x1="0%" y1="100%" x2="0%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#FFD600;stop-opacity:1"></stop>
|
||||
<stop offset="50%" style="stop-color:#FF8F00;stop-opacity:1"></stop>
|
||||
<stop offset="100%" style="stop-color:#FF5722;stop-opacity:1"></stop>
|
||||
</linearGradient>
|
||||
|
||||
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="8" result="coloredBlur"></feGaussianBlur>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"></feMergeNode>
|
||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||
</feMerge>
|
||||
</filter>
|
||||
|
||||
|
||||
<radialGradient id="sunGlow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" style="stop-color:#FFD600;stop-opacity:0.6"></stop>
|
||||
<stop offset="100%" style="stop-color:#FFD600;stop-opacity:0"></stop>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
|
||||
<circle cx="256" cy="256" r="240" fill="#1a1a2e" opacity="0.9"></circle>
|
||||
|
||||
|
||||
<circle cx="256" cy="280" r="180" fill="url(#sunGlow)" filter="url(#glow)"></circle>
|
||||
|
||||
|
||||
<path d="M256 60
 C280 120 340 160 360 220
 C380 280 360 340 340 380
 C320 420 280 460 256 470
 C232 460 192 420 172 380
 C152 340 132 280 152 220
 C172 160 232 120 256 60Z" fill="url(#fireGradient)" filter="url(#glow)">
|
||||
|
||||
</path>
|
||||
|
||||
|
||||
<path d="M256 140
 C270 180 310 210 320 260
 C330 310 320 350 300 380
 C280 410 268 430 256 440
 C244 430 232 410 212 380
 C192 350 182 310 192 260
 C202 210 242 180 256 140Z" fill="url(#innerFlame)" opacity="0.95">
|
||||
|
||||
</path>
|
||||
|
||||
|
||||
<ellipse cx="256" cy="320" rx="50" ry="80" fill="#FFFDE7" opacity="0.8">
|
||||
|
||||
|
||||
</ellipse>
|
||||
|
||||
|
||||
<circle cx="200" cy="180" r="8" fill="#4CAF50" opacity="0.8">
|
||||
|
||||
|
||||
</circle>
|
||||
<circle cx="312" cy="200" r="6" fill="#4CAF50" opacity="0.7">
|
||||
|
||||
|
||||
</circle>
|
||||
<circle cx="230" cy="150" r="5" fill="#81C784" opacity="0.6">
|
||||
|
||||
</circle>
|
||||
<circle cx="280" cy="165" r="7" fill="#66BB6A" opacity="0.7">
|
||||
|
||||
</circle>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -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==")
|
||||