aiwf 0.3.3 β 0.3.4
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.
- package/.aiwf/config/language.json +1 -1
- package/.aiwf/sessions/session_1752877859489_1t7n2fhdb.json +7 -0
- package/.aiwf/sessions/session_1752877859515_cwdv211ry.json +7 -0
- package/.aiwf/sessions/session_1752877860664_huziadmbo.json +7 -0
- package/.aiwf/sessions/session_1752877860666_kyr5u1np6.json +7 -0
- package/.aiwf/sessions/session_1752877861848_9yinepw3t.json +7 -0
- package/.aiwf/sessions/session_1752877861874_y32sputuc.json +7 -0
- package/.aiwf/sessions/session_1752877862685_ivxbl5rhz.json +7 -0
- package/.aiwf/sessions/session_1752877862820_wnq4glvxi.json +7 -0
- package/.aiwf/sessions/session_1752877864632_3ebnpaelb.json +7 -0
- package/.aiwf/sessions/session_1752877864879_b9zgjcb7d.json +7 -0
- package/.aiwf/sessions/session_1752877865533_x14m1vodi.json +7 -0
- package/.aiwf/sessions/session_1752877865562_gkoh19n7d.json +7 -0
- package/.aiwf/sessions/session_1752877866333_wp8qpdcoh.json +7 -0
- package/.aiwf/sessions/session_1752877866509_dawntfpab.json +7 -0
- package/.aiwf/sessions/session_1752877866890_plc5vfucl.json +7 -0
- package/.aiwf/sessions/session_1752877866895_ga8hmq3oi.json +7 -0
- package/.aiwf/sessions/session_1752877867761_ayjp4v5f5.json +7 -0
- package/.aiwf/sessions/session_1752877867892_lfz6jy252.json +7 -0
- package/.aiwf/sessions/session_1752877868139_7bqd9ubli.json +7 -0
- package/.aiwf/sessions/session_1752877868162_zq3ofjbsh.json +7 -0
- package/.aiwf/sessions/session_1752877869054_7rlgm9jt6.json +7 -0
- package/.aiwf/sessions/session_1752877869477_ax3nhbxzr.json +7 -0
- package/.aiwf/sessions/session_1752877870095_6hgp4vl1e.json +7 -0
- package/.aiwf/sessions/session_1752877870381_qfcrdg4li.json +7 -0
- package/.aiwf/sessions/session_1752877871895_pgd5vllhz.json +7 -0
- package/.aiwf/sessions/session_1752877872199_jbhv2g2da.json +7 -0
- package/.aiwf/sessions/session_1752877872680_x04wpffu6.json +7 -0
- package/.aiwf/sessions/session_1752877872895_3y4vbeauo.json +7 -0
- package/.aiwf/sessions/session_1752877873178_3maolzc6b.json +7 -0
- package/.aiwf/sessions/session_1752877873180_ytm83j07f.json +7 -0
- package/.aiwf/sessions/session_1752877874217_dp56c9t42.json +7 -0
- package/.aiwf/sessions/session_1752877874415_epwp1g1og.json +7 -0
- package/.aiwf/sessions/session_1752877874454_heicyuryb.json +7 -0
- package/.aiwf/sessions/session_1752877874869_1c2illeu5.json +7 -0
- package/.aiwf/sessions/session_1752877874879_k1vo4vxyx.json +7 -0
- package/.aiwf/sessions/session_1752877874913_bwgyiz9bp.json +7 -0
- package/.aiwf/sessions/session_1752877876218_44807kztz.json +7 -0
- package/.aiwf/sessions/session_1752877876515_qkhjv5kpw.json +7 -0
- package/.aiwf/sessions/session_1752877876543_p605aud1m.json +7 -0
- package/.aiwf/sessions/session_1752877876819_l65w7ifck.json +7 -0
- package/.aiwf/sessions/session_1752877876867_xrhzss4we.json +7 -0
- package/.aiwf/sessions/session_1752877876872_u7aso54qb.json +7 -0
- package/.aiwf/sessions/session_1752877877705_telnfg4o6.json +7 -0
- package/.aiwf/sessions/session_1752877878285_6s45im2ib.json +7 -0
- package/.aiwf/sessions/session_1752877878506_v0dvguqhz.json +7 -0
- package/.aiwf/sessions/session_1752877878581_3ri3yc66a.json +7 -0
- package/.aiwf/sessions/session_1752877878756_a82x9s2pd.json +7 -0
- package/.aiwf/sessions/session_1752877878886_20pjsfour.json +7 -0
- package/.aiwf/sessions/session_1752877880266_fm1ydjw0g.json +7 -0
- package/.aiwf/sessions/session_1752877881149_xbat4z03d.json +7 -0
- package/.aiwf/sessions/session_1752877881551_qy5sic9qs.json +7 -0
- package/.aiwf/sessions/session_1752877881554_24ugd96wu.json +7 -0
- package/.aiwf/sessions/session_1752877881797_e9mm25uh0.json +7 -0
- package/.aiwf/sessions/session_1752877881822_bxvjj8c9g.json +7 -0
- package/.aiwf/sessions/session_1752877881890_jv7l6jve3.json +7 -0
- package/.aiwf/sessions/session_1752877881991_f0qugdtcq.json +7 -0
- package/.aiwf/sessions/session_1752877883387_0bvw9rvnx.json +7 -0
- package/.aiwf/sessions/session_1752877883820_q6g2md2up.json +7 -0
- package/.aiwf/sessions/session_1752877884393_sqr7trvjl.json +7 -0
- package/.aiwf/sessions/session_1752877884500_whtypmzlf.json +7 -0
- package/.aiwf/sessions/session_1752877884645_y9mvvxli1.json +7 -0
- package/.aiwf/sessions/session_1752877884733_hk46eenel.json +7 -0
- package/.aiwf/sessions/session_1752877884738_c62j6214r.json +7 -0
- package/.aiwf/sessions/session_1752877884771_temhxehoz.json +7 -0
- package/.aiwf/sessions/session_1752877885643_6v5fcktc7.json +7 -0
- package/.aiwf/sessions/session_1752877886426_ee8di2fqh.json +7 -0
- package/.aiwf/sessions/session_1752877886549_3xj6tdoew.json +7 -0
- package/.aiwf/sessions/session_1752877886604_aj80yyfco.json +7 -0
- package/.aiwf/sessions/session_1752877886702_2b8841vyg.json +7 -0
- package/.aiwf/sessions/session_1752877886712_ntqc8b8bz.json +7 -0
- package/.aiwf/sessions/session_1752877886968_946m8tbw6.json +7 -0
- package/.aiwf/sessions/session_1752877887008_wg6zfg8dn.json +7 -0
- package/.aiwf/sessions/session_1752877888479_ciiu2qiqs.json +7 -0
- package/.aiwf/sessions/session_1752877889330_ravg8l66k.json +7 -0
- package/.aiwf/sessions/session_1752877889542_7zepkhjkc.json +7 -0
- package/.aiwf/sessions/session_1752877889638_ev4htqiu0.json +7 -0
- package/.aiwf/sessions/session_1752877889956_szphy2qma.json +7 -0
- package/.aiwf/sessions/session_1752877890220_o6aaa57tt.json +7 -0
- package/.aiwf/sessions/session_1752877890527_v2loy5vu1.json +7 -0
- package/.aiwf/sessions/session_1752877890572_cwq8fiuma.json +7 -0
- package/.aiwf/sessions/session_1752877892200_sgcwq1alt.json +7 -0
- package/.aiwf/sessions/session_1752877892268_v8kexpbc5.json +7 -0
- package/.aiwf/sessions/session_1752877892384_1gbbgrp4y.json +7 -0
- package/.aiwf/sessions/session_1752877893568_9p2n5p31a.json +7 -0
- package/.aiwf/sessions/session_1752877893727_un34a5tvp.json +7 -0
- package/.aiwf/sessions/session_1752877893778_01j79sw9i.json +7 -0
- package/.aiwf/sessions/session_1752877894053_jrhba6wko.json +7 -0
- package/.aiwf/sessions/session_1752877894082_x3bghawox.json +7 -0
- package/.aiwf/sessions/session_1752877894500_iytiav2ku.json +7 -0
- package/.aiwf/sessions/session_1752877894667_ucn0gc2ge.json +7 -0
- package/.aiwf/test-results-s02.json +4 -4
- package/.claude/commands/aiwf/aiwf_changelog.md +82 -0
- package/.claude/commands/aiwf/aiwf_code_review.md +88 -0
- package/.claude/commands/aiwf/aiwf_commit.md +160 -0
- package/.claude/commands/aiwf/aiwf_compress_aggressive_persona.md +53 -0
- package/.claude/commands/aiwf/aiwf_compress_context.md +116 -0
- package/.claude/commands/aiwf/aiwf_compress_minimal_persona.md +48 -0
- package/.claude/commands/aiwf/aiwf_compress_stats.md +79 -0
- package/.claude/commands/aiwf/aiwf_compress_with_persona.md +51 -0
- package/.claude/commands/aiwf/aiwf_create_feature_ledger.md +54 -0
- package/.claude/commands/aiwf/aiwf_create_general_task.md +147 -0
- package/.claude/commands/aiwf/aiwf_create_milestone_plan.md +194 -0
- package/.claude/commands/aiwf/aiwf_create_prd.md +280 -0
- package/.claude/commands/aiwf/aiwf_create_sprint_tasks.md +189 -0
- package/.claude/commands/aiwf/aiwf_create_sprints_from_milestone.md +121 -0
- package/.claude/commands/aiwf/aiwf_default_mode.md +44 -0
- package/.claude/commands/aiwf/aiwf_discuss_review.md +29 -0
- package/.claude/commands/aiwf/aiwf_do_task.md +109 -0
- package/.claude/commands/aiwf/aiwf_docs.md +225 -0
- package/.claude/commands/aiwf/aiwf_evaluate.md +92 -0
- package/.claude/commands/aiwf/aiwf_get_feature_details.md +78 -0
- package/.claude/commands/aiwf/aiwf_infinite.md +202 -0
- package/.claude/commands/aiwf/aiwf_initialize.md +134 -0
- package/.claude/commands/aiwf/aiwf_issue_create.md +65 -0
- package/.claude/commands/aiwf/aiwf_link_feature_to_milestone.md +57 -0
- package/.claude/commands/aiwf/aiwf_list_features.md +54 -0
- package/.claude/commands/aiwf/aiwf_mermaid.md +272 -0
- package/.claude/commands/aiwf/aiwf_persona_architect.md +48 -0
- package/.claude/commands/aiwf/aiwf_persona_backend.md +37 -0
- package/.claude/commands/aiwf/aiwf_persona_data_analyst.md +37 -0
- package/.claude/commands/aiwf/aiwf_persona_frontend.md +37 -0
- package/.claude/commands/aiwf/aiwf_persona_security.md +48 -0
- package/.claude/commands/aiwf/aiwf_persona_status.md +50 -0
- package/.claude/commands/aiwf/aiwf_pr_create.md +76 -0
- package/.claude/commands/aiwf/aiwf_prime.md +9 -0
- package/.claude/commands/aiwf/aiwf_project_review.md +261 -0
- package/.claude/commands/aiwf/aiwf_switch_language.md +160 -0
- package/.claude/commands/aiwf/aiwf_test.md +124 -0
- package/.claude/commands/aiwf/aiwf_testing_review.md +198 -0
- package/.claude/commands/aiwf/aiwf_tm-run-all-subtask.md +210 -0
- package/.claude/commands/aiwf/aiwf_ultrathink_code_advanced.md +460 -0
- package/.claude/commands/aiwf/aiwf_ultrathink_code_basic.md +165 -0
- package/.claude/commands/aiwf/aiwf_ultrathink_general.md +305 -0
- package/.claude/commands/aiwf/aiwf_update_docs_kr.md +25 -0
- package/.claude/commands/aiwf/aiwf_update_feature_status.md +53 -0
- package/.claude/commands/aiwf/aiwf_yolo.md +300 -0
- package/.eslintrc.json +26 -0
- package/.prettierrc +10 -0
- package/CLAUDE.md +154 -54
- package/README.ko.md +25 -0
- package/README.md +25 -0
- package/TEST_RECOVERY_PLAN.md +68 -0
- package/docs/CLEANUP_RECOMMENDATIONS.md +86 -0
- package/docs/CLI_USAGE_GUIDE.md +479 -0
- package/package.json +9 -9
- package/scripts/update-feature-status.js +70 -0
- package/{lib β src/cli}/cache-cli.js +4 -4
- package/src/cli/index.js +165 -0
- package/{language-cli.js β src/cli/language-cli.js} +11 -4
- package/src/commands/cache-templates.js +209 -0
- package/src/commands/create-offline.js +86 -0
- package/src/commands/create-project.js +151 -0
- package/src/lib/backup-manager.js +270 -0
- package/src/lib/file-downloader.js +208 -0
- package/src/lib/installer.js +676 -0
- package/src/lib/rollback-manager.js +418 -0
- package/src/lib/validator.js +561 -0
- package/src/utils/git-integration.js +173 -0
- package/{language-utils.js β src/utils/language-utils.js} +2 -1
- package/src/utils/messages.js +136 -0
- package/src/utils/paths.js +130 -0
- package/tests/application.test.js +2 -2
- package/tests/installer.test.js +2 -2
- package/tests/integration/ai-persona-system.test.js +4 -4
- package/tests/integration/test-persona-system/docs/README.md +1 -0
- package/tests/integration/test-persona-system/package.json +1 -0
- package/tests/integration/test-persona-system/src/auth.js +1 -0
- package/tests/integration/test-persona-system/src/index.js +1 -0
- package/tests/integration/test-persona-system/test/test.js +1 -0
- package/tests/language-utils.test.js +1 -1
- package/tests/temp_invalid_json/invalid.json +1 -0
- package/tests/unit/ai-persona-manager.test.js +7 -7
- package/.aiwf/05_DOCUMENTATION/performance-report.md +0 -33
- package/.aiwf/test-cache/86c7f9e80251abb8bedd58953ab0d1779e46b4f43ea33b63bffdb778106373b0.json +0 -6
- package/.aiwf/test-cache/9e252a90b80fce0585d7ed0646e7a9b4343e109f661f42a3ae678ba1ec5e22c7.json +0 -7
- package/.aiwf/token-data/alerts.json +0 -293
- package/.aiwf/token-data/monitor-config.json +0 -12
- package/CHANGELOG_M02.md +0 -52
- package/claude-code/aiwf/ko/templates/web-app/template/package-lock.json +0 -10022
- package/claude-code/aiwf/ko/token-data/alerts.json +0 -293
- package/claude-code/aiwf/ko/token-data/monitor-config.json +0 -12
- package/docs/m02-dod-checklist.md +0 -125
- package/docs/m02-gap-analysis.md +0 -214
- package/docs/m02-milestone-completion-report.md +0 -186
- package/docs/m02-milestone-executive-summary.md +0 -68
- package/index.js +0 -1951
- package/lib/cache-integration.js +0 -401
- package/lib/file-batch-processor.js +0 -401
- package/lib/performance-benchmark.js +0 -621
- package/lib/template-manager.js +0 -310
- package/scripts/validate-commands.cjs +0 -338
- /package/{lib β src/lib}/ai-persona-manager.js +0 -0
- /package/{lib β src/lib}/cache-system.js +0 -0
- /package/{lib β src/lib}/context-engine.js +0 -0
- /package/{lib β src/lib}/memory-profiler.js +0 -0
- /package/{lib β src/lib}/metrics-collector.js +0 -0
- /package/{lib β src/lib}/offline-detector.js +0 -0
- /package/{lib β src/lib}/task-analyzer.js +0 -0
- /package/{lib β src/lib}/template-cache-system.js +0 -0
- /package/{lib β src/lib}/template-downloader.js +0 -0
- /package/{lib β src/lib}/template-version-manager.js +0 -0
- /package/{lib β src/lib}/token-optimizer.js +0 -0
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import prompts from 'prompts';
|
|
7
|
+
import {
|
|
8
|
+
fetchContent,
|
|
9
|
+
downloadFile,
|
|
10
|
+
downloadDirectory,
|
|
11
|
+
GITHUB_RAW_URL,
|
|
12
|
+
GITHUB_CONTENT_PREFIX
|
|
13
|
+
} from './file-downloader.js';
|
|
14
|
+
import {
|
|
15
|
+
backupCommandsAndDocs,
|
|
16
|
+
restoreFromBackup,
|
|
17
|
+
getCurrentBackupDir,
|
|
18
|
+
setBackupDir
|
|
19
|
+
} from './backup-manager.js';
|
|
20
|
+
import { getMessages } from '../utils/messages.js';
|
|
21
|
+
import {
|
|
22
|
+
validateInstallation,
|
|
23
|
+
displaySpecCompliantValidationResults
|
|
24
|
+
} from './validator.js';
|
|
25
|
+
import { rollbackTool } from './rollback-manager.js';
|
|
26
|
+
import {
|
|
27
|
+
detectLanguage,
|
|
28
|
+
loadUserLanguageConfig,
|
|
29
|
+
saveUserLanguageConfig,
|
|
30
|
+
getInstallationLanguagePath,
|
|
31
|
+
SUPPORTED_LANGUAGES,
|
|
32
|
+
DEFAULT_LANGUAGE
|
|
33
|
+
} from '../utils/language-utils.js';
|
|
34
|
+
import {
|
|
35
|
+
FILES,
|
|
36
|
+
TOOL_DIRS,
|
|
37
|
+
TEMP_DIRS,
|
|
38
|
+
AIWF_DIRS,
|
|
39
|
+
getAllAiwfDirectories,
|
|
40
|
+
getAllClaudeMdFiles
|
|
41
|
+
} from '../utils/paths.js';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if AIWF is already installed
|
|
45
|
+
* @returns {Promise<boolean>} Installation status
|
|
46
|
+
*/
|
|
47
|
+
async function checkExistingInstallation() {
|
|
48
|
+
const flagExists = await fs
|
|
49
|
+
.access(FILES.INSTALLED_FLAG)
|
|
50
|
+
.then(() => true)
|
|
51
|
+
.catch(() => false);
|
|
52
|
+
return flagExists;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Log with spinner for debug mode
|
|
57
|
+
* @param {Object} spinner - Ora spinner instance
|
|
58
|
+
* @param {string} message - Message to log
|
|
59
|
+
* @param {boolean} debugLog - Debug mode flag
|
|
60
|
+
*/
|
|
61
|
+
function logWithSpinner(spinner, message, debugLog) {
|
|
62
|
+
if (debugLog) console.log(chalk.gray(message));
|
|
63
|
+
if (spinner) spinner.text = message;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Select language for installation
|
|
68
|
+
* @param {Object} options - Installation options
|
|
69
|
+
* @returns {Promise<string>} Selected language
|
|
70
|
+
*/
|
|
71
|
+
async function selectLanguage(options) {
|
|
72
|
+
let selectedLanguage = DEFAULT_LANGUAGE;
|
|
73
|
+
|
|
74
|
+
if (!options.force) {
|
|
75
|
+
// 1. Check existing configuration
|
|
76
|
+
const existingConfig = await loadUserLanguageConfig();
|
|
77
|
+
|
|
78
|
+
if (existingConfig && existingConfig.language) {
|
|
79
|
+
selectedLanguage = existingConfig.language;
|
|
80
|
+
console.log(
|
|
81
|
+
chalk.blue(
|
|
82
|
+
`π κΈ°μ‘΄ μΈμ΄ μ€μ μ μ¬μ©ν©λλ€ / Using existing language setting: ${selectedLanguage}`
|
|
83
|
+
)
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
// 2. Try auto-detection
|
|
87
|
+
const detectedLang = await detectLanguage();
|
|
88
|
+
|
|
89
|
+
console.log(
|
|
90
|
+
chalk.gray(`π μμ€ν
μΈμ΄ κ°μ§ / System language detected: ${detectedLang}`)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// 3. Prompt user for language selection
|
|
94
|
+
const languageResponse = await prompts({
|
|
95
|
+
type: 'select',
|
|
96
|
+
name: 'language',
|
|
97
|
+
message: 'Please select language / μΈμ΄λ₯Ό μ νν΄μ£ΌμΈμ:',
|
|
98
|
+
choices: [
|
|
99
|
+
{ title: 'English', value: 'en' },
|
|
100
|
+
{ title: 'νκ΅μ΄ (Korean)', value: 'ko' }
|
|
101
|
+
],
|
|
102
|
+
initial:
|
|
103
|
+
SUPPORTED_LANGUAGES.indexOf(detectedLang) !== -1
|
|
104
|
+
? SUPPORTED_LANGUAGES.indexOf(detectedLang)
|
|
105
|
+
: 0
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (languageResponse.language) {
|
|
109
|
+
selectedLanguage = languageResponse.language;
|
|
110
|
+
} else {
|
|
111
|
+
selectedLanguage = detectedLang;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 4. Save selected language
|
|
115
|
+
try {
|
|
116
|
+
await saveUserLanguageConfig(selectedLanguage, {
|
|
117
|
+
autoDetect: true,
|
|
118
|
+
fallback: DEFAULT_LANGUAGE
|
|
119
|
+
});
|
|
120
|
+
console.log(
|
|
121
|
+
chalk.green(
|
|
122
|
+
`β
μΈμ΄ μ€μ μ΄ μ μ₯λμμ΅λλ€ / Language preference saved: ${selectedLanguage}`
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.warn(
|
|
127
|
+
chalk.yellow(
|
|
128
|
+
`β οΈ μΈμ΄ μ€μ μ μ₯ μ€ν¨ / Failed to save language preference: ${error.message}`
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
// Force mode: try auto-detection
|
|
135
|
+
try {
|
|
136
|
+
selectedLanguage = await detectLanguage();
|
|
137
|
+
} catch (error) {
|
|
138
|
+
selectedLanguage = DEFAULT_LANGUAGE;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return selectedLanguage;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Handle existing installation
|
|
147
|
+
* @param {Object} msg - Localized messages
|
|
148
|
+
* @param {Object} options - Installation options
|
|
149
|
+
* @returns {Promise<boolean>} Continue with installation
|
|
150
|
+
*/
|
|
151
|
+
async function handleExistingInstallation(msg, options) {
|
|
152
|
+
if (options.force) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const response = await prompts({
|
|
157
|
+
type: 'select',
|
|
158
|
+
name: 'action',
|
|
159
|
+
message: msg.existingDetected,
|
|
160
|
+
choices: [
|
|
161
|
+
{ title: msg.updateOption, value: 'update' },
|
|
162
|
+
{ title: msg.skipOption, value: 'skip' },
|
|
163
|
+
{ title: msg.cancelOption, value: 'cancel' }
|
|
164
|
+
]
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (response.action === 'skip' || response.action === 'cancel') {
|
|
168
|
+
console.log(chalk.yellow(msg.installCancelled));
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (response.action === 'update') {
|
|
173
|
+
await backupCommandsAndDocs(msg);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create AIWF directory structure
|
|
181
|
+
*/
|
|
182
|
+
async function createDirectoryStructure() {
|
|
183
|
+
const aiwfDirs = getAllAiwfDirectories();
|
|
184
|
+
|
|
185
|
+
for (const dir of aiwfDirs) {
|
|
186
|
+
await fs.mkdir(dir, { recursive: true });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Download manifest file
|
|
192
|
+
* @param {string} languagePath - Language path
|
|
193
|
+
* @param {Object} spinner - Ora spinner instance
|
|
194
|
+
* @param {Object} msg - Localized messages
|
|
195
|
+
* @param {boolean} debugLog - Debug mode flag
|
|
196
|
+
*/
|
|
197
|
+
async function downloadManifest(languagePath, spinner, msg, debugLog) {
|
|
198
|
+
logWithSpinner(spinner, msg.downloading, debugLog);
|
|
199
|
+
try {
|
|
200
|
+
const manifestUrl = `${GITHUB_RAW_URL}/${GITHUB_CONTENT_PREFIX}/${languagePath}/.aiwf/00_PROJECT_MANIFEST.md`;
|
|
201
|
+
await downloadFile(manifestUrl, FILES.PROJECT_MANIFEST);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
// If manifest doesn't exist, that's okay
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Download templates
|
|
209
|
+
* @param {string} languagePath - Language path
|
|
210
|
+
* @param {Object} spinner - Ora spinner instance
|
|
211
|
+
* @param {Object} msg - Localized messages
|
|
212
|
+
* @param {boolean} debugLog - Debug mode flag
|
|
213
|
+
*/
|
|
214
|
+
async function downloadTemplates(languagePath, spinner, msg, debugLog) {
|
|
215
|
+
try {
|
|
216
|
+
logWithSpinner(spinner, msg.downloadingTemplates, debugLog);
|
|
217
|
+
await downloadDirectory(
|
|
218
|
+
`${GITHUB_CONTENT_PREFIX}/${languagePath}/.aiwf/98_PROMPTS`,
|
|
219
|
+
AIWF_DIRS.PROMPTS,
|
|
220
|
+
spinner,
|
|
221
|
+
msg
|
|
222
|
+
);
|
|
223
|
+
await downloadDirectory(
|
|
224
|
+
`${GITHUB_CONTENT_PREFIX}/${languagePath}/.aiwf/99_TEMPLATES`,
|
|
225
|
+
AIWF_DIRS.TEMPLATES,
|
|
226
|
+
spinner,
|
|
227
|
+
msg
|
|
228
|
+
);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
logWithSpinner(spinner, msg.templatesNotFound, debugLog);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Update CLAUDE.md documentation files
|
|
236
|
+
* @param {string} languagePath - Language path
|
|
237
|
+
* @param {Object} spinner - Ora spinner instance
|
|
238
|
+
* @param {Object} msg - Localized messages
|
|
239
|
+
* @param {boolean} debugLog - Debug mode flag
|
|
240
|
+
*/
|
|
241
|
+
async function updateDocumentation(languagePath, spinner, msg, debugLog) {
|
|
242
|
+
logWithSpinner(spinner, msg.updatingDocs, debugLog);
|
|
243
|
+
const claudeFiles = getAllClaudeMdFiles();
|
|
244
|
+
|
|
245
|
+
for (const claudeFile of claudeFiles) {
|
|
246
|
+
try {
|
|
247
|
+
const relativePath = claudeFile.replace(process.cwd() + '/', '');
|
|
248
|
+
const claudeUrl = `${GITHUB_RAW_URL}/${GITHUB_CONTENT_PREFIX}/${languagePath}/${relativePath}`;
|
|
249
|
+
await downloadFile(claudeUrl, claudeFile);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
// If CLAUDE.md doesn't exist, that's okay
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Update commands
|
|
258
|
+
* @param {string} languagePath - Language path
|
|
259
|
+
* @param {Object} spinner - Ora spinner instance
|
|
260
|
+
* @param {Object} msg - Localized messages
|
|
261
|
+
* @param {boolean} debugLog - Debug mode flag
|
|
262
|
+
*/
|
|
263
|
+
async function updateCommands(languagePath, spinner, msg, debugLog) {
|
|
264
|
+
// Delete existing commands folder if it exists
|
|
265
|
+
if (await fs.access(TOOL_DIRS.CLAUDE_COMMANDS).then(() => true).catch(() => false)) {
|
|
266
|
+
logWithSpinner(spinner, msg.deletingOldCommands, debugLog);
|
|
267
|
+
await fs.rm(TOOL_DIRS.CLAUDE_COMMANDS, { recursive: true, force: true });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
await fs.mkdir(TOOL_DIRS.CLAUDE_COMMANDS, { recursive: true });
|
|
271
|
+
|
|
272
|
+
// Download commands
|
|
273
|
+
logWithSpinner(spinner, msg.downloadingCommands, debugLog);
|
|
274
|
+
try {
|
|
275
|
+
await downloadDirectory(
|
|
276
|
+
`${GITHUB_CONTENT_PREFIX}/${languagePath}/${TOOL_DIRS.CLAUDE_COMMANDS}`,
|
|
277
|
+
TOOL_DIRS.CLAUDE_COMMANDS,
|
|
278
|
+
spinner,
|
|
279
|
+
msg,
|
|
280
|
+
languagePath
|
|
281
|
+
);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
logWithSpinner(spinner, msg.commandsNotFound, debugLog);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Download and process rules
|
|
289
|
+
* @param {Object} spinner - Ora spinner instance
|
|
290
|
+
* @param {Object} msg - Localized messages
|
|
291
|
+
* @param {boolean} debugLog - Debug mode flag
|
|
292
|
+
*/
|
|
293
|
+
async function downloadAndProcessRules(spinner, msg, debugLog) {
|
|
294
|
+
// Try to download from GitHub first, fallback to local files
|
|
295
|
+
const tmpRulesGlobal = path.join(os.tmpdir(), 'aiwf-rules-global');
|
|
296
|
+
const tmpRulesManual = path.join(os.tmpdir(), 'aiwf-rules-manual');
|
|
297
|
+
|
|
298
|
+
let useLocalGlobal = false;
|
|
299
|
+
let useLocalManual = false;
|
|
300
|
+
|
|
301
|
+
// Try to download rules/global from GitHub
|
|
302
|
+
try {
|
|
303
|
+
await downloadDirectory(
|
|
304
|
+
`${GITHUB_CONTENT_PREFIX}/rules/global`,
|
|
305
|
+
tmpRulesGlobal,
|
|
306
|
+
spinner,
|
|
307
|
+
debugLog
|
|
308
|
+
);
|
|
309
|
+
logWithSpinner(spinner, `Downloaded rules/global from GitHub`, debugLog);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
logWithSpinner(spinner, `Failed to download rules/global from GitHub, trying local files`, debugLog);
|
|
312
|
+
// Fallback to local files
|
|
313
|
+
const localRulesGlobal = 'rules/global';
|
|
314
|
+
try {
|
|
315
|
+
await fs.access(localRulesGlobal);
|
|
316
|
+
// Copy local files to temp directory
|
|
317
|
+
await fs.mkdir(tmpRulesGlobal, { recursive: true });
|
|
318
|
+
const files = await fs.readdir(localRulesGlobal);
|
|
319
|
+
for (const file of files) {
|
|
320
|
+
const srcPath = path.join(localRulesGlobal, file);
|
|
321
|
+
const destPath = path.join(tmpRulesGlobal, file);
|
|
322
|
+
await fs.copyFile(srcPath, destPath);
|
|
323
|
+
}
|
|
324
|
+
useLocalGlobal = true;
|
|
325
|
+
logWithSpinner(spinner, `Using local rules/global directory`, debugLog);
|
|
326
|
+
} catch (localError) {
|
|
327
|
+
logWithSpinner(spinner, msg.rulesGlobalNotFound, debugLog);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Try to download rules/manual from GitHub
|
|
332
|
+
try {
|
|
333
|
+
await downloadDirectory(
|
|
334
|
+
`${GITHUB_CONTENT_PREFIX}/rules/manual`,
|
|
335
|
+
tmpRulesManual,
|
|
336
|
+
spinner,
|
|
337
|
+
debugLog
|
|
338
|
+
);
|
|
339
|
+
logWithSpinner(spinner, `Downloaded rules/manual from GitHub`, debugLog);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
logWithSpinner(spinner, `Failed to download rules/manual from GitHub, trying local files`, debugLog);
|
|
342
|
+
// Fallback to local files
|
|
343
|
+
const localRulesManual = 'rules/manual';
|
|
344
|
+
try {
|
|
345
|
+
await fs.access(localRulesManual);
|
|
346
|
+
// Copy local files to temp directory
|
|
347
|
+
await fs.mkdir(tmpRulesManual, { recursive: true });
|
|
348
|
+
const files = await fs.readdir(localRulesManual);
|
|
349
|
+
for (const file of files) {
|
|
350
|
+
const srcPath = path.join(localRulesManual, file);
|
|
351
|
+
const destPath = path.join(tmpRulesManual, file);
|
|
352
|
+
await fs.copyFile(srcPath, destPath);
|
|
353
|
+
}
|
|
354
|
+
useLocalManual = true;
|
|
355
|
+
logWithSpinner(spinner, `Using local rules/manual directory`, debugLog);
|
|
356
|
+
} catch (localError) {
|
|
357
|
+
logWithSpinner(spinner, msg.rulesManualNotFound, debugLog);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Process rules/global
|
|
362
|
+
try {
|
|
363
|
+
await fs.access(tmpRulesGlobal);
|
|
364
|
+
await fs.mkdir(TOOL_DIRS.CURSOR_RULES, { recursive: true });
|
|
365
|
+
await fs.mkdir(TOOL_DIRS.WINDSURF_RULES, { recursive: true });
|
|
366
|
+
const files = await fs.readdir(tmpRulesGlobal);
|
|
367
|
+
for (const file of files) {
|
|
368
|
+
const srcPath = path.join(tmpRulesGlobal, file);
|
|
369
|
+
const stat = await fs.stat(srcPath);
|
|
370
|
+
if (!stat.isFile()) continue;
|
|
371
|
+
|
|
372
|
+
// .cursor/rules: add .mdc extension and header
|
|
373
|
+
const base = path.parse(file).name;
|
|
374
|
+
const cursorTarget = path.join(TOOL_DIRS.CURSOR_RULES, base + '.mdc');
|
|
375
|
+
const content = await fs.readFile(srcPath, 'utf8');
|
|
376
|
+
const header = '---\ndescription: \nglobs: \nalwaysApply: true\n---\n';
|
|
377
|
+
await fs.writeFile(cursorTarget, header + content, 'utf8');
|
|
378
|
+
logWithSpinner(
|
|
379
|
+
spinner,
|
|
380
|
+
`[rules/global] ${file} -> ${TOOL_DIRS.CURSOR_RULES}/${base}.mdc`,
|
|
381
|
+
debugLog
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
// .windsurf/rules: keep extension, no header
|
|
385
|
+
const windsurfTarget = path.join(TOOL_DIRS.WINDSURF_RULES, file);
|
|
386
|
+
const windsurfContent = await fs.readFile(srcPath, 'utf8');
|
|
387
|
+
const windsurfHeader = '---\ntrigger: always_on\n---\n';
|
|
388
|
+
await fs.writeFile(windsurfTarget, windsurfHeader + windsurfContent, 'utf8');
|
|
389
|
+
logWithSpinner(
|
|
390
|
+
spinner,
|
|
391
|
+
`[rules/global] ${file} -> ${TOOL_DIRS.WINDSURF_RULES}/${file}`,
|
|
392
|
+
debugLog
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
// Clean up temp folder (only if not using local files)
|
|
396
|
+
if (!useLocalGlobal) {
|
|
397
|
+
await fs.rm(tmpRulesGlobal, { recursive: true, force: true });
|
|
398
|
+
}
|
|
399
|
+
} catch (error) {
|
|
400
|
+
logWithSpinner(spinner, `No rules/global directory found`, debugLog);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Process rules/manual
|
|
404
|
+
try {
|
|
405
|
+
await fs.access(tmpRulesManual);
|
|
406
|
+
await fs.mkdir(TOOL_DIRS.CURSOR_RULES, { recursive: true });
|
|
407
|
+
await fs.mkdir(TOOL_DIRS.WINDSURF_RULES, { recursive: true });
|
|
408
|
+
const files = await fs.readdir(tmpRulesManual);
|
|
409
|
+
for (const file of files) {
|
|
410
|
+
const srcPath = path.join(tmpRulesManual, file);
|
|
411
|
+
const stat = await fs.stat(srcPath);
|
|
412
|
+
if (!stat.isFile()) continue;
|
|
413
|
+
|
|
414
|
+
// .cursor/rules: add .mdc extension and header (alwaysApply: false)
|
|
415
|
+
const base = path.parse(file).name;
|
|
416
|
+
const cursorTarget = path.join(TOOL_DIRS.CURSOR_RULES, base + '.mdc');
|
|
417
|
+
const content = await fs.readFile(srcPath, 'utf8');
|
|
418
|
+
const header = '---\ndescription: \nglobs: \nalwaysApply: false\n---\n';
|
|
419
|
+
await fs.writeFile(cursorTarget, header + content, 'utf8');
|
|
420
|
+
logWithSpinner(
|
|
421
|
+
spinner,
|
|
422
|
+
`[rules/manual] ${file} -> ${TOOL_DIRS.CURSOR_RULES}/${base}.mdc`,
|
|
423
|
+
debugLog
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
// .windsurf/rules: keep extension, no header
|
|
427
|
+
const windsurfTarget = path.join(TOOL_DIRS.WINDSURF_RULES, file);
|
|
428
|
+
const windsurfContent = await fs.readFile(srcPath, 'utf8');
|
|
429
|
+
const windsurfHeader = '---\ntrigger: manual\n---\n';
|
|
430
|
+
await fs.writeFile(windsurfTarget, windsurfHeader + windsurfContent, 'utf8');
|
|
431
|
+
logWithSpinner(
|
|
432
|
+
spinner,
|
|
433
|
+
`[rules/manual] ${file} -> ${TOOL_DIRS.WINDSURF_RULES}/${file}`,
|
|
434
|
+
debugLog
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
// Clean up temp folder (only if not using local files)
|
|
438
|
+
if (!useLocalManual) {
|
|
439
|
+
await fs.rm(tmpRulesManual, { recursive: true, force: true });
|
|
440
|
+
}
|
|
441
|
+
} catch (error) {
|
|
442
|
+
logWithSpinner(spinner, `No rules/manual directory found`, debugLog);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Display installation summary
|
|
448
|
+
* @param {boolean} hasExisting - Has existing installation
|
|
449
|
+
* @param {Object} msg - Localized messages
|
|
450
|
+
*/
|
|
451
|
+
function displaySummary(hasExisting, msg) {
|
|
452
|
+
if (hasExisting) {
|
|
453
|
+
console.log(chalk.blue(`\n${msg.updateHistory}`));
|
|
454
|
+
console.log(chalk.gray(` β’ ${TOOL_DIRS.CLAUDE_COMMANDS}${msg.updatedCommands}`));
|
|
455
|
+
console.log(chalk.gray(` β’ ${msg.updatedDocs}`));
|
|
456
|
+
console.log(chalk.green(`\n${msg.workPreserved}`));
|
|
457
|
+
console.log(chalk.gray(` β’ ${msg.allFilesPreserved}`));
|
|
458
|
+
console.log(chalk.gray(` β’ ${msg.backupCreated}`));
|
|
459
|
+
} else {
|
|
460
|
+
console.log(chalk.blue(`\n${msg.structureCreated}`));
|
|
461
|
+
console.log(chalk.gray(` ${AIWF_DIRS.ROOT}/ - ${msg.aiwfRoot}`));
|
|
462
|
+
console.log(chalk.gray(` ${TOOL_DIRS.CLAUDE_COMMANDS}/ - ${msg.claudeCommands}`));
|
|
463
|
+
|
|
464
|
+
console.log(chalk.green(`\n${msg.nextSteps}`));
|
|
465
|
+
console.log(chalk.white(` 1. ${msg.nextStep1}`));
|
|
466
|
+
console.log(chalk.white(` 2. ${msg.nextStep2}`));
|
|
467
|
+
console.log(chalk.white(` 3. ${msg.nextStep3}\n`));
|
|
468
|
+
|
|
469
|
+
console.log(chalk.blue(`\n${msg.gettingStarted}`));
|
|
470
|
+
console.log(chalk.gray(` 1. ${msg.startStep1}`));
|
|
471
|
+
console.log(chalk.gray(` 2. ${msg.startStep2}`));
|
|
472
|
+
console.log(chalk.gray(`\n${msg.checkDocs}`));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Offer reinstallation options
|
|
478
|
+
* @param {Object} validationResults - Validation results
|
|
479
|
+
* @param {string} language - Language code
|
|
480
|
+
* @returns {Promise<string>} Selected action
|
|
481
|
+
*/
|
|
482
|
+
async function offerReinstallationOptions(validationResults, language) {
|
|
483
|
+
console.log(
|
|
484
|
+
chalk.yellow(
|
|
485
|
+
language === 'ko'
|
|
486
|
+
? '\nβ οΈ μΌλΆ μ€μΉκ° μ€ν¨νμ΅λλ€. 무μμ νμκ² μ΅λκΉ?'
|
|
487
|
+
: '\nβ οΈ Some installations failed. What would you like to do?'
|
|
488
|
+
)
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
// List failed tools
|
|
492
|
+
const failedTools = validationResults.failed.map(({ tool }) => tool);
|
|
493
|
+
|
|
494
|
+
if (failedTools.length > 0) {
|
|
495
|
+
console.log(
|
|
496
|
+
chalk.red(
|
|
497
|
+
language === 'ko'
|
|
498
|
+
? `μ€ν¨ν λꡬ: ${failedTools.join(', ')}`
|
|
499
|
+
: `Failed tools: ${failedTools.join(', ')}`
|
|
500
|
+
)
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const response = await prompts({
|
|
505
|
+
type: 'select',
|
|
506
|
+
name: 'action',
|
|
507
|
+
message:
|
|
508
|
+
language === 'ko' ? 'μ΄λ€ μμ
μ μννμκ² μ΅λκΉ?' : 'What would you like to do?',
|
|
509
|
+
choices: [
|
|
510
|
+
{
|
|
511
|
+
title: language === 'ko' ? 'μ 체 μ¬μ€μΉ' : 'Full reinstallation',
|
|
512
|
+
value: 'reinstall'
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
title: language === 'ko' ? 'μ€ν¨ν λκ΅¬λ§ λ‘€λ°±' : 'Rollback failed tools only',
|
|
516
|
+
value: 'rollback'
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
title: language === 'ko' ? 'νμ¬ μν μ μ§' : 'Keep current state',
|
|
520
|
+
value: 'keep'
|
|
521
|
+
}
|
|
522
|
+
]
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
return response.action;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Main installation function
|
|
530
|
+
* @param {Object} options - Installation options
|
|
531
|
+
*/
|
|
532
|
+
export async function installAIWF(options = {}) {
|
|
533
|
+
const debugLog = options.debugLog || false;
|
|
534
|
+
|
|
535
|
+
// Select language
|
|
536
|
+
const selectedLanguage = await selectLanguage(options);
|
|
537
|
+
const msg = getMessages(selectedLanguage);
|
|
538
|
+
|
|
539
|
+
console.log(chalk.blue.bold(msg.welcome));
|
|
540
|
+
console.log(chalk.gray(msg.description));
|
|
541
|
+
console.log(chalk.gray(msg.optimized));
|
|
542
|
+
|
|
543
|
+
// Set language path
|
|
544
|
+
const languagePath = getInstallationLanguagePath(selectedLanguage);
|
|
545
|
+
const GITHUB_CONTENT_LANGUAGE_PREFIX = `${GITHUB_CONTENT_PREFIX}/${languagePath}`;
|
|
546
|
+
|
|
547
|
+
// Check existing installation
|
|
548
|
+
const hasExisting = await checkExistingInstallation();
|
|
549
|
+
|
|
550
|
+
if (hasExisting) {
|
|
551
|
+
const shouldContinue = await handleExistingInstallation(msg, options);
|
|
552
|
+
if (!shouldContinue) return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const spinner = ora(msg.fetching).start();
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
// Create directory structure
|
|
559
|
+
await createDirectoryStructure();
|
|
560
|
+
|
|
561
|
+
// Download manifest (fresh installs only)
|
|
562
|
+
if (!hasExisting) {
|
|
563
|
+
await downloadManifest(languagePath, spinner, msg, debugLog);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Download templates
|
|
567
|
+
await downloadTemplates(languagePath, spinner, msg, debugLog);
|
|
568
|
+
|
|
569
|
+
// Update documentation
|
|
570
|
+
await updateDocumentation(languagePath, spinner, msg, debugLog);
|
|
571
|
+
|
|
572
|
+
// Update commands
|
|
573
|
+
await updateCommands(languagePath, spinner, msg, debugLog);
|
|
574
|
+
|
|
575
|
+
// Download and process rules
|
|
576
|
+
await downloadAndProcessRules(spinner, msg, debugLog);
|
|
577
|
+
|
|
578
|
+
// Success
|
|
579
|
+
if (hasExisting) {
|
|
580
|
+
spinner.succeed(chalk.green(msg.updateSuccess));
|
|
581
|
+
} else {
|
|
582
|
+
spinner.succeed(chalk.green(msg.installSuccess));
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Display summary
|
|
586
|
+
displaySummary(hasExisting, msg);
|
|
587
|
+
|
|
588
|
+
// Run validation
|
|
589
|
+
console.log(
|
|
590
|
+
chalk.blue('\nπ Installation Validation / μ€μΉ κ²μ¦μ μμν©λλ€...')
|
|
591
|
+
);
|
|
592
|
+
const selectedTools = ['claude-code', 'cursor', 'windsurf'];
|
|
593
|
+
const validationResults = await validateInstallation(
|
|
594
|
+
selectedTools,
|
|
595
|
+
selectedLanguage
|
|
596
|
+
);
|
|
597
|
+
displaySpecCompliantValidationResults(validationResults, selectedLanguage);
|
|
598
|
+
|
|
599
|
+
// Handle partial failures
|
|
600
|
+
if (validationResults.failed.length > 0) {
|
|
601
|
+
const action = await offerReinstallationOptions(
|
|
602
|
+
validationResults,
|
|
603
|
+
selectedLanguage
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
if (action === 'reinstall') {
|
|
607
|
+
console.log(
|
|
608
|
+
chalk.blue('\nπ μ¬μ€μΉλ₯Ό μμν©λλ€... / Starting reinstallation...')
|
|
609
|
+
);
|
|
610
|
+
// Retry installation once
|
|
611
|
+
if (!options.isRetry) {
|
|
612
|
+
return await installAIWF({ ...options, isRetry: true });
|
|
613
|
+
}
|
|
614
|
+
} else if (action === 'rollback') {
|
|
615
|
+
console.log(
|
|
616
|
+
chalk.blue('\nβ©οΈ μ€ν¨ν λꡬλ₯Ό λ‘€λ°±ν©λλ€... / Rolling back failed tools...')
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
// Rollback failed tools
|
|
620
|
+
const failedTools = validationResults.failed.map(({ tool }) => tool);
|
|
621
|
+
const backupDir = getCurrentBackupDir();
|
|
622
|
+
let rollbackResults = {};
|
|
623
|
+
|
|
624
|
+
for (const tool of failedTools) {
|
|
625
|
+
if (backupDir) {
|
|
626
|
+
const rollbackResult = await rollbackTool(
|
|
627
|
+
tool,
|
|
628
|
+
backupDir,
|
|
629
|
+
selectedLanguage
|
|
630
|
+
);
|
|
631
|
+
rollbackResults[tool] = rollbackResult;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Re-validate after rollback
|
|
636
|
+
console.log(
|
|
637
|
+
chalk.blue('\nπ λ‘€λ°± ν μ¬κ²μ¦... / Re-validating after rollback...')
|
|
638
|
+
);
|
|
639
|
+
const postRollbackValidation = await validateInstallation(
|
|
640
|
+
selectedTools,
|
|
641
|
+
selectedLanguage
|
|
642
|
+
);
|
|
643
|
+
displaySpecCompliantValidationResults(
|
|
644
|
+
postRollbackValidation,
|
|
645
|
+
selectedLanguage
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
// Rollback summary
|
|
649
|
+
console.log(chalk.blue('\n=== Rollback Summary / λ‘€λ°± μμ½ ==='));
|
|
650
|
+
for (const [tool, result] of Object.entries(rollbackResults)) {
|
|
651
|
+
if (result.success) {
|
|
652
|
+
console.log(
|
|
653
|
+
chalk.green(`β
${tool}: ${result.restoredCount} files restored`)
|
|
654
|
+
);
|
|
655
|
+
} else {
|
|
656
|
+
console.log(chalk.red(`β ${tool}: ${result.error}`));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
console.log(chalk.green(msg.enjoy));
|
|
663
|
+
} catch (error) {
|
|
664
|
+
if (hasExisting) {
|
|
665
|
+
spinner.fail(chalk.red(msg.updateFailed));
|
|
666
|
+
await restoreFromBackup(spinner, msg);
|
|
667
|
+
} else {
|
|
668
|
+
spinner.fail(chalk.red(msg.installFailed));
|
|
669
|
+
}
|
|
670
|
+
console.error(chalk.red(error.message));
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Create installation flag file
|
|
675
|
+
await fs.writeFile(FILES.INSTALLED_FLAG, 'installed', 'utf8');
|
|
676
|
+
}
|