@byh3071/vhk 0.3.1 → 0.4.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/dist/index.js +1182 -109
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,580 @@
|
|
|
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(path14, 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(path14);
|
|
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 = (path14, originalPath, doThrow) => {
|
|
357
|
+
if (!isString(path14)) {
|
|
358
|
+
return doThrow(
|
|
359
|
+
`path must be a string, but got \`${originalPath}\``,
|
|
360
|
+
TypeError
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
if (!path14) {
|
|
364
|
+
return doThrow(`path must not be empty`, TypeError);
|
|
365
|
+
}
|
|
366
|
+
if (checkPath.isNotRelative(path14)) {
|
|
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 = (path14) => REGEX_TEST_INVALID_PATH.test(path14);
|
|
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 path14 = originalPath && checkPath.convert(originalPath);
|
|
406
|
+
checkPath(
|
|
407
|
+
path14,
|
|
408
|
+
originalPath,
|
|
409
|
+
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
410
|
+
);
|
|
411
|
+
return this._t(path14, cache, checkUnignored, slices);
|
|
412
|
+
}
|
|
413
|
+
checkIgnore(path14) {
|
|
414
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path14)) {
|
|
415
|
+
return this.test(path14);
|
|
416
|
+
}
|
|
417
|
+
const slices = path14.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(path14, false, MODE_CHECK_IGNORE);
|
|
431
|
+
}
|
|
432
|
+
_t(path14, cache, checkUnignored, slices) {
|
|
433
|
+
if (path14 in cache) {
|
|
434
|
+
return cache[path14];
|
|
435
|
+
}
|
|
436
|
+
if (!slices) {
|
|
437
|
+
slices = path14.split(SLASH).filter(Boolean);
|
|
438
|
+
}
|
|
439
|
+
slices.pop();
|
|
440
|
+
if (!slices.length) {
|
|
441
|
+
return cache[path14] = this._rules.test(path14, checkUnignored, MODE_IGNORE);
|
|
442
|
+
}
|
|
443
|
+
const parent = this._t(
|
|
444
|
+
slices.join(SLASH) + SLASH,
|
|
445
|
+
cache,
|
|
446
|
+
checkUnignored,
|
|
447
|
+
slices
|
|
448
|
+
);
|
|
449
|
+
return cache[path14] = parent.ignored ? parent : this._rules.test(path14, checkUnignored, MODE_IGNORE);
|
|
450
|
+
}
|
|
451
|
+
ignores(path14) {
|
|
452
|
+
return this._test(path14, this._ignoreCache, false).ignored;
|
|
453
|
+
}
|
|
454
|
+
createFilter() {
|
|
455
|
+
return (path14) => !this.ignores(path14);
|
|
456
|
+
}
|
|
457
|
+
filter(paths) {
|
|
458
|
+
return makeArray(paths).filter(this.createFilter());
|
|
459
|
+
}
|
|
460
|
+
// @returns {TestResult}
|
|
461
|
+
test(path14) {
|
|
462
|
+
return this._test(path14, this._testCache, true);
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
var factory = (options) => new Ignore(options);
|
|
466
|
+
var isPathValid = (path14) => checkPath(path14 && checkPath.convert(path14), path14, 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 = (path14) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path14) || isNotRelative(path14);
|
|
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 chalk11 from "chalk";
|
|
489
|
+
import inquirer5 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 RULES = [
|
|
496
|
+
{
|
|
497
|
+
command: "init",
|
|
498
|
+
explanation: "\uAC80\uC99D \uC2A4\uD0B5\uD558\uACE0 \uBC14\uB85C \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791 --skip-gate)",
|
|
499
|
+
confidence: "high",
|
|
500
|
+
args: ["--skip-gate"],
|
|
501
|
+
test: (t) => /기획.*(끝|완료)|노션.*(기획|완료)|검증.*(스킵|건너)|gate.*(스킵|건너)|바로.*시작/.test(t)
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
command: "init",
|
|
505
|
+
explanation: "Notion\uC5D0\uC11C \uAC00\uC838\uC640 \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791 --from-notion)",
|
|
506
|
+
confidence: "low",
|
|
507
|
+
args: ["--from-notion"],
|
|
508
|
+
test: (t) => /노션|notion/.test(t) && /(시작|만들|import|가져)/.test(t)
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
command: "init",
|
|
512
|
+
explanation: "\uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791 (vhk \uC2DC\uC791)",
|
|
513
|
+
confidence: "high",
|
|
514
|
+
test: (t) => /프로젝트.*(만들|시작)|폴더.*만들|만들고\s*싶|하네스|초기화/.test(t) || /^시작$/.test(t)
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
command: "recap",
|
|
518
|
+
explanation: "\uC624\uB298 \uD55C \uC77C \uC815\uB9AC (vhk \uC815\uB9AC)",
|
|
519
|
+
confidence: "high",
|
|
520
|
+
test: (t) => /오늘.*(정리|기록)|한\s*일|세션|회고|recap|정리해/.test(t)
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
command: "doctor",
|
|
524
|
+
explanation: "\uD658\uACBD \uC810\uAC80 (vhk doctor)",
|
|
525
|
+
confidence: "high",
|
|
526
|
+
test: (t) => /뭔가\s*안|안\s*돼|안돼|환경\s*(점검|진단)|진단|doctor|설치.*확인|왜\s*안/.test(t)
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
command: "gate",
|
|
530
|
+
explanation: "\uC544\uC774\uB514\uC5B4 \uAC80\uC99D (vhk \uAC80\uC99D)",
|
|
531
|
+
confidence: "high",
|
|
532
|
+
test: (t) => /아이디어|검증|gate|go\/refine|pain\s*point/.test(t)
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
command: "secure",
|
|
536
|
+
explanation: "\uBCF4\uC548 \uC2A4\uCE94 (vhk \uBCF4\uC548 scan)",
|
|
537
|
+
confidence: "high",
|
|
538
|
+
test: (t) => /보안|시크릿|비밀|키\s*유출|secure|scan/.test(t)
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
command: "check",
|
|
542
|
+
explanation: "\uADDC\uCE59 \uC810\uAC80 (vhk \uC810\uAC80)",
|
|
543
|
+
confidence: "high",
|
|
544
|
+
test: (t) => /규칙.*(점검|위반)|린트|check|위반/.test(t)
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
command: "sync",
|
|
548
|
+
explanation: "\uADDC\uCE59 \uD30C\uC77C \uB3D9\uAE30\uD654 (vhk \uADDC\uCE59)",
|
|
549
|
+
confidence: "high",
|
|
550
|
+
test: (t) => /규칙.*(맞|동기)|sync|cursorrules|claude\.md.*맞/.test(t)
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
command: "ship",
|
|
554
|
+
explanation: "\uBC30\uD3EC \uCCB4\uD06C + \uD68C\uACE0 (vhk ship)",
|
|
555
|
+
confidence: "high",
|
|
556
|
+
test: (t) => /배포|출시|릴리스|ship|빌드\s*전/.test(t)
|
|
557
|
+
}
|
|
558
|
+
];
|
|
559
|
+
function routeNaturalLanguage(input) {
|
|
560
|
+
const normalized = normalize(input);
|
|
561
|
+
if (!normalized) return null;
|
|
562
|
+
for (const rule of RULES) {
|
|
563
|
+
if (rule.test(normalized)) {
|
|
564
|
+
return {
|
|
565
|
+
command: rule.command,
|
|
566
|
+
explanation: rule.explanation,
|
|
567
|
+
confidence: rule.confidence,
|
|
568
|
+
args: rule.args
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
function extractNotionUrl(input) {
|
|
575
|
+
const m = input.match(/https?:\/\/[^\s]+/i);
|
|
576
|
+
return m?.[0];
|
|
577
|
+
}
|
|
10
578
|
|
|
11
579
|
// src/i18n/ko.ts
|
|
12
580
|
var ko = {
|
|
@@ -87,7 +655,9 @@ var ko = {
|
|
|
87
655
|
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
656
|
gitHintLabel: "\uD130\uBBF8\uB110\uC5D0 \uBCF5\uC0AC\uD560 \uBA85\uB839 (\uC544\uB798 \uBC15\uC2A4 \uBCF5\uBD99):",
|
|
89
657
|
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}"
|
|
658
|
+
startDev: "\uC774\uC81C \uAC1C\uBC1C\uD574 \uBCF4\uC138\uC694! \u{1F680}",
|
|
659
|
+
commandsMdDone: "\u{1F4CB} COMMANDS.md \uC0DD\uC131",
|
|
660
|
+
scriptsDone: "\u{1F4E6} package.json scripts \uCD94\uAC00"
|
|
91
661
|
},
|
|
92
662
|
recap: {
|
|
93
663
|
title: "\u{1F4DD} \uC624\uB298 \uD55C \uC77C \uC815\uB9AC",
|
|
@@ -113,6 +683,21 @@ var ko = {
|
|
|
113
683
|
allPassed: "\u{1F389} \uADDC\uCE59\uC744 \uBAA8\uB450 \uC9C0\uCF30\uC5B4\uC694!",
|
|
114
684
|
summary: "\u{1F4CA} \uC810\uAC80 \uACB0\uACFC:"
|
|
115
685
|
},
|
|
686
|
+
doctor: {
|
|
687
|
+
title: "\u{1FA7A} \uAC1C\uBC1C \uD658\uACBD \uC810\uAC80",
|
|
688
|
+
allOk: "\u{1F389} \uAC1C\uBC1C \uD658\uACBD \uC900\uBE44 \uC644\uB8CC!",
|
|
689
|
+
missing: "\u26A0\uFE0F \uC77C\uBD80 \uB3C4\uAD6C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
690
|
+
missingHint: "\uC704 \uC548\uB0B4\uB97C \uB530\uB77C \uC124\uCE58\uD558\uC138\uC694.",
|
|
691
|
+
projectFiles: "\u{1F4C1} \uD504\uB85C\uC81D\uD2B8 \uD30C\uC77C \uD655\uC778:",
|
|
692
|
+
envNotIgnored: "\u26A0\uFE0F .env\uAC00 .gitignore\uC5D0 \uC5C6\uC74C! \uCD94\uAC00\uD558\uC138\uC694",
|
|
693
|
+
nextOkMessage: "\uD658\uACBD \uC810\uAC80 \uD1B5\uACFC! \uC774\uC81C \uD504\uB85C\uC81D\uD2B8\uB97C \uC2DC\uC791\uD558\uC138\uC694.",
|
|
694
|
+
nextRetryMessage: "\uC704 \uB3C4\uAD6C\uB97C \uC124\uCE58\uD55C \uD6C4 \uB2E4\uC2DC \uC810\uAC80\uD558\uC138\uC694."
|
|
695
|
+
},
|
|
696
|
+
nlp: {
|
|
697
|
+
matched: "\uC774\uAC8C \uB9DE\uB098\uC694?",
|
|
698
|
+
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.",
|
|
699
|
+
menuHint: "vhk\uB97C \uC785\uB825\uD558\uBA74 \uBA54\uB274\uC5D0\uC11C \uC120\uD0DD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
|
|
700
|
+
},
|
|
116
701
|
secure: {
|
|
117
702
|
title: "\u{1F512} \uBE44\uBC00\uBC88\uD638\xB7\uD0A4 \uC720\uCD9C \uAC80\uC0AC",
|
|
118
703
|
noGitignore: "\u26A0\uFE0F .gitignore \uD30C\uC77C\uC774 \uC5C6\uC5B4\uC694!",
|
|
@@ -127,9 +712,48 @@ var ko = {
|
|
|
127
712
|
cursorrulesDone: "\u2705 .cursorrules \uB9DE\uCDA4 \uC644\uB8CC",
|
|
128
713
|
claudeDone: "\u2705 CLAUDE.md \uB9DE\uCDA4 \uC644\uB8CC",
|
|
129
714
|
done: "\u{1F504} \uB9DE\uCD94\uAE30 \uC644\uB8CC!"
|
|
715
|
+
},
|
|
716
|
+
ship: {
|
|
717
|
+
title: "\u{1F680} \uBC30\uD3EC \uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
|
|
718
|
+
checklist: "\u{1F4CB} \uBC30\uD3EC \uC804 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
|
|
719
|
+
retro: "\u{1F50D} \uBC30\uD3EC \uD68C\uACE0",
|
|
720
|
+
buildLogCreated: "\u2705 \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131 \uC644\uB8CC",
|
|
721
|
+
buildLogDone: (rel) => `\u2705 \uBE4C\uB4DC \uB85C\uADF8 \uC0DD\uC131 \uC644\uB8CC: ${rel}`,
|
|
722
|
+
questionWell: "\uC798\uB41C \uC810\uC740?",
|
|
723
|
+
questionWrong: "\uC5B4\uB824\uC6E0\uB358 \uC810\uC740?",
|
|
724
|
+
questionLearned: "\uBC30\uC6B4 \uC810\uC740?",
|
|
725
|
+
questionNext: "\uB2E4\uC74C \uBC84\uC804\uC5D0\uC11C \uD560 \uAC83\uC740?",
|
|
726
|
+
checkboxPrompt: "\uC644\uB8CC\uD55C \uD56D\uBAA9\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
|
|
727
|
+
incompleteHeader: "\u26A0\uFE0F \uC544\uC9C1 \uC644\uB8CC\uD558\uC9C0 \uC54A\uC740 \uD56D\uBAA9:",
|
|
728
|
+
proceedConfirm: "\uADF8\uB798\uB3C4 \uACC4\uC18D \uC9C4\uD589\uD560\uAE4C\uC694?",
|
|
729
|
+
allPassed: "\u2705 \uBAA8\uB4E0 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uD1B5\uACFC!",
|
|
730
|
+
retryMessage: "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uB97C \uB9C8\uCE5C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uBCF4\uC138\uC694.",
|
|
731
|
+
retryCursorHint: "\uBE4C\uB4DC\uD558\uACE0 \uD14C\uC2A4\uD2B8 \uB3CC\uB824\uC918",
|
|
732
|
+
versionPrompt: "\uBC30\uD3EC \uBC84\uC804\uC740?",
|
|
733
|
+
versionHint: "\uC608: 0.4.0",
|
|
734
|
+
emptySection: "(\uBBF8\uC791\uC131)",
|
|
735
|
+
emptyNext: "(\uBBF8\uC815)",
|
|
736
|
+
deployMessage: "\uBE4C\uB4DC \uB85C\uADF8\uB97C \uC800\uC7A5\uD588\uC5B4\uC694! \uC774\uC81C \uC2E4\uC81C \uBC30\uD3EC\uB97C \uC9C4\uD589\uD558\uC138\uC694.",
|
|
737
|
+
deployCursorHint: "\uBC30\uD3EC\uD574\uC918",
|
|
738
|
+
checkBuild: "\uBE4C\uB4DC\uAC00 \uC131\uACF5\uD588\uB098\uC694?",
|
|
739
|
+
hintBuild: "pnpm build",
|
|
740
|
+
checkTest: "\uBAA8\uB4E0 \uD14C\uC2A4\uD2B8\uAC00 \uD1B5\uACFC\uD588\uB098\uC694?",
|
|
741
|
+
hintTest: "pnpm test --run",
|
|
742
|
+
checkVersion: "package.json \uBC84\uC804\uC744 \uC62C\uB838\uB098\uC694?",
|
|
743
|
+
hintVersion: "version \uD544\uB4DC \uD655\uC778",
|
|
744
|
+
checkChangelog: "\uBCC0\uACBD \uB0B4\uC6A9\uC744 \uAE30\uB85D\uD588\uB098\uC694?",
|
|
745
|
+
hintChangelog: "README \uB610\uB294 CHANGELOG",
|
|
746
|
+
checkSecurity: "\uBCF4\uC548 \uC2A4\uCE94\uC744 \uB3CC\uB838\uB098\uC694?",
|
|
747
|
+
hintSecurity: "vhk \uBCF4\uC548 scan",
|
|
748
|
+
checkCommit: "\uBAA8\uB4E0 \uBCC0\uACBD\uC774 \uCEE4\uBC0B\uB418\uC5C8\uB098\uC694?",
|
|
749
|
+
hintCommit: "git status \uD655\uC778"
|
|
130
750
|
}
|
|
131
751
|
};
|
|
132
752
|
|
|
753
|
+
// src/commands/gate.ts
|
|
754
|
+
import inquirer from "inquirer";
|
|
755
|
+
import chalk2 from "chalk";
|
|
756
|
+
|
|
133
757
|
// src/lib/next-step.ts
|
|
134
758
|
import chalk from "chalk";
|
|
135
759
|
function printNextStep(step) {
|
|
@@ -287,6 +911,7 @@ ${ko.gate.verdictTitle}
|
|
|
287
911
|
// src/commands/init.ts
|
|
288
912
|
import inquirer2 from "inquirer";
|
|
289
913
|
import chalk4 from "chalk";
|
|
914
|
+
import fs2 from "fs";
|
|
290
915
|
import path2 from "path";
|
|
291
916
|
|
|
292
917
|
// src/templates/claude-md.ts
|
|
@@ -467,6 +1092,36 @@ function ADR_TEMPLATE() {
|
|
|
467
1092
|
].join("\n");
|
|
468
1093
|
}
|
|
469
1094
|
|
|
1095
|
+
// src/templates/commands-md.ts
|
|
1096
|
+
function COMMANDS_MD_TEMPLATE() {
|
|
1097
|
+
return [
|
|
1098
|
+
"# \u{1F4CB} \uD55C\uAD6D\uC5B4 \uBA85\uB839\uC5B4 \uAC00\uC774\uB4DC",
|
|
1099
|
+
"",
|
|
1100
|
+
"\uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uC790\uC8FC \uC4F0\uB294 \uBA85\uB839\uC5B4\uC785\uB2C8\uB2E4.",
|
|
1101
|
+
"Cursor\uC5D0\uAC8C \uD55C\uAD6D\uC5B4\uB85C \uB9D0\uD574\uB3C4 \uB429\uB2C8\uB2E4.",
|
|
1102
|
+
"",
|
|
1103
|
+
"## \uB9E4\uC77C \uC4F0\uB294 \uBA85\uB839\uC5B4",
|
|
1104
|
+
"",
|
|
1105
|
+
"| \uD558\uACE0 \uC2F6\uC740 \uAC83 | \uD130\uBBF8\uB110 \uBA85\uB839 | Cursor\uC5D0\uAC8C \uB9D0\uD558\uAE30 |",
|
|
1106
|
+
"|-------------|-----------|------------------|",
|
|
1107
|
+
'| \uC800\uC7A5 | `git add . && git commit -m "\uBA54\uC2DC\uC9C0"` | "\uC800\uC7A5\uD574" |',
|
|
1108
|
+
'| \uC624\uB298 \uC815\uB9AC | `vhk \uC815\uB9AC` | "\uC624\uB298 \uD55C \uC77C \uC815\uB9AC\uD574" |',
|
|
1109
|
+
'| \uADDC\uCE59 \uC810\uAC80 | `vhk \uC810\uAC80` | "\uADDC\uCE59 \uC810\uAC80\uD574" |',
|
|
1110
|
+
'| \uBCF4\uC548 \uC2A4\uCE94 | `vhk \uBCF4\uC548 scan` | "\uBCF4\uC548 \uC2A4\uCE94 \uB3CC\uB824" |',
|
|
1111
|
+
'| \uBE4C\uB4DC+\uD14C\uC2A4\uD2B8 | `pnpm build; pnpm test --run` | "\uBE4C\uB4DC\uD558\uACE0 \uD14C\uC2A4\uD2B8 \uB3CC\uB824" |',
|
|
1112
|
+
'| \uBC30\uD3EC | `vhk \uBC30\uD3EC` | "\uBC30\uD3EC\uD574" |',
|
|
1113
|
+
"",
|
|
1114
|
+
"## \uD658\uACBD \uC810\uAC80",
|
|
1115
|
+
"",
|
|
1116
|
+
"```bash",
|
|
1117
|
+
"vhk doctor",
|
|
1118
|
+
"```",
|
|
1119
|
+
"",
|
|
1120
|
+
"---",
|
|
1121
|
+
"*Generated by `vhk init` \u2014 \uC218\uC815\uD574\uB3C4 \uB429\uB2C8\uB2E4*"
|
|
1122
|
+
].join("\n");
|
|
1123
|
+
}
|
|
1124
|
+
|
|
470
1125
|
// src/utils/logger.ts
|
|
471
1126
|
import chalk3 from "chalk";
|
|
472
1127
|
var log = {
|
|
@@ -742,6 +1397,7 @@ ${ko.init.recommendedStack} ${stack.join(" + ")}
|
|
|
742
1397
|
writeFile(fullPath, content);
|
|
743
1398
|
log.success(filePath);
|
|
744
1399
|
}
|
|
1400
|
+
await writeInitExtras(cwd);
|
|
745
1401
|
console.log(chalk4.bold.green(`
|
|
746
1402
|
${ko.init.done}`));
|
|
747
1403
|
console.log(chalk4.dim(`
|
|
@@ -795,16 +1451,95 @@ function generateFiles(name, description, stack, prdContent = {}) {
|
|
|
795
1451
|
`
|
|
796
1452
|
};
|
|
797
1453
|
}
|
|
1454
|
+
var VHK_PACKAGE_SCRIPTS = {
|
|
1455
|
+
save: "git add . && git commit -m",
|
|
1456
|
+
check: "vhk check",
|
|
1457
|
+
scan: "vhk secure scan",
|
|
1458
|
+
recap: "vhk recap",
|
|
1459
|
+
ship: "vhk ship",
|
|
1460
|
+
doctor: "vhk doctor"
|
|
1461
|
+
};
|
|
1462
|
+
function enhancePackageScripts(projectDir) {
|
|
1463
|
+
const pkgPath = path2.join(projectDir, "package.json");
|
|
1464
|
+
if (!fs2.existsSync(pkgPath)) return false;
|
|
1465
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
1466
|
+
pkg.scripts = { ...pkg.scripts, ...VHK_PACKAGE_SCRIPTS };
|
|
1467
|
+
fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
1468
|
+
return true;
|
|
1469
|
+
}
|
|
1470
|
+
async function writeInitExtras(projectDir) {
|
|
1471
|
+
const commandsPath = path2.join(projectDir, "COMMANDS.md");
|
|
1472
|
+
if (fileExists(commandsPath)) {
|
|
1473
|
+
const { overwrite } = await inquirer2.prompt([{
|
|
1474
|
+
type: "confirm",
|
|
1475
|
+
name: "overwrite",
|
|
1476
|
+
message: ko.init.overwrite("COMMANDS.md"),
|
|
1477
|
+
default: false
|
|
1478
|
+
}]);
|
|
1479
|
+
if (!overwrite) {
|
|
1480
|
+
log.warn(ko.init.skipped("COMMANDS.md"));
|
|
1481
|
+
} else {
|
|
1482
|
+
writeFile(commandsPath, COMMANDS_MD_TEMPLATE());
|
|
1483
|
+
log.success(ko.init.commandsMdDone);
|
|
1484
|
+
}
|
|
1485
|
+
} else {
|
|
1486
|
+
writeFile(commandsPath, COMMANDS_MD_TEMPLATE());
|
|
1487
|
+
log.success(ko.init.commandsMdDone);
|
|
1488
|
+
}
|
|
1489
|
+
if (enhancePackageScripts(projectDir)) {
|
|
1490
|
+
log.success(ko.init.scriptsDone);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
798
1493
|
|
|
799
1494
|
// src/commands/recap.ts
|
|
800
1495
|
import inquirer3 from "inquirer";
|
|
801
1496
|
import chalk5 from "chalk";
|
|
802
|
-
import
|
|
803
|
-
import
|
|
1497
|
+
import fs5 from "fs";
|
|
1498
|
+
import path6 from "path";
|
|
804
1499
|
|
|
805
1500
|
// src/lib/git.ts
|
|
1501
|
+
import path4 from "path";
|
|
806
1502
|
import simpleGit from "simple-git";
|
|
1503
|
+
|
|
1504
|
+
// src/lib/check-secure.ts
|
|
1505
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
1506
|
+
import fs3 from "fs";
|
|
1507
|
+
import path3 from "path";
|
|
1508
|
+
function loadGitignore(rootDir) {
|
|
1509
|
+
const ig = (0, import_ignore.default)();
|
|
1510
|
+
const gitignorePath = path3.join(rootDir, ".gitignore");
|
|
1511
|
+
if (fs3.existsSync(gitignorePath)) {
|
|
1512
|
+
const content = fs3.readFileSync(gitignorePath, "utf-8");
|
|
1513
|
+
ig.add(content);
|
|
1514
|
+
}
|
|
1515
|
+
return ig;
|
|
1516
|
+
}
|
|
1517
|
+
function isPathIgnored(ig, relativePath) {
|
|
1518
|
+
const normalized = relativePath.replace(/\\/g, "/");
|
|
1519
|
+
return ig.ignores(normalized);
|
|
1520
|
+
}
|
|
1521
|
+
function filterTrackedPaths(paths, rootDir = process.cwd()) {
|
|
1522
|
+
const ig = loadGitignore(rootDir);
|
|
1523
|
+
return paths.filter((p) => !isPathIgnored(ig, p.replace(/\\/g, "/")));
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// src/lib/git.ts
|
|
807
1527
|
var git = simpleGit();
|
|
1528
|
+
function isNoiseRecapPath(filePath) {
|
|
1529
|
+
const base = path4.basename(filePath);
|
|
1530
|
+
if (base.includes("${") || base.includes("`")) return true;
|
|
1531
|
+
if (/^\d+(\.\d+)?$/.test(base)) return true;
|
|
1532
|
+
if (/^(pnpm|vhk|npm|node|yarn)$/i.test(base)) return true;
|
|
1533
|
+
if (!filePath.includes("/") && !filePath.includes("\\") && !base.includes(".")) {
|
|
1534
|
+
if (!/^(README|LICENSE|Makefile|Dockerfile|CHANGELOG)$/i.test(base)) return true;
|
|
1535
|
+
}
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
function filterRecapFiles(files) {
|
|
1539
|
+
const paths = files.map((f) => f.file);
|
|
1540
|
+
const tracked = new Set(filterTrackedPaths(paths));
|
|
1541
|
+
return files.filter((f) => tracked.has(f.file) && !isNoiseRecapPath(f.file));
|
|
1542
|
+
}
|
|
808
1543
|
function fileStatus(workingDir) {
|
|
809
1544
|
if (workingDir === "?") return "new";
|
|
810
1545
|
if (workingDir === "D") return "deleted";
|
|
@@ -818,17 +1553,19 @@ async function getSessionDiff(since) {
|
|
|
818
1553
|
const statByFile = new Map(
|
|
819
1554
|
diffSummary.files.map((f) => [f.file, f])
|
|
820
1555
|
);
|
|
821
|
-
const files =
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1556
|
+
const files = filterRecapFiles(
|
|
1557
|
+
statusResult.files.map((f) => {
|
|
1558
|
+
const stat = statByFile.get(f.path);
|
|
1559
|
+
return {
|
|
1560
|
+
file: f.path,
|
|
1561
|
+
insertions: stat?.insertions ?? 0,
|
|
1562
|
+
deletions: stat?.deletions ?? 0,
|
|
1563
|
+
status: fileStatus(f.working_dir)
|
|
1564
|
+
};
|
|
1565
|
+
})
|
|
1566
|
+
);
|
|
830
1567
|
return {
|
|
831
|
-
filesChanged:
|
|
1568
|
+
filesChanged: files.length,
|
|
832
1569
|
insertions: diffSummary.insertions,
|
|
833
1570
|
deletions: diffSummary.deletions,
|
|
834
1571
|
files
|
|
@@ -855,8 +1592,8 @@ async function isGitRepo() {
|
|
|
855
1592
|
}
|
|
856
1593
|
|
|
857
1594
|
// src/lib/adr.ts
|
|
858
|
-
import
|
|
859
|
-
import
|
|
1595
|
+
import fs4 from "fs";
|
|
1596
|
+
import path5 from "path";
|
|
860
1597
|
var ADR_RULES = [
|
|
861
1598
|
{
|
|
862
1599
|
title: "\uC758\uC874\uC131 \uBCC0\uACBD",
|
|
@@ -899,20 +1636,20 @@ function detectAdrCandidates(diff) {
|
|
|
899
1636
|
return candidates;
|
|
900
1637
|
}
|
|
901
1638
|
function nextAdrNumber(adrDir) {
|
|
902
|
-
if (!
|
|
903
|
-
const nums =
|
|
1639
|
+
if (!fs4.existsSync(adrDir)) return 1;
|
|
1640
|
+
const nums = fs4.readdirSync(adrDir).map((name) => name.match(/^ADR-(\d+)/i)?.[1]).filter((n) => Boolean(n)).map((n) => parseInt(n, 10));
|
|
904
1641
|
return nums.length ? Math.max(...nums) + 1 : 1;
|
|
905
1642
|
}
|
|
906
1643
|
function slugify(title) {
|
|
907
1644
|
return title.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9가-힣-]/g, "").slice(0, 40) || "decision";
|
|
908
1645
|
}
|
|
909
1646
|
function createAdrFile(cwd, title, context, decision, consequences) {
|
|
910
|
-
const adrDir =
|
|
911
|
-
if (!
|
|
1647
|
+
const adrDir = path5.join(cwd, "docs", "adr");
|
|
1648
|
+
if (!fs4.existsSync(adrDir)) fs4.mkdirSync(adrDir, { recursive: true });
|
|
912
1649
|
const num = nextAdrNumber(adrDir);
|
|
913
1650
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
914
1651
|
const fileName = `ADR-${String(num).padStart(3, "0")}-${slugify(title)}.md`;
|
|
915
|
-
const filePath =
|
|
1652
|
+
const filePath = path5.join(adrDir, fileName);
|
|
916
1653
|
const content = [
|
|
917
1654
|
"---",
|
|
918
1655
|
`id: ADR-${String(num).padStart(3, "0")}`,
|
|
@@ -938,7 +1675,7 @@ function createAdrFile(cwd, title, context, decision, consequences) {
|
|
|
938
1675
|
"---",
|
|
939
1676
|
`*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
940
1677
|
].join("\n");
|
|
941
|
-
|
|
1678
|
+
fs4.writeFileSync(filePath, content, "utf-8");
|
|
942
1679
|
return filePath;
|
|
943
1680
|
}
|
|
944
1681
|
|
|
@@ -953,8 +1690,9 @@ ${ko.recap.title}
|
|
|
953
1690
|
}
|
|
954
1691
|
console.log(chalk5.dim(`${ko.recap.analyzing}
|
|
955
1692
|
`));
|
|
956
|
-
const
|
|
957
|
-
const
|
|
1693
|
+
const since = options.since || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1694
|
+
const diff = await getSessionDiff(since);
|
|
1695
|
+
const commits = await getRecentCommits(10, since);
|
|
958
1696
|
if (diff.filesChanged === 0 && commits.length === 0) {
|
|
959
1697
|
console.log(chalk5.yellow(ko.recap.noChanges));
|
|
960
1698
|
return;
|
|
@@ -1004,12 +1742,12 @@ ${ko.recap.title}
|
|
|
1004
1742
|
}
|
|
1005
1743
|
]);
|
|
1006
1744
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1007
|
-
const logDir =
|
|
1008
|
-
if (!
|
|
1009
|
-
const existing =
|
|
1745
|
+
const logDir = path6.join(process.cwd(), "docs", "log");
|
|
1746
|
+
if (!fs5.existsSync(logDir)) fs5.mkdirSync(logDir, { recursive: true });
|
|
1747
|
+
const existing = fs5.readdirSync(logDir).filter((f) => f.startsWith(today));
|
|
1010
1748
|
const sessionNum = existing.length + 1;
|
|
1011
1749
|
const fileName = `${today}-session-${sessionNum}.md`;
|
|
1012
|
-
const filePath =
|
|
1750
|
+
const filePath = path6.join(logDir, fileName);
|
|
1013
1751
|
const fileList = diff.files.map((f) => `| ${f.file} | ${f.status} |`).join("\n");
|
|
1014
1752
|
const commitList = commits.slice(0, 10).map((c) => `- \`${c.hash.slice(0, 7)}\` ${c.message}`).join("\n");
|
|
1015
1753
|
const content = [
|
|
@@ -1040,7 +1778,7 @@ ${ko.recap.title}
|
|
|
1040
1778
|
"---",
|
|
1041
1779
|
`*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
1042
1780
|
].join("\n");
|
|
1043
|
-
|
|
1781
|
+
fs5.writeFileSync(filePath, content, "utf-8");
|
|
1044
1782
|
const adrCandidates = detectAdrCandidates(diff);
|
|
1045
1783
|
if (adrCandidates.length > 0) {
|
|
1046
1784
|
console.log(chalk5.cyan.bold(`
|
|
@@ -1077,7 +1815,7 @@ ${ko.recap.adrDetected} (${adrCandidates.length}\uAC74)`));
|
|
|
1077
1815
|
adrAnswers.decision,
|
|
1078
1816
|
adrAnswers.consequences
|
|
1079
1817
|
);
|
|
1080
|
-
console.log(chalk5.green(` \u2705 ADR \uC0DD\uC131: ${
|
|
1818
|
+
console.log(chalk5.green(` \u2705 ADR \uC0DD\uC131: ${path6.relative(process.cwd(), adrPath)}`));
|
|
1081
1819
|
}
|
|
1082
1820
|
}
|
|
1083
1821
|
}
|
|
@@ -1096,8 +1834,8 @@ ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
|
1096
1834
|
default: true
|
|
1097
1835
|
}]);
|
|
1098
1836
|
if (createTroubleshoot) {
|
|
1099
|
-
const tsDir =
|
|
1100
|
-
if (!
|
|
1837
|
+
const tsDir = path6.join(process.cwd(), "docs", "troubleshooting");
|
|
1838
|
+
if (!fs5.existsSync(tsDir)) fs5.mkdirSync(tsDir, { recursive: true });
|
|
1101
1839
|
const tsAnswers = await inquirer3.prompt([
|
|
1102
1840
|
{
|
|
1103
1841
|
type: "input",
|
|
@@ -1116,7 +1854,7 @@ ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
|
1116
1854
|
}
|
|
1117
1855
|
]);
|
|
1118
1856
|
const tsFileName = `${today}-${tsAnswers.problem.slice(0, 30).replace(/[^a-zA-Z0-9가-힣]/g, "-")}.md`;
|
|
1119
|
-
const tsFilePath =
|
|
1857
|
+
const tsFilePath = path6.join(tsDir, tsFileName);
|
|
1120
1858
|
const tsContent = [
|
|
1121
1859
|
`# \uD2B8\uB7EC\uBE14\uC288\uD305: ${tsAnswers.problem}`,
|
|
1122
1860
|
"",
|
|
@@ -1137,15 +1875,15 @@ ${ko.recap.troubleDetected} (${troubleCommits.length}\uAC74)`));
|
|
|
1137
1875
|
"---",
|
|
1138
1876
|
`*Generated by \`vhk recap\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
1139
1877
|
].join("\n");
|
|
1140
|
-
|
|
1141
|
-
console.log(chalk5.green(` \u2705 \uD2B8\uB7EC\uBE14\uC288\uD305 \uBB38\uC11C \uC0DD\uC131: ${
|
|
1878
|
+
fs5.writeFileSync(tsFilePath, tsContent, "utf-8");
|
|
1879
|
+
console.log(chalk5.green(` \u2705 \uD2B8\uB7EC\uBE14\uC288\uD305 \uBB38\uC11C \uC0DD\uC131: ${path6.relative(process.cwd(), tsFilePath)}`));
|
|
1142
1880
|
}
|
|
1143
1881
|
}
|
|
1144
1882
|
console.log(chalk5.green.bold(`
|
|
1145
1883
|
${ko.recap.done}`));
|
|
1146
|
-
console.log(chalk5.dim(` \u{1F4C4} ${
|
|
1147
|
-
const claudeMdPath =
|
|
1148
|
-
if (
|
|
1884
|
+
console.log(chalk5.dim(` \u{1F4C4} ${path6.relative(process.cwd(), filePath)}`));
|
|
1885
|
+
const claudeMdPath = path6.join(process.cwd(), "CLAUDE.md");
|
|
1886
|
+
if (fs5.existsSync(claudeMdPath)) {
|
|
1149
1887
|
const { updateClaude } = await inquirer3.prompt([{
|
|
1150
1888
|
type: "confirm",
|
|
1151
1889
|
name: "updateClaude",
|
|
@@ -1153,7 +1891,7 @@ ${ko.recap.done}`));
|
|
|
1153
1891
|
default: true
|
|
1154
1892
|
}]);
|
|
1155
1893
|
if (updateClaude) {
|
|
1156
|
-
let claudeContent =
|
|
1894
|
+
let claudeContent = fs5.readFileSync(claudeMdPath, "utf-8");
|
|
1157
1895
|
claudeContent = claudeContent.replace(
|
|
1158
1896
|
/- \*\*마지막 업데이트:\*\*.*/,
|
|
1159
1897
|
`- **\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:** ${today}`
|
|
@@ -1162,21 +1900,22 @@ ${ko.recap.done}`));
|
|
|
1162
1900
|
/- \*\*다음 액션:\*\*.*/,
|
|
1163
1901
|
`- **\uB2E4\uC74C \uC561\uC158:** ${answers.nextTodo}`
|
|
1164
1902
|
);
|
|
1165
|
-
|
|
1903
|
+
fs5.writeFileSync(claudeMdPath, claudeContent, "utf-8");
|
|
1166
1904
|
console.log(chalk5.green(" \u2705 CLAUDE.md \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC"));
|
|
1167
1905
|
}
|
|
1168
1906
|
}
|
|
1907
|
+
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
1908
|
printNextStep({
|
|
1170
1909
|
message: "\uC624\uB298 \uAE30\uB85D \uC644\uB8CC! \uC800\uC7A5\uD558\uACE0 \uC2F6\uC73C\uBA74:",
|
|
1171
|
-
command:
|
|
1910
|
+
command: gitSaveCmd,
|
|
1172
1911
|
cursorHint: "\uC800\uC7A5\uD574\uC918"
|
|
1173
1912
|
});
|
|
1174
1913
|
}
|
|
1175
1914
|
|
|
1176
1915
|
// src/commands/sync.ts
|
|
1177
1916
|
import chalk6 from "chalk";
|
|
1178
|
-
import
|
|
1179
|
-
import
|
|
1917
|
+
import fs6 from "fs";
|
|
1918
|
+
import path7 from "path";
|
|
1180
1919
|
var CURSORRULES_KEYS = ["\uCF54\uB529 \uADDC\uCE59", "\uAE30\uC220 \uC2A4\uD0DD", "\uC544\uD0A4\uD14D\uCC98", "\uB514\uC790\uC778", "Anti-patterns", "\uCEE4\uBC0B"];
|
|
1181
1920
|
var CLAUDE_MD_KEYS = ["\uAE30\uB85D", "\uB85C\uADF8", "ADR", "\uD2B8\uB7EC\uBE14\uC288\uD305", "TIL", "/done", "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8"];
|
|
1182
1921
|
function parseRulesMd(content) {
|
|
@@ -1248,8 +1987,8 @@ async function sync() {
|
|
|
1248
1987
|
${ko.sync.title}
|
|
1249
1988
|
`));
|
|
1250
1989
|
const cwd = process.cwd();
|
|
1251
|
-
const rulesPath =
|
|
1252
|
-
if (!
|
|
1990
|
+
const rulesPath = path7.join(cwd, "RULES.md");
|
|
1991
|
+
if (!fs6.existsSync(rulesPath)) {
|
|
1253
1992
|
console.log(chalk6.yellow(ko.sync.noRules));
|
|
1254
1993
|
console.log(chalk6.dim(" RULES.md\uB294 \uD504\uB85C\uC81D\uD2B8 \uADDC\uCE59\uC758 Single Source of Truth\uC785\uB2C8\uB2E4."));
|
|
1255
1994
|
console.log(chalk6.dim(" \uC0DD\uC131\uD558\uB824\uBA74: vhk init \uC2E4\uD589 \uD6C4 RULES.md\uB97C \uC791\uC131\uD558\uC138\uC694."));
|
|
@@ -1262,23 +2001,23 @@ ${ko.sync.title}
|
|
|
1262
2001
|
console.log(chalk6.dim(" ## \uCEE4\uBC0B \uCEE8\uBCA4\uC158"));
|
|
1263
2002
|
return;
|
|
1264
2003
|
}
|
|
1265
|
-
const rulesContent =
|
|
2004
|
+
const rulesContent = fs6.readFileSync(rulesPath, "utf-8");
|
|
1266
2005
|
const sections = parseRulesMd(rulesContent);
|
|
1267
2006
|
console.log(chalk6.dim(` \u{1F4C4} RULES.md \uD30C\uC2F1 \uC644\uB8CC \u2014 ${sections.length}\uAC1C \uC139\uC158`));
|
|
1268
2007
|
const firstLine = rulesContent.split("\n")[0];
|
|
1269
2008
|
const projectName = firstLine.replace(/^#\s*/, "").replace(/\s*—.*/, "").trim() || "Project";
|
|
1270
|
-
const cursorrulesPath =
|
|
1271
|
-
|
|
2009
|
+
const cursorrulesPath = path7.join(cwd, ".cursorrules");
|
|
2010
|
+
fs6.writeFileSync(cursorrulesPath, toCursorrules(sections, projectName), "utf-8");
|
|
1272
2011
|
console.log(chalk6.green(` ${ko.sync.cursorrulesDone}`));
|
|
1273
|
-
const claudePath =
|
|
1274
|
-
const existingClaude =
|
|
2012
|
+
const claudePath = path7.join(cwd, "CLAUDE.md");
|
|
2013
|
+
const existingClaude = fs6.existsSync(claudePath) ? fs6.readFileSync(claudePath, "utf-8") : `# \uAE30\uB85D \uADDC\uCE59 (${projectName})
|
|
1275
2014
|
|
|
1276
2015
|
## \uD604\uC7AC \uC0C1\uD0DC
|
|
1277
2016
|
- **Phase:** __FILL__
|
|
1278
2017
|
- **\uBE14\uB85C\uCEE4:** \uC5C6\uC74C
|
|
1279
2018
|
- **\uB2E4\uC74C \uC561\uC158:** __FILL__
|
|
1280
2019
|
- **\uB9C8\uC9C0\uB9C9 \uC5C5\uB370\uC774\uD2B8:** ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`;
|
|
1281
|
-
|
|
2020
|
+
fs6.writeFileSync(claudePath, toClaudeMd(sections, existingClaude), "utf-8");
|
|
1282
2021
|
console.log(chalk6.green(` ${ko.sync.claudeDone}`));
|
|
1283
2022
|
console.log(chalk6.bold.green(`
|
|
1284
2023
|
${ko.sync.done}`));
|
|
@@ -1293,15 +2032,15 @@ ${ko.sync.done}`));
|
|
|
1293
2032
|
|
|
1294
2033
|
// src/commands/check.ts
|
|
1295
2034
|
import chalk7 from "chalk";
|
|
1296
|
-
import
|
|
1297
|
-
import
|
|
2035
|
+
import path9 from "path";
|
|
2036
|
+
import fs8 from "fs";
|
|
1298
2037
|
|
|
1299
2038
|
// src/lib/rules-parser.ts
|
|
1300
|
-
import
|
|
1301
|
-
import
|
|
2039
|
+
import fs7 from "fs";
|
|
2040
|
+
import path8 from "path";
|
|
1302
2041
|
function parseRules(rulesPath) {
|
|
1303
|
-
if (!
|
|
1304
|
-
const content =
|
|
2042
|
+
if (!fs7.existsSync(rulesPath)) return [];
|
|
2043
|
+
const content = fs7.readFileSync(rulesPath, "utf-8");
|
|
1305
2044
|
const lines = content.split("\n");
|
|
1306
2045
|
const rules = [];
|
|
1307
2046
|
let currentSection = "";
|
|
@@ -1371,17 +2110,17 @@ function createNamingRule(id, section, desc, convention) {
|
|
|
1371
2110
|
description: desc,
|
|
1372
2111
|
check: (cwd) => {
|
|
1373
2112
|
const violations = [];
|
|
1374
|
-
const srcDir =
|
|
1375
|
-
if (!
|
|
2113
|
+
const srcDir = path8.join(cwd, "src");
|
|
2114
|
+
if (!fs7.existsSync(srcDir)) return violations;
|
|
1376
2115
|
walkFiles(srcDir, (filePath) => {
|
|
1377
|
-
const name =
|
|
2116
|
+
const name = path8.basename(filePath, path8.extname(filePath));
|
|
1378
2117
|
if (convention === "kebab-case" && !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
|
|
1379
2118
|
if (!["index", "vite.config", "tsconfig"].includes(name)) {
|
|
1380
2119
|
violations.push({
|
|
1381
2120
|
ruleId: id,
|
|
1382
2121
|
severity: "warning",
|
|
1383
2122
|
message: `\uD30C\uC77C\uBA85\uC774 kebab-case\uAC00 \uC544\uB2D8: ${name}`,
|
|
1384
|
-
file:
|
|
2123
|
+
file: path8.relative(cwd, filePath)
|
|
1385
2124
|
});
|
|
1386
2125
|
}
|
|
1387
2126
|
}
|
|
@@ -1397,8 +2136,8 @@ function createStructureRule(id, section, desc, expectedPath) {
|
|
|
1397
2136
|
type: "structure",
|
|
1398
2137
|
description: desc,
|
|
1399
2138
|
check: (cwd) => {
|
|
1400
|
-
const fullPath =
|
|
1401
|
-
if (!
|
|
2139
|
+
const fullPath = path8.join(cwd, expectedPath);
|
|
2140
|
+
if (!fs7.existsSync(fullPath)) {
|
|
1402
2141
|
return [{
|
|
1403
2142
|
ruleId: id,
|
|
1404
2143
|
severity: "error",
|
|
@@ -1418,11 +2157,11 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
1418
2157
|
pattern: new RegExp(escapeRegex(pattern), "i"),
|
|
1419
2158
|
check: (cwd) => {
|
|
1420
2159
|
const violations = [];
|
|
1421
|
-
const srcDir =
|
|
1422
|
-
if (!
|
|
2160
|
+
const srcDir = path8.join(cwd, "src");
|
|
2161
|
+
if (!fs7.existsSync(srcDir)) return violations;
|
|
1423
2162
|
const regex = new RegExp(escapeRegex(pattern), "i");
|
|
1424
2163
|
walkFiles(srcDir, (filePath) => {
|
|
1425
|
-
const fileContent =
|
|
2164
|
+
const fileContent = fs7.readFileSync(filePath, "utf-8");
|
|
1426
2165
|
const fileLines = fileContent.split("\n");
|
|
1427
2166
|
fileLines.forEach((line, idx) => {
|
|
1428
2167
|
if (regex.test(line)) {
|
|
@@ -1430,7 +2169,7 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
1430
2169
|
ruleId: id,
|
|
1431
2170
|
severity: type === "banned" ? "error" : "warning",
|
|
1432
2171
|
message: type === "banned" ? `\uAE08\uC9C0 \uD328\uD134 \uBC1C\uACAC: \`${pattern}\`` : `\uD544\uC218 \uD328\uD134 \uB204\uB77D: \`${pattern}\``,
|
|
1433
|
-
file:
|
|
2172
|
+
file: path8.relative(cwd, filePath),
|
|
1434
2173
|
line: idx + 1
|
|
1435
2174
|
});
|
|
1436
2175
|
}
|
|
@@ -1442,9 +2181,9 @@ function createContentRule(id, section, desc, pattern, type) {
|
|
|
1442
2181
|
};
|
|
1443
2182
|
}
|
|
1444
2183
|
function walkFiles(dir, callback) {
|
|
1445
|
-
const entries =
|
|
2184
|
+
const entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
1446
2185
|
for (const entry of entries) {
|
|
1447
|
-
const fullPath =
|
|
2186
|
+
const fullPath = path8.join(dir, entry.name);
|
|
1448
2187
|
if (entry.isDirectory()) {
|
|
1449
2188
|
if (!["node_modules", ".git", "dist", ".next"].includes(entry.name)) {
|
|
1450
2189
|
walkFiles(fullPath, callback);
|
|
@@ -1464,8 +2203,8 @@ async function check() {
|
|
|
1464
2203
|
${ko.check.title}
|
|
1465
2204
|
`));
|
|
1466
2205
|
const cwd = process.cwd();
|
|
1467
|
-
const rulesPath =
|
|
1468
|
-
if (!
|
|
2206
|
+
const rulesPath = path9.join(cwd, "RULES.md");
|
|
2207
|
+
if (!fs8.existsSync(rulesPath)) {
|
|
1469
2208
|
console.log(chalk7.yellow(ko.check.noRules));
|
|
1470
2209
|
console.log(chalk7.dim(" vhk init\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 RULES.md\uB97C \uB9CC\uB4E4\uC5B4 \uBCF4\uC138\uC694."));
|
|
1471
2210
|
return;
|
|
@@ -1523,8 +2262,8 @@ ${ko.check.title}
|
|
|
1523
2262
|
|
|
1524
2263
|
// src/commands/secure.ts
|
|
1525
2264
|
import chalk8 from "chalk";
|
|
1526
|
-
import
|
|
1527
|
-
import
|
|
2265
|
+
import fs10 from "fs";
|
|
2266
|
+
import path11 from "path";
|
|
1528
2267
|
|
|
1529
2268
|
// src/lib/secret-patterns.ts
|
|
1530
2269
|
var SECRET_PATTERNS = [
|
|
@@ -1589,9 +2328,32 @@ function maskSecret(value) {
|
|
|
1589
2328
|
return value.slice(0, visible) + "****";
|
|
1590
2329
|
}
|
|
1591
2330
|
|
|
1592
|
-
// src/
|
|
1593
|
-
|
|
1594
|
-
|
|
2331
|
+
// src/lib/scan-files.ts
|
|
2332
|
+
import fs9 from "fs";
|
|
2333
|
+
import path10 from "path";
|
|
2334
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
2335
|
+
"node_modules",
|
|
2336
|
+
".git",
|
|
2337
|
+
"dist",
|
|
2338
|
+
".next",
|
|
2339
|
+
".nuxt",
|
|
2340
|
+
"build",
|
|
2341
|
+
"coverage",
|
|
2342
|
+
"out",
|
|
2343
|
+
".turbo",
|
|
2344
|
+
".vercel",
|
|
2345
|
+
".cache",
|
|
2346
|
+
".pnpm",
|
|
2347
|
+
".idea",
|
|
2348
|
+
".claude",
|
|
2349
|
+
".cursor"
|
|
2350
|
+
]);
|
|
2351
|
+
var SKIP_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
2352
|
+
"pnpm-lock.yaml",
|
|
2353
|
+
"package-lock.json",
|
|
2354
|
+
"yarn.lock"
|
|
2355
|
+
]);
|
|
2356
|
+
var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1595
2357
|
".ts",
|
|
1596
2358
|
".tsx",
|
|
1597
2359
|
".js",
|
|
@@ -1601,12 +2363,49 @@ var SCAN_EXTENSIONS = [
|
|
|
1601
2363
|
".json",
|
|
1602
2364
|
".yaml",
|
|
1603
2365
|
".yml",
|
|
1604
|
-
".toml"
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
2366
|
+
".toml"
|
|
2367
|
+
]);
|
|
2368
|
+
var MAX_SCAN_FILE_BYTES = 512 * 1024;
|
|
2369
|
+
function isScannableFileName(fileName) {
|
|
2370
|
+
if (SKIP_FILE_NAMES.has(fileName)) return false;
|
|
2371
|
+
if (fileName.startsWith(".env")) return true;
|
|
2372
|
+
return SCAN_EXTENSIONS.has(path10.extname(fileName).toLowerCase());
|
|
2373
|
+
}
|
|
2374
|
+
function walkProjectFiles(rootDir, onFile, ig = loadGitignore(rootDir)) {
|
|
2375
|
+
function walk(dir) {
|
|
2376
|
+
let entries;
|
|
2377
|
+
try {
|
|
2378
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
2379
|
+
} catch {
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
for (const entry of entries) {
|
|
2383
|
+
const fullPath = path10.join(dir, entry.name);
|
|
2384
|
+
const rel = path10.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
2385
|
+
if (entry.isDirectory()) {
|
|
2386
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
2387
|
+
if (isPathIgnored(ig, `${rel}/`)) continue;
|
|
2388
|
+
walk(fullPath);
|
|
2389
|
+
continue;
|
|
2390
|
+
}
|
|
2391
|
+
if (!isScannableFileName(entry.name)) continue;
|
|
2392
|
+
if (isPathIgnored(ig, rel)) continue;
|
|
2393
|
+
let size = 0;
|
|
2394
|
+
try {
|
|
2395
|
+
size = fs9.statSync(fullPath).size;
|
|
2396
|
+
} catch {
|
|
2397
|
+
continue;
|
|
2398
|
+
}
|
|
2399
|
+
if (size > MAX_SCAN_FILE_BYTES) continue;
|
|
2400
|
+
onFile(fullPath, rel);
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
walk(rootDir);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
// src/commands/secure.ts
|
|
2407
|
+
var MAX_FINDINGS = 200;
|
|
2408
|
+
var MAX_LINE_CHARS = 4e3;
|
|
1610
2409
|
async function secure() {
|
|
1611
2410
|
console.log(chalk8.bold(`
|
|
1612
2411
|
${ko.secure.title}
|
|
@@ -1614,13 +2413,14 @@ ${ko.secure.title}
|
|
|
1614
2413
|
const cwd = process.cwd();
|
|
1615
2414
|
const findings = [];
|
|
1616
2415
|
let scannedFiles = 0;
|
|
1617
|
-
|
|
1618
|
-
const
|
|
2416
|
+
let truncated = false;
|
|
2417
|
+
const gitignorePath = path11.join(cwd, ".gitignore");
|
|
2418
|
+
const hasGitignore = fs10.existsSync(gitignorePath);
|
|
1619
2419
|
if (!hasGitignore) {
|
|
1620
2420
|
console.log(chalk8.yellow(` ${ko.secure.noGitignore}`));
|
|
1621
2421
|
console.log(chalk8.dim(" .env \uD30C\uC77C\uC774 \uCEE4\uBC0B\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n"));
|
|
1622
2422
|
} else {
|
|
1623
|
-
const gitignoreContent =
|
|
2423
|
+
const gitignoreContent = fs10.readFileSync(gitignorePath, "utf-8");
|
|
1624
2424
|
if (!gitignoreContent.includes(".env")) {
|
|
1625
2425
|
console.log(chalk8.yellow(` ${ko.secure.noEnvInGitignore}`));
|
|
1626
2426
|
console.log(chalk8.dim(" \uCD94\uAC00\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4.\n"));
|
|
@@ -1628,13 +2428,15 @@ ${ko.secure.title}
|
|
|
1628
2428
|
}
|
|
1629
2429
|
console.log(chalk8.dim(` ${ko.secure.scanning}
|
|
1630
2430
|
`));
|
|
1631
|
-
|
|
2431
|
+
walkProjectFiles(cwd, (filePath, relPath) => {
|
|
1632
2432
|
scannedFiles++;
|
|
1633
|
-
const content =
|
|
2433
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
1634
2434
|
const lines = content.split("\n");
|
|
1635
|
-
const relPath = path8.relative(cwd, filePath);
|
|
1636
2435
|
for (const pattern of SECRET_PATTERNS) {
|
|
2436
|
+
if (truncated) break;
|
|
1637
2437
|
lines.forEach((line, idx) => {
|
|
2438
|
+
if (truncated) return;
|
|
2439
|
+
if (line.length > MAX_LINE_CHARS) return;
|
|
1638
2440
|
const trimmed = line.trim();
|
|
1639
2441
|
if (trimmed.startsWith("//") && trimmed.includes("example")) return;
|
|
1640
2442
|
if (trimmed.startsWith("#") && trimmed.includes("example")) return;
|
|
@@ -1649,15 +2451,26 @@ ${ko.secure.title}
|
|
|
1649
2451
|
line: idx + 1,
|
|
1650
2452
|
match: maskSecret(match[0])
|
|
1651
2453
|
});
|
|
2454
|
+
if (findings.length >= MAX_FINDINGS) {
|
|
2455
|
+
truncated = true;
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
1652
2458
|
}
|
|
1653
2459
|
});
|
|
1654
2460
|
}
|
|
1655
2461
|
});
|
|
1656
|
-
console.log(chalk8.dim(` \u{1F4C2} ${scannedFiles}\uAC1C \uD30C\uC77C \uC2A4\uCE94 \uC644\uB8CC
|
|
1657
|
-
|
|
2462
|
+
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)`));
|
|
2463
|
+
if (truncated) {
|
|
2464
|
+
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.`));
|
|
2465
|
+
}
|
|
2466
|
+
console.log("");
|
|
1658
2467
|
if (findings.length === 0) {
|
|
1659
2468
|
console.log(chalk8.green.bold(` ${ko.secure.clean}`));
|
|
1660
|
-
|
|
2469
|
+
printNextStep({
|
|
2470
|
+
message: "\uBCF4\uC548 \uC774\uC0C1 \uC5C6\uC74C! \uAE68\uB057\uD569\uB2C8\uB2E4.",
|
|
2471
|
+
command: "vhk \uC815\uB9AC",
|
|
2472
|
+
cursorHint: "\uC624\uB298 \uD55C \uC77C \uC815\uB9AC\uD574\uC918"
|
|
2473
|
+
});
|
|
1661
2474
|
return;
|
|
1662
2475
|
}
|
|
1663
2476
|
const critical = findings.filter((f) => f.severity === "critical");
|
|
@@ -1698,21 +2511,225 @@ ${ko.secure.title}
|
|
|
1698
2511
|
process.exitCode = 1;
|
|
1699
2512
|
}
|
|
1700
2513
|
}
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
2514
|
+
|
|
2515
|
+
// src/commands/doctor.ts
|
|
2516
|
+
import chalk9 from "chalk";
|
|
2517
|
+
import { execSync } from "child_process";
|
|
2518
|
+
import fs11 from "fs";
|
|
2519
|
+
import path12 from "path";
|
|
2520
|
+
import { fileURLToPath } from "url";
|
|
2521
|
+
function checkCommand(name, command, hint) {
|
|
2522
|
+
try {
|
|
2523
|
+
const version = execSync(`${command} --version`, { encoding: "utf-8" }).trim().split("\n")[0];
|
|
2524
|
+
return { name, command, version, ok: true, hint };
|
|
2525
|
+
} catch {
|
|
2526
|
+
return { name, command, ok: false, hint };
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
function getVhkVersion() {
|
|
2530
|
+
const dir = path12.dirname(fileURLToPath(import.meta.url));
|
|
2531
|
+
const candidates = [
|
|
2532
|
+
path12.join(dir, "../package.json"),
|
|
2533
|
+
path12.join(dir, "../../package.json")
|
|
2534
|
+
];
|
|
2535
|
+
for (const pkgPath of candidates) {
|
|
2536
|
+
try {
|
|
2537
|
+
if (fs11.existsSync(pkgPath)) {
|
|
2538
|
+
const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
|
|
2539
|
+
return pkg.version;
|
|
1708
2540
|
}
|
|
2541
|
+
} catch {
|
|
2542
|
+
continue;
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
return void 0;
|
|
2546
|
+
}
|
|
2547
|
+
async function doctor() {
|
|
2548
|
+
console.log(chalk9.bold(`
|
|
2549
|
+
${ko.doctor.title}
|
|
2550
|
+
`));
|
|
2551
|
+
const checks = [
|
|
2552
|
+
checkCommand("Node.js", "node", "\uC124\uCE58: https://nodejs.org (LTS \uAD8C\uC7A5)"),
|
|
2553
|
+
checkCommand("npm", "npm", "Node.js \uC124\uCE58 \uC2DC \uD568\uAED8 \uC124\uCE58\uB429\uB2C8\uB2E4"),
|
|
2554
|
+
checkCommand("pnpm", "pnpm", "\uC124\uCE58: npm i -g pnpm"),
|
|
2555
|
+
checkCommand("Git", "git", "\uC124\uCE58: https://git-scm.com")
|
|
2556
|
+
];
|
|
2557
|
+
let allOk = true;
|
|
2558
|
+
for (const check2 of checks) {
|
|
2559
|
+
if (check2.ok) {
|
|
2560
|
+
console.log(chalk9.green(` \u2705 ${check2.name}`) + chalk9.dim(` \u2014 ${check2.version}`));
|
|
1709
2561
|
} else {
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
2562
|
+
console.log(chalk9.red(` \u274C ${check2.name} \uC5C6\uC74C`));
|
|
2563
|
+
console.log(chalk9.dim(` \u2192 ${check2.hint}`));
|
|
2564
|
+
allOk = false;
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
console.log("");
|
|
2568
|
+
const vhkVersion = getVhkVersion();
|
|
2569
|
+
if (vhkVersion) {
|
|
2570
|
+
console.log(chalk9.green(" \u2705 VHK") + chalk9.dim(` \u2014 v${vhkVersion}`));
|
|
2571
|
+
} else {
|
|
2572
|
+
console.log(chalk9.green(" \u2705 VHK") + chalk9.dim(" \u2014 \uC124\uCE58\uB428"));
|
|
2573
|
+
}
|
|
2574
|
+
console.log("");
|
|
2575
|
+
console.log(chalk9.bold(` ${ko.doctor.projectFiles}`));
|
|
2576
|
+
const cwd = process.cwd();
|
|
2577
|
+
const projectFiles = [
|
|
2578
|
+
{ name: "RULES.md", hint: "vhk init\uC73C\uB85C \uC0DD\uC131 \uAC00\uB2A5" },
|
|
2579
|
+
{ name: "COMMANDS.md", hint: "vhk init\uC73C\uB85C \uC0DD\uC131 \uAC00\uB2A5" },
|
|
2580
|
+
{ name: "package.json", hint: "\uD504\uB85C\uC81D\uD2B8 \uD3F4\uB354\uC5D0\uC11C \uC2E4\uD589\uD558\uC138\uC694" },
|
|
2581
|
+
{ name: ".gitignore", hint: "\uBCF4\uC548\uC744 \uC704\uD574 \uCD94\uAC00 \uAD8C\uC7A5" },
|
|
2582
|
+
{ name: ".env", hint: ".gitignore\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778" }
|
|
2583
|
+
];
|
|
2584
|
+
for (const file of projectFiles) {
|
|
2585
|
+
const exists = fs11.existsSync(path12.join(cwd, file.name));
|
|
2586
|
+
if (exists) {
|
|
2587
|
+
console.log(chalk9.green(` \u2705 ${file.name}`));
|
|
2588
|
+
if (file.name === ".env") {
|
|
2589
|
+
const gitignorePath = path12.join(cwd, ".gitignore");
|
|
2590
|
+
if (fs11.existsSync(gitignorePath)) {
|
|
2591
|
+
const gitignore = fs11.readFileSync(gitignorePath, "utf-8");
|
|
2592
|
+
if (!gitignore.includes(".env")) {
|
|
2593
|
+
console.log(chalk9.yellow(` ${ko.doctor.envNotIgnored}`));
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
1713
2596
|
}
|
|
2597
|
+
} else {
|
|
2598
|
+
console.log(chalk9.dim(` \u2B1A ${file.name}`) + chalk9.dim(` \u2014 ${file.hint}`));
|
|
1714
2599
|
}
|
|
1715
2600
|
}
|
|
2601
|
+
console.log("");
|
|
2602
|
+
if (allOk) {
|
|
2603
|
+
console.log(chalk9.green.bold(` ${ko.doctor.allOk}`));
|
|
2604
|
+
printNextStep({
|
|
2605
|
+
message: ko.doctor.nextOkMessage,
|
|
2606
|
+
command: "vhk \uC2DC\uC791",
|
|
2607
|
+
cursorHint: "\uD504\uB85C\uC81D\uD2B8 \uB9CC\uB4E4\uC5B4\uC918"
|
|
2608
|
+
});
|
|
2609
|
+
} else {
|
|
2610
|
+
console.log(chalk9.yellow.bold(` ${ko.doctor.missing} ${ko.doctor.missingHint}`));
|
|
2611
|
+
printNextStep({
|
|
2612
|
+
message: ko.doctor.nextRetryMessage,
|
|
2613
|
+
command: "vhk doctor",
|
|
2614
|
+
cursorHint: "\uD658\uACBD \uB2E4\uC2DC \uC810\uAC80\uD574\uC918"
|
|
2615
|
+
});
|
|
2616
|
+
process.exitCode = 1;
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
// src/commands/ship.ts
|
|
2621
|
+
import chalk10 from "chalk";
|
|
2622
|
+
import inquirer4 from "inquirer";
|
|
2623
|
+
import fs12 from "fs";
|
|
2624
|
+
import path13 from "path";
|
|
2625
|
+
var CHECKLIST = [
|
|
2626
|
+
{ id: "build", questionKey: "checkBuild", hintKey: "hintBuild" },
|
|
2627
|
+
{ id: "test", questionKey: "checkTest", hintKey: "hintTest" },
|
|
2628
|
+
{ id: "version", questionKey: "checkVersion", hintKey: "hintVersion" },
|
|
2629
|
+
{ id: "changelog", questionKey: "checkChangelog", hintKey: "hintChangelog" },
|
|
2630
|
+
{ id: "security", questionKey: "checkSecurity", hintKey: "hintSecurity" },
|
|
2631
|
+
{ id: "commit", questionKey: "checkCommit", hintKey: "hintCommit" }
|
|
2632
|
+
];
|
|
2633
|
+
function sanitizeVersion(version) {
|
|
2634
|
+
return version.trim().replace(/^v/i, "").replace(/[^a-zA-Z0-9._-]/g, "-") || "0.0.0";
|
|
2635
|
+
}
|
|
2636
|
+
async function ship() {
|
|
2637
|
+
console.log(chalk10.bold(`
|
|
2638
|
+
${ko.ship.title}
|
|
2639
|
+
`));
|
|
2640
|
+
const cwd = process.cwd();
|
|
2641
|
+
console.log(chalk10.cyan.bold(` ${ko.ship.checklist}
|
|
2642
|
+
`));
|
|
2643
|
+
const { passed } = await inquirer4.prompt([{
|
|
2644
|
+
type: "checkbox",
|
|
2645
|
+
name: "passed",
|
|
2646
|
+
message: ko.ship.checkboxPrompt,
|
|
2647
|
+
choices: CHECKLIST.map((c) => ({
|
|
2648
|
+
name: `${ko.ship[c.questionKey]} ${chalk10.dim(`(${ko.ship[c.hintKey]})`)}`,
|
|
2649
|
+
value: c.id
|
|
2650
|
+
}))
|
|
2651
|
+
}]);
|
|
2652
|
+
const allPassed = passed.length === CHECKLIST.length;
|
|
2653
|
+
const skipped = CHECKLIST.filter((c) => !passed.includes(c.id));
|
|
2654
|
+
if (!allPassed) {
|
|
2655
|
+
console.log(chalk10.yellow(`
|
|
2656
|
+
${ko.ship.incompleteHeader}`));
|
|
2657
|
+
skipped.forEach((s) => {
|
|
2658
|
+
console.log(chalk10.yellow(` \u2022 ${ko.ship[s.questionKey]}`));
|
|
2659
|
+
console.log(chalk10.dim(` \u2192 ${ko.ship[s.hintKey]}`));
|
|
2660
|
+
});
|
|
2661
|
+
const { proceed } = await inquirer4.prompt([{
|
|
2662
|
+
type: "confirm",
|
|
2663
|
+
name: "proceed",
|
|
2664
|
+
message: ko.ship.proceedConfirm,
|
|
2665
|
+
default: false
|
|
2666
|
+
}]);
|
|
2667
|
+
if (!proceed) {
|
|
2668
|
+
printNextStep({
|
|
2669
|
+
message: ko.ship.retryMessage,
|
|
2670
|
+
command: "vhk \uBC30\uD3EC",
|
|
2671
|
+
cursorHint: ko.ship.retryCursorHint
|
|
2672
|
+
});
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
} else {
|
|
2676
|
+
console.log(chalk10.green(`
|
|
2677
|
+
${ko.ship.allPassed}
|
|
2678
|
+
`));
|
|
2679
|
+
}
|
|
2680
|
+
console.log(chalk10.cyan.bold(` ${ko.ship.retro}
|
|
2681
|
+
`));
|
|
2682
|
+
console.log(chalk10.dim(` ${ko.ship.versionHint}`));
|
|
2683
|
+
const retro = await inquirer4.prompt([
|
|
2684
|
+
{ type: "input", name: "version", message: ko.ship.versionPrompt },
|
|
2685
|
+
{ type: "input", name: "whatWentWell", message: ko.ship.questionWell },
|
|
2686
|
+
{ type: "input", name: "whatWentWrong", message: ko.ship.questionWrong },
|
|
2687
|
+
{ type: "input", name: "learned", message: ko.ship.questionLearned },
|
|
2688
|
+
{ type: "input", name: "nextVersion", message: ko.ship.questionNext }
|
|
2689
|
+
]);
|
|
2690
|
+
const buildLogDir = path13.join(cwd, "docs", "build-log");
|
|
2691
|
+
if (!fs12.existsSync(buildLogDir)) fs12.mkdirSync(buildLogDir, { recursive: true });
|
|
2692
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2693
|
+
const versionSlug = sanitizeVersion(retro.version);
|
|
2694
|
+
const fileName = `${today}-v${versionSlug}.md`;
|
|
2695
|
+
const filePath = path13.join(buildLogDir, fileName);
|
|
2696
|
+
const content = [
|
|
2697
|
+
`# \uBE4C\uB4DC \uB85C\uADF8: v${versionSlug}`,
|
|
2698
|
+
"",
|
|
2699
|
+
`**\uBC30\uD3EC\uC77C:** ${today}`,
|
|
2700
|
+
`**\uBC84\uC804:** ${versionSlug}`,
|
|
2701
|
+
"",
|
|
2702
|
+
"## \uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
|
|
2703
|
+
...CHECKLIST.map(
|
|
2704
|
+
(c) => `- [${passed.includes(c.id) ? "x" : " "}] ${ko.ship[c.questionKey]}`
|
|
2705
|
+
),
|
|
2706
|
+
"",
|
|
2707
|
+
"## \uD68C\uACE0",
|
|
2708
|
+
"",
|
|
2709
|
+
"### \u2705 \uC798\uB41C \uC810",
|
|
2710
|
+
retro.whatWentWell || ko.ship.emptySection,
|
|
2711
|
+
"",
|
|
2712
|
+
"### \u274C \uC5B4\uB824\uC6E0\uB358 \uC810",
|
|
2713
|
+
retro.whatWentWrong || ko.ship.emptySection,
|
|
2714
|
+
"",
|
|
2715
|
+
"### \u{1F4A1} \uBC30\uC6B4 \uC810",
|
|
2716
|
+
retro.learned || ko.ship.emptySection,
|
|
2717
|
+
"",
|
|
2718
|
+
"### \u{1F52E} \uB2E4\uC74C \uBC84\uC804",
|
|
2719
|
+
retro.nextVersion || ko.ship.emptyNext,
|
|
2720
|
+
"",
|
|
2721
|
+
"---",
|
|
2722
|
+
`*Generated by \`vhk ship\` at ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
2723
|
+
].join("\n");
|
|
2724
|
+
fs12.writeFileSync(filePath, content, "utf-8");
|
|
2725
|
+
console.log(chalk10.green(`
|
|
2726
|
+
${ko.ship.buildLogDone(path13.relative(cwd, filePath))}`));
|
|
2727
|
+
printNextStep({
|
|
2728
|
+
message: ko.ship.deployMessage,
|
|
2729
|
+
command: "npm publish --access=public",
|
|
2730
|
+
cursorHint: ko.ship.deployCursorHint,
|
|
2731
|
+
alternative: `GitHub \uD0DC\uADF8\uB3C4 \uB9CC\uB4E4\uBA74 \uC88B\uC544\uC694: git tag v${versionSlug}`
|
|
2732
|
+
});
|
|
1716
2733
|
}
|
|
1717
2734
|
|
|
1718
2735
|
// src/index.ts
|
|
@@ -1724,9 +2741,11 @@ var KO_ALIASES = {
|
|
|
1724
2741
|
recap: "\uC815\uB9AC",
|
|
1725
2742
|
sync: "\uADDC\uCE59",
|
|
1726
2743
|
check: "\uC810\uAC80",
|
|
1727
|
-
secure: "\uBCF4\uC548"
|
|
2744
|
+
secure: "\uBCF4\uC548",
|
|
2745
|
+
ship: "\uBC30\uD3EC",
|
|
2746
|
+
doctor: "\uD658\uACBD"
|
|
1728
2747
|
};
|
|
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.
|
|
2748
|
+
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.4.0");
|
|
1730
2749
|
program.configureHelp({
|
|
1731
2750
|
formatHelp(cmd, helper) {
|
|
1732
2751
|
if (cmd.parent) {
|
|
@@ -1754,19 +2773,69 @@ program.command("sync").alias("\uB9DE\uCD94\uAE30").alias("\uADDC\uCE59").descri
|
|
|
1754
2773
|
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
2774
|
var secureCmd = program.command("secure").alias("\uBCF4\uC548").description("\uBCF4\uC548 \uB3C4\uAD6C \uBAA8\uC74C");
|
|
1756
2775
|
secureCmd.command("scan").alias("\uC2A4\uCE94").description("\uC2DC\uD06C\uB9BF/\uD0A4 \uC720\uCD9C \uC2A4\uCE94").action(secure);
|
|
2776
|
+
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);
|
|
2777
|
+
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);
|
|
2778
|
+
program.on("command:*", async (operands) => {
|
|
2779
|
+
const input = operands.join(" ");
|
|
2780
|
+
const route = routeNaturalLanguage(input);
|
|
2781
|
+
if (route) {
|
|
2782
|
+
console.log("");
|
|
2783
|
+
console.log(chalk11.cyan(` \u{1F4AC} "${input}"`));
|
|
2784
|
+
console.log(chalk11.cyan(` \u2192 ${route.explanation}`));
|
|
2785
|
+
if (route.confidence === "low") {
|
|
2786
|
+
const { confirm } = await inquirer5.prompt([{
|
|
2787
|
+
type: "confirm",
|
|
2788
|
+
name: "confirm",
|
|
2789
|
+
message: `${route.explanation} \u2014 ${ko.nlp.matched}`,
|
|
2790
|
+
default: true
|
|
2791
|
+
}]);
|
|
2792
|
+
if (!confirm) {
|
|
2793
|
+
console.log(chalk11.dim(` ${ko.nlp.menuHint}`));
|
|
2794
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
console.log("");
|
|
2798
|
+
switch (route.command) {
|
|
2799
|
+
case "gate":
|
|
2800
|
+
return gate();
|
|
2801
|
+
case "init":
|
|
2802
|
+
return init({
|
|
2803
|
+
skipGate: route.args?.includes("--skip-gate"),
|
|
2804
|
+
fromNotion: route.args?.includes("--from-notion") ? extractNotionUrl(input) : void 0
|
|
2805
|
+
});
|
|
2806
|
+
case "recap":
|
|
2807
|
+
return recap({});
|
|
2808
|
+
case "sync":
|
|
2809
|
+
return sync();
|
|
2810
|
+
case "check":
|
|
2811
|
+
return check();
|
|
2812
|
+
case "secure":
|
|
2813
|
+
return secure();
|
|
2814
|
+
case "ship":
|
|
2815
|
+
return ship();
|
|
2816
|
+
case "doctor":
|
|
2817
|
+
return doctor();
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
console.log(chalk11.yellow(`
|
|
2821
|
+
\u2753 "${input}" \u2014 ${ko.nlp.notMatched}
|
|
2822
|
+
`));
|
|
2823
|
+
});
|
|
1757
2824
|
program.action(async () => {
|
|
1758
2825
|
console.log("\n\u{1F3AF} VHK \u2014 \uBC14\uC774\uBE0C\uCF54\uB529 \uD504\uB85C\uC81D\uD2B8 \uCF54\uCE58\n");
|
|
1759
|
-
const { choice } = await
|
|
2826
|
+
const { choice } = await inquirer5.prompt([{
|
|
1760
2827
|
type: "list",
|
|
1761
2828
|
name: "choice",
|
|
1762
2829
|
message: "\uBB58 \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?",
|
|
1763
2830
|
choices: [
|
|
1764
2831
|
{ 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
|
|
2832
|
+
{ name: "\u{1F4E6} \uD504\uB85C\uC81D\uD2B8 \uC2DC\uC791\uD558\uAE30", value: "init" },
|
|
1766
2833
|
{ name: "\u{1F4DD} \uC624\uB298 \uD55C \uC77C \uC815\uB9AC\uD558\uAE30", value: "recap" },
|
|
1767
2834
|
{ name: "\u{1F50D} \uADDC\uCE59 \uD30C\uC77C \uC810\uAC80\uD558\uAE30", value: "check" },
|
|
1768
2835
|
{ 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" }
|
|
2836
|
+
{ name: "\u{1F504} \uADDC\uCE59 \uD30C\uC77C \uB3D9\uAE30\uD654", value: "sync" },
|
|
2837
|
+
{ name: "\u{1F680} \uBC30\uD3EC\uD558\uAE30", value: "ship" },
|
|
2838
|
+
{ name: "\u{1FA7A} \uD658\uACBD \uC810\uAC80\uD558\uAE30", value: "doctor" }
|
|
1770
2839
|
]
|
|
1771
2840
|
}]);
|
|
1772
2841
|
switch (choice) {
|
|
@@ -1782,6 +2851,10 @@ program.action(async () => {
|
|
|
1782
2851
|
return secure();
|
|
1783
2852
|
case "sync":
|
|
1784
2853
|
return sync();
|
|
2854
|
+
case "doctor":
|
|
2855
|
+
return doctor();
|
|
2856
|
+
case "ship":
|
|
2857
|
+
return ship();
|
|
1785
2858
|
}
|
|
1786
2859
|
});
|
|
1787
2860
|
program.parse();
|