create-dev-to 1.3.0 → 1.3.1

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 CHANGED
@@ -11,10 +11,10 @@ import { InstallLogger } from "./installLogger.js";
11
11
  import { displayInstallSummary } from "./visualComponents.js";
12
12
  const PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
13
13
  const __BUILD_INFO__ = {
14
- commit: "519bc16",
14
+ commit: "399e4ab",
15
15
  branch: "main",
16
- buildTime: "2026-01-10 14:05",
17
- version: "1.3.0"
16
+ buildTime: "2026-01-10 14:27",
17
+ version: "1.3.1"
18
18
  };
19
19
  const FRAMEWORKS = [
20
20
  {
@@ -0,0 +1,232 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execSync } from "node:child_process";
4
+ import { InstallRenderer } from "./visualComponents.js";
5
+ import { createParser } from "./outputParsers.js";
6
+ class InstallLogger {
7
+ packageManager;
8
+ parser;
9
+ renderer;
10
+ stats;
11
+ phases;
12
+ renderScheduled = false;
13
+ lastRenderTime = 0;
14
+ RENDER_INTERVAL = 50;
15
+ smoothProgressTimer = null;
16
+ constructor(packageManager) {
17
+ this.packageManager = packageManager;
18
+ this.parser = createParser(packageManager);
19
+ this.renderer = new InstallRenderer();
20
+ this.stats = {
21
+ packagesAdded: 0,
22
+ packagesUpdated: 0,
23
+ packagesRemoved: 0,
24
+ packagesTotal: 0,
25
+ downloadSpeed: 0,
26
+ downloadedBytes: 0,
27
+ nodeModulesSize: 0,
28
+ warnings: []
29
+ };
30
+ this.phases = [
31
+ { name: "resolving", progress: 0, active: true },
32
+ { name: "downloading", progress: 0, active: false },
33
+ { name: "installing", progress: 0, active: false }
34
+ ];
35
+ }
36
+ start() {
37
+ this.stats.startTime = performance.now();
38
+ this.render();
39
+ this.startSmoothProgress();
40
+ }
41
+ processLine(line, stream) {
42
+ if (!line || line.trim() === "") return;
43
+ try {
44
+ const parsed = this.parser.parse(line, stream);
45
+ this.handleParsedOutput(parsed);
46
+ this.scheduleRender();
47
+ } catch (error) {
48
+ if (process.env.DEBUG) {
49
+ console.error("Parse error:", error);
50
+ }
51
+ }
52
+ }
53
+ async finish(projectDir) {
54
+ this.stopSmoothProgress();
55
+ this.stats.endTime = performance.now();
56
+ this.stats.duration = this.stats.endTime - (this.stats.startTime || 0);
57
+ this.updatePhase("installing", 100);
58
+ this.render();
59
+ this.renderer.clear();
60
+ try {
61
+ this.stats.nodeModulesSize = await this.calculateNodeModulesSize(
62
+ projectDir
63
+ );
64
+ } catch {
65
+ this.stats.nodeModulesSize = 0;
66
+ }
67
+ return this.stats;
68
+ }
69
+ error() {
70
+ this.stopSmoothProgress();
71
+ this.renderer.clear();
72
+ }
73
+ startSmoothProgress() {
74
+ this.smoothProgressTimer = setInterval(() => {
75
+ const activePhase = this.phases.find((p) => p.active);
76
+ if (activePhase && activePhase.progress < 95) {
77
+ const increment = Math.max(0.5, (95 - activePhase.progress) / 20);
78
+ activePhase.progress = Math.min(95, activePhase.progress + increment);
79
+ this.render();
80
+ }
81
+ }, 200);
82
+ }
83
+ stopSmoothProgress() {
84
+ if (this.smoothProgressTimer) {
85
+ clearInterval(this.smoothProgressTimer);
86
+ this.smoothProgressTimer = null;
87
+ }
88
+ }
89
+ handleParsedOutput(parsed) {
90
+ switch (parsed.type) {
91
+ case "phase_change":
92
+ if (parsed.phase) {
93
+ this.activatePhase(parsed.phase);
94
+ }
95
+ break;
96
+ case "progress":
97
+ if (parsed.phase && parsed.progress !== void 0) {
98
+ this.updatePhase(parsed.phase, parsed.progress);
99
+ }
100
+ break;
101
+ case "package_info":
102
+ if (parsed.packageCount !== void 0) {
103
+ this.stats.packagesAdded = parsed.packageCount;
104
+ }
105
+ break;
106
+ case "warning":
107
+ if (parsed.message) {
108
+ this.stats.warnings = this.stats.warnings || [];
109
+ this.stats.warnings.push(parsed.message);
110
+ }
111
+ break;
112
+ case "error":
113
+ break;
114
+ case "ignore":
115
+ break;
116
+ }
117
+ }
118
+ activatePhase(phaseName) {
119
+ const phase = this.phases.find((p) => p.name === phaseName);
120
+ if (phase) {
121
+ if (phase.progress >= 100) {
122
+ return;
123
+ }
124
+ this.phases.forEach((p) => p.active = false);
125
+ phase.active = true;
126
+ if (phase.progress === 0) {
127
+ phase.progress = 1;
128
+ }
129
+ const phaseIndex = this.phases.findIndex((p) => p.name === phaseName);
130
+ for (let i = 0; i < phaseIndex; i++) {
131
+ const prevPhase = this.phases[i];
132
+ if (prevPhase.progress < 100) {
133
+ prevPhase.progress = 100;
134
+ }
135
+ prevPhase.active = false;
136
+ }
137
+ }
138
+ }
139
+ updatePhase(phaseName, progress) {
140
+ const phase = this.phases.find((p) => p.name === phaseName);
141
+ if (phase) {
142
+ phase.progress = Math.max(phase.progress, Math.min(100, progress));
143
+ const phaseIndex = this.phases.findIndex((p) => p.name === phaseName);
144
+ for (let i = 0; i < phaseIndex; i++) {
145
+ if (this.phases[i].progress < 100) {
146
+ this.phases[i].progress = 100;
147
+ this.phases[i].active = false;
148
+ }
149
+ }
150
+ if (phase.progress < 100) {
151
+ phase.active = true;
152
+ }
153
+ if (progress >= 100) {
154
+ phase.active = false;
155
+ if (phaseIndex < this.phases.length - 1) {
156
+ const nextPhase = this.phases[phaseIndex + 1];
157
+ if (nextPhase && nextPhase.progress === 0) {
158
+ nextPhase.active = true;
159
+ nextPhase.progress = 1;
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+ scheduleRender() {
166
+ if (this.renderScheduled) return;
167
+ const now = performance.now();
168
+ const timeSinceLastRender = now - this.lastRenderTime;
169
+ if (timeSinceLastRender >= this.RENDER_INTERVAL) {
170
+ this.render();
171
+ this.lastRenderTime = now;
172
+ } else {
173
+ this.renderScheduled = true;
174
+ setTimeout(() => {
175
+ this.render();
176
+ this.renderScheduled = false;
177
+ this.lastRenderTime = performance.now();
178
+ }, this.RENDER_INTERVAL - timeSinceLastRender);
179
+ }
180
+ }
181
+ render() {
182
+ try {
183
+ this.renderer.render(this.phases, this.stats);
184
+ } catch (error) {
185
+ if (process.env.DEBUG) {
186
+ console.error("Render error:", error);
187
+ }
188
+ }
189
+ }
190
+ async calculateNodeModulesSize(projectDir) {
191
+ const nodeModulesPath = path.join(projectDir, "node_modules");
192
+ if (!fs.existsSync(nodeModulesPath)) {
193
+ return 0;
194
+ }
195
+ if (process.platform !== "win32") {
196
+ try {
197
+ const result = execSync(`du -sk "${nodeModulesPath}"`, {
198
+ encoding: "utf-8",
199
+ stdio: ["ignore", "pipe", "ignore"]
200
+ });
201
+ const sizeKB = parseInt(result.split(" ")[0]);
202
+ return sizeKB * 1024;
203
+ } catch {
204
+ }
205
+ }
206
+ return this.getDirectorySize(nodeModulesPath);
207
+ }
208
+ getDirectorySize(dir) {
209
+ let size = 0;
210
+ try {
211
+ const files = fs.readdirSync(dir, { withFileTypes: true });
212
+ for (const file of files) {
213
+ const filePath = path.join(dir, file.name);
214
+ try {
215
+ if (file.isDirectory()) {
216
+ size += this.getDirectorySize(filePath);
217
+ } else {
218
+ const stats = fs.statSync(filePath);
219
+ size += stats.size;
220
+ }
221
+ } catch {
222
+ continue;
223
+ }
224
+ }
225
+ } catch {
226
+ }
227
+ return size;
228
+ }
229
+ }
230
+ export {
231
+ InstallLogger
232
+ };
@@ -0,0 +1,237 @@
1
+ class OutputParser {
2
+ }
3
+ class PnpmParser extends OutputParser {
4
+ totalPackages = 0;
5
+ maxResolved = 0;
6
+ lastPhase = "resolving";
7
+ parse(line, stream) {
8
+ if (line.includes("Lockfile is up to date") || line.includes("Already up to date")) {
9
+ return {
10
+ type: "progress",
11
+ phase: "installing",
12
+ progress: 100,
13
+ packageCount: 0
14
+ };
15
+ }
16
+ const progressMatch = line.match(
17
+ /Progress: resolved (\d+)(?:, reused (\d+))?(?:, downloaded (\d+))?(?:, added (\d+))?/
18
+ );
19
+ if (progressMatch) {
20
+ const resolved = parseInt(progressMatch[1]);
21
+ const reused = parseInt(progressMatch[2] || "0");
22
+ const downloaded = parseInt(progressMatch[3] || "0");
23
+ const added = parseInt(progressMatch[4] || "0");
24
+ this.maxResolved = Math.max(this.maxResolved, resolved);
25
+ if (added > 0 && added >= resolved) {
26
+ this.lastPhase = "installing";
27
+ return {
28
+ type: "progress",
29
+ phase: "installing",
30
+ packageCount: added,
31
+ progress: 100
32
+ };
33
+ } else if (added > 0) {
34
+ this.lastPhase = "installing";
35
+ const installProgress = Math.min(99, Math.max(10, added / resolved * 100));
36
+ return {
37
+ type: "progress",
38
+ phase: "installing",
39
+ progress: installProgress
40
+ };
41
+ } else if (downloaded > 0 || reused > 0) {
42
+ const total = downloaded + reused;
43
+ if (this.lastPhase !== "downloading") {
44
+ this.lastPhase = "downloading";
45
+ }
46
+ const downloadProgress = Math.min(99, Math.max(10, total / resolved * 100));
47
+ return {
48
+ type: "progress",
49
+ phase: "downloading",
50
+ progress: downloadProgress
51
+ };
52
+ } else if (resolved > 0) {
53
+ if (this.lastPhase !== "resolving") {
54
+ this.lastPhase = "resolving";
55
+ }
56
+ const resolveProgress = Math.min(95, Math.max(10, resolved / Math.max(this.maxResolved, 200) * 100));
57
+ return {
58
+ type: "progress",
59
+ phase: "resolving",
60
+ progress: resolveProgress
61
+ };
62
+ }
63
+ }
64
+ const packagesMatch = line.match(/Packages:\s*\+(\d+)/);
65
+ if (packagesMatch) {
66
+ const count = parseInt(packagesMatch[1]);
67
+ this.totalPackages = count;
68
+ return {
69
+ type: "package_info",
70
+ packageCount: count
71
+ };
72
+ }
73
+ if (line.includes("warn") && stream === "stderr") {
74
+ return {
75
+ type: "warning",
76
+ message: line
77
+ };
78
+ }
79
+ if ((line.includes("error") || line.includes("Error")) && stream === "stderr") {
80
+ return {
81
+ type: "error",
82
+ message: line
83
+ };
84
+ }
85
+ return { type: "ignore" };
86
+ }
87
+ }
88
+ class NpmParser extends OutputParser {
89
+ isInstalling = false;
90
+ parse(line, stream) {
91
+ const completionMatch = line.match(
92
+ /added (\d+) packages?(?:, (?:and|)?(?:audited|updated) (\d+) packages)?/
93
+ );
94
+ if (completionMatch) {
95
+ this.isInstalling = true;
96
+ return {
97
+ type: "progress",
98
+ packageCount: parseInt(completionMatch[1]),
99
+ progress: 100,
100
+ phase: "installing"
101
+ };
102
+ }
103
+ if (line.includes("up to date")) {
104
+ this.isInstalling = true;
105
+ return {
106
+ type: "progress",
107
+ packageCount: 0,
108
+ progress: 100,
109
+ phase: "installing"
110
+ };
111
+ }
112
+ const progressMatch = line.match(/\|(.+?)\|/);
113
+ if (progressMatch) {
114
+ const progress = progressMatch[1].length / 50 * 100;
115
+ return {
116
+ type: "progress",
117
+ progress: Math.min(95, Math.max(1, progress)),
118
+ phase: "downloading"
119
+ };
120
+ }
121
+ if (line.includes("vulnerabilities") || line.includes("vulnerability")) {
122
+ return {
123
+ type: "warning",
124
+ message: line
125
+ };
126
+ }
127
+ if (line.includes("ERR!") || line.includes("error") && stream === "stderr") {
128
+ return {
129
+ type: "error",
130
+ message: line
131
+ };
132
+ }
133
+ return { type: "ignore" };
134
+ }
135
+ }
136
+ class YarnParser extends OutputParser {
137
+ parse(line, stream) {
138
+ const phaseMatch = line.match(/\[(\d+)\/4\]\s+(.+?)\.{3}/);
139
+ if (phaseMatch) {
140
+ const stepNum = parseInt(phaseMatch[1]);
141
+ const phaseNames = [
142
+ "resolving",
143
+ "fetching",
144
+ "linking",
145
+ "building"
146
+ ];
147
+ const phase = phaseNames[stepNum - 1] || "resolving";
148
+ const mappedPhase = phase === "fetching" ? "downloading" : phase;
149
+ return {
150
+ type: "phase_change",
151
+ phase: mappedPhase
152
+ };
153
+ }
154
+ const successMatch = line.match(/Done in ([\d.]+)s/);
155
+ if (successMatch) {
156
+ return {
157
+ type: "progress",
158
+ progress: 100,
159
+ phase: "installing"
160
+ };
161
+ }
162
+ const addedMatch = line.match(/(?:Yarn )?added (\d+) packages?/);
163
+ if (addedMatch) {
164
+ return {
165
+ type: "package_info",
166
+ packageCount: parseInt(addedMatch[1])
167
+ };
168
+ }
169
+ if (line.includes("warning") && stream === "stderr") {
170
+ return {
171
+ type: "warning",
172
+ message: line
173
+ };
174
+ }
175
+ if (line.includes("error") && stream === "stderr") {
176
+ return {
177
+ type: "error",
178
+ message: line
179
+ };
180
+ }
181
+ return { type: "ignore" };
182
+ }
183
+ }
184
+ class BunParser extends OutputParser {
185
+ parse(line, stream) {
186
+ const completionMatch = line.match(/^\s*\+\s*(\d+)\s+packages installed\s*\[([\d.]+)s\]/);
187
+ if (completionMatch) {
188
+ return {
189
+ type: "progress",
190
+ packageCount: parseInt(completionMatch[1]),
191
+ progress: 100,
192
+ phase: "installing"
193
+ };
194
+ }
195
+ const alreadyMatch = line.match(/^\s*(\d+)\s+packages already installed/);
196
+ if (alreadyMatch) {
197
+ return {
198
+ type: "progress",
199
+ packageCount: 0,
200
+ progress: 100,
201
+ phase: "installing"
202
+ };
203
+ }
204
+ if (line.includes("Downloading") || line.includes("downloading")) {
205
+ return { type: "phase_change", phase: "downloading" };
206
+ }
207
+ if (line.includes("error") || line.includes("Error") || line.includes("failed") && stream === "stderr") {
208
+ return {
209
+ type: "error",
210
+ message: line
211
+ };
212
+ }
213
+ return { type: "ignore" };
214
+ }
215
+ }
216
+ function createParser(pm) {
217
+ switch (pm) {
218
+ case "pnpm":
219
+ return new PnpmParser();
220
+ case "npm":
221
+ return new NpmParser();
222
+ case "yarn":
223
+ return new YarnParser();
224
+ case "bun":
225
+ return new BunParser();
226
+ default:
227
+ return new NpmParser();
228
+ }
229
+ }
230
+ export {
231
+ BunParser,
232
+ NpmParser,
233
+ OutputParser,
234
+ PnpmParser,
235
+ YarnParser,
236
+ createParser
237
+ };
@@ -0,0 +1,251 @@
1
+ import { green, yellow, red, dim, bold } from "kolorist";
2
+ const ANSI = {
3
+ cursorUp: (n) => `\x1B[${n}A`,
4
+ cursorDown: (n) => `\x1B[${n}B`,
5
+ cursorTo: (x, y) => y !== void 0 ? `\x1B[${y};${x}H` : `\x1B[${x}G`,
6
+ clearLine: () => "\x1B[2K",
7
+ clearScreen: () => "\x1B[2J",
8
+ hideCursor: () => "\x1B[?25l",
9
+ showCursor: () => "\x1B[?25h"
10
+ };
11
+ function trueColor(r, g, b, text) {
12
+ return `\x1B[38;2;${r};${g};${b}m${text}\x1B[0m`;
13
+ }
14
+ function createGradient(text, startColor, endColor) {
15
+ const chars = text.split("");
16
+ const length = chars.length;
17
+ if (length === 0) return text;
18
+ if (length === 1) return trueColor(startColor.r, startColor.g, startColor.b, text);
19
+ let result = "";
20
+ for (let i = 0; i < length; i++) {
21
+ const ratio = i / (length - 1);
22
+ const r = Math.round(startColor.r + (endColor.r - startColor.r) * ratio);
23
+ const g = Math.round(startColor.g + (endColor.g - startColor.g) * ratio);
24
+ const b = Math.round(startColor.b + (endColor.b - startColor.b) * ratio);
25
+ result += trueColor(r, g, b, chars[i]);
26
+ }
27
+ return result;
28
+ }
29
+ function renderProgressBar(options) {
30
+ const { width, progress, label, gradient, colors, showPercentage } = options;
31
+ const filledWidth = Math.floor(width * progress / 100);
32
+ const emptyWidth = width - filledWidth;
33
+ let bar = "";
34
+ if (gradient && colors && colors.length > 0) {
35
+ for (let i = 0; i < filledWidth; i++) {
36
+ const ratio = filledWidth > 1 ? i / (filledWidth - 1) : 0;
37
+ const colorIndex = Math.min(
38
+ Math.floor(ratio * (colors.length - 1)),
39
+ colors.length - 1
40
+ );
41
+ const nextColorIndex = Math.min(colorIndex + 1, colors.length - 1);
42
+ const color = colors[colorIndex];
43
+ const nextColor = colors[nextColorIndex];
44
+ const localRatio = ratio * (colors.length - 1) - Math.floor(ratio * (colors.length - 1));
45
+ const r = Math.round(color.r + (nextColor.r - color.r) * localRatio);
46
+ const g = Math.round(color.g + (nextColor.g - color.g) * localRatio);
47
+ const b = Math.round(color.b + (nextColor.b - color.b) * localRatio);
48
+ bar += trueColor(r, g, b, "\u2593");
49
+ }
50
+ } else {
51
+ bar = "\u2593".repeat(filledWidth);
52
+ }
53
+ bar += dim("\u2591".repeat(emptyWidth));
54
+ const percentage = showPercentage ? ` ${Math.round(progress)}%` : "";
55
+ return `${bar} ${label}${percentage}`;
56
+ }
57
+ function getPhaseIcon(phase) {
58
+ const icons = {
59
+ resolving: "\u{1F50D}",
60
+ downloading: "\u2B07\uFE0F",
61
+ installing: "\u{1F4E6}",
62
+ building: "\u{1F528}"
63
+ };
64
+ return icons[phase] || "\u25CF";
65
+ }
66
+ function getPhaseColors(phase) {
67
+ const colorSets = {
68
+ resolving: [
69
+ { r: 59, g: 130, b: 246 },
70
+ { r: 37, g: 99, b: 235 }
71
+ ],
72
+ downloading: [
73
+ { r: 34, g: 197, b: 94 },
74
+ { r: 22, g: 163, b: 74 }
75
+ ],
76
+ installing: [
77
+ { r: 168, g: 85, b: 247 },
78
+ { r: 147, g: 51, b: 234 }
79
+ ],
80
+ building: [
81
+ { r: 251, g: 146, b: 60 },
82
+ { r: 249, g: 115, b: 22 }
83
+ ]
84
+ };
85
+ return colorSets[phase] || [{ r: 156, g: 163, b: 175 }];
86
+ }
87
+ function formatSpeed(bytesPerSecond) {
88
+ if (bytesPerSecond < 1024) return `${bytesPerSecond.toFixed(0)} B/s`;
89
+ if (bytesPerSecond < 1024 * 1024)
90
+ return `${(bytesPerSecond / 1024).toFixed(1)} KB/s`;
91
+ return `${(bytesPerSecond / 1024 / 1024).toFixed(1)} MB/s`;
92
+ }
93
+ function formatSize(bytes) {
94
+ if (bytes < 1024) return `${bytes} B`;
95
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
96
+ if (bytes < 1024 * 1024 * 1024)
97
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
98
+ return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
99
+ }
100
+ function formatDuration(ms) {
101
+ const seconds = ms / 1e3;
102
+ if (seconds < 60) return `${seconds.toFixed(1)}s`;
103
+ const minutes = Math.floor(seconds / 60);
104
+ const remainingSeconds = Math.round(seconds % 60);
105
+ return `${minutes}m ${remainingSeconds}s`;
106
+ }
107
+ function detectColorSupport() {
108
+ if (process.env.NO_COLOR || process.env.TERM === "dumb") {
109
+ return false;
110
+ }
111
+ return true;
112
+ }
113
+ class InstallRenderer {
114
+ isTTY;
115
+ supportsColor;
116
+ lineCount = 0;
117
+ constructor() {
118
+ this.isTTY = process.stdout.isTTY ?? false;
119
+ this.supportsColor = detectColorSupport();
120
+ }
121
+ render(phases, stats) {
122
+ if (!this.isTTY) {
123
+ this.renderSimple(phases);
124
+ return;
125
+ }
126
+ const lines = this.buildEnhancedUI(phases, stats);
127
+ this.updateTerminal(lines);
128
+ }
129
+ buildEnhancedUI(phases, stats) {
130
+ const lines = [];
131
+ lines.push("");
132
+ if (this.supportsColor) {
133
+ const title = createGradient(
134
+ "\u25C6 Installing Dependencies",
135
+ { r: 99, g: 102, b: 241 },
136
+ { r: 236, g: 72, b: 153 }
137
+ );
138
+ lines.push(bold(title));
139
+ } else {
140
+ lines.push(bold("\u25C6 Installing Dependencies"));
141
+ }
142
+ lines.push("");
143
+ phases.forEach((phase) => {
144
+ const icon = getPhaseIcon(phase.name);
145
+ const bar = renderProgressBar({
146
+ width: 40,
147
+ progress: phase.progress,
148
+ label: this.getPhaseLabel(phase.name),
149
+ gradient: this.supportsColor,
150
+ colors: getPhaseColors(phase.name),
151
+ showPercentage: true
152
+ });
153
+ lines.push(`${icon} ${bar}`);
154
+ });
155
+ lines.push("");
156
+ const statsLine = this.buildStatsLine(stats);
157
+ if (statsLine) {
158
+ lines.push(dim(statsLine));
159
+ lines.push("");
160
+ }
161
+ return lines;
162
+ }
163
+ getPhaseLabel(phase) {
164
+ const labels = {
165
+ resolving: "Resolving packages ",
166
+ downloading: "Downloading ",
167
+ installing: "Installing ",
168
+ building: "Building "
169
+ };
170
+ return labels[phase] || phase;
171
+ }
172
+ buildStatsLine(stats) {
173
+ const parts = [];
174
+ if (stats.packagesAdded) {
175
+ parts.push(`Packages: ${green(`+${stats.packagesAdded}`)}`);
176
+ }
177
+ if (stats.packagesUpdated) {
178
+ parts.push(`Updated: ${yellow(stats.packagesUpdated.toString())}`);
179
+ }
180
+ if (stats.packagesRemoved) {
181
+ parts.push(`Removed: ${red(stats.packagesRemoved.toString())}`);
182
+ }
183
+ if (stats.startTime) {
184
+ const duration = performance.now() - stats.startTime;
185
+ parts.push(`Time: ${formatDuration(duration)}`);
186
+ }
187
+ if (stats.downloadSpeed && stats.downloadSpeed > 0) {
188
+ parts.push(`Speed: ${formatSpeed(stats.downloadSpeed)}`);
189
+ }
190
+ return parts.join(" ");
191
+ }
192
+ updateTerminal(lines) {
193
+ if (this.lineCount > 0) {
194
+ for (let i = 0; i < this.lineCount; i++) {
195
+ process.stdout.write(ANSI.cursorUp(1) + ANSI.clearLine());
196
+ }
197
+ }
198
+ lines.forEach((line) => process.stdout.write(line + "\n"));
199
+ this.lineCount = lines.length;
200
+ }
201
+ renderSimple(phases) {
202
+ const activePhase = phases.find((p) => p.active);
203
+ if (activePhase) {
204
+ const label = this.getPhaseLabel(activePhase.name).trim();
205
+ console.log(
206
+ `[${label.toUpperCase()}] ${Math.round(activePhase.progress)}%`
207
+ );
208
+ }
209
+ }
210
+ clear() {
211
+ if (this.isTTY && this.lineCount > 0) {
212
+ for (let i = 0; i < this.lineCount; i++) {
213
+ process.stdout.write(ANSI.cursorUp(1) + ANSI.clearLine());
214
+ }
215
+ this.lineCount = 0;
216
+ }
217
+ }
218
+ }
219
+ function displayInstallSummary(stats) {
220
+ const lines = [];
221
+ lines.push("");
222
+ lines.push(green("\u2728 Installation Complete!"));
223
+ lines.push("");
224
+ const pkgStats = [];
225
+ if (stats.packagesAdded > 0)
226
+ pkgStats.push(`${green(`+${stats.packagesAdded}`)} added`);
227
+ if (stats.packagesUpdated > 0)
228
+ pkgStats.push(`${yellow(`~${stats.packagesUpdated}`)} updated`);
229
+ if (stats.packagesRemoved > 0)
230
+ pkgStats.push(`${red(`-${stats.packagesRemoved}`)} removed`);
231
+ if (pkgStats.length > 0) {
232
+ lines.push(` ${bold("Packages:")} ${pkgStats.join(", ")}`);
233
+ }
234
+ lines.push(` ${bold("Duration:")} ${formatDuration(stats.duration)}`);
235
+ if (stats.downloadSpeed > 0) {
236
+ lines.push(` ${bold("Avg Speed:")} ${formatSpeed(stats.downloadSpeed)}`);
237
+ }
238
+ if (stats.nodeModulesSize > 0) {
239
+ lines.push(` ${bold("Disk Usage:")} ${formatSize(stats.nodeModulesSize)}`);
240
+ }
241
+ if (stats.warnings.length > 0) {
242
+ lines.push("");
243
+ lines.push(yellow(` \u26A0\uFE0F ${stats.warnings.length} warning(s)`));
244
+ }
245
+ lines.push("");
246
+ lines.forEach((line) => console.log(line));
247
+ }
248
+ export {
249
+ InstallRenderer,
250
+ displayInstallSummary
251
+ };
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "create-dev-to",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "private": false,
5
5
  "type": "module",
6
+ "engines": {
7
+ "node": ">=18.0.0"
8
+ },
6
9
  "bin": {
7
10
  "create-dev-to": "./dist/index.js"
8
11
  },