From 684319983d6d226539d6ef1774efbb960d48f081 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Wed, 22 Oct 2025 14:18:09 +0000 Subject: [PATCH] feat(packaging): Add npm packaging and installer: wrapper, postinstall downloader, publish workflow, and packaging files --- .github/workflows/npm-publish.yml | 183 ++++++++++++++++ .npmignore | 54 +++++ bin/nupst-wrapper.js | 108 +++++++++ changelog.md | 11 + example-action.sh => docs/example-action.sh | 0 npmextra.json | 1 + package.json | 62 ++++++ readme.md | 19 +- scripts/install-binary.js | 231 ++++++++++++++++++++ serve.zone-nupst-5.0.5.tgz | Bin 0 -> 27227 bytes ts/00_commitinfo_data.ts | 12 +- 11 files changed, 673 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/npm-publish.yml create mode 100644 .npmignore create mode 100644 bin/nupst-wrapper.js rename example-action.sh => docs/example-action.sh (100%) create mode 100644 npmextra.json create mode 100644 package.json create mode 100644 scripts/install-binary.js create mode 100644 serve.zone-nupst-5.0.5.tgz diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..abd220b --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,183 @@ +name: Publish to npm + +on: + push: + tags: + - 'v*.*.*' + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g., 5.0.6)' + required: true + type: string + +jobs: + build-and-publish: + runs-on: ubuntu-latest + + steps: + # Checkout the repository + - name: Checkout code + uses: actions/checkout@v4 + + # Setup Deno + - name: Setup Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + + # Setup Node.js for npm publishing + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18.x' + registry-url: 'https://registry.npmjs.org/' + + # Compile binaries for all platforms + - name: Compile binaries + run: | + echo "Compiling binaries for all platforms..." + deno task compile + echo "" + echo "Binary sizes:" + ls -lh dist/binaries/ + + # Update version in package.json if triggered manually + - name: Update version in package.json + if: github.event_name == 'workflow_dispatch' + run: | + VERSION=${{ github.event.inputs.version }} + echo "Updating package.json to version ${VERSION}" + npm version ${VERSION} --no-git-tag-version + + # Extract version from tag if triggered by tag push + - name: Extract version from tag + if: startsWith(github.ref, 'refs/tags/') + run: | + VERSION=${GITHUB_REF#refs/tags/v} + echo "VERSION=${VERSION}" >> $GITHUB_ENV + echo "Extracted version: ${VERSION}" + + # Ensure versions are synchronized + - name: Sync versions + run: | + if [ -n "${VERSION}" ]; then + echo "Syncing version ${VERSION} across files..." + + # Update deno.json + sed -i "s/\"version\": \".*\"/\"version\": \"${VERSION}\"/" deno.json + + # Update package.json + npm version ${VERSION} --no-git-tag-version --allow-same-version + + echo "Updated versions:" + echo "deno.json: $(grep '"version"' deno.json)" + echo "package.json: $(grep '"version"' package.json | head -1)" + fi + + # Generate SHA256 checksums for binaries + - name: Generate checksums + run: | + cd dist/binaries + sha256sum * > SHA256SUMS + echo "Checksums generated:" + cat SHA256SUMS + cd ../.. + + # Create npm package + - name: Create npm package + run: | + echo "Creating npm package..." + npm pack + echo "" + echo "Package created:" + ls -lh *.tgz + + # Test package installation locally + - name: Test local installation + run: | + echo "Testing local package installation..." + PACKAGE_FILE=$(ls *.tgz) + npm install -g ${PACKAGE_FILE} + + echo "" + echo "Testing nupst command:" + nupst --version || echo "Note: Binary execution may fail in CI environment" + + echo "" + echo "Checking installed files:" + npm ls -g @serve.zone/nupst + + # Publish to npm (only on tag push or manual trigger) + - name: Publish to npm + if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + echo "Publishing to npm registry..." + npm publish --access public + + echo "" + echo "✅ Successfully published @serve.zone/nupst to npm!" + echo "" + echo "Package info:" + npm view @serve.zone/nupst + + # Create GitHub Release (only on tag push) + - name: Create GitHub Release + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + files: | + dist/binaries/nupst-* + dist/binaries/SHA256SUMS + *.tgz + generate_release_notes: true + body: | + ## NUPST ${{ env.VERSION }} + + ### Installation + + #### Via npm (recommended) + ```bash + npm install -g @serve.zone/nupst + ``` + + #### Direct download + Download the appropriate binary for your platform from the assets below. + + ### Platform Support + - Linux x64 / ARM64 + - macOS x64 / ARM64 (Apple Silicon) + - Windows x64 + + ### Checksums + SHA256 checksums are available in `SHA256SUMS` file. + + # Verify the published package + verify: + needs: build-and-publish + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' + + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18.x' + + - name: Wait for npm propagation + run: sleep 30 + + - name: Verify npm package + run: | + echo "Verifying published package..." + npm view @serve.zone/nupst + + echo "" + echo "Testing installation from npm:" + npm install -g @serve.zone/nupst + + echo "" + echo "Package installed successfully!" + which nupst || echo "Binary location check skipped" \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..4b0f1eb --- /dev/null +++ b/.npmignore @@ -0,0 +1,54 @@ +# Source code (not needed for binary distribution) +/ts/ +/test/ +mod.ts +*.ts + +# Development files +.git/ +.gitea/ +.claude/ +.serena/ +.nogit/ +.github/ +deno.json +deno.lock +tsconfig.json + +# Scripts not needed for npm +/scripts/compile-all.sh +install.sh +uninstall.sh +example-action.sh + +# Documentation files not needed for npm package +readme.plan.md +readme.hints.md +npm-publish-instructions.md +docs/ + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Keep only the install-binary.js in scripts/ +/scripts/* +!/scripts/install-binary.js + +# Exclude all dist directory (binaries will be downloaded during install) +/dist/ + +# Logs and temporary files +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Other +node_modules/ +.env +.env.* \ No newline at end of file diff --git a/bin/nupst-wrapper.js b/bin/nupst-wrapper.js new file mode 100644 index 0000000..3f3353b --- /dev/null +++ b/bin/nupst-wrapper.js @@ -0,0 +1,108 @@ +#!/usr/bin/env node + +/** + * NUPST npm wrapper + * This script executes the appropriate pre-compiled binary based on the current platform + */ + +import { spawn } from 'child_process'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; +import { existsSync } from 'fs'; +import { platform, arch } from 'os'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +/** + * Get the binary name for the current platform + */ +function getBinaryName() { + const plat = platform(); + const architecture = arch(); + + // Map Node's platform/arch to our binary naming + const platformMap = { + 'darwin': 'macos', + 'linux': 'linux', + 'win32': 'windows' + }; + + const archMap = { + 'x64': 'x64', + 'arm64': 'arm64' + }; + + const mappedPlatform = platformMap[plat]; + const mappedArch = archMap[architecture]; + + if (!mappedPlatform || !mappedArch) { + console.error(`Error: Unsupported platform/architecture: ${plat}/${architecture}`); + console.error('Supported platforms: Linux, macOS, Windows'); + console.error('Supported architectures: x64, arm64'); + process.exit(1); + } + + // Construct binary name + let binaryName = `nupst-${mappedPlatform}-${mappedArch}`; + if (plat === 'win32') { + binaryName += '.exe'; + } + + return binaryName; +} + +/** + * Execute the binary + */ +function executeBinary() { + const binaryName = getBinaryName(); + const binaryPath = join(__dirname, '..', 'dist', 'binaries', binaryName); + + // Check if binary exists + if (!existsSync(binaryPath)) { + console.error(`Error: Binary not found at ${binaryPath}`); + console.error('This might happen if:'); + console.error('1. The postinstall script failed to run'); + console.error('2. The platform is not supported'); + console.error('3. The package was not installed correctly'); + console.error(''); + console.error('Try reinstalling the package:'); + console.error(' npm uninstall -g @serve.zone/nupst'); + console.error(' npm install -g @serve.zone/nupst'); + process.exit(1); + } + + // Spawn the binary with all arguments passed through + const child = spawn(binaryPath, process.argv.slice(2), { + stdio: 'inherit', + shell: false + }); + + // Handle child process events + child.on('error', (err) => { + console.error(`Error executing nupst: ${err.message}`); + process.exit(1); + }); + + child.on('exit', (code, signal) => { + if (signal) { + process.kill(process.pid, signal); + } else { + process.exit(code || 0); + } + }); + + // Forward signals to child process + const signals = ['SIGINT', 'SIGTERM', 'SIGHUP']; + signals.forEach(signal => { + process.on(signal, () => { + if (!child.killed) { + child.kill(signal); + } + }); + }); +} + +// Execute +executeBinary(); \ No newline at end of file diff --git a/changelog.md b/changelog.md index f859b9d..923a2c3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,16 @@ # Changelog +## 2025-10-22 - 5.1.0 - feat(packaging) +Add npm packaging and installer: wrapper, postinstall downloader, publish workflow, and packaging files + +- Add package.json (v5.0.5) and npm packaging metadata to publish @serve.zone/nupst +- Include a small Node.js wrapper (bin/nupst-wrapper.js) to execute platform-specific precompiled binaries +- Add postinstall script (scripts/install-binary.js) that downloads the correct binary for the current platform and sets executable permissions +- Add GitHub Actions workflow (.github/workflows/npm-publish.yml) to build binaries, pack and publish to npm, and create releases +- Add .npmignore to keep source, tests and dev files out of npm package; keep only runtime installer and wrapper +- Move example action script into docs (docs/example-action.sh) and remove the top-level example-action.sh +- Include generated npm package artifact (serve.zone-nupst-5.0.5.tgz) and npmextra.json + ## 2025-10-18 - 4.0.0 - BREAKING CHANGE(core): Complete migration to Deno runtime **MAJOR RELEASE: NUPST v4.0 is a complete rewrite powered by Deno** diff --git a/example-action.sh b/docs/example-action.sh similarity index 100% rename from example-action.sh rename to docs/example-action.sh diff --git a/npmextra.json b/npmextra.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/npmextra.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d2ed159 --- /dev/null +++ b/package.json @@ -0,0 +1,62 @@ +{ + "name": "@serve.zone/nupst", + "version": "5.0.5", + "description": "Network UPS Shutdown Tool - Monitor SNMP-enabled UPS devices and orchestrate graceful system shutdowns during power emergencies", + "keywords": [ + "ups", + "snmp", + "power", + "shutdown", + "monitoring", + "cyberpower", + "apc", + "eaton", + "tripplite", + "liebert", + "vertiv", + "battery", + "backup" + ], + "homepage": "https://code.foss.global/serve.zone/nupst", + "bugs": { + "url": "https://code.foss.global/serve.zone/nupst/issues" + }, + "repository": { + "type": "git", + "url": "git+https://code.foss.global/serve.zone/nupst.git" + }, + "author": "Serve Zone", + "license": "MIT", + "type": "module", + "bin": { + "nupst": "./bin/nupst-wrapper.js" + }, + "scripts": { + "postinstall": "node scripts/install-binary.js", + "prepublishOnly": "echo 'Publishing NUPST binaries to npm...'", + "test": "echo 'Tests are run with Deno: deno task test'" + }, + "files": [ + "bin/", + "scripts/install-binary.js", + "readme.md", + "license", + "changelog.md" + ], + "engines": { + "node": ">=14.0.0" + }, + "os": [ + "darwin", + "linux", + "win32" + ], + "cpu": [ + "x64", + "arm64" + ], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} \ No newline at end of file diff --git a/readme.md b/readme.md index 4302e93..07aa8f5 100644 --- a/readme.md +++ b/readme.md @@ -52,7 +52,24 @@ nupst service status ## 📥 Installation -### Automated Installer (Recommended) +### Via npm (NEW! - Recommended) + +Install NUPST globally using npm: + +```bash +npm install -g @serve.zone/nupst +``` + +**Benefits:** +- Automatic platform detection and binary download +- Downloads only the binary for your platform (~400-500MB) +- Easy updates via `npm update -g @serve.zone/nupst` +- Version management with npm +- Works with Node.js >=14 + +**Note:** The installation will download the appropriate binary from GitHub releases during the postinstall step. + +### Automated Installer Script The installer script handles everything automatically: diff --git a/scripts/install-binary.js b/scripts/install-binary.js new file mode 100644 index 0000000..f495923 --- /dev/null +++ b/scripts/install-binary.js @@ -0,0 +1,231 @@ +#!/usr/bin/env node + +/** + * NUPST npm postinstall script + * Downloads the appropriate binary for the current platform from GitHub releases + */ + +import { platform, arch } from 'os'; +import { existsSync, mkdirSync, writeFileSync, chmodSync, unlinkSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import https from 'https'; +import { pipeline } from 'stream'; +import { promisify } from 'util'; +import { createWriteStream } from 'fs'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const streamPipeline = promisify(pipeline); + +// Configuration +const REPO_BASE = 'https://code.foss.global/serve.zone/nupst'; +const VERSION = process.env.npm_package_version || '5.0.5'; + +function getBinaryInfo() { + const plat = platform(); + const architecture = arch(); + + const platformMap = { + 'darwin': 'macos', + 'linux': 'linux', + 'win32': 'windows' + }; + + const archMap = { + 'x64': 'x64', + 'arm64': 'arm64' + }; + + const mappedPlatform = platformMap[plat]; + const mappedArch = archMap[architecture]; + + if (!mappedPlatform || !mappedArch) { + return { supported: false, platform: plat, arch: architecture }; + } + + let binaryName = `nupst-${mappedPlatform}-${mappedArch}`; + if (plat === 'win32') { + binaryName += '.exe'; + } + + return { + supported: true, + platform: mappedPlatform, + arch: mappedArch, + binaryName, + originalPlatform: plat + }; +} + +function downloadFile(url, destination) { + return new Promise((resolve, reject) => { + console.log(`Downloading from: ${url}`); + + // Follow redirects + const download = (url, redirectCount = 0) => { + if (redirectCount > 5) { + reject(new Error('Too many redirects')); + return; + } + + https.get(url, (response) => { + if (response.statusCode === 301 || response.statusCode === 302) { + console.log(`Following redirect to: ${response.headers.location}`); + download(response.headers.location, redirectCount + 1); + return; + } + + if (response.statusCode !== 200) { + reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`)); + return; + } + + const totalSize = parseInt(response.headers['content-length'], 10); + let downloadedSize = 0; + let lastProgress = 0; + + response.on('data', (chunk) => { + downloadedSize += chunk.length; + const progress = Math.round((downloadedSize / totalSize) * 100); + + // Only log every 10% to reduce noise + if (progress >= lastProgress + 10) { + console.log(`Download progress: ${progress}%`); + lastProgress = progress; + } + }); + + const file = createWriteStream(destination); + + pipeline(response, file, (err) => { + if (err) { + reject(err); + } else { + console.log('Download complete!'); + resolve(); + } + }); + }).on('error', reject); + }; + + download(url); + }); +} + +async function main() { + console.log('==========================================='); + console.log(' NUPST - Binary Installation'); + console.log('==========================================='); + console.log(''); + + const binaryInfo = getBinaryInfo(); + + if (!binaryInfo.supported) { + console.error(`❌ Error: Unsupported platform/architecture: ${binaryInfo.platform}/${binaryInfo.arch}`); + console.error(''); + console.error('Supported platforms:'); + console.error(' • Linux (x64, arm64)'); + console.error(' • macOS (x64, arm64)'); + console.error(' • Windows (x64)'); + console.error(''); + console.error('If you believe your platform should be supported, please file an issue:'); + console.error(' https://code.foss.global/serve.zone/nupst/issues'); + process.exit(1); + } + + console.log(`Platform: ${binaryInfo.platform} (${binaryInfo.originalPlatform})`); + console.log(`Architecture: ${binaryInfo.arch}`); + console.log(`Binary: ${binaryInfo.binaryName}`); + console.log(`Version: ${VERSION}`); + console.log(''); + + // Create dist/binaries directory if it doesn't exist + const binariesDir = join(__dirname, '..', 'dist', 'binaries'); + if (!existsSync(binariesDir)) { + console.log('Creating binaries directory...'); + mkdirSync(binariesDir, { recursive: true }); + } + + const binaryPath = join(binariesDir, binaryInfo.binaryName); + + // Check if binary already exists and skip download + if (existsSync(binaryPath)) { + console.log('✓ Binary already exists, skipping download'); + } else { + // Construct download URL + // Try release URL first, fall back to raw branch if needed + const releaseUrl = `${REPO_BASE}/releases/download/v${VERSION}/${binaryInfo.binaryName}`; + const fallbackUrl = `${REPO_BASE}/raw/branch/main/dist/binaries/${binaryInfo.binaryName}`; + + console.log('Downloading platform-specific binary...'); + console.log('This may take a moment depending on your connection speed.'); + console.log(''); + + try { + // Try downloading from release + await downloadFile(releaseUrl, binaryPath); + } catch (err) { + console.log(`Release download failed: ${err.message}`); + console.log('Trying fallback URL...'); + + try { + // Try fallback URL + await downloadFile(fallbackUrl, binaryPath); + } catch (fallbackErr) { + console.error(`❌ Error: Failed to download binary`); + console.error(` Primary URL: ${releaseUrl}`); + console.error(` Fallback URL: ${fallbackUrl}`); + console.error(''); + console.error('This might be because:'); + console.error('1. The release has not been created yet'); + console.error('2. Network connectivity issues'); + console.error('3. The version specified does not exist'); + console.error(''); + console.error('You can try:'); + console.error('1. Installing from source: https://code.foss.global/serve.zone/nupst'); + console.error('2. Downloading the binary manually from the releases page'); + console.error('3. Using the install script: curl -sSL https://code.foss.global/serve.zone/nupst/raw/branch/main/install.sh | sudo bash'); + + // Clean up partial download + if (existsSync(binaryPath)) { + unlinkSync(binaryPath); + } + + process.exit(1); + } + } + + console.log(`✓ Binary downloaded successfully`); + } + + // On Unix-like systems, ensure the binary is executable + if (binaryInfo.originalPlatform !== 'win32') { + try { + console.log('Setting executable permissions...'); + chmodSync(binaryPath, 0o755); + console.log('✓ Binary permissions updated'); + } catch (err) { + console.error(`⚠️ Warning: Could not set executable permissions: ${err.message}`); + console.error(' You may need to manually run:'); + console.error(` chmod +x ${binaryPath}`); + } + } + + console.log(''); + console.log('✅ NUPST installation completed successfully!'); + console.log(''); + console.log('You can now use NUPST by running:'); + console.log(' nupst --help'); + console.log(''); + console.log('For initial setup, run:'); + console.log(' sudo nupst ups add'); + console.log(''); + console.log('==========================================='); +} + +// Run the installation +main().catch(err => { + console.error(`❌ Installation failed: ${err.message}`); + process.exit(1); +}); \ No newline at end of file diff --git a/serve.zone-nupst-5.0.5.tgz b/serve.zone-nupst-5.0.5.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2ec8a2a673afb79dcbd88549b9cdfe245b7f4bbb GIT binary patch literal 27227 zcmV($K;yq3iwFP!00002|LncnawN%>AlA?M6;3osWTNwm8_8@|izon9g<%1OMWVXZ z(`r=)B0MvrD>5P(9+3dpRbw>SY93@`Hrg3#Wg1bMU73xf7rl>6f73rO^9izZ@moYB zs+w%}?Ci21$U=r+j~_pN?q?N*uYB z7BZJN_hOz0X(6MonB`K6Y$n1*kk4gT6qyLpd$E$a%2JWt6hWNE>0AUN%vSfUY$l3D ztc1#D#chyF5u}j_l*+<5C}bp}EG(BYErJ5(o5hJ#qFpSc*qU&Utqx8S$slRPseo_# zlemqGMOGFfm#WC)5XR_=I1Q6Bf;H&RNxY1C2I!8UP^}^prIKA-XICtN7Uc7=i;5wcy_#~{mvl1b7EvsEmWz`felVH;Tb3IJL#h!p&CyU3RQ zPGZ%Xm3bPgg~UElCRBzqekH>Keu3t*EJ?Ck*jkvSQ4Bj$<5mlfJ-Er%68AzUon}QG z61edIR`yi+lUf8xB5ouDOhzJ3Mc`~Fhv`)jq(vMgVwL4MUv-=PR_prNshGTYa{bxS z)u}j}h|8-NpPrqZo`|iZi8z~Vb;W0A*Uw(OycVAwU0oerTz@WJJP}71pNn6fU7U2q z>3@BBbvl`d7gw#b^ULRFrzc%;c5(dt<;mH_Q}OuawYYe3EuNp9pIx7xi0c;uCgh>c zPA4$f`RUd1v!jdaqsM2@&#pi3ww|0_U%;48UR;SIad~ugeRllv`O%fQe0g>GVsa{u zE>6V7i;J_1Cs$_|PfyQJFRuII>_S|;5T~D>UR;aGv!mzFVXD^A%j;(^u3&xQ_{HVt zS7%S3U5jTgo}ZjvO~m6<@%-%Q@$*xf>f&>8{QT(byem$Q&X1m+Vy_q1&rYve(2Q0m zK6`cwKf!!Q7vkvn`s~F8Y~%RF#r4(E@pV^RzqqifUHyZ@6vz5n0Ge;@u}P^x@z6Q={2 zu0@(fveg>w?6ky=xOjOvxfbbaDOQ;(7&8Q63Vc7wZqp@#9QjK zovH1QZ6kRXt3plg)37U+ucJ7pf8OSCA)myFq(8&OGK=V+Wtzn4YxuVrapsNqRTii4 zgy(6nl%~xpC>Cxj;5uGjJ-^N_gTk(@%oE#aQ4}l1AMoF9|5dz_Nu1gVRFTVI={3o- zWvt@ay&b-Y6SqZ}%b<{-!CoiW-QQ9x%u-c|SFd2PxSL1*y4y5Te<_QgU*<{YW79?N z|B;47+YV!z(V8#yRvy_UxAp!yAGcbAfjG|6Sv)UuWT?3J)#>GnSC5Y-r{WOyVpk%(5{ap;W3b({(>xEnhK3d$pE1 z@-A=Qi0y;^uzvtBnUyK>d}1z($B3k7=`3q^#Mdn$Xk*o7URUUI9<0P898!qwD9CT)bbBnemqD1R?JoVC#A$g4ztTVW*V{PV+l60m<21@{ z)pkpWx6TYOr8oNB;XX8n|KlNod`Z92KPm$(gVjn#m--?;a@YI?{QJv~D{YV9`aRM! zf8oO5OB^_!iS`dF_vmgQHSTAFY|DsDj_5uDD zR`wQVOk}|%=7O+vipbIX;n)5$-kM)v6>p~>^J?jcA3f6W*6VY|{OFO`?#ny5Z6?)= z$Cl0pioBErdbrYygk*F9pnZeDP!@&ugg4n zJ0)xvVlWU-P_huYjN)8|g)*ZQW^v+?ptb2n$61-e+pv?< zJkRp>_H~wtB}kEG`t1&0RNVo#uK#}9;(s8S>Ca_Bs{&}OvQ$ZLHF`btsjrHlDAjQm zNyM1F;Ruj>^XFZ6;T5nxF~XQ$wkR@ytr>11gGlD8pJXAPBtqPdrUBdD*r5vRkHp9u z!H0|ko6Xz)!J|iFcR2L-RXdXDlORsCBucCFc4h~BTmSi7DizG-+iB;07fXm%WJQon z;$P$I6XZ&sr9}-kUu=h2TFA8MB{H2Ci|sGFVl;Fn0&=3)EF&In=zmWFRb1xTJeNw* zR|na6zASBTM?n#6cSSo~l^RHA%laZs?nRQ#gVpYQ3b4S=r($55y0ITfe6?V@uiA6D1!2&b9$gm@QX{EaVTiEAU_n-R^9Lfc_UEoP^uAZ3sGj zeajm*^n%zs>i)LX3KVd_<}q0Yaq93uX0?yr|G#a+CYj!X1&E#?PD`AT1c67=(Ebmb zZQEtUZVYQD9#vUA%d^;4ee<4oD$k)L;`Yt|_E$vb#^Pma8k-k?;4&(J@6OP=&D(+Z zbATKPpO-(|_IC5nliIv$yrHQO-~8tP5YK_N6z#jiePF1Thx?ren=ga##pL~)e?}}S zcG%Q%!_Q|kai5jqMkcXbOZazgg+sN-$|M3cqX9JVhae==)eL~@{&D)|?SJ#T zztt4hAH0hruK+T7bOMI=PLX`Bio6UR&i&=pbN%%?zt>6{_*u;2Toqjq+!ApUgs+j4 z2yVqq9;D#{R-Z~4$;c51xckdI0b%WjUmM~7?Le!4272nj+G6KmBXWQ2j1DV-g)|Ny z+zx1A19;^I9xgvLd~4&|4e->Ly{A?(jAwDk$Lrvm*Y_F>D$C$r6v1mL0~l^|`&xd_Y~^;qhG#x6)mR%RFAfO?`RwoWvsy`nL~ud*W;t`Z?QrpyT%T z=1+u?X!yI4;zou+sqBl|&|%aUU^Uc~U=b)V3yK>lQ|7)SaW9JpM%e9(3t8M|`D=sb z>$tcV{u|uTagV0f8a=*qXbYCK3M6>jzZW1r&q@&nDWKUqw$J>lxmYSI^H7f8pWApC zHZFYutvNp!%OEX-B)O+K3ws1gtb)0G7;G<<9?e(Zk3lD&h@P4}{}#+rm6-CFeYFs8 zges#<+yrW|?TTCQx+F44MY#eA9qq!-Q|Cxo4?cMGpVi*C8?LgaA86p@tRVHaPTi-@ zD{94Sp~?`3nUzVx_xdfr>n~FAGL7$gN&H$0b*~D!R9zuc1*%Ht_+lmGoeaw&xJe{G z?hkN>sBNjJC0zpOkOj#r$fC|61ZQS^Rud`rpxEgFrk z%G4!XWIG~FMIVt4a8B!M8lGua+wdSqQX>^_J0JfSXa5HSFd0#9b1+fl-;L@2J^cR; z_ICHG_W!-z-Ql0?|NrUO{~0IXN8}rciD1et>-PUuE_*=zkVEE&@Fq|)66{b{^$4gK z|J5qEO^rD_T*OKAim&OnG{b)_jM4!l_l*{P%YJNd40y;N;g4u)*Jtrm7A{wTT{gK_ zATaBeZjfyMa7*`KAiw~9kwtP_`3g{YdO?P@YmC$RpN!-0X&kRSVc$mE3?D#O;{Lu# zx7?&eCPi_V6u@1r*nOuK+MP~~h1DCsOP%VeoE(h)7dGwNHpGL_%{wI`=iEO3elZcG zJLcUlZy%Lw`0)eU#rIH@cEx+B8i}TD(x^6T6n*St0RTAl8JHeLP&Bj3o0SU-IHXj> zvrXE1)+^Lq%$R9I^ zGPd5qKs*c5D3LrF4-AIv6p9c)3gL4VQ} zDxRl7;!F>hPyeU|WIgQbI7!<2k5wF*?pnI`?8)9@U^aM3hOV$`K=vfdZ-YGI;V^>0 z1D`oT-QbbGkQ=Isfz7%gb$i`M7a^mgT2GxZrKst?C74DSzsU zcFoi02Lw6Tx;3|wibDV1_Ml+2Hm(JGFwlg#RUyFt<;ZT|rToYL-~UQw>GvG}wSO?G z%73GSgFnfC-^+h+{kDMD*cywi+Uv0elzEHw?$81qY3bKU(lgCpv}XV1Y1+dsqCY#y84 z@jUam_~^ao>O+4sQBW3(EQdiR&`$h$mJ%qpIF1fJou6IPZ+Z#KEGiRjcN3>}jRcXg z*y=ZGxa_;PD>g-;%h_WQ&B&@L`>Rz|YFohdEv^dBg@*$Ki` zX}jGW?wd;p!lrG;pr>d9yP5!X@30Sf6~W_2GwNces1d8x4D-pNgKq%M1RSSEon6 zgg8G)sEs*qc4wlZ4JOQyP;-zf0c6QE>{_Mq-xH^4)dNes5GyPld z_lF>>2gXPya*1JE^t6N8_#TJa*@0-~SQ*EIS(!!w27rTwkMLH}u-nR4T%w8=G?o3Y zRFCw}yg>S^+p@9R5M$n;w~nRilg=AgyR&nX%iuM}Q{&7#I}ih1$b5-m&Yt}*NFzG| z`sUx11;|}C7>eW9aiQzI{vA^KogFk3B4~mlzDeZHP9IL^L-AP8PN${-z@DM&$Ku49 zaSYJwiJhG*xy;rw+SwUvc<3WyA0(NE_oAm&{+UR!bS`s|%U_jo4)CR3)xj`0kd-Lz zRz=&6!DN|$Z|G{LI@HuW&{J)1(6NTqmT0H!tHsn^yk{q@^f*cqg9o`x&{u^s2e^Vy zR}_klhyPrqt7R|A!q@%#V41+Xk*=2VuE+yyKzpF0zP3Ex8sbw27!?mK6(2m2mu{DAKqP zqw@^3gek~SPaACW$`Um-JIR;R4qd(rc`KgC0M*?9PWPsXAA5(-V7zQRbeQ?TQNOo@ z=&`T%-A@l+9qv0n99^CB-{hT54Mh7aEo9PhK43%9K3c63DJF3ehk&2(UHfVC*gbJn z7TFT-04w~AL(w`8AS_n~;s}-7aNj4-j&=_YMF`4eRW88`3mhJqd}TRm3QZ7iuVQ!S zKwla7ldv0!m|7R2X{#sxT;`d*(*UcUc_5PeDtc>xwtC_?&y?yJEil2{Spv90feJB; zBwQc73}=Jm6AcS{WZw1>-DU|ukI*G8;j!Nf7orH} z%62iHqyCy1tOiHGZmRt_d*b?jCFwvgZ%P`8Bu-J88K>g-%*42~dg5^jT0ig1z-EC_ zK&w(MG+8)XuJUXxQQOOq0CZo(GRG%13=&bl^LP&}HGqBj_kSvSV#1_g8Kl7+XAo_= z`j{(){sCNA0sohQ1Bx_%LUdRcB9~Oh4qmPl3~V(B-q=ADb%l)L0tSL7pevFXJeEOF zaz>fwS-FC7p29!gX9%Y=JrfYCa0^46#A=lU_x|H5aS>e=a@FdICxL=}8tW8?=;#d=IJs!vuT;1Yc_ zA(B#?pK^E-#PpBnqA7Yx^m?LqKW**x!F?{Tu%$sErUO}oJgUPKO@R?!gDT-3P#}q}WxC=Zz@16r% zafcv^#O4m{H>n{T#fA7Hj=tR3*0j)ol_)I#JdQru(1RWAwT#V;Nq|Y zaLefRurKV1EP^;u`d-F`cor)V@tD3$quF7wzaLE-l`2TFHu0r!#~mV1j5RW1Ka;aC z+`oY_pPz|bG9TfL*(Aq7&x4;0_X7D7=((A%W@R$84-WT#79RX;IwroMvhsAb9OE)U zEj%{S8##78nC;K@WH<%cnVgVo$|QVsk=(LO%0OfJek?|#-$$7DMR|0nA*u{wzSb}R z$A^;RSnO>)f_519@0Xb70y%X08V^?3BfnlyL_bP1qBk0LclUQeGyu^2Z0|?*E652}&ma_J4nT?4 z5xq2&i3#MRU61;AqJ2|>Lk4F5BjKeBDkD$#rTHq@JiKM7DjKJ<=z$sT7km9(P%YL+ zLg^4O#^6Z@pHNa--7ssX#^MOo`ZA&(K5BvbDU#!v>e3g_gS0DT5%yW8e=Jiuiwjj} z?LcY3e5otaf(pSz@v~T|Lp_?6IpQPXI0WgPd<45}2~XO^IZ+074}z3-rel_)*3B@k za8V$!W?@;}ogK{`KhbP43^MUnyH(08YX~^_o*`+XD)|}YIk7?oZ@DS}X`{9) zexM4jqM<W4DOYSsplwl?8xYi}Dz8SSCs9rdi~N z?UqVbggDg~6Cj8|B8o+jzE(OXEW*5igmI|U($Vul+LCvnOcGFmXrLC00DPSzNjYKd z)E_wAUZ?cSTRk;=6X3#Dj9BdJA;q%R8+Ws~lVvPQns2 zMS_tqgg?L{Z-{n;SyC<`XH4@I?%DH`wj74EZza2!1AOnFAiv zJZ31tnSxe>0L;Gw_mIv3legJw6o@ofyHkogMxQ=eX{+oF)VxqHBpJtfE{X$Wm=|s*! zDd7SKPze^U!GhaeWx%h@5to%gP`8=J!V8irl0d1ZLmF1FZxSz83FL}|jh~%}xlDNn z7%>QpvUWy>S0Igo6q8FWSH+Vof0@R=D&@9Aqw62_aQX45ihP96Wm>|%8|PcfVv$80+pGoz)xHCE*pdHO6`9^2WKt+`FxWID z{YdUvp+<{QeO_F<1EcaajnEPR@C8o9gHZ{A>7aT=zNU~!^VgDBZ_rzuov42E(QLGs zw^~P0B$j1T#61{7GZI#zWADeuEXlzEbgRm_w3-FI$n;<>93E^yu74e#6i<;w^emsj zlLSLZcTN6G7ugIy8ck2-bWkxtt}C zZH%`oR6@0Ei`vXUYh4YemS(G2i|D(W&w zs^HY%Z-OUM@fmcgFhZ6+17J^}azR{__>dcLcYRnBcr>d*cfbKZKWB;=gszAJxy(}3 zC=rj*C52&^^>&fG6IR|nW23TUG_G>NhG;|@rWzS|M*wXWJG30i1`|~4%Da^e3$_-Z z+mb5!j41^Ni9iS$&E;SSp^-)h#7t%i4c;RtDo3o%?Kog;o?d=+VeMMN30URTj0O9Q z1q?%Ac;Hco#4|~zQBy=Q++D=>DnsX~rWkY^k-@)uun!O!Fn=bVLk47$UPj`4P-v70 zP&MGGnTtV@kTl5}0C;`y)!?87G=Az4^*!B$enSQekvIzwha^|fZ!>UcjOw%F9p^HBa2XZ_G@$< zmv9rLDeOzX-|*Rmh4W~0TkrIJAX-Dvm7KwKXz)1Eqr|B&#?CpZ5&79*kwB-Ub8t^D z*521HuD%VdE7b_`r2av~yIX{BT#bNoz?E?VsB`xAmH}#0%z-0WW+b z4bmLEHA}UQ;dag-=MkAyHo3x@>x}e-y1N$(!%W~zNgx6`kdjWPLU(6&ssLX?d;)ht zJgC?(&h$g4UwX(_zhQN(`o@(t@BOg8ca25(la3ZgO$>N`JMKdg%nLm@->+0vKY(f$ zZC0YVe*5mDa*NAWfrY}2jAqu;>W=|5W*U-rZGJ*hWfkBJjX-r4#3G0jTMyF_lLq>d zpjZGVYu!!=di`>>QCo3&bp6b8J=uUv2OiTDQ3*#@x4vvVGTF?*NbX*I*Hnll>vD|9 z2T}rR^}L8dLO>=M92H@u$Vzj*JK>t@#f_Y0xpY7m7k%{Vm89h3ZTOvV85tzDCoO7y zhhEAdKoc4D%9TFHKBHkk;52CzK)+Z9SiO+#A2^AbAXYX{1o2nk${`=vUOcO1GQ;E+ zY6EYzS3!@L%~wn>*k7Krf0Pb4z|c9>S(dlhz~XcU)lQxzo)!m;P=J@(kXXkiz}8u? z&T=OjhVt7wYh?4kQ4M%ZyCxC`_PnK zbD9ko8b?JHDHvc$KX>gc2||Bmx@lQ9X}Vq@Ja?2Vlnr{Y4#QIbU3s2aZ;|RkEdz>D z!m7-{wxl+aBfI)dh_Hba8DpS68F+qsJr7l9NiatfgiD`b#m;RpH^RpS-wdJ0WdXpw zG(|?&qsL=b;OpH%;zE7>e1FNQnn^>C(9zT3`sv_FQr0X@Ay>+A-g0CG|B~YS7bsO9 zRYL`t?mB!A4An8N-ww&C;*e&;x-Mp0V+J8F2tw<<3YXS$HMGXj&0(2eOsLGR8$H>k z2qgIZ1VgnuhW|5p-`V9zn6;uSLPO3nf+M^khMpsliR9Y#b)qHtZFQ1XZ0`S5QtA36MX${)j~1# znicp;Npc1^HX;v?4j7LN0Zo@H)&`zJUJBtF8gK3iacu?-7Uo}^ozVHC3J3Kpl$pXT zEzDBGHaUl2=ctVbZ^Wp-2gJR<-yii4_V)2#`~BfIz3-f1OXFY$*`4KpXEvIdzF`yW z8W7Fw+||WW$d`nQ$B0qOIK?!TtS1oqc$z_3@qaLZT9i?kLX&hBUJjVmiaMgOKDKDr z|2XyR$t>~ez45s0oNzX~qVd}utCiPJ6HHHrHr5V884d`2#(~kwovyRj*64;kjA0)V zTl9!jg942_@0wS{;sF#27E?oe7kqQO_bZQ2BLl}OMC9| zf!fC!AN3oqH^Z6(XPY>>MjA&LATJ{oN7D4z0BfxSGMwqlcf782$(A>$SkqpcerOeY z*qG=@6L70lH*gT=w^Ra$SC@{p7&wk3}4}c)CiPF+pF-9={nA{ zl#&bb4Tif$Ta&q%mvIEcqHrq2O@?j)51+ETOc&V36JPwQj3Gm&5mo)ml1l)oOl^`u zrES6*ApCN-gb09s+@ozgn9F9^@4px9QG@Km{h^&%N?IeCCszLn0d79gF;+$`*;9T> zK6s#r1&CNic{hmUdQiCDrE!Wb3oxkm{0Jr(9Cpa9Q7Bh9b}a{QzUxNr zeXm>3u1-xSv$uhm{u@LP9iC}|Vz3 ziOZI0_ergvb_fx983bXN<=`Nj(EEg9Mbqq}d3JAna4XvS- zyTc=k#H3IH23P(b-PnlgMjgeP^=_-}!}E}=j?j3hS{8`1P+8Pw)a97)f^zGE=W0R` z1mHZs4D#28^jg(aY+5QNQa7lB!IL-r$QNa~f`|Y=teu#o2&sq?WeV2Y0w+b6EzB{d ziIEZz{R_Exm%y2#Hbs$b>gJxkN^p+my&LA;Wg>u;G}l$j<(2{4^wN3me9lu>dFin5 ztmxejdN@9e)>{my1_!G$K(WMPL^`jtus~AXCnffxUG$rQG}v`R-K2DEdQZJq+Ys$$ z-G~>8u6Mwe1%B(X4kIULbRE1|bilYK4tcDf(q6+U4OrNUBG5(nuOYyz{(fko)*hu+ zuExeZ$nm@=?68awr>*1XXI0G;ISU&hN%?Q9uPA|}gCs3`lA$Y0#E|8Ng$+ytgHz)H zHp(ziGcPN&>y}p8YH7k3C0v#Z%;^3uDd$Kgve`^0R4~R)PgHe;J6|!#*j_)j-I`<^ zMPi#o$8Apl(8(~kEH>lUFp$R6VCYab%-p=Z^|Bu!!YVsaQ?a^zJo_>06K6`lDFtF#qzz(orW>&5d zT-lC*%%q@qh>3-a*UmbXyX?AH2YHMsrL@CDv_|tPGmZ;~(Fr^IiMT?Rox2BWwLM8^*jSDnothK6YbIHNOXon41(e7!+ zaR_zr)-lbT2t@nc>cBDOdAIN;wV+`$t&_vL0x{chFu|CIVqz3xp5NNoa4Dt@!YK?T z$f`6BL^i@x6)a^T^Dd=pAR_?t0(iBsE!1J4U3HB}jJhvfp3Xb9Q8-` zQZY8x?WU~t_Cq63%M{;{a+-jg%Oa2EI!GEkKwC9o-e|+!{0AFUU@TQ43Yij;MA*6% z4+2MrAgw(iuJtF9v!F~08fcM`{bYOev)%sa@TU+EI2>-X79Es^+a)8#Hfslqx_@Fz zSnW+klj7;Oav3BPwF-_Y>sa1uO=`tcH%=Kq)~nQz3FDPR&fps&eVi&zFkz-8{+TR7 zOkix!Yy(Qz7tsbiY5C2~S*Fjz1iDaohfV?oY<`wfWGy@(!nq;{1;?LJkl&+89%a?V z>;|cYHZa*hI1fOM(k@{TzE1lK+VWMFVv@Bpc8bIaWAg^jWx-{%yEF|1v}v!>xh&}4 zF0J7FRi7Hut!6S?!Znry2k5wN^~9-yXwFzI#7#{0WAq5Ov;%Z(w@`kSq6N`S3H}9y z&3(NqfOb5dDvs8C6?MFZWsaJ%l{Fmx&lyLHa#PLueP+FlnqdA@H_F;o;9%k+U+pBkJQ!SMtxjLy(dK9Y69xgSBvwa5 zWd~)Cp}H}db-$iv$eOV$wXQ)zWks~s1$#-WV)Zy*)<){AidfDWMSj2Z%UxVJ{uAWA zWf)6Vv~Ww3CFnU03kqO$(8sZT^O_~J6nWCmNTNSLJv$k~<(2YlS&c;5i z8?@t-tBonb$621RV-Gu$w@u?5elsF*4JgE6m^Mz%ipC~Jj;*n7Mi1-61dgbU@T-z~ ztso&C6*?eUCXKbSB(tKd%JG=4u%ppe^P|M5m=I1rkk@`K=~vTlvxMY(f7qM>HI>bbm#cAx5?g3}7$Z=do_%;CPXUi)4tyW{ zd5OJ_iD4#J8uRNVz8)PHeMmTSgLH}QGic+#Fl1Q*`Hr}p3S3z`0D3c`88#@;xe*Vb zEP78PXsW@&u6m9V?s33e9J)bNfLp4JEmdQaHZCN{>neMA7!lky473rm4!q}y^Fmr# zljPeD_nmk^V4 z9WUi|9KKdnubyKlwR4HiL~dleRlfUF(4}*@`cOjhr)&c3s{EYy1{m^=T%-3en!qu-tnrYe7glU+E(DY2QY*hmmiTuF|Js6(`WbS$~{=s7|Jd=5x!FdD3Bl=Ys6&N_1}$50Yx`WbR`j4<%LdQ+P5 zarX;#ewsmQm@HV!;N|6n1uMfOKwfmmToRV5$d;heQm&DN{U4)vhT>yEo^!d5LACmK z;wx;*ZZ`(0ODL&2-(F(l4-PNlBs#Sip4NZ)D6UCl3tMF_Bvm<$~c zZcE6t&@NS}lw>9IIF`Cn3Ct4rP?4C7H8gasZZ*QpFI25h7~W>HG-5<#YL!CG+7zt< zvKpYui*O?s!8*?JrjtMPr0FiJMl~61DsmYt6Z8Wv@~i}N$0Ez>;q7!%HJ?#^NH*2h z*zjZQR}6*D2CF-)+}C8XE+;{0Gqn{^(>l4+qMXjmP;4nhb^$cik?5;F_l z8c!D*E3wz&wDfvjq>+tjedzcM4|9yJXwZdO`N~=_VHqvONI2$#(Jhqh=M2wH0x1wF z1vWVwQkb^}gEj>4Dvd-|bE~bNk#&pUeRo=|$K?{@=OE(6WkKASZ}PXJoV}p^Y987~ zNzf&=Y9b0r)Q9e-j&x)2h@&B(DuGLb0Z}BzHyZ>$JE8TqWu0=#;_*PEIEV!CV%t{?e0Ik5LryTf|Ft zUsUHA(v-#^hp}F{f{&K^)_YAyvsX!!#*tFi-G=Zf%OIB#rW*C=rI$;ANG9Z}OvzSi zLvIZ%-S{~(9A>+5h+a)4VompblcB>&6j3v2lX@FWeCsR*-5fKo&g^bTUB4MJ6`BRl zhquZiqfscU97#$wYp-Tu)-2fD^P|LlzC={XIKlhS#JoPb&m;kGJ`IvMY6?zw4swGd z0DDz%s#F;41!;R7smG%Pzx~1+8O$Ea$ZX~{TUYCYQj!9x%_@G z1833X-Vc&Bi?l{V$k##H#Er~CVvb4oa?g(l3QG7*SKn?1rq&~1)1Ibqe;WR1(i*cpoNpt)Hw)Sm1x`BU5ZM+vdT_6T93R1(wAj(Dg!mXl@`lRW(sVPW!<@ zNtiq+NB05tN#|fCe83yf+lDu|K8GrXbgmd=gN&*tN7fVjG-aIWY6Z5J8_crSuJYAt zS{u)B-*Le(E~onZTwa!3rKF5dNNx(9arC6FqQYFVO1iyrxdcLOQ_hgbYK;cFdm--1 z2iM%mn`1pd(KX3ujcfW`?PS8zb5*Bej=^l-xWF@;Qzwt^)?IM#Gq|;a2(1O3R?8M5 zX>PBzBgVquW`(#1e)u&!){ymp<1>>03MH6Ief795u;(Df`U>Q&Jj)8p{@JoYU}136 zZaML?dJspjVsb+U-XILioQ#>(1p-4O3x9Ioma4gX^BQH#2L~rsRC6QoOcgiKukNgA( zexjZF)2bVd$r|f=lbSho5S5I)I-yB`58y&#anx6ZvbVkK=PEY9#1#8I*m+N7B}7-7 zuv;%b#p9P}&re=Gxq5N_YVzXc)$yrLzXR6!fZ|)IkQgQHr$A8}5sWVUe%99I=1cOa zH98o9<>21tO#&E~m~z2@fU>cyfqB(%aQxyc;XO&A09&BCMT#X+IJFC8NMWDi#sk!} z#5I{&na5;dc1x5uMC)@&l2|5DE&AF4b!8laCu@luaM5U3Q-@pGT8@7k&2b_pZLLa-vY1x>Z+au&LxM=MXEXuvqgu^QyFf^R^cO z>UoQ^RTMoJ&@xOJYSHhmDTvMS)Mt$Znd_bLUtT>ogXR@??yCF0mq5$JK>A5U_!4w& zEF_(=N$um901aHI-mpLNG6SM=2ED+dpa|Tw%i2uo zsdzx}o3NOK$xRy*JsFHhEXq4@r&S(adK z!(I>FoDqm&Tm`?df%}l!Rn{cEu(i!}&TKNk z^E_?>3R3RlN!0bzxBa?hW|0fvEP1w5vPUfFQe~tj^S{4xW6x&e&_$h z2YUyH!_nyQaJVl%7>;&F2P5&p@cRHj3CgVxhQse*leiK6|NlDA|2sbU_c;H9-NS?Z z>iLg`2YY`y|L^0!55+fs_aE5@A4^DLA!9W7Xfp>C!-JA}zm#$%?x9w+E{&#wnY7|Q zWq_JPs>Q4<`a3&3Yzzrwl6bx-ZYBJ07xOhGvIi;-X)>Wx9vQIUFli^{Cb>%X$PXZ@ z6`;v-@VGB>4Dv93*i* zTw}5>D^YX>1@XNkN+B^#ICO6Q!tLkJlC%BUp4 zod$Y&Gi(Z2b+ z|K*?l@vpfyEZdE-S|<5qvhPx$OUHU!)~+>>t}NDpL69|u%L^%rB70bN}6MjHO@46mhjp z)W^%sbr?LqN*L;I{|9kpVrkBec?QOMj#efwIe#V4AaGJ15SDpaG;sX?Q%rzD9UBTo z)iRy(u4GlgV||!tee;{&k}jc=uZySp_x}JGU9SiFSd+wH9mI())+i`hWc%)Lze|-q zJ6*91!WR?K2B0QgakK(hPU0jEv$R7;!?i(?RN+BT0v>c0qbi#(QrW|DPG_F-$d-e&ssD15%;~b zbG-;40byMHU}r~sZaD<9_uQh75ITDG#XtZ3|N0;Q=^y`^5BzWcp>nJM!%wLKv}j+P ze)a>PbUG~;RFiIgS$z578G->M- z;~gl4`Jc+BBj-kNGw{sKSna>q9}as5L#Sj6CV)WQ8*%`a$0@9uRq;)Wo`Mq$a}-tQ z1qf#Cxdc<&jy`!b+J~K9WQ82>?1<}XHuBpTBzAq1s&27*Gr$5ojf-dHjjcY+oE0&E zpgHD5d4*i{xlXPrS$t-c@)I^&UVE7kS;0g85K2h!&nz(L*t^Nj5&yC8^v=#_z>8u= zKq`qo>az_%RS&iyhi<2}+jp?TvFz0uyZhE&UqcA(N>eB1kB=TMCFXNi#zGwwV(J^H zb>oA+(1re#RqOll=S;Np0lZkTO4SlBecz1vm(}IpJ?Q@%EU{>En1PZ^^mAZrgq$zThy1FYOjNL$f30It)hxlO^y! zOGioiA*rj9)4=aev6_;*Pl{}%^#t$HnV3LfI#+T=*IH*Q;k3?H4>Z;ZrK#~%Jt1`? zt7rxXx@gj6d#-Vnewe>Nc>R*t$?Aq#2F$U*FemaIC&SFOaR2)d`Esr@Ze{(p;~Us< zx+tzGT|9KpJV&H|pY-o>u65isT!!LjVKPtf0H*PSbF9P@- zP-O7^rX4U4oP9D>>J5F6H~^_P^`YUwf;2~VSFCoi z+Y60vw!EcB5rDMcRUyqgB5zbjL6NR{-!F3c`hqbak|K z9Kkc|Dw{G4aG-_tU7Cm3m1@}@(``FwhRHANzO@D*ip0;$mFmUO8~oghqfdZ7YG9f1 zZtlr^=l9nzt_~r7j*W3B158#*`6DD$CWl)&0rrLse0ez;G%Ov@tiE<;rcWu^(HNjY zuD;xFXUD?qhr*Fd9f-i7zztFKC7bniJIMP0tj_+w4zbB-1^%ma} z;&_n(7Jsq(4z9a~K^Si&*M?(n{Hk_ak8kQ;m%QIQiq4Wqc zu}Lf|2{Qn>VahhLHi1cq zzjV=F@ApR^b?`*cA?E9r5L*oOt+CkJ?~k^?*!!PAZ5eK2R z#^MXS4PUni#9MI$gJ=qbBjN9m8xOuzu-7&KcZ#jC8AymN$Q%bp)DQ=@Eg}`*#gSj^*^z)dJ74L{r#;!Hc=+n1}wlQp#C!S;!c1j z`J=w&e|>4TOiwuMcnh5SLdXxCZ++QvR?vVI;H6xy>Ui-OfB$_@V)QSF62U6`7s7?n z!A4v-_+y~L!S9O+EpF+wYQhbtV+}V>z+3gW@^Q(VFhLmhQzS`0$`s8SFHwX-59c27 z3%*?x^H{hR(5>(JOo!j^nT{F`)gD^w?FL3cJOP(?G)38$e!@{)WICVnotlCjj5Ep7 zdu|^1HggdC#|jC4%p)ts>ADYBsTTotwi1jM#a@>r%3hLA5MeIs*(*AwV~aDbeOcO;;1Z@r;|5pTqay#V43L;{ymIT}Sr zvshIwPJzNs#T#*U30!9mS=b<;B~t}#UAz&pKks*|k20$Nra*k5FC|t2b2lu74`*~Z zqE;F;bVH30Z^Wo8cDpd$9<^i=3@xuYkwNwFoe6+UQ$J0ip`*#&GN4o`Z^X8J$G5v; zJ6MJAe~`>9g?|)aB2VH%!XF74$>E=58{eiG%!^`nVSW*9Oo9`3+_mXf2b7<5e(OCk z;c)$j4sX+$!6J0H|8qzM0Snnqv!k+DTx5`e0Qctp1?k##dIH`CL{B1)^DelGADuKb zp!Srv0B1NqIoQULCeMzxZJS?8gv62WzW~{}Z?imN?8xJ_H`dANgvL68f3)572_4&o zhrXoT7y~EYf-^i_d6t$cyn02q5f~$zmkG&NaEvSbaU*qaqUsIYT9r$COMTx-wwt)I zw^~2ITO~^T-Q9lJCmy6#35Yl1m?(8}PmkudUKk^Q1p{=|S_Afxpj@djIwHRj)30?2 z!m$|kM#EuOw;7wn^>3#rV>4AXD^@^85KBitK&+tBHV zuZxdF&2#9XAY(#RhvVx@G)>>2EqQ1QdhNd2q+xWULGeect5D1xC%vqF(VvoU9F;l< zGc0$6<2xq*Y}V+hSH0wojOa!tbOQrgWj-%$p;fd^H{ ziZz54vs@zLec@!9p~8`|$y+h4uBxIQ~S-Ozlm zKismih0y+U=U$2iG{wR7XMH0%z*NeQfNB!|TIF&9m9ek@pp8uoZZguCc^NXhY<%$R z;25t$HFg#3jm}&xD_bbh-tB;P8??0*-4>8;q}4q!8V-Nd6?;G3A1)z%0k8$nG6h_* z%SMk!ha>z(L~Se_nuEX7vuu_0ID7_p0{`<7%-SVQ^P|xaN}bVyt(35;)D^q?!-tkS`VN;mI&hYH;H>lj7RvO!Jt8Ro+yB>{3ETyk{?JN)e^DR1SI@P2 zqFo1K-*%V74Z9q{HjSW-zOT>0VB6A8=9-k*-1Fm2hIDJ`)yiMasfRy)K>=l>-snJ# zhU2~Acy#b3f|RCkld#KZQ}f^bEil+ArK2mdI?{V6-Fe?(TvYTht!adPUiU<^2r_&< zhE#Ya;35Sgkkq=4X!5`N54B}GTvq$1fRX!fNMIlg;?%PU{PW-c_HT^rW?mYQFH}4S zM`Oxar*p9d5Y(~C;|xVTHp9Brx>>cV(eq?mmt5h;zU zslc^_{FViglnYJ(p=>^UN{h%6itdD!-??NDhCPwfWTl`BTxi#+4a8~V$=hlHRW?4* zI+scHnvNgx2{d(z@Irq-PJrL(EVSFn%kxRUxGP{1{}*)VBZok}&z4#Qy9bA=TtbcF z>Hs~;ThP-q1CZV8Oz^uVV5334+tzPN!;GX}mhNeYc^a<4d zeT`uw8D!mU4#okmG2YMjgXR^N)ruQEeKyXowa+c>F!|tqAxy>^l1#lZrYlue)_S>O zpHE}OE)K@aTC9S({JmVQje*BU>ptI||M}m69QrbiH#p_iJxe!lo0~YUXf84H@D6+D z%VvjR{jrzN9QgY`8)p+|wx-HPqD{z*mqT^z#4vy^OGy7u4C>vgRym=5h6e$LPgT~k{h3Y{a4zQ>AP$0Ph?;HhnN z5J3)+(*c&D!!|TJm_0*eYruOukE?FMmylvpp}q1%rr=t4N=ay2=b1@GZMBppw=m|^ z^thZ!z2eC47k}v6=C!+IL!{(8oJFfn!WUVgRq}X~tM{PEXx;1^i+2EB($Q5NQ%Blp zZpb=Wy1hMZMguZ}@^j|Lzl&@%OGQ|YDtRGnhuek;smG8qK zG3vr{{S%ktTB=etYIAOR%CwjW{*TL?$r|{+a3?S z3N)Ml%)sqpQ;kM&^t#Zoc+<)V`kBIhZPd*1y#S(L*vsiRI z(W*uH%WtrjDJMVoWG3xELM{uV!x7AV0S@fc$B7RxDgtU@1ShJBBnvN=z=Ud_$8!x{ zh!|K4o2-@F4}$g1+*v~plS-LZAj${OfOQeYUnbiVxKlD z^m~Z@^ci+P3KgS;bu1`uvBH)EXXTY!wCud9rFBJwaMVgzsA*1lO~RV5>ky5m;Pk2; zkTAWhj2Oj;<@YB??D?gH`Y?Zn+*yuZ!&S>=mK#x$b|b!mm@US=Z*Mt&>*(` zbS@npY!d&Pmi-r_gW>sO(WdBWXCyF~|HV)78)rPBQ!yh@MsyrxVw`r)Js%!y$U$nf z)+b3&i1w6UlPH?PG!qkVrpz~f+ol~eih}?+)XU3B4~wO^mT2wa(4!RF?w2Nqw_`8) ze+vWm@%*d#?#0dyl^W6I0us5iLvG(oLj;&wd3S`1yA^P;cXkkO30=Vs*tCVt_yG@Y zw#WH?+U%fWU2(PJY{ieo^Xb=Qz{}#ShOVNg7%^yYNe&rzxn^bL?x4K zMPX?uU_sJA^OQ_5O*0%0tNSrMC}cDE0;r7@og{Zh9 zw&rocIeXvxErC>k07>f=v=1$zgouSv`k_be2Xnob;prIa0FEvoF z^?;5X!I=}CkX#dEJ7|6L+uwcj+uwZi+uvv~2+bPM$lO`ogqG~F`SO{ zRO_EESA$x(5K4z$wWnG~CNKj(_BC{9cdd&d?zWeHLu!QIbpEY9pF!<-;e05)N3PuS z1w3y;2NaSh6SxBc%Hv%h{jrlK7CPvqAVmN&UF~o}*Pr#yVG`rG zvq07tELky!Qb&%grn3-ZZLd6tB>K>+F8#3#^4H+UWUe#*iM8<1Z;l%4ZE?QYu8?=3 zgk<~&IXwPfQDc*x5S<%<<&4{VuYIB83XGm2?r0 zQHn4D>yPI$PSLxMn<;O0^zDNelHCn9uVq_VSc|7+97*t5VoxsEbg9rsft>|0h07Yt z4~Sdv^Py^5n?Y~VWvpN@|Hf|W)Vjia{+OmRC^$=I2pcN%LOe$9OFX-JEcOm|_lDxx zT}dP!VIfM&iv=Dl z2`ptHlO+2E@E`qk*l)ES>*QaR>)pkfg79^k-6k@cGtq$`7r~k;Do`H|Lg|WNo=Zv5 ztM=G1*R;%+d>%-sRDntf7!M6Eh#Ig4#uubn?jQVQe^kr=JRJTh|L^yd|CdyBR-C&`f^=*YI-q7k zE(wHp$iD^dQM&2VEJ!#M4y1e@iWGm`zsRUNS3Ct(O3!ija+Ac{RJkzb9#$lR>2YGw zzR6|q+EGq-1iaoXlu&|A&LB0w98o&`r2V1gWO8b?dc7X#qb80y4|Qd|%>$mfy^RX_qlog)K+9+-cmA&$}@x&O6vk_&z z{**hgPov%i5GSlkT);4R&LB${!F(*HYdpptWV^#r%%Ici0gT30w|DQLV|%}MC>B|n zE7pHQog)oN4&m{%f-PRANpP~Vu?l|9X28>_TXJDanQ?Ul=QNc?59F<1?Dcnt{o%Aa zawOOOkYJvWoPH(3!KS~M#|81xTm@p<^?%IEwE7c>L-^aOREvHN1tSN)lIeO%DjM(i zB3Rv&AW&fk&=@JK=cX5XMuDJCNjV)fz_qiHxuCU%i#Um1u?N&twBe#cshkddMqyz} zA#GZ1jfx0#B*i8(_zrIg*o>fS7g-c(|0FX#{d@U3igRZI$Tv0>?XNQQBaYF_txF~J zAPW;8|E@+^yor^tI1i_%M(~?*Nj~OaeUVQHt;N&P&n7&kmVQ&K>wLvzUng+bl6P?7 zK`Xb_ACQ{E=pzI5+6|Q6isTwYb2clAtWJ(qEvCJze;Ef!QfrAnjzKf17Di&`Z#^=2 zIO;@62?#c8q13>c0nXesCsh=$*G-eKmBeY`?l%X`x}W)=2<8-F@Ay!1h`o;9)}zgq z-e9GY*TD^Ro&|}LW^uqee-=l@0z(T;==m08K1vJyW!EY@mYuc`3a~8DKeQ zd~p|$#5a^10C0@b0dCpk0mLph4M`Pvy`g@giU>4$0MoBjKGuK#;=dg^xTPBsAZ)|g zvS@Ay*U4)MKj@|=BK|GmzrJ02y5iUwRSE`YbW*kI%QY?30JTu;G~F-Mbj(I2G35fo zJD9{iU9O1CZfjDyJ*CQsNXMGnZowm3A-!8uXL8c~)#v>XA7w3 z*z4%cuGNPd_KZZ5XUBhU+_BS==ENV&ToYn9wEh)IE8~t^t`8E$V(*#S)RV~SLw&IY z_vYgd$ibBa5Z#xR9#hypnu$}TtIKh{;c5RXMbD_o^)=n@Gtj;QU;s;Je=5|rLA`xa z_aP58F+5boO63vwLaPVIVoFpBaORa))%q2DfCY0`>sz!QIfiYMLD`^Qz5Q8PQSl6R4W@a|Ktpr`<6s_^jgH6hg)@;Jx0y$3sEgSq330zDetT ziCXu{6H0`A3J43T9(r`8)zXiNd?4dNNu7oG@CWF}0UjJOU5nmCG`(>Ut~J=%fvd4& zDv;DFk02Vuq}Yxc>v#{xZ%K{mlO8{n#n?+>;CTLfxfH!#z`2Z@d`H-Os@?2x((y+@ zj;C=LPV`-wv`kHmskH7~$8fGbIx{_EtLRVkiquu$qB}8x=pk*!Y zuYz33qcmbyN%a2c2l-rKJM#oV8EU3M;AGFw(5WUis?~aeHPEbAcbkK$wY&LzR zX$&ttizBG{sw#`eG0eAz26207AU3UvVFW)idKwOixdXuAHPBm`%QTc8pJZ4S?Z!WK zJF!$UH8)`@+FalhmlCJ*u{X;XyI%r^GdE$neKGv9GXjyfD^(AK=aW&h!Jq#IR>`mK~7{C zV_=Hk91Z)hK~hUB@|e;U{IkgwZskUT%L=VKwqJP;;Bp>3{E1niP*H^FL zA;o-EJl~TTtR%1r({jp64Qg=Z=3ZwGybr~D`=&X2BEqTZJ;GW6VP57*+wD{4NvAVy zm|L&weJ8i=5m_FXuBI#78>1@FBtZ8RfOT$fuRj8B-FTjg3whg=H(l$|07an*=aS$Q z=`Wt2h>ibUoPO4N^yty%29F*+Vh?CHA-eM)OxoxkPCvSN&>!rjgHyO0MRT}`(3`ut ze$?F8O+Ef#U-aeXpPU@(@0c%JnLhsF9$usEWb&!Ms}rVmFz$xp!S!i>dOoX4p7X}$ zrjdrZOl0AtJ8$gdBmmdou4FxL?1ma^y+KHT@Q(Jlwl=F`MUcdS(s|sgNz)tqQ*ouu z>Bzkfw!s5=)EjzOcsFHHZ|Gnec-?AnuXb}`?nm5g27()sqrrmD$wP3%xIz9yN1SbJ zts6MS9v(avY%tJKMO6BvoyyzJCemZOR^so7DjGQhi0LJ8Ou1+qrmfR5B%CB^cm#b3 zrUR-S_Gi*1wteFa4*?0wr#3vmR-<4R*=+{t7dJOSj)1Z>HnkG%AOUvjp7rcp4w6rf z@Z_7n{2RiDjyo+KcjK7!b^#(x1qT(lJq>K9VwME+%7PihQ#h@u>(7rFxB+NQz|!sT z-bytV(|pOQBU(G~B#Gjm;NngFC6Uq}*F&{A!$q7*6!7siVXw#btVC^zOcy~KqMp+{ znugEU59TxNxb|*oX{`G)<4uk)#z+5QVF{!KWO$}@V48X4ts_ImH+qV+aLUuXOGbP5 zzqCVzga$AW_|53M!<^v*H^LnOli@TCmZK+moi1-89>ormQ1G*K5=cf@YBaQnC;|33{YYPp>9tFD@SaupI`4xXs<9P>l%VSr(`W)iqS?gt*{-5%HO1l3sfEi5}GM6XBQ1w3}IOu?Ng z<%aXLG#=Y_edz4ausr4hTX=s8NZb};QUX2$O{yBpf;Mc?UE%1lg_vcF+{pd$wR<1 zrcbs1Uvuz7O?7f$Bh@JJn#8IsOc&8M&Jn9B{xyVUDhlOCKN0QM+Z8_tWA*uCA%k$y zaSch7_lAc6C{{u{#$wtZcm%MX7sI?U8Z_3(>!#6~u;YB?iMA1vNXUCS8H08xF=a!O zY#xWAjn1ngk^xd)e6ZH&{D^)jy&ZD)fY;O3RYir6UstuU+$eTseFxSVvBit)g$*fO zD@(i!lQ?i|mHHLaK z=;-Y*bpUTjawQEgU=T;FRKybVn4{`8hxo1B zZS;6-D@{eF(x{GXvO(CDxs5RSND~MYIl> zndsP7ZzYXsLSTG6aNk1W-)NBRDESK-Hx&_s9F|>O)3_ORJjK)0@PposkxjNYkPg6o z+PV81>y9f5Kdo|q2V?94MXQ#9f|WXASv_@KeFD9St_dTemHHTU!O*8wiIkT9(>pl5;%e2PsR@Tsoj~H zbKZr_KLxJM5cYa@6)jjSv+%V|An!K!p}aP@<+ib<91SpMc|uFW?O<95jq7IXZUmltZJ@8i zhNuj_|N5G0bAJU!K@<%@kvCv^Z}Y0uLG#YG=Wt}2;5ivuFewxf1HJEkowDd!|L$$V zPsfp^)}i{n+EHO%NX8VbgE#@2)_F%qNrN256Y&90y~J`TS7p-M34*NsT#8*0(kL`jZ<$=Y_E5=TS9!5-SE$-u+lB^ zGF3g`pURsuEy^CHt>cfXkSlMj64k#tcp1rkRu;cl@7LOZD2HMH$UbAd!7tV$r&1K0 z``9Z*MUf4h1`?A6IHS_ebsW5wuAKhpxK)paua59krV_a&o9(kQ#v5-@A*SN9@|^e4 zCeYjwpEwyb>+7bowg;#GBVeg)R)D~y2AtbszUVO$ybEX;jy?9*IN0%;%z4YNc*s`} zU#pBWw6Vb)S{sZThk?W@T`enYy`~REMlnJDSEoV+rVE0w2g+xbIU3+q2G^PFQmkCS zdgmnK)P>0LcN!f~siIMLcI@GLInfAbp0UagJyGf7m;cHR3-ECsRqmJ8n zcU)C02{a_TZT7}^Qo6r(b}D35=R(G!|G&L!$!**SqFM74jadvRU`#`zoEU=ufnyl9 zg2-}YZL*3eiDNUNM1`c3arU`HkVVcmCkg7;UEQSO3z!=LVi(E&SG}rw-FQ*nulR!H z9>>`jN`{P&Dbsp|(R{43Sz7jO*4D*mq(IL<0x!B~wpa6MAp6c|0eoja6riOLmkDGo z+rO2m{Z8jT?6inSHj6z{9~^xgj4PYOHJ#kfuNITpy&77llNX;LJKpil36)%!m>1@X z!blk=C-NujysDqFUGb-6gN+hk!Rr~=kuhna+Zxlm8#$^cGG?6kkSc;sLS9K9`A}C! zs6_7=;4(L#_azb1pgrN`GlVL!)gB>EbmNb+x++UJ<*2s{oPYc7>i+iT$Jwpw0+43J z(i_=*t*cufM!ucs@hfv1B?@vh)4~yJGvtFdU_2QK>v3;Vo$`SLguEC65uXM_sg|%^ zX%!1GqT_pk9$)ShVq%3BKyR>B_Ww7M!8(yCd~@qrLrvg!b^{$38tZswqS{~`zx-&f z2}d>WArvtyc?_F)h|4&y;+M%#R_ocSsW4UuW9TCraEkve8K(EpKASemyZ0cF!L8lL z2gwj3tM!M+K7n64n4*?hbYwI&pBD7DB8aDDD7tMtKG1lRuc5bX0ETFo{?r=y&r>)& z2nI-e@a}TsvXL6XnGp>IQHDz~>_rgOpoV+UOv-BgTLO01QaaA6$3U9co9ZgyJJ@u@ zpDzYnBn(H9F$tI{=j<9x;63D7ybkFWp@^rfQMp;Uu?u2Ww*^E*H754*oniO%Iw~^a z_>rgbFLZxoj9+I@^}}Ed`>m+yIaF`g)>Rprv{0Dqui|XD8B>}b3KQ=-0d*eh(wxEj zFfFU1^CEL1L=L4O$5oX^;*Z}C8pC#FKW!lp4d0_gC~-%@%zL$>|rD$(ZR72tyKs(r&3b6BtQJVaSMw`>MS`8?AMEcVfd)R89a(^!D4Kk-$8{NE1H(_=6wSIi0s)UrV4~nZ;#62d<2eMAh@$x6 z!Q42e;Eh_rfS@jrRD!nH9U$i;B^IZAryw_9?@p-J6(ITMn4TZUX8l|hU4RawKcpJP zPTG;U(*g|$tR>Q2w0eS!Ru$lS(q@kb%D%gXM2@=1$gNH^-1NjR|NMo?Vi!(T4o5qU zlBPi-QziDjcFt$D_R5=Byz=|Jwoe`?;YX#dm~YLX|p>Cw7Zj9lEHR;uji&O9T`jo^sr*2%jY9U{C1vFMSOahuoL^@|($nl$ zMOv~tZ1)yL>|qv)Qj&s6=Ua!&W8^5CqcIhwOf9z^peGshMaYG+p)H#2=z;{*k|jh! z@kt94#vfMqC6_qm**@QypMZXnPx<7q5{feVBd1h1Pan3*uS`g9h4`-X1mb?~%QYhl z&u01!!qmF%vlu`Ck~AHw!kC