grab-url 1.0.7 → 1.0.8

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.
@@ -12,12 +12,13 @@
12
12
  */
13
13
  export function log(message: string|object = "", options: LogOptions = {}) {
14
14
  let {
15
- color = null,
16
- style = "color: #66ccff; font-size: 10pt;",
15
+ color,
16
+ style = "color:rgb(54, 165, 220); font-size: 10pt;",
17
17
  hideInProduction = undefined,
18
18
  startSpinner = false,
19
19
  stopSpinner = false,
20
20
  } = options;
21
+ const colors = getColors();
21
22
 
22
23
  // Auto-detect if we should hide logs in production based on hostname
23
24
  if (typeof hideInProduction === "undefined")
@@ -30,9 +31,17 @@ export function log(message: string|object = "", options: LogOptions = {}) {
30
31
  message =
31
32
  printJSONStructure(message) + "\n\n" + JSON.stringify(message, null, 2);
32
33
 
33
- //colorize in terminal (%c is only in browser)
34
+ // change color: [red] to color: red if only one
35
+ if (Array.isArray(color) && color.length == 1) color = color[0];
36
+
37
+ //colorize in terminal (%c is only in browser but we polyfill it)
34
38
  if (color && typeof process !== undefined)
35
- message = (colors[color] || "") + message + colors.reset;
39
+ if (message.includes("%c") && Array.isArray(color)) // replace each c with color[i]
40
+ message = message.replace(/%c/g, (match, index) => colors[color[index]] || "");
41
+ else if (color && typeof color === "string")
42
+ message = (colors[color] || "") + message + colors.reset;
43
+
44
+
36
45
 
37
46
  // Displays an animated spinner in the terminal with the provided text.
38
47
  var i = 0;
@@ -40,7 +49,7 @@ export function log(message: string|object = "", options: LogOptions = {}) {
40
49
  if (startSpinner)
41
50
  (global || globalThis).interval = setInterval(() => {
42
51
  process.stdout.write(
43
- (colors[color] || "") +
52
+ (Array.isArray(color) ? colors[color[0]] : colors[color] || "") +
44
53
  "\r" +
45
54
  "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏".split("")[(i = ++i % 10)] +
46
55
  " " +
@@ -51,7 +60,7 @@ export function log(message: string|object = "", options: LogOptions = {}) {
51
60
  else if (stopSpinner) {
52
61
  clearInterval((global || globalThis).interval);
53
62
  process.stdout.write(
54
- "\r" + (message || " Done") + " ".repeat(message.length + 20) + "\n"
63
+ "\r" + (message || " ") + " ".repeat(message.length + 20) + "\n"
55
64
  );
56
65
  } else if (typeof style === "string") {
57
66
  // check if style is a one word color code or named color
@@ -76,7 +85,7 @@ export interface LogOptions {
76
85
  /** CSS style string or array of CSS strings for browser console styling */
77
86
  style?: string | string[];
78
87
  /** Optional color name or code for terminal environments */
79
- color?: keyof typeof colors | null;
88
+ color?: ColorName | ColorName[] | string | string[] ;
80
89
  /** If true, hides log in production (auto-detects by hostname if undefined) */
81
90
  hideInProduction?: boolean;
82
91
  /** Start a spinner (for CLI tools, optional) */
@@ -85,42 +94,109 @@ export interface LogOptions {
85
94
  stopSpinner?: boolean;
86
95
  }
87
96
 
88
- // ANSI escape codes for terminal colors when running in Node.js
89
- export const colors = {
90
- reset: "\x1b[0m", // Reset to default color
91
- black: "\x1b[30m",
92
- red: "\x1b[31m", // Functions, errors
93
- green: "\x1b[32m", // Object braces, success
94
- yellow: "\x1b[33m", // Strings, warnings
95
- blue: "\x1b[34m", // Array brackets, info
96
- magenta: "\x1b[35m", // Booleans
97
- cyan: "\x1b[36m", // Numbers
98
- white: "\x1b[37m", // Default color, plain text
99
- gray: "\x1b[90m", // Null, undefined, subtle
100
- // Bright variants
101
- brightRed: "\x1b[91m",
102
- brightGreen: "\x1b[92m",
103
- brightYellow: "\x1b[93m",
104
- brightBlue: "\x1b[94m",
105
- brightMagenta: "\x1b[95m",
106
- brightCyan: "\x1b[96m",
107
- brightWhite: "\x1b[97m",
108
- // Background colors (optional)
109
- bgRed: "\x1b[41m",
110
- bgGreen: "\x1b[42m",
111
- bgYellow: "\x1b[43m",
112
- bgBlue: "\x1b[44m",
113
- bgMagenta: "\x1b[45m",
114
- bgCyan: "\x1b[46m",
115
- bgWhite: "\x1b[47m",
116
- bgGray: "\x1b[100m",
97
+ /**
98
+ * Available color names
99
+ */
100
+ export enum ColorName {
101
+ RESET = 'reset',
102
+ BLACK = 'black',
103
+ RED = 'red',
104
+ GREEN = 'green',
105
+ YELLOW = 'yellow',
106
+ BLUE = 'blue',
107
+ MAGENTA = 'magenta',
108
+ CYAN = 'cyan',
109
+ WHITE = 'white',
110
+ GRAY = 'gray',
111
+ BRIGHT_RED = 'brightRed',
112
+ BRIGHT_GREEN = 'brightGreen',
113
+ BRIGHT_YELLOW = 'brightYellow',
114
+ BRIGHT_BLUE = 'brightBlue',
115
+ BRIGHT_MAGENTA = 'brightMagenta',
116
+ BRIGHT_CYAN = 'brightCyan',
117
+ BRIGHT_WHITE = 'brightWhite',
118
+ BG_RED = 'bgRed',
119
+ BG_GREEN = 'bgGreen',
120
+ BG_YELLOW = 'bgYellow',
121
+ BG_BLUE = 'bgBlue',
122
+ BG_MAGENTA = 'bgMagenta',
123
+ BG_CYAN = 'bgCyan',
124
+ BG_WHITE = 'bgWhite',
125
+ BG_GRAY = 'bgGray',
126
+ BG_BLACK = 'bgBlack',
127
+ BG_BRIGHT_RED = 'bgBrightRed',
128
+ BG_BRIGHT_GREEN = 'bgBrightGreen',
129
+ BG_BRIGHT_YELLOW = 'bgBrightYellow',
130
+ BG_BRIGHT_BLUE = 'bgBrightBlue',
131
+ BG_BRIGHT_MAGENTA = 'bgBrightMagenta',
132
+ BG_BRIGHT_CYAN = 'bgBrightCyan',
133
+ BG_BRIGHT_WHITE = 'bgBrightWhite',
134
+ }
135
+
136
+ /**
137
+ * Color mapping with ANSI codes and HTML hex values
138
+ * @type {Record<ColorName, [number, string]>}
139
+ * @description Maps color names to [ansiCode, hexValue] pairs
140
+ * - ansiCode: ANSI escape sequence number for terminal colors
141
+ * - hexValue: Hex color value (without #) for HTML/CSS
142
+ */
143
+ const colorMap: Record<ColorName, [number, string]> = {
144
+ [ColorName.RESET]: [0, '000000'],
145
+ [ColorName.BLACK]: [30, '000000'],
146
+ [ColorName.RED]: [31, 'ff0000'],
147
+ [ColorName.GREEN]: [32, '00ff00'],
148
+ [ColorName.YELLOW]: [33, 'ffff00'],
149
+ [ColorName.BLUE]: [34, '0000ff'],
150
+ [ColorName.MAGENTA]: [35, 'ff00ff'],
151
+ [ColorName.CYAN]: [36, '00ffff'],
152
+ [ColorName.WHITE]: [37, 'ffffff'],
153
+ [ColorName.GRAY]: [90, '808080'],
154
+ [ColorName.BRIGHT_RED]: [91, 'ff5555'],
155
+ [ColorName.BRIGHT_GREEN]: [92, '55ff55'],
156
+ [ColorName.BRIGHT_YELLOW]: [93, 'ffff55'],
157
+ [ColorName.BRIGHT_BLUE]: [94, '5555ff'],
158
+ [ColorName.BRIGHT_MAGENTA]: [95, 'ff55ff'],
159
+ [ColorName.BRIGHT_CYAN]: [96, '55ffff'],
160
+ [ColorName.BRIGHT_WHITE]: [97, 'ffffff'],
161
+ [ColorName.BG_BLACK]: [40, '000000'],
162
+ [ColorName.BG_RED]: [41, 'ff0000'],
163
+ [ColorName.BG_GREEN]: [42, '00ff00'],
164
+ [ColorName.BG_YELLOW]: [43, 'ffff00'],
165
+ [ColorName.BG_BLUE]: [44, '0000ff'],
166
+ [ColorName.BG_MAGENTA]: [45, 'ff00ff'],
167
+ [ColorName.BG_CYAN]: [46, '00ffff'],
168
+ [ColorName.BG_WHITE]: [47, 'ffffff'],
169
+ [ColorName.BG_GRAY]: [100, '808080'],
170
+ [ColorName.BG_BRIGHT_RED]: [101, 'ff8888'],
171
+ [ColorName.BG_BRIGHT_GREEN]: [102, '88ff88'],
172
+ [ColorName.BG_BRIGHT_YELLOW]: [103, 'ffff88'],
173
+ [ColorName.BG_BRIGHT_BLUE]: [104, '8888ff'],
174
+ [ColorName.BG_BRIGHT_MAGENTA]: [105, 'ff88ff'],
175
+ [ColorName.BG_BRIGHT_CYAN]: [106, '88ffff'],
176
+ [ColorName.BG_BRIGHT_WHITE]: [107, 'ffffff'],
117
177
  };
118
178
 
179
+ /**
180
+ * Returns color codes based on the specified format
181
+ * @param format - Output format for colors
182
+ * - 'ansi': Returns ANSI escape codes (e.g., '\x1b[31m')
183
+ * - 'html': Returns HTML hex colors (e.g., '#ff0000')
184
+ * @returns Object with color names as keys and color codes as values
185
+ */
186
+ export function getColors(format: 'html' | 'ansi' = 'ansi'): Record<ColorName, string> {
187
+ const colors: Record<ColorName, string> = {} as Record<ColorName, string>;
188
+ for (const [name, [ansiCode, hexCode]] of Object.entries(colorMap)) {
189
+ colors[name] = format === 'html' ? '#' + hexCode : '\x1b[' + ansiCode + 'm';
190
+ }
191
+ return colors;
192
+ }
193
+
119
194
  /**
120
195
  * Determines the appropriate color code for a given value type
121
196
  * Used for consistent color coding in the structure visualization
122
197
  */
123
198
  function getColorForType(value) {
199
+ const colors = getColors();
124
200
  if (typeof value === "string") return colors.yellow;
125
201
  if (typeof value === "number") return colors.cyan;
126
202
  if (typeof value === "boolean") return colors.magenta;
@@ -153,20 +229,30 @@ function getTypeString(value) {
153
229
  * Creates a colored visualization of a JSON object's structure
154
230
  * Shows the shape and types of the data rather than actual values
155
231
  * Recursively processes nested objects and arrays
232
+ * @param {object} obj - The JSON object to visualize
233
+ * @param {number} indent - The number of spaces to indent the object
234
+ * @param {ColorFormat} colorFormat - The color format to use
235
+ * @returns {string} The colored visualization of the JSON object
156
236
  */
157
- export function printJSONStructure(obj, indent = 0) {
237
+ export function printJSONStructure(obj, indent = 0, colorFormat: 'html' | 'ansi' = 'ansi') {
238
+ const colors = getColors(colorFormat);
158
239
  const pad = " ".repeat(indent);
159
-
240
+ var result = "";
160
241
  // Handle primitive values and null
161
242
  if (typeof obj !== "object" || obj === null) {
162
243
  const color = getColorForType(obj);
163
244
  return color + getTypeString(obj) + colors.reset;
164
245
  }
165
-
166
246
  // Handle arrays with special bracket formatting
167
247
  if (Array.isArray(obj)) {
168
- let result = colors.blue + "[" + colors.reset;
248
+ result = colors.blue + "[" + colors.reset;
169
249
  if (obj.length) result += "\n";
250
+ // if array has items all of the same type or object types, print only once
251
+ if (obj.every((item) => typeof item === typeof obj[0])) {
252
+ result += pad + " " + printJSONStructure(obj[0], indent + 1);
253
+ result += ",";
254
+ result += "\n";
255
+ } else {
170
256
  obj.forEach((item, idx) => {
171
257
  result += pad + " " + printJSONStructure(item, indent + 1);
172
258
  if (idx < obj.length - 1) result += ",";
@@ -174,10 +260,11 @@ export function printJSONStructure(obj, indent = 0) {
174
260
  });
175
261
  result += pad + colors.blue + "]" + colors.reset;
176
262
  return result;
263
+ }
177
264
  }
178
265
 
179
266
  // Handle objects with special brace and property formatting
180
- let result = colors.green + "{" + colors.reset;
267
+ result = colors.green + "{" + colors.reset;
181
268
  const keys = Object.keys(obj);
182
269
  if (keys.length) result += "\n";
183
270
  keys.forEach((key, index) => {
@@ -219,91 +306,3 @@ export function printJSONStructure(obj, indent = 0) {
219
306
  return result;
220
307
  }
221
308
 
222
- /**
223
- * Shows message in a modal overlay with scrollable message stack
224
- * and is easier to dismiss unlike alert() which blocks window.
225
- * Creates a semi-transparent overlay with a white box containing the message.
226
- * @param {string} msg - The message to display
227
- */
228
- export function showAlert(msg) {
229
- if (typeof document === "undefined") return;
230
- let o = document.getElementById("alert-overlay"),
231
- list;
232
-
233
- // Create overlay and alert box if they don't exist
234
- if (!o) {
235
- o = document.body.appendChild(document.createElement("div"));
236
- o.id = "alert-overlay";
237
- o.setAttribute(
238
- "style",
239
- "position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center"
240
- );
241
- o.innerHTML = `<div id="alert-box" style="background:#fff;padding:1.5em 2em;border-radius:8px;box-shadow:0 2px 16px #0003;min-width:220px;max-height:80vh;position:relative;display:flex;flex-direction:column;">
242
- <button id="close-alert" style="position:absolute;top:12px;right:20px;font-size:1.5em;background:none;border:none;cursor:pointer;color:black;">&times;</button>
243
- <div id="alert-list" style="overflow:auto;flex:1;"></div>
244
- </div>`;
245
-
246
- // Add click handlers to close overlay
247
- o.addEventListener("click", (e) => e.target == o && o.remove());
248
- document.getElementById("close-alert").onclick = () => o.remove();
249
- }
250
-
251
- list = o.querySelector("#alert-list");
252
-
253
- // Add new message to list
254
- list.innerHTML += `<div style="border-bottom:1px solid #333; font-size:1.2em;margin:0.5em 0;">${msg}</div>`;
255
- }
256
-
257
- /**
258
- * Sets up development tools for debugging API requests
259
- * Adds a keyboard shortcut (Ctrl+I) that shows a modal with request history
260
- * Each request entry shows:
261
- * - Request path
262
- * - Request details
263
- * - Response data
264
- * - Timestamp
265
- */
266
- export function setupDevTools() {
267
- // Keyboard shortcut (Ctrl+I) to toggle debug view
268
- document.addEventListener("keydown", (e) => {
269
- if (e.key === "i" && e.ctrlKey) {
270
- // Create HTML of the grab.log requests
271
- let html = " ";
272
- for (let request of grab.log) {
273
- html += `<div style="margin-bottom:1em; border-bottom:1px solid #ccc; padding-bottom:1em;">
274
- <b>Path:</b> ${request.path}<br>
275
- <b>Request:</b> ${request.request}<br>
276
- <b>Response:</b> ${JSON.stringify(request.response, null, 2)}<br>
277
- <b>Time:</b> ${new Date(request.lastFetchTime).toLocaleString()}
278
- </div>`;
279
- }
280
- showAlert(html);
281
- }
282
- });
283
- }
284
-
285
- /**
286
- * Displays an animated spinner in the terminal with the provided text.
287
- * The spinner animates in-place until the returned function is called,
288
- * which stops the spinner and prints a success message.
289
- * @param {string} text - The text to display next to the spinner animation.
290
- * @returns {(success?: string) => void} Stop function with optional message.
291
- * @example
292
- * const stopSpinner = showSpinnerInTerminal('Downloading...');
293
- * setTimeout(() => {
294
- * stopSpinner('Success!');
295
- * }, 2000);
296
- */
297
- export function showSpinnerInTerminal(text) {
298
- let i = 0,
299
- interval = setInterval(() => {
300
- process.stdout.write(
301
- "\r" + "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏".split("")[(i = ++i % 10)] + " " + text
302
- );
303
- }, 50);
304
-
305
- return function (success = "✔ Done!") {
306
- clearInterval(interval);
307
- process.stdout.write("\r" + success + " ".repeat(text.length) + "\n");
308
- };
309
- }