From 2fd6929a586cfb08eb40cb80ec0881f3a96f0f3a Mon Sep 17 00:00:00 2001 From: sean Date: Wed, 4 Jun 2025 18:16:34 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=20constants.js=EF=BC=9A?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=8A=A8=E6=80=81=E5=91=BD=E4=BB=A4=E5=89=8D?= =?UTF-8?q?=E7=BC=80=E6=A3=80=E6=B5=8B=E9=80=BB=E8=BE=91=EF=BC=8C=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E4=BD=BF=E7=94=A8=E5=9B=BA=E5=AE=9A=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E5=89=8D=E7=BC=80'npx=20-y=20-f=20dpml-prompt'=EF=BC=8C?= =?UTF-8?q?=E7=AE=80=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84=E5=B9=B6?= =?UTF-8?q?=E6=8F=90=E9=AB=98=E4=B8=80=E8=87=B4=E6=80=A7=E3=80=82=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E6=9B=B4=E6=96=B0=20InitCommand=20=E4=BB=A5=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=96=B0=E7=9A=84=E5=91=BD=E4=BB=A4=E5=89=8D=E7=BC=80?= =?UTF-8?q?=EF=BC=8C=E5=88=A0=E9=99=A4=E4=B8=8D=E5=86=8D=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=9A=84=20saveCommandPrefix=20=E5=87=BD=E6=95=B0=E3=80=82?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=20command-prefix.e2e.test.js=20=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=96=87=E4=BB=B6=EF=BC=8C=E7=A1=AE=E4=BF=9D=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=95=B4=E6=B4=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants.js | 216 +++--------------- src/lib/core/pouch/commands/InitCommand.js | 9 +- src/tests/commands/command-prefix.e2e.test.js | 200 ---------------- 3 files changed, 40 insertions(+), 385 deletions(-) delete mode 100644 src/tests/commands/command-prefix.e2e.test.js diff --git a/src/constants.js b/src/constants.js index 21e49b0..8eca57d 100644 --- a/src/constants.js +++ b/src/constants.js @@ -3,176 +3,42 @@ * 统一管理命令格式、路径等配置信息 */ -const PromptXConfig = require('./lib/utils/promptxConfig') +// 固定命令前缀 - 使用 -y -f 确保总是获取最新版本 +const COMMAND_PREFIX = 'npx -y -f dpml-prompt' -// 缓存配置实例和命令前缀 -let _config = null -let _cachedPrefix = null - -/** - * 获取配置实例 - */ -function getConfig() { - if (!_config) { - _config = new PromptXConfig() - } - return _config -} - -/** - * 动态检测命令前缀 - * 优先级:环境变量 > npm环境变量检测 > 配置文件 > 默认值 - */ -function detectCommandPrefix() { - // 在 init 过程中,优先使用实时检测而不是缓存 - const isInitProcess = process.argv.includes('init') - - // 如果不是 init 过程且有缓存,返回缓存结果 - if (!isInitProcess && _cachedPrefix) { - return _cachedPrefix - } - - // 1. 环境变量优先(用于测试和自定义) - if (process.env.DPML_COMMAND_PREFIX) { - _cachedPrefix = process.env.DPML_COMMAND_PREFIX - return _cachedPrefix - } - - // 2. 如果不是 init 过程,优先尝试读取配置文件 - if (!isInitProcess) { - try { - const config = getConfig() - const configPath = config.getPath('command-prefix') - const fs = require('fs') - if (fs.existsSync(configPath)) { - const savedPrefix = fs.readFileSync(configPath, 'utf8').trim() - if (savedPrefix) { - _cachedPrefix = savedPrefix - return _cachedPrefix - } - } - } catch (error) { - // 忽略读取错误,继续下一步检测 - } - } - - // 3. npm环境变量检测 - if (process.env.npm_execpath?.includes('npx') || - process.env.npm_config_user_agent?.includes('npx')) { - _cachedPrefix = 'npx -y dpml-prompt' - return _cachedPrefix - } - - // 4. 默认值 - _cachedPrefix = 'dpml-prompt' - return _cachedPrefix -} - -/** - * 智能推测用户使用的命令前缀 - * 从 process.argv 中提取 init 之前的所有部分作为命令前缀 - */ -function reconstructCommandPrefix() { - try { - // 从 process.argv 中找到 init 命令的位置 - const initIndex = process.argv.findIndex(arg => arg === 'init') - - if (initIndex > 0) { - // 提取 init 之前的所有参数,跳过 node 可执行文件路径 - const prefixParts = process.argv.slice(1, initIndex) - - if (prefixParts.length > 0) { - // 检查脚本路径是否包含典型的开发模式特征 - const firstPart = prefixParts[0] - if (firstPart.includes('promptx.js') || - firstPart.includes('cli.js') || - firstPart.includes('bin/') || - firstPart.includes('src/bin/') || - firstPart.includes('/usr/local/bin/dpml-prompt') || - firstPart.endsWith('dpml-prompt')) { - // 开发模式或全局安装,使用包名 - return 'dpml-prompt' - } - - // 直接使用检测到的前缀 - return prefixParts.join(' ') - } - } - - // 如果找不到 init 或解析失败,使用环境变量判断 - if (process.env.npm_execpath && process.env.npm_execpath.includes('npx')) { - return 'npx -y dpml-prompt' - } - - return 'dpml-prompt' - } catch (error) { - // 解析失败时的回退逻辑 - return 'dpml-prompt' - } -} - -/** - * 保存命令前缀到配置文件 - * 在init命令中调用 - */ -async function saveCommandPrefix() { - try { - // 先清除缓存,确保使用最新的检测结果 - _cachedPrefix = null - - // 使用reconstructCommandPrefix来获取用户实际使用的命令 - const actualPrefix = reconstructCommandPrefix() - const config = getConfig() - await config.writeText('command-prefix', actualPrefix) - - // 保存后更新缓存 - _cachedPrefix = actualPrefix - - return actualPrefix - } catch (error) { - console.warn('保存命令前缀失败:', error.message) - return null - } -} - -// 动态生成命令常量(函数式) -function getCommands() { - const prefix = detectCommandPrefix() - return { - INIT: `${prefix} init`, - HELLO: `${prefix} hello`, - ACTION: `${prefix} action`, - LEARN: `${prefix} learn`, - RECALL: `${prefix} recall`, - REMEMBER: `${prefix} remember`, - HELP: `${prefix} help` - } +// 静态命令常量 +const COMMANDS = { + INIT: `${COMMAND_PREFIX} init`, + HELLO: `${COMMAND_PREFIX} hello`, + ACTION: `${COMMAND_PREFIX} action`, + LEARN: `${COMMAND_PREFIX} learn`, + RECALL: `${COMMAND_PREFIX} recall`, + REMEMBER: `${COMMAND_PREFIX} remember`, + HELP: `${COMMAND_PREFIX} help` } // 带参数的命令构建函数 -function getBuildCommand() { - const prefix = detectCommandPrefix() - return { - action: (roleId) => `${prefix} action ${roleId}`, - learn: (resource) => `${prefix} learn ${resource}`, - recall: (query = '') => `${prefix} recall${query ? ' ' + query : ''}`, - remember: (content = '') => `${prefix} remember${content !== '' ? ' "' + content + '"' : ' '}` - } +const buildCommand = { + action: (roleId) => `${COMMAND_PREFIX} action ${roleId}`, + learn: (resource) => `${COMMAND_PREFIX} learn ${resource}`, + recall: (query = '') => `${COMMAND_PREFIX} recall${query ? ' ' + query : ''}`, + remember: (content = '') => `${COMMAND_PREFIX} remember${content !== '' ? ' "' + content + '"' : ' '}` +} + +// 为了向后兼容,保留函数式API +function getCommands() { + return COMMANDS +} + +function getBuildCommand() { + return buildCommand +} + +function detectCommandPrefix() { + return COMMAND_PREFIX } -// 为了向后兼容,保留原有的静态导出方式 -// 但实际上是动态计算的 -const COMMANDS = new Proxy({}, { - get(target, prop) { - return getCommands()[prop] - } -}) -const buildCommand = new Proxy({}, { - get(target, prop) { - return getBuildCommand()[prop] - } -}) // 系统路径配置(静态) const PATHS = { @@ -195,27 +61,19 @@ const STATES = { RECALL_WAITING: 'recall-waiting' } -/** - * 清除缓存(主要用于测试) - */ -function clearCache() { - _cachedPrefix = null - _config = null -} - // 导出 module.exports = { - // 新的函数式API(推荐) + // 固定命令前缀 + COMMAND_PREFIX, + + // 命令常量 + COMMANDS, + buildCommand, + + // 向后兼容的函数式API getCommands, getBuildCommand, detectCommandPrefix, - reconstructCommandPrefix, - saveCommandPrefix, - clearCache, - - // 向后兼容的静态API - COMMANDS, - buildCommand, // 其他静态常量 PATHS, diff --git a/src/lib/core/pouch/commands/InitCommand.js b/src/lib/core/pouch/commands/InitCommand.js index 4fafce8..b9a8f2d 100644 --- a/src/lib/core/pouch/commands/InitCommand.js +++ b/src/lib/core/pouch/commands/InitCommand.js @@ -1,6 +1,6 @@ const BasePouchCommand = require('../BasePouchCommand') const { ResourceManager } = require('../../resource') -const { COMMANDS, saveCommandPrefix } = require('../../../../constants') +const { COMMANDS, COMMAND_PREFIX } = require('../../../../constants') const PromptXConfig = require('../../../utils/promptxConfig') /** @@ -23,17 +23,14 @@ class InitCommand extends BasePouchCommand { // 1. 基础环境准备 - 只创建 .promptx 目录 await this.ensurePromptXDirectory(workspacePath) - // 2. 保存命令前缀配置 (会自动处理文件创建) - const savedPrefix = await saveCommandPrefix() - - // 3. 加载协议体系 + // 2. 加载协议体系 const protocolContent = await this.loadProtocolSystem() return `🎯 PromptX 系统初始化完成! ## 🏗️ 技术环境准备 ✅ 创建了 .promptx 配置目录 -✅ 保存了命令前缀配置:${savedPrefix || '默认前缀'} +✅ 设置命令前缀:${COMMAND_PREFIX} ✅ 准备了锦囊状态机框架 ## 📋 系统基本诺记 (协议体系) diff --git a/src/tests/commands/command-prefix.e2e.test.js b/src/tests/commands/command-prefix.e2e.test.js deleted file mode 100644 index 753d5f8..0000000 --- a/src/tests/commands/command-prefix.e2e.test.js +++ /dev/null @@ -1,200 +0,0 @@ -const path = require('path') -const fs = require('fs-extra') -const { execSync } = require('child_process') -const tmp = require('tmp') - -const PromptXConfig = require('../../lib/utils/promptxConfig') -const PouchCLI = require('../../lib/core/pouch/PouchCLI') - -describe('命令前缀动态检测 E2E', () => { - let tempDir - let originalCwd - let config - - beforeEach(async () => { - // 创建临时目录 - tempDir = tmp.dirSync({ unsafeCleanup: true }).name - originalCwd = process.cwd() - process.chdir(tempDir) - - config = new PromptXConfig(tempDir) - - // 静默console输出 - jest.spyOn(console, 'log').mockImplementation(() => {}) - jest.spyOn(console, 'warn').mockImplementation(() => {}) - - // 清除constants.js的缓存,避免测试间污染 - delete require.cache[require.resolve('../../constants.js')] - const { clearCache } = require('../../constants.js') - clearCache() - }) - - afterEach(async () => { - process.chdir(originalCwd) - try { - await fs.remove(tempDir) - } catch (error) { - // 忽略清理失败 - } - - // 恢复console输出 - jest.restoreAllMocks() - }) - - describe('init命令保存命令前缀', () => { - test('npx方式调用时应保存npx前缀', async () => { - // 模拟npx调用init命令 - process.argv = ['node', 'npx', 'dpml-prompt@snapshot', 'init'] - process.env.npm_execpath = '/usr/local/lib/node_modules/npm/bin/npx-cli.js' - - // 导入并执行init命令 - const cli = new PouchCLI() - - await cli.execute('init', []) - - // 验证保存的命令前缀 - const savedPrefix = await config.readText('command-prefix') - expect(savedPrefix).toBe('npx dpml-prompt@snapshot') - }) - - test('全局安装调用时应保存直接前缀', async () => { - // 模拟全局安装调用 - process.argv = ['node', '/usr/local/bin/dpml-prompt', 'init'] - delete process.env.npm_execpath - - const cli = new PouchCLI() - - await cli.execute('init', []) - - const savedPrefix = await config.readText('command-prefix') - expect(savedPrefix).toBe('dpml-prompt') - }) - - test('指定版本号时应正确保存', async () => { - process.argv = ['node', 'npx', 'dpml-prompt@latest', 'init'] - process.env.npm_execpath = '/usr/local/lib/node_modules/npm/bin/npx-cli.js' - - const cli = new PouchCLI() - - await cli.execute('init', []) - - const savedPrefix = await config.readText('command-prefix') - expect(savedPrefix).toBe('npx dpml-prompt@latest') - }) - }) - - describe('constants.js动态读取', () => { - test('环境变量应能覆盖配置文件', async () => { - // 保存配置文件 - await config.writeText('command-prefix', 'npx dpml-prompt@snapshot') - - // 设置环境变量 - process.env.DPML_COMMAND_PREFIX = 'my-custom-prefix' - - delete require.cache[require.resolve('../../constants.js')] - const constants = require('../../constants.js') - - const commands = constants.getCommands() - expect(commands.INIT).toBe('my-custom-prefix init') - - // 清理环境变量 - delete process.env.DPML_COMMAND_PREFIX - }) - }) - - describe('各种命令格式解析', () => { - const testCases = [ - { - name: 'npx最新版本', - argv: ['node', 'npx', 'dpml-prompt', 'init'], - hasNpxEnv: true, - expected: 'npx dpml-prompt' - }, - { - name: 'npx指定版本', - argv: ['node', 'npx', 'dpml-prompt@0.1.0', 'init'], - hasNpxEnv: true, - expected: 'npx dpml-prompt@0.1.0' - }, - { - name: 'npx snapshot版本', - argv: ['node', 'npx', 'dpml-prompt@snapshot', 'init'], - hasNpxEnv: true, - expected: 'npx dpml-prompt@snapshot' - }, - { - name: '全局安装', - argv: ['node', 'dpml-prompt', 'init'], - hasNpxEnv: false, - expected: 'dpml-prompt' - }, - { - name: '开发模式', - argv: ['node', '/Users/dev/PromptX/src/bin/promptx.js', 'init'], - hasNpxEnv: false, - expected: 'dpml-prompt' - }, - { - name: 'npx -y 参数', - argv: ['node', 'npx', '-y', 'dpml-prompt', 'init'], - hasNpxEnv: true, - expected: 'npx -y dpml-prompt' - }, - { - name: 'npx 复杂参数', - argv: ['node', 'npx', '--yes', '--registry=https://registry.npm.taobao.org', 'dpml-prompt@latest', 'init'], - hasNpxEnv: true, - expected: 'npx --yes --registry=https://registry.npm.taobao.org dpml-prompt@latest' - }, - { - name: 'pnpm dlx', - argv: ['node', 'pnpm', 'dlx', 'dpml-prompt@snapshot', 'init'], - hasNpxEnv: false, - expected: 'pnpm dlx dpml-prompt@snapshot' - }, - { - name: 'yarn dlx', - argv: ['node', 'yarn', 'dlx', 'dpml-prompt', 'init'], - hasNpxEnv: false, - expected: 'yarn dlx dpml-prompt' - } - ] - - testCases.forEach(({ name, argv, hasNpxEnv, expected }) => { - test(`${name}: ${argv[1]} → ${expected}`, async () => { - process.argv = argv - - // 根据测试配置设置环境变量 - if (hasNpxEnv) { - process.env.npm_execpath = '/usr/local/lib/node_modules/npm/bin/npx-cli.js' - } else { - delete process.env.npm_execpath - } - - const cli = new PouchCLI() - - await cli.execute('init', []) - - const savedPrefix = await config.readText('command-prefix') - expect(savedPrefix).toBe(expected) - }) - }) - }) - - describe('缓存性能', () => { - test('第二次调用应使用缓存,不重新检测', async () => { - // 第一次调用保存配置 - await config.writeText('command-prefix', 'npx dpml-prompt@snapshot') - - // 模拟多次调用constants - delete require.cache[require.resolve('../../constants.js')] - const constants1 = require('../../constants.js') - - delete require.cache[require.resolve('../../constants.js')] - const constants2 = require('../../constants.js') - - // 两次调用应该返回相同结果 - expect(constants1.getCommands().INIT).toBe(constants2.getCommands().INIT) - }) - }) -}) \ No newline at end of file