grab-url 0.9.142 → 1.0.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.
Files changed (34) hide show
  1. package/dist/grab-api.cjs.js +1 -1
  2. package/dist/grab-api.cjs.js.map +1 -1
  3. package/dist/grab-api.d.ts +5 -5
  4. package/dist/grab-api.es.js.map +1 -1
  5. package/dist/icons.cjs.js +1 -1
  6. package/dist/icons.cjs.js.map +1 -1
  7. package/dist/icons.d.ts +0 -234
  8. package/dist/icons.es.js +1 -113
  9. package/dist/icons.es.js.map +1 -1
  10. package/package.json +12 -13
  11. package/readme.md +12 -18
  12. package/src/grab-api.ts +5 -5
  13. package/src/{grab-url-cli.js → grab-url.ts} +380 -354
  14. package/src/{spinners.json → icons/cli/spinners.json} +1 -482
  15. package/src/icons/index.ts +0 -255
  16. package/src/icons/svg/index.ts +313 -0
  17. package/src/grab-cli.js +0 -316
  18. /package/src/icons/{loading-bouncy-ball.svg → svg/loading-bouncy-ball.svg} +0 -0
  19. /package/src/icons/{loading-double-ring.svg → svg/loading-double-ring.svg} +0 -0
  20. /package/src/icons/{loading-eclipse.svg → svg/loading-eclipse.svg} +0 -0
  21. /package/src/icons/{loading-ellipsis.svg → svg/loading-ellipsis.svg} +0 -0
  22. /package/src/icons/{loading-floating-search.svg → svg/loading-floating-search.svg} +0 -0
  23. /package/src/icons/{loading-gears.svg → svg/loading-gears.svg} +0 -0
  24. /package/src/icons/{loading-infinity.svg → svg/loading-infinity.svg} +0 -0
  25. /package/src/icons/{loading-orbital.svg → svg/loading-orbital.svg} +0 -0
  26. /package/src/icons/{loading-pacman.svg → svg/loading-pacman.svg} +0 -0
  27. /package/src/icons/{loading-pulse-bars.svg → svg/loading-pulse-bars.svg} +0 -0
  28. /package/src/icons/{loading-red-blue-ball.svg → svg/loading-red-blue-ball.svg} +0 -0
  29. /package/src/icons/{loading-reload-arrow.svg → svg/loading-reload-arrow.svg} +0 -0
  30. /package/src/icons/{loading-ring.svg → svg/loading-ring.svg} +0 -0
  31. /package/src/icons/{loading-ripple.svg → svg/loading-ripple.svg} +0 -0
  32. /package/src/icons/{loading-spinner-oval.svg → svg/loading-spinner-oval.svg} +0 -0
  33. /package/src/icons/{loading-spinner.svg → svg/loading-spinner.svg} +0 -0
  34. /package/src/icons/{loading-square-blocks.svg → svg/loading-square-blocks.svg} +0 -0
package/src/grab-cli.js DELETED
@@ -1,316 +0,0 @@
1
- #!/usr/bin/env node
2
- import { grab, log } from './grab-api';
3
- import fs from 'fs';
4
- import path from 'path';
5
-
6
- // Lightweight argument parser polyfill
7
- class ArgParser {
8
- constructor() {
9
- this.commands = {};
10
- this.options = {};
11
- this.examples = [];
12
- this.helpText = '';
13
- this.versionText = '1.0.0';
14
- }
15
-
16
- usage(text) {
17
- this.helpText = text;
18
- return this;
19
- }
20
-
21
- command(pattern, desc, handler) {
22
- const match = pattern.match(/\$0 <(\w+)>/);
23
- if (match) {
24
- this.commands[match[1]] = { desc, handler, required: true };
25
- }
26
- return this;
27
- }
28
-
29
- option(name, opts = {}) {
30
- this.options[name] = opts;
31
- return this;
32
- }
33
-
34
- example(cmd, desc) {
35
- this.examples.push({ cmd, desc });
36
- return this;
37
- }
38
-
39
- help() {
40
- return this;
41
- }
42
-
43
- alias(short, long) {
44
- if (this.options[long]) {
45
- this.options[long].alias = short;
46
- }
47
- return this;
48
- }
49
-
50
- version(v) {
51
- if (v) this.versionText = v;
52
- return this;
53
- }
54
-
55
- strict() {
56
- return this;
57
- }
58
-
59
- parseSync() {
60
- const args = process.argv.slice(2);
61
- const result = {};
62
- const positional = [];
63
-
64
- // Check for help
65
- if (args.includes('--help') || args.includes('-h')) {
66
- this.showHelp();
67
- process.exit(0);
68
- }
69
-
70
- // Check for version
71
- if (args.includes('--version')) {
72
- console.log(this.versionText);
73
- process.exit(0);
74
- }
75
-
76
- for (let i = 0; i < args.length; i++) {
77
- const arg = args[i];
78
-
79
- if (arg.startsWith('--')) {
80
- const [key, value] = arg.split('=');
81
- const optName = key.slice(2);
82
-
83
- if (value !== undefined) {
84
- result[optName] = this.coerceValue(optName, value);
85
- } else if (this.options[optName]?.type === 'boolean') {
86
- result[optName] = true;
87
- } else {
88
- const nextArg = args[i + 1];
89
- if (nextArg && !nextArg.startsWith('-')) {
90
- result[optName] = this.coerceValue(optName, nextArg);
91
- i++; // Skip next arg
92
- } else {
93
- result[optName] = true;
94
- }
95
- }
96
- } else if (arg.startsWith('-') && arg.length === 2) {
97
- const shortFlag = arg[1];
98
- const longName = this.findLongName(shortFlag);
99
-
100
- if (longName) {
101
- if (this.options[longName]?.type === 'boolean') {
102
- result[longName] = true;
103
- } else {
104
- const nextArg = args[i + 1];
105
- if (nextArg && !nextArg.startsWith('-')) {
106
- result[longName] = this.coerceValue(longName, nextArg);
107
- i++;
108
- }
109
- }
110
- }
111
- } else {
112
- positional.push(arg);
113
- }
114
- }
115
-
116
- // Handle positional arguments
117
- if (positional.length > 0) {
118
- result.url = positional[0];
119
- }
120
-
121
- // Set defaults
122
- Object.keys(this.options).forEach(key => {
123
- if (result[key] === undefined && this.options[key].default !== undefined) {
124
- result[key] = this.options[key].default;
125
- }
126
- });
127
-
128
- // Validate required positional args
129
- if (!result.url && this.commands.url?.required) {
130
- console.error('Error: Missing required argument: url');
131
- this.showHelp();
132
- process.exit(1);
133
- }
134
-
135
- return result;
136
- }
137
-
138
- coerceValue(optName, value) {
139
- const opt = this.options[optName];
140
- if (!opt) return value;
141
-
142
- if (opt.coerce) {
143
- return opt.coerce(value);
144
- }
145
-
146
- switch (opt.type) {
147
- case 'number':
148
- return Number(value);
149
- case 'boolean':
150
- return value === 'true' || value === '1';
151
- default:
152
- return value;
153
- }
154
- }
155
-
156
- findLongName(shortFlag) {
157
- return Object.keys(this.options).find(key =>
158
- this.options[key].alias === shortFlag
159
- );
160
- }
161
-
162
- showHelp() {
163
- console.log(this.helpText || 'Usage: grab <url> [options]');
164
- console.log('\nPositional arguments:');
165
- Object.keys(this.commands).forEach(cmd => {
166
- console.log(` ${cmd.padEnd(20)} ${this.commands[cmd].desc}`);
167
- });
168
-
169
- console.log('\nOptions:');
170
- Object.keys(this.options).forEach(key => {
171
- const opt = this.options[key];
172
- const flags = opt.alias ? `-${opt.alias}, --${key}` : `--${key}`;
173
- console.log(` ${flags.padEnd(20)} ${opt.describe || ''}`);
174
- });
175
-
176
- if (this.examples.length > 0) {
177
- console.log('\nExamples:');
178
- this.examples.forEach(ex => {
179
- console.log(` ${ex.cmd}`);
180
- console.log(` ${ex.desc}`);
181
- });
182
- }
183
- }
184
- }
185
-
186
- // Create parser instance
187
- const createParser = () => new ArgParser();
188
-
189
- // Parse arguments with custom parser
190
- const argv = createParser()
191
- .usage('Usage: grab <url> [options]')
192
- .command('$0 <url>', 'Fetch data from API endpoint')
193
- .option('x', {
194
- alias: 'exec',
195
- type: 'boolean',
196
- default: false,
197
- describe: 'Execute flag (functionality TBD)'
198
- })
199
- .option('no-save', {
200
- type: 'boolean',
201
- default: false,
202
- describe: 'Don\'t save output to file, just print to console'
203
- })
204
- .option('output', {
205
- alias: 'o',
206
- type: 'string',
207
- describe: 'Output filename (default: output.json)',
208
- default: null
209
- })
210
- .option('params', {
211
- alias: 'p',
212
- type: 'string',
213
- describe: 'JSON string of query parameters (e.g., \'{"key":"value"}\')',
214
- coerce: (arg) => {
215
- if (!arg) return {};
216
- try {
217
- return JSON.parse(arg);
218
- } catch (e) {
219
- throw new Error(`Invalid JSON in params: ${arg}`);
220
- }
221
- }
222
- })
223
- .example('grab https://api.example.com/data', 'Fetch data and save to output.json')
224
- .example('grab https://api.example.com/data --no-save', 'Fetch data and print to console')
225
- .example('grab https://api.example.com/data -o result.json', 'Save output to result.json')
226
- .example('grab https://api.example.com/data -p \'{"limit":10,"page":1}\'', 'Pass query parameters')
227
- .help()
228
- .alias('h', 'help')
229
- .version()
230
- .strict()
231
- .parseSync();
232
-
233
- // Extract values from parsed arguments
234
- const { url, x: execFlag, 'no-save': noSave, output: outputFile, params = {} } = argv;
235
- // params.debug = true;
236
-
237
- // Validate URL
238
- if (!url || (!url.startsWith('http://') && !url.startsWith('https://'))) {
239
- log('Error: URL must start with http:// or https://');
240
- process.exit(1);
241
- }
242
-
243
- (async () => {
244
- const startTime = process.hrtime(); // High-res timer
245
-
246
- try {
247
- const res = await grab(url, params);
248
-
249
- if (res.error)
250
- log(`\n\nStatus: ❌ ${res.error}`);
251
-
252
-
253
- let filePath = null;
254
- let outputData;
255
- let isTextData = false;
256
-
257
- // Determine data type and prepare output
258
- if (typeof res.data === 'string') {
259
- outputData = res.data;
260
- isTextData = true;
261
- } else if (Buffer.isBuffer(res.data) || res.data instanceof Uint8Array) {
262
- // Binary data (like video files)
263
- outputData = res.data;
264
- isTextData = false;
265
- } else if (res.data instanceof Blob) {
266
- // Convert Blob to Buffer for file writing
267
- const arrayBuffer = await res.data.arrayBuffer();
268
- outputData = Buffer.from(arrayBuffer);
269
- isTextData = false;
270
- } else if (res.data && typeof res.data === 'object') {
271
- // JSON or other objects
272
- outputData = JSON.stringify(res.data, null, 2);
273
- isTextData = true;
274
- } else {
275
- // Fallback - try to stringify
276
- outputData = String(res.data);
277
- isTextData = true;
278
- }
279
-
280
- if (!noSave) {
281
- // Determine file extension based on URL or data type
282
- const urlPath = new URL(url).pathname;
283
- const urlExt = path.extname(urlPath);
284
- const defaultExt = isTextData ? '.json' : (urlExt || '.bin');
285
-
286
- filePath = outputFile
287
- ? path.resolve(outputFile)
288
- : path.resolve(process.cwd(), `output${defaultExt}`);
289
-
290
- // Write file with appropriate encoding
291
- if (isTextData) {
292
- fs.writeFileSync(filePath, outputData, 'utf8');
293
- } else {
294
- fs.writeFileSync(filePath, outputData);
295
- }
296
-
297
- // Calculate elapsed time
298
- const [seconds, nanoseconds] = process.hrtime(startTime);
299
- const elapsedMs = (seconds + nanoseconds / 1e9).toFixed(2);
300
- const stats = fs.statSync(filePath);
301
- const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(1);
302
-
303
- log(`⏱️ ${elapsedMs}s 📦 ${fileSizeMB}MB ✅ Saved to: ${filePath}`);
304
- } else {
305
- // For console output, only show text data
306
- if (isTextData) {
307
- // log(outputData);
308
- } else {
309
- log(`Binary data received (${outputData.length} bytes). Use --output to save to file.`);
310
- }
311
- }
312
- } catch (error) {
313
- log(`Error: ${error.message}`, {color: 'red'});
314
- process.exit(1);
315
- }
316
- })();