create-vite-react-boot 1.0.19 → 1.0.20
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/bin/cli.js +71 -60
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -69,60 +69,80 @@ function barLine({ width, ratio }) {
|
|
|
69
69
|
/** 진행바 기반 실행 (yarn/pnpm은 이벤트 파싱, npm은 추정 진행) */
|
|
70
70
|
async function runWithProgress(cmd, args = [], { cwd, env } = {}, label = "작업") {
|
|
71
71
|
const start = Date.now();
|
|
72
|
-
const
|
|
73
|
-
|
|
72
|
+
const cols = process.stdout.columns || 80;
|
|
73
|
+
|
|
74
|
+
// ── Windows/Git Bash 등에서 안전하게: 1줄 Compact 모드
|
|
75
|
+
const compact = process.platform === "win32" || process.env.PROGRESS_COMPACT === "1";
|
|
74
76
|
|
|
75
77
|
// 상태
|
|
76
|
-
let percent = null; // 0..1 (
|
|
78
|
+
let percent = null; // 0..1 (yarn/pnpm 이벤트 기반)
|
|
77
79
|
let downloadedBytes = 0; // 추정치(출력 길이 기반)
|
|
78
|
-
let totalHint = null; // yarn/pnpm가
|
|
80
|
+
let totalHint = null; // yarn/pnpm가 주면 총량 추정
|
|
79
81
|
let lastTick = Date.now();
|
|
80
|
-
let emaRate = 0; //
|
|
82
|
+
let emaRate = 0; // byte/s
|
|
81
83
|
const alpha = 0.15;
|
|
82
84
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
// 렌더 throttle & dedupe
|
|
86
|
+
let lastRender = 0;
|
|
87
|
+
let lastPrinted = "";
|
|
86
88
|
|
|
87
|
-
const
|
|
89
|
+
const mkLine = (brief = "") => {
|
|
88
90
|
const elapsed = (Date.now() - start) / 1000;
|
|
91
|
+
const ratio = percent != null
|
|
92
|
+
? percent
|
|
93
|
+
: Math.min(0.95, Math.max(0.1, 1 - 1 / Math.max(2, elapsed / 3)));
|
|
94
|
+
|
|
95
|
+
const barWidth = Math.min(24, Math.max(10, Math.floor(cols * 0.25)));
|
|
96
|
+
const filled = Math.round(barWidth * Math.max(0, Math.min(1, ratio)));
|
|
97
|
+
const bar = `[${"#".repeat(filled)}${"-".repeat(barWidth - filled)}]`;
|
|
98
|
+
|
|
89
99
|
const eta = (() => {
|
|
90
100
|
if (percent != null && percent > 0 && totalHint) {
|
|
91
101
|
const remain = (1 - percent) * totalHint;
|
|
92
102
|
const rate = emaRate > 1 ? emaRate : null;
|
|
93
103
|
return rate ? formatHMS(remain / rate) : "--:--";
|
|
94
104
|
}
|
|
95
|
-
|
|
96
|
-
const remainBytes = totalHint ? (totalHint - downloadedBytes) : null;
|
|
97
|
-
if (remainBytes && remainBytes > 0) return formatHMS(remainBytes / emaRate);
|
|
98
|
-
}
|
|
99
|
-
return "--:--";
|
|
105
|
+
return emaRate > 50 ? formatHMS((totalHint ? Math.max(0, totalHint - downloadedBytes) : 0) / Math.max(1, emaRate)) : "--:--";
|
|
100
106
|
})();
|
|
107
|
+
|
|
101
108
|
const sizeText = totalHint
|
|
102
|
-
? `${(downloadedBytes / 1048576).toFixed(1)}
|
|
103
|
-
: `${(downloadedBytes / 1048576).toFixed(1)}
|
|
104
|
-
|
|
109
|
+
? `${(downloadedBytes / 1048576).toFixed(1)}/${(totalHint / 1048576).toFixed(1)}MiB`
|
|
110
|
+
: `${(downloadedBytes / 1048576).toFixed(1)}MiB`;
|
|
111
|
+
|
|
112
|
+
const head = `${label} [${formatHMS(elapsed)}] ${sizeText} (eta:${eta})`;
|
|
113
|
+
const msg = brief ? ` ${brief}` : "";
|
|
114
|
+
|
|
115
|
+
// Compact 1줄로 잘라쓰기
|
|
116
|
+
let line = `${head} ${bar}${msg}`;
|
|
117
|
+
if (line.length > cols) line = line.slice(0, cols - 1);
|
|
118
|
+
return line;
|
|
105
119
|
};
|
|
106
120
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
const safeWrite = (line) => {
|
|
122
|
+
// 200ms 이하/동일 라인일 땐 스킵
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
if (now - lastRender < 200) return;
|
|
125
|
+
if (line === lastPrinted) return;
|
|
112
126
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
process.stdout.write((header()).padEnd(cols) + "\n");
|
|
118
|
-
// line 1
|
|
127
|
+
lastRender = now;
|
|
128
|
+
lastPrinted = line;
|
|
129
|
+
|
|
130
|
+
// 1줄 덮어쓰기
|
|
119
131
|
readline.clearLine(process.stdout, 0);
|
|
120
|
-
process.stdout
|
|
121
|
-
|
|
132
|
+
readline.cursorTo(process.stdout, 0);
|
|
133
|
+
process.stdout.write(line);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const finishWrite = (ok = true) => {
|
|
122
137
|
readline.clearLine(process.stdout, 0);
|
|
123
|
-
|
|
124
|
-
process.stdout.write(
|
|
125
|
-
}
|
|
138
|
+
readline.cursorTo(process.stdout, 0);
|
|
139
|
+
process.stdout.write((ok ? "… 완료" : "… 실패") + "\n");
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// 커서 숨김 (종료 시 복구는 전역 핸들러에서)
|
|
143
|
+
process.stdout.write("\x1B[?25l");
|
|
144
|
+
// Compact 모드는 별도 빈줄 확보 없이 1줄만 사용
|
|
145
|
+
safeWrite(mkLine());
|
|
126
146
|
|
|
127
147
|
await new Promise((resolve, reject) => {
|
|
128
148
|
let a = args.slice();
|
|
@@ -140,23 +160,20 @@ async function runWithProgress(cmd, args = [], { cwd, env } = {}, label = "작
|
|
|
140
160
|
});
|
|
141
161
|
|
|
142
162
|
let lastMsg = "";
|
|
143
|
-
|
|
144
163
|
const handleJSONLine = (line) => {
|
|
145
164
|
try {
|
|
146
165
|
const j = JSON.parse(line);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
totalHint = j.data.total; // tick 총량 힌트
|
|
166
|
+
if (j.type === "progressStart" && j.data && typeof j.data.total === "number") {
|
|
167
|
+
totalHint = j.data.total;
|
|
150
168
|
}
|
|
151
169
|
if (j.type === "progressTick" && j.data && typeof j.data.current === "number" && typeof j.data.total === "number") {
|
|
152
|
-
|
|
170
|
+
const p = j.data.current / j.data.total;
|
|
171
|
+
if (!Number.isNaN(p)) percent = Math.max(0, Math.min(1, p));
|
|
153
172
|
downloadedBytes += 64 * 1024; // 대략 보정
|
|
154
173
|
}
|
|
155
|
-
if (j.type === "step" && j.data)
|
|
156
|
-
lastMsg = String(j.data);
|
|
157
|
-
}
|
|
174
|
+
if (j.type === "step" && j.data) lastMsg = String(j.data);
|
|
158
175
|
if (j.msg) lastMsg = String(j.msg);
|
|
159
|
-
} catch { /* ignore
|
|
176
|
+
} catch { /* ignore */ }
|
|
160
177
|
};
|
|
161
178
|
|
|
162
179
|
const tickRate = (chunkLen) => {
|
|
@@ -174,49 +191,43 @@ async function runWithProgress(cmd, args = [], { cwd, env } = {}, label = "작
|
|
|
174
191
|
tickRate(b.length);
|
|
175
192
|
const s = b.toString("utf8");
|
|
176
193
|
if (isYarn || isPnpm) {
|
|
177
|
-
for (const ln of s.split(/\r?\n/))
|
|
178
|
-
if (!ln.trim()) continue;
|
|
179
|
-
handleJSONLine(ln);
|
|
180
|
-
}
|
|
194
|
+
for (const ln of s.split(/\r?\n/)) if (ln.trim()) handleJSONLine(ln);
|
|
181
195
|
} else {
|
|
182
196
|
lastMsg = s.trim().split(/\r?\n/).pop() || lastMsg;
|
|
183
197
|
}
|
|
184
|
-
|
|
198
|
+
safeWrite(mkLine(lastMsg));
|
|
185
199
|
});
|
|
186
200
|
child.stderr.on("data", (b) => {
|
|
187
201
|
downloadedBytes += b.length;
|
|
188
202
|
tickRate(b.length);
|
|
189
203
|
const s = b.toString("utf8");
|
|
190
204
|
if (isYarn || isPnpm) {
|
|
191
|
-
for (const ln of s.split(/\r?\n/)) {
|
|
192
|
-
if (!ln.trim()) continue;
|
|
193
|
-
if (ln.startsWith("{") && ln.endsWith("}")) handleJSONLine(ln);
|
|
194
|
-
}
|
|
205
|
+
for (const ln of s.split(/\r?\n/)) if (ln.trim() && ln.startsWith("{") && ln.endsWith("}")) handleJSONLine(ln);
|
|
195
206
|
} else {
|
|
196
207
|
lastMsg = s.trim().split(/\r?\n/).pop() || lastMsg;
|
|
197
208
|
}
|
|
198
|
-
|
|
209
|
+
safeWrite(mkLine(lastMsg));
|
|
199
210
|
});
|
|
200
211
|
|
|
201
|
-
|
|
212
|
+
// 주기적 업데이트 (출력 없을 때도 500ms마다만 갱신)
|
|
213
|
+
const iv = setInterval(() => safeWrite(mkLine(lastMsg)), 500);
|
|
202
214
|
|
|
203
215
|
child.on("error", (e) => {
|
|
204
216
|
clearInterval(iv);
|
|
205
|
-
|
|
206
|
-
process.stdout.write("\x1B[?25h"); // 커서 보이기
|
|
217
|
+
finishWrite(false);
|
|
207
218
|
reject(e);
|
|
208
219
|
});
|
|
209
220
|
child.on("close", (code) => {
|
|
210
221
|
clearInterval(iv);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
process.stdout.write("\n\x1B[?25h"); // 커서 보이기
|
|
214
|
-
if (code === 0) resolve();
|
|
215
|
-
else reject(Object.assign(new Error(`${cmd} exited ${code}`), { code }));
|
|
222
|
+
finishWrite(code === 0);
|
|
223
|
+
if (code === 0) resolve(); else reject(Object.assign(new Error(`${cmd} exited ${code}`), { code }));
|
|
216
224
|
});
|
|
217
225
|
});
|
|
226
|
+
|
|
227
|
+
// 커서 복구는 전역 핸들러가 처리
|
|
218
228
|
}
|
|
219
229
|
|
|
230
|
+
|
|
220
231
|
/** 경량 스피너 (applyScaffold 등 파일 생성용) */
|
|
221
232
|
function makeSpinner(text = "") {
|
|
222
233
|
const frames = process.platform === "win32" ? ["-", "\\", "|", "/"] : ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|