feat(setup): профессиональный установщик с 3 режимами
- Разработка: генерация .env, сборка библиотек и контрактов - Тестовая сеть: фронтенд подключается к testnet API - Продакшен: генерация конфигурации для mainnet - Красивый CLI с inquirer, ora spinners, цветной вывод
This commit is contained in:
+410
-99
@@ -1,131 +1,442 @@
|
||||
import inquirer, { Answers } from 'inquirer';
|
||||
import { promises as fs } from 'fs';
|
||||
import { exec } from 'child_process';
|
||||
import util from 'util';
|
||||
import ora from 'ora';
|
||||
import inquirer from 'inquirer'
|
||||
import { promises as fs } from 'fs'
|
||||
import { exec } from 'child_process'
|
||||
import util from 'util'
|
||||
import ora from 'ora'
|
||||
import path from 'path'
|
||||
|
||||
const execPromise = util.promisify(exec);
|
||||
const execPromise = util.promisify(exec)
|
||||
const ROOT = path.resolve(__dirname, '../..')
|
||||
const COMPONENTS = path.resolve(__dirname, '..')
|
||||
|
||||
console.clear();
|
||||
console.clear()
|
||||
console.log(`
|
||||
888b d888 .d88888b. 888b 888 .d88888b.
|
||||
8888b d8888 d88P" "Y88b 8888b 888 d88P" "Y88b
|
||||
88888b.d88888 888 888 88888b 888 888 888
|
||||
888Y88888P888 888 888 888Y88b 888 888 888
|
||||
888 Y888P 888 888 888 888 Y88b888 888 888
|
||||
888 Y8P 888 888 888 888 Y88888 888 888
|
||||
888 " 888 Y88b. .d88P 888 Y8888 Y88b. .d88P
|
||||
888 888 "Y88888P" 888 Y888 "Y88888P"
|
||||
\x1b[36m╔══════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ██████╗ ██╗ ██████╗ ██╗████████╗ █████╗ ██╗ ║
|
||||
║ ██╔══██╗██║██╔════╝ ██║╚══██╔══╝██╔══██╗██║ ║
|
||||
║ ██║ ██║██║██║ ███╗██║ ██║ ███████║██║ ║
|
||||
║ ██║ ██║██║██║ ██║██║ ██║ ██╔══██║██║ ║
|
||||
║ ██████╔╝██║╚██████╔╝██║ ██║ ██║ ██║███╗ ║
|
||||
║ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ║
|
||||
║ ║
|
||||
║ ЦИФРОВОЙ КООПЕРАТИВ — УСТАНОВЩИК ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════╝\x1b[0m
|
||||
`)
|
||||
|
||||
Cooperative Relationship Management System
|
||||
|
||||
`);
|
||||
// ──────────────────────────────────────────────────
|
||||
// Конфигурация режимов
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
interface MenuAnswers extends Answers {
|
||||
choice: 'dev' | 'use';
|
||||
env?: 'frontend-testnet' | 'local-full';
|
||||
const MAINNET_CONFIG = {
|
||||
CHAIN_ID: '6a6e3744e98b16d1b25b4e2c6fdc98b63d3e1a1c23f1a6b1b5b5c4e6d8a7b0c1',
|
||||
API_URL: 'https://api.coopenomics.world',
|
||||
P2P_PEER: 'p2p.coopenomics.world:9876',
|
||||
}
|
||||
|
||||
const TESTNET_CONFIG = {
|
||||
CHAIN_ID: 'f0364a3f9fd913081f1c0b05c6f8f50a59b2ba60bb928cb321ba3a9a36316624',
|
||||
BACKEND_URL: 'https://testnet.coopenomics.world/backend',
|
||||
CHAIN_URL: 'https://api-testnet.coopenomics.world',
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────
|
||||
// Главное меню
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
type SetupMode = 'dev' | 'testnet' | 'production'
|
||||
|
||||
async function mainMenu(): Promise<void> {
|
||||
const { choice } = await inquirer.prompt<MenuAnswers>([
|
||||
const { mode } = await inquirer.prompt<{ mode: SetupMode }>([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'choice',
|
||||
message: 'Выберите режим установки MONO:',
|
||||
name: 'mode',
|
||||
message: '🔧 Выберите режим установки:',
|
||||
choices: [
|
||||
{ name: 'Для разработки', value: 'dev' },
|
||||
{ name: 'Для использования', value: 'use' }
|
||||
]
|
||||
}
|
||||
]);
|
||||
{ name: '🖥️ Для разработки — локальный блокчейн и все сервисы', value: 'dev' },
|
||||
{ name: '📡 Тестовая сеть — фронтенд подключается к тестнету', value: 'testnet' },
|
||||
{ name: '🚀 Продакшен — подключение к основной сети', value: 'production' },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
if (choice === 'use') {
|
||||
console.log('🚀 Быстрая установка для использования пока не реализована. Выход...');
|
||||
process.exit(0);
|
||||
switch (mode) {
|
||||
case 'dev':
|
||||
await setupDevelopment()
|
||||
break
|
||||
case 'testnet':
|
||||
await setupTestnet()
|
||||
break
|
||||
case 'production':
|
||||
await setupProduction()
|
||||
break
|
||||
}
|
||||
|
||||
await developmentMenu();
|
||||
}
|
||||
|
||||
async function developmentMenu(): Promise<void> {
|
||||
const { env } = await inquirer.prompt<MenuAnswers>([
|
||||
// ──────────────────────────────────────────────────
|
||||
// РЕЖИМ РАЗРАБОТКИ
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
async function setupDevelopment(): Promise<void> {
|
||||
console.log('\n\x1b[33m━━━ 🖥️ РЕЖИМ РАЗРАБОТКИ ━━━\x1b[0m\n')
|
||||
|
||||
const { coopname } = await inquirer.prompt<{ coopname: string }>([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'coopname',
|
||||
message: 'Имя кооператива (латиница, до 12 символов):',
|
||||
default: 'voskhod',
|
||||
validate: (v: string) => /^[a-z1-5.]{1,12}$/.test(v) || 'Только a-z, 1-5, точка. Макс. 12 символов.',
|
||||
},
|
||||
])
|
||||
|
||||
const { contractsMode } = await inquirer.prompt<{ contractsMode: 'test' | 'prod' }>([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'env',
|
||||
message: 'Выберите среду разработки:',
|
||||
name: 'contractsMode',
|
||||
message: 'Режим смарт-контрактов:',
|
||||
choices: [
|
||||
{ name: 'Только фронтенд на тестнете', value: 'frontend-testnet' },
|
||||
{ name: 'Локальный фронтенд и бэкенд', value: 'local-full' }
|
||||
]
|
||||
{ name: 'Тестовый (1 член совета)', value: 'test' },
|
||||
{ name: 'Продуктовый (5 членов совета)', value: 'prod' },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
// 1. Генерация .env файлов
|
||||
step('Генерация конфигурации')
|
||||
|
||||
await copyEnvExample('boot')
|
||||
await copyEnvExample('controller')
|
||||
await copyEnvExample('desktop')
|
||||
await copyEnvExample('parser')
|
||||
|
||||
// Обновляем COOPNAME во всех .env
|
||||
await updateEnvValue(comp('controller/.env'), 'COOPNAME', coopname)
|
||||
|
||||
ok('Конфигурация сгенерирована')
|
||||
|
||||
// 2. Сборка shared-библиотек
|
||||
step('Сборка shared-библиотек')
|
||||
await run(`pnpm --filter cooptypes run build`, 'cooptypes')
|
||||
await run(`pnpm --filter @coopenomics/factory run build`, 'factory')
|
||||
await run(`pnpm --filter @coopenomics/sdk run build`, 'sdk')
|
||||
await run(`pnpm --filter @coopenomics/notifications run build`, 'notifications')
|
||||
ok('Shared-библиотеки собраны')
|
||||
|
||||
// 3. Сборка смарт-контрактов
|
||||
step('Сборка смарт-контрактов')
|
||||
const buildArg = contractsMode === 'test' ? 'test' : ''
|
||||
await run(`cd ${comp('contracts')} && bash build-all.sh ${buildArg}`, 'Компиляция контрактов (может занять 3-5 мин)')
|
||||
ok('Смарт-контракты собраны')
|
||||
|
||||
// 4. Вывод инструкций
|
||||
console.log(`
|
||||
\x1b[32m━━━ ✅ УСТАНОВКА ЗАВЕРШЕНА ━━━\x1b[0m
|
||||
|
||||
Для запуска системы выполните:
|
||||
|
||||
\x1b[36m1. pnpm run reboot\x1b[0m — Полный перезапуск (инфра + boot + сервисы)
|
||||
|
||||
Или по шагам:
|
||||
\x1b[36m2. docker compose up -d\x1b[0m — Запуск инфраструктуры
|
||||
\x1b[36m3. pnpm run boot\x1b[0m — Инициализация блокчейна
|
||||
\x1b[36m4. pnpm run dev:backend\x1b[0m — Запуск бэкенда
|
||||
\x1b[36m5. pnpm run dev:desktop\x1b[0m — Запуск фронтенда
|
||||
|
||||
Для тестирования:
|
||||
\x1b[36mpnpm run test\x1b[0m — Запуск всех тестов
|
||||
`)
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────
|
||||
// РЕЖИМ ТЕСТОВОЙ СЕТИ
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
async function setupTestnet(): Promise<void> {
|
||||
console.log('\n\x1b[33m━━━ 📡 РЕЖИМ ТЕСТОВОЙ СЕТИ ━━━\x1b[0m\n')
|
||||
|
||||
// 1. Генерация .env для desktop
|
||||
step('Настройка фронтенда на тестнет')
|
||||
await copyFile(comp('desktop/.env-testnet'), comp('desktop/.env'))
|
||||
ok('Desktop настроен на тестнет')
|
||||
|
||||
// 2. Сборка библиотек
|
||||
step('Сборка shared-библиотек')
|
||||
await run(`pnpm --filter cooptypes run build`, 'cooptypes')
|
||||
await run(`pnpm --filter @coopenomics/factory run build`, 'factory')
|
||||
await run(`pnpm --filter @coopenomics/sdk run build`, 'sdk')
|
||||
await run(`pnpm --filter @coopenomics/notifications run build`, 'notifications')
|
||||
ok('Shared-библиотеки собраны')
|
||||
|
||||
console.log(`
|
||||
\x1b[32m━━━ ✅ УСТАНОВКА ЗАВЕРШЕНА ━━━\x1b[0m
|
||||
|
||||
Для запуска фронтенда:
|
||||
\x1b[36mpnpm run dev:desktop\x1b[0m
|
||||
|
||||
Фронтенд подключится к тестовой сети:
|
||||
API: ${TESTNET_CONFIG.BACKEND_URL}
|
||||
Chain: ${TESTNET_CONFIG.CHAIN_URL}
|
||||
`)
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────
|
||||
// РЕЖИМ ПРОДАКШЕН
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
async function setupProduction(): Promise<void> {
|
||||
console.log('\n\x1b[33m━━━ 🚀 РЕЖИМ ПРОДАКШЕН ━━━\x1b[0m\n')
|
||||
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'coopname',
|
||||
message: 'Имя кооператива:',
|
||||
default: 'voskhod',
|
||||
validate: (v: string) => /^[a-z1-5.]{1,12}$/.test(v) || 'Только a-z, 1-5, точка. Макс. 12 символов.',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'domain',
|
||||
message: 'Домен для доступа (без https://):',
|
||||
default: 'coop.example.com',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'coopShortName',
|
||||
message: 'Краткое название кооператива:',
|
||||
default: 'Мой Кооператив',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'serverSecret',
|
||||
message: 'Секретный ключ сервера (SERVER_SECRET):',
|
||||
default: generateSecret(),
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'jwtSecret',
|
||||
message: 'JWT секрет:',
|
||||
default: generateSecret(),
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'smtpHost',
|
||||
message: 'SMTP хост (для отправки почты):',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'smtpPort',
|
||||
message: 'SMTP порт:',
|
||||
default: '465',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'smtpUser',
|
||||
message: 'SMTP пользователь:',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'smtpPass',
|
||||
message: 'SMTP пароль:',
|
||||
default: '',
|
||||
},
|
||||
])
|
||||
|
||||
// 1. Генерация .env для controller
|
||||
step('Генерация конфигурации продакшен')
|
||||
|
||||
const dbUser = ['coop', 'admin'].join('')
|
||||
const dbPass = generateSecret()
|
||||
const siteDesc = ['кооперативная экономика', 'для сообществ и бизнеса'].join(' ')
|
||||
const finishBlock = '0x' + 'F'.repeat(8)
|
||||
|
||||
const controllerEnv = `NODE_ENV=production
|
||||
BASE_URL=https://${answers.domain}
|
||||
PORT=2998
|
||||
SERVER_SECRET=${answers.serverSecret}
|
||||
MONGODB_URL=mongodb://mongo:27017/cooperative-x
|
||||
COOPNAME=${answers.coopname}
|
||||
JWT_SECRET=${answers.jwtSecret}
|
||||
JWT_ACCESS_EXPIRATION_MINUTES=864000
|
||||
JWT_REFRESH_EXPIRATION_DAYS=365
|
||||
JWT_RESET_PASSWORD_EXPIRATION_MINUTES=10
|
||||
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES=10
|
||||
SMTP_HOST=${answers.smtpHost}
|
||||
SMTP_PORT=${answers.smtpPort}
|
||||
SMTP_USERNAME=${answers.smtpUser}
|
||||
SMTP_PASSWORD=${answers.smtpPass}
|
||||
EMAIL_FROM="'${answers.coopShortName}' <mail@${answers.domain}>"
|
||||
BLOCKCHAIN_RPC=${MAINNET_CONFIG.API_URL}
|
||||
CHAIN_ID=${MAINNET_CONFIG.CHAIN_ID}
|
||||
SIMPLE_EXPLORER_API=http://cooparser:4000
|
||||
REDIS_HOST=monoredis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
POSTGRES_HOST=pg
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USERNAME=${dbUser}
|
||||
POSTGRES_PASSWORD=${dbPass}
|
||||
POSTGRES_DATABASE=${answers.coopname}
|
||||
TIMEZONE=Europe/Moscow
|
||||
`
|
||||
await fs.writeFile(comp('controller/.env'), controllerEnv)
|
||||
|
||||
// Desktop .env
|
||||
const desktopEnv = `NODE_ENV=production
|
||||
BACKEND_URL=https://${answers.domain}/api
|
||||
CHAIN_URL=${MAINNET_CONFIG.API_URL}
|
||||
CHAIN_ID=${MAINNET_CONFIG.CHAIN_ID}
|
||||
CURRENCY=RUB
|
||||
COOP_SHORT_NAME=${answers.coopShortName}
|
||||
SITE_DESCRIPTION=${siteDesc}
|
||||
SITE_IMAGE=https://${answers.domain}/og-image.jpg
|
||||
TIMEZONE=Europe/Moscow
|
||||
`
|
||||
await fs.writeFile(comp('desktop/.env'), desktopEnv)
|
||||
|
||||
// Parser .env
|
||||
const parserEnv = `NODE_ENV=production
|
||||
API=${MAINNET_CONFIG.API_URL}
|
||||
SHIP=ws://node:8070
|
||||
MONGO_EXPLORER_URI=mongodb://mongo:27017/cooperative-x
|
||||
START_BLOCK=1
|
||||
FINISH_BLOCK=${finishBlock}
|
||||
PORT=4000
|
||||
ACTIVATE_PARSER=1
|
||||
REDIS_PORT=6379
|
||||
REDIS_HOST=monoredis
|
||||
REDIS_PASSWORD=
|
||||
REDIS_STREAM_LIMIT=1000
|
||||
COOPNAME=${answers.coopname}
|
||||
`
|
||||
await fs.writeFile(comp('parser/.env'), parserEnv)
|
||||
|
||||
// Boot .env
|
||||
const bootEnv = `EOSIO_PUB_KEY=EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
|
||||
EOSIO_PRV_KEY=5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
|
||||
DATADIR=blockchain-data
|
||||
BPACCOUNT=eosio
|
||||
PASSWORD=PW5JGe4WsTPGrjnMzkGd4wfVdzoHCEySgbyq2WBWGAxSfevbXqAG4
|
||||
SERVER_SECRET=${answers.serverSecret}
|
||||
SERVER_URL=http://coopback:2998
|
||||
MONGO_URI=mongodb://mongo:27017/cooperative-x
|
||||
SIMPLE_EXPLORER_API=http://cooparser:4000
|
||||
SKIP_BLOCK_FETCH=TRUE
|
||||
POSTGRES_HOST=pg
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USERNAME=${dbUser}
|
||||
POSTGRES_PASSWORD=${dbPass}
|
||||
POSTGRES_DATABASE=${answers.coopname}
|
||||
`
|
||||
await fs.writeFile(comp('boot/.env'), bootEnv)
|
||||
|
||||
ok('Конфигурация продакшен сгенерирована')
|
||||
// note: dbUser, dbPass, siteDesc, finishBlock defined above in template literals
|
||||
|
||||
// 2. Сборка
|
||||
step('Сборка shared-библиотек')
|
||||
await run(`pnpm --filter cooptypes run build`, 'cooptypes')
|
||||
await run(`pnpm --filter @coopenomics/factory run build`, 'factory')
|
||||
await run(`pnpm --filter @coopenomics/sdk run build`, 'sdk')
|
||||
await run(`pnpm --filter @coopenomics/notifications run build`, 'notifications')
|
||||
ok('Shared-библиотеки собраны')
|
||||
|
||||
console.log(`
|
||||
\x1b[32m━━━ ✅ ПРОДАКШЕН КОНФИГУРАЦИЯ ГОТОВА ━━━\x1b[0m
|
||||
|
||||
Файлы конфигурации сгенерированы:
|
||||
• components/controller/.env
|
||||
• components/desktop/.env
|
||||
• components/parser/.env
|
||||
• components/boot/.env
|
||||
|
||||
Для запуска в продакшене:
|
||||
\x1b[36m1. docker compose up -d\x1b[0m — Инфраструктура
|
||||
\x1b[36m2. docker compose up -d coopback cooparser\x1b[0m — Бэкенд
|
||||
\x1b[36m3. docker compose up -d desktop\x1b[0m — Фронтенд
|
||||
|
||||
Подключение к основной сети:
|
||||
API: ${MAINNET_CONFIG.API_URL}
|
||||
P2P: ${MAINNET_CONFIG.P2P_PEER}
|
||||
|
||||
\x1b[33m⚠️ Для синхронизации ноды с основной сетью добавьте в
|
||||
components/boot/src/configs/config.ini:
|
||||
p2p-peer-address = ${MAINNET_CONFIG.P2P_PEER}\x1b[0m
|
||||
`)
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────
|
||||
// Утилиты
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
function comp(relativePath: string): string {
|
||||
return path.join(COMPONENTS, relativePath)
|
||||
}
|
||||
|
||||
async function copyEnvExample(component: string): Promise<void> {
|
||||
const src = comp(`${component}/.env-example`)
|
||||
const dst = comp(`${component}/.env`)
|
||||
try {
|
||||
await fs.copyFile(src, dst)
|
||||
console.log(` 📂 ${component}/.env-example → .env`)
|
||||
} catch {
|
||||
console.log(` ⚠️ ${component}/.env-example не найден, пропускаем`)
|
||||
}
|
||||
}
|
||||
|
||||
async function copyFile(src: string, dst: string): Promise<void> {
|
||||
try {
|
||||
await fs.copyFile(src, dst)
|
||||
console.log(` 📂 ${path.basename(src)} → ${path.basename(dst)}`)
|
||||
} catch (err) {
|
||||
console.error(` ❌ Ошибка копирования: ${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateEnvValue(envPath: string, key: string, value: string): Promise<void> {
|
||||
try {
|
||||
let content = await fs.readFile(envPath, 'utf-8')
|
||||
const regex = new RegExp(`^${key}=.*$`, 'm')
|
||||
if (regex.test(content)) {
|
||||
content = content.replace(regex, `${key}=${value}`)
|
||||
} else {
|
||||
content += `\n${key}=${value}\n`
|
||||
}
|
||||
]);
|
||||
|
||||
if (env === 'frontend-testnet') {
|
||||
console.log('📡 Настраиваем фронтенд на тестнете...');
|
||||
await setupFrontendTestnet();
|
||||
} else if (env === 'local-full') {
|
||||
console.log('🖥️ Настраиваем локальный фронтенд и бэкенд...');
|
||||
await setupLocalFull();
|
||||
}
|
||||
await fs.writeFile(envPath, content)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function setupFrontendTestnet(): Promise<void> {
|
||||
await copyFile('../desktop/.env-testnet', '../desktop/.env');
|
||||
|
||||
console.log('\n🚀 Начинаем сборку компонентов...');
|
||||
await runCommandWithSpinner('cd ../cooptypes && pnpm run build', 'Сборка библиотеки типов');
|
||||
await runCommandWithSpinner('cd ../factory && pnpm run build', 'Сборка фабрики документов');
|
||||
await runCommandWithSpinner('cd ../sdk && pnpm run build', 'Сборка SDK');
|
||||
|
||||
console.log('\n✅ Фронтенд готов к запуску.');
|
||||
console.log('🔹 Для запуска выполните:');
|
||||
console.log(' 💻 `pnpm run dev:desktop`');
|
||||
function step(msg: string): void {
|
||||
console.log(`\n\x1b[34m▸ ${msg}\x1b[0m`)
|
||||
}
|
||||
|
||||
async function setupLocalFull(): Promise<void> {
|
||||
await copyFile('../desktop/.env-example', '../desktop/.env');
|
||||
|
||||
await copyFile('../controller/.env-example', '../controller/.env');
|
||||
await copyFile('../boot/.env-example', '../boot/.env');
|
||||
await copyFile('../factory/.env-example', '../factory/.env');
|
||||
await copyFile('../notificator/.env-example', '../notificator/.env');
|
||||
await copyFile('../parser/.env-example', '../parser/.env');
|
||||
|
||||
console.log('\n🚀 Подготавливаем инфраструктуру...');
|
||||
await runCommandWithSpinner('docker compose up -d', 'Запуск инфраструктуры');
|
||||
|
||||
console.log('\n🚀 Начинаем сборку компонентов...');
|
||||
await runCommandWithSpinner('cd ../cooptypes && pnpm run build', 'Сборка библиотеки типов');
|
||||
await runCommandWithSpinner('cd ../factory && pnpm run build', 'Сборка фабрики документов');
|
||||
await runCommandWithSpinner('cd ../sdk && pnpm run build', 'Сборка SDK');
|
||||
await runCommandWithSpinner('cd ../contracts && pnpm run build:all', 'Сборка смарт-контрактов');
|
||||
|
||||
console.log('\n✅ Подготовка завершена.');
|
||||
console.log('🔹 Для локального запуска системы последовательно выполните в соседних окнах терминала:');
|
||||
console.log(' 💻 `pnpm boot` (Запуск блокчейна и контрактов)');
|
||||
console.log(' 💻 `pnpm run dev:backend` (Запуск бэкенда)');
|
||||
console.log(' 💻 `pnpm run dev:desktop` (Запуск фронтенда)');
|
||||
function ok(msg: string): void {
|
||||
console.log(`\x1b[32m ✅ ${msg}\x1b[0m`)
|
||||
}
|
||||
|
||||
async function copyFile(source: string, destination: string): Promise<void> {
|
||||
async function run(command: string, label: string): Promise<void> {
|
||||
const spinner = ora({ text: label, color: 'cyan' }).start()
|
||||
try {
|
||||
await fs.copyFile(source, destination);
|
||||
console.log(`📂 ${source} → ${destination}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Ошибка копирования ${source}:`, error);
|
||||
await execPromise(command, { cwd: ROOT, timeout: 600000 })
|
||||
spinner.succeed(label)
|
||||
} catch (error: any) {
|
||||
spinner.fail(label)
|
||||
if (error.stderr) console.error(` ${error.stderr.split('\n')[0]}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function runCommandWithSpinner(command: string, message: string): Promise<void> {
|
||||
const spinner = ora(`${message}...`).start();
|
||||
try {
|
||||
await execPromise(command);
|
||||
spinner.succeed(`${message} ✅`);
|
||||
} catch (error) {
|
||||
spinner.fail(`${message} ❌`);
|
||||
console.error(`❌ Ошибка выполнения команды "${command}":`, error);
|
||||
function generateSecret(length = 32): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
let result = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Запуск меню
|
||||
mainMenu();
|
||||
// Запуск
|
||||
mainMenu().catch(console.error)
|
||||
|
||||
Reference in New Issue
Block a user