UNPKG

7.79 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21var __importDefault = (this && this.__importDefault) || function (mod) {
22 return (mod && mod.__esModule) ? mod : { "default": mod };
23};
24Object.defineProperty(exports, "__esModule", { value: true });
25exports.paintDashboard = exports.getServerInfoMessage = exports.getPort = exports.paintEvent = void 0;
26const detect_port_1 = __importDefault(require("detect-port"));
27const colors = __importStar(require("kleur/colors"));
28const path_1 = __importDefault(require("path"));
29const readline_1 = __importDefault(require("readline"));
30const logger_1 = require("../logger");
31const IS_FILE_CHANGED_MESSAGE = /File changed\.\.\./;
32/** Convert a logger's history into the proper dev console format. */
33function summarizeHistory(history) {
34 // Note: history array can get long over time. Performance matters here!
35 return history.reduce((historyString, record) => {
36 let line;
37 // We want to summarize common repeat "file changed" events to reduce noise.
38 // All other logs should be included verbatim, with all repeats added.
39 if (record.count === 1) {
40 line = record.val;
41 }
42 else if (IS_FILE_CHANGED_MESSAGE.test(record.val)) {
43 line = record.val + colors.green(` [x${record.count}]`);
44 }
45 else {
46 line = Array(record.count).fill(record.val).join('\n');
47 }
48 // Note: this includes an extra '\n' character at the start.
49 // Fine for our use-case, but be aware.
50 return historyString + '\n' + line;
51 }, '');
52}
53exports.paintEvent = {
54 BUILD_FILE: 'BUILD_FILE',
55 LOAD_ERROR: 'LOAD_ERROR',
56 SERVER_START: 'SERVER_START',
57 WORKER_COMPLETE: 'WORKER_COMPLETE',
58 WORKER_MSG: 'WORKER_MSG',
59 WORKER_RESET: 'WORKER_RESET',
60};
61/**
62 * Get the actual port, based on the `defaultPort`.
63 * If the default port was not available, then we'll prompt the user if its okay
64 * to use the next available port.
65 */
66async function getPort(defaultPort) {
67 const bestAvailablePort = await detect_port_1.default(defaultPort);
68 if (defaultPort !== bestAvailablePort) {
69 let useNextPort = false;
70 if (process.stdout.isTTY) {
71 const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
72 useNextPort = await new Promise((resolve) => {
73 rl.question(colors.yellow(`! Port ${colors.bold(defaultPort)} not available. Run on port ${colors.bold(bestAvailablePort)} instead? (Y/n) `), (answer) => {
74 resolve(!/^no?$/i.test(answer));
75 });
76 });
77 rl.close();
78 }
79 if (!useNextPort) {
80 logger_1.logger.error(`✘ Port ${colors.bold(defaultPort)} not available. Use ${colors.bold('--port')} to specify a different port.`);
81 process.exit(1);
82 }
83 }
84 return bestAvailablePort;
85}
86exports.getPort = getPort;
87function getServerInfoMessage({ startTimeMs, port, protocol, hostname, remoteIp }, isBuilding = false) {
88 let output = '';
89 const isServerStarted = startTimeMs > 0 && port > 0 && protocol;
90 if (isServerStarted) {
91 output += ` ${colors.bold(colors.cyan(`${protocol}//${hostname}:${port}`))}`;
92 if (remoteIp) {
93 output += `${colors.cyan(` • `)}${colors.bold(colors.cyan(`${protocol}//${remoteIp}:${port}`))}`;
94 }
95 output += '\n';
96 output += colors.dim(
97 // Not to hide slow startup times, but likely there were extraneous factors (prompts, etc.) where the speed isn’t accurate
98 startTimeMs < 1000 ? ` Server started in ${startTimeMs}ms.` : ` Server started.`);
99 if (isBuilding) {
100 output += colors.dim(` Building...`);
101 }
102 output += '\n\n';
103 }
104 else {
105 output += colors.dim(` Server starting…`) + '\n\n';
106 }
107 return output;
108}
109exports.getServerInfoMessage = getServerInfoMessage;
110const WORKER_BASE_STATE = { done: false, error: null, output: '' };
111function paintDashboard(bus, config) {
112 let serverInfo;
113 const allWorkerStates = {};
114 const allFileBuilds = new Set();
115 for (const plugin of config.plugins.map((p) => p.name)) {
116 allWorkerStates[plugin] = { ...WORKER_BASE_STATE };
117 }
118 function setupWorker(id) {
119 if (!allWorkerStates[id]) {
120 allWorkerStates[id] = { ...WORKER_BASE_STATE };
121 }
122 }
123 function repaint() {
124 // Clear Page
125 process.stdout.write(process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H');
126 // Header
127 process.stdout.write(`${colors.bold(`snowpack`)}\n\n`);
128 // Server Stats
129 serverInfo && process.stdout.write(getServerInfoMessage(serverInfo, allFileBuilds.size > 0));
130 // Console Output
131 const history = logger_1.logger.getHistory();
132 if (history.length) {
133 process.stdout.write(`${colors.underline(colors.bold('▼ Console'))}\n`);
134 process.stdout.write(summarizeHistory(history));
135 process.stdout.write('\n\n');
136 }
137 // Worker Dashboards
138 for (const [script, workerState] of Object.entries(allWorkerStates)) {
139 if (!workerState.output) {
140 continue;
141 }
142 const colorsFn = Array.isArray(workerState.error) ? colors.red : colors.reset;
143 process.stdout.write(`${colorsFn(colors.underline(colors.bold('▼ ' + script)))}\n\n`);
144 process.stdout.write(' ' + workerState.output.trim().replace(/\n/gm, '\n '));
145 process.stdout.write('\n\n');
146 }
147 }
148 bus.on(exports.paintEvent.BUILD_FILE, ({ id, isBuilding }) => {
149 if (isBuilding) {
150 allFileBuilds.add(path_1.default.relative(config.root, id));
151 }
152 else {
153 allFileBuilds.delete(path_1.default.relative(config.root, id));
154 }
155 repaint();
156 });
157 bus.on(exports.paintEvent.WORKER_MSG, ({ id, msg }) => {
158 setupWorker(id);
159 allWorkerStates[id].output += msg;
160 repaint();
161 });
162 bus.on(exports.paintEvent.WORKER_COMPLETE, ({ id, error }) => {
163 allWorkerStates[id].done = true;
164 allWorkerStates[id].error = allWorkerStates[id].error || error;
165 repaint();
166 });
167 bus.on(exports.paintEvent.WORKER_RESET, ({ id }) => {
168 allWorkerStates[id] = { ...WORKER_BASE_STATE };
169 repaint();
170 });
171 bus.on(exports.paintEvent.SERVER_START, (info) => {
172 serverInfo = info;
173 repaint();
174 });
175 // replace logging behavior with repaint (note: messages are retrieved later, with logger.getHistory())
176 logger_1.logger.on('debug', () => {
177 repaint();
178 });
179 logger_1.logger.on('info', () => {
180 repaint();
181 });
182 logger_1.logger.on('warn', () => {
183 repaint();
184 });
185 logger_1.logger.on('error', () => {
186 repaint();
187 });
188 repaint();
189}
190exports.paintDashboard = paintDashboard;