@byh3071/vhk 0.6.0 → 0.7.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.
- package/README.md +5 -1
- package/dist/{chunk-IU37BEQA.js → chunk-NJDRNI3S.js} +20 -2
- package/dist/index.js +404 -18
- package/dist/mcp/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ vhk 기획 끝났고 바로 시작
|
|
|
83
83
|
| `vhk sync` | `규칙`, `맞추기` | RULES.md → `.cursorrules` + CLAUDE.md |
|
|
84
84
|
| `vhk check` | `점검`, `린트` | RULES.md 규칙 위반 검사 |
|
|
85
85
|
| `vhk secure` | `보안` | 시크릿·키 유출 스캔 (`scan` / `스캔` 동일). **CRITICAL/HIGH 발견 시 exit code 1** (CI용) |
|
|
86
|
-
| `vhk ship` |
|
|
86
|
+
| `vhk ship` | `출하` | 배포 체크리스트 + 회고 + 빌드 로그 |
|
|
87
87
|
| `vhk doctor` | `환경`, `진단` | Node / npm / pnpm / Git 환경 점검 |
|
|
88
88
|
| `vhk save` | `저장`, `커밋` | git add · commit · push 한 번에 |
|
|
89
89
|
| `vhk undo` | `되돌리기`, `취소` | 최근 커밋 soft reset (변경은 staged 유지) |
|
|
@@ -91,6 +91,10 @@ vhk 기획 끝났고 바로 시작
|
|
|
91
91
|
| `vhk status` | `상태`, `현황` | 브랜치·변경·커밋·원격·버전 대시보드 |
|
|
92
92
|
| `vhk mcp` | — | MCP 서버 시작 (Cursor 등 MCP 클라이언트용, stdio) |
|
|
93
93
|
| `vhk mcp-init` | `mcp설정` | Cursor `.cursor/mcp.json` 자동 생성 |
|
|
94
|
+
| `vhk deploy` | `배포` | 프로덕션 배포 (Vercel / Netlify / Cloudflare 자동 감지) |
|
|
95
|
+
| `vhk env` | `환경변수` | `.env` → `.env.example` 동기화 + `.gitignore`에 `.env` 자동 추가 |
|
|
96
|
+
| `vhk env-check` | `환경변수점검` | `.env.example` 기준 누락 환경변수 검사 |
|
|
97
|
+
| `vhk publish` | `출시` | npm 배포 자동화 (버전 범프 → 빌드 → 테스트 → publish → git tag) |
|
|
94
98
|
|
|
95
99
|
### init 옵션
|
|
96
100
|
|
|
@@ -29,9 +29,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
29
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
30
30
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
31
31
|
import { z } from "zod";
|
|
32
|
-
import { execFileSync } from "child_process";
|
|
33
32
|
import { existsSync, readFileSync } from "fs";
|
|
34
|
-
|
|
33
|
+
|
|
34
|
+
// src/lib/exec.ts
|
|
35
|
+
import { execFileSync } from "child_process";
|
|
35
36
|
var SHIM_BINARIES = /* @__PURE__ */ new Set(["pnpm", "npm", "npx", "yarn"]);
|
|
36
37
|
function platformCmd(cmd) {
|
|
37
38
|
if (process.platform === "win32" && SHIM_BINARIES.has(cmd)) {
|
|
@@ -51,6 +52,21 @@ function safeExecFile(cmd, args) {
|
|
|
51
52
|
return { ok: false, err: msg };
|
|
52
53
|
}
|
|
53
54
|
}
|
|
55
|
+
function safeExecFileStream(cmd, args) {
|
|
56
|
+
try {
|
|
57
|
+
execFileSync(platformCmd(cmd), args, {
|
|
58
|
+
encoding: "utf-8",
|
|
59
|
+
stdio: "inherit"
|
|
60
|
+
});
|
|
61
|
+
return { ok: true };
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
64
|
+
return { ok: false, err: msg };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/mcp/server.ts
|
|
69
|
+
var SERVER_VERSION = "0.7.0";
|
|
54
70
|
function isGitRepo() {
|
|
55
71
|
return safeExecFile("git", ["rev-parse", "--is-inside-work-tree"]).ok;
|
|
56
72
|
}
|
|
@@ -294,5 +310,7 @@ async function startMcpServer() {
|
|
|
294
310
|
export {
|
|
295
311
|
__commonJS,
|
|
296
312
|
__toESM,
|
|
313
|
+
safeExecFile,
|
|
314
|
+
safeExecFileStream,
|
|
297
315
|
startMcpServer
|
|
298
316
|
};
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
__commonJS,
|
|
4
4
|
__toESM,
|
|
5
|
+
safeExecFile,
|
|
6
|
+
safeExecFileStream,
|
|
5
7
|
startMcpServer
|
|
6
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-NJDRNI3S.js";
|
|
7
9
|
|
|
8
10
|
// node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
|
|
9
11
|
var require_ignore = __commonJS({
|
|
@@ -465,7 +467,7 @@ var require_ignore = __commonJS({
|
|
|
465
467
|
|
|
466
468
|
// src/index.ts
|
|
467
469
|
import { Command, Help } from "commander";
|
|
468
|
-
import
|
|
470
|
+
import inquirer10 from "inquirer";
|
|
469
471
|
|
|
470
472
|
// src/lib/nlp-router.ts
|
|
471
473
|
function normalize(input) {
|
|
@@ -571,9 +573,33 @@ var RULES = [
|
|
|
571
573
|
},
|
|
572
574
|
{
|
|
573
575
|
command: "ship",
|
|
574
|
-
explanation: "\uBC30\uD3EC \uCCB4\uD06C + \uD68C\uACE0 (vhk
|
|
576
|
+
explanation: "\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 + \uD68C\uACE0 (vhk \uCD9C\uD558)",
|
|
577
|
+
confidence: "high",
|
|
578
|
+
test: (t2) => /^출하$|^ship$|빌드\s*전|(배포|출하)\s*(체크|준비|점검)/.test(t2)
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
command: "deploy",
|
|
582
|
+
explanation: "\uD504\uB85C\uB355\uC158 \uBC30\uD3EC (vhk deploy)",
|
|
575
583
|
confidence: "high",
|
|
576
|
-
test: (t2) =>
|
|
584
|
+
test: (t2) => /^배포$|배포\s*해|배포하|배포해줘|^deploy$|디플로이|vercel|netlify|cloudflare|wrangler|프로덕션|올려줘/.test(t2) && !/체크|준비|점검|출하|회고|빌드\s*전/.test(t2)
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
command: "env-check",
|
|
588
|
+
explanation: "\uD658\uACBD\uBCC0\uC218 \uB204\uB77D \uAC80\uC0AC (vhk env-check)",
|
|
589
|
+
confidence: "high",
|
|
590
|
+
test: (t2) => /환경변수\s*(점검|확인|누락)|env\s*(체크|확인|check)|키\s*(확인|누락)/.test(t2)
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
command: "env",
|
|
594
|
+
explanation: "\uD658\uACBD\uBCC0\uC218 \uAD00\uB9AC (vhk env)",
|
|
595
|
+
confidence: "high",
|
|
596
|
+
test: (t2) => /환경변수|\.env|env\s*example|env\s*동기화|시크릿\s*정리|키\s*설정/.test(t2) && !/점검|확인|누락|체크|check/.test(t2)
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
command: "publish",
|
|
600
|
+
explanation: "npm \uBC30\uD3EC (vhk publish)",
|
|
601
|
+
confidence: "high",
|
|
602
|
+
test: (t2) => /^출시$|출시\s*해|^publish$|퍼블리시|npm\s*(배포|출시)|버전\s*올|^릴리즈$|^release$/.test(t2) && !/체크|준비|회고/.test(t2)
|
|
577
603
|
}
|
|
578
604
|
];
|
|
579
605
|
function routeNaturalLanguage(input) {
|
|
@@ -618,8 +644,6 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
|
|
|
618
644
|
"scan",
|
|
619
645
|
"\uC2A4\uCE94",
|
|
620
646
|
"ship",
|
|
621
|
-
"\uBC30\uD3EC",
|
|
622
|
-
"\uB9B4\uB9AC\uC988",
|
|
623
647
|
"doctor",
|
|
624
648
|
"\uD658\uACBD",
|
|
625
649
|
"\uC9C4\uB2E8",
|
|
@@ -636,6 +660,15 @@ var KNOWN_COMMAND_TOKENS = /* @__PURE__ */ new Set([
|
|
|
636
660
|
"mcp",
|
|
637
661
|
"mcp-init",
|
|
638
662
|
"mcp\uC124\uC815",
|
|
663
|
+
"deploy",
|
|
664
|
+
"\uBC30\uD3EC",
|
|
665
|
+
"env",
|
|
666
|
+
"\uD658\uACBD\uBCC0\uC218",
|
|
667
|
+
"env-check",
|
|
668
|
+
"\uD658\uACBD\uBCC0\uC218\uC810\uAC80",
|
|
669
|
+
"publish",
|
|
670
|
+
"\uCD9C\uC2DC",
|
|
671
|
+
"\uCD9C\uD558",
|
|
639
672
|
"help"
|
|
640
673
|
]);
|
|
641
674
|
function isOptionToken(token) {
|
|
@@ -659,8 +692,8 @@ function detectNaturalLanguageInput(argv) {
|
|
|
659
692
|
}
|
|
660
693
|
|
|
661
694
|
// src/lib/nlp-run.ts
|
|
662
|
-
import
|
|
663
|
-
import
|
|
695
|
+
import chalk20 from "chalk";
|
|
696
|
+
import inquirer9 from "inquirer";
|
|
664
697
|
|
|
665
698
|
// src/i18n/ko.ts
|
|
666
699
|
var ko = {
|
|
@@ -908,6 +941,30 @@ var ko = {
|
|
|
908
941
|
mcp: {
|
|
909
942
|
initTitle: "Cursor MCP \uC5F0\uB3D9 \uC124\uC815",
|
|
910
943
|
serverStarted: "VHK MCP \uC11C\uBC84 \uC2DC\uC791\uB428"
|
|
944
|
+
},
|
|
945
|
+
deploy: {
|
|
946
|
+
title: "\uBC30\uD3EC\uD558\uAE30",
|
|
947
|
+
selectPlatform: "\uC5B4\uB5A4 \uD50C\uB7AB\uD3FC\uC5D0 \uBC30\uD3EC\uD560\uAE4C\uC694?",
|
|
948
|
+
deploying: "\uBC30\uD3EC \uC911...",
|
|
949
|
+
success: "\uBC30\uD3EC \uC131\uACF5!",
|
|
950
|
+
failed: "\uBC30\uD3EC \uC2E4\uD328"
|
|
951
|
+
},
|
|
952
|
+
env: {
|
|
953
|
+
title: "\uD658\uACBD\uBCC0\uC218 \uAD00\uB9AC",
|
|
954
|
+
checkTitle: "\uD658\uACBD\uBCC0\uC218 \uC810\uAC80"
|
|
955
|
+
},
|
|
956
|
+
publish: {
|
|
957
|
+
title: "npm \uBC30\uD3EC",
|
|
958
|
+
selectBump: "\uBC84\uC804\uC744 \uC5B4\uB5BB\uAC8C \uC62C\uB9B4\uAE4C\uC694?",
|
|
959
|
+
building: "\uBE4C\uB4DC \uC911...",
|
|
960
|
+
buildSuccess: "\uBE4C\uB4DC \uC131\uACF5",
|
|
961
|
+
buildFailed: "\uBE4C\uB4DC \uC2E4\uD328",
|
|
962
|
+
testing: "\uD14C\uC2A4\uD2B8 \uC911...",
|
|
963
|
+
testSuccess: "\uD14C\uC2A4\uD2B8 \uD1B5\uACFC",
|
|
964
|
+
testFailed: "\uD14C\uC2A4\uD2B8 \uC2E4\uD328",
|
|
965
|
+
publishing: "npm \uBC30\uD3EC \uC911...",
|
|
966
|
+
publishSuccess: "npm \uBC30\uD3EC \uC131\uACF5!",
|
|
967
|
+
publishFailed: "npm \uBC30\uD3EC \uC2E4\uD328"
|
|
911
968
|
}
|
|
912
969
|
};
|
|
913
970
|
function lookup(path15) {
|
|
@@ -3552,6 +3609,16 @@ import { join } from "path";
|
|
|
3552
3609
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3553
3610
|
import chalk16 from "chalk";
|
|
3554
3611
|
function resolveVhkMcpPath() {
|
|
3612
|
+
try {
|
|
3613
|
+
const pkgPath = join(process.cwd(), "package.json");
|
|
3614
|
+
if (existsSync(pkgPath)) {
|
|
3615
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
3616
|
+
if (pkg.name === "@byh3071/vhk") {
|
|
3617
|
+
return join(process.cwd(), "dist", "mcp", "index.js");
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
} catch {
|
|
3621
|
+
}
|
|
3555
3622
|
try {
|
|
3556
3623
|
const url = import.meta.resolve?.("@byh3071/vhk/dist/mcp/index.js");
|
|
3557
3624
|
if (typeof url === "string") return fileURLToPath2(url);
|
|
@@ -3595,6 +3662,301 @@ async function mcpInit() {
|
|
|
3595
3662
|
console.log(chalk16.gray('\n\u{1F4A1} \uC608: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC54C\uB824\uC918" \u2192 Cursor\uAC00 vhk status \uD638\uCD9C'));
|
|
3596
3663
|
}
|
|
3597
3664
|
|
|
3665
|
+
// src/commands/deploy.ts
|
|
3666
|
+
import { existsSync as existsSync2 } from "fs";
|
|
3667
|
+
import chalk17 from "chalk";
|
|
3668
|
+
import inquirer7 from "inquirer";
|
|
3669
|
+
var PLATFORMS = {
|
|
3670
|
+
vercel: {
|
|
3671
|
+
name: "Vercel",
|
|
3672
|
+
detectFiles: ["vercel.json", ".vercel"],
|
|
3673
|
+
command: "vercel",
|
|
3674
|
+
commandArgs: ["--prod"],
|
|
3675
|
+
checkArgs: ["--version"],
|
|
3676
|
+
installHint: "npm i -g vercel"
|
|
3677
|
+
},
|
|
3678
|
+
netlify: {
|
|
3679
|
+
name: "Netlify",
|
|
3680
|
+
detectFiles: ["netlify.toml", ".netlify"],
|
|
3681
|
+
command: "netlify",
|
|
3682
|
+
commandArgs: ["deploy", "--prod"],
|
|
3683
|
+
checkArgs: ["--version"],
|
|
3684
|
+
installHint: "npm i -g netlify-cli"
|
|
3685
|
+
},
|
|
3686
|
+
cloudflare: {
|
|
3687
|
+
name: "Cloudflare Workers",
|
|
3688
|
+
detectFiles: ["wrangler.toml"],
|
|
3689
|
+
command: "wrangler",
|
|
3690
|
+
commandArgs: ["deploy"],
|
|
3691
|
+
checkArgs: ["--version"],
|
|
3692
|
+
installHint: "npm i -g wrangler"
|
|
3693
|
+
}
|
|
3694
|
+
};
|
|
3695
|
+
function detectPlatform() {
|
|
3696
|
+
for (const [key, config] of Object.entries(PLATFORMS)) {
|
|
3697
|
+
for (const file of config.detectFiles) {
|
|
3698
|
+
if (existsSync2(file)) return key;
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
return null;
|
|
3702
|
+
}
|
|
3703
|
+
function isCLIAvailable(cmd, checkArgs) {
|
|
3704
|
+
return safeExecFile(cmd, checkArgs).ok;
|
|
3705
|
+
}
|
|
3706
|
+
async function deploy() {
|
|
3707
|
+
console.log(chalk17.bold("\n\u{1F680} " + t("deploy.title")));
|
|
3708
|
+
console.log(chalk17.gray("\u2500".repeat(40)));
|
|
3709
|
+
let platform = detectPlatform();
|
|
3710
|
+
if (platform) {
|
|
3711
|
+
console.log(chalk17.cyan(`
|
|
3712
|
+
\u{1F50D} \uAC10\uC9C0\uB41C \uD50C\uB7AB\uD3FC: ${PLATFORMS[platform].name}`));
|
|
3713
|
+
} else {
|
|
3714
|
+
const { selected } = await inquirer7.prompt([
|
|
3715
|
+
{
|
|
3716
|
+
type: "list",
|
|
3717
|
+
name: "selected",
|
|
3718
|
+
message: t("deploy.selectPlatform"),
|
|
3719
|
+
choices: [
|
|
3720
|
+
{ name: "\u25B2 Vercel", value: "vercel" },
|
|
3721
|
+
{ name: "\u25C6 Netlify", value: "netlify" },
|
|
3722
|
+
{ name: "\u2601 Cloudflare Workers", value: "cloudflare" }
|
|
3723
|
+
]
|
|
3724
|
+
}
|
|
3725
|
+
]);
|
|
3726
|
+
platform = selected;
|
|
3727
|
+
}
|
|
3728
|
+
const config = PLATFORMS[platform];
|
|
3729
|
+
if (!isCLIAvailable(config.command, config.checkArgs)) {
|
|
3730
|
+
console.log(chalk17.red(`
|
|
3731
|
+
\u274C ${config.name} CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`));
|
|
3732
|
+
console.log(chalk17.yellow(` \u2192 ${config.installHint}`));
|
|
3733
|
+
return;
|
|
3734
|
+
}
|
|
3735
|
+
const { confirm } = await inquirer7.prompt([
|
|
3736
|
+
{
|
|
3737
|
+
type: "confirm",
|
|
3738
|
+
name: "confirm",
|
|
3739
|
+
message: `${config.name}\uC5D0 \uD504\uB85C\uB355\uC158 \uBC30\uD3EC\uD560\uAE4C\uC694?`,
|
|
3740
|
+
default: true
|
|
3741
|
+
}
|
|
3742
|
+
]);
|
|
3743
|
+
if (!confirm) {
|
|
3744
|
+
console.log(chalk17.gray("\uCDE8\uC18C\uB428"));
|
|
3745
|
+
return;
|
|
3746
|
+
}
|
|
3747
|
+
console.log(chalk17.cyan(`
|
|
3748
|
+
${t("deploy.deploying")}
|
|
3749
|
+
`));
|
|
3750
|
+
const result = safeExecFileStream(config.command, config.commandArgs);
|
|
3751
|
+
if (result.ok) {
|
|
3752
|
+
console.log(chalk17.green(`
|
|
3753
|
+
\u2705 ${t("deploy.success")}`));
|
|
3754
|
+
printNextStep({
|
|
3755
|
+
message: "\uBC30\uD3EC \uC644\uB8CC! \uC0AC\uC774\uD2B8\uB97C \uD655\uC778\uD558\uC138\uC694.",
|
|
3756
|
+
command: "vhk status",
|
|
3757
|
+
cursorHint: "\uC0C1\uD0DC \uD655\uC778\uD574\uC918"
|
|
3758
|
+
});
|
|
3759
|
+
} else {
|
|
3760
|
+
console.log(chalk17.red(`
|
|
3761
|
+
\u274C ${t("deploy.failed")}`));
|
|
3762
|
+
console.log(chalk17.red(result.err));
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
// src/commands/env.ts
|
|
3767
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, appendFileSync } from "fs";
|
|
3768
|
+
import chalk18 from "chalk";
|
|
3769
|
+
function parseEnvKeys(content) {
|
|
3770
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.split("=")[0].trim()).filter(Boolean);
|
|
3771
|
+
}
|
|
3772
|
+
function ensureGitignore() {
|
|
3773
|
+
const gitignorePath = ".gitignore";
|
|
3774
|
+
if (existsSync3(gitignorePath)) {
|
|
3775
|
+
const content = readFileSync2(gitignorePath, "utf-8");
|
|
3776
|
+
if (!content.split("\n").some((l) => l.trim() === ".env")) {
|
|
3777
|
+
appendFileSync(gitignorePath, "\n.env\n");
|
|
3778
|
+
console.log(chalk18.green("\n\u{1F512} .gitignore\uC5D0 .env \uCD94\uAC00\uB428"));
|
|
3779
|
+
}
|
|
3780
|
+
} else {
|
|
3781
|
+
writeFileSync2(gitignorePath, ".env\nnode_modules/\ndist/\n");
|
|
3782
|
+
console.log(chalk18.green("\n\u{1F512} .gitignore \uC0DD\uC131 (.env \uD3EC\uD568)"));
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
async function env() {
|
|
3786
|
+
console.log(chalk18.bold("\n\u{1F510} " + t("env.title")));
|
|
3787
|
+
console.log(chalk18.gray("\u2500".repeat(40)));
|
|
3788
|
+
if (!existsSync3(".env")) {
|
|
3789
|
+
console.log(chalk18.yellow("\n\u26A0\uFE0F .env \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
3790
|
+
console.log(chalk18.gray(" .env \uD30C\uC77C\uC744 \uBA3C\uC800 \uB9CC\uB4E4\uC5B4\uC8FC\uC138\uC694."));
|
|
3791
|
+
return;
|
|
3792
|
+
}
|
|
3793
|
+
const envContent = readFileSync2(".env", "utf-8");
|
|
3794
|
+
const keys = parseEnvKeys(envContent);
|
|
3795
|
+
if (keys.length === 0) {
|
|
3796
|
+
console.log(chalk18.yellow("\n\u{1F4ED} .env\uC5D0 \uD658\uACBD\uBCC0\uC218\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
3797
|
+
return;
|
|
3798
|
+
}
|
|
3799
|
+
const exampleContent = keys.map((k) => `${k}=`).join("\n") + "\n";
|
|
3800
|
+
writeFileSync2(".env.example", exampleContent, "utf-8");
|
|
3801
|
+
console.log(chalk18.green(`
|
|
3802
|
+
\u2705 .env.example \uC0DD\uC131 (${keys.length}\uAC1C \uD0A4)`));
|
|
3803
|
+
keys.forEach((k) => console.log(chalk18.gray(` ${k}`)));
|
|
3804
|
+
ensureGitignore();
|
|
3805
|
+
printNextStep({
|
|
3806
|
+
message: ".env.example \uC0DD\uC131 \uC644\uB8CC!",
|
|
3807
|
+
command: "vhk env-check",
|
|
3808
|
+
cursorHint: "\uD658\uACBD\uBCC0\uC218 \uC810\uAC80\uD574\uC918"
|
|
3809
|
+
});
|
|
3810
|
+
}
|
|
3811
|
+
async function envCheck() {
|
|
3812
|
+
console.log(chalk18.bold("\n\u{1F50D} " + t("env.checkTitle")));
|
|
3813
|
+
console.log(chalk18.gray("\u2500".repeat(40)));
|
|
3814
|
+
if (!existsSync3(".env.example")) {
|
|
3815
|
+
console.log(chalk18.yellow("\n\u26A0\uFE0F .env.example\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 vhk env\uB97C \uC2E4\uD589\uD558\uC138\uC694."));
|
|
3816
|
+
return;
|
|
3817
|
+
}
|
|
3818
|
+
const requiredKeys = parseEnvKeys(readFileSync2(".env.example", "utf-8"));
|
|
3819
|
+
const currentKeys = existsSync3(".env") ? parseEnvKeys(readFileSync2(".env", "utf-8")) : [];
|
|
3820
|
+
const missing = requiredKeys.filter((k) => !currentKeys.includes(k));
|
|
3821
|
+
const extra = currentKeys.filter((k) => !requiredKeys.includes(k));
|
|
3822
|
+
console.log(chalk18.cyan(`
|
|
3823
|
+
\u{1F4CB} \uD544\uC218 \uD658\uACBD\uBCC0\uC218: ${requiredKeys.length}\uAC1C`));
|
|
3824
|
+
if (missing.length === 0) {
|
|
3825
|
+
console.log(chalk18.green("\n\u2705 \uBAA8\uB4E0 \uD544\uC218 \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4!"));
|
|
3826
|
+
} else {
|
|
3827
|
+
console.log(chalk18.red(`
|
|
3828
|
+
\u274C \uB204\uB77D\uB41C \uD658\uACBD\uBCC0\uC218 (${missing.length}\uAC1C):`));
|
|
3829
|
+
missing.forEach((k) => console.log(chalk18.red(` \u2022 ${k}`)));
|
|
3830
|
+
}
|
|
3831
|
+
if (extra.length > 0) {
|
|
3832
|
+
console.log(chalk18.yellow(`
|
|
3833
|
+
\u{1F4A1} .env.example\uC5D0 \uC5C6\uB294 \uCD94\uAC00 \uBCC0\uC218 (${extra.length}\uAC1C):`));
|
|
3834
|
+
extra.forEach((k) => console.log(chalk18.yellow(` \u2022 ${k}`)));
|
|
3835
|
+
}
|
|
3836
|
+
ensureGitignore();
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
// src/commands/publish.ts
|
|
3840
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
3841
|
+
import chalk19 from "chalk";
|
|
3842
|
+
import inquirer8 from "inquirer";
|
|
3843
|
+
import ora2 from "ora";
|
|
3844
|
+
function bumpVersion(current, type) {
|
|
3845
|
+
const [major, minor, patch] = current.split(".").map((n) => parseInt(n, 10) || 0);
|
|
3846
|
+
switch (type) {
|
|
3847
|
+
case "major":
|
|
3848
|
+
return `${major + 1}.0.0`;
|
|
3849
|
+
case "minor":
|
|
3850
|
+
return `${major}.${minor + 1}.0`;
|
|
3851
|
+
case "patch":
|
|
3852
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
async function publish() {
|
|
3856
|
+
console.log(chalk19.bold("\n\u{1F4E6} " + t("publish.title")));
|
|
3857
|
+
console.log(chalk19.gray("\u2500".repeat(40)));
|
|
3858
|
+
if (!existsSync4("package.json")) {
|
|
3859
|
+
console.log(chalk19.red("\u274C package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
3860
|
+
return;
|
|
3861
|
+
}
|
|
3862
|
+
let pkg;
|
|
3863
|
+
try {
|
|
3864
|
+
pkg = JSON.parse(readFileSync3("package.json", "utf-8"));
|
|
3865
|
+
} catch {
|
|
3866
|
+
console.log(chalk19.red("\u274C package.json \uD30C\uC2F1 \uC2E4\uD328"));
|
|
3867
|
+
return;
|
|
3868
|
+
}
|
|
3869
|
+
const currentVersion = pkg.version || "0.0.0";
|
|
3870
|
+
console.log(chalk19.cyan(`
|
|
3871
|
+
\u{1F4CC} \uD604\uC7AC \uBC84\uC804: v${currentVersion}`));
|
|
3872
|
+
const { bumpType } = await inquirer8.prompt([
|
|
3873
|
+
{
|
|
3874
|
+
type: "list",
|
|
3875
|
+
name: "bumpType",
|
|
3876
|
+
message: t("publish.selectBump"),
|
|
3877
|
+
choices: [
|
|
3878
|
+
{ name: `\u{1F527} patch (${bumpVersion(currentVersion, "patch")}) \u2014 \uBC84\uADF8 \uC218\uC815`, value: "patch" },
|
|
3879
|
+
{ name: `\u2728 minor (${bumpVersion(currentVersion, "minor")}) \u2014 \uC0C8 \uAE30\uB2A5`, value: "minor" },
|
|
3880
|
+
{ name: `\u{1F4A5} major (${bumpVersion(currentVersion, "major")}) \u2014 \uD638\uD658\uC131 \uBCC0\uACBD`, value: "major" }
|
|
3881
|
+
]
|
|
3882
|
+
}
|
|
3883
|
+
]);
|
|
3884
|
+
const newVersion = bumpVersion(currentVersion, bumpType);
|
|
3885
|
+
console.log(chalk19.cyan(`
|
|
3886
|
+
\u{1F195} \uC0C8 \uBC84\uC804: v${newVersion}`));
|
|
3887
|
+
pkg.version = newVersion;
|
|
3888
|
+
writeFileSync3("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3889
|
+
console.log(chalk19.green("\u2705 package.json \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8"));
|
|
3890
|
+
const buildSpinner = ora2(t("publish.building")).start();
|
|
3891
|
+
const buildResult = safeExecFile("pnpm", ["build"]);
|
|
3892
|
+
if (!buildResult.ok) {
|
|
3893
|
+
buildSpinner.fail(t("publish.buildFailed"));
|
|
3894
|
+
console.log(chalk19.red(buildResult.err.slice(0, 500)));
|
|
3895
|
+
pkg.version = currentVersion;
|
|
3896
|
+
writeFileSync3("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
buildSpinner.succeed(t("publish.buildSuccess"));
|
|
3900
|
+
const testSpinner = ora2(t("publish.testing")).start();
|
|
3901
|
+
const testResult = safeExecFile("pnpm", ["test", "--run"]);
|
|
3902
|
+
if (!testResult.ok) {
|
|
3903
|
+
testSpinner.fail(t("publish.testFailed"));
|
|
3904
|
+
console.log(chalk19.red(testResult.err.slice(0, 500)));
|
|
3905
|
+
pkg.version = currentVersion;
|
|
3906
|
+
writeFileSync3("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3907
|
+
return;
|
|
3908
|
+
}
|
|
3909
|
+
testSpinner.succeed(t("publish.testSuccess"));
|
|
3910
|
+
const { confirm } = await inquirer8.prompt([
|
|
3911
|
+
{
|
|
3912
|
+
type: "confirm",
|
|
3913
|
+
name: "confirm",
|
|
3914
|
+
message: `v${newVersion}\uC744 npm\uC5D0 \uBC30\uD3EC\uD560\uAE4C\uC694?`,
|
|
3915
|
+
default: true
|
|
3916
|
+
}
|
|
3917
|
+
]);
|
|
3918
|
+
if (!confirm) {
|
|
3919
|
+
pkg.version = currentVersion;
|
|
3920
|
+
writeFileSync3("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3921
|
+
console.log(chalk19.gray("\uCDE8\uC18C\uB428. \uBC84\uC804\uC774 \uC6D0\uB798\uB300\uB85C \uBCF5\uAD6C\uB429\uB2C8\uB2E4."));
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
const pubSpinner = ora2(t("publish.publishing")).start();
|
|
3925
|
+
const pubResult = safeExecFile("npm", ["publish", "--access", "public"]);
|
|
3926
|
+
if (!pubResult.ok) {
|
|
3927
|
+
pubSpinner.fail(t("publish.publishFailed"));
|
|
3928
|
+
console.log(chalk19.red(pubResult.err.slice(0, 500)));
|
|
3929
|
+
pkg.version = currentVersion;
|
|
3930
|
+
writeFileSync3("package.json", JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3931
|
+
console.log(chalk19.gray(`\u{1F4E6} package.json \uBC84\uC804\uC744 v${currentVersion}\uB85C \uBCF5\uAD6C\uD588\uC2B5\uB2C8\uB2E4.`));
|
|
3932
|
+
return;
|
|
3933
|
+
}
|
|
3934
|
+
pubSpinner.succeed(t("publish.publishSuccess"));
|
|
3935
|
+
const addResult = safeExecFile("git", ["add", "package.json"]);
|
|
3936
|
+
if (addResult.ok) {
|
|
3937
|
+
safeExecFile("git", ["commit", "-m", `chore: release v${newVersion}`]);
|
|
3938
|
+
const tagResult = safeExecFile("git", ["tag", `v${newVersion}`]);
|
|
3939
|
+
if (tagResult.ok) {
|
|
3940
|
+
const pushResult = safeExecFile("git", ["push"]);
|
|
3941
|
+
const pushTagsResult = safeExecFile("git", ["push", "--tags"]);
|
|
3942
|
+
if (pushResult.ok && pushTagsResult.ok) {
|
|
3943
|
+
console.log(chalk19.green(`
|
|
3944
|
+
\u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131 + push \uC644\uB8CC`));
|
|
3945
|
+
} else {
|
|
3946
|
+
console.log(chalk19.yellow(`
|
|
3947
|
+
\u{1F3F7}\uFE0F git tag v${newVersion} \uC0DD\uC131\uB428 (push\uB294 \uC218\uB3D9\uC73C\uB85C)`));
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
console.log(chalk19.green.bold(`
|
|
3952
|
+
\u{1F389} v${newVersion} \uBC30\uD3EC \uC644\uB8CC!`));
|
|
3953
|
+
printNextStep({
|
|
3954
|
+
message: "npm \uBC30\uD3EC \uC644\uB8CC!",
|
|
3955
|
+
command: "vhk status",
|
|
3956
|
+
cursorHint: "\uC0C1\uD0DC \uD655\uC778\uD574\uC918"
|
|
3957
|
+
});
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3598
3960
|
// src/lib/nlp-run.ts
|
|
3599
3961
|
async function dispatchNlpRoute(route, input) {
|
|
3600
3962
|
switch (route.command) {
|
|
@@ -3627,28 +3989,36 @@ async function dispatchNlpRoute(route, input) {
|
|
|
3627
3989
|
return diff();
|
|
3628
3990
|
case "mcp-init":
|
|
3629
3991
|
return mcpInit();
|
|
3992
|
+
case "deploy":
|
|
3993
|
+
return deploy();
|
|
3994
|
+
case "env":
|
|
3995
|
+
return env();
|
|
3996
|
+
case "env-check":
|
|
3997
|
+
return envCheck();
|
|
3998
|
+
case "publish":
|
|
3999
|
+
return publish();
|
|
3630
4000
|
}
|
|
3631
4001
|
}
|
|
3632
4002
|
async function runNaturalLanguageRoute(input) {
|
|
3633
4003
|
const route = routeNaturalLanguage(input);
|
|
3634
4004
|
if (!route) {
|
|
3635
|
-
console.log(
|
|
4005
|
+
console.log(chalk20.yellow(`
|
|
3636
4006
|
\u2753 "${input}" \u2014 ${ko.nlp.notMatched}
|
|
3637
4007
|
`));
|
|
3638
4008
|
return;
|
|
3639
4009
|
}
|
|
3640
4010
|
console.log("");
|
|
3641
|
-
console.log(
|
|
3642
|
-
console.log(
|
|
4011
|
+
console.log(chalk20.cyan(` \u{1F4AC} "${input}"`));
|
|
4012
|
+
console.log(chalk20.cyan(` \u2192 ${route.explanation}`));
|
|
3643
4013
|
if (route.confidence === "low") {
|
|
3644
|
-
const { confirm } = await
|
|
4014
|
+
const { confirm } = await inquirer9.prompt([{
|
|
3645
4015
|
type: "confirm",
|
|
3646
4016
|
name: "confirm",
|
|
3647
4017
|
message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
|
|
3648
4018
|
default: true
|
|
3649
4019
|
}]);
|
|
3650
4020
|
if (!confirm) {
|
|
3651
|
-
console.log(
|
|
4021
|
+
console.log(chalk20.dim(` ${ko.nlp.menuHint}`));
|
|
3652
4022
|
return;
|
|
3653
4023
|
}
|
|
3654
4024
|
}
|
|
@@ -3666,14 +4036,18 @@ var KO_ALIASES = {
|
|
|
3666
4036
|
sync: "\uADDC\uCE59",
|
|
3667
4037
|
check: "\uC810\uAC80",
|
|
3668
4038
|
secure: "\uBCF4\uC548",
|
|
3669
|
-
ship: "\
|
|
4039
|
+
ship: "\uCD9C\uD558",
|
|
3670
4040
|
doctor: "\uD658\uACBD",
|
|
3671
4041
|
save: "\uC800\uC7A5",
|
|
3672
4042
|
undo: "\uB418\uB3CC\uB9AC\uAE30",
|
|
3673
4043
|
status: "\uC0C1\uD0DC",
|
|
3674
|
-
diff: "\uBCC0\uACBD"
|
|
4044
|
+
diff: "\uBCC0\uACBD",
|
|
4045
|
+
deploy: "\uBC30\uD3EC",
|
|
4046
|
+
env: "\uD658\uACBD\uBCC0\uC218",
|
|
4047
|
+
"env-check": "\uD658\uACBD\uBCC0\uC218\uC810\uAC80",
|
|
4048
|
+
publish: "\uCD9C\uC2DC"
|
|
3675
4049
|
};
|
|
3676
|
-
program.name("vhk").description("VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 (\uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4)").version("0.
|
|
4050
|
+
program.name("vhk").description("VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58 (\uD55C\uAD6D\uC5B4\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4)").version("0.7.0");
|
|
3677
4051
|
program.configureHelp({
|
|
3678
4052
|
formatHelp(cmd, helper) {
|
|
3679
4053
|
if (cmd.parent) {
|
|
@@ -3704,7 +4078,7 @@ program.command("sync").alias("\uB9DE\uCD94\uAE30").alias("\uADDC\uCE59").descri
|
|
|
3704
4078
|
program.command("check").alias("\uC810\uAC80").alias("\uB9B0\uD2B8").description("RULES.md \uADDC\uCE59 \uC810\uAC80 \u2014 \uCF54\uB4DC \uC704\uBC18 \uAC80\uC0AC").action(check);
|
|
3705
4079
|
var secureCmd = program.command("secure").alias("\uBCF4\uC548").description("\uBCF4\uC548 \uB3C4\uAD6C \uBAA8\uC74C \u2014 scan: \uC2DC\uD06C\uB9BF\xB7\uD0A4 \uC720\uCD9C \uAC80\uC0AC").action(secure);
|
|
3706
4080
|
secureCmd.command("scan").alias("\uC2A4\uCE94").description("\uC2DC\uD06C\uB9BF/\uD0A4 \uC720\uCD9C \uC2A4\uCE94").action(secure);
|
|
3707
|
-
program.command("ship").alias("\
|
|
4081
|
+
program.command("ship").alias("\uCD9C\uD558").description("\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 + \uD68C\uACE0 + \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131").action(ship);
|
|
3708
4082
|
program.command("doctor").alias("\uD658\uACBD").alias("\uC9C4\uB2E8").description("\uAC1C\uBC1C \uD658\uACBD \uC810\uAC80 \u2014 Node/Git/npm \uC0C1\uD0DC \uD655\uC778").action(doctor);
|
|
3709
4083
|
program.command("save").alias("\uC800\uC7A5").description("\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)").action(async () => {
|
|
3710
4084
|
await save();
|
|
@@ -3722,6 +4096,18 @@ program.command("mcp").description("MCP \uC11C\uBC84 \uC2DC\uC791 (Cursor \uB4F1
|
|
|
3722
4096
|
program.command("mcp-init").alias("mcp\uC124\uC815").description("Cursor MCP \uC5F0\uB3D9 \uC124\uC815 \uC790\uB3D9 \uC0DD\uC131 (.cursor/mcp.json)").action(async () => {
|
|
3723
4097
|
await mcpInit();
|
|
3724
4098
|
});
|
|
4099
|
+
program.command("deploy").alias("\uBC30\uD3EC").description("\uD504\uB85C\uB355\uC158 \uBC30\uD3EC (Vercel/Netlify/Cloudflare \uC790\uB3D9 \uAC10\uC9C0)").action(async () => {
|
|
4100
|
+
await deploy();
|
|
4101
|
+
});
|
|
4102
|
+
program.command("env").alias("\uD658\uACBD\uBCC0\uC218").description(".env \u2192 .env.example \uB3D9\uAE30\uD654 + .gitignore \uC790\uB3D9 \uCD94\uAC00").action(async () => {
|
|
4103
|
+
await env();
|
|
4104
|
+
});
|
|
4105
|
+
program.command("env-check").alias("\uD658\uACBD\uBCC0\uC218\uC810\uAC80").description("\uD544\uC218 \uD658\uACBD\uBCC0\uC218 \uB204\uB77D \uAC80\uC0AC").action(async () => {
|
|
4106
|
+
await envCheck();
|
|
4107
|
+
});
|
|
4108
|
+
program.command("publish").alias("\uCD9C\uC2DC").description("npm \uBC30\uD3EC (\uBC84\uC804 \uBC94\uD504 \u2192 \uBE4C\uB4DC \u2192 \uD14C\uC2A4\uD2B8 \u2192 publish)").action(async () => {
|
|
4109
|
+
await publish();
|
|
4110
|
+
});
|
|
3725
4111
|
program.on("command:*", async (operands) => {
|
|
3726
4112
|
const unknown = operands[0] ?? "";
|
|
3727
4113
|
const rest = operands.slice(1);
|
|
@@ -3730,7 +4116,7 @@ program.on("command:*", async (operands) => {
|
|
|
3730
4116
|
});
|
|
3731
4117
|
program.action(async () => {
|
|
3732
4118
|
console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
|
|
3733
|
-
const { choice } = await
|
|
4119
|
+
const { choice } = await inquirer10.prompt([{
|
|
3734
4120
|
type: "list",
|
|
3735
4121
|
name: "choice",
|
|
3736
4122
|
message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
|
package/dist/mcp/index.js
CHANGED