build(deps): align toolchain peers and replace npm-run-all

- align TypeScript with the current eslint peer range across packages
- replace npm-run-all with a local run-many script and test coverage
- tighten remaining mcp-server typing so the package lint stays clean
- update the lockfile after removing redundant root-level tooling deps
This commit is contained in:
linshen
2026-03-27 22:18:57 +08:00
parent 37d6fa564c
commit 520ae2be99
19 changed files with 463 additions and 1124 deletions

View File

@@ -9,24 +9,24 @@
"yarn": "请使用pnpm代替yarn"
},
"scripts": {
"build": "npm-run-all build:core build:ui build:parallel",
"build": "node scripts/run-many.js build:core build:ui build:parallel",
"build:core": "pnpm -F @prompt-optimizer/core build",
"build:ui": "pnpm -F @prompt-optimizer/ui build",
"build:parallel": "npm-run-all --parallel build:web build:ext",
"build:parallel": "node scripts/run-many.js --parallel build:web build:ext",
"build:web": "pnpm -F @prompt-optimizer/web build",
"build:ext": "pnpm -F @prompt-optimizer/extension build",
"build:desktop-only": "pnpm -F @prompt-optimizer/desktop build",
"build:desktop-only:ci": "pnpm -F @prompt-optimizer/desktop build:ci",
"build:desktop": "npm-run-all build:core build:ui build:web build:desktop-only",
"build:desktop:ci": "npm-run-all build:core build:ui build:web build:desktop-only:ci",
"dev": "npm-run-all clean:dist build:core build:ui dev:parallel",
"dev:fresh": "npm-run-all kill:dev clean pnpm-install dev",
"build:desktop": "node scripts/run-many.js build:core build:ui build:web build:desktop-only",
"build:desktop:ci": "node scripts/run-many.js build:core build:ui build:web build:desktop-only:ci",
"dev": "node scripts/run-many.js clean:dist build:core build:ui dev:parallel",
"dev:fresh": "node scripts/run-many.js kill:dev clean pnpm-install dev",
"dev:parallel": "concurrently -k -p \"[{name}]\" -n \"UI,WEB\" \"pnpm -F @prompt-optimizer/ui build --watch\" \"pnpm -F @prompt-optimizer/web dev\"",
"dev:ext": "pnpm -F @prompt-optimizer/extension dev",
"dev:desktop": "npm-run-all clean:dist build:core build:ui dev:desktop:parallel",
"dev:desktop:fresh": "npm-run-all kill:dev clean pnpm-install dev:desktop",
"dev:desktop": "node scripts/run-many.js clean:dist build:core build:ui dev:desktop:parallel",
"dev:desktop:fresh": "node scripts/run-many.js kill:dev clean pnpm-install dev:desktop",
"dev:desktop:parallel": "concurrently -k -p \"[{name}]\" -n \"WEB,DESKTOP\" \"pnpm -F @prompt-optimizer/web dev\" \"pnpm -F @prompt-optimizer/desktop dev\"",
"test": "npm-run-all -s test:unit test:e2e:smart",
"test": "node scripts/run-many.js -s test:unit test:e2e:smart",
"test:unit": "pnpm -r test --run --passWithNoTests",
"test:e2e": "playwright test",
"test:e2e:smart": "node scripts/smart-e2e.js",
@@ -36,12 +36,12 @@
"test:gate:core": "pnpm -F @prompt-optimizer/core test:gate",
"test:gate:ui": "pnpm -F @prompt-optimizer/core build && pnpm -F @prompt-optimizer/ui test",
"test:gate:e2e": "playwright test tests/e2e/regression.spec.ts tests/e2e/workflows/p0-route-smoke.spec.ts",
"test:gate": "npm-run-all -s test:gate:core test:gate:ui",
"test:gate:full": "npm-run-all -s test:gate test:gate:e2e",
"test:gate": "node scripts/run-many.js -s test:gate:core test:gate:ui",
"test:gate:full": "node scripts/run-many.js -s test:gate test:gate:e2e",
"test:fast": "pnpm -r test --run --passWithNoTests",
"test:e2e:ui": "playwright test --ui",
"test:e2e:debug": "playwright test --debug",
"clean": "npm-run-all clean:dist clean:vite",
"clean": "node scripts/run-many.js clean:dist clean:vite",
"clean:dist": "rimraf packages/core/dist packages/ui/dist packages/web/dist packages/extension/dist packages/desktop/dist packages/desktop/web-dist",
"clean:vite": "rimraf packages/core/node_modules/.vite packages/ui/node_modules/.vite packages/web/node_modules/.vite packages/extension/node_modules/.vite",
"pnpm-install": "pnpm install",
@@ -62,9 +62,7 @@
"@playwright/test": "^1.58.2",
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
"npm-run-all": "^4.1.5",
"rimraf": "^6.1.3",
"typescript": "^6.0.2"
"rimraf": "^6.1.3"
},
"keywords": [],
"author": "",

View File

@@ -29,7 +29,7 @@
"dotenv": "^17.3.1",
"msw": "^2.12.14",
"tsup": "^8.5.1",
"typescript": "^6.0.2",
"typescript": "^5.9.3",
"vitest": "^4.1.2"
},
"dependencies": {

View File

@@ -9,7 +9,7 @@
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"ignoreDeprecations": "6.0",
"ignoreDeprecations": "5.0",
"noEmit": true,
"strict": true,
"noUnusedLocals": true,

View File

@@ -33,14 +33,16 @@
"express": "^5.2.1"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/debug": "^4.1.13",
"@types/express": "^5.0.6",
"@types/node": "^22.19.15",
"@typescript-eslint/eslint-plugin": "^8.57.2",
"@typescript-eslint/parser": "^8.57.2",
"eslint": "^10.1.0",
"globals": "^17.4.0",
"tsup": "^8.5.1",
"typescript": "^6.0.2",
"typescript": "^5.9.3",
"vitest": "^4.1.2"
},
"engines": {

View File

@@ -108,7 +108,7 @@ export class CoreServicesManager {
// 检查是否有任何可用的模型配置
this.showEnvironmentHint();
throw new Error(`Core services initialization failed: ${(error as Error).message}`);
throw new Error(`Core services initialization failed: ${(error as Error).message}`, { cause: error });
}
}
@@ -134,7 +134,7 @@ export class CoreServicesManager {
logger.info(`Default model configured with preferred provider: ${config.preferredModelProvider || 'auto-selected'}`);
}
} catch (error) {
throw new Error(`Failed to setup default model: ${(error as Error).message}`);
throw new Error(`Failed to setup default model: ${(error as Error).message}`, { cause: error });
}
}

View File

@@ -3,7 +3,7 @@
* 完全复用 core 包的模型管理功能
*/
import { ModelManager } from '@prompt-optimizer/core';
import { ModelManager, type TextModelConfig } from '@prompt-optimizer/core';
/**
* 为 MCP 服务器设置默认模型
@@ -23,7 +23,7 @@ export async function setupDefaultModel(
throw new Error('No enabled models found in core defaultModels');
}
let selectedModel: [string, any] | undefined;
let selectedModel: [string, TextModelConfig] | undefined;
// 1. 如果指定了 preferredProvider尝试匹配
if (preferredProvider) {
@@ -64,4 +64,3 @@ export async function setupDefaultModel(
await modelManager.addModel(mcpModelKey, finalConfig);
}
}

View File

@@ -35,7 +35,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { ListToolsRequestSchema, CallToolRequestSchema, isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
import { CoreServicesManager } from './adapters/core-services.js';
import { loadConfig } from './config/environment.js';
import { loadConfig, type MCPServerConfig } from './config/environment.js';
import * as logger from './utils/logging.js';
import { ParameterValidator } from './adapters/parameter-adapter.js';
import { getTemplateOptions, getDefaultTemplateId } from './config/templates.js';
@@ -43,7 +43,7 @@ import { randomUUID } from 'node:crypto';
import express from 'express';
// 创建服务器实例的工厂函数
async function createServerInstance(config: any) {
async function createServerInstance(config: MCPServerConfig) {
// 创建 MCP Server 实例 - 使用正确的 API
const server = new Server({
name: 'prompt-optimizer-mcp-server',

View File

@@ -15,7 +15,7 @@
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"ignoreDeprecations": "6.0",
"ignoreDeprecations": "5.0",
"noEmit": false,
"lib": [
"ES2022",

View File

@@ -52,6 +52,7 @@
"vue-router": "^5.0.4"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@tailwindcss/forms": "^0.5.11",
"@tailwindcss/postcss": "^4.2.2",
"@tailwindcss/typography": "^0.5.19",
@@ -63,12 +64,14 @@
"@vue/tsconfig": "^0.9.1",
"eslint": "^10.1.0",
"eslint-plugin-vue": "^10.8.0",
"globals": "^17.4.0",
"jsdom": "^29.0.1",
"postcss": "^8.5.8",
"tailwindcss": "^4.2.2",
"typescript": "^6.0.2",
"typescript": "^5.9.3",
"vite": "^8.0.3",
"vitest": "^4.1.2",
"vue-eslint-parser": "^10.4.0",
"vue-tsc": "^3.2.6"
}
}

View File

@@ -540,22 +540,16 @@ const handleExtractionConfirm = (data: {
const placeholder = `{{${data.variableName}}}`;
const text = editorView.state.doc.toString();
let newValue = text;
if (data.replaceAll && occurrenceCount.value > 1) {
// 全部替换
newValue = replaceAllOccurrencesOutsideVariables(
text,
currentSelection.value.rawText,
placeholder,
);
} else {
// 仅替换当前选中的文本
newValue =
text.substring(0, currentSelection.value.start) +
placeholder +
text.substring(currentSelection.value.end);
}
const newValue =
data.replaceAll && occurrenceCount.value > 1
? replaceAllOccurrencesOutsideVariables(
text,
currentSelection.value.rawText,
placeholder,
)
: text.substring(0, currentSelection.value.start) +
placeholder +
text.substring(currentSelection.value.end);
// 更新编辑器内容
editorView.dispatch({

View File

@@ -58,7 +58,7 @@ export function useVariableDetection(
// 分类变量并获取值
let source: DetectedVariable["source"];
let value = "";
let value: string;
// 优先级: 预定义 > 全局 > 临时 > 缺失
if (predefinedVariables.value[name] !== undefined) {

View File

@@ -431,7 +431,10 @@ export function useImageModelManager() {
const adapter = registry.getAdapter(selectedProviderId.value)
selectedModel = adapter.buildDefaultModel(configForm.value.modelId)
} catch (error) {
throw new Error(`无法构建模型 ${configForm.value.modelId}: ${error instanceof Error ? error.message : String(error)}`)
throw new Error(
`无法构建模型 ${configForm.value.modelId}: ${error instanceof Error ? error.message : String(error)}`,
{ cause: error }
)
}
}
@@ -615,7 +618,10 @@ export function useImageModelManager() {
const adapter = registry.getAdapter(selectedProviderId.value)
cachedModel = adapter.buildDefaultModel(selectedModelId.value)
} catch (error) {
throw new Error(`无法构建模型 ${selectedModelId.value}: ${error instanceof Error ? error.message : String(error)}`)
throw new Error(
`无法构建模型 ${selectedModelId.value}: ${error instanceof Error ? error.message : String(error)}`,
{ cause: error }
)
}
}

View File

@@ -562,7 +562,7 @@ export function useTextModelManager() {
// but surface the failure to avoid a misleading "success" toast.
const errorMessage = getI18nErrorMessage(error, t('modelManager.loadFailed'))
let staticCount = 0
let staticCount: number
try {
const staticModels = textAdapterRegistry.getStaticModels(providerTemplateId)
staticCount = staticModels.length

View File

@@ -44,7 +44,7 @@ export function useClipboard(): ClipboardHooks {
const errorMessage = err instanceof Error ? err.message : 'Failed to copy to clipboard'
error.value = errorMessage
console.error('[useClipboard] Failed to copy text:', err)
throw new Error(errorMessage)
throw new Error(errorMessage, { cause: err })
} finally {
isLoading.value = false
}
@@ -68,7 +68,7 @@ export function useClipboard(): ClipboardHooks {
const errorMessage = err instanceof Error ? err.message : 'Failed to read from clipboard'
error.value = errorMessage
console.error('[useClipboard] Failed to read text:', err)
throw new Error(errorMessage)
throw new Error(errorMessage, { cause: err })
} finally {
isLoading.value = false
}
@@ -81,4 +81,4 @@ export function useClipboard(): ClipboardHooks {
isLoading,
error
}
}
}

View File

@@ -125,7 +125,10 @@ export class DataImportExportManager implements DataImportExport {
URL.revokeObjectURL(url)
} catch (error) {
console.error('Export to file failed:', error)
throw new Error(`Export failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
throw new Error(
`Export failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
{ cause: error }
)
}
}

View File

@@ -289,7 +289,7 @@ function parseJsonObject(rawText: string): Record<string, unknown> {
if (error instanceof Error && error.message === 'Model response is not a valid JSON object') {
throw error
}
throw new Error('Model response is not valid JSON')
throw new Error('Model response is not valid JSON', { cause: error })
}
}

1319
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

99
scripts/run-many.js Normal file
View File

@@ -0,0 +1,99 @@
#!/usr/bin/env node
const { spawn } = require('node:child_process');
function getRunnerSpec(platform = process.platform) {
if (platform === 'win32') {
return {
command: 'pnpm',
shell: true,
};
}
return {
command: 'pnpm',
shell: false,
};
}
function parseArgs(argv) {
let parallel = false;
const scripts = [];
for (const arg of argv) {
if (arg === '--parallel' || arg === '-p') {
parallel = true;
continue;
}
if (arg === '--silent' || arg === '-s') {
continue;
}
scripts.push(arg);
}
if (scripts.length === 0) {
throw new Error('No scripts provided');
}
return { parallel, scripts };
}
function runPackageScript(script, options = {}) {
return new Promise((resolve, reject) => {
const { command, shell } = getRunnerSpec();
const child = spawn(command, ['run', script], {
stdio: 'inherit',
shell,
env: process.env,
...options,
});
child.on('error', reject);
child.on('exit', (code, signal) => {
if (code === 0) {
resolve();
return;
}
if (signal) {
reject(new Error(`Script "${script}" terminated by signal ${signal}`));
return;
}
reject(new Error(`Script "${script}" exited with code ${code}`));
});
});
}
async function runSequential(scripts, runner = runPackageScript) {
for (const script of scripts) {
await runner(script);
}
}
async function runParallel(scripts, runner = runPackageScript) {
await Promise.all(scripts.map((script) => runner(script)));
}
async function main(argv = process.argv.slice(2)) {
const { parallel, scripts } = parseArgs(argv);
if (parallel) {
await runParallel(scripts);
return;
}
await runSequential(scripts);
}
if (require.main === module) {
main().catch((error) => {
console.error(error.message || error);
process.exit(1);
});
}
module.exports = {
getRunnerSpec,
main,
parseArgs,
runPackageScript,
runParallel,
runSequential,
};

60
scripts/run-many.test.mjs Normal file
View File

@@ -0,0 +1,60 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { getRunnerSpec, parseArgs, runSequential, runParallel } from './run-many.js';
test('parseArgs supports sequential mode by default', () => {
assert.deepEqual(parseArgs(['build:core', 'build:ui']), {
parallel: false,
scripts: ['build:core', 'build:ui'],
});
});
test('parseArgs supports parallel aliases', () => {
assert.deepEqual(parseArgs(['--parallel', 'build:web', 'build:ext']), {
parallel: true,
scripts: ['build:web', 'build:ext'],
});
assert.deepEqual(parseArgs(['-p', 'build:web', 'build:ext']), {
parallel: true,
scripts: ['build:web', 'build:ext'],
});
});
test('getRunnerSpec uses shell mode for Windows pnpm invocation', () => {
assert.deepEqual(getRunnerSpec('win32'), {
command: 'pnpm',
shell: true,
});
assert.deepEqual(getRunnerSpec('linux'), {
command: 'pnpm',
shell: false,
});
});
test('runSequential executes scripts in order and stops on failure', async () => {
const executed = [];
const runner = async (script) => {
executed.push(script);
if (script === 'build:ui') {
throw new Error('boom');
}
};
await assert.rejects(
runSequential(['build:core', 'build:ui', 'build:web'], runner),
/boom/
);
assert.deepEqual(executed, ['build:core', 'build:ui']);
});
test('runParallel waits for all scripts to finish when all succeed', async () => {
const executed = [];
const runner = async (script) => {
await new Promise((resolve) => setTimeout(resolve, script === 'build:web' ? 10 : 1));
executed.push(script);
};
await runParallel(['build:web', 'build:ext'], runner);
assert.deepEqual(new Set(executed), new Set(['build:web', 'build:ext']));
});