pqm-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +254 -0
  2. package/bin/pqm.js +6 -0
  3. package/package.json +31 -0
  4. package/src/ai/analyzer/collector.js +191 -0
  5. package/src/ai/analyzer/dependency.js +269 -0
  6. package/src/ai/analyzer/index.js +234 -0
  7. package/src/ai/analyzer/quality.js +241 -0
  8. package/src/ai/analyzer/security.js +302 -0
  9. package/src/ai/index.js +16 -0
  10. package/src/ai/providers/bailian.js +121 -0
  11. package/src/ai/providers/base.js +177 -0
  12. package/src/ai/providers/deepseek.js +100 -0
  13. package/src/ai/providers/index.js +100 -0
  14. package/src/ai/providers/openai.js +100 -0
  15. package/src/builders/base.js +35 -0
  16. package/src/builders/rollup.js +47 -0
  17. package/src/builders/vite.js +47 -0
  18. package/src/cli.js +41 -0
  19. package/src/commands/ai.js +317 -0
  20. package/src/commands/build.js +24 -0
  21. package/src/commands/commit.js +68 -0
  22. package/src/commands/config.js +113 -0
  23. package/src/commands/doctor.js +146 -0
  24. package/src/commands/init.js +61 -0
  25. package/src/commands/login.js +37 -0
  26. package/src/commands/publish.js +250 -0
  27. package/src/commands/release.js +107 -0
  28. package/src/commands/scan.js +239 -0
  29. package/src/commands/status.js +129 -0
  30. package/src/commands/watch.js +170 -0
  31. package/src/commands/webhook.js +240 -0
  32. package/src/config/detector.js +82 -0
  33. package/src/config/global.js +136 -0
  34. package/src/config/loader.js +49 -0
  35. package/src/core/builder.js +88 -0
  36. package/src/index.js +5 -0
  37. package/src/logs/build.js +47 -0
  38. package/src/logs/manager.js +60 -0
  39. package/src/report/formatter.js +282 -0
  40. package/src/utils/http.js +130 -0
  41. package/src/utils/logger.js +24 -0
  42. package/src/utils/prompt.js +132 -0
  43. package/src/utils/spinner.js +134 -0
@@ -0,0 +1,317 @@
1
+ import chalk from 'chalk';
2
+ import { logger } from '../utils/logger.js';
3
+ import { Spinner } from '../utils/spinner.js';
4
+ import {
5
+ loadGlobalConfig,
6
+ saveGlobalConfig,
7
+ getAIConfig,
8
+ updateAIConfig,
9
+ isAIConfigured,
10
+ CONFIG_FILE
11
+ } from '../config/global.js';
12
+ import { createProvider, getSupportedProviders } from '../ai/index.js';
13
+ import { promptUser, select, promptPassword } from '../utils/prompt.js';
14
+
15
+ export default function (program) {
16
+ const aiCmd = program
17
+ .command('ai')
18
+ .description('AI Provider 配置管理');
19
+
20
+ // ai config - Interactive configuration
21
+ aiCmd
22
+ .command('config')
23
+ .description('交互式配置 AI Provider')
24
+ .option('--list', '查看当前配置')
25
+ .action(async (options) => {
26
+ if (options.list) {
27
+ await showCurrentConfig();
28
+ return;
29
+ }
30
+
31
+ await interactiveConfig();
32
+ });
33
+
34
+ // ai status - Check connection status
35
+ aiCmd
36
+ .command('status')
37
+ .description('检查 AI Provider 连接状态')
38
+ .action(async () => {
39
+ await checkStatus();
40
+ });
41
+
42
+ // ai providers - List supported providers
43
+ aiCmd
44
+ .command('providers')
45
+ .description('列出支持的 AI Provider')
46
+ .action(async () => {
47
+ await listProviders();
48
+ });
49
+
50
+ // ai set - Quick set config
51
+ aiCmd
52
+ .command('set <key> <value>')
53
+ .description('设置配置值')
54
+ .action(async (key, value) => {
55
+ await setConfigValue(key, value);
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Show current AI configuration
61
+ */
62
+ async function showCurrentConfig() {
63
+ const config = loadGlobalConfig();
64
+ const aiConfig = config.ai || {};
65
+
66
+ console.log('');
67
+ console.log(chalk.bold('当前 AI 配置:'));
68
+ console.log(chalk.gray('─'.repeat(40)));
69
+ console.log(` Provider: ${aiConfig.provider || '(未设置)'}`);
70
+ console.log(` Model: ${aiConfig.model || '(未设置)'}`);
71
+ console.log(` API Key: ${aiConfig.apiKey ? maskAPIKey(aiConfig.apiKey) : '(未设置)'}`);
72
+ console.log(` Enabled: ${aiConfig.enabled !== false ? '是' : '否'}`);
73
+ console.log(chalk.gray('─'.repeat(40)));
74
+ console.log(` 配置文件: ${CONFIG_FILE}`);
75
+ console.log('');
76
+ }
77
+
78
+ /**
79
+ * Interactive configuration
80
+ */
81
+ async function interactiveConfig() {
82
+ console.log('');
83
+ console.log(chalk.bold('🤖 AI Provider 配置向导'));
84
+ console.log('');
85
+
86
+ const providers = getSupportedProviders();
87
+
88
+ // Step 1: Select provider
89
+ console.log(chalk.cyan('步骤 1: 选择 AI Provider'));
90
+ console.log('');
91
+
92
+ providers.forEach((p, index) => {
93
+ const freeText = p.free ? chalk.green(' (免费额度)') : '';
94
+ console.log(` ${index + 1}. ${chalk.white(p.displayName)}${freeText}`);
95
+ console.log(` ${chalk.gray(p.description)}`);
96
+ console.log(` ${chalk.gray('支持模型: ' + p.models.join(', '))}`);
97
+ console.log('');
98
+ });
99
+
100
+ const providerAnswer = await promptUser('请输入序号', '1');
101
+ const providerIndex = parseInt(providerAnswer, 10) - 1;
102
+
103
+ if (isNaN(providerIndex) || providerIndex < 0 || providerIndex >= providers.length) {
104
+ logger.error('无效的选择');
105
+ return;
106
+ }
107
+
108
+ const selectedProvider = providers[providerIndex];
109
+ console.log('');
110
+ console.log(chalk.green(`已选择: ${selectedProvider.displayName}`));
111
+ console.log('');
112
+
113
+ // Step 2: Enter API Key
114
+ console.log(chalk.cyan('步骤 2: 输入 API Key'));
115
+ console.log('');
116
+ console.log(chalk.gray(`获取 API Key: ${selectedProvider.website}`));
117
+ console.log('');
118
+
119
+ const apiKey = await promptPassword(`请输入 ${selectedProvider.displayName} API Key`);
120
+
121
+ if (!apiKey || apiKey.trim() === '') {
122
+ logger.error('API Key 不能为空');
123
+ return;
124
+ }
125
+
126
+ // Step 3: Select model
127
+ console.log('');
128
+ console.log(chalk.cyan('步骤 3: 选择模型'));
129
+ console.log('');
130
+
131
+ selectedProvider.models.forEach((model, index) => {
132
+ const isDefault = model === selectedProvider.defaultModel;
133
+ const marker = isDefault ? chalk.green(' (推荐)') : '';
134
+ console.log(` ${index + 1}. ${model}${marker}`);
135
+ });
136
+
137
+ console.log('');
138
+ const modelAnswer = await promptUser('请输入序号', '1');
139
+ const modelIndex = parseInt(modelAnswer, 10) - 1;
140
+
141
+ let selectedModel;
142
+ if (isNaN(modelIndex) || modelIndex < 0 || modelIndex >= selectedProvider.models.length) {
143
+ selectedModel = selectedProvider.defaultModel;
144
+ } else {
145
+ selectedModel = selectedProvider.models[modelIndex];
146
+ }
147
+
148
+ console.log('');
149
+ console.log(chalk.green(`已选择模型: ${selectedModel}`));
150
+ console.log('');
151
+
152
+ // Step 4: Confirm
153
+ console.log(chalk.cyan('步骤 4: 确认配置'));
154
+ console.log('');
155
+ console.log(chalk.gray('─'.repeat(40)));
156
+ console.log(` Provider: ${selectedProvider.displayName}`);
157
+ console.log(` Model: ${selectedModel}`);
158
+ console.log(` API Key: ${maskAPIKey(apiKey)}`);
159
+ console.log(chalk.gray('─'.repeat(40)));
160
+ console.log('');
161
+
162
+ const confirm = await promptUser('确认保存配置? (y/N)', 'y');
163
+
164
+ if (confirm.toLowerCase() !== 'y') {
165
+ logger.info('配置已取消');
166
+ return;
167
+ }
168
+
169
+ // Save configuration
170
+ updateAIConfig({
171
+ provider: selectedProvider.name,
172
+ apiKey: apiKey.trim(),
173
+ model: selectedModel,
174
+ enabled: true
175
+ });
176
+
177
+ // Try to validate
178
+ console.log('');
179
+ const spinner = new Spinner('正在验证 API 连接...').start();
180
+
181
+ try {
182
+ const provider = createProvider(selectedProvider.name, {
183
+ apiKey: apiKey.trim(),
184
+ model: selectedModel
185
+ });
186
+
187
+ const isValid = await provider.validateConfig();
188
+
189
+ if (isValid) {
190
+ spinner.succeed(chalk.green('API 连接验证成功'));
191
+ } else {
192
+ spinner.warn(chalk.yellow('API 连接验证失败,请检查 API Key'));
193
+ }
194
+ } catch (error) {
195
+ spinner.warn(chalk.yellow(`验证出错: ${error.message}`));
196
+ }
197
+
198
+ console.log('');
199
+ logger.success(`配置已保存到: ${CONFIG_FILE}`);
200
+ console.log('');
201
+ }
202
+
203
+ /**
204
+ * Check AI connection status
205
+ */
206
+ async function checkStatus() {
207
+ console.log('');
208
+ console.log(chalk.bold('🔍 AI Provider 状态检查'));
209
+ console.log('');
210
+
211
+ const aiConfig = getAIConfig();
212
+
213
+ if (!aiConfig || !aiConfig.apiKey) {
214
+ logger.warn('尚未配置 AI Provider');
215
+ console.log('');
216
+ console.log(chalk.gray('运行 pqm ai config 进行配置'));
217
+ console.log('');
218
+ return;
219
+ }
220
+
221
+ console.log(`Provider: ${aiConfig.provider}`);
222
+ console.log(`Model: ${aiConfig.model}`);
223
+ console.log('');
224
+
225
+ const spinner = new Spinner('正在连接...').start();
226
+
227
+ try {
228
+ const provider = createProvider(aiConfig.provider, {
229
+ apiKey: aiConfig.apiKey,
230
+ model: aiConfig.model
231
+ });
232
+
233
+ const isValid = await provider.validateConfig();
234
+
235
+ if (isValid) {
236
+ spinner.succeed(chalk.green('连接成功'));
237
+ } else {
238
+ spinner.fail(chalk.red('连接失败'));
239
+ console.log('');
240
+ console.log(chalk.yellow('可能的原因:'));
241
+ console.log(' - API Key 无效或已过期');
242
+ console.log(' - 网络连接问题');
243
+ console.log(' - API 服务不可用');
244
+ }
245
+ } catch (error) {
246
+ spinner.fail(chalk.red('连接失败'));
247
+ console.log('');
248
+ console.log(chalk.red(`错误: ${error.message}`));
249
+ }
250
+
251
+ console.log('');
252
+ }
253
+
254
+ /**
255
+ * List supported providers
256
+ */
257
+ async function listProviders() {
258
+ console.log('');
259
+ console.log(chalk.bold('📋 支持的 AI Provider'));
260
+ console.log('');
261
+
262
+ const providers = getSupportedProviders();
263
+
264
+ providers.forEach(p => {
265
+ const freeText = p.free ? chalk.green(' ✓ 免费') : '';
266
+ console.log(` ${chalk.cyan(p.displayName)}${freeText}`);
267
+ console.log(` ${chalk.gray(p.description)}`);
268
+ console.log(` 默认模型: ${p.defaultModel}`);
269
+ console.log(` 支持模型: ${p.models.join(', ')}`);
270
+ console.log(` 获取 API Key: ${p.website}`);
271
+ console.log('');
272
+ });
273
+ }
274
+
275
+ /**
276
+ * Set configuration value
277
+ */
278
+ async function setConfigValue(key, value) {
279
+ const validKeys = ['provider', 'model', 'apiKey', 'enabled'];
280
+
281
+ if (!validKeys.includes(key)) {
282
+ logger.error(`无效的配置项: ${key}`);
283
+ console.log('');
284
+ console.log(`有效的配置项: ${validKeys.join(', ')}`);
285
+ return;
286
+ }
287
+
288
+ const config = loadGlobalConfig();
289
+
290
+ if (!config.ai) {
291
+ config.ai = {};
292
+ }
293
+
294
+ // Handle boolean values
295
+ if (value === 'true') {
296
+ config.ai[key] = true;
297
+ } else if (value === 'false') {
298
+ config.ai[key] = false;
299
+ } else {
300
+ config.ai[key] = value;
301
+ }
302
+
303
+ saveGlobalConfig(config);
304
+ logger.success(`已设置 ${key} = ${key === 'apiKey' ? maskAPIKey(value) : value}`);
305
+ }
306
+
307
+ /**
308
+ * Mask API key for display
309
+ * @param {string} key - API key
310
+ * @returns {string} Masked key
311
+ */
312
+ function maskAPIKey(key) {
313
+ if (!key || key.length < 8) {
314
+ return '****';
315
+ }
316
+ return key.substring(0, 4) + '****' + key.substring(key.length - 4);
317
+ }
@@ -0,0 +1,24 @@
1
+ import { logger } from '../utils/logger.js';
2
+
3
+ export default function (program) {
4
+ program
5
+ .command('build')
6
+ .description('Trigger a manual build')
7
+ .option('-t, --tool <tool>', 'Build tool (vite/rollup)', 'vite')
8
+ .option('-m, --mode <mode>', 'Build mode (development/production)', 'production')
9
+ .action(async (options) => {
10
+ logger.info(`Starting build with ${options.tool}...`);
11
+
12
+ try {
13
+ const { buildProject } = await import('../core/builder.js');
14
+ await buildProject({
15
+ tool: options.tool,
16
+ mode: options.mode
17
+ });
18
+ logger.success('Build completed successfully');
19
+ } catch (err) {
20
+ logger.error(`Build failed: ${err.message}`);
21
+ process.exit(1);
22
+ }
23
+ });
24
+ }
@@ -0,0 +1,68 @@
1
+ import { execSync } from 'child_process';
2
+ import { logger } from '../utils/logger.js';
3
+
4
+ export default function (program) {
5
+ program
6
+ .command('commit [message]')
7
+ .description('Commit and push changes to git')
8
+ .option('-m, --message <message>', 'Commit message')
9
+ .action((message, options) => {
10
+ const commitMessage = message || options.message;
11
+
12
+ if (!commitMessage) {
13
+ logger.error('请提供提交信息');
14
+ console.log('用法: pqm commit "提交信息"');
15
+ process.exit(1);
16
+ }
17
+
18
+ // 检查是否有远程仓库
19
+ const remoteUrl = execSync('git remote get-url origin 2>/dev/null', {
20
+ encoding: 'utf8'
21
+ }).trim();
22
+
23
+ if (!remoteUrl) {
24
+ logger.error('未配置远程仓库');
25
+ process.exit(1);
26
+ }
27
+
28
+ // 获取当前分支
29
+ const branch = execSync('git branch --show-current', {
30
+ encoding: 'utf8'
31
+ }).trim();
32
+
33
+ logger.info(`远程: ${remoteUrl}`);
34
+ logger.info(`分支: ${branch}`);
35
+
36
+ // 检查是否有变更
37
+ const status = execSync('git status --porcelain', {
38
+ encoding: 'utf8'
39
+ }).trim();
40
+
41
+ if (!status) {
42
+ logger.warn('没有变更需要提交');
43
+ process.exit(0);
44
+ }
45
+
46
+ console.log('\n变更文件:');
47
+ status.split('\n').forEach(line => {
48
+ console.log(` ${line}`);
49
+ });
50
+
51
+ // 执行提交
52
+ try {
53
+ logger.info('执行: git add .');
54
+ execSync('git add .', { stdio: 'inherit' });
55
+
56
+ logger.info(`执行: git commit -m "${commitMessage}"`);
57
+ execSync(`git commit -m "${commitMessage}"`, { stdio: 'inherit' });
58
+
59
+ logger.info(`执行: git push origin ${branch}`);
60
+ execSync(`git push origin ${branch}`, { stdio: 'inherit' });
61
+
62
+ logger.success('提交并推送成功!');
63
+ } catch (error) {
64
+ logger.error(`操作失败: ${error.message}`);
65
+ process.exit(1);
66
+ }
67
+ });
68
+ }
@@ -0,0 +1,113 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { logger } from '../utils/logger.js';
4
+
5
+ const CONFIG_FILE = '.pqmrc';
6
+
7
+ export default function (program) {
8
+ const configCmd = program
9
+ .command('config')
10
+ .description('View or manage configuration');
11
+
12
+ configCmd
13
+ .command('list')
14
+ .description('List current configuration')
15
+ .action(() => {
16
+ const config = loadConfig();
17
+ console.log('\nCurrent Configuration:');
18
+ console.log(JSON.stringify(config, null, 2));
19
+ });
20
+
21
+ configCmd
22
+ .command('get <key>')
23
+ .description('Get a configuration value')
24
+ .action((key) => {
25
+ const config = loadConfig();
26
+ const value = getNestedValue(config, key);
27
+ if (value === undefined) {
28
+ logger.warn(`Key '${key}' not found`);
29
+ } else if (typeof value === 'object') {
30
+ console.log(JSON.stringify(value, null, 2));
31
+ } else {
32
+ console.log(value);
33
+ }
34
+ });
35
+
36
+ configCmd
37
+ .command('set <key> <value>')
38
+ .description('Set a configuration value')
39
+ .action((key, value) => {
40
+ const configPath = path.resolve(process.cwd(), CONFIG_FILE);
41
+ let config = {};
42
+
43
+ if (fs.existsSync(configPath)) {
44
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
45
+ }
46
+
47
+ setNestedValue(config, key, parseValue(value));
48
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
49
+ logger.success(`Set ${key} = ${value}`);
50
+ });
51
+ }
52
+
53
+ function loadConfig() {
54
+ const configPath = path.resolve(process.cwd(), CONFIG_FILE);
55
+ const defaultConfig = {
56
+ root: './src',
57
+ exclude: ['node_modules', 'dist', '.git', '**/*.test.js', '**/*.spec.js'],
58
+ buildTool: 'auto',
59
+ buildMode: 'incremental',
60
+ buildOnStart: true,
61
+ webhook: {
62
+ enabled: false,
63
+ port: 3200
64
+ },
65
+ log: {
66
+ level: 'info',
67
+ file: '.pqm/pqm.log'
68
+ }
69
+ };
70
+
71
+ if (fs.existsSync(configPath)) {
72
+ try {
73
+ const userConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
74
+ return { ...defaultConfig, ...userConfig };
75
+ } catch (err) {
76
+ logger.warn('Failed to parse .pqmrc, using defaults');
77
+ }
78
+ }
79
+
80
+ return defaultConfig;
81
+ }
82
+
83
+ function getNestedValue(obj, key) {
84
+ const keys = key.split('.');
85
+ let result = obj;
86
+ for (const k of keys) {
87
+ result = result?.[k];
88
+ }
89
+ return result;
90
+ }
91
+
92
+ function setNestedValue(obj, key, value) {
93
+ const keys = key.split('.');
94
+ let current = obj;
95
+ for (let i = 0; i < keys.length - 1; i++) {
96
+ if (!current[keys[i]]) {
97
+ current[keys[i]] = {};
98
+ }
99
+ current = current[keys[i]];
100
+ }
101
+ current[keys[keys.length - 1]] = value;
102
+ }
103
+
104
+ function parseValue(value) {
105
+ // Try to parse as JSON for booleans, numbers, arrays, objects
106
+ try {
107
+ return JSON.parse(value);
108
+ } catch {
109
+ return value;
110
+ }
111
+ }
112
+
113
+ export { loadConfig };
@@ -0,0 +1,146 @@
1
+ import { execSync } from 'child_process';
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { join, resolve } from 'path';
4
+ import { logger } from '../utils/logger.js';
5
+ import chalk from 'chalk';
6
+
7
+ export default function (program) {
8
+ program
9
+ .command('doctor')
10
+ .description('Diagnose the environment and configuration')
11
+ .action(() => {
12
+ console.log('\n' + chalk.bold('PQM Environment Diagnostics\n'));
13
+
14
+ const checks = [
15
+ checkNodeVersion,
16
+ checkNpmVersion,
17
+ checkConfigFile,
18
+ checkSrcDirectory,
19
+ checkBuildTools,
20
+ checkDependencies
21
+ ];
22
+
23
+ let passed = 0;
24
+ let failed = 0;
25
+
26
+ for (const check of checks) {
27
+ try {
28
+ const result = check();
29
+ if (result.passed) {
30
+ logger.success(result.message);
31
+ passed++;
32
+ } else {
33
+ logger.error(result.message);
34
+ if (result.hint) {
35
+ console.log(chalk.gray(` → ${result.hint}`));
36
+ }
37
+ failed++;
38
+ }
39
+ } catch (err) {
40
+ logger.error(`Check failed: ${err.message}`);
41
+ failed++;
42
+ }
43
+ }
44
+
45
+ console.log('\n' + chalk.bold('Summary:'));
46
+ console.log(` ${chalk.green('Passed')}: ${passed}`);
47
+ console.log(` ${chalk.red('Failed')}: ${failed}`);
48
+
49
+ if (failed === 0) {
50
+ console.log(`\n${chalk.green("All checks passed! You're ready to use pqm.")}`);
51
+ } else {
52
+ console.log(`\n${chalk.yellow('Some checks failed. Please fix the issues above.')}`);
53
+ }
54
+ });
55
+ }
56
+
57
+ function checkNodeVersion() {
58
+ const version = process.version;
59
+ const major = parseInt(version.slice(1).split('.')[0], 10);
60
+ return {
61
+ passed: major >= 18,
62
+ message: `Node.js version: ${version}`,
63
+ hint: major < 18 ? 'Upgrade to Node.js 18 or later' : null
64
+ };
65
+ }
66
+
67
+ function checkNpmVersion() {
68
+ try {
69
+ const version = execSync('npm --version', { encoding: 'utf-8' }).trim();
70
+ return { passed: true, message: `npm version: ${version}` };
71
+ } catch {
72
+ return { passed: false, message: 'npm not found' };
73
+ }
74
+ }
75
+
76
+ function checkConfigFile() {
77
+ const configPath = resolve(process.cwd(), '.pqmrc');
78
+ const exists = existsSync(configPath);
79
+ return {
80
+ passed: exists,
81
+ message: exists ? '.pqmrc found' : '.pqmrc not found',
82
+ hint: exists ? null : 'Run `pqm init` to create configuration'
83
+ };
84
+ }
85
+
86
+ function checkSrcDirectory() {
87
+ const srcPath = resolve(process.cwd(), 'src');
88
+ const exists = existsSync(srcPath);
89
+ return {
90
+ passed: exists,
91
+ message: exists ? 'src/ directory found' : 'src/ directory not found',
92
+ hint: exists ? null : 'Create a src/ directory or update .pqmrc root path'
93
+ };
94
+ }
95
+
96
+ function checkBuildTools() {
97
+ const cwd = process.cwd();
98
+ const tools = [];
99
+
100
+ if (existsSync(join(cwd, 'vite.config.js')) ||
101
+ existsSync(join(cwd, 'vite.config.ts'))) {
102
+ tools.push('vite');
103
+ }
104
+
105
+ if (existsSync(join(cwd, 'rollup.config.js')) ||
106
+ existsSync(join(cwd, 'rollup.config.mjs'))) {
107
+ tools.push('rollup');
108
+ }
109
+
110
+ if (existsSync(join(cwd, 'tsconfig.json'))) {
111
+ tools.push('typescript');
112
+ }
113
+
114
+ return {
115
+ passed: tools.length > 0,
116
+ message: tools.length > 0
117
+ ? `Build tools detected: ${tools.join(', ')}`
118
+ : 'No build tools detected',
119
+ hint: tools.length === 0 ? 'Install vite, rollup, or typescript' : null
120
+ };
121
+ }
122
+
123
+ function checkDependencies() {
124
+ const pkgPath = join(process.cwd(), 'package.json');
125
+
126
+ if (!existsSync(pkgPath)) {
127
+ return {
128
+ passed: false,
129
+ message: 'package.json not found',
130
+ hint: 'Run `npm init` to create package.json'
131
+ };
132
+ }
133
+
134
+ try {
135
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
136
+ const hasVite = pkg.devDependencies?.vite || pkg.dependencies?.vite;
137
+ const hasRollup = pkg.devDependencies?.rollup || pkg.dependencies?.rollup;
138
+
139
+ if (hasVite || hasRollup) {
140
+ return { passed: true, message: 'Build dependencies found' };
141
+ }
142
+ return { passed: true, message: 'package.json found (no build deps)' };
143
+ } catch {
144
+ return { passed: false, message: 'Failed to parse package.json' };
145
+ }
146
+ }