重构 constants.js:移除动态命令前缀检测逻辑,改为使用固定命令前缀'npx -y -f dpml-prompt',简化代码结构并提高一致性。同时更新 InitCommand 以使用新的命令前缀,删除不再使用的 saveCommandPrefix 函数。移除 command-prefix.e2e.test.js 测试文件,确保代码整洁。
This commit is contained in:
216
src/constants.js
216
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
|
const COMMANDS = {
|
||||||
let _cachedPrefix = null
|
INIT: `${COMMAND_PREFIX} init`,
|
||||||
|
HELLO: `${COMMAND_PREFIX} hello`,
|
||||||
/**
|
ACTION: `${COMMAND_PREFIX} action`,
|
||||||
* 获取配置实例
|
LEARN: `${COMMAND_PREFIX} learn`,
|
||||||
*/
|
RECALL: `${COMMAND_PREFIX} recall`,
|
||||||
function getConfig() {
|
REMEMBER: `${COMMAND_PREFIX} remember`,
|
||||||
if (!_config) {
|
HELP: `${COMMAND_PREFIX} help`
|
||||||
_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`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 带参数的命令构建函数
|
// 带参数的命令构建函数
|
||||||
function getBuildCommand() {
|
const buildCommand = {
|
||||||
const prefix = detectCommandPrefix()
|
action: (roleId) => `${COMMAND_PREFIX} action ${roleId}`,
|
||||||
return {
|
learn: (resource) => `${COMMAND_PREFIX} learn ${resource}`,
|
||||||
action: (roleId) => `${prefix} action ${roleId}`,
|
recall: (query = '') => `${COMMAND_PREFIX} recall${query ? ' ' + query : ''}`,
|
||||||
learn: (resource) => `${prefix} learn ${resource}`,
|
remember: (content = '<content>') => `${COMMAND_PREFIX} remember${content !== '<content>' ? ' "' + content + '"' : ' <content>'}`
|
||||||
recall: (query = '') => `${prefix} recall${query ? ' ' + query : ''}`,
|
}
|
||||||
remember: (content = '<content>') => `${prefix} remember${content !== '<content>' ? ' "' + 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 = {
|
const PATHS = {
|
||||||
@ -195,27 +61,19 @@ const STATES = {
|
|||||||
RECALL_WAITING: 'recall-waiting'
|
RECALL_WAITING: 'recall-waiting'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除缓存(主要用于测试)
|
|
||||||
*/
|
|
||||||
function clearCache() {
|
|
||||||
_cachedPrefix = null
|
|
||||||
_config = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出
|
// 导出
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// 新的函数式API(推荐)
|
// 固定命令前缀
|
||||||
|
COMMAND_PREFIX,
|
||||||
|
|
||||||
|
// 命令常量
|
||||||
|
COMMANDS,
|
||||||
|
buildCommand,
|
||||||
|
|
||||||
|
// 向后兼容的函数式API
|
||||||
getCommands,
|
getCommands,
|
||||||
getBuildCommand,
|
getBuildCommand,
|
||||||
detectCommandPrefix,
|
detectCommandPrefix,
|
||||||
reconstructCommandPrefix,
|
|
||||||
saveCommandPrefix,
|
|
||||||
clearCache,
|
|
||||||
|
|
||||||
// 向后兼容的静态API
|
|
||||||
COMMANDS,
|
|
||||||
buildCommand,
|
|
||||||
|
|
||||||
// 其他静态常量
|
// 其他静态常量
|
||||||
PATHS,
|
PATHS,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const BasePouchCommand = require('../BasePouchCommand')
|
const BasePouchCommand = require('../BasePouchCommand')
|
||||||
const { ResourceManager } = require('../../resource')
|
const { ResourceManager } = require('../../resource')
|
||||||
const { COMMANDS, saveCommandPrefix } = require('../../../../constants')
|
const { COMMANDS, COMMAND_PREFIX } = require('../../../../constants')
|
||||||
const PromptXConfig = require('../../../utils/promptxConfig')
|
const PromptXConfig = require('../../../utils/promptxConfig')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,17 +23,14 @@ class InitCommand extends BasePouchCommand {
|
|||||||
// 1. 基础环境准备 - 只创建 .promptx 目录
|
// 1. 基础环境准备 - 只创建 .promptx 目录
|
||||||
await this.ensurePromptXDirectory(workspacePath)
|
await this.ensurePromptXDirectory(workspacePath)
|
||||||
|
|
||||||
// 2. 保存命令前缀配置 (会自动处理文件创建)
|
// 2. 加载协议体系
|
||||||
const savedPrefix = await saveCommandPrefix()
|
|
||||||
|
|
||||||
// 3. 加载协议体系
|
|
||||||
const protocolContent = await this.loadProtocolSystem()
|
const protocolContent = await this.loadProtocolSystem()
|
||||||
|
|
||||||
return `🎯 PromptX 系统初始化完成!
|
return `🎯 PromptX 系统初始化完成!
|
||||||
|
|
||||||
## 🏗️ 技术环境准备
|
## 🏗️ 技术环境准备
|
||||||
✅ 创建了 .promptx 配置目录
|
✅ 创建了 .promptx 配置目录
|
||||||
✅ 保存了命令前缀配置:${savedPrefix || '默认前缀'}
|
✅ 设置命令前缀:${COMMAND_PREFIX}
|
||||||
✅ 准备了锦囊状态机框架
|
✅ 准备了锦囊状态机框架
|
||||||
|
|
||||||
## 📋 系统基本诺记 (协议体系)
|
## 📋 系统基本诺记 (协议体系)
|
||||||
|
|||||||
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user