From 8c4c7bd60dfa79aa8525455301ce1ef7f6a8fe36 Mon Sep 17 00:00:00 2001 From: imfly Date: Fri, 2 Aug 2024 09:13:52 +0800 Subject: [PATCH] feat: add access constans.*.js method to @ddn/core --- examples/fun-tests/app.js | 8 +- examples/fun-tests/package.json | 2 +- packages/core/README.md | 42 ++- packages/core/package.json | 2 +- packages/core/src/getUserConfig.js | 96 +++++- packages/core/src/index.js | 4 +- scripts/ddnd.js | 461 +++++++++++++++++++++++++++++ 7 files changed, 589 insertions(+), 26 deletions(-) create mode 100644 scripts/ddnd.js diff --git a/examples/fun-tests/app.js b/examples/fun-tests/app.js index dae67464..af58abe8 100644 --- a/examples/fun-tests/app.js +++ b/examples/fun-tests/app.js @@ -9,7 +9,6 @@ const path = require('path') const fs = require('fs') const DdnCore = require('@ddn/core') const DdnPeer = require('@ddn/peer').default -const constants = require('./constants.mainnet') /** * 整理系统配置文件生成输入参数 @@ -48,7 +47,6 @@ async function genOptions () { configObject.version = packageFile.version configObject.basedir = baseDir configObject.buildVersion = packageFile.version - configObject.net = process.env.NET || 'testnet' configObject.publicDir = path.join(baseDir, 'public') configObject.dappsDir = command.dapps || path.join(baseDir, 'dapps') if (command.port) { @@ -81,6 +79,12 @@ async function genOptions () { configObject.loading.verifyOnLoading = true } + // 获取用户配置的常量,依赖于用户的配置 + const constants = await DdnCore.getUserConstants({ cwd: baseDir, net: configObject.net }) + + console.log('configObject:', configObject) + console.log('constants: ', constants) + return { baseDir, configObject, diff --git a/examples/fun-tests/package.json b/examples/fun-tests/package.json index 3813eaf4..4b3d1eeb 100644 --- a/examples/fun-tests/package.json +++ b/examples/fun-tests/package.json @@ -52,7 +52,7 @@ "build-localnet": "./node_modules/.bin/gulp linux-build-local" }, "dependencies": { - "@ddn/asset-aob": "^1.2.3", + "@ddn/asset-aob": "workspace:^", "@ddn/asset-base": "workspace:^", "@ddn/asset-dao": "workspace:^", "@ddn/asset-dapp": "workspace:^", diff --git a/packages/core/README.md b/packages/core/README.md index 259a5490..071c8257 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,18 +1,19 @@ # @DDN/core -用于对 DDN 区块链 相关的配置等相关的基本操作,让系统支持 .ddnrc.js 等根据环境不同的配置文件 +用于对 DDN 区块链 相关的配置等相关的基本操作,让系统支持 .ddnrc.js、constants.js 等根据环境不同的配置文件 -# 开发配置 +## 配置文件 -DDN 允许在 `.ddnrc.t|js` ,`config/config.json` 或 `config/config.t|js`(五选一,`.ddnrc.t|js` 优先)中进行配置,支持 ES6 语法。 +DDN 允许在 `.ddnrc.t|js` ,`config/config.json` 或 `config/config.t|js`(五选一,`.ddnrc.t|js` 优先),以及基于该配置的 `constants.t|js`, `config/constants.t|js`(四选一,`constants.t|js`优先) 中进行配置,支持 ES6 语法。 -**注意**:不要将上述配置混合使用,按照优先级,其他的配置不起作用。 +**注意**:不要将上述配置混合使用,按照优先级,只能保留一个。 -> 为简化说明,后续文档里只会出现 `.ddnrc.t|js`。 +> 为简化说明,后续文档里只会出现 `.ddnrc.js`。 比如: ```js +// .ddnrc.js export default { publicPath: 'http://bar.com/foo', assets: [ @@ -25,19 +26,34 @@ export default { 具体配置项详见[配置](/zh/config/)。 -## .ddnrc.local.js +## 优先级 + +总的原则是,`NODE_ENV`区分生产环境、开发环境,`DDN_ENV`区分物理环境(文件)。生产环境下,`*.mainnet.*`文件优先,其他环境下,优先级为 `*.local.*` > *.{DDN_ENV}.* > `*.testnet.*`。 + + - 1、production: constants.mainnet.* [生产环境下最优先] + - 2、local: constants.local.* + - 3、custom: constants.{DdnEnv}.* + - 4、development: constants.testnet.*; + +## 本地配置 + +.ddnrc.local.js + +`.ddnrc.local.js` 是本地的配置文件,**不要提交到 git**,所以通常需要配置到 `.gitignore`。 + +`.ddnrc.local.js` 默认有效。如果存在,会自动覆盖 `.ddnrc.js` 的同名配置,再返回。所以,你如果有个性化的本地配置,可以添加该文件,从而避免对正式配置文件的污染。 -`.ddnrc.local.js` 是本地的配置文件,**不要提交到 git**,所以通常需要配置到 `.gitignore`。如果存在,会和 `.ddnrc.js` 合并后再返回。 +## 环境变量 -`.ddnrc.local.js` 需要配合环境变量 `NODE_ENV=development` 才有效。 +### 1. NODE_ENV -## NODE_ENV +程序默认是开发环境,即:非生产环境,`.ddnrc.local.js` 和 `.ddnrc.testnet.js` 起作用,且前者覆盖后者,`.ddnrc.mainnet.js` 不起作用。 -程序默认是生产环境,即:非开发环境,`.ddnrc.mainnet.js` 将被自动合并到最终的配置中去。如果设置 `NODE_ENV=development` ,程序将运行在开发环境下,`.ddnrc.local.js` 和 `.ddnrc.testnet.js` 将都被合并到配置中去,同时,前者覆盖后者。 +如果设置 `NODE_ENV=production` ,程序将运行在生产环境下,`.ddnrc.local.js` 和 `.ddnrc.testnet.js` 将被`.ddnrc.mainnet.js`自动覆盖。 -## DDN_ENV +### 2. DDN_ENV -可以通过环境变量 `DDN_ENV` 区分不同物理环境(比如:cloud, custom)来指定配置。 +如果需要临时或定制化的环境配置,可以通过环境变量 `DDN_ENV` 区分,例如:针对不同物理环境(比如:cloud, custom)来指定配置。 举个例子, @@ -45,7 +61,7 @@ export default { // .ddnrc.js export default { a: 1, b: 2 }; -// .ddnrc.cloud.js +// DDN_ENV=cloud -> .ddnrc.cloud.js export default { b: 'cloud', c: 'cloud' }; // .ddnrc.local.js diff --git a/packages/core/package.json b/packages/core/package.json index a5bff8db..6759e42c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@ddn/core", - "version": "2.4.5", + "version": "2.4.6", "description": "DDN core.", "main": "./dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/core/src/getUserConfig.js b/packages/core/src/getUserConfig.js index 4811b05d..de299291 100644 --- a/packages/core/src/getUserConfig.js +++ b/packages/core/src/getUserConfig.js @@ -5,8 +5,8 @@ import extend from 'extend2' import winPath from './winPath' /** - * 获取指定目录下的配置文件 - * 如果环境变量 DDN_CONFIG_FILE 存在,通常是 .ddnrc.* 系列文件,将使用它的值来确定配置文件。否则,将检查默认的配置文件名列表。 + * 获取指定目录下的配置文件(并确保唯一) + * 如果环境变量 CONFIG_FILE 存在,通常是 .ddnrc.* 系列文件,将使用它的值来确定配置文件。否则,将检查默认的配置文件名列表。 * 可以通过逗号分隔配置文件名列表,并且可以通过配置环境变量来指定配置文件。 * * @param {string} cwd - 当前工作路径 @@ -14,8 +14,8 @@ import winPath from './winPath' * @throws {Error} 如果找到多个配置文件,抛出错误 */ export function getConfigFile (cwd) { - const files = process.env.DDN_CONFIG_FILE - ? process.env.DDN_CONFIG_FILE.split(',').filter(v => v && v.trim()) + const files = process.env.CONFIG_FILE + ? process.env.CONFIG_FILE.split(',').filter(v => v && v.trim()) : ['.ddnrc.ts', '.ddnrc.js', 'config/config.json', 'config/config.ts', 'config/config.js'] // const validFiles = files.filter(f => {}) @@ -30,6 +30,38 @@ export function getConfigFile (cwd) { } } +/** + * 获取指定目录下的常量配置文件(确保唯一) + * 如果环境变量 CONSTANTS_FILE 存在,通常是 constants.* 系列文件,将使用它的值来确定配置文件。否则,将检查默认的配置文件名列表。 + * 可以通过逗号分隔配置文件名列表,并且可以通过配置环境变量来指定配置文件。 + * + * @param {string} cwd - 当前工作路径 + * @returns {string|undefined} - 如果找到配置文件,返回配置文件的完整路径;否则返回 undefined + * @throws {Error} 如果找到多个配置文件,抛出错误 + */ +export function getConstantsFile (cwd) { + const files = process.env.CONSTANTS_FILE + ? process.env.CONSTANTS_FILE.split(',').filter(v => v && v.trim()) + : ['constants.ts', 'constants.js', 'config/constants.ts', 'config/constants.js'] + + // const validFiles = files.filter(f => {}) + const validFiles = files.filter(f => existsSync(join(cwd, f))) + + assert( + validFiles.length <= 1, + `Multiple config files (${validFiles.join(', ')}) were detected, please keep only one.` + ) + if (validFiles[0]) { + return winPath(join(cwd, validFiles[0])) + } +} + +/** + * 在文件名字符串的末尾添加指定的后缀 + * @param {string} file - 要修改的文件名 + * @param {string} affix - 要添加的后缀 + * @returns {string} - 添加后缀后的文件名 + */ export function addAffix (file, affix) { const ext = extname(file) return file.replace(new RegExp(`${ext}$`), `.${affix}${ext}`) @@ -67,14 +99,17 @@ export async function getConfigByConfigFile (configFile, opts = {}) { const requireOpts = { onError } /** - * development: config.testnet.* and config.local.*; - * production: mainnet + * 优先级: + * 1、production: .ddnrc.mainnet.* [生产环境下最优先] + * 2、local: .ddnrc.local.* + * 3、custom: .ddnrc.{DdnEnv}.* + * 4、development: .ddnrc.testnet.*; */ const configs = [ defaultConfig, await requireFile(configFile, requireOpts), - ddnEnv && (await requireFile(addAffix(configFile, ddnEnv), requireOpts)), !isProd && (await requireFile(addAffix(configFile, 'testnet'), requireOpts)), + ddnEnv && (await requireFile(addAffix(configFile, ddnEnv), requireOpts)), !isProd && (await requireFile(addAffix(configFile, 'local'), requireOpts)), isProd && (await requireFile(addAffix(configFile, 'mainnet'), requireOpts)) ] @@ -82,6 +117,33 @@ export async function getConfigByConfigFile (configFile, opts = {}) { return mergeConfigs(...configs) } +export async function getConstantsByFile (constantsFile, opts = {}) { + const { defaultConstants, onError, net } = opts + console.log('net', net) + const isMainnet = net === 'mainnet' + const ddnEnv = process.env.DDN_ENV + + const requireOpts = { onError } + + /** + * 优先级: + * 1、production: constants.mainnet.* [生产环境下最优先] + * 2、local: constants.local.* + * 3、custom: constants.{DdnEnv}.* + * 4、development: constants.testnet.*; + */ + const configs = [ + defaultConstants, + await requireFile(constantsFile, requireOpts), + !isMainnet && (await requireFile(addAffix(constantsFile, 'testnet'), requireOpts)), + ddnEnv && (await requireFile(addAffix(constantsFile, ddnEnv), requireOpts)), + !isMainnet && (await requireFile(addAffix(constantsFile, 'local'), requireOpts)), + isMainnet && (await requireFile(addAffix(constantsFile, 'mainnet'), requireOpts)) + ] + + return mergeConfigs(...configs) +} + // Use DDN_ENV to add yourself config, e.g: DDN_ENV=prod config.prod.js export function getConfigPaths (cwd) { const env = process.env.DDN_ENV @@ -127,3 +189,23 @@ export async function getUserConfig (opts = {}) { return {} } } + +/** + * 配置调用 + * 默认 .ddnrc.js,config/config.json, config/config.j(t)s是主要配置文件, + * 当 DDN_ENV 给定值的时候,主配置文件不变,但是 在 .{DDN_ENV}.js 中的配置覆盖 + * 主配置文件而生效。 + * @param opts {cwd: cwd, defaultConstants: constants.default.js } + */ +export async function getUserConstants (opts = {}) { + const { cwd, defaultConstants, net } = opts + const absConfigFile = getConstantsFile(cwd) + + if (absConfigFile) { + return await getConstantsByFile(absConfigFile, { + defaultConstants, net + }) + } else { + return {} + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 15a97299..4240b5d4 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,4 +1,4 @@ import getPaths from './getPaths' -import { getUserConfig, requireFile, mergeConfigs } from './getUserConfig' +import { getUserConfig, getUserConstants, requireFile, mergeConfigs } from './getUserConfig' -export { getPaths, getUserConfig, requireFile, mergeConfigs } \ No newline at end of file +export { getPaths, getUserConfig, getUserConstants, requireFile, mergeConfigs } \ No newline at end of file diff --git a/scripts/ddnd.js b/scripts/ddnd.js new file mode 100644 index 00000000..18d1a0b0 --- /dev/null +++ b/scripts/ddnd.js @@ -0,0 +1,461 @@ +const fs = require('fs'); +const path = require('path'); +const { exec, execSync } = require('child_process'); +const readline = require('readline'); +const https = require('https'); +const http = require('http'); + +// 全局变量声明 + +const PROG_DIR = process.cwd(); +const PID_FILE = path.join(PROG_DIR, 'ddn.pid'); + +// 修改主节点 +const IPS = [ + "https://ddn.link/static/download-International", + "http://ddn.link/static/download-Chinese" +]; + +let statusArray = []; +let length; +let net; +let meta_file; + +function readPort() { + const ddnrcContent = fs.readFileSync(path.join(PROG_DIR, '.ddnrc.js'), 'utf8'); + const portMatch = ddnrcContent.match(/"port"\s*:\s*(\d+)/); + return portMatch ? portMatch[1].trim() : null; +} + +function isRunning() { + if (fs.existsSync(PID_FILE)) { + const pid = fs.readFileSync(PID_FILE, 'utf8'); + try { + process.kill(parseInt(pid), 0); + return true; + } catch (e) { + return false; + } + } + return false; +} + +function status() { + if (isRunning()) { + console.log("DDN server is running"); + } else { + console.log("DDN server is not running"); + } +} + +function start(...args) { + if (isRunning()) { + console.log("DDN server is already started"); + } else { + if (fs.existsSync(PID_FILE)) { + fs.unlinkSync(PID_FILE); + } + const child = exec(`node ${path.join(PROG_DIR, 'app.js')} --base ${PROG_DIR} --daemon ${args.join(' ')}`); + fs.writeFileSync(PID_FILE, child.pid.toString()); + console.log("DDN server is starting"); + } +} + +function stop() { + if (fs.existsSync(PID_FILE)) { + const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8')); + if (pid && !isNaN(pid)) { + try { + process.kill(pid); + console.log("Stopping DDN server..."); + let i = 0; + const checkInterval = setInterval(() => { + try { + process.kill(pid, 0); + i++; + if (i === 5) { + process.kill(pid, 'SIGKILL'); + console.log("DDN server killed"); + clearInterval(checkInterval); + } else { + console.log("Still waiting for ddn server to stop ..."); + } + } catch (e) { + console.log("DDN server stopped"); + clearInterval(checkInterval); + } + }, 1000); + } catch (e) { + console.log("DDN server is not running"); + } + } else { + console.log("Invalid PID in PID file"); + } + fs.unlinkSync(PID_FILE); + } else { + console.log("DDN server is not running"); + } + } + + function restart() { + stop(); + setTimeout(() => { + start(); + }, 5000); // 等待5秒后启动,确保完全停止 + } + + function ismainnet() { + const ddnrcContent = fs.readFileSync(path.join(PROG_DIR, '.ddnrc.js'), 'utf8'); + const netMatch = ddnrcContent.match(/net\s*:\s*"(\w+)"/); + if (netMatch) { + const net = netMatch[1]; + if (net !== "mainnet" && net !== "testnet") { + console.log("Wrong net!"); + process.exit(2); + } + console.log(`net is ${net}`); + return net; + } else { + console.log("Unable to determine network type"); + process.exit(2); + } + } + +function chosenode() { + return new Promise((resolve, reject) => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + rl.question("Please input your choice [default 0] : ", (num) => { + num = num || "0"; + num = parseInt(num); + + if (num >= length) { + console.log("You chosen wrong number, please run ddnd again!"); + rl.close(); + process.exit(1); + } + + if (statusArray[num] === "N") { + console.log("This node has no data, please run ddnd again!"); + rl.close(); + process.exit(2); + } + + const ip = IPS[num].split('-')[0]; + + const protocol = ip.startsWith('https') ? https : http; + protocol.get(`${ip}/${meta_file}`, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + const [Name, Md5] = data.split(' '); + rl.close(); + resolve({ ip, Name, Md5 }); + }); + }).on('error', (err) => { + console.error("Error fetching metadata:", err); + rl.close(); + reject(err); + }); + }); + }); +} + +async function rebuild() { + net = ismainnet(); + meta_file = `metadata_rebuild_${net}.txt`; + + console.log("Please select one of the following sources to use"); + length = IPS.length; + + for (let i = 0; i < length; i++) { + const [ip, country] = IPS[i].split('-'); + const memo = `${country} users recommended`; + + try { + const response = await new Promise((resolve, reject) => { + const protocol = ip.startsWith('https') ? https : http; + protocol.get(`${ip}/${meta_file}`, resolve).on('error', reject); + }); + + let data = ''; + response.on('data', (chunk) => { data += chunk; }); + await new Promise((resolve) => response.on('end', resolve)); + + if (!data.includes('html')) { + statusArray[i] = "Y"; + const [Name, Md5, Date, Height, Size, DB] = data.split(' '); + console.log(`${i}. ${ip} [${memo}] Height:${Height} Date:${Date} Size:${Size}`); + } else { + statusArray[i] = "N"; + console.log(`${i}. ${ip} [No data, do not use.]`); + } + } catch (error) { + statusArray[i] = "N"; + console.log(`${i}. ${ip} [Error: ${error.message}]`); + } + } + + const nodeInfo = await chosenode(); + console.log(`Downloading blockchain snapshot ${nodeInfo.Name}...`); + + // 下载文件 + await new Promise((resolve, reject) => { + const file = fs.createWriteStream(nodeInfo.Name); + const protocol = nodeInfo.ip.startsWith('https') ? https : http; + protocol.get(`${nodeInfo.ip}/${nodeInfo.Name}`, (response) => { + response.pipe(file); + file.on('finish', () => { + file.close(resolve); + }); + }).on('error', (err) => { + fs.unlink(nodeInfo.Name, () => reject(err)); + }); + }); + + // 验证 MD5 + const fileBuffer = fs.readFileSync(nodeInfo.Name); + const hashSum = crypto.createHash('md5'); + hashSum.update(fileBuffer); + const newMd5 = hashSum.digest('hex'); + + if (newMd5 === nodeInfo.Md5) { + console.log("Check md5 passed!"); + } else { + console.log("Check md5 failed, please run again!"); + fs.unlinkSync(nodeInfo.Name); + process.exit(1); + } + + // 停止 DDN 服务器 + execSync('./ddnd stop'); + + // 解压文件 + execSync(`tar zxf ${nodeInfo.Name}`); + + // 删除下载的压缩文件 + fs.unlinkSync(nodeInfo.Name); + + // 移动数据库文件 + const dbFile = fs.readdirSync('.').find(file => file.endsWith('.db')); + if (dbFile) { + fs.renameSync(dbFile, 'blockchain.db'); + } else { + console.log("Database file not found in the extracted contents."); + process.exit(1); + } + + // 启动 DDN 服务器 + execSync('./ddnd start'); + + console.log("Rebuild process completed successfully."); +} + +function version() { + try { + const versionOutput = execSync(`node ${path.join(PROG_DIR, 'app.js')} --version`, { encoding: 'utf8' }); + console.log(versionOutput.trim()); + } catch (error) { + console.error("Error getting version:", error.message); + } +} + +function check_os() { + try { + const osRelease = fs.readFileSync('/etc/os-release', 'utf8'); + const isUbuntu = osRelease.includes('"Ubuntu"'); + if (!isUbuntu) { + console.log("Linux is not Ubuntu, please configure manually!"); + process.exit(1); + } + } catch (error) { + console.error("Error checking OS:", error.message); + process.exit(1); + } +} + +function configure() { + check_os(); + try { + execSync(`sudo bash ${path.join(PROG_DIR, 'init/install_deps.sh')}`, { stdio: 'inherit' }); + execSync(`sudo bash ${path.join(PROG_DIR, 'init/config_ntp.sh')}`, { stdio: 'inherit' }); + execSync(`sudo bash ${path.join(PROG_DIR, 'init/config_monitor.sh')}`, { stdio: 'inherit' }); + } catch (error) { + console.error("Error during configuration:", error.message); + process.exit(1); + } +} + +async function upgrade() { + net = ismainnet(); + meta_file = `metadata_upgrade_${net}.txt`; + + console.log("Please select one of the following sources to use"); + length = IPS.length; + + for (let i = 0; i < length; i++) { + const [ip, country] = IPS[i].split('-'); + const memo = `${country} users recommended`; + + try { + const response = await new Promise((resolve, reject) => { + const protocol = ip.startsWith('https') ? https : http; + protocol.get(`${ip}/${meta_file}`, resolve).on('error', reject); + }); + + let data = ''; + response.on('data', (chunk) => { data += chunk; }); + await new Promise((resolve) => response.on('end', resolve)); + + if (!data.includes('html')) { + statusArray[i] = "Y"; + const [Name, Md5, Date, Version, Size] = data.split(' '); + console.log(`${i}. ${ip} [${memo}] Version:${Version} Date:${Date} Size:${Size}`); + } else { + statusArray[i] = "N"; + console.log(`${i}. ${ip} [No data, do not use.]`); + } + } catch (error) { + statusArray[i] = "N"; + console.log(`${i}. ${ip} [Error: ${error.message}]`); + } + } + + const nodeInfo = await chosenode(); + + // 创建临时目录 + if (!fs.existsSync('tmp')) { + fs.mkdirSync('tmp'); + } + + // 下载文件 + await new Promise((resolve, reject) => { + const file = fs.createWriteStream(path.join('tmp', nodeInfo.Name)); + const protocol = nodeInfo.ip.startsWith('https') ? https : http; + protocol.get(`${nodeInfo.ip}/${nodeInfo.Name}`, (response) => { + response.pipe(file); + file.on('finish', () => { + file.close(resolve); + }); + }).on('error', (err) => { + fs.unlink(path.join('tmp', nodeInfo.Name), () => reject(err)); + }); + }); + + // 验证 MD5 + const fileBuffer = fs.readFileSync(path.join('tmp', nodeInfo.Name)); + const hashSum = crypto.createHash('md5'); + hashSum.update(fileBuffer); + const newMd5 = hashSum.digest('hex'); + + if (newMd5 === nodeInfo.Md5) { + console.log("Check md5 passed!"); + } else { + console.log("Check md5 failed, please run again!"); + fs.unlinkSync(path.join('tmp', nodeInfo.Name)); + process.exit(1); + } + + console.log("Extracting new package ..."); + execSync(`tar zxf ${path.join('tmp', nodeInfo.Name)} -C tmp/`); + + const extractedDir = fs.readdirSync('tmp') + .filter(file => fs.statSync(path.join('tmp', file)).isDirectory()) + .find(dir => dir.includes('ddn')); + + if (!extractedDir) { + console.log("Extracted directory not found"); + process.exit(1); + } + + const currentVersion = execSync('./ddnd version', { encoding: 'utf8' }).trim(); + fs.chmodSync(path.join('tmp', extractedDir, 'ddnd'), '755'); + const newVersion = execSync(`${path.join('tmp', extractedDir, 'ddnd')} version`, { encoding: 'utf8' }).trim(); + + execSync('./ddnd stop'); + + console.log("Copying new files ..."); + fs.readdirSync(path.join('tmp', extractedDir)).forEach(file => { + if (file !== '.ddnrc.js' && file !== 'blockchain.db') { + console.log(`copy ${path.join('tmp', extractedDir, file)} ...`); + fs.cpSync(path.join('tmp', extractedDir, file), path.join(PROG_DIR, file), { recursive: true }); + } + }); + + fs.rmSync(path.join('tmp', extractedDir), { recursive: true, force: true }); + fs.unlinkSync(path.join('tmp', nodeInfo.Name)); + + console.log(`Upgrade to ${newVersion} done.`); + execSync('./ddnd start'); +} + +function enable(...secret) { + const port = readPort(); + const data = JSON.stringify({ secret: secret.join(' ') }); + + const options = { + hostname: 'localhost', + port: port, + path: '/api/delegates/forging/enable', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': data.length + } + }; + + const req = http.request(options, (res) => { + let responseData = ''; + + res.on('data', (chunk) => { + responseData += chunk; + }); + + res.on('end', () => { + console.log('Response:', responseData); + }); + }); + + req.on('error', (error) => { + console.error('Error:', error); + }); + + req.write(data); + req.end(); +} + +function main(args) { + // 将 PROG_DIR/bin 添加到 PATH + process.env.PATH = `${path.join(PROG_DIR, 'bin')}:${process.env.PATH}`; + + const command = args[0]; + const commandArgs = args.slice(1); + + const commands = { + start, + stop, + restart, + status, + rebuild, + version, + configure, + upgrade, + enable + }; + + if (command in commands) { + commands[command](...commandArgs); + } else { + console.log("Command not supported"); + } +} + +// 启动主函数 +if (require.main === module) { + main(process.argv.slice(2)); +} \ No newline at end of file