@byh3071/vhk 0.3.1 → 0.5.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 +138 -28
- package/dist/index.js +1721 -130
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,672 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
|
|
28
|
+
// node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
|
|
29
|
+
var require_ignore = __commonJS({
|
|
30
|
+
"node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js"(exports, module) {
|
|
31
|
+
"use strict";
|
|
32
|
+
function makeArray(subject) {
|
|
33
|
+
return Array.isArray(subject) ? subject : [subject];
|
|
34
|
+
}
|
|
35
|
+
var UNDEFINED = void 0;
|
|
36
|
+
var EMPTY = "";
|
|
37
|
+
var SPACE = " ";
|
|
38
|
+
var ESCAPE = "\\";
|
|
39
|
+
var REGEX_TEST_BLANK_LINE = /^\s+$/;
|
|
40
|
+
var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
|
|
41
|
+
var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
|
|
42
|
+
var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
|
|
43
|
+
var REGEX_SPLITALL_CRLF = /\r?\n/g;
|
|
44
|
+
var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
|
|
45
|
+
var REGEX_TEST_TRAILING_SLASH = /\/$/;
|
|
46
|
+
var SLASH = "/";
|
|
47
|
+
var TMP_KEY_IGNORE = "node-ignore";
|
|
48
|
+
if (typeof Symbol !== "undefined") {
|
|
49
|
+
TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
|
|
50
|
+
}
|
|
51
|
+
var KEY_IGNORE = TMP_KEY_IGNORE;
|
|
52
|
+
var define = (object, key, value) => {
|
|
53
|
+
Object.defineProperty(object, key, { value });
|
|
54
|
+
return value;
|
|
55
|
+
};
|
|
56
|
+
var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
|
|
57
|
+
var RETURN_FALSE = () => false;
|
|
58
|
+
var sanitizeRange = (range) => range.replace(
|
|
59
|
+
REGEX_REGEXP_RANGE,
|
|
60
|
+
(match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
|
|
61
|
+
);
|
|
62
|
+
var cleanRangeBackSlash = (slashes) => {
|
|
63
|
+
const { length } = slashes;
|
|
64
|
+
return slashes.slice(0, length - length % 2);
|
|
65
|
+
};
|
|
66
|
+
var REPLACERS = [
|
|
67
|
+
[
|
|
68
|
+
// Remove BOM
|
|
69
|
+
// TODO:
|
|
70
|
+
// Other similar zero-width characters?
|
|
71
|
+
/^\uFEFF/,
|
|
72
|
+
() => EMPTY
|
|
73
|
+
],
|
|
74
|
+
// > Trailing spaces are ignored unless they are quoted with backslash ("\")
|
|
75
|
+
[
|
|
76
|
+
// (a\ ) -> (a )
|
|
77
|
+
// (a ) -> (a)
|
|
78
|
+
// (a ) -> (a)
|
|
79
|
+
// (a \ ) -> (a )
|
|
80
|
+
/((?:\\\\)*?)(\\?\s+)$/,
|
|
81
|
+
(_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
|
|
82
|
+
],
|
|
83
|
+
// Replace (\ ) with ' '
|
|
84
|
+
// (\ ) -> ' '
|
|
85
|
+
// (\\ ) -> '\\ '
|
|
86
|
+
// (\\\ ) -> '\\ '
|
|
87
|
+
[
|
|
88
|
+
/(\\+?)\s/g,
|
|
89
|
+
(_, m1) => {
|
|
90
|
+
const { length } = m1;
|
|
91
|
+
return m1.slice(0, length - length % 2) + SPACE;
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
// Escape metacharacters
|
|
95
|
+
// which is written down by users but means special for regular expressions.
|
|
96
|
+
// > There are 12 characters with special meanings:
|
|
97
|
+
// > - the backslash \,
|
|
98
|
+
// > - the caret ^,
|
|
99
|
+
// > - the dollar sign $,
|
|
100
|
+
// > - the period or dot .,
|
|
101
|
+
// > - the vertical bar or pipe symbol |,
|
|
102
|
+
// > - the question mark ?,
|
|
103
|
+
// > - the asterisk or star *,
|
|
104
|
+
// > - the plus sign +,
|
|
105
|
+
// > - the opening parenthesis (,
|
|
106
|
+
// > - the closing parenthesis ),
|
|
107
|
+
// > - and the opening square bracket [,
|
|
108
|
+
// > - the opening curly brace {,
|
|
109
|
+
// > These special characters are often called "metacharacters".
|
|
110
|
+
[
|
|
111
|
+
/[\\$.|*+(){^]/g,
|
|
112
|
+
(match) => `\\${match}`
|
|
113
|
+
],
|
|
114
|
+
[
|
|
115
|
+
// > a question mark (?) matches a single character
|
|
116
|
+
/(?!\\)\?/g,
|
|
117
|
+
() => "[^/]"
|
|
118
|
+
],
|
|
119
|
+
// leading slash
|
|
120
|
+
[
|
|
121
|
+
// > A leading slash matches the beginning of the pathname.
|
|
122
|
+
// > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
|
|
123
|
+
// A leading slash matches the beginning of the pathname
|
|
124
|
+
/^\//,
|
|
125
|
+
() => "^"
|
|
126
|
+
],
|
|
127
|
+
// replace special metacharacter slash after the leading slash
|
|
128
|
+
[
|
|
129
|
+
/\//g,
|
|
130
|
+
() => "\\/"
|
|
131
|
+
],
|
|
132
|
+
[
|
|
133
|
+
// > A leading "**" followed by a slash means match in all directories.
|
|
134
|
+
// > For example, "**/foo" matches file or directory "foo" anywhere,
|
|
135
|
+
// > the same as pattern "foo".
|
|
136
|
+
// > "**/foo/bar" matches file or directory "bar" anywhere that is directly
|
|
137
|
+
// > under directory "foo".
|
|
138
|
+
// Notice that the '*'s have been replaced as '\\*'
|
|
139
|
+
/^\^*\\\*\\\*\\\//,
|
|
140
|
+
// '**/foo' <-> 'foo'
|
|
141
|
+
() => "^(?:.*\\/)?"
|
|
142
|
+
],
|
|
143
|
+
// starting
|
|
144
|
+
[
|
|
145
|
+
// there will be no leading '/'
|
|
146
|
+
// (which has been replaced by section "leading slash")
|
|
147
|
+
// If starts with '**', adding a '^' to the regular expression also works
|
|
148
|
+
/^(?=[^^])/,
|
|
149
|
+
function startingReplacer() {
|
|
150
|
+
return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
// two globstars
|
|
154
|
+
[
|
|
155
|
+
// Use lookahead assertions so that we could match more than one `'/**'`
|
|
156
|
+
/\\\/\\\*\\\*(?=\\\/|$)/g,
|
|
157
|
+
// Zero, one or several directories
|
|
158
|
+
// should not use '*', or it will be replaced by the next replacer
|
|
159
|
+
// Check if it is not the last `'/**'`
|
|
160
|
+
(_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
|
|
161
|
+
],
|
|
162
|
+
// normal intermediate wildcards
|
|
163
|
+
[
|
|
164
|
+
// Never replace escaped '*'
|
|
165
|
+
// ignore rule '\*' will match the path '*'
|
|
166
|
+
// 'abc.*/' -> go
|
|
167
|
+
// 'abc.*' -> skip this rule,
|
|
168
|
+
// coz trailing single wildcard will be handed by [trailing wildcard]
|
|
169
|
+
/(^|[^\\]+)(\\\*)+(?=.+)/g,
|
|
170
|
+
// '*.js' matches '.js'
|
|
171
|
+
// '*.js' doesn't match 'abc'
|
|
172
|
+
(_, p1, p2) => {
|
|
173
|
+
const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
|
|
174
|
+
return p1 + unescaped;
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
[
|
|
178
|
+
// unescape, revert step 3 except for back slash
|
|
179
|
+
// For example, if a user escape a '\\*',
|
|
180
|
+
// after step 3, the result will be '\\\\\\*'
|
|
181
|
+
/\\\\\\(?=[$.|*+(){^])/g,
|
|
182
|
+
() => ESCAPE
|
|
183
|
+
],
|
|
184
|
+
[
|
|
185
|
+
// '\\\\' -> '\\'
|
|
186
|
+
/\\\\/g,
|
|
187
|
+
() => ESCAPE
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
// > The range notation, e.g. [a-zA-Z],
|
|
191
|
+
// > can be used to match one of the characters in a range.
|
|
192
|
+
// `\` is escaped by step 3
|
|
193
|
+
/(\\)?\[([^\]/]*?)(\\*)($|\])/g,
|
|
194
|
+
(match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"
|
|
195
|
+
],
|
|
196
|
+
// ending
|
|
197
|
+
[
|
|
198
|
+
// 'js' will not match 'js.'
|
|
199
|
+
// 'ab' will not match 'abc'
|
|
200
|
+
/(?:[^*])$/,
|
|
201
|
+
// WTF!
|
|
202
|
+
// https://git-scm.com/docs/gitignore
|
|
203
|
+
// changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
|
|
204
|
+
// which re-fixes #24, #38
|
|
205
|
+
// > If there is a separator at the end of the pattern then the pattern
|
|
206
|
+
// > will only match directories, otherwise the pattern can match both
|
|
207
|
+
// > files and directories.
|
|
208
|
+
// 'js*' will not match 'a.js'
|
|
209
|
+
// 'js/' will not match 'a.js'
|
|
210
|
+
// 'js' will match 'a.js' and 'a.js/'
|
|
211
|
+
(match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
|
|
212
|
+
]
|
|
213
|
+
];
|
|
214
|
+
var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
|
|
215
|
+
var MODE_IGNORE = "regex";
|
|
216
|
+
var MODE_CHECK_IGNORE = "checkRegex";
|
|
217
|
+
var UNDERSCORE = "_";
|
|
218
|
+
var TRAILING_WILD_CARD_REPLACERS = {
|
|
219
|
+
[MODE_IGNORE](_, p1) {
|
|
220
|
+
const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
|
|
221
|
+
return `${prefix}(?=$|\\/$)`;
|
|
222
|
+
},
|
|
223
|
+
[MODE_CHECK_IGNORE](_, p1) {
|
|
224
|
+
const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
|
|
225
|
+
return `${prefix}(?=$|\\/$)`;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var makeRegexPrefix = (pattern) => REPLACERS.reduce(
|
|
229
|
+
(prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
|
|
230
|
+
pattern
|
|
231
|
+
);
|
|
232
|
+
var isString = (subject) => typeof subject === "string";
|
|
233
|
+
var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
|
|
234
|
+
var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
|
|
235
|
+
var IgnoreRule = class {
|
|
236
|
+
constructor(pattern, mark, body, ignoreCase, negative, prefix) {
|
|
237
|
+
this.pattern = pattern;
|
|
238
|
+
this.mark = mark;
|
|
239
|
+
this.negative = negative;
|
|
240
|
+
define(this, "body", body);
|
|
241
|
+
define(this, "ignoreCase", ignoreCase);
|
|
242
|
+
define(this, "regexPrefix", prefix);
|
|
243
|
+
}
|
|
244
|
+
get regex() {
|
|
245
|
+
const key = UNDERSCORE + MODE_IGNORE;
|
|
246
|
+
if (this[key]) {
|
|
247
|
+
return this[key];
|
|
248
|
+
}
|
|
249
|
+
return this._make(MODE_IGNORE, key);
|
|
250
|
+
}
|
|
251
|
+
get checkRegex() {
|
|
252
|
+
const key = UNDERSCORE + MODE_CHECK_IGNORE;
|
|
253
|
+
if (this[key]) {
|
|
254
|
+
return this[key];
|
|
255
|
+
}
|
|
256
|
+
return this._make(MODE_CHECK_IGNORE, key);
|
|
257
|
+
}
|
|
258
|
+
_make(mode, key) {
|
|
259
|
+
const str = this.regexPrefix.replace(
|
|
260
|
+
REGEX_REPLACE_TRAILING_WILDCARD,
|
|
261
|
+
// It does not need to bind pattern
|
|
262
|
+
TRAILING_WILD_CARD_REPLACERS[mode]
|
|
263
|
+
);
|
|
264
|
+
const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
|
|
265
|
+
return define(this, key, regex);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
var createRule = ({
|
|
269
|
+
pattern,
|
|
270
|
+
mark
|
|
271
|
+
}, ignoreCase) => {
|
|
272
|
+
let negative = false;
|
|
273
|
+
let body = pattern;
|
|
274
|
+
if (body.indexOf("!") === 0) {
|
|
275
|
+
negative = true;
|
|
276
|
+
body = body.substr(1);
|
|
277
|
+
}
|
|
278
|
+
body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
|
|
279
|
+
const regexPrefix = makeRegexPrefix(body);
|
|
280
|
+
return new IgnoreRule(
|
|
281
|
+
pattern,
|
|
282
|
+
mark,
|
|
283
|
+
body,
|
|
284
|
+
ignoreCase,
|
|
285
|
+
negative,
|
|
286
|
+
regexPrefix
|
|
287
|
+
);
|
|
288
|
+
};
|
|
289
|
+
var RuleManager = class {
|
|
290
|
+
constructor(ignoreCase) {
|
|
291
|
+
this._ignoreCase = ignoreCase;
|
|
292
|
+
this._rules = [];
|
|
293
|
+
}
|
|
294
|
+
_add(pattern) {
|
|
295
|
+
if (pattern && pattern[KEY_IGNORE]) {
|
|
296
|
+
this._rules = this._rules.concat(pattern._rules._rules);
|
|
297
|
+
this._added = true;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (isString(pattern)) {
|
|
301
|
+
pattern = {
|
|
302
|
+
pattern
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
if (checkPattern(pattern.pattern)) {
|
|
306
|
+
const rule = createRule(pattern, this._ignoreCase);
|
|
307
|
+
this._added = true;
|
|
308
|
+
this._rules.push(rule);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// @param {Array<string> | string | Ignore} pattern
|
|
312
|
+
add(pattern) {
|
|
313
|
+
this._added = false;
|
|
314
|
+
makeArray(
|
|
315
|
+
isString(pattern) ? splitPattern(pattern) : pattern
|
|
316
|
+
).forEach(this._add, this);
|
|
317
|
+
return this._added;
|
|
318
|
+
}
|
|
319
|
+
// Test one single path without recursively checking parent directories
|
|
320
|
+
//
|
|
321
|
+
// - checkUnignored `boolean` whether should check if the path is unignored,
|
|
322
|
+
// setting `checkUnignored` to `false` could reduce additional
|
|
323
|
+
// path matching.
|
|
324
|
+
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
325
|
+
// @returns {TestResult} true if a file is ignored
|
|
326
|
+
test(path15, checkUnignored, mode) {
|
|
327
|
+
let ignored = false;
|
|
328
|
+
let unignored = false;
|
|
329
|
+
let matchedRule;
|
|
330
|
+
this._rules.forEach((rule) => {
|
|
331
|
+
const { negative } = rule;
|
|
332
|
+
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const matched = rule[mode].test(path15);
|
|
336
|
+
if (!matched) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
ignored = !negative;
|
|
340
|
+
unignored = negative;
|
|
341
|
+
matchedRule = negative ? UNDEFINED : rule;
|
|
342
|
+
});
|
|
343
|
+
const ret = {
|
|
344
|
+
ignored,
|
|
345
|
+
unignored
|
|
346
|
+
};
|
|
347
|
+
if (matchedRule) {
|
|
348
|
+
ret.rule = matchedRule;
|
|
349
|
+
}
|
|
350
|
+
return ret;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
var throwError = (message, Ctor) => {
|
|
354
|
+
throw new Ctor(message);
|
|
355
|
+
};
|
|
356
|
+
var checkPath = (path15, originalPath, doThrow) => {
|
|
357
|
+
if (!isString(path15)) {
|
|
358
|
+
return doThrow(
|
|
359
|
+
`path must be a string, but got \`${originalPath}\``,
|
|
360
|
+
TypeError
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
if (!path15) {
|
|
364
|
+
return doThrow(`path must not be empty`, TypeError);
|
|
365
|
+
}
|
|
366
|
+
if (checkPath.isNotRelative(path15)) {
|
|
367
|
+
const r = "`path.relative()`d";
|
|
368
|
+
return doThrow(
|
|
369
|
+
`path should be a ${r} string, but got "${originalPath}"`,
|
|
370
|
+
RangeError
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
return true;
|
|
374
|
+
};
|
|
375
|
+
var isNotRelative = (path15) => REGEX_TEST_INVALID_PATH.test(path15);
|
|
376
|
+
checkPath.isNotRelative = isNotRelative;
|
|
377
|
+
checkPath.convert = (p) => p;
|
|
378
|
+
var Ignore = class {
|
|
379
|
+
constructor({
|
|
380
|
+
ignorecase = true,
|
|
381
|
+
ignoreCase = ignorecase,
|
|
382
|
+
allowRelativePaths = false
|
|
383
|
+
} = {}) {
|
|
384
|
+
define(this, KEY_IGNORE, true);
|
|
385
|
+
this._rules = new RuleManager(ignoreCase);
|
|
386
|
+
this._strictPathCheck = !allowRelativePaths;
|
|
387
|
+
this._initCache();
|
|
388
|
+
}
|
|
389
|
+
_initCache() {
|
|
390
|
+
this._ignoreCache = /* @__PURE__ */ Object.create(null);
|
|
391
|
+
this._testCache = /* @__PURE__ */ Object.create(null);
|
|
392
|
+
}
|
|
393
|
+
add(pattern) {
|
|
394
|
+
if (this._rules.add(pattern)) {
|
|
395
|
+
this._initCache();
|
|
396
|
+
}
|
|
397
|
+
return this;
|
|
398
|
+
}
|
|
399
|
+
// legacy
|
|
400
|
+
addPattern(pattern) {
|
|
401
|
+
return this.add(pattern);
|
|
402
|
+
}
|
|
403
|
+
// @returns {TestResult}
|
|
404
|
+
_test(originalPath, cache, checkUnignored, slices) {
|
|
405
|
+
const path15 = originalPath && checkPath.convert(originalPath);
|
|
406
|
+
checkPath(
|
|
407
|
+
path15,
|
|
408
|
+
originalPath,
|
|
409
|
+
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
410
|
+
);
|
|
411
|
+
return this._t(path15, cache, checkUnignored, slices);
|
|
412
|
+
}
|
|
413
|
+
checkIgnore(path15) {
|
|
414
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path15)) {
|
|
415
|
+
return this.test(path15);
|
|
416
|
+
}
|
|
417
|
+
const slices = path15.split(SLASH).filter(Boolean);
|
|
418
|
+
slices.pop();
|
|
419
|
+
if (slices.length) {
|
|
420
|
+
const parent = this._t(
|
|
421
|
+
slices.join(SLASH) + SLASH,
|
|
422
|
+
this._testCache,
|
|
423
|
+
true,
|
|
424
|
+
slices
|
|
425
|
+
);
|
|
426
|
+
if (parent.ignored) {
|
|
427
|
+
return parent;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return this._rules.test(path15, false, MODE_CHECK_IGNORE);
|
|
431
|
+
}
|
|
432
|
+
_t(path15, cache, checkUnignored, slices) {
|
|
433
|
+
if (path15 in cache) {
|
|
434
|
+
return cache[path15];
|
|
435
|
+
}
|
|
436
|
+
if (!slices) {
|
|
437
|
+
slices = path15.split(SLASH).filter(Boolean);
|
|
438
|
+
}
|
|
439
|
+
slices.pop();
|
|
440
|
+
if (!slices.length) {
|
|
441
|
+
return cache[path15] = this._rules.test(path15, checkUnignored, MODE_IGNORE);
|
|
442
|
+
}
|
|
443
|
+
const parent = this._t(
|
|
444
|
+
slices.join(SLASH) + SLASH,
|
|
445
|
+
cache,
|
|
446
|
+
checkUnignored,
|
|
447
|
+
slices
|
|
448
|
+
);
|
|
449
|
+
return cache[path15] = parent.ignored ? parent : this._rules.test(path15, checkUnignored, MODE_IGNORE);
|
|
450
|
+
}
|
|
451
|
+
ignores(path15) {
|
|
452
|
+
return this._test(path15, this._ignoreCache, false).ignored;
|
|
453
|
+
}
|
|
454
|
+
createFilter() {
|
|
455
|
+
return (path15) => !this.ignores(path15);
|
|
456
|
+
}
|
|
457
|
+
filter(paths) {
|
|
458
|
+
return makeArray(paths).filter(this.createFilter());
|
|
459
|
+
}
|
|
460
|
+
// @returns {TestResult}
|
|
461
|
+
test(path15) {
|
|
462
|
+
return this._test(path15, this._testCache, true);
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
var factory = (options) => new Ignore(options);
|
|
466
|
+
var isPathValid = (path15) => checkPath(path15 && checkPath.convert(path15), path15, RETURN_FALSE);
|
|
467
|
+
var setupWindows = () => {
|
|
468
|
+
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
469
|
+
checkPath.convert = makePosix;
|
|
470
|
+
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
471
|
+
checkPath.isNotRelative = (path15) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path15) || isNotRelative(path15);
|
|
472
|
+
};
|
|
473
|
+
if (
|
|
474
|
+
// Detect `process` so that it can run in browsers.
|
|
475
|
+
typeof process !== "undefined" && process.platform === "win32"
|
|
476
|
+
) {
|
|
477
|
+
setupWindows();
|
|
478
|
+
}
|
|
479
|
+
module.exports = factory;
|
|
480
|
+
factory.default = factory;
|
|
481
|
+
module.exports.isPathValid = isPathValid;
|
|
482
|
+
define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
2
485
|
|
|
3
486
|
// src/index.ts
|
|
4
487
|
import { Command, Help } from "commander";
|
|
5
|
-
import
|
|
488
|
+
import chalk15 from "chalk";
|
|
489
|
+
import inquirer7 from "inquirer";
|
|
6
490
|
|
|
7
|
-
// src/
|
|
8
|
-
|
|
9
|
-
|
|
491
|
+
// src/lib/nlp-router.ts
|
|
492
|
+
function normalize(input) {
|
|
493
|
+
return input.trim().toLowerCase().replace(/\s+/g, " ");
|
|
494
|
+
}
|
|
495
|
+
var NLP_KEYWORDS = {
|
|
496
|
+
save: ["\uC800\uC7A5", "\uC138\uC774\uBE0C", "\uCEE4\uBC0B", "\uC62C\uB824", "\uC62C\uB9AC\uAE30", "\uD478\uC2DC", "push", "commit"],
|
|
497
|
+
undo: ["\uB418\uB3CC\uB824", "\uB418\uB3CC\uB9AC\uAE30", "\uCDE8\uC18C", "\uC6D0\uB798\uB300\uB85C", "\uB864\uBC31", "\uB9AC\uC14B", "reset", "rollback"],
|
|
498
|
+
status: ["\uC0C1\uD0DC", "\uD604\uD669", "\uC5B4\uB5BB\uAC8C", "\uC5B4\uB54C", "\uC9C0\uAE08", "\uD655\uC778"],
|
|
499
|
+
diff: ["\uBCC0\uACBD", "\uBC14\uB010", "\uBB50\uBC14\uB01C", "\uCC28\uC774", "\uB2EC\uB77C\uC9C4", "\uC218\uC815\uB41C"]
|
|
500
|
+
};
|
|
501
|
+
function matchesKeywords(text, command) {
|
|
502
|
+
const keywords = NLP_KEYWORDS[command];
|
|
503
|
+
if (!keywords) return false;
|
|
504
|
+
return keywords.some((kw) => text.includes(kw.toLowerCase()));
|
|
505
|
+
}
|
|
506
|
+
var RULES = [
|
|
507
|
+
{
|
|
508
|
+
command: "init",
|
|
509
|
+
explanation: "\uAC80\uC99D \uC2A4\uD0B5\uD558\uACE0 \uBC14\uB85C \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791 --skip-gate)",
|
|
510
|
+
confidence: "high",
|
|
511
|
+
args: ["--skip-gate"],
|
|
512
|
+
test: (t2) => /기획.*(끝|완료)|노션.*(기획|완료)|검증.*(스킵|건너)|gate.*(스킵|건너)|바로.*시작/.test(t2)
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
command: "init",
|
|
516
|
+
explanation: "Notion\uC5D0\uC11C \uAC00\uC838\uC640 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791 --from-notion)",
|
|
517
|
+
confidence: "low",
|
|
518
|
+
args: ["--from-notion"],
|
|
519
|
+
test: (t2) => /노션|notion/.test(t2) && /(시작|만들|import|가져)/.test(t2)
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
command: "init",
|
|
523
|
+
explanation: "\uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791)",
|
|
524
|
+
confidence: "high",
|
|
525
|
+
test: (t2) => /프로젝트.*(만들|시작)|폴더.*만들|만들고\s*싶|하네스|초기화/.test(t2) || /^시작$/.test(t2)
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
command: "diff",
|
|
529
|
+
explanation: "\uBCC0\uACBD\uC0AC\uD56D \uC694\uC57D (vhk diff)",
|
|
530
|
+
confidence: "high",
|
|
531
|
+
test: (t2) => (matchesKeywords(t2, "diff") || /^diff$/.test(t2) || /변경사항|수정\s*내역|차이\s*보/.test(t2)) && !/저장|커밋|push|푸시|상태|현황|세이브|commit/.test(t2)
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
command: "undo",
|
|
535
|
+
explanation: "\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30 (vhk \uB418\uB3CC\uB9AC\uAE30)",
|
|
536
|
+
confidence: "high",
|
|
537
|
+
test: (t2) => matchesKeywords(t2, "undo") || /undo|커밋\s*취/.test(t2)
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
command: "status",
|
|
541
|
+
explanation: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uD655\uC778 (vhk \uC0C1\uD0DC)",
|
|
542
|
+
confidence: "high",
|
|
543
|
+
test: (t2) => matchesKeywords(t2, "status") || /^status$/.test(t2) || /브랜치.*(뭐|어디)|git\s*상태|동기화\s*상태/.test(t2)
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
command: "save",
|
|
547
|
+
explanation: "Git\uC5D0 \uC800\uC7A5 (vhk \uC800\uC7A5)",
|
|
548
|
+
confidence: "high",
|
|
549
|
+
test: (t2) => (matchesKeywords(t2, "save") || /깃허브|github/.test(t2)) && !/정리|recap|되돌|취소|rollback|reset|리셋|롤백|원래대로/.test(t2)
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
command: "recap",
|
|
553
|
+
explanation: "\uC624\uB298 \uD55C \uC77C \uC815\uB9AC (vhk \uC815\uB9AC)",
|
|
554
|
+
confidence: "high",
|
|
555
|
+
test: (t2) => /오늘.*(정리|기록)|한\s*일|세션|회고|recap|정리해/.test(t2)
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
command: "doctor",
|
|
559
|
+
explanation: "\uD658\uACBD \uC810\uAC80 (vhk doctor)",
|
|
560
|
+
confidence: "high",
|
|
561
|
+
test: (t2) => /뭔가\s*안|안\s*돼|안돼|환경\s*(점검|진단)|진단|doctor|설치.*확인|왜\s*안/.test(t2)
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
command: "gate",
|
|
565
|
+
explanation: "\uC544\uC774\uB514\uC5B4 \uAC80\uC99D (vhk \uAC80\uC99D)",
|
|
566
|
+
confidence: "high",
|
|
567
|
+
test: (t2) => /아이디어|검증|gate|go\/refine|pain\s*point/.test(t2)
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
command: "secure",
|
|
571
|
+
explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548 scan)",
|
|
572
|
+
confidence: "high",
|
|
573
|
+
test: (t2) => /보안|시크릿|비밀|키\s*유출|secure|scan/.test(t2)
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
command: "check",
|
|
577
|
+
explanation: "\uADDC\uCE59 \uC810\uAC80 (vhk \uC810\uAC80)",
|
|
578
|
+
confidence: "high",
|
|
579
|
+
test: (t2) => /규칙.*(점검|위반)|린트|check|위반/.test(t2)
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
command: "sync",
|
|
583
|
+
explanation: "\uADDC\uCE59 \uD30C\uC77C \uB3D9\uAE30\uD654 (vhk \uADDC\uCE59)",
|
|
584
|
+
confidence: "high",
|
|
585
|
+
test: (t2) => /규칙.*(맞|동기)|sync|cursorrules|claude\.md.*맞/.test(t2)
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
command: "ship",
|
|
589
|
+
explanation: "\uBC30\uD3EC \uCCB4\uD06C + \uD68C\uACE0 (vhk ship)",
|
|
590
|
+
confidence: "high",
|
|
591
|
+
test: (t2) => /배포|출시|릴리스|ship|빌드\s*전/.test(t2)
|
|
592
|
+
}
|
|
593
|
+
];
|
|
594
|
+
function routeNaturalLanguage(input) {
|
|
595
|
+
const normalized = normalize(input);
|
|
596
|
+
if (!normalized) return null;
|
|
597
|
+
for (const rule of RULES) {
|
|
598
|
+
if (rule.test(normalized)) {
|
|
599
|
+
return {
|
|
600
|
+
command: rule.command,
|
|
601
|
+
explanation: rule.explanation,
|
|
602
|
+
confidence: rule.confidence,
|
|
603
|
+
args: rule.args
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
function extractNotionUrl(input) {
|
|
610
|
+
const m = input.match(/https?:\/\/[^\s]+/i);
|
|
611
|
+
return m?.[0];
|
|
612
|
+
}
|
|
10
613
|
|
|
11
614
|
// src/i18n/ko.ts
|
|
12
615
|
var ko = {
|
|
616
|
+
status: {
|
|
617
|
+
title: "\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC",
|
|
618
|
+
notGitRepo: "Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2C8\uC5D0\uC694. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
|
|
619
|
+
branch: "\uBE0C\uB79C\uCE58:",
|
|
620
|
+
changes: "\uBCC0\uACBD:",
|
|
621
|
+
recentCommits: "\uCD5C\uADFC \uCEE4\uBC0B (3):",
|
|
622
|
+
noCommits: "\uCEE4\uBC0B \uC5C6\uC74C",
|
|
623
|
+
remote: "\uC6D0\uACA9:",
|
|
624
|
+
noUpstream: "upstream \uC5C6\uC74C",
|
|
625
|
+
inSync: "\uB3D9\uAE30\uD654\uB428",
|
|
626
|
+
ahead: (n) => `\u2191${n} ahead`,
|
|
627
|
+
behind: (n) => `\u2193${n} behind`,
|
|
628
|
+
package: "package.json:",
|
|
629
|
+
noPackage: "package.json \uC5C6\uC74C",
|
|
630
|
+
detached: "(detached HEAD)",
|
|
631
|
+
unknownBranch: "(\uC54C \uC218 \uC5C6\uC74C)"
|
|
632
|
+
},
|
|
633
|
+
save: {
|
|
634
|
+
title: "\uC800\uC7A5\uD558\uAE30",
|
|
635
|
+
notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
|
|
636
|
+
noChanges: "\uC800\uC7A5\uD560 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
637
|
+
filesHeader: (n) => `\uBCC0\uACBD\uB41C \uD30C\uC77C (${n}\uAC1C):`,
|
|
638
|
+
commitMessage: "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 (Enter\uB85C \uAE30\uBCF8\uAC12 \uC0AC\uC6A9):",
|
|
639
|
+
saving: "\uC800\uC7A5 \uC911...",
|
|
640
|
+
pushing: "\uC6D0\uACA9 \uC800\uC7A5\uC18C\uC5D0 \uC62C\uB9AC\uB294 \uC911...",
|
|
641
|
+
successWithPush: "\uC800\uC7A5 + \uC6D0\uACA9 \uC5C5\uB85C\uB4DC \uC644\uB8CC!",
|
|
642
|
+
successLocal: "\uB85C\uCEEC \uC800\uC7A5 \uC644\uB8CC!",
|
|
643
|
+
noRemote: "\uC6D0\uACA9 \uC800\uC7A5\uC18C\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC544 push\uB97C \uAC74\uB108\uB6F0\uC5C8\uC2B5\uB2C8\uB2E4.",
|
|
644
|
+
failed: "\uC800\uC7A5 \uC2E4\uD328",
|
|
645
|
+
done: (n) => `${n}\uAC1C \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC!`
|
|
646
|
+
},
|
|
647
|
+
undo: {
|
|
648
|
+
title: "\uB418\uB3CC\uB9AC\uAE30",
|
|
649
|
+
notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
|
|
650
|
+
noCommits: "\uB418\uB3CC\uB9B4 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
651
|
+
recentHeader: "\u{1F4CB} \uCD5C\uADFC \uCEE4\uBC0B:",
|
|
652
|
+
howMany: "\uBA87 \uAC1C\uC758 \uCEE4\uBC0B\uC744 \uB418\uB3CC\uB9B4\uAE4C\uC694?",
|
|
653
|
+
alreadyPushed: "\uC774 \uCEE4\uBC0B\uC740 \uC774\uBBF8 \uC6D0\uACA9\uC5D0 \uC62C\uB77C\uAC14\uC2B5\uB2C8\uB2E4. \uB418\uB3CC\uB9AC\uBA74 \uCDA9\uB3CC\uC774 \uC0DD\uAE38 \uC218 \uC788\uC5B4\uC694.",
|
|
654
|
+
confirmMessage: "\uCD5C\uADFC \uCEE4\uBC0B\uC744 \uB418\uB3CC\uB9AC\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
|
|
655
|
+
cancelled: "\uCDE8\uC18C\uB428",
|
|
656
|
+
success: "\uB418\uB3CC\uB9AC\uAE30 \uC644\uB8CC! \uBCC0\uACBD\uC0AC\uD56D\uC740 \uADF8\uB300\uB85C \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4.",
|
|
657
|
+
stagedHint: "\uBCC0\uACBD\uC0AC\uD56D\uC740 \uC2A4\uD14C\uC774\uC9D5 \uC601\uC5ED\uC5D0 \uB0A8\uC544 \uC788\uC5B4\uC694.",
|
|
658
|
+
failed: "\uB418\uB3CC\uB9AC\uAE30 \uC2E4\uD328"
|
|
659
|
+
},
|
|
660
|
+
diff: {
|
|
661
|
+
title: "\uBCC0\uACBD\uC0AC\uD56D \uD655\uC778",
|
|
662
|
+
notGitRepo: "git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
|
|
663
|
+
noChanges: "\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
|
|
664
|
+
stagedHeader: "\u{1F4E6} \uCEE4\uBC0B \uB300\uAE30 (staged):",
|
|
665
|
+
unstagedHeader: "\u270F\uFE0F \uC218\uC815\uB428 (unstaged):",
|
|
666
|
+
untrackedHeader: (n) => `\u2795 \uC0C8 \uD30C\uC77C (${n}\uAC1C):`,
|
|
667
|
+
summaryHeader: "\u{1F4CA} \uCD1D \uBCC0\uACBD \uC694\uC57D (\uC791\uC5C5 \uD2B8\uB9AC vs HEAD)",
|
|
668
|
+
filesLine: (n) => `\uD30C\uC77C: ${n}\uAC1C`
|
|
669
|
+
},
|
|
13
670
|
start: {
|
|
14
671
|
title: "\u{1F527} VHK \u2014 \uBB34\uC5C7\uC744 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
|
|
15
672
|
subtitle: "\uBC88\uD638\uB9CC \uACE0\uB974\uBA74 \uB429\uB2C8\uB2E4. \uBA85\uB839\uC5B4\uB97C \uC678\uC6B8 \uD544\uC694 \uC5C6\uC5B4\uC694.",
|
|
@@ -87,7 +744,9 @@ var ko = {
|
|
|
87
744
|
notionReviewHint: "docs/PRD.md\uB97C \uC77D\uACE0 \u{1F449} \uC5EC\uAE30\uB97C \uCC44\uC6CC\uC8FC\uC138\uC694 \uD56D\uBAA9\uC744 \uCC44\uC6B0\uC138\uC694",
|
|
88
745
|
gitHintLabel: "\uD130\uBBF8\uB110\uC5D0 \uBCF5\uC0AC\uD560 \uBA85\uB839 (\uC544\uB798 \uBC15\uC2A4 \uBCF5\uBD99):",
|
|
89
746
|
gitHintCommand: 'git init && git add . && git commit -m "feat: \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791"',
|
|
90
|
-
startDev: "\uC774\uC81C \uAC1C\uBC1C\uD574 \uBCF4\uC138\uC694! \u{1F680}"
|
|
747
|
+
startDev: "\uC774\uC81C \uAC1C\uBC1C\uD574 \uBCF4\uC138\uC694! \u{1F680}",
|
|
748
|
+
commandsMdDone: "\u{1F4CB} COMMANDS.md \uC0DD\uC131",
|
|
749
|
+
scriptsDone: "\u{1F4E6} package.json scripts \uCD94\uAC00"
|
|
91
750
|
},
|
|
92
751
|
recap: {
|
|
93
752
|
title: "\u{1F4DD} \uC624\uB298 \uD55C \uC77C \uC815\uB9AC",
|
|
@@ -113,6 +772,21 @@ var ko = {
|
|
|
113
772
|
allPassed: "\u{1F389} \uADDC\uCE59\uC744 \uBAA8\uB450 \uC9C0\uCF30\uC5B4\uC694!",
|
|
114
773
|
summary: "\u{1F4CA} \uC810\uAC80 \uACB0\uACFC:"
|
|
115
774
|
},
|
|
775
|
+
doctor: {
|
|
776
|
+
title: "\u{1FA7A} \uAC1C\uBC1C \uD658\uACBD \uC810\uAC80",
|
|
777
|
+
allOk: "\u{1F389} \uAC1C\uBC1C \uD658\uACBD \uC900\uBE44 \uC644\uB8CC!",
|
|
778
|
+
missing: "\u26A0\uFE0F \uC77C\uBD80 \uB3C4\uAD6C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
779
|
+
missingHint: "\uC704 \uC548\uB0B4\uB97C \uB530\uB77C \uC124\uCE58\uD558\uC138\uC694.",
|
|
780
|
+
projectFiles: "\u{1F4C1} \uD504\uB85C\uC81D\uD2B8 \uD30C\uC77C \uD655\uC778:",
|
|
781
|
+
envNotIgnored: "\u26A0\uFE0F .env\uAC00 .gitignore\uC5D0 \uC5C6\uC74C! \uCD94\uAC00\uD558\uC138\uC694",
|
|
782
|
+
nextOkMessage: "\uD658\uACBD \uC810\uAC80 \uD1B5\uACFC! \uC774\uC81C \uD504\uB85C\uC81D\uD2B8\uB97C \uC2DC\uC791\uD558\uC138\uC694.",
|
|
783
|
+
nextRetryMessage: "\uC704 \uB3C4\uAD6C\uB97C \uC124\uCE58\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694."
|
|
784
|
+
},
|
|
785
|
+
nlp: {
|
|
786
|
+
matched: "\uC774\uAC8C \uB9DE\uB098\uC694?",
|
|
787
|
+
notMatched: "\uBB34\uC2A8 \uB73B\uC778\uC9C0 \uBAA8\uB974\uACA0\uC5B4\uC694. vhk\uB97C \uC785\uB825\uD558\uBA74 \uBA54\uB274\uC5D0\uC11C \uC120\uD0DD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
788
|
+
menuHint: "vhk\uB97C \uC785\uB825\uD558\uBA74 \uBA54\uB274\uC5D0\uC11C \uC120\uD0DD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
|
|
789
|
+
},
|
|
116
790
|
secure: {
|
|
117
791
|
title: "\u{1F512} \uBE44\uBC00\uBC88\uD638\xB7\uD0A4 \uC720\uCD9C \uAC80\uC0AC",
|
|
118
792
|
noGitignore: "\u26A0\uFE0F .gitignore \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694!",
|
|
@@ -127,8 +801,64 @@ var ko = {
|
|
|
127
801
|
cursorrulesDone: "\u2705 .cursorrules \uB9DE\uCDA4 \uC644\uB8CC",
|
|
128
802
|
claudeDone: "\u2705 CLAUDE.md \uB9DE\uCDA4 \uC644\uB8CC",
|
|
129
803
|
done: "\u{1F504} \uB9DE\uCD94\uAE30 \uC644\uB8CC!"
|
|
804
|
+
},
|
|
805
|
+
ship: {
|
|
806
|
+
title: "\u{1F680} \uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
|
|
807
|
+
checklist: "\u{1F4CB} \uBC30\uD3EC \uC804 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
|
|
808
|
+
retro: "\u{1F50D} \uBC30\uD3EC \uD68C\uACE0",
|
|
809
|
+
buildLogCreated: "\u2705 \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131 \uC644\uB8CC",
|
|
810
|
+
buildLogDone: (rel) => `\u2705 \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131 \uC644\uB8CC: ${rel}`,
|
|
811
|
+
questionWell: "\uC798\uB41C \uC810\uC740?",
|
|
812
|
+
questionWrong: "\uC5B4\uB824\uC6E0\uB358 \uC810\uC740?",
|
|
813
|
+
questionLearned: "\uBC30\uC6B4 \uC810\uC740?",
|
|
814
|
+
questionNext: "\uB2E4\uC74C \uBC84\uC804\uC5D0\uC11C \uD560 \uAC83\uC740?",
|
|
815
|
+
checkboxPrompt: "\uC644\uB8CC\uD55C \uD56D\uBAA9\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
|
|
816
|
+
incompleteHeader: "\u26A0\uFE0F \uC544\uC9C1 \uC644\uB8CC\uD558\uC9C0 \uC54A\uC740 \uD56D\uBAA9:",
|
|
817
|
+
proceedConfirm: "\uADF8\uB798\uB3C4 \uACC4\uC18D \uC9C4\uD589\uD560\uAE4C\uC694?",
|
|
818
|
+
allPassed: "\u2705 \uBAA8\uB4E0 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uD1B5\uACFC!",
|
|
819
|
+
retryMessage: "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uB97C \uB9C8\uCE5C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uBCF4\uC138\uC694.",
|
|
820
|
+
retryCursorHint: "\uBE4C\uB4DC\uD558\uACE0 \uD14C\uC2A4\uD2B8 \uB3CC\uB824\uC918",
|
|
821
|
+
versionPrompt: "\uBC30\uD3EC \uBC84\uC804\uC740?",
|
|
822
|
+
versionHint: "\uC608: 0.4.0",
|
|
823
|
+
emptySection: "(\uBBF8\uC791\uC131)",
|
|
824
|
+
emptyNext: "(\uBBF8\uC815)",
|
|
825
|
+
deployMessage: "\uBE4C\uB4DC \uB85C\uADF8\uB97C \uC800\uC7A5\uD588\uC5B4\uC694! \uC774\uC81C \uC2E4\uC81C \uBC30\uD3EC\uB97C \uC9C4\uD589\uD558\uC138\uC694.",
|
|
826
|
+
deployCursorHint: "\uBC30\uD3EC\uD574\uC918",
|
|
827
|
+
checkBuild: "\uBE4C\uB4DC\uAC00 \uC131\uACF5\uD588\uB098\uC694?",
|
|
828
|
+
hintBuild: "pnpm build",
|
|
829
|
+
checkTest: "\uBAA8\uB4E0 \uD14C\uC2A4\uD2B8\uAC00 \uD1B5\uACFC\uD588\uB098\uC694?",
|
|
830
|
+
hintTest: "pnpm test --run",
|
|
831
|
+
checkVersion: "package.json \uBC84\uC804\uC744 \uC62C\uB838\uB098\uC694?",
|
|
832
|
+
hintVersion: "version \uD544\uB4DC \uD655\uC778",
|
|
833
|
+
checkChangelog: "\uBCC0\uACBD \uB0B4\uC6A9\uC744 \uAE30\uB85D\uD588\uB098\uC694?",
|
|
834
|
+
hintChangelog: "README \uB610\uB294 CHANGELOG",
|
|
835
|
+
checkSecurity: "\uBCF4\uC548 \uC2A4\uCE94\uC744 \uB3CC\uB838\uB098\uC694?",
|
|
836
|
+
hintSecurity: "vhk \uBCF4\uC548 scan",
|
|
837
|
+
checkCommit: "\uBAA8\uB4E0 \uBCC0\uACBD\uC774 \uCEE4\uBC0B\uB418\uC5C8\uB098\uC694?",
|
|
838
|
+
hintCommit: "git status \uD655\uC778"
|
|
130
839
|
}
|
|
131
840
|
};
|
|
841
|
+
function lookup(path15) {
|
|
842
|
+
const parts = path15.split(".");
|
|
843
|
+
let cur = ko;
|
|
844
|
+
for (const part of parts) {
|
|
845
|
+
if (cur === null || typeof cur !== "object") return void 0;
|
|
846
|
+
cur = cur[part];
|
|
847
|
+
}
|
|
848
|
+
return cur;
|
|
849
|
+
}
|
|
850
|
+
function t(key, ...args) {
|
|
851
|
+
const value = lookup(key);
|
|
852
|
+
if (typeof value === "function") {
|
|
853
|
+
return value(...args);
|
|
854
|
+
}
|
|
855
|
+
if (typeof value === "string") return value;
|
|
856
|
+
return key;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// src/commands/gate.ts
|
|
860
|
+
import inquirer from "inquirer";
|
|
861
|
+
import chalk2 from "chalk";
|
|
132
862
|
|
|
133
863
|
// src/lib/next-step.ts
|
|
134
864
|
import chalk from "chalk";
|
|
@@ -237,7 +967,7 @@ ${ko.gate.checklistStart}
|
|
|
237
967
|
name: "answer",
|
|
238
968
|
message: `[${i + 1}/${total}] ${q.stage}: ${q.question}`
|
|
239
969
|
}]);
|
|
240
|
-
const { status } = await inquirer.prompt([{
|
|
970
|
+
const { status: status2 } = await inquirer.prompt([{
|
|
241
971
|
type: "list",
|
|
242
972
|
name: "status",
|
|
243
973
|
message: ko.gate.verdictPrompt(q.failIf),
|
|
@@ -247,10 +977,10 @@ ${ko.gate.checklistStart}
|
|
|
247
977
|
{ name: ko.gate.statusFailChoice, value: "fail" }
|
|
248
978
|
]
|
|
249
979
|
}]);
|
|
250
|
-
if (
|
|
251
|
-
if (
|
|
252
|
-
results.push({ id: q.id, stage: q.stage, status, answer });
|
|
253
|
-
const icon =
|
|
980
|
+
if (status2 === "fail") failCount++;
|
|
981
|
+
if (status2 === "hold") holdCount++;
|
|
982
|
+
results.push({ id: q.id, stage: q.stage, status: status2, answer });
|
|
983
|
+
const icon = status2 === "pass" ? chalk2.green(ko.gate.statusPassLine) : status2 === "hold" ? chalk2.yellow(ko.gate.statusHoldLine) : chalk2.red(ko.gate.statusFailLine);
|
|
254
984
|
console.log(icon);
|
|
255
985
|
}
|
|
256
986
|
console.log(chalk2.bold(`
|
|
@@ -287,6 +1017,7 @@ ${ko.gate.verdictTitle}
|
|
|
287
1017
|
// src/commands/init.ts
|
|
288
1018
|
import inquirer2 from "inquirer";
|
|
289
1019
|
import chalk4 from "chalk";
|
|
1020
|
+
import fs2 from "fs";
|
|
290
1021
|
import path2 from "path";
|
|
291
1022
|
|
|
292
1023
|
// src/templates/claude-md.ts
|
|
@@ -467,6 +1198,36 @@ function ADR_TEMPLATE() {
|
|
|
467
1198
|
].join("\n");
|
|
468
1199
|
}
|
|
469
1200
|
|
|
1201
|
+
// src/templates/commands-md.ts
|
|
1202
|
+
function COMMANDS_MD_TEMPLATE() {
|
|
1203
|
+
return [
|
|
1204
|
+
"# \u{1F4CB} \uD55C\uAD6D\uC5B4 \uBA85\uB839\uC5B4 \uAC00\uC774\uB4DC",
|
|
1205
|
+
"",
|
|
1206
|
+
"\uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uC790\uC8FC \uC4F0\uB294 \uBA85\uB839\uC5B4\uC785\uB2C8\uB2E4.",
|
|
1207
|
+
"Cursor\uC5D0\uAC8C \uD55C\uAD6D\uC5B4\uB85C \uB9D0\uD574\uB3C4 \uB429\uB2C8\uB2E4.",
|
|
1208
|
+
"",
|
|
1209
|
+
"## \uB9E4\uC77C \uC4F0\uB294 \uBA85\uB839\uC5B4",
|
|
1210
|
+
"",
|
|
1211
|
+
"| \uD558\uACE0 \uC2F6\uC740 \uAC83 | \uD130\uBBF8\uB110 \uBA85\uB839 | Cursor\uC5D0\uAC8C \uB9D0\uD558\uAE30 |",
|
|
1212
|
+
"|-------------|-----------|------------------|",
|
|
1213
|
+
'| \uC800\uC7A5 | `git add . && git commit -m "\uBA54\uC2DC\uC9C0"` | "\uC800\uC7A5\uD574" |',
|
|
1214
|
+
'| \uC624\uB298 \uC815\uB9AC | `vhk \uC815\uB9AC` | "\uC624\uB298 \uD55C \uC77C \uC815\uB9AC\uD574" |',
|
|
1215
|
+
'| \uADDC\uCE59 \uC810\uAC80 | `vhk \uC810\uAC80` | "\uADDC\uCE59 \uC810\uAC80\uD574" |',
|
|
1216
|
+
'| \uBCF4\uC548 \uC2A4\uCE94 | `vhk \uBCF4\uC548 scan` | "\uBCF4\uC548 \uC2A4\uCE94 \uB3CC\uB824" |',
|
|
1217
|
+
'| \uBE4C\uB4DC+\uD14C\uC2A4\uD2B8 | `pnpm build; pnpm test --run` | "\uBE4C\uB4DC\uD558\uACE0 \uD14C\uC2A4\uD2B8 \uB3CC\uB824" |',
|
|
1218
|
+
'| \uBC30\uD3EC | `vhk \uBC30\uD3EC` | "\uBC30\uD3EC\uD574" |',
|
|
1219
|
+
"",
|
|
1220
|
+
"## \uD658\uACBD \uC810\uAC80",
|
|
1221
|
+
"",
|
|
1222
|
+
"```bash",
|
|
1223
|
+
"vhk doctor",
|
|
1224
|
+
"```",
|
|
1225
|
+
"",
|
|
1226
|
+
"---",
|
|
1227
|
+
"*Generated by `vhk init` \u2014 \uC218\uC815\uD574\uB3C4 \uB429\uB2C8\uB2E4*"
|
|
1228
|
+
].join("\n");
|
|
1229
|
+
}
|
|
1230
|
+
|
|
470
1231
|
// src/utils/logger.ts
|
|
471
1232
|
import chalk3 from "chalk";
|
|
472
1233
|
var log = {
|
|
@@ -521,7 +1282,7 @@ function extractPageId(url) {
|
|
|
521
1282
|
function getPageTitle(page) {
|
|
522
1283
|
for (const prop of Object.values(page.properties)) {
|
|
523
1284
|
if (prop.type === "title") {
|
|
524
|
-
return prop.title.map((
|
|
1285
|
+
return prop.title.map((t2) => t2.plain_text).join("");
|
|
525
1286
|
}
|
|
526
1287
|
}
|
|
527
1288
|
return "Untitled";
|
|
@@ -551,7 +1312,7 @@ function extractText(block) {
|
|
|
551
1312
|
const type = block.type;
|
|
552
1313
|
const data = block[type];
|
|
553
1314
|
if (!data?.rich_text) return "";
|
|
554
|
-
return data.rich_text.map((
|
|
1315
|
+
return data.rich_text.map((t2) => t2.plain_text).join("");
|
|
555
1316
|
}
|
|
556
1317
|
function parseBlocks(blocks) {
|
|
557
1318
|
const sections = {};
|
|
@@ -643,7 +1404,7 @@ var PROJECT_TYPES = [
|
|
|
643
1404
|
{ name: "\u{1F916} \uB178\uC158 \uD1B5\uD569/MCP \uC11C\uBC84", value: "notion" },
|
|
644
1405
|
{ name: "\u{1F4F1} \uBAA8\uBC14\uC77C \uC571 (Flutter)", value: "mobile" }
|
|
645
1406
|
];
|
|
646
|
-
var VALID_TYPES = PROJECT_TYPES.map((
|
|
1407
|
+
var VALID_TYPES = PROJECT_TYPES.map((t2) => t2.value);
|
|
647
1408
|
var STACK_PRESETS = {
|
|
648
1409
|
webapp: ["Next.js", "TypeScript", "Tailwind CSS", "shadcn/ui", "Supabase", "Vercel"],
|
|
649
1410
|
extension: ["Vite", "TypeScript", "@crxjs/vite-plugin", "Chrome Extension Manifest V3"],
|
|
@@ -742,6 +1503,7 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
|
|
|
742
1503
|
writeFile(fullPath, content);
|
|
743
1504
|
log.success(filePath);
|
|
744
1505
|
}
|
|
1506
|
+
await writeInitExtras(cwd);
|
|
745
1507
|
console.log(chalk4.bold.green(`
|
|
746
1508
|
${ko.init.done}`));
|
|
747
1509
|
console.log(chalk4.dim(`
|
|
@@ -795,16 +1557,95 @@ function generateFiles(name, description, stack, prdContent = {}) {
|
|
|
795
1557
|
`
|
|
796
1558
|
};
|
|
797
1559
|
}
|
|
1560
|
+
var VHK_PACKAGE_SCRIPTS = {
|
|
1561
|
+
save: "git add . && git commit -m",
|
|
1562
|
+
check: "vhk check",
|
|
1563
|
+
scan: "vhk secure scan",
|
|
1564
|
+
recap: "vhk recap",
|
|
1565
|
+
ship: "vhk ship",
|
|
1566
|
+
doctor: "vhk doctor"
|
|
1567
|
+
};
|
|
1568
|
+
function enhancePackageScripts(projectDir) {
|
|
1569
|
+
const pkgPath = path2.join(projectDir, "package.json");
|
|
1570
|
+
if (!fs2.existsSync(pkgPath)) return false;
|
|
1571
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
1572
|
+
pkg.scripts = { ...pkg.scripts, ...VHK_PACKAGE_SCRIPTS };
|
|
1573
|
+
fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
1574
|
+
return true;
|
|
1575
|
+
}
|
|
1576
|
+
async function writeInitExtras(projectDir) {
|
|
1577
|
+
const commandsPath = path2.join(projectDir, "COMMANDS.md");
|
|
1578
|
+
if (fileExists(commandsPath)) {
|
|
1579
|
+
const { overwrite } = await inquirer2.prompt([{
|
|
1580
|
+
type: "confirm",
|
|
1581
|
+
name: "overwrite",
|
|
1582
|
+
message: ko.init.overwrite("COMMANDS.md"),
|
|
1583
|
+
default: false
|
|
1584
|
+
}]);
|
|
1585
|
+
if (!overwrite) {
|
|
1586
|
+
log.warn(ko.init.skipped("COMMANDS.md"));
|
|
1587
|
+
} else {
|
|
1588
|
+
writeFile(commandsPath, COMMANDS_MD_TEMPLATE());
|
|
1589
|
+
log.success(ko.init.commandsMdDone);
|
|
1590
|
+
}
|
|
1591
|
+
} else {
|
|
1592
|
+
writeFile(commandsPath, COMMANDS_MD_TEMPLATE());
|
|
1593
|
+
log.success(ko.init.commandsMdDone);
|
|
1594
|
+
}
|
|
1595
|
+
if (enhancePackageScripts(projectDir)) {
|
|
1596
|
+
log.success(ko.init.scriptsDone);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
798
1599
|
|
|
799
1600
|
// src/commands/recap.ts
|
|
800
1601
|
import inquirer3 from "inquirer";
|
|
801
1602
|
import chalk5 from "chalk";
|
|
802
|
-
import
|
|
803
|
-
import
|
|
1603
|
+
import fs5 from "fs";
|
|
1604
|
+
import path6 from "path";
|
|
804
1605
|
|
|
805
1606
|
// src/lib/git.ts
|
|
1607
|
+
import path4 from "path";
|
|
806
1608
|
import simpleGit from "simple-git";
|
|
1609
|
+
|
|
1610
|
+
// src/lib/check-secure.ts
|
|
1611
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
1612
|
+
import fs3 from "fs";
|
|
1613
|
+
import path3 from "path";
|
|
1614
|
+
function loadGitignore(rootDir) {
|
|
1615
|
+
const ig = (0, import_ignore.default)();
|
|
1616
|
+
const gitignorePath = path3.join(rootDir, ".gitignore");
|
|
1617
|
+
if (fs3.existsSync(gitignorePath)) {
|
|
1618
|
+
const content = fs3.readFileSync(gitignorePath, "utf-8");
|
|
1619
|
+
ig.add(content);
|
|
1620
|
+
}
|
|
1621
|
+
return ig;
|
|
1622
|
+
}
|
|
1623
|
+
function isPathIgnored(ig, relativePath) {
|
|
1624
|
+
const normalized = relativePath.replace(/\\/g, "/");
|
|
1625
|
+
return ig.ignores(normalized);
|
|
1626
|
+
}
|
|
1627
|
+
function filterTrackedPaths(paths, rootDir = process.cwd()) {
|
|
1628
|
+
const ig = loadGitignore(rootDir);
|
|
1629
|
+
return paths.filter((p) => !isPathIgnored(ig, p.replace(/\\/g, "/")));
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// src/lib/git.ts
|
|
807
1633
|
var git = simpleGit();
|
|
1634
|
+
function isNoiseRecapPath(filePath) {
|
|
1635
|
+
const base = path4.basename(filePath);
|
|
1636
|
+
if (base.includes("${") || base.includes("`")) return true;
|
|
1637
|
+
if (/^\d+(\.\d+)?$/.test(base)) return true;
|
|
1638
|
+
if (/^(pnpm|vhk|npm|node|yarn)$/i.test(base)) return true;
|
|
1639
|
+
if (!filePath.includes("/") && !filePath.includes("\\") && !base.includes(".")) {
|
|
1640
|
+
if (!/^(README|LICENSE|Makefile|Dockerfile|CHANGELOG)$/i.test(base)) return true;
|
|
1641
|
+
}
|
|
1642
|
+
return false;
|
|
1643
|
+
}
|
|
1644
|
+
function filterRecapFiles(files) {
|
|
1645
|
+
const paths = files.map((f) => f.file);
|
|
1646
|
+
const tracked = new Set(filterTrackedPaths(paths));
|
|
1647
|
+
return files.filter((f) => tracked.has(f.file) && !isNoiseRecapPath(f.file));
|
|
1648
|
+
}
|
|
808
1649
|
function fileStatus(workingDir) {
|
|
809
1650
|
if (workingDir === "?") return "new";
|
|
810
1651
|
if (workingDir === "D") return "deleted";
|
|
@@ -818,17 +1659,19 @@ async function getSessionDiff(since) {
|
|
|
818
1659
|
const statByFile = new Map(
|
|
819
1660
|
diffSummary.files.map((f) => [f.file, f])
|
|
820
1661
|
);
|
|
821
|
-
const files =
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1662
|
+
const files = filterRecapFiles(
|
|
1663
|
+
statusResult.files.map((f) => {
|
|
1664
|
+
const stat = statByFile.get(f.path);
|
|
1665
|
+
return {
|
|
1666
|
+
file: f.path,
|
|
1667
|
+
insertions: stat?.insertions ?? 0,
|
|
1668
|
+
deletions: stat?.deletions ?? 0,
|
|
1669
|
+
status: fileStatus(f.working_dir)
|
|
1670
|
+
};
|
|
1671
|
+
})
|
|
1672
|
+
);
|
|
830
1673
|
return {
|
|
831
|
-
filesChanged:
|
|
1674
|
+
filesChanged: files.length,
|
|
832
1675
|
insertions: diffSummary.insertions,
|
|
833
1676
|
deletions: diffSummary.deletions,
|
|
834
1677
|
files
|
|
@@ -855,8 +1698,8 @@ async function isGitRepo() {
|
|
|
855
1698
|
}
|
|
856
1699
|
|
|
857
1700
|
// src/lib/adr.ts
|
|
858
|
-
import
|
|
859
|
-
import
|
|
1701
|
+
import fs4 from "fs";
|
|
1702
|
+
import path5 from "path";
|
|
860
1703
|
var ADR_RULES = [
|
|
861
1704
|
{
|
|
862
1705
|
title: "\uC758\uC874\uC131 \uBCC0\uACBD",
|
|
@@ -884,10 +1727,10 @@ var ADR_RULES = [
|
|
|
884
1727
|
test: (f) => /\.env\.example$|auth\/|middleware\.(ts|js)$/.test(f)
|
|
885
1728
|
}
|
|
886
1729
|
];
|
|
887
|
-
function detectAdrCandidates(
|
|
1730
|
+
function detectAdrCandidates(diff2) {
|
|
888
1731
|
const candidates = [];
|
|
889
1732
|
for (const rule of ADR_RULES) {
|
|
890
|
-
const matched =
|
|
1733
|
+
const matched = diff2.files.map((f) => f.file).filter(rule.test);
|
|
891
1734
|
if (matched.length > 0) {
|
|
892
1735
|
candidates.push({
|
|
893
1736
|
title: rule.title,
|
|
@@ -899,20 +1742,20 @@ function detectAdrCandidates(diff) {
|
|
|
899
1742
|
return candidates;
|
|
900
1743
|
}
|
|
901
1744
|
function nextAdrNumber(adrDir) {
|
|
902
|
-
if (!
|
|
903
|
-
const nums =
|
|
1745
|
+
if (!fs4.existsSync(adrDir)) return 1;
|
|
1746
|
+
const nums = fs4.readdirSync(adrDir).map((name) => name.match(/^ADR-(\d+)/i)?.[1]).filter((n) => Boolean(n)).map((n) => parseInt(n, 10));
|
|
904
1747
|
return nums.length ? Math.max(...nums) + 1 : 1;
|
|
905
1748
|
}
|
|
906
1749
|
function slugify(title) {
|
|
907
1750
|
return title.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9가-힣-]/g, "").slice(0, 40) || "decision";
|
|
908
1751
|
}
|
|
909
1752
|
function createAdrFile(cwd, title, context, decision, consequences) {
|
|
910
|
-
const adrDir =
|
|
911
|
-
if (!
|
|
1753
|
+
const adrDir = path5.join(cwd, "docs", "adr");
|
|
1754
|
+
if (!fs4.existsSync(adrDir)) fs4.mkdirSync(adrDir, { recursive: true });
|
|
912
1755
|
const num = nextAdrNumber(adrDir);
|
|
913
1756
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
914
1757
|
const fileName = `ADR-${String(num).padStart(3, "0")}-${slugify(title)}.md`;
|
|
915
|
-
const filePath =
|
|
1758
|
+
const filePath = path5.join(adrDir, fileName);
|
|
916
1759
|
const content = [
|
|
917
1760
|
"---",
|
|
918
1761
|
`id: ADR-${String(num).padStart(3, "0")}`,
|
|
@@ -938,7 +1781,7 @@ function createAdrFile(cwd, title, context, decision, consequences) {
|
|
|
938
1781
|
"---",
|
|
939
1782
|
`*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
940
1783
|
].join("\n");
|
|
941
|
-
|
|
1784
|
+
fs4.writeFileSync(filePath, content, "utf-8");
|
|
942
1785
|
return filePath;
|
|
943
1786
|
}
|
|
944
1787
|
|
|
@@ -953,23 +1796,24 @@ ${ko.recap.title}
|
|
|
953
1796
|
}
|
|
954
1797
|
console.log(chalk5.dim(`${ko.recap.analyzing}
|
|
955
1798
|
`));
|
|
956
|
-
const
|
|
957
|
-
const
|
|
958
|
-
|
|
1799
|
+
const since = options.since || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1800
|
+
const diff2 = await getSessionDiff(since);
|
|
1801
|
+
const commits = await getRecentCommits(10, since);
|
|
1802
|
+
if (diff2.filesChanged === 0 && commits.length === 0) {
|
|
959
1803
|
console.log(chalk5.yellow(ko.recap.noChanges));
|
|
960
1804
|
return;
|
|
961
1805
|
}
|
|
962
1806
|
console.log(chalk5.bold("\u{1F4CA} \uBCC0\uACBD \uC694\uC57D:"));
|
|
963
|
-
console.log(` \uD30C\uC77C: ${chalk5.cyan(String(
|
|
964
|
-
console.log(` \uCD94\uAC00: ${chalk5.green("+" +
|
|
965
|
-
if (
|
|
1807
|
+
console.log(` \uD30C\uC77C: ${chalk5.cyan(String(diff2.filesChanged))}\uAC1C \uBCC0\uACBD`);
|
|
1808
|
+
console.log(` \uCD94\uAC00: ${chalk5.green("+" + diff2.insertions)} / \uC0AD\uC81C: ${chalk5.red("-" + diff2.deletions)}`);
|
|
1809
|
+
if (diff2.files.length > 0) {
|
|
966
1810
|
console.log(chalk5.dim("\n \uBCC0\uACBD \uD30C\uC77C:"));
|
|
967
|
-
|
|
1811
|
+
diff2.files.slice(0, 15).forEach((f) => {
|
|
968
1812
|
const icon = f.status === "new" ? chalk5.green("\u{1F195}") : f.status === "deleted" ? chalk5.red("\u{1F5D1}\uFE0F") : chalk5.yellow("\u270F\uFE0F");
|
|
969
1813
|
console.log(` ${icon} ${f.file}`);
|
|
970
1814
|
});
|
|
971
|
-
if (
|
|
972
|
-
console.log(chalk5.dim(` ... \uC678 ${
|
|
1815
|
+
if (diff2.files.length > 15) {
|
|
1816
|
+
console.log(chalk5.dim(` ... \uC678 ${diff2.files.length - 15}\uAC1C`));
|
|
973
1817
|
}
|
|
974
1818
|
}
|
|
975
1819
|
if (commits.length > 0) {
|
|
@@ -1004,13 +1848,13 @@ ${ko.recap.title}
|
|
|
1004
1848
|
}
|
|
1005
1849
|
]);
|
|
1006
1850
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1007
|
-
const logDir =
|
|
1008
|
-
if (!
|
|
1009
|
-
const existing =
|
|
1851
|
+
const logDir = path6.join(process.cwd(), "docs", "log");
|
|
1852
|
+
if (!fs5.existsSync(logDir)) fs5.mkdirSync(logDir, { recursive: true });
|
|
1853
|
+
const existing = fs5.readdirSync(logDir).filter((f) => f.startsWith(today));
|
|
1010
1854
|
const sessionNum = existing.length + 1;
|
|
1011
1855
|
const fileName = `${today}-session-${sessionNum}.md`;
|
|
1012
|
-
const filePath =
|
|
1013
|
-
const fileList =
|
|
1856
|
+
const filePath = path6.join(logDir, fileName);
|
|
1857
|
+
const fileList = diff2.files.map((f) => `| ${f.file} | ${f.status} |`).join("\n");
|
|
1014
1858
|
const commitList = commits.slice(0, 10).map((c) => `- \`${c.hash.slice(0, 7)}\` ${c.message}`).join("\n");
|
|
1015
1859
|
const content = [
|
|
1016
1860
|
`# \uC138\uC158 \uB85C\uADF8 \u2014 ${today} #${sessionNum}`,
|
|
@@ -1028,7 +1872,7 @@ ${ko.recap.title}
|
|
|
1028
1872
|
answers.blockers,
|
|
1029
1873
|
"",
|
|
1030
1874
|
"## \uBCC0\uACBD \uD30C\uC77C",
|
|
1031
|
-
`\uCD1D ${
|
|
1875
|
+
`\uCD1D ${diff2.filesChanged}\uAC1C \uD30C\uC77C (+${diff2.insertions} -${diff2.deletions})`,
|
|
1032
1876
|
"",
|
|
1033
1877
|
"| \uD30C\uC77C | \uC0C1\uD0DC |",
|
|
1034
1878
|
"|------|------|",
|
|
@@ -1040,8 +1884,8 @@ ${ko.recap.title}
|
|
|
1040
1884
|
"---",
|
|
1041
1885
|
`*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
1042
1886
|
].join("\n");
|
|
1043
|
-
|
|
1044
|
-
const adrCandidates = detectAdrCandidates(
|
|
1887
|
+
fs5.writeFileSync(filePath, content, "utf-8");
|
|
1888
|
+
const adrCandidates = detectAdrCandidates(diff2);
|
|
1045
1889
|
if (adrCandidates.length > 0) {
|
|
1046
1890
|
console.log(chalk5.cyan.bold(`
|
|
1047
1891
|
${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
|
|
@@ -1077,7 +1921,7 @@ ${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
|
|
|
1077
1921
|
adrAnswers.decision,
|
|
1078
1922
|
adrAnswers.consequences
|
|
1079
1923
|
);
|
|
1080
|
-
console.log(chalk5.green(` \u2705 ADR \uC0DD\uC131: ${
|
|
1924
|
+
console.log(chalk5.green(` \u2705 ADR \uC0DD\uC131: ${path6.relative(process.cwd(), adrPath)}`));
|
|
1081
1925
|
}
|
|
1082
1926
|
}
|
|
1083
1927
|
}
|
|
@@ -1096,8 +1940,8 @@ ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
|
1096
1940
|
default: true
|
|
1097
1941
|
}]);
|
|
1098
1942
|
if (createTroubleshoot) {
|
|
1099
|
-
const tsDir =
|
|
1100
|
-
if (!
|
|
1943
|
+
const tsDir = path6.join(process.cwd(), "docs", "troubleshooting");
|
|
1944
|
+
if (!fs5.existsSync(tsDir)) fs5.mkdirSync(tsDir, { recursive: true });
|
|
1101
1945
|
const tsAnswers = await inquirer3.prompt([
|
|
1102
1946
|
{
|
|
1103
1947
|
type: "input",
|
|
@@ -1116,7 +1960,7 @@ ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
|
1116
1960
|
}
|
|
1117
1961
|
]);
|
|
1118
1962
|
const tsFileName = `${today}-${tsAnswers.problem.slice(0, 30).replace(/[^a-zA-Z0-9가-힣]/g, "-")}.md`;
|
|
1119
|
-
const tsFilePath =
|
|
1963
|
+
const tsFilePath = path6.join(tsDir, tsFileName);
|
|
1120
1964
|
const tsContent = [
|
|
1121
1965
|
`# \uD2B8\uB7EC\uBE14\uC288\uD305: ${tsAnswers.problem}`,
|
|
1122
1966
|
"",
|
|
@@ -1137,15 +1981,15 @@ ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
|
1137
1981
|
"---",
|
|
1138
1982
|
`*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
1139
1983
|
].join("\n");
|
|
1140
|
-
|
|
1141
|
-
console.log(chalk5.green(` \u2705 \uD2B8\uB7EC\uBE14\uC288\uD305 \uBB38\uC11C \uC0DD\uC131: ${
|
|
1984
|
+
fs5.writeFileSync(tsFilePath, tsContent, "utf-8");
|
|
1985
|
+
console.log(chalk5.green(` \u2705 \uD2B8\uB7EC\uBE14\uC288\uD305 \uBB38\uC11C \uC0DD\uC131: ${path6.relative(process.cwd(), tsFilePath)}`));
|
|
1142
1986
|
}
|
|
1143
1987
|
}
|
|
1144
1988
|
console.log(chalk5.green.bold(`
|
|
1145
1989
|
${ko.recap.done}`));
|
|
1146
|
-
console.log(chalk5.dim(` \u{1F4C4} ${
|
|
1147
|
-
const claudeMdPath =
|
|
1148
|
-
if (
|
|
1990
|
+
console.log(chalk5.dim(` \u{1F4C4} ${path6.relative(process.cwd(), filePath)}`));
|
|
1991
|
+
const claudeMdPath = path6.join(process.cwd(), "CLAUDE.md");
|
|
1992
|
+
if (fs5.existsSync(claudeMdPath)) {
|
|
1149
1993
|
const { updateClaude } = await inquirer3.prompt([{
|
|
1150
1994
|
type: "confirm",
|
|
1151
1995
|
name: "updateClaude",
|
|
@@ -1153,7 +1997,7 @@ ${ko.recap.done}`));
|
|
|
1153
1997
|
default: true
|
|
1154
1998
|
}]);
|
|
1155
1999
|
if (updateClaude) {
|
|
1156
|
-
let claudeContent =
|
|
2000
|
+
let claudeContent = fs5.readFileSync(claudeMdPath, "utf-8");
|
|
1157
2001
|
claudeContent = claudeContent.replace(
|
|
1158
2002
|
/- \*\*마지막 업데이트:\*\*.*/,
|
|
1159
2003
|
`- **\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:** ${today}`
|
|
@@ -1162,21 +2006,22 @@ ${ko.recap.done}`));
|
|
|
1162
2006
|
/- \*\*다음 액션:\*\*.*/,
|
|
1163
2007
|
`- **\uB2E4\uC74C \uC561\uC158:** ${answers.nextTodo}`
|
|
1164
2008
|
);
|
|
1165
|
-
|
|
2009
|
+
fs5.writeFileSync(claudeMdPath, claudeContent, "utf-8");
|
|
1166
2010
|
console.log(chalk5.green(" \u2705 CLAUDE.md \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC"));
|
|
1167
2011
|
}
|
|
1168
2012
|
}
|
|
2013
|
+
const gitSaveCmd = process.platform === "win32" ? 'git add .; git commit -m "recap: \uC138\uC158 \uAE30\uB85D"' : 'git add . && git commit -m "recap: \uC138\uC158 \uAE30\uB85D"';
|
|
1169
2014
|
printNextStep({
|
|
1170
2015
|
message: "\uC624\uB298 \uAE30\uB85D \uC644\uB8CC! \uC800\uC7A5\uD558\uACE0 \uC2F6\uC73C\uBA74:",
|
|
1171
|
-
command:
|
|
2016
|
+
command: gitSaveCmd,
|
|
1172
2017
|
cursorHint: "\uC800\uC7A5\uD574\uC918"
|
|
1173
2018
|
});
|
|
1174
2019
|
}
|
|
1175
2020
|
|
|
1176
2021
|
// src/commands/sync.ts
|
|
1177
2022
|
import chalk6 from "chalk";
|
|
1178
|
-
import
|
|
1179
|
-
import
|
|
2023
|
+
import fs6 from "fs";
|
|
2024
|
+
import path7 from "path";
|
|
1180
2025
|
var CURSORRULES_KEYS = ["\uCF54\uB529 \uADDC\uCE59", "\uAE30\uC220 \uC2A4\uD0DD", "\uC544\uD0A4\uD14D\uCC98", "\uB514\uC790\uC778", "Anti-patterns", "\uCEE4\uBC0B"];
|
|
1181
2026
|
var CLAUDE_MD_KEYS = ["\uAE30\uB85D", "\uB85C\uADF8", "ADR", "\uD2B8\uB7EC\uBE14\uC288\uD305", "TIL", "/done", "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8"];
|
|
1182
2027
|
function parseRulesMd(content) {
|
|
@@ -1248,8 +2093,8 @@ async function sync() {
|
|
|
1248
2093
|
${ko.sync.title}
|
|
1249
2094
|
`));
|
|
1250
2095
|
const cwd = process.cwd();
|
|
1251
|
-
const rulesPath =
|
|
1252
|
-
if (!
|
|
2096
|
+
const rulesPath = path7.join(cwd, "RULES.md");
|
|
2097
|
+
if (!fs6.existsSync(rulesPath)) {
|
|
1253
2098
|
console.log(chalk6.yellow(ko.sync.noRules));
|
|
1254
2099
|
console.log(chalk6.dim(" RULES.md\uB294 \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59\uC758 Single Source of Truth\uC785\uB2C8\uB2E4."));
|
|
1255
2100
|
console.log(chalk6.dim(" \uC0DD\uC131\uD558\uB824\uBA74: vhk init \uC2E4\uD589 \uD6C4 RULES.md\uB97C \uC791\uC131\uD558\uC138\uC694."));
|
|
@@ -1262,23 +2107,23 @@ ${ko.sync.title}
|
|
|
1262
2107
|
console.log(chalk6.dim(" ## \uCEE4\uBC0B \uCEE8\uBCA4\uC158"));
|
|
1263
2108
|
return;
|
|
1264
2109
|
}
|
|
1265
|
-
const rulesContent =
|
|
2110
|
+
const rulesContent = fs6.readFileSync(rulesPath, "utf-8");
|
|
1266
2111
|
const sections = parseRulesMd(rulesContent);
|
|
1267
2112
|
console.log(chalk6.dim(` \u{1F4C4} RULES.md \uD30C\uC2F1 \uC644\uB8CC \u2014 ${sections.length}\uAC1C \uC139\uC158`));
|
|
1268
2113
|
const firstLine = rulesContent.split("\n")[0];
|
|
1269
2114
|
const projectName = firstLine.replace(/^#\s*/, "").replace(/\s*—.*/, "").trim() || "Project";
|
|
1270
|
-
const cursorrulesPath =
|
|
1271
|
-
|
|
2115
|
+
const cursorrulesPath = path7.join(cwd, ".cursorrules");
|
|
2116
|
+
fs6.writeFileSync(cursorrulesPath, toCursorrules(sections, projectName), "utf-8");
|
|
1272
2117
|
console.log(chalk6.green(` ${ko.sync.cursorrulesDone}`));
|
|
1273
|
-
const claudePath =
|
|
1274
|
-
const existingClaude =
|
|
2118
|
+
const claudePath = path7.join(cwd, "CLAUDE.md");
|
|
2119
|
+
const existingClaude = fs6.existsSync(claudePath) ? fs6.readFileSync(claudePath, "utf-8") : `# \uAE30\uB85D \uADDC\uCE59 (${projectName})
|
|
1275
2120
|
|
|
1276
2121
|
## \uD604\uC7AC \uC0C1\uD0DC
|
|
1277
2122
|
- **Phase:** __FILL__
|
|
1278
2123
|
- **\uBE14\uB85C\uCEE4:** \uC5C6\uC74C
|
|
1279
2124
|
- **\uB2E4\uC74C \uC561\uC158:** __FILL__
|
|
1280
2125
|
- **\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:** ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`;
|
|
1281
|
-
|
|
2126
|
+
fs6.writeFileSync(claudePath, toClaudeMd(sections, existingClaude), "utf-8");
|
|
1282
2127
|
console.log(chalk6.green(` ${ko.sync.claudeDone}`));
|
|
1283
2128
|
console.log(chalk6.bold.green(`
|
|
1284
2129
|
${ko.sync.done}`));
|
|
@@ -1293,15 +2138,15 @@ ${ko.sync.done}`));
|
|
|
1293
2138
|
|
|
1294
2139
|
// src/commands/check.ts
|
|
1295
2140
|
import chalk7 from "chalk";
|
|
1296
|
-
import
|
|
1297
|
-
import
|
|
2141
|
+
import path9 from "path";
|
|
2142
|
+
import fs8 from "fs";
|
|
1298
2143
|
|
|
1299
2144
|
// src/lib/rules-parser.ts
|
|
1300
|
-
import
|
|
1301
|
-
import
|
|
2145
|
+
import fs7 from "fs";
|
|
2146
|
+
import path8 from "path";
|
|
1302
2147
|
function parseRules(rulesPath) {
|
|
1303
|
-
if (!
|
|
1304
|
-
const content =
|
|
2148
|
+
if (!fs7.existsSync(rulesPath)) return [];
|
|
2149
|
+
const content = fs7.readFileSync(rulesPath, "utf-8");
|
|
1305
2150
|
const lines = content.split("\n");
|
|
1306
2151
|
const rules = [];
|
|
1307
2152
|
let currentSection = "";
|
|
@@ -1371,17 +2216,17 @@ function createNamingRule(id, section, desc, convention) {
|
|
|
1371
2216
|
description: desc,
|
|
1372
2217
|
check: (cwd) => {
|
|
1373
2218
|
const violations = [];
|
|
1374
|
-
const srcDir =
|
|
1375
|
-
if (!
|
|
2219
|
+
const srcDir = path8.join(cwd, "src");
|
|
2220
|
+
if (!fs7.existsSync(srcDir)) return violations;
|
|
1376
2221
|
walkFiles(srcDir, (filePath) => {
|
|
1377
|
-
const name =
|
|
2222
|
+
const name = path8.basename(filePath, path8.extname(filePath));
|
|
1378
2223
|
if (convention === "kebab-case" && !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
|
|
1379
2224
|
if (!["index", "vite.config", "tsconfig"].includes(name)) {
|
|
1380
2225
|
violations.push({
|
|
1381
2226
|
ruleId: id,
|
|
1382
2227
|
severity: "warning",
|
|
1383
2228
|
message: `\uD30C\uC77C\uBA85\uC774 kebab-case\uAC00 \uC544\uB2D8: ${name}`,
|
|
1384
|
-
file:
|
|
2229
|
+
file: path8.relative(cwd, filePath)
|
|
1385
2230
|
});
|
|
1386
2231
|
}
|
|
1387
2232
|
}
|
|
@@ -1397,8 +2242,8 @@ function createStructureRule(id, section, desc, expectedPath) {
|
|
|
1397
2242
|
type: "structure",
|
|
1398
2243
|
description: desc,
|
|
1399
2244
|
check: (cwd) => {
|
|
1400
|
-
const fullPath =
|
|
1401
|
-
if (!
|
|
2245
|
+
const fullPath = path8.join(cwd, expectedPath);
|
|
2246
|
+
if (!fs7.existsSync(fullPath)) {
|
|
1402
2247
|
return [{
|
|
1403
2248
|
ruleId: id,
|
|
1404
2249
|
severity: "error",
|
|
@@ -1418,11 +2263,11 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
1418
2263
|
pattern: new RegExp(escapeRegex(pattern), "i"),
|
|
1419
2264
|
check: (cwd) => {
|
|
1420
2265
|
const violations = [];
|
|
1421
|
-
const srcDir =
|
|
1422
|
-
if (!
|
|
2266
|
+
const srcDir = path8.join(cwd, "src");
|
|
2267
|
+
if (!fs7.existsSync(srcDir)) return violations;
|
|
1423
2268
|
const regex = new RegExp(escapeRegex(pattern), "i");
|
|
1424
2269
|
walkFiles(srcDir, (filePath) => {
|
|
1425
|
-
const fileContent =
|
|
2270
|
+
const fileContent = fs7.readFileSync(filePath, "utf-8");
|
|
1426
2271
|
const fileLines = fileContent.split("\n");
|
|
1427
2272
|
fileLines.forEach((line, idx) => {
|
|
1428
2273
|
if (regex.test(line)) {
|
|
@@ -1430,7 +2275,7 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
1430
2275
|
ruleId: id,
|
|
1431
2276
|
severity: type === "banned" ? "error" : "warning",
|
|
1432
2277
|
message: type === "banned" ? `\uAE08\uC9C0 \uD328\uD134 \uBC1C\uACAC: \`${pattern}\`` : `\uD544\uC218 \uD328\uD134 \uB204\uB77D: \`${pattern}\``,
|
|
1433
|
-
file:
|
|
2278
|
+
file: path8.relative(cwd, filePath),
|
|
1434
2279
|
line: idx + 1
|
|
1435
2280
|
});
|
|
1436
2281
|
}
|
|
@@ -1442,9 +2287,9 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
1442
2287
|
};
|
|
1443
2288
|
}
|
|
1444
2289
|
function walkFiles(dir, callback) {
|
|
1445
|
-
const entries =
|
|
2290
|
+
const entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
1446
2291
|
for (const entry of entries) {
|
|
1447
|
-
const fullPath =
|
|
2292
|
+
const fullPath = path8.join(dir, entry.name);
|
|
1448
2293
|
if (entry.isDirectory()) {
|
|
1449
2294
|
if (!["node_modules", ".git", "dist", ".next"].includes(entry.name)) {
|
|
1450
2295
|
walkFiles(fullPath, callback);
|
|
@@ -1464,8 +2309,8 @@ async function check() {
|
|
|
1464
2309
|
${ko.check.title}
|
|
1465
2310
|
`));
|
|
1466
2311
|
const cwd = process.cwd();
|
|
1467
|
-
const rulesPath =
|
|
1468
|
-
if (!
|
|
2312
|
+
const rulesPath = path9.join(cwd, "RULES.md");
|
|
2313
|
+
if (!fs8.existsSync(rulesPath)) {
|
|
1469
2314
|
console.log(chalk7.yellow(ko.check.noRules));
|
|
1470
2315
|
console.log(chalk7.dim(" vhk init\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 RULES.md\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
|
|
1471
2316
|
return;
|
|
@@ -1523,8 +2368,8 @@ ${ko.check.title}
|
|
|
1523
2368
|
|
|
1524
2369
|
// src/commands/secure.ts
|
|
1525
2370
|
import chalk8 from "chalk";
|
|
1526
|
-
import
|
|
1527
|
-
import
|
|
2371
|
+
import fs10 from "fs";
|
|
2372
|
+
import path11 from "path";
|
|
1528
2373
|
|
|
1529
2374
|
// src/lib/secret-patterns.ts
|
|
1530
2375
|
var SECRET_PATTERNS = [
|
|
@@ -1589,9 +2434,32 @@ function maskSecret(value) {
|
|
|
1589
2434
|
return value.slice(0, visible) + "****";
|
|
1590
2435
|
}
|
|
1591
2436
|
|
|
1592
|
-
// src/
|
|
1593
|
-
|
|
1594
|
-
|
|
2437
|
+
// src/lib/scan-files.ts
|
|
2438
|
+
import fs9 from "fs";
|
|
2439
|
+
import path10 from "path";
|
|
2440
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
2441
|
+
"node_modules",
|
|
2442
|
+
".git",
|
|
2443
|
+
"dist",
|
|
2444
|
+
".next",
|
|
2445
|
+
".nuxt",
|
|
2446
|
+
"build",
|
|
2447
|
+
"coverage",
|
|
2448
|
+
"out",
|
|
2449
|
+
".turbo",
|
|
2450
|
+
".vercel",
|
|
2451
|
+
".cache",
|
|
2452
|
+
".pnpm",
|
|
2453
|
+
".idea",
|
|
2454
|
+
".claude",
|
|
2455
|
+
".cursor"
|
|
2456
|
+
]);
|
|
2457
|
+
var SKIP_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
2458
|
+
"pnpm-lock.yaml",
|
|
2459
|
+
"package-lock.json",
|
|
2460
|
+
"yarn.lock"
|
|
2461
|
+
]);
|
|
2462
|
+
var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1595
2463
|
".ts",
|
|
1596
2464
|
".tsx",
|
|
1597
2465
|
".js",
|
|
@@ -1601,12 +2469,49 @@ var SCAN_EXTENSIONS = [
|
|
|
1601
2469
|
".json",
|
|
1602
2470
|
".yaml",
|
|
1603
2471
|
".yml",
|
|
1604
|
-
".toml"
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
2472
|
+
".toml"
|
|
2473
|
+
]);
|
|
2474
|
+
var MAX_SCAN_FILE_BYTES = 512 * 1024;
|
|
2475
|
+
function isScannableFileName(fileName) {
|
|
2476
|
+
if (SKIP_FILE_NAMES.has(fileName)) return false;
|
|
2477
|
+
if (fileName.startsWith(".env")) return true;
|
|
2478
|
+
return SCAN_EXTENSIONS.has(path10.extname(fileName).toLowerCase());
|
|
2479
|
+
}
|
|
2480
|
+
function walkProjectFiles(rootDir, onFile, ig = loadGitignore(rootDir)) {
|
|
2481
|
+
function walk(dir) {
|
|
2482
|
+
let entries;
|
|
2483
|
+
try {
|
|
2484
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
2485
|
+
} catch {
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
for (const entry of entries) {
|
|
2489
|
+
const fullPath = path10.join(dir, entry.name);
|
|
2490
|
+
const rel = path10.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
2491
|
+
if (entry.isDirectory()) {
|
|
2492
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
2493
|
+
if (isPathIgnored(ig, `${rel}/`)) continue;
|
|
2494
|
+
walk(fullPath);
|
|
2495
|
+
continue;
|
|
2496
|
+
}
|
|
2497
|
+
if (!isScannableFileName(entry.name)) continue;
|
|
2498
|
+
if (isPathIgnored(ig, rel)) continue;
|
|
2499
|
+
let size = 0;
|
|
2500
|
+
try {
|
|
2501
|
+
size = fs9.statSync(fullPath).size;
|
|
2502
|
+
} catch {
|
|
2503
|
+
continue;
|
|
2504
|
+
}
|
|
2505
|
+
if (size > MAX_SCAN_FILE_BYTES) continue;
|
|
2506
|
+
onFile(fullPath, rel);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
walk(rootDir);
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
// src/commands/secure.ts
|
|
2513
|
+
var MAX_FINDINGS = 200;
|
|
2514
|
+
var MAX_LINE_CHARS = 4e3;
|
|
1610
2515
|
async function secure() {
|
|
1611
2516
|
console.log(chalk8.bold(`
|
|
1612
2517
|
${ko.secure.title}
|
|
@@ -1614,13 +2519,14 @@ ${ko.secure.title}
|
|
|
1614
2519
|
const cwd = process.cwd();
|
|
1615
2520
|
const findings = [];
|
|
1616
2521
|
let scannedFiles = 0;
|
|
1617
|
-
|
|
1618
|
-
const
|
|
2522
|
+
let truncated = false;
|
|
2523
|
+
const gitignorePath = path11.join(cwd, ".gitignore");
|
|
2524
|
+
const hasGitignore = fs10.existsSync(gitignorePath);
|
|
1619
2525
|
if (!hasGitignore) {
|
|
1620
2526
|
console.log(chalk8.yellow(` ${ko.secure.noGitignore}`));
|
|
1621
2527
|
console.log(chalk8.dim(" .env \uD30C\uC77C\uC774 \uCEE4\uBC0B\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n"));
|
|
1622
2528
|
} else {
|
|
1623
|
-
const gitignoreContent =
|
|
2529
|
+
const gitignoreContent = fs10.readFileSync(gitignorePath, "utf-8");
|
|
1624
2530
|
if (!gitignoreContent.includes(".env")) {
|
|
1625
2531
|
console.log(chalk8.yellow(` ${ko.secure.noEnvInGitignore}`));
|
|
1626
2532
|
console.log(chalk8.dim(" \uCD94\uAC00\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4.\n"));
|
|
@@ -1628,13 +2534,15 @@ ${ko.secure.title}
|
|
|
1628
2534
|
}
|
|
1629
2535
|
console.log(chalk8.dim(` ${ko.secure.scanning}
|
|
1630
2536
|
`));
|
|
1631
|
-
|
|
2537
|
+
walkProjectFiles(cwd, (filePath, relPath) => {
|
|
1632
2538
|
scannedFiles++;
|
|
1633
|
-
const content =
|
|
2539
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
1634
2540
|
const lines = content.split("\n");
|
|
1635
|
-
const relPath = path8.relative(cwd, filePath);
|
|
1636
2541
|
for (const pattern of SECRET_PATTERNS) {
|
|
2542
|
+
if (truncated) break;
|
|
1637
2543
|
lines.forEach((line, idx) => {
|
|
2544
|
+
if (truncated) return;
|
|
2545
|
+
if (line.length > MAX_LINE_CHARS) return;
|
|
1638
2546
|
const trimmed = line.trim();
|
|
1639
2547
|
if (trimmed.startsWith("//") && trimmed.includes("example")) return;
|
|
1640
2548
|
if (trimmed.startsWith("#") && trimmed.includes("example")) return;
|
|
@@ -1649,15 +2557,26 @@ ${ko.secure.title}
|
|
|
1649
2557
|
line: idx + 1,
|
|
1650
2558
|
match: maskSecret(match[0])
|
|
1651
2559
|
});
|
|
2560
|
+
if (findings.length >= MAX_FINDINGS) {
|
|
2561
|
+
truncated = true;
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
1652
2564
|
}
|
|
1653
2565
|
});
|
|
1654
2566
|
}
|
|
1655
2567
|
});
|
|
1656
|
-
console.log(chalk8.dim(` \u{1F4C2} ${scannedFiles}\uAC1C \uD30C\uC77C \uC2A4\uCE94 \uC644\uB8CC
|
|
1657
|
-
|
|
2568
|
+
console.log(chalk8.dim(` \u{1F4C2} ${scannedFiles}\uAC1C \uD30C\uC77C \uC2A4\uCE94 \uC644\uB8CC (lock\xB7node_modules\xB7>${MAX_SCAN_FILE_BYTES / 1024}KB \uC81C\uC678)`));
|
|
2569
|
+
if (truncated) {
|
|
2570
|
+
console.log(chalk8.yellow(` \u26A0\uFE0F \uACB0\uACFC ${MAX_FINDINGS}\uAC74\uC5D0\uC11C \uCD9C\uB825\uC744 \uC81C\uD55C\uD588\uC2B5\uB2C8\uB2E4. lock \uD30C\uC77C \uB4F1\uC740 \uC790\uB3D9 \uC81C\uC678\uB429\uB2C8\uB2E4.`));
|
|
2571
|
+
}
|
|
2572
|
+
console.log("");
|
|
1658
2573
|
if (findings.length === 0) {
|
|
1659
2574
|
console.log(chalk8.green.bold(` ${ko.secure.clean}`));
|
|
1660
|
-
|
|
2575
|
+
printNextStep({
|
|
2576
|
+
message: "\uBCF4\uC548 \uC774\uC0C1 \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
|
|
2577
|
+
command: "vhk \uC815\uB9AC",
|
|
2578
|
+
cursorHint: "\uC624\uB298 \uD55C \uC77C \uC815\uB9AC\uD574\uC918"
|
|
2579
|
+
});
|
|
1661
2580
|
return;
|
|
1662
2581
|
}
|
|
1663
2582
|
const critical = findings.filter((f) => f.severity === "critical");
|
|
@@ -1698,21 +2617,603 @@ ${ko.secure.title}
|
|
|
1698
2617
|
process.exitCode = 1;
|
|
1699
2618
|
}
|
|
1700
2619
|
}
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
2620
|
+
|
|
2621
|
+
// src/commands/doctor.ts
|
|
2622
|
+
import chalk9 from "chalk";
|
|
2623
|
+
import { execSync } from "child_process";
|
|
2624
|
+
import fs11 from "fs";
|
|
2625
|
+
import path12 from "path";
|
|
2626
|
+
import { fileURLToPath } from "url";
|
|
2627
|
+
function checkCommand(name, command, hint) {
|
|
2628
|
+
try {
|
|
2629
|
+
const version = execSync(`${command} --version`, { encoding: "utf-8" }).trim().split("\n")[0];
|
|
2630
|
+
return { name, command, version, ok: true, hint };
|
|
2631
|
+
} catch {
|
|
2632
|
+
return { name, command, ok: false, hint };
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
function getVhkVersion() {
|
|
2636
|
+
const dir = path12.dirname(fileURLToPath(import.meta.url));
|
|
2637
|
+
const candidates = [
|
|
2638
|
+
path12.join(dir, "../package.json"),
|
|
2639
|
+
path12.join(dir, "../../package.json")
|
|
2640
|
+
];
|
|
2641
|
+
for (const pkgPath of candidates) {
|
|
2642
|
+
try {
|
|
2643
|
+
if (fs11.existsSync(pkgPath)) {
|
|
2644
|
+
const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
|
|
2645
|
+
return pkg.version;
|
|
1708
2646
|
}
|
|
2647
|
+
} catch {
|
|
2648
|
+
continue;
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
return void 0;
|
|
2652
|
+
}
|
|
2653
|
+
async function doctor() {
|
|
2654
|
+
console.log(chalk9.bold(`
|
|
2655
|
+
${ko.doctor.title}
|
|
2656
|
+
`));
|
|
2657
|
+
const checks = [
|
|
2658
|
+
checkCommand("Node.js", "node", "\uC124\uCE58: https://nodejs.org (LTS \uAD8C\uC7A5)"),
|
|
2659
|
+
checkCommand("npm", "npm", "Node.js \uC124\uCE58 \uC2DC \uD568\uAED8 \uC124\uCE58\uB429\uB2C8\uB2E4"),
|
|
2660
|
+
checkCommand("pnpm", "pnpm", "\uC124\uCE58: npm i -g pnpm"),
|
|
2661
|
+
checkCommand("Git", "git", "\uC124\uCE58: https://git-scm.com")
|
|
2662
|
+
];
|
|
2663
|
+
let allOk = true;
|
|
2664
|
+
for (const check2 of checks) {
|
|
2665
|
+
if (check2.ok) {
|
|
2666
|
+
console.log(chalk9.green(` \u2705 ${check2.name}`) + chalk9.dim(` \u2014 ${check2.version}`));
|
|
1709
2667
|
} else {
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
2668
|
+
console.log(chalk9.red(` \u274C ${check2.name} \uC5C6\uC74C`));
|
|
2669
|
+
console.log(chalk9.dim(` \u2192 ${check2.hint}`));
|
|
2670
|
+
allOk = false;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
console.log("");
|
|
2674
|
+
const vhkVersion = getVhkVersion();
|
|
2675
|
+
if (vhkVersion) {
|
|
2676
|
+
console.log(chalk9.green(" \u2705 VHK") + chalk9.dim(` \u2014 v${vhkVersion}`));
|
|
2677
|
+
} else {
|
|
2678
|
+
console.log(chalk9.green(" \u2705 VHK") + chalk9.dim(" \u2014 \uC124\uCE58\uB428"));
|
|
2679
|
+
}
|
|
2680
|
+
console.log("");
|
|
2681
|
+
console.log(chalk9.bold(` ${ko.doctor.projectFiles}`));
|
|
2682
|
+
const cwd = process.cwd();
|
|
2683
|
+
const projectFiles = [
|
|
2684
|
+
{ name: "RULES.md", hint: "vhk init\uC73C\uB85C \uC0DD\uC131 \uAC00\uB2A5" },
|
|
2685
|
+
{ name: "COMMANDS.md", hint: "vhk init\uC73C\uB85C \uC0DD\uC131 \uAC00\uB2A5" },
|
|
2686
|
+
{ name: "package.json", hint: "\uD504\uB85C\uC81D\uD2B8 \uD3F4\uB354\uC5D0\uC11C \uC2E4\uD589\uD558\uC138\uC694" },
|
|
2687
|
+
{ name: ".gitignore", hint: "\uBCF4\uC548\uC744 \uC704\uD574 \uCD94\uAC00 \uAD8C\uC7A5" },
|
|
2688
|
+
{ name: ".env", hint: ".gitignore\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778" }
|
|
2689
|
+
];
|
|
2690
|
+
for (const file of projectFiles) {
|
|
2691
|
+
const exists = fs11.existsSync(path12.join(cwd, file.name));
|
|
2692
|
+
if (exists) {
|
|
2693
|
+
console.log(chalk9.green(` \u2705 ${file.name}`));
|
|
2694
|
+
if (file.name === ".env") {
|
|
2695
|
+
const gitignorePath = path12.join(cwd, ".gitignore");
|
|
2696
|
+
if (fs11.existsSync(gitignorePath)) {
|
|
2697
|
+
const gitignore = fs11.readFileSync(gitignorePath, "utf-8");
|
|
2698
|
+
if (!gitignore.includes(".env")) {
|
|
2699
|
+
console.log(chalk9.yellow(` ${ko.doctor.envNotIgnored}`));
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
1713
2702
|
}
|
|
2703
|
+
} else {
|
|
2704
|
+
console.log(chalk9.dim(` \u2B1A ${file.name}`) + chalk9.dim(` \u2014 ${file.hint}`));
|
|
1714
2705
|
}
|
|
1715
2706
|
}
|
|
2707
|
+
console.log("");
|
|
2708
|
+
if (allOk) {
|
|
2709
|
+
console.log(chalk9.green.bold(` ${ko.doctor.allOk}`));
|
|
2710
|
+
printNextStep({
|
|
2711
|
+
message: ko.doctor.nextOkMessage,
|
|
2712
|
+
command: "vhk \uC2DC\uC791",
|
|
2713
|
+
cursorHint: "\uD504\uB85C\uC81D\uD2B8 \uB9CC\uB4E4\uC5B4\uC918"
|
|
2714
|
+
});
|
|
2715
|
+
} else {
|
|
2716
|
+
console.log(chalk9.yellow.bold(` ${ko.doctor.missing} ${ko.doctor.missingHint}`));
|
|
2717
|
+
printNextStep({
|
|
2718
|
+
message: ko.doctor.nextRetryMessage,
|
|
2719
|
+
command: "vhk doctor",
|
|
2720
|
+
cursorHint: "\uD658\uACBD \uB2E4\uC2DC \uC810\uAC80\uD574\uC918"
|
|
2721
|
+
});
|
|
2722
|
+
process.exitCode = 1;
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
// src/commands/ship.ts
|
|
2727
|
+
import chalk10 from "chalk";
|
|
2728
|
+
import inquirer4 from "inquirer";
|
|
2729
|
+
import fs12 from "fs";
|
|
2730
|
+
import path13 from "path";
|
|
2731
|
+
var CHECKLIST = [
|
|
2732
|
+
{ id: "build", questionKey: "checkBuild", hintKey: "hintBuild" },
|
|
2733
|
+
{ id: "test", questionKey: "checkTest", hintKey: "hintTest" },
|
|
2734
|
+
{ id: "version", questionKey: "checkVersion", hintKey: "hintVersion" },
|
|
2735
|
+
{ id: "changelog", questionKey: "checkChangelog", hintKey: "hintChangelog" },
|
|
2736
|
+
{ id: "security", questionKey: "checkSecurity", hintKey: "hintSecurity" },
|
|
2737
|
+
{ id: "commit", questionKey: "checkCommit", hintKey: "hintCommit" }
|
|
2738
|
+
];
|
|
2739
|
+
function sanitizeVersion(version) {
|
|
2740
|
+
return version.trim().replace(/^v/i, "").replace(/[^a-zA-Z0-9._-]/g, "-") || "0.0.0";
|
|
2741
|
+
}
|
|
2742
|
+
async function ship() {
|
|
2743
|
+
console.log(chalk10.bold(`
|
|
2744
|
+
${ko.ship.title}
|
|
2745
|
+
`));
|
|
2746
|
+
const cwd = process.cwd();
|
|
2747
|
+
console.log(chalk10.cyan.bold(` ${ko.ship.checklist}
|
|
2748
|
+
`));
|
|
2749
|
+
const { passed } = await inquirer4.prompt([{
|
|
2750
|
+
type: "checkbox",
|
|
2751
|
+
name: "passed",
|
|
2752
|
+
message: ko.ship.checkboxPrompt,
|
|
2753
|
+
choices: CHECKLIST.map((c) => ({
|
|
2754
|
+
name: `${ko.ship[c.questionKey]} ${chalk10.dim(`(${ko.ship[c.hintKey]})`)}`,
|
|
2755
|
+
value: c.id
|
|
2756
|
+
}))
|
|
2757
|
+
}]);
|
|
2758
|
+
const allPassed = passed.length === CHECKLIST.length;
|
|
2759
|
+
const skipped = CHECKLIST.filter((c) => !passed.includes(c.id));
|
|
2760
|
+
if (!allPassed) {
|
|
2761
|
+
console.log(chalk10.yellow(`
|
|
2762
|
+
${ko.ship.incompleteHeader}`));
|
|
2763
|
+
skipped.forEach((s) => {
|
|
2764
|
+
console.log(chalk10.yellow(` \u2022 ${ko.ship[s.questionKey]}`));
|
|
2765
|
+
console.log(chalk10.dim(` \u2192 ${ko.ship[s.hintKey]}`));
|
|
2766
|
+
});
|
|
2767
|
+
const { proceed } = await inquirer4.prompt([{
|
|
2768
|
+
type: "confirm",
|
|
2769
|
+
name: "proceed",
|
|
2770
|
+
message: ko.ship.proceedConfirm,
|
|
2771
|
+
default: false
|
|
2772
|
+
}]);
|
|
2773
|
+
if (!proceed) {
|
|
2774
|
+
printNextStep({
|
|
2775
|
+
message: ko.ship.retryMessage,
|
|
2776
|
+
command: "vhk \uBC30\uD3EC",
|
|
2777
|
+
cursorHint: ko.ship.retryCursorHint
|
|
2778
|
+
});
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
} else {
|
|
2782
|
+
console.log(chalk10.green(`
|
|
2783
|
+
${ko.ship.allPassed}
|
|
2784
|
+
`));
|
|
2785
|
+
}
|
|
2786
|
+
console.log(chalk10.cyan.bold(` ${ko.ship.retro}
|
|
2787
|
+
`));
|
|
2788
|
+
console.log(chalk10.dim(` ${ko.ship.versionHint}`));
|
|
2789
|
+
const retro = await inquirer4.prompt([
|
|
2790
|
+
{ type: "input", name: "version", message: ko.ship.versionPrompt },
|
|
2791
|
+
{ type: "input", name: "whatWentWell", message: ko.ship.questionWell },
|
|
2792
|
+
{ type: "input", name: "whatWentWrong", message: ko.ship.questionWrong },
|
|
2793
|
+
{ type: "input", name: "learned", message: ko.ship.questionLearned },
|
|
2794
|
+
{ type: "input", name: "nextVersion", message: ko.ship.questionNext }
|
|
2795
|
+
]);
|
|
2796
|
+
const buildLogDir = path13.join(cwd, "docs", "build-log");
|
|
2797
|
+
if (!fs12.existsSync(buildLogDir)) fs12.mkdirSync(buildLogDir, { recursive: true });
|
|
2798
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2799
|
+
const versionSlug = sanitizeVersion(retro.version);
|
|
2800
|
+
const fileName = `${today}-v${versionSlug}.md`;
|
|
2801
|
+
const filePath = path13.join(buildLogDir, fileName);
|
|
2802
|
+
const content = [
|
|
2803
|
+
`# \uBE4C\uB4DC \uB85C\uADF8: v${versionSlug}`,
|
|
2804
|
+
"",
|
|
2805
|
+
`**\uBC30\uD3EC\uC77C:** ${today}`,
|
|
2806
|
+
`**\uBC84\uC804:** ${versionSlug}`,
|
|
2807
|
+
"",
|
|
2808
|
+
"## \uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
|
|
2809
|
+
...CHECKLIST.map(
|
|
2810
|
+
(c) => `- [${passed.includes(c.id) ? "x" : " "}] ${ko.ship[c.questionKey]}`
|
|
2811
|
+
),
|
|
2812
|
+
"",
|
|
2813
|
+
"## \uD68C\uACE0",
|
|
2814
|
+
"",
|
|
2815
|
+
"### \u2705 \uC798\uB41C \uC810",
|
|
2816
|
+
retro.whatWentWell || ko.ship.emptySection,
|
|
2817
|
+
"",
|
|
2818
|
+
"### \u274C \uC5B4\uB824\uC6E0\uB358 \uC810",
|
|
2819
|
+
retro.whatWentWrong || ko.ship.emptySection,
|
|
2820
|
+
"",
|
|
2821
|
+
"### \u{1F4A1} \uBC30\uC6B4 \uC810",
|
|
2822
|
+
retro.learned || ko.ship.emptySection,
|
|
2823
|
+
"",
|
|
2824
|
+
"### \u{1F52E} \uB2E4\uC74C \uBC84\uC804",
|
|
2825
|
+
retro.nextVersion || ko.ship.emptyNext,
|
|
2826
|
+
"",
|
|
2827
|
+
"---",
|
|
2828
|
+
`*Generated by \`vhk ship\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
2829
|
+
].join("\n");
|
|
2830
|
+
fs12.writeFileSync(filePath, content, "utf-8");
|
|
2831
|
+
console.log(chalk10.green(`
|
|
2832
|
+
${ko.ship.buildLogDone(path13.relative(cwd, filePath))}`));
|
|
2833
|
+
printNextStep({
|
|
2834
|
+
message: ko.ship.deployMessage,
|
|
2835
|
+
command: "npm publish --access=public",
|
|
2836
|
+
cursorHint: ko.ship.deployCursorHint,
|
|
2837
|
+
alternative: `GitHub \uD0DC\uADF8\uB3C4 \uB9CC\uB4E4\uBA74 \uC88B\uC544\uC694: git tag v${versionSlug}`
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// src/commands/save.ts
|
|
2842
|
+
import { execFileSync, execSync as execSync2 } from "child_process";
|
|
2843
|
+
import chalk11 from "chalk";
|
|
2844
|
+
import ora from "ora";
|
|
2845
|
+
import inquirer5 from "inquirer";
|
|
2846
|
+
function gitOut(args) {
|
|
2847
|
+
return execFileSync("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
2848
|
+
}
|
|
2849
|
+
function gitRun(args) {
|
|
2850
|
+
execFileSync("git", args, { stdio: "pipe" });
|
|
2851
|
+
}
|
|
2852
|
+
function formatDefaultCommitMessage(date = /* @__PURE__ */ new Date()) {
|
|
2853
|
+
const y = date.getFullYear();
|
|
2854
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
2855
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
2856
|
+
const h = String(date.getHours()).padStart(2, "0");
|
|
2857
|
+
const min = String(date.getMinutes()).padStart(2, "0");
|
|
2858
|
+
return `\u2728 vhk save: ${y}-${m}-${d} ${h}:${min}`;
|
|
2859
|
+
}
|
|
2860
|
+
function statusIcon(code) {
|
|
2861
|
+
if (code.includes("M")) return "\u270F\uFE0F";
|
|
2862
|
+
if (code.includes("A") || code.includes("?")) return "\u2795";
|
|
2863
|
+
if (code.includes("D")) return "\u{1F5D1}\uFE0F";
|
|
2864
|
+
return "\u{1F4C4}";
|
|
2865
|
+
}
|
|
2866
|
+
async function save() {
|
|
2867
|
+
console.log(chalk11.bold(`
|
|
2868
|
+
\u{1F4BE} ${t("save.title")}`));
|
|
2869
|
+
console.log(chalk11.gray("\u2500".repeat(40)));
|
|
2870
|
+
try {
|
|
2871
|
+
execSync2("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
2872
|
+
} catch {
|
|
2873
|
+
console.log(chalk11.red(`\u274C ${t("save.notGitRepo")}`));
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
const status2 = gitOut(["status", "--porcelain"]).trim();
|
|
2877
|
+
if (!status2) {
|
|
2878
|
+
console.log(chalk11.yellow(`\u{1F4ED} ${t("save.noChanges")}`));
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
const files = status2.split("\n").filter(Boolean);
|
|
2882
|
+
console.log(chalk11.cyan(`
|
|
2883
|
+
\u{1F4CB} ${t("save.filesHeader", files.length)}`));
|
|
2884
|
+
files.forEach((line) => {
|
|
2885
|
+
const code = line.substring(0, 2);
|
|
2886
|
+
const name = line.substring(3);
|
|
2887
|
+
console.log(` ${statusIcon(code)} ${name}`);
|
|
2888
|
+
});
|
|
2889
|
+
const { message } = await inquirer5.prompt([{
|
|
2890
|
+
type: "input",
|
|
2891
|
+
name: "message",
|
|
2892
|
+
message: t("save.commitMessage"),
|
|
2893
|
+
default: formatDefaultCommitMessage()
|
|
2894
|
+
}]);
|
|
2895
|
+
const spinner = ora(t("save.saving")).start();
|
|
2896
|
+
try {
|
|
2897
|
+
gitRun(["add", "."]);
|
|
2898
|
+
gitRun(["commit", "-m", message]);
|
|
2899
|
+
spinner.text = t("save.pushing");
|
|
2900
|
+
try {
|
|
2901
|
+
gitRun(["push"]);
|
|
2902
|
+
spinner.succeed(t("save.successWithPush"));
|
|
2903
|
+
} catch {
|
|
2904
|
+
spinner.succeed(t("save.successLocal"));
|
|
2905
|
+
console.log(chalk11.yellow(` \u{1F4A1} ${t("save.noRemote")}`));
|
|
2906
|
+
}
|
|
2907
|
+
console.log(chalk11.green(`
|
|
2908
|
+
\u2705 ${t("save.done", files.length)}`));
|
|
2909
|
+
} catch (err) {
|
|
2910
|
+
spinner.fail(t("save.failed"));
|
|
2911
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2912
|
+
console.log(chalk11.red(msg));
|
|
2913
|
+
process.exitCode = 1;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
// src/commands/undo.ts
|
|
2918
|
+
import { execFileSync as execFileSync2, execSync as execSync3 } from "child_process";
|
|
2919
|
+
import chalk12 from "chalk";
|
|
2920
|
+
import inquirer6 from "inquirer";
|
|
2921
|
+
function gitOut2(args) {
|
|
2922
|
+
return execFileSync2("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
2923
|
+
}
|
|
2924
|
+
function gitRun2(args) {
|
|
2925
|
+
execFileSync2("git", args, { stdio: "pipe" });
|
|
2926
|
+
}
|
|
2927
|
+
function parseRecentCommits(logOutput) {
|
|
2928
|
+
return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2929
|
+
}
|
|
2930
|
+
function countUnpushedCommits() {
|
|
2931
|
+
try {
|
|
2932
|
+
const out = gitOut2(["rev-list", "--count", "@{u}..HEAD"]).trim();
|
|
2933
|
+
return parseInt(out, 10) || 0;
|
|
2934
|
+
} catch {
|
|
2935
|
+
return -1;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
function willUndoPushedCommits(undoCount, unpushedCount) {
|
|
2939
|
+
if (unpushedCount < 0) return false;
|
|
2940
|
+
return undoCount > unpushedCount;
|
|
2941
|
+
}
|
|
2942
|
+
async function undo() {
|
|
2943
|
+
console.log(chalk12.bold(`
|
|
2944
|
+
\u23EA ${t("undo.title")}`));
|
|
2945
|
+
console.log(chalk12.gray("\u2500".repeat(40)));
|
|
2946
|
+
try {
|
|
2947
|
+
execSync3("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
2948
|
+
} catch {
|
|
2949
|
+
console.log(chalk12.red(`\u274C ${t("undo.notGitRepo")}`));
|
|
2950
|
+
return;
|
|
2951
|
+
}
|
|
2952
|
+
let logOutput;
|
|
2953
|
+
try {
|
|
2954
|
+
logOutput = gitOut2(["log", "--oneline", "-5"]).trim();
|
|
2955
|
+
} catch {
|
|
2956
|
+
console.log(chalk12.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
const commits = parseRecentCommits(logOutput);
|
|
2960
|
+
if (commits.length === 0) {
|
|
2961
|
+
console.log(chalk12.yellow(`\u{1F4ED} ${t("undo.noCommits")}`));
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
console.log(chalk12.cyan(`
|
|
2965
|
+
${t("undo.recentHeader")}`));
|
|
2966
|
+
commits.forEach((c, i) => {
|
|
2967
|
+
console.log(` ${i === 0 ? "\u{1F449}" : " "} ${c}`);
|
|
2968
|
+
});
|
|
2969
|
+
const maxUndo = commits.length;
|
|
2970
|
+
const { count } = await inquirer6.prompt([{
|
|
2971
|
+
type: "number",
|
|
2972
|
+
name: "count",
|
|
2973
|
+
message: t("undo.howMany"),
|
|
2974
|
+
default: 1,
|
|
2975
|
+
min: 1,
|
|
2976
|
+
max: maxUndo
|
|
2977
|
+
}]);
|
|
2978
|
+
const undoCount = Math.min(Math.max(1, count || 1), maxUndo);
|
|
2979
|
+
const unpushed = countUnpushedCommits();
|
|
2980
|
+
if (willUndoPushedCommits(undoCount, unpushed)) {
|
|
2981
|
+
console.log(chalk12.red(`
|
|
2982
|
+
\u26A0\uFE0F ${t("undo.alreadyPushed")}`));
|
|
2983
|
+
}
|
|
2984
|
+
const { confirm } = await inquirer6.prompt([{
|
|
2985
|
+
type: "confirm",
|
|
2986
|
+
name: "confirm",
|
|
2987
|
+
message: t("undo.confirmMessage", undoCount),
|
|
2988
|
+
default: false
|
|
2989
|
+
}]);
|
|
2990
|
+
if (!confirm) {
|
|
2991
|
+
console.log(chalk12.gray(t("undo.cancelled")));
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
try {
|
|
2995
|
+
gitRun2(["reset", "--soft", `HEAD~${undoCount}`]);
|
|
2996
|
+
console.log(chalk12.green(`
|
|
2997
|
+
\u2705 ${t("undo.success", undoCount)}`));
|
|
2998
|
+
console.log(chalk12.gray(` \u{1F4A1} ${t("undo.stagedHint")}`));
|
|
2999
|
+
} catch (err) {
|
|
3000
|
+
console.log(chalk12.red(`\u274C ${t("undo.failed")}`));
|
|
3001
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3002
|
+
console.log(chalk12.red(msg));
|
|
3003
|
+
process.exitCode = 1;
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
// src/commands/diff.ts
|
|
3008
|
+
import { execFileSync as execFileSync3, execSync as execSync4 } from "child_process";
|
|
3009
|
+
import chalk13 from "chalk";
|
|
3010
|
+
function gitOut3(args) {
|
|
3011
|
+
try {
|
|
3012
|
+
return execFileSync3("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3013
|
+
} catch {
|
|
3014
|
+
return "";
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
function parseDiffStat(stat) {
|
|
3018
|
+
const files = [];
|
|
3019
|
+
const lines = stat.split("\n");
|
|
3020
|
+
for (const line of lines) {
|
|
3021
|
+
const match = line.match(/^\s*(.+?)\s*\|\s*(\d+)/);
|
|
3022
|
+
if (!match) continue;
|
|
3023
|
+
const name = match[1].trim();
|
|
3024
|
+
if (name.includes("changed") || name.includes("file")) continue;
|
|
3025
|
+
const plusMatch = line.match(/(\++)/);
|
|
3026
|
+
const minusMatch = line.match(/(\-+)/);
|
|
3027
|
+
files.push({
|
|
3028
|
+
name,
|
|
3029
|
+
additions: plusMatch ? plusMatch[1].length : 0,
|
|
3030
|
+
deletions: minusMatch ? minusMatch[1].length : 0
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
return files;
|
|
3034
|
+
}
|
|
3035
|
+
function summarizeNumstat(numstat) {
|
|
3036
|
+
let totalAdd = 0;
|
|
3037
|
+
let totalDel = 0;
|
|
3038
|
+
let fileCount = 0;
|
|
3039
|
+
for (const line of numstat.split("\n").filter(Boolean)) {
|
|
3040
|
+
const [add, del] = line.split(" ");
|
|
3041
|
+
if (add === void 0 || del === void 0) continue;
|
|
3042
|
+
totalAdd += parseInt(add, 10) || 0;
|
|
3043
|
+
totalDel += parseInt(del, 10) || 0;
|
|
3044
|
+
fileCount++;
|
|
3045
|
+
}
|
|
3046
|
+
return { fileCount, totalAdd, totalDel };
|
|
3047
|
+
}
|
|
3048
|
+
function printFile(f) {
|
|
3049
|
+
const adds = f.additions > 0 ? chalk13.green(`+${f.additions}`) : "";
|
|
3050
|
+
const dels = f.deletions > 0 ? chalk13.red(`-${f.deletions}`) : "";
|
|
3051
|
+
const change = [adds, dels].filter(Boolean).join(" ");
|
|
3052
|
+
console.log(` ${f.name} ${change}`);
|
|
3053
|
+
}
|
|
3054
|
+
async function diff() {
|
|
3055
|
+
console.log(chalk13.bold(`
|
|
3056
|
+
\u{1F50D} ${t("diff.title")}`));
|
|
3057
|
+
console.log(chalk13.gray("\u2500".repeat(40)));
|
|
3058
|
+
try {
|
|
3059
|
+
execSync4("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
3060
|
+
} catch {
|
|
3061
|
+
console.log(chalk13.red(`\u274C ${t("diff.notGitRepo")}`));
|
|
3062
|
+
return;
|
|
3063
|
+
}
|
|
3064
|
+
const unstaged = gitOut3(["diff", "--stat"]);
|
|
3065
|
+
const staged = gitOut3(["diff", "--cached", "--stat"]);
|
|
3066
|
+
const untracked = gitOut3(["ls-files", "--others", "--exclude-standard"]);
|
|
3067
|
+
if (!unstaged && !staged && !untracked) {
|
|
3068
|
+
console.log(chalk13.green(`
|
|
3069
|
+
\u2705 ${t("diff.noChanges")}`));
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
if (staged) {
|
|
3073
|
+
console.log(chalk13.cyan(`
|
|
3074
|
+
${t("diff.stagedHeader")}`));
|
|
3075
|
+
parseDiffStat(staged).forEach((f) => printFile(f));
|
|
3076
|
+
}
|
|
3077
|
+
if (unstaged) {
|
|
3078
|
+
console.log(chalk13.cyan(`
|
|
3079
|
+
${t("diff.unstagedHeader")}`));
|
|
3080
|
+
parseDiffStat(unstaged).forEach((f) => printFile(f));
|
|
3081
|
+
}
|
|
3082
|
+
if (untracked) {
|
|
3083
|
+
const files = untracked.split("\n").filter(Boolean);
|
|
3084
|
+
console.log(chalk13.cyan(`
|
|
3085
|
+
${t("diff.untrackedHeader", files.length)}`));
|
|
3086
|
+
files.forEach((f) => console.log(` ${chalk13.green("+")} ${f}`));
|
|
3087
|
+
}
|
|
3088
|
+
const numstat = gitOut3(["diff", "--numstat", "HEAD"]);
|
|
3089
|
+
if (numstat) {
|
|
3090
|
+
const { fileCount, totalAdd, totalDel } = summarizeNumstat(numstat);
|
|
3091
|
+
console.log(chalk13.cyan(`
|
|
3092
|
+
${t("diff.summaryHeader")}`));
|
|
3093
|
+
console.log(` ${t("diff.filesLine", fileCount)}`);
|
|
3094
|
+
console.log(` \uCD94\uAC00: ${chalk13.green(`+${totalAdd}`)}\uC904`);
|
|
3095
|
+
console.log(` \uC0AD\uC81C: ${chalk13.red(`-${totalDel}`)}\uC904`);
|
|
3096
|
+
}
|
|
3097
|
+
console.log("");
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
// src/commands/status.ts
|
|
3101
|
+
import { execFileSync as execFileSync4, execSync as execSync5 } from "child_process";
|
|
3102
|
+
import fs13 from "fs";
|
|
3103
|
+
import path14 from "path";
|
|
3104
|
+
import chalk14 from "chalk";
|
|
3105
|
+
function gitOut4(args) {
|
|
3106
|
+
return execFileSync4("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
3107
|
+
}
|
|
3108
|
+
function countFileChanges(porcelain) {
|
|
3109
|
+
const lines = porcelain.split("\n").filter(Boolean);
|
|
3110
|
+
let staged = 0;
|
|
3111
|
+
let unstaged = 0;
|
|
3112
|
+
let untracked = 0;
|
|
3113
|
+
for (const line of lines) {
|
|
3114
|
+
const x = line[0];
|
|
3115
|
+
const y = line[1];
|
|
3116
|
+
if (x === "?" && y === "?") {
|
|
3117
|
+
untracked++;
|
|
3118
|
+
continue;
|
|
3119
|
+
}
|
|
3120
|
+
if (x !== " ") staged++;
|
|
3121
|
+
if (y !== " ") unstaged++;
|
|
3122
|
+
}
|
|
3123
|
+
return { staged, unstaged, untracked };
|
|
3124
|
+
}
|
|
3125
|
+
function parseSyncCounts(revListOutput) {
|
|
3126
|
+
const parts = revListOutput.trim().split(/\s+/);
|
|
3127
|
+
return {
|
|
3128
|
+
ahead: parseInt(parts[0] ?? "0", 10) || 0,
|
|
3129
|
+
behind: parseInt(parts[1] ?? "0", 10) || 0,
|
|
3130
|
+
hasUpstream: true
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
function formatSyncLabel(sync2) {
|
|
3134
|
+
if (!sync2.hasUpstream) return t("status.noUpstream");
|
|
3135
|
+
if (sync2.ahead === 0 && sync2.behind === 0) return t("status.inSync");
|
|
3136
|
+
const parts = [];
|
|
3137
|
+
if (sync2.ahead > 0) parts.push(t("status.ahead", sync2.ahead));
|
|
3138
|
+
if (sync2.behind > 0) parts.push(t("status.behind", sync2.behind));
|
|
3139
|
+
return parts.join(" \xB7 ");
|
|
3140
|
+
}
|
|
3141
|
+
function parseRecentCommitLines(logOutput) {
|
|
3142
|
+
return logOutput.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3143
|
+
}
|
|
3144
|
+
function readProjectPackage(cwd = process.cwd()) {
|
|
3145
|
+
const pkgPath = path14.join(cwd, "package.json");
|
|
3146
|
+
if (!fs13.existsSync(pkgPath)) return null;
|
|
3147
|
+
try {
|
|
3148
|
+
const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
|
|
3149
|
+
if (!pkg.name && !pkg.version) return null;
|
|
3150
|
+
return {
|
|
3151
|
+
name: pkg.name ?? "(no name)",
|
|
3152
|
+
version: pkg.version ?? "(no version)"
|
|
3153
|
+
};
|
|
3154
|
+
} catch {
|
|
3155
|
+
return null;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
function getSyncCounts() {
|
|
3159
|
+
try {
|
|
3160
|
+
const out = gitOut4(["rev-list", "--left-right", "--count", "HEAD...@{u}"]);
|
|
3161
|
+
return parseSyncCounts(out);
|
|
3162
|
+
} catch {
|
|
3163
|
+
return { ahead: 0, behind: 0, hasUpstream: false };
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
async function status() {
|
|
3167
|
+
console.log(chalk14.bold(`
|
|
3168
|
+
\u{1F4CA} ${t("status.title")}`));
|
|
3169
|
+
console.log(chalk14.gray("\u2500".repeat(40)));
|
|
3170
|
+
try {
|
|
3171
|
+
execSync5("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
3172
|
+
} catch {
|
|
3173
|
+
console.log(chalk14.red(`\u274C ${t("status.notGitRepo")}`));
|
|
3174
|
+
return;
|
|
3175
|
+
}
|
|
3176
|
+
let branch;
|
|
3177
|
+
try {
|
|
3178
|
+
branch = gitOut4(["branch", "--show-current"]).trim() || t("status.detached");
|
|
3179
|
+
} catch {
|
|
3180
|
+
branch = t("status.unknownBranch");
|
|
3181
|
+
}
|
|
3182
|
+
const porcelain = gitOut4(["status", "--porcelain"]).trim();
|
|
3183
|
+
const counts = countFileChanges(porcelain);
|
|
3184
|
+
const sync2 = getSyncCounts();
|
|
3185
|
+
let commits = [];
|
|
3186
|
+
try {
|
|
3187
|
+
commits = parseRecentCommitLines(gitOut4(["log", "--oneline", "-3"]).trim());
|
|
3188
|
+
} catch {
|
|
3189
|
+
commits = [];
|
|
3190
|
+
}
|
|
3191
|
+
const pkg = readProjectPackage();
|
|
3192
|
+
console.log(chalk14.cyan(`
|
|
3193
|
+
\u{1F33F} ${t("status.branch")}`) + chalk14.white(` ${branch}`));
|
|
3194
|
+
console.log(
|
|
3195
|
+
chalk14.cyan(`\u{1F4C1} ${t("status.changes")}`) + chalk14.white(
|
|
3196
|
+
` staged ${counts.staged} \xB7 unstaged ${counts.unstaged} \xB7 untracked ${counts.untracked}`
|
|
3197
|
+
)
|
|
3198
|
+
);
|
|
3199
|
+
console.log(chalk14.cyan(`
|
|
3200
|
+
\u{1F4CB} ${t("status.recentCommits")}`));
|
|
3201
|
+
if (commits.length === 0) {
|
|
3202
|
+
console.log(chalk14.dim(` ${t("status.noCommits")}`));
|
|
3203
|
+
} else {
|
|
3204
|
+
commits.forEach((c) => console.log(` ${chalk14.dim("\u2022")} ${c}`));
|
|
3205
|
+
}
|
|
3206
|
+
console.log(
|
|
3207
|
+
chalk14.cyan(`
|
|
3208
|
+
\u{1F504} ${t("status.remote")}`) + chalk14.white(` ${formatSyncLabel(sync2)}`)
|
|
3209
|
+
);
|
|
3210
|
+
console.log(chalk14.gray("\n" + "\u2500".repeat(40)));
|
|
3211
|
+
if (pkg) {
|
|
3212
|
+
console.log(chalk14.cyan(`\u{1F4E6} ${t("status.package")}`) + chalk14.white(` ${pkg.name} v${pkg.version}`));
|
|
3213
|
+
} else {
|
|
3214
|
+
console.log(chalk14.dim(`\u{1F4E6} ${t("status.noPackage")}`));
|
|
3215
|
+
}
|
|
3216
|
+
console.log("");
|
|
1716
3217
|
}
|
|
1717
3218
|
|
|
1718
3219
|
// src/index.ts
|
|
@@ -1724,9 +3225,15 @@ var KO_ALIASES = {
|
|
|
1724
3225
|
recap: "\uC815\uB9AC",
|
|
1725
3226
|
sync: "\uADDC\uCE59",
|
|
1726
3227
|
check: "\uC810\uAC80",
|
|
1727
|
-
secure: "\uBCF4\uC548"
|
|
3228
|
+
secure: "\uBCF4\uC548",
|
|
3229
|
+
ship: "\uBC30\uD3EC",
|
|
3230
|
+
doctor: "\uD658\uACBD",
|
|
3231
|
+
save: "\uC800\uC7A5",
|
|
3232
|
+
undo: "\uB418\uB3CC\uB9AC\uAE30",
|
|
3233
|
+
status: "\uC0C1\uD0DC",
|
|
3234
|
+
diff: "\uBCC0\uACBD"
|
|
1728
3235
|
};
|
|
1729
|
-
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.
|
|
3236
|
+
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.5.0");
|
|
1730
3237
|
program.configureHelp({
|
|
1731
3238
|
formatHelp(cmd, helper) {
|
|
1732
3239
|
if (cmd.parent) {
|
|
@@ -1734,7 +3241,7 @@ program.configureHelp({
|
|
|
1734
3241
|
}
|
|
1735
3242
|
const subs = helper.visibleCommands(cmd).filter((c) => c.name() !== "help");
|
|
1736
3243
|
const terms = subs.map((c) => `${c.name()} (${KO_ALIASES[c.name()]})`);
|
|
1737
|
-
const termWidth = Math.max(...terms.map((
|
|
3244
|
+
const termWidth = Math.max(...terms.map((t2) => t2.length), 0);
|
|
1738
3245
|
const lines = [
|
|
1739
3246
|
helper.commandDescription(cmd),
|
|
1740
3247
|
"",
|
|
@@ -1754,19 +3261,91 @@ program.command("sync").alias("\uB9DE\uCD94\uAE30").alias("\uADDC\uCE59").descri
|
|
|
1754
3261
|
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);
|
|
1755
3262
|
var secureCmd = program.command("secure").alias("\uBCF4\uC548").description("\uBCF4\uC548 \uB3C4\uAD6C \uBAA8\uC74C");
|
|
1756
3263
|
secureCmd.command("scan").alias("\uC2A4\uCE94").description("\uC2DC\uD06C\uB9BF/\uD0A4 \uC720\uCD9C \uC2A4\uCE94").action(secure);
|
|
3264
|
+
program.command("ship").alias("\uBC30\uD3EC").alias("\uB9B4\uB9AC\uC988").description("\uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 + \uD68C\uACE0 + \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131").action(ship);
|
|
3265
|
+
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);
|
|
3266
|
+
program.command("save").alias("\uC800\uC7A5").description("\uBCC0\uACBD\uC0AC\uD56D \uC800\uC7A5 (git add \u2192 commit \u2192 push)").action(async () => {
|
|
3267
|
+
await save();
|
|
3268
|
+
});
|
|
3269
|
+
program.command("undo").alias("\uB418\uB3CC\uB9AC\uAE30").description("\uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30").action(async () => {
|
|
3270
|
+
await undo();
|
|
3271
|
+
});
|
|
3272
|
+
program.command("status").alias("\uC0C1\uD0DC").description("\uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uB300\uC2DC\uBCF4\uB4DC").action(async () => {
|
|
3273
|
+
await status();
|
|
3274
|
+
});
|
|
3275
|
+
program.command("diff").alias("\uBCC0\uACBD").alias("\uCC28\uC774").description("Git \uBCC0\uACBD\uC0AC\uD56D \uD55C\uAD6D\uC5B4 \uC694\uC57D (staged / unstaged / \uC0C8 \uD30C\uC77C)").action(diff);
|
|
3276
|
+
program.on("command:*", async (operands) => {
|
|
3277
|
+
const input = operands.join(" ");
|
|
3278
|
+
const route = routeNaturalLanguage(input);
|
|
3279
|
+
if (route) {
|
|
3280
|
+
console.log("");
|
|
3281
|
+
console.log(chalk15.cyan(` \u{1F4AC} "${input}"`));
|
|
3282
|
+
console.log(chalk15.cyan(` \u2192 ${route.explanation}`));
|
|
3283
|
+
if (route.confidence === "low") {
|
|
3284
|
+
const { confirm } = await inquirer7.prompt([{
|
|
3285
|
+
type: "confirm",
|
|
3286
|
+
name: "confirm",
|
|
3287
|
+
message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
|
|
3288
|
+
default: true
|
|
3289
|
+
}]);
|
|
3290
|
+
if (!confirm) {
|
|
3291
|
+
console.log(chalk15.dim(` ${ko.nlp.menuHint}`));
|
|
3292
|
+
return;
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
console.log("");
|
|
3296
|
+
switch (route.command) {
|
|
3297
|
+
case "gate":
|
|
3298
|
+
return gate();
|
|
3299
|
+
case "init":
|
|
3300
|
+
return init({
|
|
3301
|
+
skipGate: route.args?.includes("--skip-gate"),
|
|
3302
|
+
fromNotion: route.args?.includes("--from-notion") ? extractNotionUrl(input) : void 0
|
|
3303
|
+
});
|
|
3304
|
+
case "recap":
|
|
3305
|
+
return recap({});
|
|
3306
|
+
case "sync":
|
|
3307
|
+
return sync();
|
|
3308
|
+
case "check":
|
|
3309
|
+
return check();
|
|
3310
|
+
case "secure":
|
|
3311
|
+
return secure();
|
|
3312
|
+
case "ship":
|
|
3313
|
+
return ship();
|
|
3314
|
+
case "doctor":
|
|
3315
|
+
return doctor();
|
|
3316
|
+
case "save":
|
|
3317
|
+
return save();
|
|
3318
|
+
case "undo":
|
|
3319
|
+
return undo();
|
|
3320
|
+
case "status":
|
|
3321
|
+
return status();
|
|
3322
|
+
case "diff":
|
|
3323
|
+
return diff();
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
console.log(chalk15.yellow(`
|
|
3327
|
+
\u2753 "${input}" \u2014 ${ko.nlp.notMatched}
|
|
3328
|
+
`));
|
|
3329
|
+
});
|
|
1757
3330
|
program.action(async () => {
|
|
1758
3331
|
console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
|
|
1759
|
-
const { choice } = await
|
|
3332
|
+
const { choice } = await inquirer7.prompt([{
|
|
1760
3333
|
type: "list",
|
|
1761
3334
|
name: "choice",
|
|
1762
3335
|
message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
|
|
1763
3336
|
choices: [
|
|
1764
3337
|
{ name: "\u{1F4A1} \uC0C8 \uC544\uC774\uB514\uC5B4 \uAC80\uC99D\uD558\uAE30", value: "gate" },
|
|
1765
|
-
{ name: "\u{1F4E6} \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791\uD558\uAE30
|
|
3338
|
+
{ name: "\u{1F4E6} \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791\uD558\uAE30", value: "init" },
|
|
1766
3339
|
{ name: "\u{1F4DD} \uC624\uB298 \uD55C \uC77C \uC815\uB9AC\uD558\uAE30", value: "recap" },
|
|
1767
3340
|
{ name: "\u{1F50D} \uADDC\uCE59 \uD30C\uC77C \uC810\uAC80\uD558\uAE30", value: "check" },
|
|
1768
3341
|
{ name: "\u{1F512} \uBCF4\uC548 \uC2A4\uCE94 \uB3CC\uB9AC\uAE30", value: "secure" },
|
|
1769
|
-
{ name: "\u{1F504} \uADDC\uCE59 \uD30C\uC77C \uB3D9\uAE30\uD654", value: "sync" }
|
|
3342
|
+
{ name: "\u{1F504} \uADDC\uCE59 \uD30C\uC77C \uB3D9\uAE30\uD654", value: "sync" },
|
|
3343
|
+
{ name: "\u{1F680} \uBC30\uD3EC\uD558\uAE30", value: "ship" },
|
|
3344
|
+
{ name: "\u{1FA7A} \uD658\uACBD \uC810\uAC80\uD558\uAE30", value: "doctor" },
|
|
3345
|
+
{ name: "\u{1F4BE} Git\uC5D0 \uC800\uC7A5\uD558\uAE30", value: "save" },
|
|
3346
|
+
{ name: "\u23EA \uCD5C\uADFC \uCEE4\uBC0B \uB418\uB3CC\uB9AC\uAE30", value: "undo" },
|
|
3347
|
+
{ name: "\u{1F50D} \uBCC0\uACBD\uC0AC\uD56D \uBCF4\uAE30", value: "diff" },
|
|
3348
|
+
{ name: "\u{1F4CA} \uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uBCF4\uAE30", value: "status" }
|
|
1770
3349
|
]
|
|
1771
3350
|
}]);
|
|
1772
3351
|
switch (choice) {
|
|
@@ -1782,6 +3361,18 @@ program.action(async () => {
|
|
|
1782
3361
|
return secure();
|
|
1783
3362
|
case "sync":
|
|
1784
3363
|
return sync();
|
|
3364
|
+
case "doctor":
|
|
3365
|
+
return doctor();
|
|
3366
|
+
case "ship":
|
|
3367
|
+
return ship();
|
|
3368
|
+
case "save":
|
|
3369
|
+
return save();
|
|
3370
|
+
case "undo":
|
|
3371
|
+
return undo();
|
|
3372
|
+
case "status":
|
|
3373
|
+
return status();
|
|
3374
|
+
case "diff":
|
|
3375
|
+
return diff();
|
|
1785
3376
|
}
|
|
1786
3377
|
});
|
|
1787
3378
|
program.parse();
|